feat: retain live snapshots while revalidating (#139)
This commit is contained in:
1 parent
3807e7afd1
commit
ea7bd15721
5 files changed
+225
-13
No files matched your search
@@ -9,8 +9,12 @@ 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):
|
||||
@@ -147,7 +151,7 @@ async def test_live_snapshot_keeps_fresh_activity_when_work_fails(monkeypatch):
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_live_snapshot_coalesces_only_simultaneous_requests(monkeypatch):
|
||||
async def test_live_snapshot_reuses_completed_snapshot_inside_freshness_window(monkeypatch):
|
||||
user_calls = 0
|
||||
release = asyncio.Event()
|
||||
|
||||
@@ -183,7 +187,47 @@ async def test_live_snapshot_coalesces_only_simultaneous_requests(monkeypatch):
|
||||
await asyncio.gather(first, second)
|
||||
await main.live_snapshot()
|
||||
|
||||
assert user_calls == 2
|
||||
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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import asyncio
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
@@ -45,3 +47,62 @@ async def test_mark_notification_read_api_is_bounded_and_never_cacheable(monkeyp
|
||||
assert response.headers["cache-control"] == "no-store"
|
||||
assert invalid.status_code == 422
|
||||
assert marked == [42]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mark_notification_read_removes_thread_from_retained_live_snapshot(monkeypatch):
|
||||
async def mark(_thread_id):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(main, "mark_notification_read", mark)
|
||||
monkeypatch.setattr(
|
||||
main,
|
||||
"_live_snapshot_value",
|
||||
{
|
||||
"context": {},
|
||||
"events": [],
|
||||
"notifications": [{"id": 42}, {"id": 43}],
|
||||
"sections": {"notifications": "fresh"},
|
||||
},
|
||||
)
|
||||
|
||||
response = await main.read_notification(42)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert main._live_snapshot_value is not None
|
||||
assert main._live_snapshot_value["notifications"] == [{"id": 43}]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_inflight_refresh_cannot_restore_a_notification_marked_read(monkeypatch):
|
||||
refresh_started = asyncio.Event()
|
||||
release_refresh = asyncio.Event()
|
||||
|
||||
async def mark(_thread_id):
|
||||
return None
|
||||
|
||||
async def stale_upstream_snapshot():
|
||||
refresh_started.set()
|
||||
await release_refresh.wait()
|
||||
return {
|
||||
"context": {},
|
||||
"events": [],
|
||||
"notifications": [{"id": 42}, {"id": 43}],
|
||||
"sections": {"notifications": "fresh"},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(main, "mark_notification_read", mark)
|
||||
monkeypatch.setattr(
|
||||
main, "_build_live_snapshot_before_deadline", stale_upstream_snapshot
|
||||
)
|
||||
monkeypatch.setattr(main, "_live_snapshot_task", None)
|
||||
monkeypatch.setattr(main, "_live_snapshot_value", {"notifications": [{"id": 42}]})
|
||||
|
||||
refresh = main._start_live_snapshot_refresh()
|
||||
await refresh_started.wait()
|
||||
await main.read_notification(42)
|
||||
release_refresh.set()
|
||||
await refresh
|
||||
|
||||
assert main._live_snapshot_value is not None
|
||||
assert main._live_snapshot_value["notifications"] == [{"id": 43}]
|
||||
@@ -26,4 +26,12 @@ async def test_one_live_snapshot_updates_work_and_activity_on_one_timer():
|
||||
assert "renderContextSnapshot(snapshot.context)" in html
|
||||
assert "paintEventStream(snapshot.events)" in html
|
||||
assert "loadEventStream" not in html
|
||||
assert "5000" not in html
|
||||
assert "5000" not in html
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_dashboard_announces_when_recent_snapshot_is_revalidating():
|
||||
html = await dashboard()
|
||||
|
||||
assert "snapshot.freshness?.revalidating" in html
|
||||
assert "Refreshing · showing recent snapshot" in html
|
||||
Reference in new issue
Block a user