mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-04-18 23:49:20 +01:00
Simplify message deduplication
This commit is contained in:
179
ts/test-electron/messages/handleDataMessage_test.preload.ts
Normal file
179
ts/test-electron/messages/handleDataMessage_test.preload.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { handleDataMessage } from '../../messages/handleDataMessage.preload.js';
|
||||
import type { MessageAttributesType } from '../../model-types.d.ts';
|
||||
import { MessageModel } from '../../models/messages.preload.js';
|
||||
import { MessageCache } from '../../services/MessageCache.preload.js';
|
||||
import { DataWriter } from '../../sql/Client.preload.js';
|
||||
import { itemStorage } from '../../textsecure/Storage.preload.js';
|
||||
import type { ProcessedDataMessage } from '../../textsecure/Types.d.ts';
|
||||
import {
|
||||
type AciString,
|
||||
generateAci,
|
||||
generatePni,
|
||||
} from '../../types/ServiceId.std.js';
|
||||
import { DurationInSeconds } from '../../util/durations/duration-in-seconds.std.js';
|
||||
import { SignalService } from '../../protobuf/index.std.js';
|
||||
|
||||
describe('handleDataMessage', () => {
|
||||
let ourAci: AciString;
|
||||
|
||||
beforeEach(async () => {
|
||||
ourAci = generateAci();
|
||||
MessageCache.install();
|
||||
await itemStorage.user.setAciAndDeviceId(ourAci, 1);
|
||||
await itemStorage.user.setPni(generatePni());
|
||||
|
||||
window.ConversationController.reset();
|
||||
MessageCache.install();
|
||||
await window.ConversationController.load();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await DataWriter.removeAll();
|
||||
await itemStorage.fetch();
|
||||
window.ConversationController.reset();
|
||||
});
|
||||
|
||||
it('deduplicates incoming messages with same sender/timestamp, if existing message saved to DB', async () => {
|
||||
const senderAci = generateAci();
|
||||
const conversation = await window.ConversationController.getOrCreateAndWait(
|
||||
senderAci,
|
||||
'private'
|
||||
);
|
||||
const sentAt = Date.now();
|
||||
|
||||
const existingAttributes: MessageAttributesType = {
|
||||
id: uuid(),
|
||||
conversationId: conversation.id,
|
||||
type: 'incoming',
|
||||
sourceServiceId: senderAci,
|
||||
sourceDevice: 1,
|
||||
sent_at: sentAt,
|
||||
timestamp: sentAt,
|
||||
received_at: sentAt,
|
||||
};
|
||||
|
||||
await DataWriter.saveMessage(existingAttributes, {
|
||||
ourAci,
|
||||
forceSave: true,
|
||||
postSaveUpdates: () => Promise.resolve(),
|
||||
});
|
||||
|
||||
const dataMessage: ProcessedDataMessage = {
|
||||
attachments: [],
|
||||
flags: 0,
|
||||
body: 'body',
|
||||
expireTimer: DurationInSeconds.fromDays(0),
|
||||
expireTimerVersion: 1,
|
||||
isViewOnce: false,
|
||||
timestamp: sentAt,
|
||||
requiredProtocolVersion:
|
||||
SignalService.DataMessage.ProtocolVersion.CURRENT,
|
||||
};
|
||||
|
||||
const duplicateMessage = new MessageModel({
|
||||
id: uuid(),
|
||||
conversationId: conversation.id,
|
||||
type: 'incoming',
|
||||
sourceServiceId: senderAci,
|
||||
sourceDevice: 2,
|
||||
sent_at: sentAt,
|
||||
timestamp: sentAt,
|
||||
received_at: sentAt + 1,
|
||||
});
|
||||
|
||||
const saveAndNotify = sinon.stub();
|
||||
const confirm = sinon.stub();
|
||||
|
||||
await handleDataMessage(
|
||||
duplicateMessage,
|
||||
dataMessage,
|
||||
confirm,
|
||||
{},
|
||||
{ saveAndNotify }
|
||||
);
|
||||
|
||||
assert.strictEqual(saveAndNotify.callCount, 0, 'not saved');
|
||||
assert.strictEqual(confirm.callCount, 1, 'confirmed immediately');
|
||||
});
|
||||
|
||||
it('deduplicates incoming messages with same sender/timestamp, if existing message only in memory', async () => {
|
||||
const senderAci = generateAci();
|
||||
const conversation = await window.ConversationController.getOrCreateAndWait(
|
||||
senderAci,
|
||||
'private'
|
||||
);
|
||||
const sentAt = Date.now();
|
||||
const dataMessage: ProcessedDataMessage = {
|
||||
attachments: [],
|
||||
flags: 0,
|
||||
body: 'body',
|
||||
expireTimer: DurationInSeconds.fromDays(0),
|
||||
expireTimerVersion: 1,
|
||||
isViewOnce: false,
|
||||
timestamp: sentAt,
|
||||
requiredProtocolVersion:
|
||||
SignalService.DataMessage.ProtocolVersion.CURRENT,
|
||||
};
|
||||
const saveAndNotify = sinon.stub();
|
||||
const confirm = sinon.stub();
|
||||
|
||||
const attributes: MessageAttributesType = {
|
||||
id: uuid(),
|
||||
conversationId: conversation.id,
|
||||
type: 'incoming',
|
||||
sourceServiceId: senderAci,
|
||||
sourceDevice: 1,
|
||||
sent_at: sentAt,
|
||||
timestamp: sentAt,
|
||||
received_at: sentAt,
|
||||
};
|
||||
|
||||
await handleDataMessage(
|
||||
new MessageModel(attributes),
|
||||
dataMessage,
|
||||
confirm,
|
||||
{},
|
||||
{ saveAndNotify }
|
||||
);
|
||||
|
||||
assert.strictEqual(saveAndNotify.callCount, 1, 'initial message saved');
|
||||
assert.strictEqual(confirm.callCount, 0, 'not confirmed until saved');
|
||||
|
||||
// Calling it again with same message does not call saveAndNotify, but does confirm()
|
||||
await handleDataMessage(
|
||||
new MessageModel(attributes),
|
||||
dataMessage,
|
||||
confirm,
|
||||
{},
|
||||
{ saveAndNotify }
|
||||
);
|
||||
|
||||
assert.strictEqual(saveAndNotify.callCount, 1, 'not saved again');
|
||||
assert.strictEqual(confirm.callCount, 1, 'duplicate confirmed immediately');
|
||||
|
||||
// Calling it again with different message but same aci/timestamp does not call
|
||||
// saveAndNotify, but does confirm()
|
||||
await handleDataMessage(
|
||||
new MessageModel({
|
||||
...attributes,
|
||||
// we intentionally (if suboptimally) do not consider deviceId when deduplicating
|
||||
sourceDevice: 2,
|
||||
id: uuid(),
|
||||
}),
|
||||
dataMessage,
|
||||
confirm,
|
||||
{},
|
||||
{ saveAndNotify }
|
||||
);
|
||||
|
||||
assert.strictEqual(saveAndNotify.callCount, 1, 'not saved again');
|
||||
assert.strictEqual(confirm.callCount, 2, 'duplicate confirmed immediately');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user