Backup support for link preview and contact attachments
This commit is contained in:
parent
4aa898f495
commit
534029d2e6
7 changed files with 357 additions and 54 deletions
253
ts/test-electron/backup/attachments_test.ts
Normal file
253
ts/test-electron/backup/attachments_test.ts
Normal file
|
@ -0,0 +1,253 @@
|
|||
// 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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue