#!/usr/bin/env python
"""End-to-end tests for the API.

API plugins are tested with their own dedicated unit-tests that are
protocol- and server-independent. End-to-end tests are meant to use
the full GRR server stack (web server + API client library).
"""

import logging
from unittest import mock

import portpicker

from grr_api_client import api as grr_api
from grr_api_client import utils as grr_api_utils
from grr_response_core.lib import utils
from grr_response_server.flows.general import registry_init  # pylint: disable=unused-import
from grr_response_server.gui import api_auth_manager
from grr_response_server.gui import api_call_context
from grr_response_server.gui import api_call_router_registry
from grr_response_server.gui import api_call_router_without_checks
from grr_response_server.gui import webauth
from grr_response_server.gui import wsgiapp_testlib
from grr_response_server.gui.root import api_root_router
from grr.test_lib import acl_test_lib
from grr.test_lib import test_lib


class ApiIntegrationTest(test_lib.GRRBaseTest, acl_test_lib.AclTestMixin):
  """Base class for all API E2E tests."""

  def setUp(self):
    super().setUp()

    api_auth_manager.InitializeApiAuthManager()
    self.context = api_call_context.ApiCallContext("api_test_robot_user")
    self.test_username = self.context.username
    try:
      webauth.WEBAUTH_MANAGER.SetUserName(self.context.username)
    except AttributeError:
      # Only the NullWebAuthManager supports SetUserName
      pass

    self.CreateUser(self.context.username)

    self.port = ApiIntegrationTest.server_port
    self.endpoint = "http://localhost:%s" % self.port
    self.api = grr_api.InitHttp(api_endpoint=self.endpoint)

    poll_stubber = utils.MultiStubber(
        (grr_api_utils, "DEFAULT_POLL_INTERVAL", 0.1),
        (grr_api_utils, "DEFAULT_POLL_TIMEOUT", 10),
    )
    poll_stubber.Start()
    self.addCleanup(poll_stubber.Stop)

  _api_set_up_done = False
  _server_trd = None
  server_port = None

  # NOTE: there's no corresponding tearDownClass method for the logic
  # below, since we want to have one SSL server thread not per test
  # suite, but per process. It effectively has to survive set up/tear down
  # of all the test suites inheriting from ApiIntegrationTest.
  @classmethod
  def setUpClass(cls):
    super(ApiIntegrationTest, cls).setUpClass()
    if ApiIntegrationTest._api_set_up_done:
      return

    # Set up HTTP server
    port = portpicker.pick_unused_port()
    ApiIntegrationTest.server_port = port
    logging.info("Picked free AdminUI port for HTTP %d.", port)

    ApiIntegrationTest._server_trd = wsgiapp_testlib.ServerThread(
        port, name="api_integration_server"
    )
    ApiIntegrationTest._server_trd.StartAndWaitUntilServing()

    ApiIntegrationTest._api_set_up_done = True


class RootApiBinaryManagementTestRouter(
    api_root_router.ApiRootRouter,
    api_call_router_without_checks.ApiCallRouterWithoutChecks,
):
  """Root router combined with an unrestricted router for easier testing."""


class RootApiIntegrationTest(ApiIntegrationTest):
  """Base class for tests dealing with root API calls."""

  def setUp(self):
    super().setUp()

    default_router = RootApiBinaryManagementTestRouter
    root_api_config_overrider = test_lib.ConfigOverrider(
        {"API.DefaultRouter": default_router.__name__}
    )
    root_api_config_overrider.Start()
    self.addCleanup(root_api_config_overrider.Stop)

    patcher = mock.patch.object(
        api_call_router_registry, "_API_CALL_ROUTER_REGISTRY", {}
    )
    patcher.start()
    self.addCleanup(patcher.stop)
    api_call_router_registry.RegisterApiCallRouter(
        "RootApiBinaryManagementTestRouter", RootApiBinaryManagementTestRouter
    )
    self.addCleanup(
        lambda: api_call_router_registry.UnregisterApiCallRouter(  # pylint:disable=g-long-lambda
            "RootApiBinaryManagementTestRouter"
        )
    )

    # Force creation of new APIAuthorizationManager, so that configuration
    # changes are picked up.
    api_auth_manager.InitializeApiAuthManager()
