import json import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[1] STORE = ROOT / "frontend" / "search-reply-draft-store.js" ATTACHMENT = ROOT / "frontend" / "issue-attachment.js" INDEX = ROOT / "frontend" / "index.html" DASHBOARD = ROOT / "frontend" / "dashboard.js" REGISTRY = ROOT / "frontend" / "private-data-registry.js" WORKER = ROOT / "frontend" / "service-worker.js" def run_node(script: str) -> str: return subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ).stdout def test_search_reply_photo_drafts_restore_only_for_the_confirmed_account_and_exact_result(): script = f""" const createStore = require({json.dumps(str(STORE))}); const records = new Map(); let login = 'timmy'; 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 store = createStore({{transaction, getOwnerLogin:()=>login}}); const target = {{kind:'issue', repository:'stackchain/dashboard', number:957}}; const other = {{kind:'issue', repository:'stackchain/dashboard', number:958}}; const photo = new Blob(['field-evidence'], {{type:'image/webp'}}); (async()=>{{ await store.save(target, [{{ filename:'camera.webp', contentType:'image/webp', blob:photo, note:'rack label', operationId:'upload-stable-1', confirmed:{{markdown:'![camera](confirmed-url)'}}, }}]); const restored = await store.load(target); const wrongTarget = await store.load(other); login = 'alexander'; const wrongAccount = await store.load(target); process.stdout.write(JSON.stringify({{ restored:{{...restored[0], blobText:await restored[0].blob.text(), blob:undefined}}, wrongTarget, wrongAccount, recordCount:records.size, }})); }})().catch(error=>{{console.error(error);process.exit(1);}}); """ output = json.loads(run_node(script)) assert output == { "restored": { "filename": "camera.webp", "contentType": "image/webp", "note": "rack label", "operationId": "upload-stable-1", "confirmed": {"markdown": "![camera](confirmed-url)"}, "blobText": "field-evidence", }, "wrongTarget": None, "wrongAccount": None, "recordCount": 1, } def test_photo_bundle_checkpoint_restores_stable_uploads_without_repeating_them(): script = f""" const attachment = require({json.dumps(str(ATTACHMENT))}); const photo = new Blob(['proof'], {{type:'image/png'}}); photo.name = 'proof.png'; const uploads = []; const first = attachment.create({{ createOperationId:()=> 'stable-upload-957', upload:async payload=>{{uploads.push(payload.operation_id);return {{markdown:'![proof](saved-url)'}};}}, }}); first.select(photo); (async()=>{{ await first.prepareComment({{repository:'stackchain/dashboard',number:957}}, 'Ready'); const checkpoint = await first.serialize(); const restored = attachment.create({{ createOperationId:()=> 'must-not-replace-operation', upload:async payload=>{{uploads.push(payload.operation_id);throw new Error('must not upload twice');}}, }}); restored.restore(checkpoint); const comment = await restored.prepareComment({{repository:'stackchain/dashboard',number:957}}, 'Ready'); process.stdout.write(JSON.stringify({{checkpoint:{{...checkpoint,blob:undefined}},comment,uploads}})); }})().catch(error=>{{console.error(error);process.exit(1);}}); """ output = json.loads(run_node(script)) assert output == { "checkpoint": { "filename": "proof.png", "contentType": "image/png", "operationId": "stable-upload-957", "confirmed": {"markdown": "![proof](saved-url)"}, }, "comment": "Ready\n\n![proof](saved-url)", "uploads": ["stable-upload-957"], } def test_dashboard_checkpoints_restores_and_clears_search_photo_drafts_at_user_boundaries(): html = INDEX.read_text() dashboard = DASHBOARD.read_text() registry = REGISTRY.read_text() worker = WORKER.read_text() assert '' in html assert html.index('static/search-reply-draft-store.js') < html.index('static/dashboard.js') assert "'stackchain-search-reply-drafts-v1'" in registry assert "BASE + 'static/search-reply-draft-store.js'" in worker assert "createSearchReplyDraftStore({" in dashboard assert "getOwnerLogin:() => confirmedOwnerLogin" in dashboard assert "onCheckpoint:() => persistSearchReplyPhotos()" in dashboard assert "searchReplyDraftStore.load(target)" in dashboard assert "searchReplyDraftStore.remove(item)" in dashboard