authentik.root.test_plugin

 1import math
 2from os import environ
 3from ssl import OPENSSL_VERSION
 4from time import monotonic
 5from typing import TextIO
 6
 7import pytest
 8from cryptography.hazmat.backends.openssl.backend import backend
 9from pytest import Config, Item, TerminalReporter
10
11from authentik import authentik_full_version
12from tests.decorators import get_local_ip
13
14IS_CI = "CI" in environ
15
16
17@pytest.hookimpl(hookwrapper=True)
18def pytest_sessionstart(*_, **__):
19    """Clear the console ahead of the pytest output starting"""
20    if not IS_CI:
21        print("\x1b[2J\x1b[H")
22    # Pre-warm cryptography's PyO3 PyDateTime type cache with the real
23    # datetime class. If the first extraction happens under @freeze_time
24    # (e.g. in MTLSStageTests), PyO3 caches freezegun's FakeDatetime,
25    # which breaks every later test that passes a real datetime into
26    # cryptography ("TypeError: 'datetime' object is not an instance
27    # of 'FakeDatetime'"). The discard is intentional — only side
28    # effect needed is the type-cache initialization.
29    from datetime import UTC, datetime
30
31    from cryptography.x509.verification import PolicyBuilder
32
33    PolicyBuilder().time(datetime.now(tz=UTC))
34    yield
35
36
37@pytest.hookimpl(trylast=True)
38def pytest_report_header(*_, **__):
39    """Add authentik version to pytest output"""
40    return [
41        f"authentik version: {authentik_full_version()}",
42        f"OpenSSL version: {OPENSSL_VERSION}, FIPS: {backend._fips_enabled}",
43        f"Local IP: {get_local_ip()} (Detected as {get_local_ip(override=False)})",
44    ]
45
46
47def pytest_collection_modifyitems(config: Config, items: list[Item]) -> None:
48    current_id = int(environ.get("CI_RUN_ID", "0")) - 1
49    total_ids = int(environ.get("CI_TOTAL_RUNS", "0"))
50
51    if total_ids:
52        num_tests = len(items)
53        matrix_size = math.ceil(num_tests / total_ids)
54
55        start = current_id * matrix_size
56        end = (current_id + 1) * matrix_size
57
58        deselected_items = items[:start] + items[end:]
59        config.hook.pytest_deselected(items=deselected_items)
60        items[:] = items[start:end]
61        print(f" Executing {start} - {end} tests")
62
63
64@pytest.hookimpl(trylast=True)
65def pytest_configure(config: Config):
66    # Replace the default terminal reporter
67    reporter = config.pluginmanager.get_plugin("terminalreporter")
68    if reporter:
69        config.pluginmanager.unregister(reporter)
70        config.pluginmanager.register(
71            RelativeTimeTerminalReporter(config),
72            "terminalreporter",
73        )
74
75
76class RelativeTimeTerminalReporter(TerminalReporter):
77    _start_time: None | float
78
79    def __init__(self, config: Config, file: TextIO | None = None):
80        super().__init__(config, file)
81        self._start_time = None
82
83    def pytest_runtest_logstart(self, nodeid, location):
84        # Set start time on the first test
85        if self._start_time is None:
86            self._start_time = monotonic()
87        super().pytest_runtest_logstart(nodeid, location)
88
89    def _locationline(self, nodeid, fspath, lineno, domain):
90        original = super()._locationline(nodeid, fspath, lineno, domain)
91        if self._start_time is None:
92            return original
93        elapsed = monotonic() - self._start_time
94        minutes, seconds = divmod(elapsed, 60)
95        timestamp = f"{int(minutes):02d}:{seconds:06.3f}"
96        return f"[+{timestamp}] {original}"
IS_CI = True
@pytest.hookimpl(hookwrapper=True)
def pytest_sessionstart(*_, **__):
18@pytest.hookimpl(hookwrapper=True)
19def pytest_sessionstart(*_, **__):
20    """Clear the console ahead of the pytest output starting"""
21    if not IS_CI:
22        print("\x1b[2J\x1b[H")
23    # Pre-warm cryptography's PyO3 PyDateTime type cache with the real
24    # datetime class. If the first extraction happens under @freeze_time
25    # (e.g. in MTLSStageTests), PyO3 caches freezegun's FakeDatetime,
26    # which breaks every later test that passes a real datetime into
27    # cryptography ("TypeError: 'datetime' object is not an instance
28    # of 'FakeDatetime'"). The discard is intentional — only side
29    # effect needed is the type-cache initialization.
30    from datetime import UTC, datetime
31
32    from cryptography.x509.verification import PolicyBuilder
33
34    PolicyBuilder().time(datetime.now(tz=UTC))
35    yield

Clear the console ahead of the pytest output starting

@pytest.hookimpl(trylast=True)
def pytest_report_header(*_, **__):
38@pytest.hookimpl(trylast=True)
39def pytest_report_header(*_, **__):
40    """Add authentik version to pytest output"""
41    return [
42        f"authentik version: {authentik_full_version()}",
43        f"OpenSSL version: {OPENSSL_VERSION}, FIPS: {backend._fips_enabled}",
44        f"Local IP: {get_local_ip()} (Detected as {get_local_ip(override=False)})",
45    ]

Add authentik version to pytest output

def pytest_collection_modifyitems(config: _pytest.config.Config, items: list[_pytest.nodes.Item]) -> None:
48def pytest_collection_modifyitems(config: Config, items: list[Item]) -> None:
49    current_id = int(environ.get("CI_RUN_ID", "0")) - 1
50    total_ids = int(environ.get("CI_TOTAL_RUNS", "0"))
51
52    if total_ids:
53        num_tests = len(items)
54        matrix_size = math.ceil(num_tests / total_ids)
55
56        start = current_id * matrix_size
57        end = (current_id + 1) * matrix_size
58
59        deselected_items = items[:start] + items[end:]
60        config.hook.pytest_deselected(items=deselected_items)
61        items[:] = items[start:end]
62        print(f" Executing {start} - {end} tests")
@pytest.hookimpl(trylast=True)
def pytest_configure(config: _pytest.config.Config):
65@pytest.hookimpl(trylast=True)
66def pytest_configure(config: Config):
67    # Replace the default terminal reporter
68    reporter = config.pluginmanager.get_plugin("terminalreporter")
69    if reporter:
70        config.pluginmanager.unregister(reporter)
71        config.pluginmanager.register(
72            RelativeTimeTerminalReporter(config),
73            "terminalreporter",
74        )
class RelativeTimeTerminalReporter(_pytest.terminal.TerminalReporter):
77class RelativeTimeTerminalReporter(TerminalReporter):
78    _start_time: None | float
79
80    def __init__(self, config: Config, file: TextIO | None = None):
81        super().__init__(config, file)
82        self._start_time = None
83
84    def pytest_runtest_logstart(self, nodeid, location):
85        # Set start time on the first test
86        if self._start_time is None:
87            self._start_time = monotonic()
88        super().pytest_runtest_logstart(nodeid, location)
89
90    def _locationline(self, nodeid, fspath, lineno, domain):
91        original = super()._locationline(nodeid, fspath, lineno, domain)
92        if self._start_time is None:
93            return original
94        elapsed = monotonic() - self._start_time
95        minutes, seconds = divmod(elapsed, 60)
96        timestamp = f"{int(minutes):02d}:{seconds:06.3f}"
97        return f"[+{timestamp}] {original}"
RelativeTimeTerminalReporter(config: _pytest.config.Config, file: TextIO | None = None)
80    def __init__(self, config: Config, file: TextIO | None = None):
81        super().__init__(config, file)
82        self._start_time = None
def pytest_runtest_logstart(self, nodeid, location):
84    def pytest_runtest_logstart(self, nodeid, location):
85        # Set start time on the first test
86        if self._start_time is None:
87            self._start_time = monotonic()
88        super().pytest_runtest_logstart(nodeid, location)