92 lines
2.8 KiB
Python
92 lines
2.8 KiB
Python
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
|