feat: add photo evidence to Today progress updates (Closes #1054)
This commit is contained in:
1 parent
7502d6f44d
commit
9a4a1711fe
7 files changed
+229
-25
No files matched your search
@@ -79,6 +79,36 @@ const photos = [
|
||||
}
|
||||
|
||||
|
||||
def test_store_isolates_today_progress_photos_from_conversation_photos():
|
||||
script = f"""
|
||||
const createStore = require({json.dumps(str(STORE))});
|
||||
const records = new Map();
|
||||
const transaction = async (operation, key, value) => {{
|
||||
if (operation === 'put') records.set(key, structuredClone(value));
|
||||
if (operation === 'get') return records.has(key) ? structuredClone(records.get(key)) : null;
|
||||
if (operation === 'delete') records.delete(key);
|
||||
}};
|
||||
const conversation = createStore({{transaction,getOwnerLogin:()=> 'timmy'}});
|
||||
const progress = createStore({{transaction,getOwnerLogin:()=> 'timmy',scope:'today-progress'}});
|
||||
const target={{kind:'issue',repository:'stackchain/dashboard',number:1054}};
|
||||
(async()=>{{
|
||||
await conversation.save(target,[{{filename:'reply.jpg',contentType:'image/jpeg',blob:new Blob(['reply'])}}]);
|
||||
await progress.save(target,[{{filename:'progress.jpg',contentType:'image/jpeg',blob:new Blob(['progress'])}}]);
|
||||
const reply=await conversation.load(target); const today=await progress.load(target);
|
||||
await progress.remove(target);
|
||||
process.stdout.write(JSON.stringify({{reply:reply[0].filename,today:today[0].filename,
|
||||
replyAfter:(await conversation.load(target))[0].filename,progressAfter:await progress.load(target),count:records.size}}));
|
||||
}})().catch(error=>{{console.error(error);process.exit(1);}});
|
||||
"""
|
||||
assert json.loads(run_node(script)) == {
|
||||
"reply": "reply.jpg",
|
||||
"today": "progress.jpg",
|
||||
"replyAfter": "reply.jpg",
|
||||
"progressAfter": None,
|
||||
"count": 1,
|
||||
}
|
||||
|
||||
|
||||
def test_coordinator_ignores_stale_restores_and_clears_only_after_durable_completion():
|
||||
script = f"""
|
||||
const createCoordinator = require({json.dumps(str(COORDINATOR))});
|
||||
|
||||
@@ -84,6 +84,67 @@ process.stdout.write(JSON.stringify({{error,retained,after:progress.load(target.
|
||||
assert output["admitted"]["background"] is True
|
||||
|
||||
|
||||
def test_photo_only_progress_is_durably_admitted_and_retained_until_success():
|
||||
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 photo = {{filename:'result.jpg',contentType:'image/jpeg',blob:{{size:42}},operationId:'photo-op-1'}};
|
||||
const calls=[]; let fail=true;
|
||||
const progress=createProgress({{
|
||||
storage, getLogin:()=> 'timmy', makeId:()=> 'progress-photo-op-1',
|
||||
admit:async message => {{ calls.push(message); if (fail) throw new Error('photo store unavailable'); return {{background:true}}; }},
|
||||
}});
|
||||
const target={{identity:'issue:stackchain/dashboard:12:',kind:'issue',repository:'stackchain/dashboard',number:12}};
|
||||
let error='';
|
||||
try {{ await progress.post(target, '', [photo]); }} catch (caught) {{ error=caught.message; }}
|
||||
const retained=progress.load(target.identity);
|
||||
fail=false;
|
||||
await progress.post(target, undefined, [photo]);
|
||||
process.stdout.write(JSON.stringify({{error,retained,after:progress.load(target.identity),calls}}));
|
||||
"""
|
||||
output = run_node("(async()=>{" + script + "})().catch(error=>{console.error(error);process.exit(1)})")
|
||||
assert output["error"] == "photo store unavailable"
|
||||
assert output["retained"] == ""
|
||||
assert output["after"] == ""
|
||||
assert len(output["calls"]) == 2
|
||||
assert output["calls"][0] == output["calls"][1] == {
|
||||
"kind": "issue-comment",
|
||||
"repository": "stackchain/dashboard",
|
||||
"number": 12,
|
||||
"body": "",
|
||||
"operationId": "progress-photo-op-1",
|
||||
"attachments": [{
|
||||
"filename": "result.jpg",
|
||||
"contentType": "image/jpeg",
|
||||
"blob": {"size": 42},
|
||||
"operationId": "photo-op-1",
|
||||
}],
|
||||
}
|
||||
|
||||
|
||||
def test_photo_cleanup_failure_retries_with_the_same_comment_operation():
|
||||
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 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;}}
|
||||
cleanupFails=false;
|
||||
await progress.post(target,undefined,[photo],cleanup);
|
||||
process.stdout.write(JSON.stringify({{error,calls,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["after"] == ""
|
||||
|
||||
|
||||
def test_progress_rejects_inactive_or_unsupported_targets_without_mutation():
|
||||
script = f"""
|
||||
const createProgress = require({json.dumps(str(TODAY_PROGRESS))});
|
||||
@@ -116,6 +177,9 @@ def test_mobile_progress_sheet_is_accessible_bundled_and_safe_area_aware():
|
||||
assert 'id="today-progress-sheet"' in html
|
||||
assert 'aria-labelledby="today-progress-title"' in html
|
||||
assert 'id="today-progress-body"' in html
|
||||
assert 'id="take-today-progress-photo"' in html
|
||||
assert 'id="today-progress-attachment"' in html
|
||||
assert 'id="today-progress-attachment-preview"' in html
|
||||
assert 'maxlength="2000"' in html
|
||||
assert 'id="save-today-progress"' in html
|
||||
assert 'id="post-today-progress"' in html
|
||||
@@ -123,6 +187,11 @@ def test_mobile_progress_sheet_is_accessible_bundled_and_safe_area_aware():
|
||||
assert '"static/today-progress.js"' in bundle
|
||||
assert "workSession.target('continue')" in dashboard
|
||||
assert "authoredOutbox.enqueueDurably" in dashboard
|
||||
assert "createTodayProgressPhotos" in dashboard
|
||||
assert "scope:'today-progress'" in TODAY_PROGRESS.read_text()
|
||||
assert "lanes:{ today:" in TODAY_PROGRESS.read_text()
|
||||
assert "photos:todayProgressPhotos" in dashboard
|
||||
assert "const controller = issueAttachment.mount" 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