from __future__ import annotations from app.services.runtime_chat import RuntimeChatOperationContext, RuntimeChatService from app.services.runtime_chat_attempts import RuntimeChatAttemptPermit class RecordingAttemptObserver: def __init__(self, timeline: list[str] | None = None) -> None: self.permits = [] self.completed = [] self.failures = [] self.timeline = timeline def on_permit(self, event): self.permits.append(event) if self.timeline is not None: self.timeline.append(f"permit:{event.identity.attempt}") return None def on_completed(self, event) -> None: self.completed.append(event) if self.timeline is not None: self.timeline.append( f"completed:{event.identity.attempt}:{event.outcome}" ) def on_observer_failure(self, failure) -> None: self.failures.append(failure) class FailingCompletionObserver(RecordingAttemptObserver): def on_completed(self, event) -> None: super().on_completed(event) raise RuntimeError("completion observer unavailable") class FailingPermitObserver(RecordingAttemptObserver): def on_permit(self, event): super().on_permit(event) raise RuntimeError("permit observer unavailable") class DenyingAttemptObserver(RecordingAttemptObserver): def on_permit(self, event): super().on_permit(event) return RuntimeChatAttemptPermit( allowed=False, reason="tenant quota exhausted", ) def build_operation_context() -> RuntimeChatOperationContext: return RuntimeChatOperationContext( tenant_id="tenant-runtime-chat", operation_id="operation-runtime-chat-001", run_id="run-runtime-chat-001", invocation_seq=7, attempt_scope="unit-test-completion", ) def patch_single_slot( monkeypatch, service: RuntimeChatService, *, provider: str, model: str, ) -> None: monkeypatch.setattr( service, "_load_chat_slot", lambda slot: { "slot": slot, "provider": provider, "endpoint": "https://example.com/v1", "model": model, "apiKey": "secret", }, )