383 lines
11 KiB
TypeScript
383 lines
11 KiB
TypeScript
|
// Copyright 2021 Signal Messenger, LLC
|
||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||
|
|
||
|
import { assert } from 'chai';
|
||
|
import { v4 as uuid } from 'uuid';
|
||
|
import type { MessageAttributesType } from '../../model-types.d';
|
||
|
import { MessageModel } from '../../models/messages';
|
||
|
import { strictAssert } from '../../util/assert';
|
||
|
|
||
|
import { MessageCache } from '../../services/MessageCache';
|
||
|
|
||
|
describe('MessageCache', () => {
|
||
|
describe('filterBySentAt', () => {
|
||
|
it('returns an empty iterable if no messages match', () => {
|
||
|
const mc = new MessageCache();
|
||
|
|
||
|
assert.isEmpty([...mc.__DEPRECATED$filterBySentAt(123)]);
|
||
|
});
|
||
|
|
||
|
it('returns all messages that match the timestamp', () => {
|
||
|
const mc = new MessageCache();
|
||
|
|
||
|
let message1 = new MessageModel({
|
||
|
conversationId: 'xyz',
|
||
|
body: 'message1',
|
||
|
id: uuid(),
|
||
|
received_at: 1,
|
||
|
sent_at: 1234,
|
||
|
timestamp: 9999,
|
||
|
type: 'incoming',
|
||
|
});
|
||
|
let message2 = new MessageModel({
|
||
|
conversationId: 'xyz',
|
||
|
body: 'message2',
|
||
|
id: uuid(),
|
||
|
received_at: 2,
|
||
|
sent_at: 1234,
|
||
|
timestamp: 9999,
|
||
|
type: 'outgoing',
|
||
|
});
|
||
|
const message3 = new MessageModel({
|
||
|
conversationId: 'xyz',
|
||
|
body: 'message3',
|
||
|
id: uuid(),
|
||
|
received_at: 3,
|
||
|
sent_at: 5678,
|
||
|
timestamp: 9999,
|
||
|
type: 'outgoing',
|
||
|
});
|
||
|
|
||
|
message1 = mc.__DEPRECATED$register(message1.id, message1, 'test');
|
||
|
message2 = mc.__DEPRECATED$register(message2.id, message2, 'test');
|
||
|
// We deliberately register this message twice for testing.
|
||
|
message2 = mc.__DEPRECATED$register(message2.id, message2, 'test');
|
||
|
mc.__DEPRECATED$register(message3.id, message3, 'test');
|
||
|
|
||
|
const filteredMessages = Array.from(
|
||
|
mc.__DEPRECATED$filterBySentAt(1234)
|
||
|
).map(x => x.attributes);
|
||
|
|
||
|
assert.deepEqual(
|
||
|
filteredMessages,
|
||
|
[message1.attributes, message2.attributes],
|
||
|
'first'
|
||
|
);
|
||
|
|
||
|
mc.__DEPRECATED$unregister(message2.id);
|
||
|
|
||
|
const filteredMessages2 = Array.from(
|
||
|
mc.__DEPRECATED$filterBySentAt(1234)
|
||
|
).map(x => x.attributes);
|
||
|
|
||
|
assert.deepEqual(filteredMessages2, [message1.attributes], 'second');
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('__DEPRECATED$register: syncing with backbone', () => {
|
||
|
it('backbone to redux', () => {
|
||
|
const message1 = new MessageModel({
|
||
|
conversationId: 'xyz',
|
||
|
id: uuid(),
|
||
|
body: 'test1',
|
||
|
received_at: 1,
|
||
|
sent_at: Date.now(),
|
||
|
timestamp: Date.now(),
|
||
|
type: 'outgoing',
|
||
|
});
|
||
|
const messageFromController = window.MessageCache.__DEPRECATED$register(
|
||
|
message1.id,
|
||
|
message1,
|
||
|
'test'
|
||
|
);
|
||
|
|
||
|
assert.strictEqual(
|
||
|
message1,
|
||
|
messageFromController,
|
||
|
'same objects from mc.__DEPRECATED$register'
|
||
|
);
|
||
|
|
||
|
const messageById = window.MessageCache.__DEPRECATED$getById(message1.id);
|
||
|
|
||
|
assert.strictEqual(message1, messageById, 'same objects from mc.getById');
|
||
|
|
||
|
const messageInCache = window.MessageCache.accessAttributes(message1.id);
|
||
|
strictAssert(messageInCache, 'no message found');
|
||
|
assert.deepEqual(
|
||
|
message1.attributes,
|
||
|
messageInCache,
|
||
|
'same attributes as in cache'
|
||
|
);
|
||
|
|
||
|
message1.set({ body: 'test2' });
|
||
|
assert.equal(message1.attributes.body, 'test2', 'message model updated');
|
||
|
assert.equal(
|
||
|
messageById?.attributes.body,
|
||
|
'test2',
|
||
|
'old reference from messageById was updated'
|
||
|
);
|
||
|
assert.equal(
|
||
|
messageInCache.body,
|
||
|
'test1',
|
||
|
'old cache reference not updated'
|
||
|
);
|
||
|
|
||
|
const newMessageById = window.MessageCache.__DEPRECATED$getById(
|
||
|
message1.id
|
||
|
);
|
||
|
assert.deepEqual(
|
||
|
message1.attributes,
|
||
|
newMessageById?.attributes,
|
||
|
'same attributes from mc.getById (2)'
|
||
|
);
|
||
|
|
||
|
const newMessageInCache = window.MessageCache.accessAttributes(
|
||
|
message1.id
|
||
|
);
|
||
|
strictAssert(newMessageInCache, 'no message found');
|
||
|
assert.deepEqual(
|
||
|
message1.attributes,
|
||
|
newMessageInCache,
|
||
|
'same attributes as in cache (2)'
|
||
|
);
|
||
|
});
|
||
|
|
||
|
it('redux to backbone (working with models)', () => {
|
||
|
const message = new MessageModel({
|
||
|
conversationId: 'xyz',
|
||
|
id: uuid(),
|
||
|
body: 'test1',
|
||
|
received_at: 1,
|
||
|
sent_at: Date.now(),
|
||
|
timestamp: Date.now(),
|
||
|
type: 'outgoing',
|
||
|
});
|
||
|
|
||
|
window.MessageCache.toMessageAttributes(message.attributes);
|
||
|
|
||
|
const messageFromController = window.MessageCache.__DEPRECATED$register(
|
||
|
message.id,
|
||
|
message,
|
||
|
'test'
|
||
|
);
|
||
|
|
||
|
assert.notStrictEqual(
|
||
|
message,
|
||
|
messageFromController,
|
||
|
'mc.__DEPRECATED$register returns existing but it is not the same reference'
|
||
|
);
|
||
|
assert.deepEqual(
|
||
|
message.attributes,
|
||
|
messageFromController.attributes,
|
||
|
'mc.__DEPRECATED$register returns existing and is the same attributes'
|
||
|
);
|
||
|
|
||
|
messageFromController.set({ body: 'test2' });
|
||
|
|
||
|
assert.notEqual(
|
||
|
message.get('body'),
|
||
|
messageFromController.get('body'),
|
||
|
'new model is not equal to old model'
|
||
|
);
|
||
|
|
||
|
const messageInCache = window.MessageCache.accessAttributes(message.id);
|
||
|
strictAssert(messageInCache, 'no message found');
|
||
|
assert.equal(
|
||
|
messageFromController.get('body'),
|
||
|
messageInCache.body,
|
||
|
'new update is in cache'
|
||
|
);
|
||
|
|
||
|
assert.isUndefined(
|
||
|
messageFromController.get('storyReplyContext'),
|
||
|
'storyReplyContext is undefined'
|
||
|
);
|
||
|
|
||
|
window.MessageCache.setAttributes({
|
||
|
messageId: message.id,
|
||
|
messageAttributes: {
|
||
|
storyReplyContext: {
|
||
|
attachment: undefined,
|
||
|
authorAci: undefined,
|
||
|
messageId: 'test123',
|
||
|
},
|
||
|
},
|
||
|
skipSaveToDatabase: true,
|
||
|
});
|
||
|
|
||
|
// This works because we refresh the model whenever an attribute changes
|
||
|
// but this should log a warning.
|
||
|
assert.equal(
|
||
|
messageFromController.get('storyReplyContext')?.messageId,
|
||
|
'test123',
|
||
|
'storyReplyContext was updated (stale model)'
|
||
|
);
|
||
|
|
||
|
const newMessageFromController =
|
||
|
window.MessageCache.__DEPRECATED$register(message.id, message, 'test');
|
||
|
|
||
|
assert.equal(
|
||
|
newMessageFromController.get('storyReplyContext')?.messageId,
|
||
|
'test123',
|
||
|
'storyReplyContext was updated (not stale)'
|
||
|
);
|
||
|
});
|
||
|
|
||
|
it('redux to backbone (working with attributes)', () => {
|
||
|
it('sets the attributes and returns a fresh copy', () => {
|
||
|
const mc = new MessageCache();
|
||
|
|
||
|
const messageAttributes: MessageAttributesType = {
|
||
|
conversationId: uuid(),
|
||
|
id: uuid(),
|
||
|
received_at: 1,
|
||
|
sent_at: Date.now(),
|
||
|
timestamp: Date.now(),
|
||
|
type: 'incoming',
|
||
|
};
|
||
|
|
||
|
const messageModel = mc.__DEPRECATED$register(
|
||
|
messageAttributes.id,
|
||
|
messageAttributes,
|
||
|
'test/updateAttributes'
|
||
|
);
|
||
|
|
||
|
assert.deepEqual(
|
||
|
messageAttributes,
|
||
|
messageModel.attributes,
|
||
|
'initial attributes matches message model'
|
||
|
);
|
||
|
|
||
|
const proposedStoryReplyContext = {
|
||
|
attachment: undefined,
|
||
|
authorAci: undefined,
|
||
|
messageId: 'test123',
|
||
|
};
|
||
|
|
||
|
assert.notDeepEqual(
|
||
|
messageModel.attributes.storyReplyContext,
|
||
|
proposedStoryReplyContext,
|
||
|
'attributes were changed outside of the message model'
|
||
|
);
|
||
|
|
||
|
mc.setAttributes({
|
||
|
messageId: messageAttributes.id,
|
||
|
messageAttributes: {
|
||
|
storyReplyContext: proposedStoryReplyContext,
|
||
|
},
|
||
|
skipSaveToDatabase: true,
|
||
|
});
|
||
|
|
||
|
const nextMessageAttributes = mc.accessAttributesOrThrow(
|
||
|
'test',
|
||
|
messageAttributes.id
|
||
|
);
|
||
|
|
||
|
assert.notDeepEqual(
|
||
|
messageAttributes,
|
||
|
nextMessageAttributes,
|
||
|
'initial attributes are stale'
|
||
|
);
|
||
|
assert.notDeepEqual(
|
||
|
messageAttributes.storyReplyContext,
|
||
|
proposedStoryReplyContext,
|
||
|
'initial attributes are stale 2'
|
||
|
);
|
||
|
|
||
|
assert.deepEqual(
|
||
|
nextMessageAttributes.storyReplyContext,
|
||
|
proposedStoryReplyContext,
|
||
|
'fresh attributes match what was proposed'
|
||
|
);
|
||
|
assert.notStrictEqual(
|
||
|
nextMessageAttributes.storyReplyContext,
|
||
|
proposedStoryReplyContext,
|
||
|
'fresh attributes are not the same reference as proposed attributes'
|
||
|
);
|
||
|
|
||
|
assert.deepEqual(
|
||
|
messageModel.attributes,
|
||
|
nextMessageAttributes,
|
||
|
'model was updated'
|
||
|
);
|
||
|
|
||
|
assert.equal(
|
||
|
messageModel.get('storyReplyContext')?.messageId,
|
||
|
'test123',
|
||
|
'storyReplyContext in model is set correctly'
|
||
|
);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('accessAttributes', () => {
|
||
|
it('gets the attributes if they exist', () => {
|
||
|
const mc = new MessageCache();
|
||
|
|
||
|
const messageAttributes: MessageAttributesType = {
|
||
|
conversationId: uuid(),
|
||
|
id: uuid(),
|
||
|
received_at: 1,
|
||
|
sent_at: Date.now(),
|
||
|
timestamp: Date.now(),
|
||
|
type: 'incoming',
|
||
|
};
|
||
|
|
||
|
mc.toMessageAttributes(messageAttributes);
|
||
|
|
||
|
const accessAttributes = mc.accessAttributes(messageAttributes.id);
|
||
|
|
||
|
assert.deepEqual(
|
||
|
accessAttributes,
|
||
|
messageAttributes,
|
||
|
'attributes returned have the same values'
|
||
|
);
|
||
|
assert.notStrictEqual(
|
||
|
accessAttributes,
|
||
|
messageAttributes,
|
||
|
'attributes returned are not the same references'
|
||
|
);
|
||
|
|
||
|
const undefinedMessage = mc.accessAttributes(uuid());
|
||
|
assert.isUndefined(undefinedMessage, 'access did not find message');
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('accessAttributesOrThrow', () => {
|
||
|
it('accesses the attributes or throws if they do not exist', () => {
|
||
|
const mc = new MessageCache();
|
||
|
|
||
|
const messageAttributes: MessageAttributesType = {
|
||
|
conversationId: uuid(),
|
||
|
id: uuid(),
|
||
|
received_at: 1,
|
||
|
sent_at: Date.now(),
|
||
|
timestamp: Date.now(),
|
||
|
type: 'incoming',
|
||
|
};
|
||
|
|
||
|
mc.toMessageAttributes(messageAttributes);
|
||
|
|
||
|
const accessAttributes = mc.accessAttributesOrThrow(
|
||
|
'tests.1',
|
||
|
messageAttributes.id
|
||
|
);
|
||
|
|
||
|
assert.deepEqual(
|
||
|
accessAttributes,
|
||
|
messageAttributes,
|
||
|
'attributes returned have the same values'
|
||
|
);
|
||
|
assert.notStrictEqual(
|
||
|
accessAttributes,
|
||
|
messageAttributes,
|
||
|
'attributes returned are not the same references'
|
||
|
);
|
||
|
|
||
|
assert.throws(() => {
|
||
|
mc.accessAttributesOrThrow('tests.2', uuid());
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|