feat: use binary screenshot transport (Closes #491)
CI / lint (pull_request) Successful in 1m6s
CI / build-release (pull_request) Successful in 6s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-10 15:03:45 +00:00
1 parent 59b4d5e6bf
commit 16604d697b
15 files changed
+306 -130

No files matched your search

+9 -5
View File
@@ -498,7 +498,7 @@ async def test_mobile_dashboard_loads_and_operates_authored_message_outbox():
assert "authoredOutbox.reconcileBackground(records)" in html
def test_screenshot_comment_admits_bytes_to_indexeddb_without_putting_them_in_localstorage():
def test_screenshot_comment_admits_blob_to_indexeddb_without_putting_bytes_in_localstorage():
script = f"""
const createAuthoredOutbox = require({json.dumps(str(OUTBOX))});
const values = new Map(); const mirrors=[];
@@ -508,12 +508,15 @@ const outbox=createAuthoredOutbox({{
backgroundSync:{{reconcile:async(items,lane)=>mirrors.push({{items,lane}}),requestSync:async()=>{{}}}},
}});
(async()=>{{
const blob=new Blob(['PRIVATE-IMAGE-BYTES'],{{type:'image/png'}});
const admission=await outbox.enqueueDurably({{
kind:'issue-comment',repository:'stackchain/dashboard',number:477,
body:'Broken at 320px',operationId:'comment-image-477',
attachment:{{filename:'phone.png',contentType:'image/png',data:'PRIVATE-IMAGE-BYTES'}},
attachment:{{filename:'phone.png',contentType:'image/png',blob}},
}});
process.stdout.write(JSON.stringify({{admission,local:values.get('stackchain.authored-outbox.v1'),mirrored:mirrors[0]}}));
const durable=mirrors[0].items[0].attachment;
process.stdout.write(JSON.stringify({{admission,local:values.get('stackchain.authored-outbox.v1'),
mirrored:{{lane:mirrors[0].lane,isBlob:durable.blob instanceof Blob,size:durable.blob?.size,text:await durable.blob?.text()}}}}));
}})();
"""
output = run_node(script)
@@ -523,8 +526,9 @@ const outbox=createAuthoredOutbox({{
assert local_item["attachment"] == {
"filename": "phone.png", "contentType": "image/png", "stored": True
}
assert output["mirrored"]["lane"] == "authored"
assert output["mirrored"]["items"][0]["attachment"]["data"] == "PRIVATE-IMAGE-BYTES"
assert output["mirrored"] == {
"lane": "authored", "isBlob": True, "size": 19, "text": "PRIVATE-IMAGE-BYTES"
}
assert output["admission"]["durability"] == "background"
+47 -8
View File
@@ -82,7 +82,10 @@ const store = {{
}};
let uploadAttempts=0;
const fetchJson=async(url,options={{}})=>{{
state.calls.push({{url,key:options.headers?.['Idempotency-Key'],body:options.body&&JSON.parse(options.body)}});
let body=null;
if(options.body instanceof FormData){{const file=options.body.get('file');body={{filename:file.name,multipart:true}};}}
else if(options.body)body=JSON.parse(options.body);
state.calls.push({{url,key:options.headers?.['Idempotency-Key'],body}});
if(url==='api/v1/background-identity')return{{login:'timmy'}};
if(url.endsWith('/issues'))return{{repository:'stackchain/dashboard',number:469,title:'Broken mobile layout'}};
if(url.endsWith('/attachments') && uploadAttempts++ === 0){{const error=new Error('Upload unavailable');error.status=503;throw error;}}
@@ -113,6 +116,37 @@ const fetchJson=async(url,options={{}})=>{{
assert output["second"]["confirmed"][0]["number"] == 469
def test_closed_app_sync_uploads_blob_as_multipart_and_preserves_original_bytes():
script = f"""
const createBackgroundIssueSync=require({json.dumps(str(SYNC))});
const blob=new Blob(['offline-binary'],{{type:'image/png'}});
const item={{id:'binary',operationId:'binary',ownerLogin:'timmy',repository:'o/r',title:'Visual',body:'',labelIds:[],
attachment:{{filename:'phone.png',contentType:'image/png',blob}}}};
let queued=true;
const observed={{}};
const store={{claimNext:async()=>queued?(queued=false,{{...item}}):null,update:async()=>{{}},complete:async()=>{{}},release:async()=>{{}},fail:async()=>{{}},countBlocked:async()=>0}};
const fetchJson=async(url,options={{}})=>{{
if(url==='api/v1/background-identity')return{{login:'timmy'}};
if(url.endsWith('/issues'))return{{number:7}};
if(url.endsWith('/attachments')){{const file=options.body.get('file');Object.assign(observed,{{
isFormData:options.body instanceof FormData,contentTypeHeader:options.headers['Content-Type']||null,
filename:file.name,type:file.type,size:file.size,text:await file.text()
}});return{{markdown:'![phone](url)'}};}}
return{{id:8}};
}};
(async()=>{{await createBackgroundIssueSync({{store,fetchJson}}).flush();process.stdout.write(JSON.stringify(observed));}})();
"""
assert run_node(script) == {
"isFormData": True,
"contentTypeHeader": None,
"filename": "phone.png",
"type": "image/png",
"size": 14,
"text": "offline-binary",
}
def test_capture_attachment_stage_keys_remain_within_idempotency_limit():
script = f"""
const createBackgroundIssueSync=require({json.dumps(str(SYNC))});
@@ -175,7 +209,10 @@ const store={{claimNext:async()=>item?{{...item}}:null,update:async(_id,transfor
fail:async()=>{{}},countBlocked:async()=>0}};
const fetchJson=async(url,options={{}})=>{{
if(url==='api/v1/background-identity')return{{login:'timmy'}};
state.calls.push({{url,key:options.headers?.['Idempotency-Key'],body:options.body&&JSON.parse(options.body)}});
let body=null;
if(options.body instanceof FormData){{const file=options.body.get('file');body={{filename:file.name,multipart:true}};}}
else if(options.body)body=JSON.parse(options.body);
state.calls.push({{url,key:options.headers?.['Idempotency-Key'],body}});
if(url.endsWith('/attachments'))return{{markdown:'![phone.png](https://forge.example/phone.png)'}};
if(commentAttempts++===0){{const error=new Error('Comment unavailable');error.status=503;throw error;}}
return{{id:91}};
@@ -435,7 +472,7 @@ const transaction=work=>{{const run=tail.then(()=>work({{
]
def test_reconciling_a_lightweight_attachment_reference_preserves_indexeddb_bytes():
def test_reconciling_a_lightweight_attachment_reference_preserves_indexeddb_blob():
script = f"""
const createBackgroundIssueSync = require({json.dumps(str(SYNC))});
const records=new Map();let tail=Promise.resolve();
@@ -447,21 +484,23 @@ const transaction=work=>{{const run=tail.then(()=>work({{
const store=createBackgroundIssueSync.createIssueSyncStore({{transaction}});
await store.reconcile([{{
id:'first',operationId:'first',ownerLogin:'timmy',status:'queued',
attachment:{{filename:'one.png',contentType:'image/png',data:'first-image-bytes'}},
attachment:{{filename:'one.png',contentType:'image/png',blob:new Blob(['first-image-bytes'],{{type:'image/png'}})}},
}}]);
await store.reconcile([
{{id:'first',operationId:'first',ownerLogin:'timmy',status:'queued',
attachment:{{filename:'one.png',contentType:'image/png',stored:true}}}},
{{id:'second',operationId:'second',ownerLogin:'timmy',status:'queued',
attachment:{{filename:'two.png',contentType:'image/png',data:'second-image-bytes'}}}},
attachment:{{filename:'two.png',contentType:'image/png',data:'c2Vjb25kLWltYWdlLWJ5dGVz'}}}},
]);
process.stdout.write(JSON.stringify(await store.snapshot()));
const snapshot=await store.snapshot();
process.stdout.write(JSON.stringify({{first:{{isBlob:snapshot[0].attachment.blob instanceof Blob,
text:await snapshot[0].attachment.blob?.text()}},second:snapshot[1].attachment.data}}));
}})();
"""
output = run_node(script)
assert output[0]["attachment"]["data"] == "first-image-bytes"
assert output[1]["attachment"]["data"] == "second-image-bytes"
assert output["first"] == {"isBlob": True, "text": "first-image-bytes"}
assert output["second"] == "c2Vjb25kLWltYWdlLWJ5dGVz"
def test_issue_sync_store_hydrates_one_capture_by_key_without_scanning_all_records():
+54 -27
View File
@@ -18,15 +18,19 @@ def run_node(script: str) -> str:
).stdout
def test_mobile_attachment_prepares_comment_and_reuses_confirmed_upload():
def test_mobile_attachment_prepares_comment_and_reuses_confirmed_binary_upload():
script = f"""
const attachment = require({json.dumps(str(ATTACHMENT))});
const calls = [];
const file = {{name:'checkout.png', type:'image/png', size:8}};
const file = new Blob(['png-bytes'],{{type:'image/png'}}); file.name='checkout.png';
const controller = attachment.create({{
createOperationId: () => 'attachment-comment-471',
readDataUrl: async selected => {{ calls.push('read:' + selected.name); return 'data:image/png;base64,iVBORw0KGgo='; }},
upload: async payload => {{ calls.push(payload); return {{markdown:'![checkout.png](https://forge.example/a.png)'}}; }},
readDataUrl: async () => {{ throw new Error('base64 conversion must not run'); }},
upload: async payload => {{ calls.push({{
repository:payload.repository,number:payload.number,filename:payload.filename,
content_type:payload.content_type,isBlob:payload.blob instanceof Blob,
text:await payload.blob.text(),operation_id:payload.operation_id
}}); return {{markdown:'![checkout.png](https://forge.example/a.png)'}}; }},
}});
controller.select(file);
(async()=>{{
@@ -40,19 +44,17 @@ controller.select(file);
"Layout breaks\n\n![checkout.png](https://forge.example/a.png)"
)
assert output["second"] == output["first"]
assert output["calls"] == [
"read:checkout.png",
{
"repository": "stackchain/api",
"number": 17,
"filename": "checkout.png",
"content_type": "image/png",
"data": "iVBORw0KGgo=",
"operation_id": "attachment-comment-471",
},
]
assert output["calls"] == [{
"repository": "stackchain/api",
"number": 17,
"filename": "checkout.png",
"content_type": "image/png",
"isBlob": True,
"text": "png-bytes",
"operation_id": "attachment-comment-471",
}]
assert output["state"] == {
"name": "checkout.png", "size": 8, "uploaded": True
"name": "checkout.png", "size": 9, "uploaded": True
}
@@ -82,26 +84,50 @@ controller.select({{name:'first.png',type:'image/png',size:8}});
]
def test_selected_screenshot_serializes_for_durable_issue_capture_without_uploading():
def test_selected_screenshot_serializes_as_binary_blob_without_base64_expansion():
script = f"""
const attachment = require({json.dumps(str(ATTACHMENT))});
const calls=[];
const file=new Blob(['binary-image'],{{type:'image/webp'}});
file.name='phone.webp';
const controller=attachment.create({{
readDataUrl:async file=>{{calls.push('read:'+file.name);return 'data:image/webp;base64,UklGRg==';}},
readDataUrl:async()=>{{throw new Error('base64 conversion must not run');}},
upload:async()=>{{calls.push('upload');}},
}});
controller.select({{name:'phone.webp',type:'image/webp',size:8}});
controller.serialize().then(value=>process.stdout.write(JSON.stringify({{value,calls}})));
controller.select(file);
controller.serialize().then(async value=>process.stdout.write(JSON.stringify({{
filename:value.filename,contentType:value.contentType,
isBlob:value.blob instanceof Blob,size:value.blob.size,text:await value.blob.text(),calls
}})));
"""
output = json.loads(run_node(script))
assert output == {
"value": {
"filename": "phone.webp",
"contentType": "image/webp",
"data": "UklGRg==",
},
"calls": ["read:phone.webp"],
"filename": "phone.webp",
"contentType": "image/webp",
"isBlob": True,
"size": 12,
"text": "binary-image",
"calls": [],
}
def test_binary_screenshot_builds_multipart_body_with_original_bytes():
script = f"""
const attachment=require({json.dumps(str(ATTACHMENT))});
const blob=new Blob(['original-bytes'],{{type:'image/png'}});
const form=attachment.multipart({{filename:'phone.png',contentType:'image/png',blob}});
const file=form.get('file');
(async()=>process.stdout.write(JSON.stringify({{
filename:file.name,type:file.type,size:file.size,text:await file.text()
}})))();
"""
assert json.loads(run_node(script)) == {
"filename": "phone.png",
"type": "image/png",
"size": 14,
"text": "original-bytes",
}
@@ -220,7 +246,7 @@ process.stdout.write(JSON.stringify({{invalid,selected,removed:{{hidden:preview.
}
def test_issue_comment_actions_upload_before_posting_and_clear_after_acceptance():
def test_issue_comment_actions_upload_binary_multipart_before_posting_and_clear_after_acceptance():
source = DASHBOARD.read_text()
assert "issueAttachment.mount({" in source
@@ -229,6 +255,7 @@ def test_issue_comment_actions_upload_before_posting_and_clear_after_acceptance(
assert "issueAttachmentController.prepareComment(selectedIssue, body)" in source
assert source.count("issueAttachmentController.clear();") >= 2
assert "'Idempotency-Key': payload.operation_id" in source
assert "body: issueAttachment.multipart(payload)" in source
def test_closing_issue_sheet_cannot_carry_a_screenshot_to_another_issue():
+26
View File
@@ -58,6 +58,32 @@ async def test_attachment_endpoint_uploads_valid_screenshot_to_assigned_issue(mo
]
@pytest.mark.anyio
async def test_attachment_endpoint_accepts_binary_multipart_without_base64_expansion(monkeypatch):
calls = []
async def upload(repository, number, filename, content_type, content):
calls.append((repository, number, filename, content_type, content))
return {
"name": filename,
"url": "https://forge.example/attachments/checkout.png",
"size": len(content),
}
monkeypatch.setattr(main.gitea_proxy, "upload_assigned_issue_attachment", upload)
transport = httpx.ASGITransport(app=main.app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post(
"/api/v1/repos/stackchain/api/issues/17/attachments",
files={"file": ("checkout.png", PNG_BYTES, "image/png")},
)
assert response.status_code == 201
assert calls == [
("stackchain/api", 17, "checkout.png", "image/png", PNG_BYTES)
]
@pytest.mark.anyio
async def test_attachment_endpoint_replays_confirmed_upload_for_same_key(monkeypatch):
calls = []
+25 -9
View File
@@ -234,20 +234,27 @@ const calls=[];let uploadAttempts=0;
const outbox=createIssueOutbox({{
storage,getOwnerLogin:()=>'timmy',createOperationId:()=>'image-op',
fetchJson:async(url,options={{}})=>{{
calls.push({{url,key:options.headers?.['Idempotency-Key'],body:options.body&&JSON.parse(options.body)}});
const form=options.body instanceof FormData ? options.body : null;
const file=form?.get('file');
calls.push({{url,key:options.headers?.['Idempotency-Key'],multipart:Boolean(form),
filename:file?.name,text:file?await file.text():null}});
if(url.endsWith('/issues'))return{{repository:'o/r',number:7,title:'Visual bug'}};
if(url.endsWith('/attachments') && uploadAttempts++ === 0){{const error=new Error('offline');error.status=503;throw error;}}
if(url.endsWith('/attachments'))return{{markdown:'![screen.png](<https://forge/a.png>)'}};
return{{id:8}};
}},
}});
const queued=outbox.enqueue({{repository:'o/r',title:'Visual bug',attachment:{{filename:'screen.png',contentType:'image/png',data:'abc'}}}});
const queued=outbox.enqueue({{repository:'o/r',title:'Visual bug',attachment:{{filename:'screen.png',contentType:'image/png',data:'b2ZmbGluZQ=='}}}});
(async()=>{{await outbox.flush('timmy');const partial=outbox.list()[0];const result=await outbox.retry(queued.id,'timmy');process.stdout.write(JSON.stringify({{calls,partial,result,remaining:outbox.list()}}));}})();
"""
output = run_node(script)
assert output["partial"]["deliveredIssue"]["number"] == 7
assert [call["url"] for call in output["calls"]].count("api/v1/repos/o/r/issues") == 1
uploads = [call for call in output["calls"] if call["url"].endswith("/attachments")]
assert uploads[-1]["multipart"] is True
assert uploads[-1]["filename"] == "screen.png"
assert uploads[-1]["text"] == "offline"
assert output["calls"][-1]["url"] == "api/v1/repos/o/r/issues/7/comments"
assert output["result"]["confirmed"][0]["number"] == 7
assert output["remaining"] == []
@@ -452,14 +459,14 @@ Promise.resolve().then(async () => {{
assert output["items"][0]["title"] == "Keep this"
def test_durable_screenshot_is_mirrored_before_a_payload_free_local_index_is_committed():
def test_durable_screenshot_is_mirrored_as_blob_before_a_payload_free_local_index_is_committed():
script = f"""
const createIssueOutbox = require({json.dumps(str(OUTBOX))});
const values = new Map(); const events = []; const snapshots = [];
const storage = {{
getItem:key => values.get(key) || null,
setItem:(key,value) => {{
if (value.includes('base64-screenshot-bytes')) throw new Error('screenshot leaked into localStorage');
if (value.includes('binary-screenshot-bytes')) throw new Error('screenshot leaked into localStorage');
events.push('local-index'); values.set(key,value);
}},
}};
@@ -471,26 +478,35 @@ const outbox = createIssueOutbox({{
}},
}});
(async () => {{
const blob=new Blob(['binary-screenshot-bytes'],{{type:'image/webp'}});
const result = await outbox.enqueueDurably({{
repository:'stackchain/dashboard', title:'Mobile layout',
attachment:{{filename:'phone.webp',contentType:'image/webp',data:'base64-screenshot-bytes'}},
attachment:{{filename:'phone.webp',contentType:'image/webp',blob}},
}});
const durable=snapshots[0][0].attachment;
process.stdout.write(JSON.stringify({{
events, mirrored:snapshots[0][0], local:outbox.list()[0], result:result.item,
raw:values.get('stackchain.issue-outbox.v1'),
events, mirrored:{{filename:durable.filename,contentType:durable.contentType,
isBlob:durable.blob instanceof Blob,size:durable.blob?.size,text:await durable.blob?.text()}},
local:outbox.list()[0], result:result.item, raw:values.get('stackchain.issue-outbox.v1'),
}}));
}})();
"""
output = run_node(script)
assert output["events"] == ["indexeddb", "local-index", "sync"]
assert output["mirrored"]["attachment"]["data"] == "base64-screenshot-bytes"
assert output["mirrored"] == {
"filename": "phone.webp",
"contentType": "image/webp",
"isBlob": True,
"size": 23,
"text": "binary-screenshot-bytes",
}
assert output["local"]["attachment"] == {
"filename": "phone.webp",
"contentType": "image/webp",
"stored": True,
}
assert "base64-screenshot-bytes" not in output["raw"]
assert "binary-screenshot-bytes" not in output["raw"]
assert output["result"]["attachment"] == output["local"]["attachment"]
+1 -1
View File
@@ -116,7 +116,7 @@ def test_request_limits_are_route_specific_and_cover_api_mutations():
main.request_body_limit(
"POST", "/api/v1/repos/stackchain/project/issues/17/attachments"
)
== 3 * 1024 * 1024
== 2 * 1024 * 1024 + 64 * 1024
)
assert main.request_body_limit("POST", "/unrelated/attachments") is None
assert main.request_body_limit("POST", "/unrelated") is None