stackchain-dashboard/tests/test_search_reply_draft_store.py
timmy 5af45cda57
All checks were successful
CI / lint (pull_request) Successful in 2m10s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Successful in 57s
CI / release-candidate (pull_request) Has been skipped
feat: preserve Search photo reply drafts (Closes #957)
2026-08-16 10:07:46 +00:00

119 lines
4.7 KiB
Python

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 '<script src="static/search-reply-draft-store.js"></script>' 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