signal-desktop/ts/test-electron/MessageReceipts_test.ts
2024-10-08 13:17:03 +10:00

252 lines
6.9 KiB
TypeScript

// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { v4 as uuid } from 'uuid';
import { assert } from 'chai';
import { type AciString, generateAci } from '../types/ServiceId';
import type { MessageAttributesType } from '../model-types';
import { DataReader, DataWriter } from '../sql/Client';
import { SendStatus } from '../messages/MessageSendState';
import type {
MessageReceiptAttributesType,
MessageReceiptType,
} from '../messageModifiers/MessageReceipts';
import {
onReceipt,
messageReceiptTypeSchema,
} from '../messageModifiers/MessageReceipts';
import { ReadStatus } from '../messages/MessageReadStatus';
describe('MessageReceipts', () => {
let ourAci: AciString;
beforeEach(async () => {
ourAci = generateAci();
await window.textsecure.storage.put('uuid_id', `${ourAci}.1`);
await window.textsecure.storage.put('read-receipt-setting', true);
await window.ConversationController.load();
});
function generateReceipt(
sourceConversationId: string,
messageSentAt: number,
type: MessageReceiptType
): MessageReceiptAttributesType {
return {
envelopeId: uuid(),
syncTaskId: uuid(),
receiptSync: {
messageSentAt,
receiptTimestamp: 1,
sourceConversationId,
sourceDevice: 1,
sourceServiceId: generateAci(),
type,
wasSentEncrypted: true,
},
};
}
it('processes all receipts in a batch', async () => {
const id = uuid();
const sentAt = Date.now();
const messageAttributes: MessageAttributesType = {
conversationId: uuid(),
id,
received_at: 1,
sent_at: sentAt,
timestamp: sentAt,
type: 'outgoing',
sendStateByConversationId: {
aaaa: {
status: SendStatus.Sent,
updatedAt: Date.now(),
},
bbbb: {
status: SendStatus.Sent,
updatedAt: Date.now(),
},
cccc: {
status: SendStatus.Sent,
updatedAt: Date.now(),
},
dddd: {
status: SendStatus.Sent,
updatedAt: Date.now(),
},
},
};
await DataWriter.saveMessage(messageAttributes, {
forceSave: true,
ourAci,
});
await Promise.all([
onReceipt(
generateReceipt('aaaa', sentAt, messageReceiptTypeSchema.enum.Delivery)
),
onReceipt(
generateReceipt('bbbb', sentAt, messageReceiptTypeSchema.enum.Delivery)
),
onReceipt(
generateReceipt('cccc', sentAt, messageReceiptTypeSchema.enum.Read)
),
onReceipt(
generateReceipt('aaaa', sentAt, messageReceiptTypeSchema.enum.Read)
),
]);
const messageFromDatabase = await DataReader.getMessageById(id);
const savedSendState = messageFromDatabase?.sendStateByConversationId;
assert.equal(savedSendState?.aaaa.status, SendStatus.Read, 'aaaa');
assert.equal(savedSendState?.bbbb.status, SendStatus.Delivered, 'bbbb');
assert.equal(savedSendState?.cccc.status, SendStatus.Read, 'cccc');
assert.equal(savedSendState?.dddd.status, SendStatus.Sent, 'dddd');
});
it('updates sendStateByConversationId for edits', async () => {
const id = uuid();
const sentAt = Date.now();
const editedSentAt = sentAt + 1000;
const defaultSendState = {
aaaa: {
status: SendStatus.Sent,
updatedAt: Date.now(),
},
bbbb: {
status: SendStatus.Sent,
updatedAt: Date.now(),
},
cccc: {
status: SendStatus.Sent,
updatedAt: Date.now(),
},
dddd: {
status: SendStatus.Sent,
updatedAt: Date.now(),
},
};
const messageAttributes: MessageAttributesType = {
conversationId: uuid(),
id,
received_at: 1,
sent_at: sentAt,
timestamp: sentAt,
editMessageTimestamp: editedSentAt,
type: 'outgoing',
sendStateByConversationId: defaultSendState,
editHistory: [
{
sendStateByConversationId: defaultSendState,
timestamp: editedSentAt,
received_at: 2,
received_at_ms: Date.now(),
},
{
sendStateByConversationId: defaultSendState,
timestamp: sentAt,
received_at: 1,
received_at_ms: Date.now(),
},
],
};
await DataWriter.saveMessage(messageAttributes, {
forceSave: true,
ourAci,
});
await DataWriter.saveEditedMessage(messageAttributes, ourAci, {
conversationId: messageAttributes.conversationId,
messageId: messageAttributes.id,
readStatus: ReadStatus.Read,
sentAt: editedSentAt,
});
await Promise.all([
// send receipts for original message
onReceipt(
generateReceipt('aaaa', sentAt, messageReceiptTypeSchema.enum.Delivery)
),
onReceipt(
generateReceipt('bbbb', sentAt, messageReceiptTypeSchema.enum.Delivery)
),
onReceipt(
generateReceipt('cccc', sentAt, messageReceiptTypeSchema.enum.Read)
),
onReceipt(
generateReceipt('aaaa', sentAt, messageReceiptTypeSchema.enum.Read)
),
// and send receipts for edited message
onReceipt(
generateReceipt(
'aaaa',
editedSentAt,
messageReceiptTypeSchema.enum.Delivery
)
),
onReceipt(
generateReceipt(
'bbbb',
editedSentAt,
messageReceiptTypeSchema.enum.Delivery
)
),
onReceipt(
generateReceipt(
'cccc',
editedSentAt,
messageReceiptTypeSchema.enum.Read
)
),
onReceipt(
generateReceipt(
'bbbb',
editedSentAt,
messageReceiptTypeSchema.enum.Read
)
),
]);
const messageFromDatabase = await DataReader.getMessageById(id);
const rootSendState = messageFromDatabase?.sendStateByConversationId;
assert.deepEqual(
rootSendState,
messageFromDatabase?.editHistory?.[0].sendStateByConversationId,
'edit history version should match root version'
);
assert.equal(rootSendState?.aaaa.status, SendStatus.Delivered, 'aaaa');
assert.equal(rootSendState?.bbbb.status, SendStatus.Read, 'bbbb');
assert.equal(rootSendState?.cccc.status, SendStatus.Read, 'cccc');
assert.equal(rootSendState?.dddd.status, SendStatus.Sent, 'dddd');
const originalMessageSendState =
messageFromDatabase?.editHistory?.[1].sendStateByConversationId;
assert.equal(
originalMessageSendState?.aaaa.status,
SendStatus.Read,
'original-aaaa'
);
assert.equal(
originalMessageSendState?.bbbb.status,
SendStatus.Delivered,
'original-bbbb'
);
assert.equal(
originalMessageSendState?.cccc.status,
SendStatus.Read,
'original-cccc'
);
assert.equal(
originalMessageSendState?.dddd.status,
SendStatus.Sent,
'original-dddd'
);
});
});