feat: make push delivery multi-worker safe (Closes #551)
CI / lint (pull_request) Successful in 1m7s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-11 07:25:55 +00:00
1 parent 4c64cc4c3e
commit 59c263fc59
5 files changed
+229 -33

No files matched your search

+116
View File
@@ -1,4 +1,6 @@
import json
import asyncio
import os
from types import SimpleNamespace
import pytest
@@ -8,6 +10,32 @@ from src.push_notifications import PushConfiguration, dispatch_unread_updates
from src.push_subscription_store import PushSubscriptionStore
def test_dispatch_lease_is_exclusive_recoverable_and_owner_fenced(tmp_path):
path = tmp_path / "push.sqlite3"
first = PushSubscriptionStore(path)
second = PushSubscriptionStore(path)
assert first.acquire_dispatch_lease("worker-a", now=100, lease_seconds=30) is True
assert second.acquire_dispatch_lease("worker-b", now=100, lease_seconds=30) is False
assert second.acquire_dispatch_lease("worker-b", now=131, lease_seconds=30) is True
assert first.release_dispatch_lease("worker-a") is False
assert second.release_dispatch_lease("worker-b") is True
def test_subscription_store_uses_private_filesystem_permissions(tmp_path):
state_dir = tmp_path / "push-state"
previous_umask = os.umask(0)
try:
database = state_dir / "push.sqlite3"
PushSubscriptionStore(database)
finally:
os.umask(previous_umask)
assert state_dir.stat().st_mode & 0o777 == 0o700
assert database.stat().st_mode & 0o777 == 0o600
def test_subscription_store_delivers_each_unread_thread_once_per_device(tmp_path):
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
subscription = {
@@ -79,6 +107,40 @@ async def test_dispatch_sends_one_privacy_safe_deep_link_per_new_thread(tmp_path
assert "Secret title" not in json.dumps(sent)
@pytest.mark.anyio
async def test_concurrent_workers_do_not_dispatch_the_same_update(tmp_path):
path = tmp_path / "push.sqlite3"
first = PushSubscriptionStore(path)
second = PushSubscriptionStore(path)
first.upsert("session-a", {
"endpoint": "https://push.example/device-a",
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
})
sending = asyncio.Event()
finish = asyncio.Event()
sent = []
async def unread():
return {"items": [{"notification_id": 42}]}
async def send(_subscription, payload):
sent.append(json.loads(payload)["tag"])
sending.set()
await finish.wait()
config = PushConfiguration("public", "private", "mailto:ops@example.com")
active = asyncio.create_task(dispatch_unread_updates(first, config, unread, send))
await sending.wait()
competing = await asyncio.wait_for(
dispatch_unread_updates(second, config, unread, send), timeout=0.1
)
finish.set()
assert competing == 0
assert await active == 1
assert sent == ["stackchain-update-42"]
@pytest.mark.anyio
async def test_dispatch_removes_an_expired_push_endpoint(tmp_path):
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
@@ -101,6 +163,60 @@ async def test_dispatch_removes_an_expired_push_endpoint(tmp_path):
assert store.is_subscribed("session-a") is False
@pytest.mark.anyio
async def test_transient_endpoint_failure_does_not_block_healthy_devices(tmp_path):
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
for session_id in ("session-a", "session-b"):
store.upsert(session_id, {
"endpoint": f"https://push.example/{session_id}",
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
})
sent = []
async def unread():
return {"items": [{"notification_id": 8}]}
async def send(subscription, _payload):
if subscription["endpoint"].endswith("session-a"):
raise RuntimeError("provider unavailable")
sent.append(subscription["endpoint"])
config = PushConfiguration("public", "private", "mailto:ops@example.com")
assert await dispatch_unread_updates(store, config, unread, send) == 1
assert sent == ["https://push.example/session-b"]
assert store.claim_unseen({8})[0].session_id == "session-a"
@pytest.mark.anyio
async def test_timed_out_endpoint_does_not_stall_push_fanout(tmp_path):
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
for session_id in ("session-a", "session-b"):
store.upsert(session_id, {
"endpoint": f"https://push.example/{session_id}",
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
})
sent = []
async def unread():
return {"items": [{"notification_id": 13}]}
async def send(subscription, _payload):
if subscription["endpoint"].endswith("session-a"):
await asyncio.Event().wait()
sent.append(subscription["endpoint"])
config = PushConfiguration("public", "private", "mailto:ops@example.com")
delivered = await asyncio.wait_for(
dispatch_unread_updates(
store, config, unread, send, send_timeout_seconds=0.01
),
timeout=0.2,
)
assert delivered == 1
assert sent == ["https://push.example/session-b"]
@pytest.mark.anyio
async def test_authenticated_device_can_subscribe_report_status_and_unsubscribe(tmp_path, monkeypatch):
store = PushSubscriptionStore(tmp_path / "push.sqlite3")