163 lines
5.4 KiB
Python
163 lines
5.4 KiB
Python
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",
|
|
"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_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 == []
|
|
|
|
|
|
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,
|
|
}
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_assigned_deadline_snapshot_is_pagination_complete(monkeypatch):
|
|
pages = {
|
|
1: {"items": [{"id": 1}], "has_more": True},
|
|
2: {"items": [{"id": 2}], "has_more": False},
|
|
}
|
|
|
|
async def work_page(stream, page, limit):
|
|
assert stream == "issue"
|
|
assert limit == 50
|
|
return pages[page]
|
|
|
|
monkeypatch.setattr(gitea_proxy, "work_page", work_page)
|
|
|
|
assert await gitea_proxy.assigned_issue_snapshot() == {
|
|
"items": [{"id": 1}, {"id": 2}],
|
|
"complete": True,
|
|
}
|
|
|
|
|
|
@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
|