Reuse files on disk for outgoing messages

This commit is contained in:
trevor-signal
2026-02-23 15:35:11 -05:00
committed by GitHub
parent 491de86ad3
commit 2b243bb457
12 changed files with 410 additions and 111 deletions

View File

@@ -5,8 +5,8 @@ import createDebug from 'debug';
import { assert } from 'chai';
import { expect } from 'playwright/test';
import { type PrimaryDevice, StorageState } from '@signalapp/mock-server';
import * as path from 'node:path';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { access, readFile } from 'node:fs/promises';
import type { App } from '../playwright.node.js';
import { Bootstrap } from '../bootstrap.node.js';
@@ -20,10 +20,11 @@ import * as durations from '../../util/durations/index.std.js';
import { strictAssert } from '../../util/assert.std.js';
import { VIDEO_MP4 } from '../../types/MIME.std.js';
import { toBase64 } from '../../Bytes.std.js';
import type { AttachmentType } from '../../types/Attachment.std.js';
export const debug = createDebug('mock:test:attachments');
const CAT_PATH = path.join(
const CAT_PATH = join(
__dirname,
'..',
'..',
@@ -31,7 +32,7 @@ const CAT_PATH = path.join(
'fixtures',
'cat-screenshot.png'
);
const VIDEO_PATH = path.join(
const VIDEO_PATH = join(
__dirname,
'..',
'..',
@@ -83,12 +84,11 @@ describe('attachments', function (this: Mocha.Suite) {
await page.getByTestId(pinned.device.aci).click();
const [attachmentCat] = await sendMessageWithAttachments(
page,
pinned,
'This is my cat',
[CAT_PATH]
);
const {
attachments: [attachmentCat],
} = await sendMessageWithAttachments(page, pinned, 'This is my cat', [
CAT_PATH,
]);
const Message = getTimelineMessageWithText(page, 'This is my cat');
const MessageSent = Message.locator(
@@ -212,4 +212,189 @@ describe('attachments', function (this: Mocha.Suite) {
assert.strictEqual(attachmentInDB?.chunkSize, undefined);
}
});
it('reuses attachment file when receiving and sending same video and deletes only when all references removed', async () => {
const page = await app.getWindow();
const { desktop, phone } = bootstrap;
await page.getByTestId(pinned.device.aci).click();
const plaintextVideo = await readFile(VIDEO_PATH);
const ciphertextVideo = await bootstrap.encryptAndStoreAttachmentOnCDN(
plaintextVideo,
VIDEO_MP4
);
const ciphertextVideo2 = await bootstrap.encryptAndStoreAttachmentOnCDN(
plaintextVideo,
VIDEO_MP4
);
const phoneTimestamp = bootstrap.getTimestamp();
const friendTimestamp = bootstrap.getTimestamp();
debug('receiving attachment from primary');
await page.getByTestId(pinned.device.aci).click();
await sendTextMessage({
from: phone,
to: pinned,
text: 'from phone',
desktop,
attachments: [{ ...ciphertextVideo, width: 568, height: 320 }],
timestamp: phoneTimestamp,
});
await getMessageInTimelineByTimestamp(page, phoneTimestamp)
.locator('.module-image--loaded')
.waitFor();
debug('receiving same attachment from contact');
await page.getByTestId(pinned.device.aci).click();
await sendTextMessage({
from: pinned,
to: desktop,
text: 'from friend',
desktop,
attachments: [{ ...ciphertextVideo2, width: 568, height: 320 }],
timestamp: friendTimestamp,
});
await getMessageInTimelineByTimestamp(page, friendTimestamp)
.locator('.module-image--loaded')
.waitFor();
debug('sending same attachment from desktop');
const { timestamp: sentTimestamp } = await sendMessageWithAttachments(
page,
pinned,
'from desktop',
[VIDEO_PATH]
);
strictAssert(sentTimestamp, 'outgoing timestamp must exist');
const phoneDBMessage = (await app.getMessagesBySentAt(phoneTimestamp))[0];
const phoneDBAttachment: AttachmentType = phoneDBMessage.attachments?.[0];
const friendDBMessage = (await app.getMessagesBySentAt(friendTimestamp))[0];
const friendDBAttachment: AttachmentType = friendDBMessage.attachments?.[0];
const sentDBMessage = (await app.getMessagesBySentAt(sentTimestamp))[0];
const sentDBAttachment: AttachmentType = sentDBMessage.attachments?.[0];
strictAssert(phoneDBAttachment, 'outgoing sync message exists in DB');
strictAssert(friendDBAttachment, 'incoming message exists in DB');
strictAssert(sentDBAttachment, 'sent message exists in DB');
const { path } = phoneDBAttachment;
const thumbnailPath = phoneDBAttachment.thumbnail?.path;
strictAssert(path, 'path exists');
strictAssert(thumbnailPath, 'thumbnail path exists');
debug(
'checking that incoming and outgoing messages deduplicated data on disk'
);
assert.strictEqual(phoneDBAttachment.path, path);
assert.strictEqual(friendDBAttachment.path, path);
assert.strictEqual(sentDBAttachment.path, path);
assert.strictEqual(phoneDBAttachment.thumbnail?.path, thumbnailPath);
assert.strictEqual(friendDBAttachment.thumbnail?.path, thumbnailPath);
assert.strictEqual(sentDBAttachment.thumbnail?.path, thumbnailPath);
debug('deleting two of the messages');
const sentMessage = getMessageInTimelineByTimestamp(page, sentTimestamp);
await sentMessage.click({ button: 'right' });
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByRole('button', { name: 'Delete for me' }).click();
await expect(sentMessage).not.toBeVisible();
const friendMessage = getMessageInTimelineByTimestamp(
page,
friendTimestamp
);
await friendMessage.click({ button: 'right' });
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByRole('button', { name: 'Delete for me' }).click();
await expect(friendMessage).not.toBeVisible();
await app.waitForMessageToBeCleanedUp(sentDBMessage.id);
await app.waitForMessageToBeCleanedUp(friendDBMessage.id);
assert.strictEqual(
(await app.getMessagesBySentAt(friendTimestamp)).length,
0
);
assert.strictEqual(
(await app.getMessagesBySentAt(sentTimestamp)).length,
0
);
// Path still exists!
await access(bootstrap.getAbsoluteAttachmentPath(path));
debug('delete last of messages');
const phoneMessage = getMessageInTimelineByTimestamp(page, phoneTimestamp);
await phoneMessage.click({ button: 'right' });
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByRole('button', { name: 'Delete for me' }).click();
await expect(phoneMessage).not.toBeVisible();
await app.waitForMessageToBeCleanedUp(phoneDBMessage.id);
assert.strictEqual(
(await app.getMessagesBySentAt(phoneTimestamp)).length,
0
);
await expect(
access(bootstrap.getAbsoluteAttachmentPath(path))
).rejects.toMatchObject({
code: 'ENOENT',
});
});
it('reencodes images to same data and reuses same file when sending', async () => {
const page = await app.getWindow();
await page.getByTestId(pinned.device.aci).click();
const { timestamp: firstTimestamp } = await sendMessageWithAttachments(
page,
pinned,
'first send',
[CAT_PATH]
);
const { timestamp: secondTimestamp } = await sendMessageWithAttachments(
page,
pinned,
'second send',
[CAT_PATH]
);
const firstAttachment: AttachmentType = (
await app.getMessagesBySentAt(firstTimestamp)
)[0].attachments?.[0];
const secondAttachment: AttachmentType = (
await app.getMessagesBySentAt(secondTimestamp)
)[0].attachments?.[0];
strictAssert(firstAttachment, 'firstAttachment exists in DB');
strictAssert(secondAttachment, 'secondAttachment exists in DB');
debug(
'checking that incoming and outgoing messages deduplicated data on disk'
);
assert.strictEqual(firstAttachment.path, secondAttachment.path);
assert.strictEqual(firstAttachment.localKey, secondAttachment.localKey);
assert.strictEqual(
firstAttachment.thumbnail?.path,
secondAttachment.thumbnail?.path
);
});
});