"""Step definitions for auth_middleware_events.feature.""" from __future__ import annotations from behave import given, then, when from behave.runner import Context from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from cleveragents.application.services.audit_event_subscriber import ( AuditEventSubscriber, ) from cleveragents.application.services.audit_service import AuditService from cleveragents.application.services.auth_middleware import TokenAuthMiddleware from cleveragents.config.settings import Settings from cleveragents.infrastructure.database.models import Base from cleveragents.infrastructure.events.reactive import ReactiveEventBus from cleveragents.infrastructure.events.types import EventType from features.mocks.recording_event_bus import RecordingEventBus def _fresh_audit_service() -> AuditService: Settings._instance = None settings = Settings(database_url="sqlite:///:memory:") engine = create_engine("sqlite:///:memory:", echo=False) Base.metadata.create_all(engine) session = sessionmaker(bind=engine)() return AuditService(settings=settings, session=session) @given('an auth middleware with expected token "{token}"') def step_auth_middleware_with_token(context: Context, token: str) -> None: context.expected_token = token @given("an auth middleware with no configured token") def step_auth_middleware_no_token(context: Context) -> None: context.expected_token = None @given("an auth recording event bus") def step_auth_recording_event_bus(context: Context) -> None: context.event_bus = RecordingEventBus() context.auth_middleware = TokenAuthMiddleware( expected_token=context.expected_token, event_bus=context.event_bus, ) context.auth_result_history = [] @given('auth middleware is wired to AuditEventSubscriber with expected token "{token}"') def step_auth_middleware_audit_pipeline(context: Context, token: str) -> None: context.audit_service = _fresh_audit_service() context.event_bus = ReactiveEventBus() context.audit_subscriber = AuditEventSubscriber( audit_service=context.audit_service, event_bus=context.event_bus, ) context.auth_middleware = TokenAuthMiddleware( expected_token=token, event_bus=context.event_bus, ) context.auth_result_history = [] @when('I authenticate with token "{token}" identity "{identity}" and ip "{ip_address}"') def step_authenticate( context: Context, token: str, identity: str, ip_address: str ) -> None: context.auth_result = context.auth_middleware.authenticate( token, identity=identity, ip_address=ip_address, ) context.auth_result_history.append(context.auth_result) @when("I try to authenticate with an empty token") def step_try_authenticate_empty_token(context: Context) -> None: context.auth_error = None try: context.auth_middleware.authenticate("", identity="empty@example.com") except ValueError as exc: context.auth_error = exc @then("the auth result should be true") def step_auth_result_true(context: Context) -> None: assert context.auth_result is True @then("the auth result should be false") def step_auth_result_false(context: Context) -> None: assert context.auth_result is False @then("the auth event bus should have exactly {count:d} event") def step_auth_event_count(context: Context, count: int) -> None: assert len(context.event_bus.events) == count, ( f"Expected {count} events, got {len(context.event_bus.events)}" ) @then("the latest auth event type should be {event_type_name}") def step_latest_auth_event_type(context: Context, event_type_name: str) -> None: expected = EventType[event_type_name] actual = context.event_bus.events[-1].event_type assert actual == expected, f"Expected event type {expected}, got {actual}" @then( 'the latest auth event should contain detail "{key}" with value "{expected_value}"' ) def step_latest_auth_event_detail( context: Context, key: str, expected_value: str ) -> None: details = context.event_bus.events[-1].details assert details.get(key) == expected_value, ( f"Expected {key}={expected_value!r}, got {details.get(key)!r}" ) @then("an auth ValueError should be raised") def step_auth_value_error(context: Context) -> None: assert context.auth_error is not None, ( "Expected ValueError but no exception was raised" ) assert isinstance(context.auth_error, ValueError), ( f"Expected ValueError, got {type(context.auth_error).__name__}" ) @then('the auth result history should be "{history_csv}"') def step_auth_result_history(context: Context, history_csv: str) -> None: expected = [part.strip().lower() == "true" for part in history_csv.split(",")] assert context.auth_result_history == expected, ( f"Expected history {expected}, got {context.auth_result_history}" ) @then("the audit log should contain 1 auth_success entry from middleware") def step_audit_has_auth_success(context: Context) -> None: entries = context.audit_service.list_entries(event_type="auth_success") assert len(entries) == 1, f"Expected 1 auth_success entry, got {len(entries)}" @then("the audit log should contain 1 auth_failure entry from middleware") def step_audit_has_auth_failure(context: Context) -> None: entries = context.audit_service.list_entries(event_type="auth_failure") assert len(entries) == 1, f"Expected 1 auth_failure entry, got {len(entries)}" @then( 'the latest {audit_event_type} audit entry should contain detail "{key}" with value "{expected_value}"' ) def step_latest_audit_entry_detail( context: Context, audit_event_type: str, key: str, expected_value: str ) -> None: entries = context.audit_service.list_entries(event_type=audit_event_type) assert entries, f"Expected at least one {audit_event_type} audit entry" details = entries[0].details assert details.get(key) == expected_value, ( f"Expected {audit_event_type}.{key}={expected_value!r}, " f"got {details.get(key)!r}" )