From c55ee7e68ddcf2dde957dd5e37bf3439e7d8226d Mon Sep 17 00:00:00 2001 From: "Brent E. Edwards" Date: Thu, 26 Mar 2026 10:03:40 +0000 Subject: [PATCH] =?UTF-8?q?test:=20add=20TDD=20bug-capture=20test=20for=20?= =?UTF-8?q?#992=20=E2=80=94=20DI=20container=20audit=20cache=20failure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a dedicated Behave bug-capture scenario that reproduces the get_container() audit subscriber initialization caching defect from #992. The steps patch container construction so audit subscriber initialization fails once then succeeds, call get_container() twice, and assert a retry should occur on the second access. The assertion intentionally fails against current behavior (only one initialization attempt), and the @tdd_expected_fail + @tdd_bug + @tdd_bug_992 tags invert that failure to a CI pass until the production fix lands. This creates a precise regression guard that will only pass without inversion when retry semantics are implemented. ISSUES CLOSED: #1096 --- .../steps/tdd_di_audit_cache_failure_steps.py | 70 +++++++++++++++++++ features/tdd_di_audit_cache_failure.feature | 21 ++++++ 2 files changed, 91 insertions(+) create mode 100644 features/steps/tdd_di_audit_cache_failure_steps.py create mode 100644 features/tdd_di_audit_cache_failure.feature 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