Exemple #1
0
    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()
Exemple #2
0
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
Exemple #3
0
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
Exemple #4
0
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()
Exemple #5
0
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
Exemple #6
0
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
Exemple #7
0
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
Exemple #8
0
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
Exemple #9
0
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
Exemple #10
0
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
Exemple #11
0
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",
    }
Exemple #12
0
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()
Exemple #13
0
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()
Exemple #14
0
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")
Exemple #15
0
def init(settings: str) -> None:
    """Initialize the database storage."""
    config_dependency.set_settings_path(settings)
    config = config_dependency()
    initialize_database(config)