Backups: update integration tests

This commit is contained in:
trevor-signal
2025-06-30 17:54:21 -04:00
committed by GitHub
parent 0771075a75
commit 746b22d3dc
6 changed files with 127 additions and 31 deletions

View File

@@ -18,9 +18,19 @@ import { pngUrl, squareStickerUrl } from '../../storybook/Fixtures';
import { fakeAttachment } from '../../test-helpers/fakeAttachment';
import { strictAssert } from '../../util/assert';
import { isDownloadable } from '../../types/Attachment';
import { toBase64 } from '../../Bytes';
import { DIGEST_LENGTH, KEY_SET_LENGTH } from '../../types/Crypto';
const { i18n } = window.SignalContext;
const getRandomBytes = (length: number): Uint8Array => {
const arr = new Uint8Array(length);
return window.crypto.getRandomValues(arr);
};
const MOCK_KEY = toBase64(getRandomBytes(KEY_SET_LENGTH));
const MOCK_DIGEST = toBase64(getRandomBytes(DIGEST_LENGTH));
export default {
title: 'Components/Conversation/ImageGrid',
argTypes: {
@@ -1052,8 +1062,8 @@ export function DownloadPill(args: Props): JSX.Element {
width: 3000,
path: undefined,
blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
key: 'mock-key',
digest: 'mock-digest',
key: MOCK_KEY,
digest: MOCK_DIGEST,
cdnKey: 'mock-cdn-key',
cdnNumber: 4000,
});
@@ -1065,8 +1075,8 @@ export function DownloadPill(args: Props): JSX.Element {
width: 3000,
path: undefined,
blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
key: 'mock-key',
digest: 'mock-digest',
key: MOCK_KEY,
digest: MOCK_DIGEST,
cdnKey: 'mock-cdn-key',
cdnNumber: 4000,
});

View File

@@ -2922,9 +2922,12 @@ export class BackupExportStream extends Readable {
isLocalBackup: boolean;
}): Promise<Backups.IViewOnceMessage> {
const attachment = message.attachments?.at(0);
// Integration tests use the 'link-and-sync' version of export, which will include
// view-once attachments
const shouldIncludeAttachments = isTestOrMockEnvironment();
return {
attachment:
attachment == null
!shouldIncludeAttachments || attachment == null
? null
: await this.#processMessageAttachment({
attachment,

View File

@@ -1511,7 +1511,7 @@ export class BackupImportStream extends Writable {
} else if (item.viewOnceMessage) {
attributes = {
...attributes,
...(await this.#fromViewOnceMessage(item.viewOnceMessage)),
...(await this.#fromViewOnceMessage(item)),
};
} else if (item.directStoryReplyMessage) {
strictAssert(item.directionless == null, 'reply cannot be directionless');
@@ -1938,28 +1938,37 @@ export class BackupImportStream extends Writable {
.filter(isNotNil);
}
async #fromViewOnceMessage({
attachment,
reactions,
}: Backups.IViewOnceMessage): Promise<Partial<MessageAttributesType>> {
return {
...(attachment
? {
attachments: [
convertBackupMessageAttachmentToAttachment(
attachment,
this.#getFilePointerOptions()
),
].filter(isNotNil),
}
: {
attachments: undefined,
readStatus: ReadStatus.Viewed,
isErased: true,
}),
async #fromViewOnceMessage(
item: Backups.IChatItem
): Promise<Partial<MessageAttributesType>> {
const { incoming, viewOnceMessage } = item;
strictAssert(viewOnceMessage, 'view once message must not be null');
const { attachment, reactions } = viewOnceMessage;
const result: Partial<MessageAttributesType> = {
attachments: attachment
? [
convertBackupMessageAttachmentToAttachment(
attachment,
this.#getFilePointerOptions()
),
].filter(isNotNil)
: undefined,
reactions: this.#fromReactions(reactions),
isViewOnce: true,
};
if (!result.attachments?.length) {
result.isErased = true;
// Only mark it viewed if the message is read. Non-link-and-sync backups do not
// roundtrip view-once attachments, even if unread.
if (incoming?.read) {
result.readStatus = ReadStatus.Viewed;
}
}
return result;
}
#fromDirectStoryReplyMessage(

View File

@@ -24,8 +24,9 @@ import {
import { loadAllAndReinitializeRedux } from '../../services/allLoaders';
import { strictAssert } from '../../util/assert';
import type { MessageAttributesType } from '../../model-types';
import { TEXT_ATTACHMENT } from '../../types/MIME';
import { IMAGE_PNG, TEXT_ATTACHMENT } from '../../types/MIME';
import { MY_STORY_ID } from '../../types/Stories';
import { generateAttachmentKeys } from '../../AttachmentCrypto';
const CONTACT_A = generateAci();
const CONTACT_B = generateAci();
@@ -1107,4 +1108,79 @@ describe('backup/bubble messages', () => {
await asymmetricRoundtripHarness([incomingReply, outgoingReply], []);
});
});
describe('view-once', () => {
it('roundtrips incoming viewed view-once message', async () => {
await symmetricRoundtripHarness([
{
conversationId: contactA.id,
id: generateGuid(),
type: 'incoming',
isErased: true,
isViewOnce: true,
received_at: 3,
received_at_ms: 3,
sent_at: 3,
sourceServiceId: CONTACT_A,
readStatus: ReadStatus.Viewed,
seenStatus: SeenStatus.Seen,
unidentifiedDeliveryReceived: false,
timestamp: 3,
},
]);
});
it('roundtrips incoming unviewed view-once message', async () => {
await symmetricRoundtripHarness([
{
conversationId: contactA.id,
id: generateGuid(),
type: 'incoming',
isViewOnce: true,
attachments: [
{
size: 128,
contentType: IMAGE_PNG,
cdnKey: 'cdnKey',
cdnNumber: 2,
uploadTimestamp: 2001,
key: Bytes.toBase64(generateAttachmentKeys()),
digest: Bytes.toBase64(getRandomBytes(32)),
caption: 'shhhh',
},
],
received_at: 3,
received_at_ms: 3,
sent_at: 3,
sourceServiceId: CONTACT_A,
readStatus: ReadStatus.Unread,
seenStatus: SeenStatus.Unseen,
unidentifiedDeliveryReceived: false,
timestamp: 3,
},
]);
});
it('roundtrips outgoing view-once message', async () => {
await symmetricRoundtripHarness([
{
conversationId: contactA.id,
id: generateGuid(),
type: 'outgoing',
isViewOnce: true,
isErased: true,
received_at: 3,
received_at_ms: 3,
sent_at: 3,
sourceServiceId: OUR_ACI,
seenStatus: SeenStatus.Seen,
sendStateByConversationId: {
[contactA.id]: {
status: SendStatus.Delivered,
},
},
unidentifiedDeliveries: [CONTACT_A],
timestamp: 3,
},
]);
});
});
});

View File

@@ -40,8 +40,6 @@ describe('backup/integration', () => {
const files = readdirSync(BACKUP_INTEGRATION_DIR)
.filter(file => file.endsWith('.binproto'))
// TODO: DESKTOP-8906
.filter(file => file !== 'chat_item_view_once_07.binproto')
.map(file => join(BACKUP_INTEGRATION_DIR, file));
if (files.length === 0) {

View File

@@ -37,7 +37,7 @@ export function isValidAttachmentKey(
return false;
}
const bytes = fromBase64(keyBase64);
return bytes.byteLength > 0;
return bytes.byteLength === KEY_SET_LENGTH;
}
export function isValidDigest(
@@ -47,7 +47,7 @@ export function isValidDigest(
return false;
}
const bytes = fromBase64(digestBase64);
return bytes.byteLength > 0;
return bytes.byteLength === DIGEST_LENGTH;
}
export function isValidPlaintextHash(
@@ -57,5 +57,5 @@ export function isValidPlaintextHash(
return false;
}
const bytes = fromHex(plaintextHashHex);
return bytes.byteLength > 0;
return bytes.byteLength === PLAINTEXT_HASH_LENGTH;
}