import json import subprocess from pathlib import Path import pytest WORKER = Path(__file__).resolve().parents[1] / "frontend" / "service-worker.js" PRIVATE_DATA_REGISTRY = WORKER.parent / "private-data-registry.js" def run_worker_scenario(scenario: str) -> dict: harness = f""" const fs = require('fs'); const vm = require('vm'); const listeners = {{}}; const state = {{ added: [], addAttempts: [], individuallyAdded: [], failedAdds: [], deleted: [], deletedDatabases: [], claimed: false, skipped: false, fetches: [], puts: [], migrated: [], activationOrder: [], oldCachedAssets: {{}}, sharedRecords: {{}}, todayCommands: {{}}, failTodayCommandPut: false, failSharedPut: false, backgroundFlushes: 0, backgroundResumes: 0, outboxPurges: 0, outboxLifecycle: [], notifications: [], closedNotifications: 0, focused: [], opened: [], failFetch: false, stallFetch: false, lateFetch: false, fetchAborted: false, fetchStatus: 200, fetchRedirected: false, cachedBody: null }}; const storedResponses = new Map(); storedResponses.set( 'https://forge.example/dashboard/__offline-session-lease', new Response(JSON.stringify({{ expires_at: Math.floor(Date.now() / 1000) + 3600, idle_expires_at: Math.floor(Date.now() / 1000) + 900, }})), ); const cache = {{ addAll: async urls => {{ state.added = urls; }}, add: async url => {{ state.addAttempts.push(url); if (state.failedAdds.includes(url)) throw new Error('optional asset unavailable'); state.individuallyAdded.push(url); }}, match: async request => {{ const key = String(request.url || request); if (storedResponses.has(key)) return storedResponses.get(key).clone(); return state.cachedBody === null ? null : new Response(state.cachedBody); }}, put: async (request, response) => {{ const key = String(request.url || request); state.puts.push(key); state.migrated.push(key); state.activationOrder.push('put:' + key); storedResponses.set(new URL(key, 'https://forge.example').href, response.clone()); }}, }}; const oldCache = {{ match: async request => {{ const key = String(request.url || request); const body = state.oldCachedAssets[key]; return body === undefined ? null : new Response(body); }}, }}; const context = {{ URL, Request, Response, Headers, AbortController, setTimeout, clearTimeout, console, self: {{ location: {{ href: 'https://forge.example/dashboard/service-worker.js', origin: 'https://forge.example' }}, __STACKCHAIN_NAVIGATION_TIMEOUT_MS: 15, __STACKCHAIN_PUSH_ACTION_TIMEOUT_MS: 15, stackchainPrivateDatabases: require({json.dumps(str(PRIVATE_DATA_REGISTRY))}), __STACKCHAIN_SHARED_ATTACHMENT_STORE: {{ put: async (id, value) => {{ if (state.failSharedPut) throw new Error('quota'); state.sharedRecords[id] = value; }}, get: async id => state.sharedRecords[id] || null, delete: async id => {{ delete state.sharedRecords[id]; }}, }}, __STACKCHAIN_TODAY_ACTION_STORE: {{ put: async (clientId, command) => {{ if (state.failTodayCommandPut) throw new Error('quota'); state.todayCommands[clientId] = command; }}, claim: async (clientId, now) => {{ const command = state.todayCommands[clientId] || null; delete state.todayCommands[clientId]; return command && command.expiresAt > now ? command : null; }}, purge: async () => {{ state.todayCommands = {{}}; }}, }}, addEventListener: (name, handler) => {{ listeners[name] = handler; }}, skipWaiting: async () => {{ state.skipped = true; }}, clients: {{ claim: async () => {{ state.claimed = true; }}, matchAll: async () => state.clientList || [], openWindow: async url => {{ state.opened.push(url); return state.openedClient || {{id:'opened-client',url}}; }}, }}, registration: {{ showNotification: async (title, options) => state.notifications.push({{title,options}}), getNotifications: async () => [{{close:()=>{{state.closedNotifications += 1;}}}}], }}, __issueSync: {{ flush: async () => {{ state.backgroundFlushes += 1; state.outboxLifecycle.push('flush'); return state.flushResult; }}, purge: async () => {{ state.outboxPurges += 1; state.outboxLifecycle.push('purge'); }}, resume: async () => {{ state.backgroundResumes += 1; state.outboxLifecycle.push('resume'); }}, getReceiptPreference: async login => state.receiptLogin === login, }}, }}, importScripts: () => {{}}, indexedDB: {{deleteDatabase:name=>{{ state.deletedDatabases.push(name); const request={{}}; queueMicrotask(()=>request.onsuccess?.()); return request; }}}}, caches: {{ open: async key => key === 'stackchain-dashboard-old' ? oldCache : cache, keys: async () => ['stackchain-dashboard-old', 'another-app-cache'], delete: async key => {{ state.deleted.push(key); state.activationOrder.push('delete:' + key); return true; }}, match: async request => cache.match(request), }}, fetch: async (request, options = {{}}) => {{ state.fetches.push(String(request.url || request)); if (state.failFetch) throw new Error('offline'); if (state.stallFetch) return new Promise((resolve, reject) => {{ options.signal?.addEventListener('abort', () => {{ state.fetchAborted = true; reject(new Error('aborted')); }}, {{ once: true }}); }}); if (state.lateFetch) return new Promise(resolve => {{ options.signal?.addEventListener('abort', () => {{ state.fetchAborted = true; }}, {{ once: true }}); setTimeout(() => resolve(new Response('late network')), 50); }}); const response = new Response('network', {{ status: state.fetchStatus }}); Object.defineProperty(response, 'redirected', {{ value: state.fetchRedirected }}); return response; }}, }}; vm.createContext(context); vm.runInContext(fs.readFileSync({json.dumps(str(WORKER))}, 'utf8') + '\\nself.__testFetchJson = fetchJson; self.__testWithSessionCsrf = withSessionCsrf; self.__testOptionalFeatures = OPTIONAL_FEATURES;', context); async function dispatch(name, request) {{ let pending; let response; listeners[name]({{ request, waitUntil: promise => {{ pending = promise; }}, respondWith: promise => {{ response = promise; }}, }}); if (pending) await pending; return response ? await response : null; }} async function dispatchSync(tag) {{ let pending; listeners.sync({{ tag, waitUntil: promise => {{ pending = promise; }} }}); if (pending) await pending; }} async function dispatchMessage(data, ports = [], source = null) {{ let pending; listeners.message({{ data, ports, source, waitUntil: promise => {{ pending = promise; }} }}); if (pending) await pending; }} async function dispatchNotificationClick(route, action = '', notificationId = null, tag = null, url = null, actionToken = '') {{ let pending; listeners.notificationclick({{ action, notification: {{tag: tag || ('stackchain-update-' + notificationId), data: {{route, notificationId, ...(url ? {{url}} : {{}}), ...(actionToken ? {{actionToken}} : {{}})}}, close: () => {{ state.notificationClosed = true; }}}}, waitUntil: promise => {{ pending = promise; }}, }}); if (pending) await pending; }} async function dispatchPush(payload) {{ let pending; listeners.push({{ data: {{json: () => payload}}, waitUntil: promise => {{ pending = promise; }}, }}); if (pending) await pending; }} (async () => {{ {scenario} }})().catch(error => {{ console.error(error); process.exit(1); }}); """ completed = subprocess.run( ["node", "-e", harness], capture_output=True, check=True, text=True ) return json.loads(completed.stdout) def test_private_today_action_mailbox_rolls_the_offline_shell(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source def test_per_day_week_conflict_ui_rolls_the_offline_shell(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/week-plan.js'" in source def test_resumable_today_session_ships_in_a_new_offline_shell(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/my-work.js'" in source assert "BASE + 'static/dashboard.js'" in source assert "BASE + 'static/dashboard.css'" in source def test_mobile_conversation_photo_bundles_roll_the_offline_shell(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/dashboard.js'" in source assert "BASE + 'static/authored-outbox.js'" in source assert "BASE + 'static/background-issue-sync.js'" in source def test_photo_metadata_sanitizer_rolls_the_cached_optimizer_atomically(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/issue-evidence-review.js'" in source assert "BASE + 'static/issue-attachment.js'" in source def test_ownership_exit_runtime_rolls_the_offline_shell_cache(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/dashboard.js'" in source def test_offline_review_next_ships_today_completion_atomically(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/today-completion.js'" in source assert "BASE + 'static/dashboard.js'" in source def test_duplicate_aware_capture_ships_in_a_new_offline_shell(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/create-issue-sheet.js'" in source assert "BASE + 'static/dashboard.js'" in source def test_inline_checklist_step_flow_rolls_the_offline_shell_atomically(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/issue-sheet.js'" in source assert "BASE + 'static/checklist-conflict.js'" in source assert "BASE + 'static/dashboard.js'" in source assert "BASE + 'static/dashboard.css'" in source def test_exact_later_picker_ships_atomically_in_a_new_offline_shell(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/later-picker.js'" in source def test_navigation_deadline_ships_in_a_new_shell_cache(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/dashboard.css'" in source assert "BASE + 'static/dashboard.js'" in source assert "BASE + 'static/install-app.js'" in source def test_today_convergence_ships_in_a_new_shell_cache(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/today-sync.js'" in source def test_mobile_search_viewport_ships_in_a_new_offline_shell(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/mobile-search-viewport.js'" in source def test_update_ownership_flow_ships_atomically_in_a_new_offline_shell(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/update-ownership.js'" in source def test_image_share_target_stages_one_supported_image_and_redirects_to_capture(): result = run_worker_scenario( """ const image = new Blob(['pixels'], {type:'image/png'}); Object.defineProperty(image, 'name', {value:'bug.png'}); const form = new FormData(); form.append('title', 'Broken checkout'); form.append('text', 'Steps to reproduce'); form.append('url', 'https://example.test/checkout'); form.append('image', image, 'bug.png'); const request = new Request('https://forge.example/dashboard/share-target', { method:'POST', body:form, headers:{'Sec-Fetch-Site':'none'}, }); Object.defineProperty(request, 'mode', {value:'navigate'}); const response = await dispatch('fetch', request); const [id, record] = Object.entries(state.sharedRecords)[0]; process.stdout.write(JSON.stringify({ status:response.status, location:response.headers.get('Location'), id, title:record.title, text:record.text, url:record.url, filename:record.attachments[0].filename, contentType:record.attachments[0].contentType, size:record.attachments[0].blob.size, })); """ ) assert result == { "status": 303, "location": "/dashboard/?launch=new&shared=bundle", "id": "shared-image", "title": "Broken checkout", "text": "Steps to reproduce", "url": "https://example.test/checkout", "filename": "bug.png", "contentType": "image/png", "size": 6, } def test_image_share_target_stages_ordered_evidence_bundle(): result = run_worker_scenario( """ const form = new FormData(); form.append('image', new Blob(['one'], {type:'image/png'}), 'one.png'); form.append('image', new Blob(['two'], {type:'image/jpeg'}), 'two.jpg'); const request = new Request('https://forge.example/dashboard/share-target', { method:'POST', body:form, headers:{'Sec-Fetch-Site':'same-origin'}, }); Object.defineProperty(request, 'mode', {value:'navigate'}); const response = await dispatch('fetch', request); const record=state.sharedRecords['shared-image']; process.stdout.write(JSON.stringify({ status:response.status, location:response.headers.get('Location'), names:record.attachments.map(value=>value.filename),sizes:record.attachments.map(value=>value.blob.size), })); """ ) assert result == { "status": 303, "location": "/dashboard/?launch=new&shared=bundle", "names": ["one.png", "two.jpg"], "sizes": [3, 3], } def test_share_target_rejects_cross_site_posts_without_touching_staged_bundle(): result = run_worker_scenario( """ state.sharedRecords['shared-image']={title:'Keep me',text:'private',url:'',attachments:[]}; const form=new FormData();form.append('title','Replace me'); const request=new Request('https://forge.example/dashboard/share-target',{ method:'POST',body:form,headers:{'Sec-Fetch-Site':'cross-site'}, }); Object.defineProperty(request,'mode',{value:'navigate'}); const response=await dispatch('fetch',request); process.stdout.write(JSON.stringify({status:response.status,record:state.sharedRecords['shared-image']})); """ ) assert result == { "status": 403, "record": {"title": "Keep me", "text": "private", "url": "", "attachments": []}, } def test_invalid_share_target_preserves_the_previous_atomic_bundle(): result = run_worker_scenario( """ state.sharedRecords['shared-image']={title:'Keep me',text:'private',url:'',attachments:[]}; const form=new FormData(); form.append('image',new Blob(['payload'],{type:'application/pdf'}),'secret.pdf'); const request=new Request('https://forge.example/dashboard/share-target',{ method:'POST',body:form,headers:{'Sec-Fetch-Site':'none'}, }); Object.defineProperty(request,'mode',{value:'navigate'}); const response=await dispatch('fetch',request); process.stdout.write(JSON.stringify({location:response.headers.get('Location'),record:state.sharedRecords['shared-image']})); """ ) assert result == { "location": "/dashboard/?launch=new&shared=unsupported", "record": {"title": "Keep me", "text": "private", "url": "", "attachments": []}, } def test_share_target_storage_failure_preserves_bundle_and_returns_recovery_marker(): result = run_worker_scenario( """ state.sharedRecords['shared-image']={title:'Keep me',text:'private',url:'',attachments:[]}; state.failSharedPut=true; const form=new FormData();form.append('title','New private content'); const request=new Request('https://forge.example/dashboard/share-target',{ method:'POST',body:form,headers:{'Sec-Fetch-Site':'none'}, }); Object.defineProperty(request,'mode',{value:'navigate'}); const response=await dispatch('fetch',request); process.stdout.write(JSON.stringify({location:response.headers.get('Location'),record:state.sharedRecords['shared-image']})); """ ) assert result == { "location": "/dashboard/?launch=new&shared=unavailable", "record": {"title": "Keep me", "text": "private", "url": "", "attachments": []}, } def test_share_target_manifest_uses_dedicated_admission_path(): manifest = json.loads((WORKER.parent / "manifest.webmanifest").read_text()) assert manifest["share_target"]["action"] == "./share-target" def test_background_sync_event_flushes_closed_app_issue_outbox_only_for_its_tag(): result = run_worker_scenario( """ await dispatchSync('stackchain-issue-outbox-v1'); await dispatchSync('another-app-sync'); process.stdout.write(JSON.stringify(state)); """ ) assert result["backgroundFlushes"] == 1 def test_background_delivery_preserves_structured_uncertain_error(): result = run_worker_scenario( """ context.fetch=async()=>new Response(JSON.stringify({detail:{code:'delivery_uncertain',message:'Verify it was not posted before retrying.'}}),{status:422,headers:{'Content-Type':'application/json'}}); const outcome=await context.self.__testFetchJson('/dashboard/api/v1/repos/o/r/issues',{method:'POST'}) .then(()=>({ok:true}),error=>({status:error.status,code:error.code,message:error.message})); process.stdout.write(JSON.stringify(outcome)); """ ) assert result == { "status": 422, "code": "delivery_uncertain", "message": "Verify it was not posted before retrying.", } def test_revoked_background_session_purges_worker_data_and_notifies_dashboard_clients(): result = run_worker_scenario( """ state.clientMessages=[]; state.clientList=[{postMessage:message=>state.clientMessages.push(message)}]; context.fetch=async()=>new Response(JSON.stringify({detail:'Authentication required',code:'session_revoked'}),{status:401,headers:{'Content-Type':'application/json'}}); const outcome=await context.self.__testFetchJson('/dashboard/api/v1/repos/o/r/issues',{method:'POST'}) .then(()=>({ok:true}),error=>({status:error.status,code:error.code})); process.stdout.write(JSON.stringify({state,outcome})); """ ) assert result["outcome"] == {"status": 401, "code": "session_revoked"} assert result["state"]["outboxPurges"] == 1 assert result["state"]["deletedDatabases"] == [ "stackchain-background-outbox-v1", "stackchain-offline-work-v2", "stackchain-unfiled-captures-v1", "stackchain-voice-transcripts-v1", "stackchain-search-reply-drafts-v1", "stackchain-conversation-reply-drafts-v1", "stackchain-today-action-mailbox-v1", ] assert result["state"]["deleted"] == ["stackchain-dashboard-old"] assert result["state"]["clientMessages"] == [ {"type": "stackchain-session-revoked"} ] def test_idle_background_session_preserves_outbox_and_notifies_dashboard_clients(): result = run_worker_scenario( """ state.clientMessages=[]; state.clientList=[{postMessage:message=>state.clientMessages.push(message)}]; context.fetch=async()=>new Response(JSON.stringify({detail:'Authentication required',code:'session_idle'}),{status:401,headers:{'Content-Type':'application/json'}}); const outcome=await context.self.__testFetchJson('/dashboard/api/v1/repos/o/r/issues',{method:'POST'}) .then(()=>({ok:true}),error=>({status:error.status,code:error.code})); process.stdout.write(JSON.stringify({state,outcome})); """ ) assert result["outcome"] == {"status": 401, "code": "session_idle"} assert result["state"]["outboxPurges"] == 0 assert result["state"]["deleted"] == [] assert result["state"]["clientMessages"] == [ {"type": "stackchain-session-idle"} ] def test_authenticated_page_message_resumes_queued_background_delivery(): result = run_worker_scenario( """ await dispatchMessage({type:'stackchain-resume-outbox'}); process.stdout.write(JSON.stringify(state)); """ ) assert result["backgroundFlushes"] == 1 def test_authenticated_resume_rearms_a_previously_purged_worker_before_flushing(): result = run_worker_scenario( """ const replies = []; await dispatchMessage({type:'stackchain-purge-outbox'}, [{postMessage:value=>replies.push(value)}]); await dispatchMessage({type:'stackchain-resume-outbox'}); process.stdout.write(JSON.stringify({state,replies})); """ ) assert result["replies"] == [{"ok": True}] assert result["state"]["outboxLifecycle"] == ["purge", "resume", "flush"] assert result["state"]["backgroundResumes"] == 1 def test_active_today_message_replaces_one_privacy_safe_lock_screen_notification(): result = run_worker_scenario( """ await dispatchMessage({type:'stackchain-today-lock-screen',active:true,running:true,identity:'secret/repo#42',actionToken:'opaque-token-1234567890'}); process.stdout.write(JSON.stringify(state)); """ ) assert result["notifications"] == [ { "title": "Today session running", "options": { "body": "Your active Today timer is running.", "tag": "stackchain-today-session", "renotify": False, "silent": True, "actions": [ {"action": "pause-today", "title": "Pause"}, {"action": "finish-today", "title": "Finish current"}, ], "data": {"route": "#/my-work/today", "actionToken": "opaque-token-1234567890"}, }, } ] assert "secret" not in json.dumps(result["notifications"]) def test_live_today_break_shows_return_time_and_safe_resume_controls(): result = run_worker_scenario( """ const deadline = Date.now() + 5 * 60 * 1000; await dispatchMessage({type:'stackchain-today-lock-screen',active:true,running:false,breakDeadlineAt:deadline,identity:'secret/repo#42',actionToken:'opaque-token-1234567890'}); process.stdout.write(JSON.stringify(state)); """ ) notification = result["notifications"][0] assert notification["title"] == "On a Today break" assert notification["options"]["body"].startswith("Return at ") assert notification["options"]["actions"] == [ {"action": "resume-today", "title": "Resume now"}, {"action": "open-today", "title": "Open Today"}, ] assert notification["options"]["data"]["actionToken"] == "opaque-token-1234567890" assert "secret" not in json.dumps(notification) def test_invalid_or_expired_break_deadline_falls_back_to_paused_controls(): result = run_worker_scenario( """ await dispatchMessage({type:'stackchain-today-lock-screen',active:true,running:false,breakDeadlineAt:Date.now()-1,actionToken:'opaque-token-1234567890'}); process.stdout.write(JSON.stringify(state)); """ ) assert result["notifications"][0]["title"] == "Today session paused" assert result["notifications"][0]["options"]["actions"][0] == { "action": "resume-today", "title": "Resume" } def test_inactive_today_message_closes_the_lock_screen_notification(): result = run_worker_scenario( """ await dispatchMessage({type:'stackchain-today-lock-screen',active:false,running:false}); process.stdout.write(JSON.stringify(state)); """ ) assert result["notifications"] == [] assert result["closedNotifications"] == 1 def test_private_data_purge_also_removes_the_today_lock_screen_notification(): result = run_worker_scenario( """ await dispatchMessage({type:'stackchain-purge-outbox'}, [{postMessage:()=>{}}]); process.stdout.write(JSON.stringify(state)); """ ) assert result["outboxPurges"] == 1 assert result["closedNotifications"] == 1 def test_today_lock_screen_action_updates_an_open_dashboard_without_navigation(): result = run_worker_scenario( """ state.clientMessages=[]; state.clientList=[{ url:'https://forge.example/dashboard/#/my-work/today', postMessage:message=>state.clientMessages.push(message), focus:async()=>state.focused.push('today'), }]; await dispatchNotificationClick('#/my-work/today','pause-today',null,'stackchain-today-session'); process.stdout.write(JSON.stringify(state)); """ ) assert result["clientMessages"] == [ {"type": "stackchain-today-timer-action", "action": "pause"} ] assert result["focused"] == ["today"] assert result["opened"] == [] def test_today_lock_screen_action_opens_clean_route_and_privately_hands_off_once_when_closed(): result = run_worker_scenario( """ state.openedClient={id:'cold-client',url:'https://forge.example/dashboard/#/my-work/today'}; await dispatchNotificationClick('#/my-work/today','resume-today',null,'stackchain-today-session',null,'opaque-token-1234567890'); const messages=[]; const source={id:'cold-client',url:'https://forge.example/dashboard/#/my-work/today',postMessage:message=>messages.push(message)}; await dispatchMessage({type:'stackchain-claim-today-action'}, [], source); await dispatchMessage({type:'stackchain-claim-today-action'}, [], source); state.claimMessages=messages; process.stdout.write(JSON.stringify(state)); """ ) assert result["opened"] == [ "https://forge.example/dashboard/#/my-work/today" ] assert "today_timer_action" not in result["opened"][0] assert "today_action_token" not in result["opened"][0] assert result["claimMessages"] == [{ "type": "stackchain-today-timer-action", "action": "resume", "actionToken": "opaque-token-1234567890", }] assert result["todayCommands"] == {} def test_today_lock_screen_private_handoff_rejects_wrong_client_and_expires_closed(): result = run_worker_scenario( """ state.openedClient={id:'intended-client',url:'https://forge.example/dashboard/#/my-work/today'}; await dispatchNotificationClick('#/my-work/today','finish-today',null,'stackchain-today-session',null,'opaque-token-1234567890'); const wrong=[]; await dispatchMessage({type:'stackchain-claim-today-action'}, [], {id:'other-client',url:'https://forge.example/dashboard/#/my-work/today',postMessage:message=>wrong.push(message)}); state.todayCommands['intended-client'].expiresAt=0; const expired=[]; await dispatchMessage({type:'stackchain-claim-today-action'}, [], {id:'intended-client',url:'https://forge.example/dashboard/#/my-work/today',postMessage:message=>expired.push(message)}); process.stdout.write(JSON.stringify({wrong,expired,commands:state.todayCommands})); """ ) assert result == {"wrong": [], "expired": [], "commands": {}} def test_today_action_mailbox_persistence_failure_opens_clean_route_without_mutation(): result = run_worker_scenario( """ state.failTodayCommandPut=true; state.openedClient={id:'cold-client',url:'https://forge.example/dashboard/#/my-work/today'}; const outcome=await dispatchNotificationClick('#/my-work/today','finish-today',null,'stackchain-today-session',null,'opaque-token-1234567890') .then(()=> 'safe', error=>error.message); process.stdout.write(JSON.stringify({outcome,opened:state.opened,commands:state.todayCommands})); """ ) assert result == { "outcome": "safe", "opened": ["https://forge.example/dashboard/#/my-work/today"], "commands": {}, } def test_private_data_purge_removes_pending_today_action_handoffs(): result = run_worker_scenario( """ state.openedClient={id:'cold-client',url:'https://forge.example/dashboard/#/my-work/today'}; await dispatchNotificationClick('#/my-work/today','finish-today',null,'stackchain-today-session',null,'opaque-token-1234567890'); await dispatchMessage({type:'stackchain-purge-outbox'}, [{postMessage:()=>{}}]); const messages=[]; await dispatchMessage({type:'stackchain-claim-today-action'}, [], {id:'cold-client',url:'https://forge.example/dashboard/#/my-work/today',postMessage:message=>messages.push(message)}); process.stdout.write(JSON.stringify({messages,commands:state.todayCommands})); """ ) assert result == {"messages": [], "commands": {}} def test_finish_today_lock_screen_action_forwards_only_its_opaque_token(): result = run_worker_scenario( """ state.clientMessages=[]; state.clientList=[{ url:'https://forge.example/dashboard/#/my-work/today', postMessage:message=>state.clientMessages.push(message), focus:async()=>state.focused.push('today'), }]; await dispatchNotificationClick('#/my-work/today','finish-today',null,'stackchain-today-session',null,'opaque-token-1234567890'); process.stdout.write(JSON.stringify(state)); """ ) assert result["clientMessages"] == [{ "type": "stackchain-today-timer-action", "action": "complete", "actionToken": "opaque-token-1234567890", }] def test_break_resume_action_forwards_opaque_token_to_reject_stale_breaks(): result = run_worker_scenario( """ state.clientMessages=[]; state.clientList=[{ url:'https://forge.example/dashboard/#/my-work/today', postMessage:message=>state.clientMessages.push(message), focus:async()=>state.focused.push('today'), }]; await dispatchNotificationClick('#/my-work/today','resume-today',null,'stackchain-today-session',null,'opaque-token-1234567890'); process.stdout.write(JSON.stringify(state)); """ ) assert result["clientMessages"] == [{ "type": "stackchain-today-timer-action", "action": "resume", "actionToken": "opaque-token-1234567890", }] def test_background_mutation_abort_also_cancels_stalled_csrf_lookup(): result = run_worker_scenario( """ let mutations=0; context.fetch=(request, options={})=>{ if(String(request).includes('/api/v1/session')){ return new Promise((resolve,reject)=>options.signal?.addEventListener('abort',()=>reject(new Error('aborted')), {once:true})); } mutations+=1; return Promise.resolve(new Response('{}',{status:200,headers:{'Content-Type':'application/json'}})); }; const controller=new AbortController(); const request=context.self.__testWithSessionCsrf(() => context.self.__testFetchJson('/dashboard/api/v1/repos/o/r/issues',{method:'POST',signal:controller.signal}) ).then(()=> 'completed', error=>error.message); controller.abort(); const outcome=await Promise.race([request,new Promise(resolve=>setTimeout(()=>resolve('blocked'),40))]); process.stdout.write(JSON.stringify({outcome,mutations})); process.exit(0); """ ) assert result == {"outcome": "aborted", "mutations": 0} def test_device_purge_message_stops_worker_outbox_and_acknowledges_completion(): result = run_worker_scenario( """ const replies = []; await dispatchMessage({type:'stackchain-purge-outbox'}, [{postMessage: value => replies.push(value)}]); process.stdout.write(JSON.stringify({state,replies})); """ ) assert result["state"]["outboxPurges"] == 1 assert result["state"]["deletedDatabases"] == [ "stackchain-background-outbox-v1", "stackchain-offline-work-v2", "stackchain-unfiled-captures-v1", "stackchain-voice-transcripts-v1", "stackchain-search-reply-drafts-v1", "stackchain-conversation-reply-drafts-v1", "stackchain-today-action-mailbox-v1", ] assert result["replies"] == [{"ok": True}] def test_opted_in_background_sync_notifies_privately_and_receipt_tap_focuses_route(): result = run_worker_scenario( """ state.receiptLogin = 'timmy'; state.flushResult = {login:'timmy', receipts:[ {id:'capture-1',status:'confirmed',kind:'issue',route:'#/my-work/issue/stackchain/api/44'}, {id:'bad-1',status:'attention',kind:'message',route:'#/my-work/drafts'}, {id:'review-1',status:'authorization',kind:'message',route:'#/my-work/drafts'}, ]}; state.clientList = [{url:'https://forge.example/dashboard/', navigate:async function(url){ this.url=url; }, focus:async function(){ state.focused.push(this.url); }}]; await dispatchSync('stackchain-issue-outbox-v1'); await dispatchNotificationClick('#/my-work/issue/stackchain/api/44'); process.stdout.write(JSON.stringify(state)); """ ) assert result["notifications"] == [ { "title": "Queued issue created", "options": { "body": "Tap to open it in Stackchain.", "tag": "stackchain-delivery-capture-1", "data": {"route": "#/my-work/issue/stackchain/api/44"}, }, }, { "title": "Queued work needs attention", "options": { "body": "Tap to review it in Drafts.", "tag": "stackchain-delivery-bad-1", "data": {"route": "#/my-work/drafts"}, }, }, { "title": "Queued review needs authorization", "options": { "body": "Tap to authorize it in the Delivery center.", "tag": "stackchain-delivery-review-1", "data": {"route": "#/my-work/drafts"}, }, }, ] assert result["focused"] == [ "https://forge.example/dashboard/#/my-work/issue/stackchain/api/44" ] assert result["opened"] == [] assert result["notificationClosed"] is True def test_created_issue_receipt_tap_opens_the_confirmed_canonical_gitea_url(): result = run_worker_scenario( """ const issueUrl = 'https://forge.example/git/stackchain/api/issues/44'; state.receiptLogin = 'timmy'; state.flushResult = {login:'timmy', receipts:[ {id:'capture-1',status:'confirmed',kind:'issue',url:issueUrl}, ]}; state.clientList = [{url:'https://forge.example/dashboard/', navigate:async function(url){ this.url=url; }, focus:async function(){ state.focused.push(this.url); }}]; await dispatchSync('stackchain-issue-outbox-v1'); await dispatchNotificationClick('', '', null, 'stackchain-delivery-capture-1', issueUrl); process.stdout.write(JSON.stringify(state)); """ ) assert result["notifications"] == [{ "title": "Queued issue created", "options": { "body": "Tap to open it in Stackchain.", "tag": "stackchain-delivery-capture-1", "data": {"url": "https://forge.example/git/stackchain/api/issues/44"}, }, }] assert result["focused"] == [ "https://forge.example/git/stackchain/api/issues/44" ] assert result["opened"] == [] assert result["notificationClosed"] is True def test_inbound_push_renders_generic_update_and_tap_opens_existing_workflow(): result = run_worker_scenario( """ await dispatchPush({ title:'New work update', body:'Tap to review it in Stackchain.', tag:'stackchain-update-42', route:'#/my-work/update/42', notification_id:42, repository:'must-not-render', }); await dispatchNotificationClick('#/my-work/update/42'); process.stdout.write(JSON.stringify(state)); """ ) assert result["notifications"] == [{ "title": "New work update", "options": { "body": "Tap to review it in Stackchain.", "tag": "stackchain-update-42", "actions": [ {"action": "mark-read", "title": "Mark read"}, {"action": "tomorrow", "title": "Tomorrow"}, ], "data": { "route": "#/my-work/update/42", "notificationId": 42, }, }, }] assert result["opened"] == [ "https://forge.example/dashboard/#/my-work/update/42" ] assert "must-not-render" not in json.dumps(result["notifications"]) def test_start_day_push_is_private_and_prepare_action_opens_cached_launch_route(): result = run_worker_scenario( """ await dispatchPush({ title:'must-not-render', body:'private/repo#42', tag:'stackchain-start-day-2026-08-20', route:'#/my-work/start-day', plan_date:'2026-08-20', }); await dispatchNotificationClick( '#/my-work/start-day','prepare-today',null,'stackchain-start-day-2026-08-20' ); process.stdout.write(JSON.stringify(state)); """ ) assert result["notifications"] == [{ "title": "Your planned day is ready", "options": { "body": "Open Stackchain to prepare Today.", "tag": "stackchain-start-day-2026-08-20", "actions": [{"action": "prepare-today", "title": "Prepare Today"}], "data": {"route": "#/my-work/start-day"}, }, }] assert result["opened"] == [ "https://forge.example/dashboard/#/my-work/start-day" ] assert "private/repo" not in json.dumps(result["notifications"]) def test_update_digest_push_opens_unread_inbox_without_item_actions_or_private_copy(): result = run_worker_scenario( """ await dispatchPush({ title:'must-not-render', body:'private details must-not-render', tag:'stackchain-update-digest', route:'#/my-work/updates', update_count:7, }); await dispatchNotificationClick('#/my-work/updates'); process.stdout.write(JSON.stringify(state)); """ ) assert result["notifications"] == [{ "title": "7 new work updates", "options": { "body": "Tap to review them in Stackchain.", "tag": "stackchain-update-digest", "data": {"route": "#/my-work/updates"}, }, }] assert result["opened"] == [ "https://forge.example/dashboard/#/my-work/updates" ] assert "must-not-render" not in json.dumps(result["notifications"]) def test_deadline_digest_push_offers_protect_today_and_snooze_without_rendering_private_copy(): result = run_worker_scenario( """ await dispatchPush({ title:'must-not-render', body:'private details must-not-render', tag:'stackchain-deadline-digest-2026-08-13', route:'#/my-work/agenda', protect_route:'#/my-work/agenda/protect-today', deadline_count:3, }); await dispatchNotificationClick('#/my-work/agenda/protect-today', 'protect-today', null, 'stackchain-deadline-digest-2026-08-13'); process.stdout.write(JSON.stringify(state)); """ ) assert result["notifications"] == [{ "title": "3 deadlines need attention", "options": { "body": "Open Agenda to review or replan them.", "tag": "stackchain-deadline-digest-2026-08-13", "actions": [ {"action": "protect-today", "title": "Protect Today"}, {"action": "snooze-deadline", "title": "Remind in 1 hour"}, ], "data": { "route": "#/my-work/agenda", "protectRoute": "#/my-work/agenda/protect-today", }, }, }] assert result["opened"] == [ "https://forge.example/dashboard/#/my-work/agenda/protect-today" ] assert "must-not-render" not in json.dumps(result["notifications"]) def test_deadline_digest_snooze_action_records_device_bound_reminder_without_opening_app(): result = run_worker_scenario( """ await dispatchPush({ tag:'stackchain-deadline-digest-2026-08-13', route:'#/my-work/agenda', protect_route:'#/my-work/agenda/protect-today', deadline_count:1, }); const calls = []; context.fetch = async (request, options = {}) => { const url = String(request.url || request); const headers = new Headers(options.headers || {}); calls.push({url, method:String(options.method || 'GET'), csrf:headers.get('X-CSRF-Token')}); if (url.endsWith('/api/v1/session')) { return new Response(JSON.stringify({csrf_token:'session-proof'}), { status:200, headers:{'Content-Type':'application/json'}, }); } return new Response(JSON.stringify({snoozed:true}), { status:200, headers:{'Content-Type':'application/json'}, }); }; await dispatchNotificationClick('#/my-work/agenda', 'snooze-deadline', null, 'stackchain-deadline-digest-2026-08-13'); process.stdout.write(JSON.stringify({state,calls})); """ ) assert result["calls"] == [ { "url": "https://forge.example/dashboard/api/v1/session", "method": "GET", "csrf": None, }, { "url": "https://forge.example/dashboard/api/v1/push-subscription/deadlines/snooze", "method": "PATCH", "csrf": "session-proof", }, ] assert result["state"]["notificationClosed"] is True assert result["state"]["opened"] == [] def test_deadline_digest_failed_snooze_opens_agenda_instead_of_losing_reminder(): result = run_worker_scenario( """ context.fetch = async () => { throw new Error('offline'); }; await dispatchNotificationClick( '#/my-work/agenda', 'snooze-deadline', null, 'stackchain-deadline-digest-2026-08-13' ); process.stdout.write(JSON.stringify(state)); """ ) assert result["opened"] == ["https://forge.example/dashboard/#/my-work/agenda"] assert result["notificationClosed"] is True def test_push_mark_read_action_confirms_authenticated_mutation_without_opening_app(): result = run_worker_scenario( """ const calls = []; context.fetch = async (request, options = {}) => { const url = String(request.url || request); const headers = new Headers(options.headers || {}); calls.push({url, method:String(options.method || 'GET'), csrf:headers.get('X-CSRF-Token')}); if (url.endsWith('/api/v1/session')) { return new Response(JSON.stringify({csrf_token:'session-proof'}), { status:200, headers:{'Content-Type':'application/json'}, }); } return new Response(JSON.stringify({ok:true}), { status:200, headers:{'Content-Type':'application/json'}, }); }; await dispatchNotificationClick('#/my-work/update/42', 'mark-read', 42); process.stdout.write(JSON.stringify({state,calls})); """ ) assert result["calls"] == [ { "url": "https://forge.example/dashboard/api/v1/session", "method": "GET", "csrf": None, }, { "url": "https://forge.example/dashboard/api/v1/notifications/42/read", "method": "PATCH", "csrf": "session-proof", }, ] assert result["state"]["notificationClosed"] is True assert result["state"]["opened"] == [] assert result["state"]["focused"] == [] def test_push_tomorrow_action_defers_privately_without_opening_or_marking_read(): result = run_worker_scenario( """ const calls = []; context.self.__STACKCHAIN_NOW = () => new Date('2026-08-11T18:30:00.000Z'); context.fetch = async (request, options = {}) => { const url = String(request.url || request); const headers = new Headers(options.headers || {}); calls.push({ url, method:String(options.method || 'GET'), csrf:headers.get('X-CSRF-Token'), body:options.body ? JSON.parse(options.body) : null, }); if (url.endsWith('/api/v1/session')) { return new Response(JSON.stringify({csrf_token:'session-proof'}), { status:200, headers:{'Content-Type':'application/json'}, }); } return new Response(JSON.stringify({id:42,status:'deferred'}), { status:200, headers:{'Content-Type':'application/json'}, }); }; await dispatchNotificationClick('#/my-work/update/42', 'tomorrow', 42, 'stackchain-update-42'); process.stdout.write(JSON.stringify({state,calls})); """ ) assert result["calls"] == [ { "url": "https://forge.example/dashboard/api/v1/session", "method": "GET", "csrf": None, "body": None, }, { "url": "https://forge.example/dashboard/api/v1/notifications/42/later", "method": "PATCH", "csrf": "session-proof", "body": {"wake_at": "2026-08-12T09:00:00.000Z"}, }, ] assert result["state"]["notificationClosed"] is True assert result["state"]["opened"] == [] assert all(not call["url"].endswith("/read") for call in result["calls"]) def test_push_mark_read_failure_opens_existing_update_reader(): result = run_worker_scenario( """ state.clientList = [{ url:'https://forge.example/dashboard/', navigate:async function(url){ this.url=url; }, focus:async function(){ state.focused.push(this.url); }, }]; context.fetch = async (request, options = {}) => { const url = String(request.url || request); if (url.endsWith('/api/v1/session')) { return new Response(JSON.stringify({csrf_token:'session-proof'}), { status:200, headers:{'Content-Type':'application/json'}, }); } return new Response(JSON.stringify({error:'unavailable'}), { status:503, headers:{'Content-Type':'application/json'}, }); }; await dispatchNotificationClick('#/my-work/update/42', 'mark-read', 42); process.stdout.write(JSON.stringify(state)); """ ) assert result["focused"] == [ "https://forge.example/dashboard/#/my-work/update/42" ] assert result["opened"] == [] assert result["notificationClosed"] is True def test_push_mark_read_rejects_mismatched_notification_identity(): result = run_worker_scenario( """ const calls = []; context.fetch = async (request, options = {}) => { calls.push({url:String(request.url || request), method:String(options.method || 'GET')}); return new Response(JSON.stringify({csrf_token:'session-proof'}), { status:200, headers:{'Content-Type':'application/json'}, }); }; await dispatchNotificationClick('#/my-work/update/42', 'mark-read', 43); process.stdout.write(JSON.stringify({state,calls})); """ ) assert result["calls"] == [] assert result["state"].get("notificationClosed") is not True assert result["state"]["opened"] == [] def test_push_mark_read_timeout_aborts_request_and_opens_update_reader(): result = run_worker_scenario( """ state.clientList = [{ url:'https://forge.example/dashboard/', navigate:async function(url){ this.url=url; }, focus:async function(){ state.focused.push(this.url); }, }]; context.fetch = async (request, options = {}) => { const url = String(request.url || request); if (url.endsWith('/api/v1/session')) { return new Response(JSON.stringify({csrf_token:'session-proof'}), { status:200, headers:{'Content-Type':'application/json'}, }); } return new Promise((resolve, reject) => { options.signal?.addEventListener('abort', () => { state.actionFetchAborted = true; reject(new Error('aborted')); }, {once:true}); }); }; dispatchNotificationClick('#/my-work/update/42', 'mark-read', 42); await new Promise(resolve => setTimeout(resolve, 50)); process.stdout.write(JSON.stringify(state)); process.exit(0); """ ) assert result["actionFetchAborted"] is True assert result["focused"] == [ "https://forge.example/dashboard/#/my-work/update/42" ] assert result["notificationClosed"] is True def test_background_mutations_obtain_session_bound_csrf_proof(): source = WORKER.read_text() assert "async function sessionCsrf(signal)" in source assert "BASE + 'api/v1/session'" in source assert "signal," in source assert "headers.set('X-CSRF-Token', csrf)" in source def test_one_session_bound_csrf_proof_is_reused_for_a_background_drain(): source = WORKER.read_text() assert "async function withSessionCsrf(work)" in source assert "batchedCsrfController = new AbortController()" in source assert "batchedCsrf = sessionCsrf(batchedCsrfController.signal)" in source assert "batchedCsrfController.abort()" in source assert "await (batchedCsrf || sessionCsrf(options.signal))" in source assert "if (options.signal?.aborted)" in source assert "batch: withSessionCsrf" in source def test_queue_today_ships_atomically_in_a_new_offline_shell(): source = WORKER.read_text() assert "stackchain-dashboard-shell-v127" in source assert "BASE + 'static/queue-today.js'" in source def test_install_precaches_complete_subpath_scoped_app_shell(): result = run_worker_scenario( """ await dispatch('install'); process.stdout.write(JSON.stringify(state)); """ ) assert result["skipped"] is True assert result["added"][0] == "/dashboard/" assert set(result["added"]) == { "/dashboard/", "/dashboard/manifest.webmanifest", "/dashboard/static/dashboard.css", "/dashboard/static/dashboard.js", "/dashboard/static/icons/stackchain-192.png", "/dashboard/static/icons/stackchain-512.png", "/dashboard/static/session.js", "/dashboard/static/sign-out-review.js", "/dashboard/static/feature-loader.js", "/dashboard/static/workspace-bootstrap.js", "/dashboard/static/conversation-action-hydrator.js", "/dashboard/static/security-center.js", "/dashboard/static/markdown.js", "/dashboard/static/commands.js", "/dashboard/static/saved-searches.js", "/dashboard/static/search-preview.js", "/dashboard/static/search-reply-draft-store.js", "/dashboard/static/conversation-reply-draft-store.js", "/dashboard/static/conversation-photo-drafts.js", "/dashboard/static/search-defer.js", "/dashboard/static/widgets.js", "/dashboard/static/drafts.js", "/dashboard/static/unfiled-captures.js", "/dashboard/static/unfiled-draft-sync.js", "/dashboard/static/shared-image-capture.js", "/dashboard/static/draft-filing-session.js", "/dashboard/static/draft-capacity-dialog.js", "/dashboard/static/outbox-coordinator.js", "/dashboard/static/issue-outbox.js", "/dashboard/static/reconnect-outboxes.js", "/dashboard/static/authored-outbox.js", "/dashboard/static/offline-issue-close.js", "/dashboard/static/offline-issue-blocker.js", "/dashboard/static/notification-read-outbox.js", "/dashboard/static/offline-work.js", "/dashboard/static/offline-today.js", "/dashboard/static/my-work.js", "/dashboard/static/agenda-replan.js", "/dashboard/static/agenda-calendar.js", "/dashboard/static/protect-today.js", "/dashboard/static/notification-undo.js", "/dashboard/static/card-planning.js", "/dashboard/static/work-selection.js", "/dashboard/static/today-work.js", "/dashboard/static/today-timer.js", "/dashboard/static/today-break.js", "/dashboard/static/today-progress.js", "/dashboard/static/today-lock-screen.js", "/dashboard/static/today-session-sync.js", "/dashboard/static/today-recap.js", "/dashboard/static/today-wrap-up.js", "/dashboard/static/today-summary.js", "/dashboard/static/today-handoff.js", "/dashboard/static/today-completion.js", "/dashboard/static/today-readiness.js", "/dashboard/static/comment-next.js", "/dashboard/static/update-reply-read-next.js", "/dashboard/static/plan-today.js", "/dashboard/static/plan-today-readiness.js", "/dashboard/static/plan-today-preview.js", "/dashboard/static/tomorrow-plan.js", "/dashboard/static/week-plan.js", "/dashboard/static/today-sync.js", "/dashboard/static/today-rollover.js", "/dashboard/static/update-ownership.js", "/dashboard/static/update-follow-up.js", "/dashboard/static/later-work.js", "/dashboard/static/later-sync.js", "/dashboard/static/later-and-start.js", "/dashboard/static/detail-defer.js", "/dashboard/static/later-picker.js", "/dashboard/static/pick-work.js", "/dashboard/static/batch-find-work.js", "/dashboard/static/search-batch-plan.js", "/dashboard/static/conversation.js", "/dashboard/static/comment-actions.js", "/dashboard/static/issue-evidence-review.js", "/dashboard/static/issue-evidence-editor.js", "/dashboard/static/issue-attachment.js", "/dashboard/static/issue-filing-review.js", "/dashboard/static/issue-sheet.js", "/dashboard/static/mobile-issue-detail-nav.js", "/dashboard/static/mobile-update-detail-nav.js", "/dashboard/static/mobile-review-detail-nav.js", "/dashboard/static/mobile-search-preview-nav.js", "/dashboard/static/mobile-plan-today-nav.js", "/dashboard/static/mobile-find-work-nav.js", "/dashboard/static/mobile-pull-refresh.js", "/dashboard/static/checklist-conflict.js", "/dashboard/static/voice-transcript-store.js", "/dashboard/static/voice-issue-capture.js", "/dashboard/static/voice-conversation-capture.js", "/dashboard/static/create-issue-sheet.js", "/dashboard/static/mobile-create-issue-nav.js", "/dashboard/static/create-and-start.js", "/dashboard/static/assign-and-start.js", "/dashboard/static/filed-claim.js", "/dashboard/static/queue-today.js", "/dashboard/static/pull-sheet.js", "/dashboard/static/review-sheet.js", "/dashboard/static/release-receipt.js", "/dashboard/static/work-route.js", "/dashboard/static/task-overlay-history.js", "/dashboard/static/context-poller.js", "/dashboard/static/live-data-status.js", "/dashboard/static/mobile-today-command-bar.js", "/dashboard/static/mobile-task-dock.js", "/dashboard/static/mobile-first-task.js", "/dashboard/static/mobile-work-entry.js", "/dashboard/static/mobile-queue-launcher.js", "/dashboard/static/mobile-delivery-recovery.js", "/dashboard/static/mobile-start-day.js", "/dashboard/static/update-triage-session.js", "/dashboard/static/update-review-handoff.js", "/dashboard/static/update-read-position.js", "/dashboard/static/work-detail-position.js", "/dashboard/static/update-triage-launcher.js", "/dashboard/static/update-triage-gesture.js", "/dashboard/static/update-decision-transaction.js", "/dashboard/static/agenda-session-launcher.js", "/dashboard/static/mobile-launch.js", "/dashboard/static/mobile-insights.js", "/dashboard/static/mobile-app-shortcuts.js", "/dashboard/static/install-app.js", "/dashboard/static/private-data-inventory.js", "/dashboard/static/private-device-data.js", "/dashboard/static/device-storage.js", "/dashboard/static/mobile-device-setup.js", "/dashboard/static/mobile-search-viewport.js", "/dashboard/static/mobile-composer-viewport.js", "/dashboard/static/mention-composer.js", "/dashboard/static/push-notifications.js", "/dashboard/static/issue-filing-receipt.js", "/dashboard/static/background-issue-sync.js", } def test_activate_deletes_only_stale_stackchain_caches(): result = run_worker_scenario( """ await dispatch('activate'); process.stdout.write(JSON.stringify(state)); """ ) assert result["claimed"] is True assert result["deleted"] == ["stackchain-dashboard-old"] def test_offline_activation_migrates_cached_optional_feature_before_deleting_old_cache(): result = run_worker_scenario( """ const asset = '/dashboard/feature-issue-capture-test.js'; state.oldCachedAssets[asset] = 'previous cached feature'; state.failedAdds = [asset]; context.self.__testOptionalFeatures.push(asset); await dispatch('activate'); state.failFetch = true; const response = await dispatch('fetch', { method: 'GET', mode: 'cors', url: 'https://forge.example/dashboard/feature-issue-capture-test.js', }); process.stdout.write(JSON.stringify({ body: await response.text(), state })); """ ) assert result["body"] == "previous cached feature" state = result["state"] assert state["migrated"] == [ "/dashboard/feature-issue-capture-test.js" ] assert state["individuallyAdded"] == [] assert "/dashboard/feature-issue-capture-test.js" not in state["addAttempts"] assert state["activationOrder"] == [ "put:/dashboard/feature-issue-capture-test.js", "delete:stackchain-dashboard-old", ] assert state["claimed"] is True def test_successful_optional_feature_fetch_is_cached_for_offline_reopen(): result = run_worker_scenario( """ const asset = '/dashboard/feature-today-timer-test.js'; context.self.__testOptionalFeatures.push(asset); const response = await dispatch('fetch', { method: 'GET', mode: 'cors', url: 'https://forge.example/dashboard/feature-today-timer-test.js', }); process.stdout.write(JSON.stringify({ body: await response.text(), state })); """ ) assert result["body"] == "network" assert result["state"]["puts"] == [ "https://forge.example/dashboard/feature-today-timer-test.js" ] def test_activate_warms_optional_features_without_blocking_siblings_or_claim(): result = run_worker_scenario( """ state.failedAdds = ['/dashboard/feature-security-center-test.js']; context.self.__testOptionalFeatures.push( '/dashboard/feature-issue-capture-test.js', '/dashboard/feature-security-center-test.js', '/dashboard/feature-pull-workflow-test.js', ); await dispatch('activate'); process.stdout.write(JSON.stringify(state)); """ ) assert result["claimed"] is True assert result["individuallyAdded"] == [ "/dashboard/feature-issue-capture-test.js", "/dashboard/feature-pull-workflow-test.js", ] def test_warmed_optional_feature_is_served_from_cache_while_offline(): result = run_worker_scenario( """ context.self.__testOptionalFeatures.push('/dashboard/feature-issue-capture-test.js'); state.cachedBody = 'cached feature'; state.failFetch = true; const response = await dispatch('fetch', { method: 'GET', mode: 'cors', url: 'https://forge.example/dashboard/feature-issue-capture-test.js', }); process.stdout.write(JSON.stringify({ body: await response.text(), state })); """ ) assert result["body"] == "cached feature" assert result["state"]["fetches"] == [] def test_offline_navigation_returns_cached_shell_for_share_target_url(): result = run_worker_scenario( """ state.failFetch = true; state.cachedBody = 'cached dashboard'; const response = await dispatch('fetch', { method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/?title=Shared&url=https%3A%2F%2Fexample.com', }); process.stdout.write(JSON.stringify({ body: await response.text(), state })); """ ) assert result["body"] == "cached dashboard" assert result["state"]["fetches"] == [ "https://forge.example/dashboard/?title=Shared&url=https%3A%2F%2Fexample.com" ] def test_expired_offline_lease_purges_private_worker_data_and_refuses_cached_shell(): result = run_worker_scenario( """ await dispatchMessage({type:'stackchain-session-lease', expiresAt:1, idleExpiresAt:1}); state.failFetch = true; state.cachedBody = 'private cached dashboard'; const response = await dispatch('fetch', { method:'GET', mode:'navigate', url:'https://forge.example/dashboard/', }); process.stdout.write(JSON.stringify({ status: response.status, body: await response.text(), state, })); """ ) assert result["status"] == 401 assert result["body"] == "Your Stackchain session expired. Reconnect and sign in." assert result["state"]["outboxPurges"] == 1 assert result["state"]["deletedDatabases"] == [ "stackchain-background-outbox-v1", "stackchain-offline-work-v2", "stackchain-unfiled-captures-v1", "stackchain-voice-transcripts-v1", "stackchain-search-reply-drafts-v1", "stackchain-conversation-reply-drafts-v1", "stackchain-today-action-mailbox-v1", ] assert result["state"]["deleted"] == ["stackchain-dashboard-old"] assert "private cached dashboard" not in result["body"] def test_expired_idle_lease_locks_cached_shell_without_purging_recoverable_work(): result = run_worker_scenario( """ state.clientList = [{postMessage: message => { state.clientMessage = message; }}]; await dispatchMessage({ type:'stackchain-session-lease', expiresAt:Math.floor(Date.now() / 1000) + 3600, idleExpiresAt:1, }); state.failFetch = true; state.cachedBody = 'private cached dashboard'; const response = await dispatch('fetch', { method:'GET', mode:'navigate', url:'https://forge.example/dashboard/', }); process.stdout.write(JSON.stringify({ state, status:response.status, body:await response.text(), cacheControl:response.headers.get('Cache-Control'), })); """ ) assert result["status"] == 401 assert result["body"] == "Your Stackchain session is locked. Reconnect and sign in." assert result["cacheControl"] == "no-store" assert result["state"]["outboxPurges"] == 0 assert result["state"]["deleted"] == [] assert result["state"]["clientMessage"] == {"type": "stackchain-session-idle"} assert "private cached dashboard" not in result["body"] def test_stalled_navigation_is_aborted_and_returns_cached_shell_within_deadline(): result = run_worker_scenario( """ state.stallFetch = true; state.cachedBody = 'cached dashboard'; const response = await Promise.race([ dispatch('fetch', { method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/', }), new Promise(resolve => setTimeout(() => resolve(null), 80)), ]); process.stdout.write(JSON.stringify({ timedOut: response === null, body: response ? await response.text() : null, state, })); """ ) assert result["timedOut"] is False assert result["body"] == "cached dashboard" assert result["state"]["fetchAborted"] is True assert result["state"]["puts"] == [] def test_stalled_navigation_without_cached_shell_returns_deterministic_504(): result = run_worker_scenario( """ state.stallFetch = true; const response = await Promise.race([ dispatch('fetch', { method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/', }), new Promise(resolve => setTimeout(() => resolve(null), 80)), ]); process.stdout.write(JSON.stringify({ status: response?.status || null, body: response ? await response.text() : null, contentType: response?.headers.get('Content-Type') || null, state, })); """ ) assert result["status"] == 504 assert result["body"] == "Stackchain is offline and the dashboard is not cached yet. Reconnect and try again." assert result["contentType"] == "text/plain; charset=utf-8" assert result["state"]["fetchAborted"] is True def test_navigation_deadline_wins_when_fetch_ignores_abort_and_prevents_late_cache_write(): result = run_worker_scenario( """ state.lateFetch = true; state.cachedBody = 'cached dashboard'; const response = await Promise.race([ dispatch('fetch', { method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/', }), new Promise(resolve => setTimeout(() => resolve(null), 35)), ]); const body = response ? await response.text() : null; await new Promise(resolve => setTimeout(resolve, 60)); process.stdout.write(JSON.stringify({timedOut: response === null, body, state})); """ ) assert result["timedOut"] is False assert result["body"] == "cached dashboard" assert result["state"]["fetchAborted"] is True assert result["state"]["puts"] == [] def test_redirected_login_navigation_does_not_replace_cached_dashboard_shell(): result = run_worker_scenario( """ state.fetchRedirected = true; const response = await dispatch('fetch', { method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/?title=Shared', }); process.stdout.write(JSON.stringify({ status: response.status, state })); """ ) assert result["status"] == 200 assert result["state"]["puts"] == [] def test_cross_origin_navigation_is_not_intercepted_or_cached(): result = run_worker_scenario( """ const response = await dispatch('fetch', { method: 'GET', mode: 'navigate', url: 'https://outside.example/page', }); process.stdout.write(JSON.stringify({ intercepted: response !== null, state })); """ ) assert result["intercepted"] is False assert result["state"]["fetches"] == [] assert result["state"]["puts"] == [] @pytest.mark.parametrize("status", [500, 502, 503, 504]) def test_server_outage_navigation_returns_working_cached_shell_without_replacing_it(status): result = run_worker_scenario( f""" state.fetchStatus = {status}; state.cachedBody = 'cached dashboard'; const response = await dispatch('fetch', {{ method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/', }}); process.stdout.write(JSON.stringify({{ status: response.status, body: await response.text(), state }})); """ ) assert result["status"] == 200 assert result["body"] == "cached dashboard" assert result["state"]["puts"] == [] def test_server_outage_without_cached_shell_preserves_network_error(): result = run_worker_scenario( """ state.fetchStatus = 503; const response = await dispatch('fetch', { method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/', }); process.stdout.write(JSON.stringify({ status: response.status, body: await response.text(), state })); """ ) assert result["status"] == 503 assert result["body"] == "network" assert result["state"]["puts"] == [] def test_client_error_navigation_is_not_hidden_by_cached_shell(): result = run_worker_scenario( """ state.fetchStatus = 401; state.cachedBody = 'cached dashboard'; const response = await dispatch('fetch', { method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/', }); process.stdout.write(JSON.stringify({ status: response.status, body: await response.text(), state })); """ ) assert result["status"] == 401 assert result["body"] == "network" assert result["state"]["puts"] == []