def configure( self, template: str = "github", *, oidc_clients: Optional[List[OIDCClient]] = None, **settings: str, ) -> None: """Change the test application configuration. This cannot be used to change the database URL because the internal session is not recreated. Parameters ---------- template : `str` Settings template to use. oidc_clients : List[`gafaelfawr.config.OIDCClient`] or `None` Configuration information for clients of the OpenID Connect server. **settings : str Any additional settings to add to the settings file. """ settings_path = build_settings( self.tmp_path, template, oidc_clients, **settings, ) config_dependency.set_settings_path(str(settings_path)) self.config = config_dependency()
def initialize(tmp_path: Path) -> Config: """Do basic initialization and return a configuration. This shared logic can be used either with `SetupTest`, which assumes an ASGI application and an async test, or with non-async tests such as the tests of the command-line interface. Parameters ---------- tmp_path : `pathlib.Path` The path for temporary files. Returns ------- config : `gafaelfawr.config.Config` The generated config, using the same defaults as `SetupTest`. """ settings_path = build_settings(tmp_path, "github") config_dependency.set_settings_path(str(settings_path)) config = config_dependency() if not os.environ.get("REDIS_6379_TCP_PORT"): redis_dependency.is_mocked = True # Initialize the database. Non-SQLite databases need to be reset between # tests. should_reset = not urlparse(config.database_url).scheme == "sqlite" initialize_database(config, reset=should_reset) return config
async def selenium_config( tmp_path: Path, driver: webdriver.Chrome, empty_database: None ) -> AsyncIterator[SeleniumConfig]: """Start a server for Selenium tests. The server will be automatically stopped at the end of the test. The Selenium web driver will be automatically configured with a valid authentication token in a cookie. Returns ------- config : `tests.support.selenium.SeleniumConfig` Configuration information for the server. """ settings_path = build_settings(tmp_path, "selenium") config_dependency.set_settings_path(str(settings_path)) async with run_app(tmp_path, settings_path) as config: cookie = await State(token=config.token).as_cookie() driver.header_overrides = {"Cookie": f"{COOKIE_NAME}={cookie}"} # The synthetic cookie doesn't have a CSRF token, so we want to # replace it with a real cookie. Do this by visiting the top-level # page of the UI and waiting for the token list to appear, which will # trigger fleshing out the state, and then dropping the header # override for subsequent calls so that the cookie set in the browser # will be used. driver.get(urljoin(config.url, "/auth/tokens/")) tokens_page = TokensPage(driver) tokens_page.get_tokens(TokenType.session) del driver.header_overrides yield config
async def run_app(tmp_path: Path, settings_path: Path) -> AsyncIterator[SeleniumConfig]: """Run the application as a separate process for Selenium access. Parameters ---------- tmp_path : `pathlib.Path` The temporary directory for testing. settings_path : `pathlib.Path` The path to the settings file. """ config_dependency.set_settings_path(str(settings_path)) config = await config_dependency() token_path = tmp_path / "token" # Create the socket that the app will listen on. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", 0)) port = s.getsockname()[1] # Spawn the app in a separate process using uvicorn. cmd = [ "uvicorn", "--fd", "0", "--factory", "tests.support.selenium:create_app", ] logging.info("Starting server with command %s", " ".join(cmd)) p = subprocess.Popen( cmd, cwd=str(tmp_path), stdin=s.fileno(), env={ **os.environ, "GAFAELFAWR_SETTINGS_PATH": str(settings_path), "GAFAELFAWR_TEST_TOKEN_PATH": str(token_path), "PYTHONPATH": os.getcwd(), }, ) s.close() logging.info("Waiting for server to start") _wait_for_server(port) try: selenium_config = SeleniumConfig( config=config, token=Token.from_str(token_path.read_text()), url=f"http://localhost:{port}", ) yield selenium_config finally: p.terminate()
def config(tmp_path: Path) -> Config: """Set up and return the default test configuration. Notes ----- This fixture must not be async so that it can be used by the cli tests, which must not be async because the Click support starts its own asyncio loop. """ settings_path = build_settings(tmp_path, "github") config_dependency.set_settings_path(str(settings_path)) assert config_dependency._config return config_dependency._config
def test_database_password(tmp_path: Path) -> None: settings_path = build_settings( tmp_path, "github", database_url="postgresql://gafaelfawr@localhost/gafaelfawr", ) os.environ["GAFAELFAWR_DATABASE_PASSWORD"] = "******" config_dependency.set_settings_path(str(settings_path)) config = config_dependency() del os.environ["GAFAELFAWR_DATABASE_PASSWORD"] expected = "postgresql://*****:*****@localhost/gafaelfawr" assert config.database_url == expected
async def test_redis_password(tmp_path: Path) -> None: redis_password_file = store_secret(tmp_path, "redis", b"some-password") settings_path = build_settings( tmp_path, "github", redis_password_file=str(redis_password_file)) config_dependency.set_settings_path(str(settings_path)) function = "gafaelfawr.dependencies.redis.create_redis_pool" with patch(function) as mock_create: redis_dependency.is_mocked = False await redis_dependency(config_dependency()) assert mock_create.call_args_list == [ call("redis://localhost:6379/0", password="******") ] redis_dependency.redis = None
def selenium_config(tmp_path: Path) -> Iterable[SeleniumConfig]: """Start a server for Selenium tests. The server will be automatically stopped at the end of the test. Returns ------- config : `tests.support.selenium.SeleniumConfig` Configuration information for the server. """ settings_path = build_settings(tmp_path, "selenium") config_dependency.set_settings_path(str(settings_path)) with run_app(tmp_path, settings_path) as config: yield config
async def test_redis_password(tmp_path: Path) -> None: redis_password_file = store_secret(tmp_path, "redis", b"some-password") settings_path = build_settings( tmp_path, "github", redis_password_file=str(redis_password_file) ) config_dependency.set_settings_path(str(settings_path)) with patch.object(Redis, "from_url") as mock_from_url: redis_dependency.redis = None config = await config_dependency() await redis_dependency(config) assert mock_from_url.call_args_list == [ call("redis://localhost:6379/0", password="******") ] redis_dependency.redis = None
async def mock_ldap(tmp_path: Path, config: Config) -> AsyncIterator[MockLDAP]: """Replace the bonsai LDAP API with a mock class. Returns ------- mock_ldap : `tests.support.ldap.MockLDAP` The mock LDAP API object. """ settings_path = build_settings(tmp_path, "oidc-ldap") config_dependency.set_settings_path(str(settings_path)) config = await config_dependency() assert config.ldap ldap = MockLDAP(config.ldap) with patch.object(bonsai, "LDAPClient") as mock: mock.return_value = ldap yield ldap
def test_update_service_tokens_no_config(tmp_path: Path, mock_kubernetes: MockCoreV1Api, caplog: LogCaptureFixture) -> None: initialize(tmp_path) settings_path = build_settings(tmp_path, "oidc") config_dependency.set_settings_path(str(settings_path)) caplog.clear() runner = CliRunner() result = runner.invoke(main, ["update-service-tokens"]) assert result.exit_code == 0 assert json.loads(caplog.record_tuples[0][2]) == { "event": "No Kubernetes secrets configured", "level": "info", "logger": "gafaelfawr", }
def run_app(tmp_path: Path, settings_path: Path) -> Iterator[SeleniumConfig]: """Run the application as a separate process for Selenium access. Parameters ---------- tmp_path : `pathlib.Path` The temporary directory for testing. settings_path : `pathlib.Path` The path to the settings file. """ config_dependency.set_settings_path(str(settings_path)) config = config_dependency() initialize_database(config) token_path = tmp_path / "token" app_source = APP_TEMPLATE.format( settings_path=str(settings_path), token_path=str(token_path), ) app_path = tmp_path / "testing.py" with app_path.open("w") as f: f.write(app_source) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", 0)) port = s.getsockname()[1] cmd = ["uvicorn", "--fd", "0", "testing:app"] logging.info("Starting server with command %s", " ".join(cmd)) p = subprocess.Popen(cmd, cwd=str(tmp_path), stdin=s.fileno()) s.close() logging.info("Waiting for server to start") _wait_for_server(port) try: selenium_config = SeleniumConfig( token=Token.from_str(token_path.read_text()), url=f"http://localhost:{port}", ) yield selenium_config finally: p.terminate()
async def configure( tmp_path: Path, template: str, *, oidc_clients: Optional[List[OIDCClient]] = None, **settings: str, ) -> Config: """Change the test application configuration. This cannot be used to change the database URL because sessions will not be recreated or the database reinitialized. Parameters ---------- tmp_path : `pathlib.Path` Root of the test temporary directory, used to write the settings file. template : `str` Settings template to use. oidc_clients : List[`gafaelfawr.config.OIDCClient`], optional Configuration information for clients of the OpenID Connect server. **settings : str, optional Any additional settings to add to the settings file. Returns ------- config : `gafaelfawr.config.Config` The new configuration. """ settings_path = build_settings( tmp_path, template, oidc_clients, **settings, ) config_dependency.set_settings_path(str(settings_path)) return await config_dependency()
import time from contextlib import contextmanager from dataclasses import dataclass from typing import TYPE_CHECKING from seleniumwire import webdriver from gafaelfawr.database import initialize_database from gafaelfawr.dependencies.config import config_dependency from gafaelfawr.models.token import Token if TYPE_CHECKING: from pathlib import Path from typing import Iterator APP_TEMPLATE = """ from unittest.mock import MagicMock import structlog from fastapi_sqlalchemy import db from gafaelfawr.dependencies.config import config_dependency from gafaelfawr.dependencies.redis import redis_dependency from gafaelfawr.factory import ComponentFactory from gafaelfawr.main import app from gafaelfawr.models.token import TokenUserInfo config_dependency.set_settings_path("{settings_path}") redis_dependency.is_mocked = True @app.on_event("startup")
def init(settings: str) -> None: """Initialize the database storage.""" config_dependency.set_settings_path(settings) config = config_dependency() initialize_database(config)