618 lines
21 KiB
Python
618 lines
21 KiB
Python
import asyncio
|
|
import json
|
|
from datetime import datetime, timezone
|
|
|
|
import pytest
|
|
|
|
from src import gitea_proxy
|
|
from src.push_notifications import PushConfiguration, dispatch_deadline_reminders
|
|
from src.push_subscription_store import PushSubscriptionStore
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_deadline_reminder_sends_one_private_local_day_digest_and_deduplicates(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
store.upsert("device-a", {
|
|
"endpoint": "https://push.example/device-a",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
store.set_deadline_preferences(
|
|
"device-a", enabled=True, timezone="America/New_York", reminder_hour=9
|
|
)
|
|
sent = []
|
|
|
|
async def assigned():
|
|
return {
|
|
"complete": True,
|
|
"items": [
|
|
{
|
|
"id": 42,
|
|
"title": "Private launch plan",
|
|
"repository": {"full_name": "private/repo"},
|
|
"due_date": "2026-08-14T12:00:00Z",
|
|
},
|
|
{"id": 43, "due_date": "2026-08-20T12:00:00Z"},
|
|
],
|
|
}
|
|
|
|
async def send(_subscription, payload):
|
|
sent.append(json.loads(payload))
|
|
|
|
config = PushConfiguration("public", "private", "mailto:ops@example.com")
|
|
now = datetime(2026, 8, 13, 13, 5, tzinfo=timezone.utc)
|
|
|
|
assert await dispatch_deadline_reminders(store, config, assigned, send, now=now) == 1
|
|
assert await dispatch_deadline_reminders(store, config, assigned, send, now=now) == 0
|
|
assert sent == [{
|
|
"title": "1 deadline needs attention",
|
|
"body": "Open Agenda to review or replan it.",
|
|
"route": "#/my-work/agenda",
|
|
"protect_route": "#/my-work/agenda/protect-today",
|
|
"tag": "stackchain-deadline-digest-2026-08-13",
|
|
"deadline_count": 1,
|
|
}]
|
|
assert "Private launch plan" not in json.dumps(sent)
|
|
assert "private/repo" not in json.dumps(sent)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_snoozed_deadline_revalidates_and_sends_once_after_expiry(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
store.upsert("device-a", {
|
|
"endpoint": "https://push.example/device-a",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
store.set_deadline_preferences(
|
|
"device-a", enabled=True, timezone="UTC", reminder_hour=9
|
|
)
|
|
store.mark_deadline_reminder_delivered("device-a", "2026-08-13")
|
|
snoozed_at = datetime(2026, 8, 13, 10, 0, tzinfo=timezone.utc)
|
|
store.snooze_deadline_reminder(
|
|
"device-a", now=snoozed_at.timestamp(), delay_seconds=3_600
|
|
)
|
|
snapshots = 0
|
|
sent = []
|
|
|
|
async def assigned():
|
|
nonlocal snapshots
|
|
snapshots += 1
|
|
return {"complete": True, "items": [{"id": 1, "due_date": "2026-08-14"}]}
|
|
|
|
async def send(_subscription, payload):
|
|
sent.append(json.loads(payload))
|
|
|
|
config = PushConfiguration("public", "private", "mailto:ops@example.com")
|
|
assert await dispatch_deadline_reminders(
|
|
store, config, assigned, send,
|
|
now=datetime(2026, 8, 13, 10, 59, tzinfo=timezone.utc),
|
|
) == 0
|
|
assert snapshots == 0
|
|
|
|
assert await dispatch_deadline_reminders(
|
|
store, config, assigned, send,
|
|
now=datetime(2026, 8, 13, 11, 0, tzinfo=timezone.utc),
|
|
) == 1
|
|
assert snapshots == 1
|
|
assert sent[0]["deadline_count"] == 1
|
|
assert sent[0]["tag"] == "stackchain-deadline-digest-2026-08-13"
|
|
assert store.deadline_reminder_devices()[0].snoozed_until is None
|
|
assert await dispatch_deadline_reminders(
|
|
store, config, assigned, send,
|
|
now=datetime(2026, 8, 13, 11, 1, tzinfo=timezone.utc),
|
|
) == 0
|
|
assert snapshots == 1
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_expired_deadline_snooze_clears_when_no_deadlines_remain(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
store.upsert("device-a", {
|
|
"endpoint": "https://push.example/device-a",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
store.set_deadline_preferences(
|
|
"device-a", enabled=True, timezone="UTC", reminder_hour=9
|
|
)
|
|
store.mark_deadline_reminder_delivered("device-a", "2026-08-13")
|
|
store.snooze_deadline_reminder(
|
|
"device-a",
|
|
now=datetime(2026, 8, 13, 10, 0, tzinfo=timezone.utc).timestamp(),
|
|
)
|
|
|
|
async def resolved():
|
|
return {"complete": True, "items": []}
|
|
|
|
assert await dispatch_deadline_reminders(
|
|
store,
|
|
PushConfiguration("public", "private", "mailto:ops@example.com"),
|
|
resolved,
|
|
now=datetime(2026, 8, 13, 11, 0, tzinfo=timezone.utc),
|
|
) == 0
|
|
assert store.deadline_reminder_devices()[0].snoozed_until is None
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_expired_deadline_snooze_clears_when_deadlines_move_beyond_horizon(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
store.upsert("device-a", {
|
|
"endpoint": "https://push.example/device-a",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
store.set_deadline_preferences(
|
|
"device-a", enabled=True, timezone="UTC", reminder_hour=9, reminder_days=2
|
|
)
|
|
store.mark_deadline_reminder_delivered("device-a", "2026-08-13")
|
|
store.snooze_deadline_reminder(
|
|
"device-a",
|
|
now=datetime(2026, 8, 13, 10, 0, tzinfo=timezone.utc).timestamp(),
|
|
)
|
|
|
|
async def replanned():
|
|
return {"complete": True, "items": [{"id": 1, "due_date": "2026-08-20"}]}
|
|
|
|
assert await dispatch_deadline_reminders(
|
|
store,
|
|
PushConfiguration("public", "private", "mailto:ops@example.com"),
|
|
replanned,
|
|
now=datetime(2026, 8, 13, 11, 0, tzinfo=timezone.utc),
|
|
) == 0
|
|
assert store.deadline_reminder_devices()[0].snoozed_until is None
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_deadline_reminder_counts_calendar_days_per_device_timezone(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
for device, timezone_name in (("tokyo", "Asia/Tokyo"), ("la", "America/Los_Angeles")):
|
|
store.upsert(device, {
|
|
"endpoint": f"https://push.example/{device}",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
store.set_deadline_preferences(device, enabled=True, timezone=timezone_name, reminder_hour=0)
|
|
|
|
async def assigned():
|
|
return {"complete": True, "items": [
|
|
{"id": 1, "due_date": "2026-08-15T23:59:59Z"},
|
|
{"id": 2, "due_date": "2026-08-16T23:59:59Z"},
|
|
]}
|
|
|
|
sent = {}
|
|
async def send(subscription, payload):
|
|
device = subscription["endpoint"].rsplit("/", 1)[-1]
|
|
sent[device] = json.loads(payload)["deadline_count"]
|
|
|
|
delivered = await dispatch_deadline_reminders(
|
|
store, PushConfiguration("public", "private", "mailto:ops@example.com"),
|
|
assigned, send, now=datetime(2026, 8, 13, 23, 30, tzinfo=timezone.utc),
|
|
)
|
|
|
|
assert delivered == 2
|
|
assert sent == {"tokyo": 2, "la": 1}
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_deadline_reminder_uses_each_devices_confirmed_horizon(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
for device, reminder_days in (("today", 0), ("week", 7)):
|
|
store.upsert(device, {
|
|
"endpoint": f"https://push.example/{device}",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
store.set_deadline_preferences(
|
|
device,
|
|
enabled=True,
|
|
timezone="UTC",
|
|
reminder_hour=9,
|
|
reminder_days=reminder_days,
|
|
)
|
|
|
|
async def assigned():
|
|
return {"complete": True, "items": [
|
|
{"id": 1, "due_date": "2026-08-13"},
|
|
{"id": 2, "due_date": "2026-08-15"},
|
|
{"id": 3, "due_date": "2026-08-20"},
|
|
{"id": 4, "due_date": "2026-08-21"},
|
|
]}
|
|
|
|
sent = {}
|
|
|
|
async def send(subscription, payload):
|
|
device = subscription["endpoint"].rsplit("/", 1)[-1]
|
|
sent[device] = json.loads(payload)["deadline_count"]
|
|
|
|
delivered = await dispatch_deadline_reminders(
|
|
store,
|
|
PushConfiguration("public", "private", "mailto:ops@example.com"),
|
|
assigned,
|
|
send,
|
|
now=datetime(2026, 8, 13, 10, 0, tzinfo=timezone.utc),
|
|
)
|
|
|
|
assert delivered == 2
|
|
assert sent == {"today": 1, "week": 3}
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_deadline_reminder_fails_closed_for_incomplete_snapshot_and_before_local_hour(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
store.upsert("device-a", {
|
|
"endpoint": "https://push.example/device-a",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
store.set_deadline_preferences(
|
|
"device-a", enabled=True, timezone="America/Los_Angeles", reminder_hour=9
|
|
)
|
|
sent = []
|
|
|
|
async def incomplete():
|
|
return {"complete": False, "items": [{"id": 42, "due_date": "2026-08-14T12:00:00Z"}]}
|
|
|
|
async def complete():
|
|
return {"complete": True, "items": [{"id": 42, "due_date": "2026-08-14T12:00:00Z"}]}
|
|
|
|
async def send(_subscription, payload):
|
|
sent.append(payload)
|
|
|
|
config = PushConfiguration("public", "private", "mailto:ops@example.com")
|
|
assert await dispatch_deadline_reminders(
|
|
store, config, incomplete, send,
|
|
now=datetime(2026, 8, 13, 18, 0, tzinfo=timezone.utc),
|
|
) == 0
|
|
assert await dispatch_deadline_reminders(
|
|
store, config, complete, send,
|
|
now=datetime(2026, 8, 13, 15, 0, tzinfo=timezone.utc),
|
|
) == 0
|
|
assert sent == []
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_deadline_reminder_skips_snapshot_until_a_device_reaches_its_local_hour(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
store.upsert("device-a", {
|
|
"endpoint": "https://push.example/device-a",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
store.set_deadline_preferences(
|
|
"device-a", enabled=True, timezone="America/Los_Angeles", reminder_hour=9
|
|
)
|
|
snapshot_calls = 0
|
|
|
|
async def assigned():
|
|
nonlocal snapshot_calls
|
|
snapshot_calls += 1
|
|
return {"complete": True, "items": []}
|
|
|
|
delivered = await dispatch_deadline_reminders(
|
|
store,
|
|
PushConfiguration("public", "private", "mailto:ops@example.com"),
|
|
assigned,
|
|
now=datetime(2026, 8, 13, 15, 0, tzinfo=timezone.utc),
|
|
)
|
|
|
|
assert delivered == 0
|
|
assert snapshot_calls == 0
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_deadline_reminder_skips_snapshot_after_every_device_was_delivered_today(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
for session_id, timezone_name in (
|
|
("device-a", "UTC"),
|
|
("device-b", "America/New_York"),
|
|
):
|
|
store.upsert(session_id, {
|
|
"endpoint": f"https://push.example/{session_id}",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
store.set_deadline_preferences(
|
|
session_id, enabled=True, timezone=timezone_name, reminder_hour=9
|
|
)
|
|
store.mark_deadline_reminder_delivered(session_id, "2026-08-13")
|
|
snapshot_calls = 0
|
|
|
|
async def assigned():
|
|
nonlocal snapshot_calls
|
|
snapshot_calls += 1
|
|
return {"complete": True, "items": []}
|
|
|
|
delivered = await dispatch_deadline_reminders(
|
|
store,
|
|
PushConfiguration("public", "private", "mailto:ops@example.com"),
|
|
assigned,
|
|
now=datetime(2026, 8, 13, 15, 0, tzinfo=timezone.utc),
|
|
)
|
|
|
|
assert delivered == 0
|
|
assert snapshot_calls == 0
|
|
|
|
|
|
def test_deadline_preferences_persist_on_the_existing_device_subscription(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
store.upsert("device-a", {
|
|
"endpoint": "https://push.example/device-a",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
|
|
store.set_deadline_preferences(
|
|
"device-a", enabled=True, timezone="Europe/London", reminder_hour=8
|
|
)
|
|
|
|
assert store.deadline_preferences("device-a") == {
|
|
"enabled": True,
|
|
"timezone": "Europe/London",
|
|
"reminder_hour": 8,
|
|
"reminder_days": 2,
|
|
"snoozed_until": None,
|
|
}
|
|
|
|
|
|
def test_deadline_snooze_is_device_bound_and_requires_enabled_reminders(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
for session_id in ("enabled", "disabled"):
|
|
store.upsert(session_id, {
|
|
"endpoint": f"https://push.example/{session_id}",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
store.set_deadline_preferences(
|
|
"enabled", enabled=True, timezone="UTC", reminder_hour=9
|
|
)
|
|
store.set_deadline_preferences(
|
|
"disabled", enabled=False, timezone="UTC", reminder_hour=9
|
|
)
|
|
|
|
assert store.snooze_deadline_reminder(
|
|
"enabled", now=1_765_000_000, delay_seconds=3_600
|
|
) is True
|
|
assert store.snooze_deadline_reminder(
|
|
"disabled", now=1_765_000_000, delay_seconds=3_600
|
|
) is False
|
|
assert store.snooze_deadline_reminder(
|
|
"missing", now=1_765_000_000, delay_seconds=3_600
|
|
) is False
|
|
|
|
devices = {device.session_id: device for device in store.deadline_reminder_devices()}
|
|
assert devices["enabled"].snoozed_until == 1_765_003_600
|
|
assert "disabled" not in devices
|
|
|
|
|
|
def test_existing_deadline_preferences_migrate_to_two_day_horizon(tmp_path):
|
|
database = tmp_path / "push.sqlite3"
|
|
import sqlite3
|
|
|
|
with sqlite3.connect(database) as connection:
|
|
connection.executescript("""
|
|
CREATE TABLE push_subscriptions (
|
|
session_id TEXT PRIMARY KEY,
|
|
endpoint TEXT NOT NULL UNIQUE,
|
|
subscription_json TEXT NOT NULL
|
|
);
|
|
CREATE TABLE push_deadline_preferences (
|
|
session_id TEXT PRIMARY KEY,
|
|
enabled INTEGER NOT NULL DEFAULT 0,
|
|
timezone TEXT NOT NULL DEFAULT 'UTC',
|
|
reminder_hour INTEGER NOT NULL DEFAULT 9,
|
|
delivered_local_day TEXT
|
|
);
|
|
INSERT INTO push_subscriptions VALUES (
|
|
'device-a',
|
|
'https://push.example/a',
|
|
'{"endpoint":"https://push.example/a","keys":{"p256dh":"key","auth":"secret"}}'
|
|
);
|
|
INSERT INTO push_deadline_preferences VALUES ('device-a', 1, 'UTC', 8, NULL);
|
|
""")
|
|
|
|
store = PushSubscriptionStore(database)
|
|
|
|
assert store.deadline_preferences("device-a")["reminder_days"] == 2
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_assigned_deadline_snapshot_is_pagination_complete(monkeypatch):
|
|
pages = {
|
|
1: {"items": [{"id": 1}], "total": 2, "has_more": True},
|
|
2: {"items": [{"id": 2}], "total": 2, "has_more": False},
|
|
}
|
|
|
|
async def work_page(stream, page, limit):
|
|
assert stream == "issue"
|
|
assert limit == 1
|
|
return pages[page]
|
|
|
|
monkeypatch.setattr(gitea_proxy, "work_page", work_page)
|
|
|
|
assert await gitea_proxy.assigned_issue_snapshot(limit=1) == {
|
|
"items": [{"id": 1}, {"id": 2}],
|
|
"complete": True,
|
|
}
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_assigned_deadline_snapshot_fetches_remaining_pages_concurrently(monkeypatch):
|
|
active = 0
|
|
peak = 0
|
|
remaining_started = asyncio.Event()
|
|
|
|
async def work_page(stream, page, limit):
|
|
nonlocal active, peak
|
|
assert stream == "issue"
|
|
assert limit == 1
|
|
if page == 1:
|
|
return {"items": [{"id": 1}], "total": 5, "has_more": True}
|
|
active += 1
|
|
peak = max(peak, active)
|
|
if active == 2:
|
|
remaining_started.set()
|
|
await asyncio.wait_for(remaining_started.wait(), timeout=0.2)
|
|
await asyncio.sleep(0)
|
|
active -= 1
|
|
return {"items": [{"id": page}], "total": 5, "has_more": page < 5}
|
|
|
|
monkeypatch.setattr(gitea_proxy, "work_page", work_page)
|
|
|
|
assert await gitea_proxy.assigned_issue_snapshot(limit=1, max_concurrency=2) == {
|
|
"items": [{"id": 1}, {"id": 2}, {"id": 3}, {"id": 4}, {"id": 5}],
|
|
"complete": True,
|
|
}
|
|
assert peak == 2
|
|
|
|
|
|
@pytest.mark.anyio
|
|
@pytest.mark.parametrize(
|
|
"remaining",
|
|
[
|
|
{"items": [{"id": 2}], "total": 3, "has_more": True},
|
|
{"items": [{"id": 1}], "total": 2, "has_more": False},
|
|
],
|
|
)
|
|
async def test_assigned_deadline_snapshot_rejects_changed_or_duplicate_pages(
|
|
monkeypatch, remaining
|
|
):
|
|
async def work_page(_stream, page, _limit):
|
|
if page == 1:
|
|
return {"items": [{"id": 1}], "total": 2, "has_more": True}
|
|
return remaining
|
|
|
|
monkeypatch.setattr(gitea_proxy, "work_page", work_page)
|
|
|
|
with pytest.raises(ValueError, match="incomplete|changed"):
|
|
await gitea_proxy.assigned_issue_snapshot(limit=1)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_assigned_deadline_snapshot_enforces_aggregate_deadline(monkeypatch):
|
|
cancelled = asyncio.Event()
|
|
|
|
async def work_page(_stream, _page, _limit):
|
|
try:
|
|
await asyncio.sleep(60)
|
|
finally:
|
|
cancelled.set()
|
|
|
|
monkeypatch.setattr(gitea_proxy, "work_page", work_page)
|
|
|
|
with pytest.raises(TimeoutError):
|
|
await gitea_proxy.assigned_issue_snapshot(deadline_seconds=0.01)
|
|
assert cancelled.is_set()
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_competing_workers_send_one_deadline_digest(tmp_path):
|
|
path = tmp_path / "push.sqlite3"
|
|
first = PushSubscriptionStore(path)
|
|
second = PushSubscriptionStore(path)
|
|
first.upsert("device-a", {
|
|
"endpoint": "https://push.example/device-a",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
first.set_deadline_preferences(
|
|
"device-a", enabled=True, timezone="UTC", reminder_hour=9
|
|
)
|
|
sending = __import__("asyncio").Event()
|
|
release = __import__("asyncio").Event()
|
|
sent = []
|
|
|
|
async def assigned():
|
|
return {"complete": True, "items": [{"id": 1, "due_date": "2026-08-14T00:00:00Z"}]}
|
|
|
|
async def send(_subscription, payload):
|
|
sent.append(payload)
|
|
sending.set()
|
|
await release.wait()
|
|
|
|
config = PushConfiguration("public", "private", "mailto:ops@example.com")
|
|
now = datetime(2026, 8, 13, 10, 0, tzinfo=timezone.utc)
|
|
active = __import__("asyncio").create_task(
|
|
dispatch_deadline_reminders(first, config, assigned, send, now=now)
|
|
)
|
|
await sending.wait()
|
|
competing = await dispatch_deadline_reminders(second, config, assigned, send, now=now)
|
|
release.set()
|
|
|
|
assert competing == 0
|
|
assert await active == 1
|
|
assert len(sent) == 1
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_deadline_reminders_bound_fanout_and_isolate_failed_devices(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
for index in range(4):
|
|
session_id = f"device-{index}"
|
|
store.upsert(session_id, {
|
|
"endpoint": f"https://push.example/{session_id}",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
store.set_deadline_preferences(
|
|
session_id, enabled=True, timezone="UTC", reminder_hour=9
|
|
)
|
|
active = 0
|
|
peak = 0
|
|
two_active = asyncio.Event()
|
|
|
|
async def assigned():
|
|
return {"complete": True, "items": [{"id": 1, "due_date": "2026-08-14T00:00:00Z"}]}
|
|
|
|
async def send(subscription, _payload):
|
|
nonlocal active, peak
|
|
active += 1
|
|
peak = max(peak, active)
|
|
if active == 2:
|
|
two_active.set()
|
|
await asyncio.wait_for(two_active.wait(), timeout=0.5)
|
|
active -= 1
|
|
if subscription["endpoint"].endswith("device-1"):
|
|
raise RuntimeError("provider failed")
|
|
|
|
delivered = await dispatch_deadline_reminders(
|
|
store,
|
|
PushConfiguration("public", "private", "mailto:ops@example.com"),
|
|
assigned,
|
|
send,
|
|
now=datetime(2026, 8, 13, 10, 0, tzinfo=timezone.utc),
|
|
max_concurrency=2,
|
|
)
|
|
|
|
assert delivered == 3
|
|
assert peak == 2
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_deadline_reminders_bulk_authorize_due_devices_and_prune_inactive(tmp_path):
|
|
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
|
for session_id in ("active-device", "revoked-device"):
|
|
store.upsert(session_id, {
|
|
"endpoint": f"https://push.example/{session_id}",
|
|
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
|
})
|
|
store.set_deadline_preferences(
|
|
session_id, enabled=True, timezone="UTC", reminder_hour=9
|
|
)
|
|
authorization_calls = []
|
|
sent = []
|
|
|
|
async def assigned():
|
|
return {"complete": True, "items": [{"id": 1, "due_date": "2026-08-14T00:00:00Z"}]}
|
|
|
|
async def session_statuses(management_ids):
|
|
authorization_calls.append(list(management_ids))
|
|
return {
|
|
management_id: "active" if management_id == "active-device" else "revoked"
|
|
for management_id in management_ids
|
|
}
|
|
|
|
async def send(subscription, _payload):
|
|
sent.append(subscription["endpoint"])
|
|
|
|
delivered = await dispatch_deadline_reminders(
|
|
store,
|
|
PushConfiguration("public", "private", "mailto:ops@example.com"),
|
|
assigned,
|
|
send,
|
|
now=datetime(2026, 8, 13, 10, 0, tzinfo=timezone.utc),
|
|
session_statuses=session_statuses,
|
|
)
|
|
|
|
assert delivered == 1
|
|
assert authorization_calls == [["active-device", "revoked-device"]]
|
|
assert sent == ["https://push.example/active-device"]
|
|
assert store.is_subscribed("revoked-device") is False
|