import json from datetime import datetime, timezone from pathlib import Path import pytest from types import SimpleNamespace from src import main from src.push_notifications import PushConfiguration, dispatch_start_day_reminders from src.push_subscription_store import PushSubscriptionStore @pytest.mark.anyio async def test_start_day_reminder_sends_one_private_prompt_for_due_plan(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_start_day_preferences( "device-a", enabled=True, timezone="America/New_York", reminder_hour=9 ) sent = [] async def tomorrow(): return { "revision": 3, "ids": ["private/repo#42"], "plan_date": "2026-08-20", "timezone": "America/New_York", } async def send(_subscription, payload): sent.append(json.loads(payload)) configuration = PushConfiguration("public", "private", "mailto:ops@example.com") now = datetime(2026, 8, 20, 13, 5, tzinfo=timezone.utc) assert await dispatch_start_day_reminders( store, configuration, tomorrow, send, now=now ) == 1 assert await dispatch_start_day_reminders( store, configuration, tomorrow, send, now=now ) == 0 assert sent == [{ "title": "Your planned day is ready", "body": "Open Stackchain to prepare Today.", "route": "#/my-work/start-day", "tag": "stackchain-start-day-2026-08-20", "plan_date": "2026-08-20", }] assert "private/repo" not in json.dumps(sent) @pytest.mark.anyio async def test_start_day_reminder_waits_for_hour_and_nonempty_due_plan(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_start_day_preferences( "device-a", enabled=True, timezone="America/Los_Angeles", reminder_hour=9 ) sent = [] async def send(_subscription, payload): sent.append(payload) configuration = PushConfiguration("public", "private", "mailto:ops@example.com") before_hour = datetime(2026, 8, 20, 15, 59, tzinfo=timezone.utc) async def due_plan(): return {"ids": ["one"], "plan_date": "2026-08-20"} assert await dispatch_start_day_reminders( store, configuration, due_plan, send, now=before_hour ) == 0 async def empty_plan(): return {"ids": [], "plan_date": "2026-08-20"} assert await dispatch_start_day_reminders( store, configuration, empty_plan, send, now=datetime(2026, 8, 20, 16, 0, tzinfo=timezone.utc), ) == 0 assert sent == [] @pytest.mark.anyio async def test_authenticated_device_can_enable_start_day_reminders(tmp_path, monkeypatch): store = PushSubscriptionStore(tmp_path / "push.sqlite3") store.upsert("device-a", { "endpoint": "https://push.example/device-a", "keys": {"p256dh": "public-key", "auth": "auth-secret"}, }) monkeypatch.setattr(main, "_push_subscription_store", store) async def management_id(_session): return "device-a" monkeypatch.setattr(main.dashboard_auth, "session_management_id", management_id) request = SimpleNamespace(state=SimpleNamespace(dashboard_session=object())) payload = main.StartDayReminderPayload( enabled=True, timezone="America/New_York", reminder_hour=8 ) assert await main.update_start_day_reminders(payload, request) == { "start_day_enabled": True, "start_day_timezone": "America/New_York", "start_day_reminder_hour": 8, } assert store.start_day_preferences("device-a") == { "enabled": True, "timezone": "America/New_York", "reminder_hour": 8, } @pytest.mark.anyio async def test_worker_snapshot_reads_the_confirmed_accounts_tomorrow_plan(monkeypatch): async def current_user(): return {"login": "Timmy"} class Store: def get_tomorrow(self, login): assert login == "Timmy" return {"ids": ["one"], "plan_date": "2026-08-20"} monkeypatch.setattr(main, "current_user", current_user) monkeypatch.setattr(main, "_today_store", lambda: Store()) assert await main._start_day_plan_snapshot() == { "ids": ["one"], "plan_date": "2026-08-20", } def test_mobile_settings_and_launch_route_wire_start_day_into_existing_promotion(): frontend = Path(__file__).parents[1] / "frontend" html = (frontend / "index.html").read_text() dashboard = (frontend / "dashboard.js").read_text() assert 'id="push-start-day"' in html assert 'id="push-start-day-hour"' in html assert 'id="device-setup-start-day"' in html assert "startDayControl:qs('#push-start-day')" in dashboard assert "startDayHour:qs('#push-start-day-hour')" in dashboard assert "window.location.hash === '#/my-work/start-day'" in dashboard assert "promoteTomorrowIfDue(plan).finally(() =>" in dashboard assert "onRemotePlan: async plan =>" not in dashboard assert "openMobileStartDay()" in dashboard