import json import subprocess from pathlib import Path import pytest from tests.dashboard_bundle import dashboard MODULE = Path(__file__).parents[1] / "frontend" / "offline-issue-blocker.js" def run_node(script: str): completed = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) return json.loads(completed.stdout) def test_offline_blocker_reports_queued_only_after_durable_admission(): script = f""" const createOfflineIssueBlocker=require({json.dumps(str(MODULE))}); const events=[]; const queue=createOfflineIssueBlocker({{ createOperationId:()=> 'blocker-op', enqueueDurably:async message=>{{events.push(['admit',message]);return{{item:message,durability:'background'}};}}, onQueued:(item,blocker,present,admission)=>events.push(['queued',item.number,blocker.number,present,admission.durability]), }}); (async()=>{{ const result=await queue( {{repository:'stackchain/dashboard',number:17}}, {{repository:'stackchain/api',number:9,title:'Restore API'}}, false ); process.stdout.write(JSON.stringify({{events,result}})); }})(); """ output = run_node(script) assert output["events"] == [ ["admit", { "kind": "issue-blocker", "repository": "stackchain/dashboard", "number": 17, "blockerRepository": "stackchain/api", "blockerNumber": 9, "present": False, "operationId": "blocker-op", }], ["queued", 17, 9, False, "background"], ] assert output["result"]["admission"]["durability"] == "background" def test_offline_blocker_does_not_report_queued_when_durable_admission_fails(): script = f""" const createOfflineIssueBlocker=require({json.dumps(str(MODULE))}); let queued=false; const queue=createOfflineIssueBlocker({{ enqueueDurably:async()=>{{throw new Error('IndexedDB unavailable');}}, onQueued:()=>queued=true, }}); queue({{repository:'o/r',number:1}},{{repository:'o/b',number:2}},true) .then(()=>process.stdout.write(JSON.stringify({{queued}}))) .catch(error=>process.stdout.write(JSON.stringify({{queued,error:error.message}}))); """ output = run_node(script) assert output == {"queued": False, "error": "IndexedDB unavailable"} @pytest.mark.anyio async def test_saved_issue_exposes_offline_blocker_queue_in_mobile_sheet(): html = await dashboard() dashboard_js = (MODULE.parent / "dashboard.js").read_text() service_worker = (MODULE.parent / "service-worker.js").read_text() assert '' in html assert "BASE + 'static/offline-issue-blocker.js'" in service_worker assert "const queueOfflineIssueBlocker = createOfflineIssueBlocker({" in dashboard_js assert "qs('#manage-issue-blockers').hidden = !assignedIssue;" in dashboard_js assert "await queueOfflineIssueBlocker(item, blocker, !remove);" in dashboard_js assert "Blocker removal queued. Today remains blocked until Stackchain confirms delivery." in dashboard_js assert "const candidates = selectedIssueOffline ? workSession.items()" in dashboard_js