feat(ext/sw): upload_attachment + download_attachment router handlers

Both popup-only. upload_attachment encrypts via WASM, putBlobs via
GitHost (Git Data API fallback for >900 KB), persists the AttachmentRef
on the item + manifest summaries. Duplicate uploads (same content =
same id from sha256) return the existing ref without a re-upload.
download_attachment reads + decrypts and returns plaintext bytes for
the popup to wrap in a Blob. 4 new router tests (accept × 2, reject × 2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-25 16:04:06 -04:00
parent 559c881dca
commit 5217d04034
3 changed files with 145 additions and 4 deletions

View File

@@ -732,3 +732,49 @@ describe('get_vault_settings / update_vault_settings', () => {
expect(res).toEqual({ ok: false, error: 'unauthorized_sender' });
});
});
// --- upload_attachment / download_attachment (γ₁ Task 6) ---
describe('upload_attachment / download_attachment', () => {
it('upload_attachment accepted from popup', async () => {
const state = makeState();
const result = await route(
{ type: 'upload_attachment', itemId: 'abc', filename: 'f.pdf', mimeType: 'application/pdf', bytes: new ArrayBuffer(10) },
state,
makePopupSender(),
);
// The handler may return ok: false (vault_locked) since session is not primed,
// but the router MUST reach the handler — i.e. not return unauthorized_sender.
expect(result).not.toEqual({ ok: false, error: 'unauthorized_sender' });
});
it('upload_attachment rejected from content script', async () => {
const state = makeState();
const result = await route(
{ type: 'upload_attachment', itemId: 'abc', filename: 'f.pdf', mimeType: 'application/pdf', bytes: new ArrayBuffer(10) },
state,
makeContentSender(),
);
expect(result).toEqual({ ok: false, error: 'unauthorized_sender' });
});
it('download_attachment accepted from popup', async () => {
const state = makeState();
const result = await route(
{ type: 'download_attachment', itemId: 'abc', attachmentId: 'aid' },
state,
makePopupSender(),
);
expect(result).not.toEqual({ ok: false, error: 'unauthorized_sender' });
});
it('download_attachment rejected from content script', async () => {
const state = makeState();
const result = await route(
{ type: 'download_attachment', itemId: 'abc', attachmentId: 'aid' },
state,
makeContentSender(),
);
expect(result).toEqual({ ok: false, error: 'unauthorized_sender' });
});
});