feat: add offline delivery center (Closes #481)
CI / lint (pull_request) Successful in 54s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-10 11:57:30 +00:00
1 parent b667963968
commit 9dede4bf2a
8 files changed
+177 -7

No files matched your search

+24
View File
@@ -77,6 +77,30 @@ outbox.flush('timmy').then(result => process.stdout.write(JSON.stringify({{persi
assert output["remaining"] == []
def test_authored_outbox_exposes_sending_then_preserves_transient_attempt_details():
script = f"""
const createAuthoredOutbox = require({json.dumps(str(OUTBOX))});
const values = new Map();
const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}};
let release; const gate = new Promise(resolve => release = resolve);
const outbox = createAuthoredOutbox({{
storage, getOwnerLogin:()=>'timmy', now:()=>777,
fetchJson:async()=>{{await gate; const error=new Error('Network unavailable');error.status=503;throw error;}},
}});
outbox.enqueue({{kind:'issue-comment',repository:'o/r',number:1,body:'Wait',operationId:'attempt'}});
const pending=outbox.flush('timmy');
const sending=outbox.list()[0];
release();
pending.then(()=>process.stdout.write(JSON.stringify({{sending,after:outbox.list()[0]}})));
"""
output = run_node(script)
assert output["sending"]["status"] == "sending"
assert output["after"]["status"] == "queued"
assert output["after"]["lastAttemptAt"] == 777
assert output["after"]["lastAttemptError"] == "Network unavailable"
def test_authored_outbox_persists_and_delivers_complete_pull_review_payload():
script = f"""
const createAuthoredOutbox = require({json.dumps(str(OUTBOX))});
+48
View File
@@ -225,6 +225,36 @@ process.stdout.write(JSON.stringify(drafts));
assert by_id["mine"]["quarantined"] is False
def test_draft_inbox_partitions_delivery_center_and_counts_only_safe_waiting_retries():
script = f"""
const createDraftInbox = require({json.dumps(str(DRAFTS))});
const values = new Map([
['stackchain.issue-comment.v1:stackchain/api#17', 'Unfinished comment'],
['stackchain.issue-outbox.v1', JSON.stringify({{version:3,items:[
{{id:'waiting',repository:'o/r',title:'Waiting issue',body:'Body',ownerLogin:'timmy',status:'queued',queuedAt:100,lastAttemptAt:90,lastAttemptError:'Network unavailable'}},
{{id:'sending',repository:'o/r',title:'Sending issue',body:'Body',ownerLogin:'timmy',status:'sending',queuedAt:200}},
{{id:'attention',repository:'o/r',title:'Broken issue',body:'Body',ownerLogin:'timmy',status:'attention',queuedAt:300}},
{{id:'other-user',repository:'o/r',title:'Private issue',body:'Body',ownerLogin:'alexander',status:'queued',queuedAt:400}}
]}})],
['stackchain.authored-outbox.v1', JSON.stringify({{version:2,items:[
{{id:'uncertain',kind:'issue-comment',repository:'o/r',number:4,body:'Maybe sent',ownerLogin:'timmy',status:'attention',deliveryState:'uncertain',queuedAt:500}}
]}})],
]);
const storage = {{get length(){{return values.size}},key:i=>Array.from(values.keys())[i]||null,getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}};
const inbox = createDraftInbox({{storage,getCurrentLogin:()=>'timmy'}});
process.stdout.write(JSON.stringify(inbox.partition()));
"""
output = run_node(script)
assert len(output["drafts"]) == 1
assert len(output["deliveries"]) == 5
assert output["counts"] == {"waiting": 2, "sending": 1, "attention": 2}
assert [item["outbox_id"] for item in output["retryable"]] == ["waiting"]
waiting = next(item for item in output["deliveries"] if item["outbox_id"] == "waiting")
assert waiting["last_attempt_at"] == 90
assert waiting["last_attempt_error"] == "Network unavailable"
@pytest.mark.anyio
async def test_mobile_dashboard_exposes_touch_safe_draft_recovery_lane():
html = await dashboard()
@@ -245,6 +275,24 @@ async def test_mobile_dashboard_exposes_touch_safe_draft_recovery_lane():
assert "error.code = payload.detail?.code" in html
@pytest.mark.anyio
async def test_mobile_dashboard_renders_delivery_center_separately_and_retries_waiting_work():
html = await dashboard()
assert 'class="delivery-center"' in html
assert 'id="retry-waiting-deliveries"' in html
assert 'Waiting <strong data-delivery-count="waiting">' in html
assert 'Sending <strong data-delivery-count="sending">' in html
assert 'Needs attention <strong data-delivery-count="attention">' in html
assert "const deliveryCenter = draftInbox.partition(lastDrafts);" in html
assert "await Promise.all([issueOutbox.flush(activeFlushLogin), authoredOutbox.flush(activeFlushLogin)])" in html
assert "deliveryCenter.retryable.length" in html
assert "item.status === 'sending' ? 'Sending'" in html
assert "item.last_attempt_error ? '<span class=\"delivery-attempt small\">Last attempt '" in html
assert '.delivery-center { display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;' in html
assert '.delivery-center button { min-height:44px;' in html
@pytest.mark.anyio
async def test_dashboard_only_flushes_account_bound_outboxes_after_a_fresh_identity_snapshot():
html = await dashboard()
+24
View File
@@ -202,6 +202,30 @@ outbox.flush('timmy').then(result => process.stdout.write(JSON.stringify({{resul
assert output["remaining"][0]["status"] == "queued"
def test_issue_outbox_exposes_sending_then_preserves_transient_attempt_details():
script = f"""
const createIssueOutbox = require({json.dumps(str(OUTBOX))});
const values = new Map();
const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}};
let release; const gate = new Promise(resolve => release = resolve);
const outbox = createIssueOutbox({{
storage, getOwnerLogin:()=>'timmy', createOperationId:()=>'attempt', now:()=>888,
fetchJson:async()=>{{await gate;const error=new Error('Gateway timeout');error.status=503;throw error;}},
}});
outbox.enqueue({{repository:'o/r',title:'Wait',body:'Context'}});
const pending=outbox.flush('timmy');
const sending=outbox.list()[0];
release();
pending.then(()=>process.stdout.write(JSON.stringify({{sending,after:outbox.list()[0]}})));
"""
output = run_node(script)
assert output["sending"]["status"] == "sending"
assert output["after"]["status"] == "queued"
assert output["after"]["lastAttemptAt"] == 888
assert output["after"]["lastAttemptError"] == "Gateway timeout"
def test_foreground_attachment_retry_does_not_create_a_second_issue():
script = f"""
const createIssueOutbox = require({json.dumps(str(OUTBOX))});