74 lines
3.0 KiB
TypeScript
74 lines
3.0 KiB
TypeScript
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.
|