signal-desktop/ts/test-electron/backup/attachments_test.ts

253 lines
7.1 KiB
TypeScript

// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { v4 as generateGuid } from 'uuid';
import { BackupLevel } from '@signalapp/libsignal-client/zkgroup';
import { omit } from 'lodash';
import type { ConversationModel } from '../../models/conversations';
import * as Bytes from '../../Bytes';
import Data from '../../sql/Client';
import { generateAci } from '../../types/ServiceId';
import { ReadStatus } from '../../messages/MessageReadStatus';
import { SeenStatus } from '../../MessageSeenStatus';
import { loadCallsHistory } from '../../services/callHistoryLoader';
import { setupBasics, asymmetricRoundtripHarness } from './helpers';
import { IMAGE_JPEG } from '../../types/MIME';
import type { MessageAttributesType } from '../../model-types';
import type { AttachmentType } from '../../types/Attachment';
import { strictAssert } from '../../util/assert';
const CONTACT_A = generateAci();
describe('backup/attachments', () => {
let contactA: ConversationModel;
beforeEach(async () => {
await Data._removeAllMessages();
await Data._removeAllConversations();
window.storage.reset();
await setupBasics();
contactA = await window.ConversationController.getOrCreateAndWait(
CONTACT_A,
'private',
{ systemGivenName: 'CONTACT_A' }
);
await loadCallsHistory();
});
function getBase64(str: string): string {
return Bytes.toBase64(Bytes.fromString(str));
}
function composeAttachment(
index: number,
overrides?: Partial<AttachmentType>
): AttachmentType {
return {
cdnKey: `cdnKey${index}`,
cdnNumber: 3,
key: getBase64(`key${index}`),
digest: getBase64(`digest${index}`),
iv: getBase64(`iv${index}`),
size: 100,
contentType: IMAGE_JPEG,
path: `/path/to/file${index}.png`,
uploadTimestamp: index,
...overrides,
};
}
function composeMessage(
timestamp: number,
overrides?: Partial<MessageAttributesType>
): MessageAttributesType {
return {
conversationId: contactA.id,
id: generateGuid(),
type: 'incoming',
received_at: timestamp,
received_at_ms: timestamp,
sourceServiceId: CONTACT_A,
sourceDevice: timestamp,
sent_at: timestamp,
timestamp,
readStatus: ReadStatus.Read,
seenStatus: SeenStatus.Seen,
...overrides,
};
}
describe('normal attachments', () => {
it('BackupLevel.Messages, roundtrips normal attachments', async () => {
const attachment1 = composeAttachment(1);
const attachment2 = composeAttachment(2);
await asymmetricRoundtripHarness(
[
composeMessage(1, {
attachments: [attachment1, attachment2],
}),
],
// path & iv will not be roundtripped
[
composeMessage(1, {
attachments: [
omit(attachment1, ['path', 'iv']),
omit(attachment2, ['path', 'iv']),
],
}),
],
BackupLevel.Messages
);
});
it('BackupLevel.Media, roundtrips normal attachments', async () => {
const attachment = composeAttachment(1);
strictAssert(attachment.digest, 'digest exists');
await asymmetricRoundtripHarness(
[
composeMessage(1, {
attachments: [attachment],
}),
],
[
composeMessage(1, {
// path, iv, and uploadTimestamp will not be roundtripped,
// but there will be a backupLocator
attachments: [
{
...omit(attachment, ['path', 'iv', 'uploadTimestamp']),
backupLocator: { mediaName: attachment.digest },
},
],
}),
],
BackupLevel.Media
);
});
});
describe('Preview attachments', () => {
it('BackupLevel.Messages, roundtrips preview attachments', async () => {
const attachment = composeAttachment(1);
await asymmetricRoundtripHarness(
[
composeMessage(1, {
preview: [{ url: 'url', date: 1, image: attachment }],
}),
],
// path & iv will not be roundtripped
[
composeMessage(1, {
preview: [
{ url: 'url', date: 1, image: omit(attachment, ['path', 'iv']) },
],
}),
],
BackupLevel.Messages
);
});
it('BackupLevel.Media, roundtrips preview attachments', async () => {
const attachment = composeAttachment(1);
strictAssert(attachment.digest, 'digest exists');
await asymmetricRoundtripHarness(
[
composeMessage(1, {
preview: [
{
url: 'url',
date: 1,
title: 'title',
description: 'description',
image: attachment,
},
],
}),
],
[
composeMessage(1, {
preview: [
{
url: 'url',
date: 1,
title: 'title',
description: 'description',
image: {
// path, iv, and uploadTimestamp will not be roundtripped,
// but there will be a backupLocator
...omit(attachment, ['path', 'iv', 'uploadTimestamp']),
backupLocator: { mediaName: attachment.digest },
},
},
],
}),
],
BackupLevel.Media
);
});
});
describe('contact attachments', () => {
it('BackupLevel.Messages, roundtrips contact attachments', async () => {
const attachment = composeAttachment(1);
await asymmetricRoundtripHarness(
[
composeMessage(1, {
contact: [{ avatar: { avatar: attachment, isProfile: false } }],
}),
],
// path & iv will not be roundtripped
[
composeMessage(1, {
contact: [
{
avatar: {
avatar: omit(attachment, ['path', 'iv']),
isProfile: false,
},
},
],
}),
],
BackupLevel.Messages
);
});
it('BackupLevel.Media, roundtrips contact attachments', async () => {
const attachment = composeAttachment(1);
strictAssert(attachment.digest, 'digest exists');
await asymmetricRoundtripHarness(
[
composeMessage(1, {
contact: [{ avatar: { avatar: attachment, isProfile: false } }],
}),
],
// path, iv, and uploadTimestamp will not be roundtripped,
// but there will be a backupLocator
[
composeMessage(1, {
contact: [
{
avatar: {
avatar: {
...omit(attachment, ['path', 'iv', 'uploadTimestamp']),
backupLocator: { mediaName: attachment.digest },
},
isProfile: false,
},
},
],
}),
],
BackupLevel.Media
);
});
});
});