diff --git a/features/steps/tdd_di_audit_cache_failure_steps.py b/features/steps/tdd_di_audit_cache_failure_steps.py new file mode 100644 index 000000000..9b0f907d3 --- /dev/null +++ b/features/steps/tdd_di_audit_cache_failure_steps.py @@ -0,0 +1,70 @@ +"""Step definitions for tdd_di_audit_cache_failure.feature. + +This test captures bug #992: ``get_container()`` caches a container even when +eager audit subscriber initialization fails once during startup, and it does +not retry on subsequent ``get_container()`` calls. + +The scenario is tagged ``@tdd_expected_fail`` so the expected assertion failure +(which proves the bug still exists) is inverted to a CI pass until the fix for +#992 lands. After the fix, remove ``@tdd_expected_fail``. +""" + +from __future__ import annotations + +from typing import Any + +from behave import given, then, when +from behave.runner import Context + + +@given("g992- a container whose audit subscriber init fails once then succeeds") +def step_given_container_with_transient_audit_init_failure(context: Context) -> None: + """Patch container factory so audit subscriber fails once, then succeeds.""" + from cleveragents.application import container as container_module + + class FakeContainer: + """Minimal fake container to track audit subscriber initialization calls.""" + + def __init__(self) -> None: + self.audit_init_attempts = 0 + + def audit_event_subscriber(self) -> object: + """Fail first call (simulating DB not ready), then succeed.""" + self.audit_init_attempts += 1 + if self.audit_init_attempts == 1: + raise RuntimeError("database not ready yet") + return object() + + context.g992_original_container_class = container_module.Container + container_module.Container = FakeContainer + + def _cleanup() -> None: + container_module.Container = context.g992_original_container_class + container_module.reset_container() + + context.add_cleanup(_cleanup) + container_module.reset_container() + + +@when("g992- I call get_container twice") +def step_when_call_get_container_twice(context: Context) -> None: + """Call get_container twice and keep references for assertions.""" + from cleveragents.application.container import get_container + + context.g992_first_container = get_container() + context.g992_second_container = get_container() + + +@then("g992- audit subscriber init should have been attempted twice") +def step_then_audit_subscriber_should_be_retried(context: Context) -> None: + """Expect retry on second access after first initialization failure.""" + first = context.g992_first_container + second = context.g992_second_container + assert first is second, "Expected get_container() to return a singleton instance" + + attempts: Any = getattr(first, "audit_init_attempts", None) + assert attempts == 2, ( + "Bug #992: get_container() did not retry audit subscriber initialization " + "after the first failure. Expected 2 attempts after two get_container() " + f"calls, but observed {attempts}." + ) diff --git a/features/tdd_di_audit_cache_failure.feature b/features/tdd_di_audit_cache_failure.feature new file mode 100644 index 000000000..879812552 --- /dev/null +++ b/features/tdd_di_audit_cache_failure.feature @@ -0,0 +1,21 @@ +@tdd_expected_fail @tdd_issue @tdd_issue_992 +Feature: TDD Bug #992 — get_container() caches failed audit subscriber initialization + As a long-lived process using the shared DI container + I want get_container() to retry audit subscriber initialization after an initial failure + So that audit subscriptions are eventually registered once prerequisites become available + + This feature captures bug #992. Today, get_container() eagerly calls + audit_event_subscriber() only during first container creation. If that first call + fails, the exception is logged and swallowed, but the global _container remains set. + Subsequent get_container() calls return the cached container without retrying the + subscriber initialization. + + This scenario is intentionally tagged with @tdd_expected_fail until bug #992 is + fixed. The underlying assertion fails (proving the bug exists) and the tag inversion + mechanism converts that failure to a CI pass. Remove @tdd_expected_fail when #992 is + fixed so this runs as a normal regression test. + + Scenario: Bug #992 — get_container retries audit subscriber initialization after first failure + Given g992- a container whose audit subscriber init fails once then succeeds + When g992- I call get_container twice + Then g992- audit subscriber init should have been attempted twice