from contextlib import asynccontextmanager from types import SimpleNamespace from uuid import uuid4 import pytest from fastapi import HTTPException from app.api.v1.endpoints import workbench_events as endpoint from app.auth.project_dependencies import ProjectContext from app.infra.db.project_routing import ActiveProjectRouting @pytest.fixture def anyio_backend(): return "asyncio" class FakeHub: generation = 0 async def oldest_cursor(self): return 5 async def current_cursor(self): return 8 class FakeHubManager: @asynccontextmanager async def subscribe(self, _project_id, _dsn): yield FakeHub() class FakeMetadataRepository: def __init__(self, routings): self.routings = routings self.session = SimpleNamespace(closed=False, close=self.close) async def close(self): self.session.closed = True async def list_active_project_db_routings(self, _role): return self.routings @pytest.mark.anyio async def test_expired_event_cursor_requires_snapshot_reset(monkeypatch): project_id = uuid4() dsn = "postgresql://test@db.example:5432/project_a" routing = ActiveProjectRouting("project-a", dsn, project_id=project_id) metadata = FakeMetadataRepository([ SimpleNamespace(project_id=project_id, dsn=dsn) ]) monkeypatch.setattr(endpoint, "workbench_event_hubs", FakeHubManager()) response = await endpoint.workbench_events( after=1, last_event_id=None, ctx=ProjectContext( project_id=project_id, project_code="project-a", user_id=uuid4(), project_role="member", ), routing=routing, metadata_repo=metadata, ) first = await anext(response.body_iterator) assert metadata.session.closed is True assert "event: reset_required" in first assert '"cursor":1' in first @pytest.mark.anyio async def test_shared_business_database_disables_event_stream(): project_id = uuid4() dsn = "postgresql://user:secret@db.example:5432/shared" metadata = FakeMetadataRepository([ SimpleNamespace(project_id=project_id, dsn=dsn), SimpleNamespace(project_id=uuid4(), dsn="postgresql://other:other@db.example/shared"), ]) with pytest.raises(HTTPException) as exc: await endpoint.workbench_events( after=0, last_event_id=None, ctx=ProjectContext( project_id=project_id, project_code="project-a", user_id=uuid4(), project_role="member", ), routing=ActiveProjectRouting("project-a", dsn, project_id=project_id), metadata_repo=metadata, ) assert exc.value.status_code == 503