import json import subprocess from pathlib import Path import pytest from tests.dashboard_bundle import dashboard HANDOFF = Path(__file__).resolve().parents[1] / "frontend" / "update-review-handoff.js" def run_handoff(script): source = f"const createHandoff = require({json.dumps(str(HANDOFF))});\n" + script result = subprocess.run(["node", "-e", source], capture_output=True, text=True) assert result.returncode == 0, result.stderr return json.loads(result.stdout) def test_review_handoff_only_admits_review_requested_pull_updates(): result = run_handoff(""" const handoff = createHandoff({openReview:()=>{}, admitRead:async()=>{}, advance:()=>{}}); const cases = [ {is_review:true, update_reason:'Review requested', subject_type:'Pull', repository:'acme/app', number:7, notification_id:41}, {is_review:true, update_reason:'Mentioned', subject_type:'Pull', repository:'acme/app', number:7, notification_id:42}, {is_review:true, update_reason:'Review requested', subject_type:'Issue', repository:'acme/app', number:7, notification_id:43}, {is_review:false, update_reason:'Review requested', subject_type:'Pull', repository:'acme/app', number:7, notification_id:44}, ]; process.stdout.write(JSON.stringify(cases.map(item => handoff.eligible(item)))); """) assert result == [True, False, False, False] def test_review_handoff_preserves_update_on_cancel_and_advances_after_read_admission(): result = run_handoff(""" (async () => { const events = []; const update = {is_review:true, update_reason:'Review requested', subject_type:'Pull', repository:'acme/app', number:7, notification_id:41, title:'Ship it'}; const handoff = createHandoff({ openReview:item=>events.push(['open', item.repository, item.number]), restoreUpdate:item=>events.push(['restore', item.notification_id]), admitRead:async id=>events.push(['read', id]), advance:()=>events.push(['advance']), }); handoff.begin(update); handoff.cancel(); handoff.begin(update); const completed = await handoff.complete(); process.stdout.write(JSON.stringify({events, completed, active:handoff.active()})); })().catch(error => { console.error(error); process.exit(1); }); """) assert result == { "events": [ ["open", "acme/app", 7], ["restore", 41], ["open", "acme/app", 7], ["read", 41], ["advance"], ], "completed": True, "active": False, } def test_review_handoff_keeps_current_update_when_read_admission_fails(): result = run_handoff(""" (async () => { const events = []; const update = {is_review:true, update_reason:'Review requested', subject_type:'Pull', repository:'acme/app', number:7, notification_id:41}; const handoff = createHandoff({ openReview:()=>events.push('open'), admitRead:async()=>{ events.push('read'); throw new Error('queue unavailable'); }, advance:()=>events.push('advance'), }); handoff.begin(update); let message = ''; try { await handoff.complete(); } catch (error) { message = error.message; } process.stdout.write(JSON.stringify({events, message, active:handoff.active()})); })().catch(error => { console.error(error); process.exit(1); }); """) assert result == {"events": ["open", "read"], "message": "queue unavailable", "active": True} @pytest.mark.anyio async def test_dashboard_wires_review_requested_update_into_existing_review_workspace(): html = await dashboard() assert '' in html assert 'id="review-update-now"' in html assert "updateReviewHandoff.begin(selectedUpdate)" in html assert "await updateReviewHandoff.complete()" in html assert "updateReviewHandoff.cancel()" in html assert "updateReviewHandoff.eligible(detail" in html