fix: bind Today progress retries to evidence (Closes #1056)
This commit is contained in:
1 parent
157d485685
commit
1410a66a36
4 files changed
+160
-21
No files matched your search
@@ -15,6 +15,44 @@ def run_node(script: str):
|
||||
return json.loads(result.stdout)
|
||||
|
||||
|
||||
def test_repeated_operation_id_rejects_a_different_comment_payload():
|
||||
script = f"""
|
||||
const createAuthoredOutbox = require({json.dumps(str(OUTBOX))});
|
||||
const values = new Map();
|
||||
const storage = {{getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value)}};
|
||||
const outbox=createAuthoredOutbox({{storage,getOwnerLogin:()=> 'timmy'}});
|
||||
const original={{kind:'issue-comment',repository:'o/r',number:7,body:'Done',operationId:'stable',attachments:[{{filename:'before.jpg',contentType:'image/jpeg',note:'Before',operationId:'photo-1'}}]}};
|
||||
const identical=outbox.enqueue(original);
|
||||
let error='';
|
||||
try {{ outbox.enqueue({{...original,attachments:[{{filename:'after.jpg',contentType:'image/jpeg',note:'After',operationId:'photo-2'}}]}}); }}
|
||||
catch (caught) {{ error=caught.message; }}
|
||||
process.stdout.write(JSON.stringify({{identical,error,items:outbox.list()}}));
|
||||
"""
|
||||
output = run_node(script)
|
||||
assert output["identical"]["operationId"] == "stable"
|
||||
assert output["error"] == "This operation ID is already bound to a different queued action."
|
||||
assert len(output["items"]) == 1
|
||||
assert output["items"][0]["attachment"]["operationId"] == "photo-1"
|
||||
|
||||
|
||||
def test_repeated_pull_review_operation_uses_the_default_comment_decision():
|
||||
script = f"""
|
||||
const createAuthoredOutbox = require({json.dumps(str(OUTBOX))});
|
||||
const values = new Map();
|
||||
const storage = {{getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value)}};
|
||||
const outbox=createAuthoredOutbox({{storage,getOwnerLogin:()=> 'timmy'}});
|
||||
const review={{kind:'pull-review',repository:'o/r',number:8,body:'Notes',expectedHeadSha:'abc',operationId:'review-op'}};
|
||||
outbox.enqueue(review);
|
||||
let error='';
|
||||
try {{ outbox.enqueue(review); }} catch (caught) {{ error=caught.message; }}
|
||||
process.stdout.write(JSON.stringify({{error,items:outbox.list()}}));
|
||||
"""
|
||||
output = run_node(script)
|
||||
assert output["error"] == ""
|
||||
assert len(output["items"]) == 1
|
||||
assert output["items"][0]["decision"] == "comment"
|
||||
|
||||
|
||||
def test_authored_outbox_quarantines_legacy_and_cross_account_messages():
|
||||
script = f"""
|
||||
const createAuthoredOutbox = require({json.dumps(str(OUTBOX))});
|
||||
|
||||
@@ -123,25 +123,59 @@ process.stdout.write(JSON.stringify({{error,retained,after:progress.load(target.
|
||||
}
|
||||
|
||||
|
||||
def test_photo_cleanup_failure_retries_with_the_same_comment_operation():
|
||||
def test_photo_payload_changes_rotate_the_progress_operation_identity():
|
||||
script = f"""
|
||||
const createProgress = require({json.dumps(str(TODAY_PROGRESS))});
|
||||
const values = new Map(); const ids = ['progress-1', 'progress-2', 'progress-3'];
|
||||
const storage = {{getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)}};
|
||||
const progress=createProgress({{storage,getLogin:()=> 'timmy',makeId:()=>ids.shift(),admit:async()=>({{}})}});
|
||||
const identity='issue:stackchain/dashboard:12:';
|
||||
const before={{filename:'proof.jpg',contentType:'image/jpeg',note:'Before',operationId:'photo-1'}};
|
||||
const replacement={{filename:'proof.jpg',contentType:'image/jpeg',note:'After',operationId:'photo-2'}};
|
||||
progress.save(identity, 'Repair complete', [before]);
|
||||
const first=JSON.parse(values.values().next().value).drafts[identity].operation_id;
|
||||
progress.save(identity, 'Repair complete', [before]);
|
||||
const unchanged=JSON.parse(values.values().next().value).drafts[identity].operation_id;
|
||||
progress.save(identity, 'Repair complete', [replacement]);
|
||||
const replaced=JSON.parse(values.values().next().value).drafts[identity].operation_id;
|
||||
replacement.note='Redacted replacement';
|
||||
progress.save(identity, 'Repair complete', [replacement]);
|
||||
const recaptioned=JSON.parse(values.values().next().value).drafts[identity].operation_id;
|
||||
process.stdout.write(JSON.stringify({{first,unchanged,replaced,recaptioned}}));
|
||||
"""
|
||||
output = run_node(script)
|
||||
assert output == {
|
||||
"first": "progress-1",
|
||||
"unchanged": "progress-1",
|
||||
"replaced": "progress-2",
|
||||
"recaptioned": "progress-3",
|
||||
}
|
||||
|
||||
|
||||
def test_photo_cleanup_failure_resumes_cleanup_without_readmitting_delivery():
|
||||
script = f"""
|
||||
const createProgress = require({json.dumps(str(TODAY_PROGRESS))});
|
||||
const values=new Map();
|
||||
const storage={{getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)}};
|
||||
const calls=[]; let cleanupFails=true;
|
||||
const progress=createProgress({{storage,getLogin:()=> 'timmy',makeId:()=> 'stable-progress-op',admit:async message=>{{calls.push(message);return {{background:true}};}}}});
|
||||
const calls=[]; let cleanupFails=true; let cleanupCalls=0;
|
||||
const options={{storage,getLogin:()=> 'timmy',makeId:()=> 'stable-progress-op',admit:async message=>{{calls.push(message);return {{background:true}};}}}};
|
||||
let progress=createProgress(options);
|
||||
const target={{identity:'pull:stackchain/dashboard:22:',kind:'pull',repository:'stackchain/dashboard',number:22}};
|
||||
const photo={{filename:'proof.webp',contentType:'image/webp',blob:{{size:9}},operationId:'photo-op'}};
|
||||
const cleanup=async()=>{{if(cleanupFails)throw new Error('draft cleanup blocked')}};
|
||||
let error='';
|
||||
try {{await progress.post(target,'',[photo],cleanup);}} catch(caught){{error=caught.message;}}
|
||||
const cleanup=async()=>{{cleanupCalls++;if(cleanupFails)throw new Error('draft cleanup blocked')}};
|
||||
let error=''; let deliveryAdmitted=false;
|
||||
try {{await progress.post(target,'',[photo],cleanup);}} catch(caught){{error=caught.message;deliveryAdmitted=caught.deliveryAdmitted===true;}}
|
||||
progress=createProgress(options);
|
||||
cleanupFails=false;
|
||||
await progress.post(target,undefined,[photo],cleanup);
|
||||
process.stdout.write(JSON.stringify({{error,calls,after:progress.load(target.identity)}}));
|
||||
const recovered=await progress.post(target,'edited text must not be admitted',[{{...photo,operationId:'replacement'}}],cleanup);
|
||||
process.stdout.write(JSON.stringify({{error,deliveryAdmitted,calls,cleanupCalls,recovered,after:progress.load(target.identity)}}));
|
||||
"""
|
||||
output = run_node("(async()=>{" + script + "})().catch(error=>{console.error(error);process.exit(1)})")
|
||||
assert output["error"] == "draft cleanup blocked"
|
||||
assert [call["operationId"] for call in output["calls"]] == ["stable-progress-op", "stable-progress-op"]
|
||||
assert output["error"] == "Progress update is already queued; photo cleanup is pending."
|
||||
assert output["deliveryAdmitted"] is True
|
||||
assert len(output["calls"]) == 1
|
||||
assert output["cleanupCalls"] == 2
|
||||
assert output["recovered"]["alreadyAdmitted"] is True
|
||||
assert output["after"] == ""
|
||||
|
||||
|
||||
@@ -192,6 +226,9 @@ def test_mobile_progress_sheet_is_accessible_bundled_and_safe_area_aware():
|
||||
assert "lanes:{ today:" in TODAY_PROGRESS.read_text()
|
||||
assert "photos:todayProgressPhotos" in dashboard
|
||||
assert "const controller = issueAttachment.mount" in TODAY_PROGRESS.read_text()
|
||||
assert "const checkpointAttachments = await photos?.serialize?.() || [];" in TODAY_PROGRESS.read_text()
|
||||
assert "progress.save(openedTarget.identity, body.value, checkpointAttachments)" in TODAY_PROGRESS.read_text()
|
||||
assert "error.deliveryAdmitted" in TODAY_PROGRESS.read_text()
|
||||
assert ".today-progress-panel" in css
|
||||
assert "env(safe-area-inset-bottom)" in css
|
||||
assert ".today-progress-actions button" in css and "min-height:44px" in css
|
||||
Reference in new issue
Block a user