signal-desktop/ts/test-electron/sql/sendLog_test.ts

743 lines
21 KiB
TypeScript
Raw Normal View History

// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import { v4 as generateUuid } from 'uuid';
2024-07-22 18:16:33 +00:00
import { DataReader, DataWriter } from '../../sql/Client';
import { generateAci } from '../../types/ServiceId';
2021-09-24 00:49:05 +00:00
import { constantTimeEqual, getRandomBytes } from '../../Crypto';
import { singleProtoJobQueue } from '../../jobs/singleProtoJobQueue';
const {
_getAllSentProtoMessageIds,
_getAllSentProtoRecipients,
2024-07-22 18:16:33 +00:00
getAllSentProtos,
} = DataReader;
const {
deleteSentProtoByMessageId,
deleteSentProtoRecipient,
deleteSentProtosOlderThan,
getSentProtoByRecipient,
insertProtoRecipients,
insertSentProto,
removeAllSentProtos,
removeMessage,
saveMessage,
2024-07-22 18:16:33 +00:00
} = DataWriter;
describe('sql/sendLog', () => {
beforeEach(async () => {
await removeAllSentProtos();
2024-07-22 19:27:09 +00:00
await window.ConversationController.load();
});
it('roundtrips with insertSentProto/getAllSentProtos', async () => {
2021-09-24 00:49:05 +00:00
const bytes = getRandomBytes(128);
const timestamp = Date.now();
const proto = {
contentHint: 1,
proto: bytes,
timestamp,
urgent: false,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
await insertSentProto(proto, {
messageIds: [generateUuid()],
recipients: {
[generateAci()]: [1, 2],
},
});
const allProtos = await getAllSentProtos();
assert.lengthOf(allProtos, 1);
const actual = allProtos[0];
assert.strictEqual(actual.contentHint, proto.contentHint);
2021-09-24 00:49:05 +00:00
assert.isTrue(constantTimeEqual(actual.proto, proto.proto));
assert.strictEqual(actual.timestamp, proto.timestamp);
assert.strictEqual(actual.urgent, proto.urgent);
2022-08-15 21:53:33 +00:00
assert.strictEqual(
actual.hasPniSignatureMessage,
proto.hasPniSignatureMessage
);
await removeAllSentProtos();
assert.lengthOf(await getAllSentProtos(), 0);
});
it('cascades deletes into both tables with foreign keys', async () => {
assert.lengthOf(await getAllSentProtos(), 0);
assert.lengthOf(await _getAllSentProtoMessageIds(), 0);
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
2021-09-24 00:49:05 +00:00
const bytes = getRandomBytes(128);
const timestamp = Date.now();
const proto = {
contentHint: 1,
proto: bytes,
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: true,
};
await insertSentProto(proto, {
messageIds: [generateUuid(), generateUuid()],
recipients: {
[generateAci()]: [1, 2],
[generateAci()]: [1],
},
});
const allProtos = await getAllSentProtos();
assert.lengthOf(allProtos, 1);
const actual = allProtos[0];
assert.strictEqual(actual.contentHint, proto.contentHint);
assert.isTrue(constantTimeEqual(actual.proto, proto.proto));
assert.strictEqual(actual.timestamp, proto.timestamp);
assert.strictEqual(actual.urgent, proto.urgent);
2022-08-15 21:53:33 +00:00
assert.strictEqual(
actual.hasPniSignatureMessage,
proto.hasPniSignatureMessage
);
assert.lengthOf(await _getAllSentProtoMessageIds(), 2);
assert.lengthOf(await _getAllSentProtoRecipients(), 3);
await removeAllSentProtos();
assert.lengthOf(await getAllSentProtos(), 0);
assert.lengthOf(await _getAllSentProtoMessageIds(), 0);
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
});
it('trigger deletes payload when referenced message is deleted', async () => {
const id = generateUuid();
const timestamp = Date.now();
const ourAci = generateAci();
await saveMessage(
{
id,
body: 'some text',
conversationId: generateUuid(),
received_at: timestamp,
sent_at: timestamp,
timestamp,
type: 'outgoing',
},
{ forceSave: true, ourAci }
);
2021-09-24 00:49:05 +00:00
const bytes = getRandomBytes(128);
const proto = {
contentHint: 1,
proto: bytes,
timestamp,
urgent: false,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
await insertSentProto(proto, {
messageIds: [id],
recipients: {
[generateAci()]: [1, 2],
},
});
const allProtos = await getAllSentProtos();
assert.lengthOf(allProtos, 1);
const actual = allProtos[0];
assert.strictEqual(actual.timestamp, proto.timestamp);
await removeMessage(id, { singleProtoJobQueue });
assert.lengthOf(await getAllSentProtos(), 0);
});
describe('#insertSentProto', () => {
it('supports adding duplicates', async () => {
const timestamp = Date.now();
const messageIds = [generateUuid()];
const recipients = {
[generateAci()]: [1],
};
const proto1 = {
contentHint: 7,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
const proto2 = {
contentHint: 9,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp,
urgent: false,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: true,
};
assert.lengthOf(await getAllSentProtos(), 0);
assert.lengthOf(await _getAllSentProtoMessageIds(), 0);
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
await insertSentProto(proto1, { messageIds, recipients });
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoMessageIds(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 1);
await insertSentProto(proto2, { messageIds, recipients });
assert.lengthOf(await getAllSentProtos(), 2);
assert.lengthOf(await _getAllSentProtoMessageIds(), 2);
assert.lengthOf(await _getAllSentProtoRecipients(), 2);
});
});
describe('#insertProtoRecipients', () => {
it('handles duplicates, adding new recipients if needed', async () => {
const timestamp = Date.now();
const messageIds = [generateUuid()];
const proto = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
assert.lengthOf(await getAllSentProtos(), 0);
assert.lengthOf(await _getAllSentProtoMessageIds(), 0);
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
const id = await insertSentProto(proto, {
messageIds,
recipients: {
[generateAci()]: [1],
},
});
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoMessageIds(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 1);
const recipientServiceId = generateAci();
await insertProtoRecipients({
id,
recipientServiceId,
deviceIds: [1, 2],
});
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoMessageIds(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 3);
});
});
describe('#deleteSentProtosOlderThan', () => {
it('deletes all older timestamps', async () => {
const timestamp = Date.now();
const proto1 = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp: timestamp + 10,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
const proto2 = {
contentHint: 2,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
const proto3 = {
contentHint: 0,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp: timestamp - 15,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
await insertSentProto(proto1, {
messageIds: [generateUuid()],
recipients: {
[generateAci()]: [1],
},
});
await insertSentProto(proto2, {
messageIds: [generateUuid()],
recipients: {
[generateAci()]: [1, 2],
},
});
await insertSentProto(proto3, {
messageIds: [generateUuid()],
recipients: {
[generateAci()]: [1, 2, 3],
},
});
assert.lengthOf(await getAllSentProtos(), 3);
await deleteSentProtosOlderThan(timestamp);
const allProtos = await getAllSentProtos();
assert.lengthOf(allProtos, 2);
const actual1 = allProtos[0];
assert.strictEqual(actual1.contentHint, proto1.contentHint);
2021-09-24 00:49:05 +00:00
assert.isTrue(constantTimeEqual(actual1.proto, proto1.proto));
assert.strictEqual(actual1.timestamp, proto1.timestamp);
const actual2 = allProtos[1];
assert.strictEqual(actual2.contentHint, proto2.contentHint);
2021-09-24 00:49:05 +00:00
assert.isTrue(constantTimeEqual(actual2.proto, proto2.proto));
assert.strictEqual(actual2.timestamp, proto2.timestamp);
});
});
describe('#deleteSentProtoByMessageId', () => {
2022-02-09 20:33:19 +00:00
it('deletes all records related to that messageId', async () => {
assert.lengthOf(await getAllSentProtos(), 0);
assert.lengthOf(await _getAllSentProtoMessageIds(), 0);
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
const messageId = generateUuid();
const timestamp = Date.now();
const proto1 = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
const proto2 = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp: timestamp - 10,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
const proto3 = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp: timestamp - 20,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
await insertSentProto(proto1, {
messageIds: [messageId, generateUuid()],
recipients: {
[generateAci()]: [1, 2],
[generateAci()]: [1],
},
});
await insertSentProto(proto2, {
messageIds: [messageId],
recipients: {
[generateAci()]: [1],
},
});
await insertSentProto(proto3, {
messageIds: [generateUuid()],
recipients: {
[generateAci()]: [1],
},
});
assert.lengthOf(await getAllSentProtos(), 3);
assert.lengthOf(await _getAllSentProtoMessageIds(), 4);
assert.lengthOf(await _getAllSentProtoRecipients(), 5);
await deleteSentProtoByMessageId(messageId);
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoMessageIds(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 1);
});
});
describe('#deleteSentProtoRecipient', () => {
it('does not delete payload if recipient remains', async () => {
const timestamp = Date.now();
const recipientServiceId1 = generateAci();
const recipientServiceId2 = generateAci();
const proto = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
await insertSentProto(proto, {
messageIds: [generateUuid()],
recipients: {
[recipientServiceId1]: [1, 2],
[recipientServiceId2]: [1],
},
});
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 3);
2022-08-15 21:53:33 +00:00
const { successfulPhoneNumberShares } = await deleteSentProtoRecipient({
timestamp,
recipientServiceId: recipientServiceId1,
deviceId: 1,
});
2022-08-15 21:53:33 +00:00
assert.lengthOf(successfulPhoneNumberShares, 0);
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 2);
});
it('deletes payload if no recipients remain', async () => {
const timestamp = Date.now();
const recipientServiceId1 = generateAci();
const recipientServiceId2 = generateAci();
const proto = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
await insertSentProto(proto, {
messageIds: [generateUuid()],
recipients: {
[recipientServiceId1]: [1, 2],
[recipientServiceId2]: [1],
},
});
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 3);
2022-08-15 21:53:33 +00:00
{
const { successfulPhoneNumberShares } = await deleteSentProtoRecipient({
timestamp,
recipientServiceId: recipientServiceId1,
2022-08-15 21:53:33 +00:00
deviceId: 1,
});
assert.lengthOf(successfulPhoneNumberShares, 0);
}
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 2);
2022-08-15 21:53:33 +00:00
{
const { successfulPhoneNumberShares } = await deleteSentProtoRecipient({
timestamp,
recipientServiceId: recipientServiceId1,
2022-08-15 21:53:33 +00:00
deviceId: 2,
});
assert.lengthOf(successfulPhoneNumberShares, 0);
}
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 1);
2022-08-15 21:53:33 +00:00
{
const { successfulPhoneNumberShares } = await deleteSentProtoRecipient({
timestamp,
recipientServiceId: recipientServiceId2,
2022-08-15 21:53:33 +00:00
deviceId: 1,
});
assert.lengthOf(successfulPhoneNumberShares, 0);
}
assert.lengthOf(await getAllSentProtos(), 0);
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
});
it('returns deleted recipients when pni signature was sent', async () => {
const timestamp = Date.now();
const recipientServiceId1 = generateAci();
const recipientServiceId2 = generateAci();
2022-08-15 21:53:33 +00:00
const proto = {
contentHint: 1,
proto: getRandomBytes(128),
timestamp,
2022-08-15 21:53:33 +00:00
urgent: true,
hasPniSignatureMessage: true,
};
await insertSentProto(proto, {
messageIds: [generateUuid()],
2022-08-15 21:53:33 +00:00
recipients: {
[recipientServiceId1]: [1, 2],
[recipientServiceId2]: [1],
2022-08-15 21:53:33 +00:00
},
});
2022-08-15 21:53:33 +00:00
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 3);
{
const { successfulPhoneNumberShares } = await deleteSentProtoRecipient({
timestamp,
recipientServiceId: recipientServiceId1,
2022-08-15 21:53:33 +00:00
deviceId: 1,
});
assert.lengthOf(successfulPhoneNumberShares, 0);
}
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 2);
{
const { successfulPhoneNumberShares } = await deleteSentProtoRecipient({
timestamp,
recipientServiceId: recipientServiceId1,
2022-08-15 21:53:33 +00:00
deviceId: 2,
});
assert.deepStrictEqual(successfulPhoneNumberShares, [
recipientServiceId1,
]);
2022-08-15 21:53:33 +00:00
}
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 1);
{
const { successfulPhoneNumberShares } = await deleteSentProtoRecipient({
timestamp,
recipientServiceId: recipientServiceId2,
2022-08-15 21:53:33 +00:00
deviceId: 1,
});
assert.deepStrictEqual(successfulPhoneNumberShares, [
recipientServiceId2,
]);
2022-08-15 21:53:33 +00:00
}
assert.lengthOf(await getAllSentProtos(), 0);
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
});
2021-08-31 21:35:01 +00:00
it('deletes multiple recipients in a single transaction', async () => {
const timestamp = Date.now();
const recipientServiceId1 = generateAci();
const recipientServiceId2 = generateAci();
2021-08-31 21:35:01 +00:00
const proto = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
2021-08-31 21:35:01 +00:00
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
2021-08-31 21:35:01 +00:00
};
await insertSentProto(proto, {
messageIds: [generateUuid()],
2021-08-31 21:35:01 +00:00
recipients: {
[recipientServiceId1]: [1, 2],
[recipientServiceId2]: [1],
2021-08-31 21:35:01 +00:00
},
});
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 3);
2022-08-15 21:53:33 +00:00
const { successfulPhoneNumberShares } = await deleteSentProtoRecipient([
2021-08-31 21:35:01 +00:00
{
timestamp,
recipientServiceId: recipientServiceId1,
2021-08-31 21:35:01 +00:00
deviceId: 1,
},
{
timestamp,
recipientServiceId: recipientServiceId1,
2021-08-31 21:35:01 +00:00
deviceId: 2,
},
{
timestamp,
recipientServiceId: recipientServiceId2,
2021-08-31 21:35:01 +00:00
deviceId: 1,
},
]);
2022-08-15 21:53:33 +00:00
assert.lengthOf(successfulPhoneNumberShares, 0);
2021-08-31 21:35:01 +00:00
assert.lengthOf(await getAllSentProtos(), 0);
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
});
});
describe('#getSentProtoByRecipient', () => {
it('returns matching payload', async () => {
const timestamp = Date.now();
const recipientServiceId = generateAci();
const messageIds = [generateUuid(), generateUuid()];
const proto = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
await insertSentProto(proto, {
messageIds,
recipients: {
[recipientServiceId]: [1, 2],
},
});
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 2);
assert.lengthOf(await _getAllSentProtoMessageIds(), 2);
const actual = await getSentProtoByRecipient({
now: timestamp,
timestamp,
recipientServiceId,
});
if (!actual) {
throw new Error('Failed to fetch proto!');
}
assert.strictEqual(actual.contentHint, proto.contentHint);
2021-09-24 00:49:05 +00:00
assert.isTrue(constantTimeEqual(actual.proto, proto.proto));
assert.strictEqual(actual.timestamp, proto.timestamp);
assert.sameMembers(actual.messageIds, messageIds);
});
it('returns matching payload with no messageIds', async () => {
const timestamp = Date.now();
const recipientServiceId = generateAci();
const proto = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
await insertSentProto(proto, {
messageIds: [],
recipients: {
[recipientServiceId]: [1, 2],
},
});
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 2);
assert.lengthOf(await _getAllSentProtoMessageIds(), 0);
const actual = await getSentProtoByRecipient({
now: timestamp,
timestamp,
recipientServiceId,
});
if (!actual) {
throw new Error('Failed to fetch proto!');
}
assert.strictEqual(actual.contentHint, proto.contentHint);
2021-09-24 00:49:05 +00:00
assert.isTrue(constantTimeEqual(actual.proto, proto.proto));
assert.strictEqual(actual.timestamp, proto.timestamp);
assert.deepEqual(actual.messageIds, []);
});
it('returns nothing if payload does not have recipient', async () => {
const timestamp = Date.now();
const recipientServiceId = generateAci();
const proto = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
await insertSentProto(proto, {
messageIds: [generateUuid()],
recipients: {
[recipientServiceId]: [1, 2],
},
});
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 2);
const actual = await getSentProtoByRecipient({
now: timestamp,
timestamp,
recipientServiceId: generateAci(),
});
assert.isUndefined(actual);
});
it('returns nothing if timestamp does not match', async () => {
const timestamp = Date.now();
const recipientServiceId = generateAci();
const proto = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
await insertSentProto(proto, {
messageIds: [generateUuid()],
recipients: {
[recipientServiceId]: [1, 2],
},
});
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 2);
const actual = await getSentProtoByRecipient({
now: timestamp,
timestamp: timestamp + 1,
recipientServiceId,
});
assert.isUndefined(actual);
});
it('returns nothing if timestamp proto is too old', async () => {
const TWO_DAYS = 2 * 24 * 60 * 60 * 1000;
const timestamp = Date.now();
const recipientServiceId = generateAci();
const proto = {
contentHint: 1,
2021-09-24 00:49:05 +00:00
proto: getRandomBytes(128),
timestamp,
urgent: true,
2022-08-15 21:53:33 +00:00
hasPniSignatureMessage: false,
};
await insertSentProto(proto, {
messageIds: [generateUuid()],
recipients: {
[recipientServiceId]: [1, 2],
},
});
assert.lengthOf(await getAllSentProtos(), 1);
assert.lengthOf(await _getAllSentProtoRecipients(), 2);
const actual = await getSentProtoByRecipient({
now: timestamp + TWO_DAYS,
timestamp,
recipientServiceId,
});
assert.isUndefined(actual);
assert.lengthOf(await getAllSentProtos(), 0);
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
});
});
});