import asyncio import json import pytest from src import main @pytest.fixture(autouse=True) def reset_live_snapshot_task(): main._live_snapshot_task = None main._live_snapshot_value = None main._live_snapshot_created_at = None yield main._live_snapshot_task = None main._live_snapshot_value = None main._live_snapshot_created_at = None def payload(response): return json.loads(response.body) @pytest.mark.anyio async def test_live_snapshot_fetches_user_once_and_updates_work_and_activity(monkeypatch): calls = {"user": 0} async def user(): calls["user"] += 1 return {"id": 1, "login": "timmy"} async def empty(): return [] async def events(authenticated_user): assert authenticated_user["login"] == "timmy" return [{"type": "push"}] async def updates(): return { "items": [{"id": 42, "title": "Mentioned you"}], "page": 1, "total": 125, "has_more": True, } monkeypatch.setattr(main, "current_user", user) monkeypatch.setattr(main, "repos", empty) monkeypatch.setattr(main, "issues", empty) monkeypatch.setattr(main, "pull_requests", empty) monkeypatch.setattr(main, "activity_events", events) monkeypatch.setattr(main, "notifications", updates) response = await main.live_snapshot() result = payload(response) assert calls["user"] == 1 assert result["context"]["user"]["login"] == "timmy" assert result["events"] == [{"type": "push"}] assert result["notifications"] == [{"id": 42, "title": "Mentioned you"}] assert result["notification_pagination"] == { "page": 1, "total": 125, "has_more": True } assert result["sections"] == { "context": "fresh", "events": "fresh", "notifications": "fresh" } @pytest.mark.anyio async def test_live_snapshot_keeps_fresh_context_when_activity_fails(monkeypatch): async def user(): return {"id": 1, "login": "timmy"} async def empty(): return [] async def failing_events(authenticated_user): raise ConnectionError("secret upstream detail") monkeypatch.setattr(main, "current_user", user) monkeypatch.setattr(main, "repos", empty) monkeypatch.setattr(main, "issues", empty) monkeypatch.setattr(main, "pull_requests", empty) monkeypatch.setattr(main, "activity_events", failing_events) monkeypatch.setattr(main, "notifications", empty) result = payload(await main.live_snapshot()) assert result["context"]["user"]["login"] == "timmy" assert result["events"] is None assert result["sections"] == { "context": "fresh", "events": "temporarily unavailable", "notifications": "fresh", } assert "secret" not in json.dumps(result) @pytest.mark.anyio async def test_live_snapshot_keeps_work_and_activity_when_notifications_fail(monkeypatch): async def user(): return {"id": 1, "login": "timmy"} async def empty(): return [] async def events(_authenticated_user): return [{"type": "push"}] async def failing_updates(): raise ConnectionError("private notification failure") monkeypatch.setattr(main, "current_user", user) monkeypatch.setattr(main, "repos", empty) monkeypatch.setattr(main, "issues", empty) monkeypatch.setattr(main, "pull_requests", empty) monkeypatch.setattr(main, "activity_events", events) monkeypatch.setattr(main, "notifications", failing_updates) result = payload(await main.live_snapshot()) assert result["context"]["user"]["login"] == "timmy" assert result["events"] == [{"type": "push"}] assert result["notifications"] is None assert result["sections"]["notifications"] == "temporarily unavailable" assert "private" not in json.dumps(result) @pytest.mark.anyio async def test_live_snapshot_keeps_fresh_activity_when_work_fails(monkeypatch): async def user(): return {"id": 1, "login": "timmy"} async def failing_repos(): raise ConnectionError("work unavailable") async def empty(): return [] async def events(authenticated_user): return [{"type": "push"}] monkeypatch.setattr(main, "current_user", user) monkeypatch.setattr(main, "repos", failing_repos) monkeypatch.setattr(main, "issues", empty) monkeypatch.setattr(main, "pull_requests", empty) monkeypatch.setattr(main, "activity_events", events) monkeypatch.setattr(main, "notifications", empty) result = payload(await main.live_snapshot()) assert result["context"] is None assert result["events"] == [{"type": "push"}] assert result["sections"] == { "context": "temporarily unavailable", "events": "fresh", "notifications": "fresh", } @pytest.mark.anyio async def test_live_snapshot_reuses_completed_snapshot_inside_freshness_window(monkeypatch): user_calls = 0 release = asyncio.Event() async def user(): nonlocal user_calls user_calls += 1 return {"id": 1, "login": "timmy"} async def blocked_repos(): await release.wait() return [] async def empty(): return [] async def events(authenticated_user): return [] monkeypatch.setattr(main, "current_user", user) monkeypatch.setattr(main, "repos", blocked_repos) monkeypatch.setattr(main, "issues", empty) monkeypatch.setattr(main, "pull_requests", empty) monkeypatch.setattr(main, "activity_events", events) monkeypatch.setattr(main, "notifications", empty) first = asyncio.create_task(main.live_snapshot()) await asyncio.sleep(0) second = asyncio.create_task(main.live_snapshot()) await asyncio.sleep(0) assert user_calls == 1 release.set() await asyncio.gather(first, second) await main.live_snapshot() assert user_calls == 1 @pytest.mark.anyio async def test_stale_snapshot_returns_immediately_while_one_refresh_revalidates(monkeypatch): now = 100.0 refresh_started = asyncio.Event() release_refresh = asyncio.Event() builds = 0 async def snapshot(): nonlocal builds builds += 1 if builds == 2: refresh_started.set() await release_refresh.wait() return { "context": {"generation": builds}, "events": [], "notifications": [], "sections": {}, } monkeypatch.setattr(main.time, "monotonic", lambda: now) monkeypatch.setattr(main, "_build_live_snapshot", snapshot) first = payload(await main.live_snapshot()) now += main.LIVE_SNAPSHOT_FRESHNESS_SECONDS + 1 stale_request = asyncio.create_task(main.live_snapshot()) await refresh_started.wait() await asyncio.sleep(0) returned_immediately = stale_request.done() release_refresh.set() stale = payload(await stale_request) assert returned_immediately is True assert first["context"]["generation"] == 1 assert stale["context"]["generation"] == 1 assert stale["freshness"]["stale"] is True assert stale["freshness"]["revalidating"] is True assert builds == 2 @pytest.mark.anyio async def test_cancelling_one_waiter_does_not_cancel_the_shared_snapshot(monkeypatch): started = asyncio.Event() release = asyncio.Event() snapshot_calls = 0 async def blocked_snapshot(): nonlocal snapshot_calls snapshot_calls += 1 started.set() await release.wait() return { "context": {}, "events": [], "notifications": [], "sections": {}, } monkeypatch.setattr(main, "_build_live_snapshot", blocked_snapshot) disconnected = asyncio.create_task(main.live_snapshot()) await started.wait() survivor = asyncio.create_task(main.live_snapshot()) await asyncio.sleep(0) disconnected.cancel() with pytest.raises(asyncio.CancelledError): await disconnected assert main._live_snapshot_task is not None assert not main._live_snapshot_task.done() release.set() response = await survivor assert response.status_code == 200 assert snapshot_calls == 1 @pytest.mark.anyio async def test_shared_snapshot_deadline_cancels_upstream_work_for_all_waiters(monkeypatch): started = asyncio.Event() cancelled = asyncio.Event() async def snapshot_that_exceeds_deadline(): started.set() try: await asyncio.Event().wait() finally: cancelled.set() monkeypatch.setattr(main, "_build_live_snapshot", snapshot_that_exceeds_deadline) monkeypatch.setattr(main, "CONTEXT_TIMEOUT_SECONDS", 0.01) first = asyncio.create_task(main.live_snapshot()) await started.wait() second = asyncio.create_task(main.live_snapshot()) try: responses = await asyncio.gather(first, second) await asyncio.wait_for(cancelled.wait(), timeout=0.1) assert [response.status_code for response in responses] == [503, 503] assert [payload(response)["error"] for response in responses] == [ "Gitea live snapshot timed out after 0.01s", "Gitea live snapshot timed out after 0.01s", ] finally: task = main._live_snapshot_task if task is not None and not task.done(): task.cancel() with pytest.raises(asyncio.CancelledError): await task