import { test } from 'node:test'; import assert from 'node:assert/strict'; import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment'; import { buildCompressedUploadItem } from './msgContent'; import { TUploadItem } from '../../state/room/roomInputDrafts'; // buildCompressedUploadItem decides which bytes are uploaded and which encInfo // (if any) the resulting m.image event carries. Getting this wrong either leaks // a plaintext image into an E2EE room or produces an undecryptable attachment. const enc = (tag: string): EncryptedAttachmentInfo => ({ v: 'v2', key: { alg: 'A256CTR', k: tag }, iv: `iv-${tag}`, hashes: { sha256: `sha-${tag}` }, }) as unknown as EncryptedAttachmentInfo; const fakeFile = (name: string, size: number): File => new File([new Uint8Array(size)], name, { type: 'image/jpeg' }); const makeItem = (encInfo?: EncryptedAttachmentInfo): TUploadItem => ({ file: fakeFile('photo.png', 900), originalFile: fakeFile('photo.png', 900), encInfo, metadata: { markedAsSpoiler: false, compressImage: true }, }) as unknown as TUploadItem; test('unencrypted room: compressed item uploads the plain file and carries no encInfo', () => { const compressed = fakeFile('photo.jpg', 300); const item = buildCompressedUploadItem(makeItem(), compressed); assert.equal(item.file, compressed); assert.equal(item.originalFile, compressed); assert.equal(item.encInfo, undefined); }); test('encrypted room: compressed item carries the NEW encInfo, never the original one', () => { const compressed = fakeFile('photo.jpg', 300); const encryptedBlob = fakeFile('photo.jpg', 320); const item = buildCompressedUploadItem(makeItem(enc('original')), compressed, { file: encryptedBlob, encInfo: enc('compressed'), }); // The ciphertext is what gets uploaded; the plaintext stays available for // dimensions/blurhash only. assert.equal(item.file, encryptedBlob); assert.equal(item.originalFile, compressed); assert.deepEqual(item.encInfo, enc('compressed')); assert.notDeepEqual(item.encInfo, enc('original')); }); test('encrypted room: an encInfo-less compressed item never inherits the original encInfo', () => { // Defensive: even if the caller forgets to re-encrypt, we must not emit the // stale encInfo (that is the bug this helper exists to prevent). const item = buildCompressedUploadItem(makeItem(enc('original')), fakeFile('photo.jpg', 300)); assert.equal(item.encInfo, undefined); }); test('metadata (caption, spoiler) is preserved on the compressed item', () => { const base = makeItem(); base.metadata.caption = 'a caption'; base.metadata.markedAsSpoiler = true; const item = buildCompressedUploadItem(base, fakeFile('photo.jpg', 300)); assert.equal(item.metadata.caption, 'a caption'); assert.equal(item.metadata.markedAsSpoiler, true); }); // getImageMsgContent itself is not covered here: it needs a DOM (loadImageElement). // Its encInfo branch (content.file vs content.url) is exercised by the sibling // msgContent.test.ts builders, which share the same shape.