import json import subprocess from pathlib import Path SOURCE = Path(__file__).parents[1] / "frontend" / "today-lock-screen.js" ROOT = SOURCE.parents[1] def run_scenario(scenario: str) -> dict: script = SOURCE.read_text() + r""" const values = new Map(); const messages = []; const listeners = {}; const control = {checked:false, disabled:false, addEventListener:(name, fn)=>listeners[name]=fn}; const status = {textContent:''}; const serviceWorker = { controller:{postMessage:message=>messages.push(message)}, ready:Promise.resolve({active:{postMessage:message=>messages.push(message)}}), addEventListener:(name, fn)=>listeners['sw-' + name]=fn, }; const storage = {getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)}; let permission = 'default'; const NotificationRef = {get permission(){return permission;},requestPermission:async()=>{permission='granted';return permission;}}; const actions = []; const locationRef = {href:'https://forge.example/dashboard/#/my-work/today'}; const historyRef = {replaceState:(_a,_b,url)=>{locationRef.href=new URL(url, locationRef.href).href;}}; let tokenCounter = 0; const tokens = ['opaque-token-1234567890', 'new-opaque-token-0987654321']; (async()=>{ const lockScreen = createTodayLockScreen({storage,getLogin:()=> 'Timmy',serviceWorker,NotificationRef,control,status,locationRef,historyRef,randomToken:()=> tokens[tokenCounter++],fingerprint:async (_token, identity)=>identity.startsWith('issue:') ? 'opaque-issue-fingerprint' : 'opaque-pull-fingerprint',onAction:(action, identity)=>actions.push([action, identity])}); %SCENARIO% })().catch(error=>{console.error(error);process.exit(1)}); """.replace("%SCENARIO%", scenario) completed = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) return json.loads(completed.stdout) def test_operator_opt_in_syncs_one_privacy_safe_session_notification(): result = run_scenario(r""" await lockScreen.enable(); await lockScreen.sync({identity:'issue:secret/repo:42:',running:true,elapsed_ms:125000}, true); process.stdout.write(JSON.stringify({checked:control.checked,status:status.textContent,messages,stored:[...values.entries()]})); """) assert result["checked"] is True assert result["status"] == "Lock-screen Today controls are on." assert result["stored"] == [ ["stackchain.today-lock-screen.v1.timmy", "1"], ["stackchain.today-lock-screen-action.v1.timmy", '{"token":"opaque-token-1234567890","fingerprint":"opaque-issue-fingerprint"}'], ] assert result["messages"][-1] == { "type": "stackchain-today-lock-screen", "active": True, "running": True, "actionToken": "opaque-token-1234567890", } assert "secret" not in json.dumps(result) def test_disabling_or_ending_session_removes_notification(): result = run_scenario(r""" await lockScreen.enable(); await lockScreen.sync({identity:'issue:r:42:',running:false,elapsed_ms:0}, false); await lockScreen.disable(); process.stdout.write(JSON.stringify({checked:control.checked,messages,stored:[...values.entries()]})); """) assert result["checked"] is False assert result["stored"] == [] assert result["messages"][-2:] == [ {"type": "stackchain-today-lock-screen", "active": False, "running": False}, {"type": "stackchain-today-lock-screen", "active": False, "running": False}, ] def test_valid_notification_action_is_consumed_once_and_removed_from_url(): result = run_scenario(r""" values.set('stackchain.today-lock-screen.v1.timmy','1'); locationRef.href='https://forge.example/dashboard/?today_timer_action=pause#/my-work/today'; const consumed = await lockScreen.consumeLaunchAction(); const second = await lockScreen.consumeLaunchAction(); listeners['sw-message']({data:{type:'stackchain-today-timer-action',action:'resume'}}); process.stdout.write(JSON.stringify({consumed,second,actions,href:locationRef.href})); """) assert result == { "consumed": True, "second": False, "actions": [["pause", None], ["resume", None]], "href": "https://forge.example/dashboard/#/my-work/today", } def test_finish_action_is_bound_to_the_exact_active_identity_and_consumed_once(): result = run_scenario(r""" await lockScreen.enable(); await lockScreen.sync({identity:'issue:private/repo:42:',running:true}, true); const first = await lockScreen.consumeAction('complete', 'opaque-token-1234567890'); const replay = await lockScreen.consumeAction('complete', 'opaque-token-1234567890'); await lockScreen.sync({identity:'pull:private/repo:9:',running:true}, true); const stale = await lockScreen.consumeAction('complete', 'opaque-token-1234567890'); process.stdout.write(JSON.stringify({first,replay,stale,actions,messages,stored:[...values.entries()]})); """) assert result["first"] is True assert result["replay"] is False assert result["stale"] is False assert result["actions"] == [["complete", "issue:private/repo:42:"]] assert all("private/repo" not in json.dumps(message) for message in result["messages"]) def test_cold_launch_finish_action_is_removed_from_history_before_completion(): result = run_scenario(r""" await lockScreen.enable(); await lockScreen.sync({identity:'issue:private/repo:42:',running:true}, true); locationRef.href='https://forge.example/dashboard/?today_timer_action=complete&today_action_token=opaque-token-1234567890#/my-work/today'; const consumed = await lockScreen.consumeLaunchAction(); process.stdout.write(JSON.stringify({consumed,actions,href:locationRef.href})); """) assert result == { "consumed": True, "actions": [["complete", "issue:private/repo:42:"]], "href": "https://forge.example/dashboard/#/my-work/today", } def test_permission_denial_is_truthful_and_does_not_persist_opt_in(): result = run_scenario(r""" NotificationRef.requestPermission=async()=>{permission='denied';return permission;}; const enabled = await lockScreen.enable(); process.stdout.write(JSON.stringify({enabled,checked:control.checked,status:status.textContent,stored:[...values.entries()]})); """) assert result == { "enabled": False, "checked": False, "status": "Lock-screen controls are blocked in browser settings.", "stored": [], } def test_lock_screen_flow_is_wired_into_the_packaged_today_journey(): index = (ROOT / "frontend" / "index.html").read_text() dashboard = (ROOT / "frontend" / "dashboard.js").read_text() bundle = (ROOT / "src" / "frontend_bundle.py").read_text() assert 'id="today-lock-screen"' in index assert 'id="today-lock-screen-status"' in index assert '' in index assert index.index('static/today-lock-screen.js') < index.index('static/dashboard.js') assert 'createTodayLockScreen({' in dashboard assert "action === 'complete'" in dashboard assert "completeTodayItem(item)" in dashboard assert "todayLockScreen.consumeLaunchAction()" in dashboard assert dashboard.index("const completeTodayItem = createTodayCompletion({") < dashboard.index("todayLockScreen.consumeLaunchAction()") assert "todayLockScreen.sync(snapshot, workSession.checkpointed())" in dashboard assert "todayLockScreen.consumeLaunchAction()" in dashboard assert '"static/today-lock-screen.js"' in bundle