import asyncio from types import SimpleNamespace from unittest.mock import AsyncMock from uuid import uuid4 import pytest from app.infra.db.postgresql.workbench_event_hub import WorkbenchEventHub, WorkbenchEventHubManager from app.infra.db.postgresql import workbench_event_hub as hub_module @pytest.fixture def anyio_backend(): return "asyncio" @pytest.mark.anyio async def test_project_subscribers_share_one_hub_and_idle_close(monkeypatch): start = AsyncMock() close = AsyncMock() monkeypatch.setattr(WorkbenchEventHub, "start", start) monkeypatch.setattr(WorkbenchEventHub, "close", close) manager = WorkbenchEventHubManager(idle_seconds=0.01) project_id = uuid4() async with manager.subscribe(project_id, "postgresql://test/db") as first: async with manager.subscribe(project_id, "postgresql://test/db") as second: assert first is second assert first.subscribers == 2 assert first.subscribers == 1 await asyncio.sleep(0.03) assert len(manager._hubs) == 0 close.assert_awaited_once() @pytest.mark.anyio async def test_idle_subscribers_share_cursor_reads_and_notification_invalidates_the_cache(monkeypatch): reads = 0 class Repository: def __init__(self, *_args): pass async def read_after(self, _after): nonlocal reads reads += 1 return [] monkeypatch.setattr(hub_module, "WorkbenchEventRepository", Repository) hub = WorkbenchEventHub(uuid4(), "postgresql://test/db") hub._connection = SimpleNamespace(closed=False) monkeypatch.setattr(hub, "start", AsyncMock()) await asyncio.gather(*(hub.read_after(0) for _ in range(20))) assert reads == 1 await hub._wake() await asyncio.gather(*(hub.read_after(0) for _ in range(20))) assert reads == 2 @pytest.mark.anyio async def test_notification_during_read_does_not_cache_stale_results(monkeypatch): entered, release = asyncio.Event(), asyncio.Event() reads = 0 class Repository: def __init__(self, *_args): pass async def read_after(self, _after): nonlocal reads reads += 1 if reads == 1: entered.set() await release.wait() return [] monkeypatch.setattr(hub_module, "WorkbenchEventRepository", Repository) hub = WorkbenchEventHub(uuid4(), "postgresql://test/db") hub._connection = SimpleNamespace(closed=False) monkeypatch.setattr(hub, "start", AsyncMock()) first = asyncio.create_task(hub.read_after(0)) await entered.wait() await hub._wake() release.set() await first await hub.read_after(0) assert reads == 2 for cursor in range(100): await hub.read_after(cursor) assert len(hub._reads) == 32