Migration and data access functions for stories

This commit is contained in:
Scott Nonnenberg 2021-12-08 11:52:46 -08:00 committed by GitHub
parent 9f4a01c535
commit fdc9885baa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 3428 additions and 202 deletions

View file

@ -0,0 +1,266 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import dataInterface from '../../sql/Client';
import { UUID } from '../../types/UUID';
import type { UUIDStringType } from '../../types/UUID';
import type { MessageAttributesType } from '../../model-types.d';
const {
removeAll,
_getAllMessages,
saveMessages,
getMessagesWithVisualMediaAttachments,
getMessagesWithFileAttachments,
} = dataInterface;
function getUuid(): UUIDStringType {
return UUID.generate().toString();
}
describe('sql/allMedia', () => {
beforeEach(async () => {
await removeAll();
});
describe('getMessagesWithVisualMediaAttachments', () => {
it('returns messages matching with visual attachments', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const now = Date.now();
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId,
sent_at: now - 20,
received_at: now - 20,
timestamp: now - 20,
hasVisualMediaAttachments: true,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: now - 10,
received_at: now - 10,
timestamp: now - 10,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'outgoing',
conversationId: getUuid(),
sent_at: now,
received_at: now,
timestamp: now,
hasVisualMediaAttachments: true,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const searchResults = await getMessagesWithVisualMediaAttachments(
conversationId,
{ limit: 5 }
);
assert.lengthOf(searchResults, 1);
assert.strictEqual(searchResults[0].id, message1.id);
});
it('excludes stories and story replies', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const now = Date.now();
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId,
sent_at: now - 20,
received_at: now - 20,
timestamp: now - 20,
hasVisualMediaAttachments: true,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: now - 10,
received_at: now - 10,
timestamp: now - 10,
storyId: getUuid(),
hasVisualMediaAttachments: true,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'story',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
storyId: getUuid(),
hasVisualMediaAttachments: true,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const searchResults = await getMessagesWithVisualMediaAttachments(
conversationId,
{ limit: 5 }
);
assert.lengthOf(searchResults, 1);
assert.strictEqual(searchResults[0].id, message1.id);
});
});
describe('getMessagesWithFileAttachments', () => {
it('returns messages matching with visual attachments', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const now = Date.now();
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId,
sent_at: now - 20,
received_at: now - 20,
timestamp: now - 20,
hasFileAttachments: true,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: now - 10,
received_at: now - 10,
timestamp: now - 10,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'outgoing',
conversationId: getUuid(),
sent_at: now,
received_at: now,
timestamp: now,
hasFileAttachments: true,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const searchResults = await getMessagesWithFileAttachments(
conversationId,
{ limit: 5 }
);
assert.lengthOf(searchResults, 1);
assert.strictEqual(searchResults[0].id, message1.id);
});
it('excludes stories and story replies', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const now = Date.now();
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId,
sent_at: now - 20,
received_at: now - 20,
timestamp: now - 20,
hasFileAttachments: true,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: now - 10,
received_at: now - 10,
timestamp: now - 10,
storyId: getUuid(),
hasFileAttachments: true,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'story',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
storyId: getUuid(),
hasFileAttachments: true,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const searchResults = await getMessagesWithFileAttachments(
conversationId,
{ limit: 5 }
);
assert.lengthOf(searchResults, 1);
assert.strictEqual(searchResults[0].id, message1.id);
});
});
});

View file

@ -0,0 +1,210 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import dataInterface from '../../sql/Client';
import { UUID } from '../../types/UUID';
import type { UUIDStringType } from '../../types/UUID';
import type { MessageAttributesType } from '../../model-types.d';
const {
removeAll,
_getAllMessages,
saveMessages,
saveMessage,
searchMessages,
} = dataInterface;
function getUuid(): UUIDStringType {
return UUID.generate().toString();
}
describe('sql/fullTextSearch', () => {
beforeEach(async () => {
await removeAll();
});
it('returns messages matching query', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const now = Date.now();
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1 - generic string',
type: 'outgoing',
conversationId,
sent_at: now - 20,
received_at: now - 20,
timestamp: now - 20,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2 - unique string',
type: 'outgoing',
conversationId,
sent_at: now - 10,
received_at: now - 10,
timestamp: now - 10,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3 - generic string',
type: 'outgoing',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const searchResults = await searchMessages('unique');
assert.lengthOf(searchResults, 1);
assert.strictEqual(searchResults[0].id, message2.id);
message3.body = 'message 3 - unique string';
await saveMessage(message3);
const searchResults2 = await searchMessages('unique');
assert.lengthOf(searchResults2, 2);
assert.strictEqual(searchResults2[0].id, message3.id);
assert.strictEqual(searchResults2[1].id, message2.id);
});
it('excludes messages with isViewOnce = true', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const now = Date.now();
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1 - unique string',
type: 'outgoing',
conversationId,
sent_at: now - 20,
received_at: now - 20,
timestamp: now - 20,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2 - unique string',
type: 'outgoing',
conversationId,
sent_at: now - 10,
received_at: now - 10,
timestamp: now - 10,
isViewOnce: true,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3 - generic string',
type: 'outgoing',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
isViewOnce: true,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const searchResults = await searchMessages('unique');
assert.lengthOf(searchResults, 1);
assert.strictEqual(searchResults[0].id, message1.id);
message1.body = 'message 3 - unique string';
await saveMessage(message3);
const searchResults2 = await searchMessages('unique');
assert.lengthOf(searchResults2, 1);
assert.strictEqual(searchResults2[0].id, message1.id);
});
it('excludes messages with storyId !== null', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const now = Date.now();
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1 - unique string',
type: 'outgoing',
conversationId,
sent_at: now - 20,
received_at: now - 20,
timestamp: now - 20,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2 - unique string',
type: 'outgoing',
conversationId,
sent_at: now - 10,
received_at: now - 10,
timestamp: now - 10,
storyId: getUuid(),
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3 - generic string',
type: 'outgoing',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
storyId: getUuid(),
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const searchResults = await searchMessages('unique');
assert.lengthOf(searchResults, 1);
assert.strictEqual(searchResults[0].id, message1.id);
message1.body = 'message 3 - unique string';
await saveMessage(message3);
const searchResults2 = await searchMessages('unique');
assert.lengthOf(searchResults2, 1);
assert.strictEqual(searchResults2[0].id, message1.id);
});
});

View file

@ -0,0 +1,738 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import dataInterface from '../../sql/Client';
import { UUID } from '../../types/UUID';
import type { UUIDStringType } from '../../types/UUID';
import type { ReactionType } from '../../types/Reactions';
import type { MessageAttributesType } from '../../model-types.d';
import { ReadStatus } from '../../messages/MessageReadStatus';
const {
_removeAllMessages,
_removeAllReactions,
_getAllReactions,
_getAllMessages,
addReaction,
saveMessages,
getTotalUnreadForConversation,
getUnreadByConversationAndMarkRead,
getUnreadReactionsAndMarkRead,
} = dataInterface;
function getUuid(): UUIDStringType {
return UUID.generate().toString();
}
describe('sql/markRead', () => {
beforeEach(async () => {
await _removeAllMessages();
await _removeAllReactions();
});
it('properly finds and reads unread messages in current conversation', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const start = Date.now();
const readAt = start + 20;
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'incoming',
conversationId,
sent_at: start + 1,
received_at: start + 1,
timestamp: start + 1,
readStatus: ReadStatus.Read,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'incoming',
conversationId,
sent_at: start + 2,
received_at: start + 2,
timestamp: start + 2,
readStatus: ReadStatus.Unread,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'incoming',
conversationId: getUuid(),
sent_at: start + 3,
received_at: start + 3,
timestamp: start + 3,
readStatus: ReadStatus.Unread,
};
const message4: MessageAttributesType = {
id: getUuid(),
body: 'message 4',
type: 'incoming',
conversationId,
sent_at: start + 4,
received_at: start + 4,
timestamp: start + 4,
readStatus: ReadStatus.Unread,
};
const message5: MessageAttributesType = {
id: getUuid(),
body: 'message 5',
type: 'story',
conversationId,
sent_at: start + 5,
received_at: start + 5,
timestamp: start + 5,
readStatus: ReadStatus.Unread,
storyId: getUuid(),
};
const message6: MessageAttributesType = {
id: getUuid(),
body: 'message 6',
type: 'incoming',
conversationId,
sent_at: start + 6,
received_at: start + 6,
timestamp: start + 6,
readStatus: ReadStatus.Unread,
storyId: getUuid(),
};
const message7: MessageAttributesType = {
id: getUuid(),
body: 'message 7',
type: 'incoming',
conversationId,
sent_at: start + 7,
received_at: start + 7,
timestamp: start + 7,
readStatus: ReadStatus.Unread,
};
await saveMessages(
[message1, message2, message3, message4, message5, message6, message7],
{
forceSave: true,
}
);
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
7
);
assert.strictEqual(
await getTotalUnreadForConversation(conversationId),
3,
'uread count'
);
const markedRead = await getUnreadByConversationAndMarkRead({
conversationId,
newestUnreadAt: message4.received_at,
readAt,
});
assert.lengthOf(markedRead, 2, 'two messages marked read');
assert.strictEqual(
await getTotalUnreadForConversation(conversationId),
1,
'unread count'
);
// Sorted in descending order
assert.strictEqual(
markedRead[0].id,
message4.id,
'first should be message4'
);
assert.strictEqual(
markedRead[1].id,
message2.id,
'second should be message2'
);
const markedRead2 = await getUnreadByConversationAndMarkRead({
conversationId,
newestUnreadAt: message7.received_at,
readAt,
});
assert.lengthOf(markedRead2, 1, 'one message marked read');
assert.strictEqual(markedRead2[0].id, message7.id, 'should be message7');
assert.strictEqual(
await getTotalUnreadForConversation(conversationId),
0,
'unread count'
);
});
it('properly finds and reads unread messages in story', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const start = Date.now();
const readAt = start + 20;
const conversationId = getUuid();
const storyId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'story',
conversationId,
sent_at: start + 1,
received_at: start + 1,
timestamp: start + 1,
readStatus: ReadStatus.Read,
storyId,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'incoming',
conversationId,
sent_at: start + 2,
received_at: start + 2,
timestamp: start + 2,
readStatus: ReadStatus.Unread,
storyId,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'incoming',
conversationId,
sent_at: start + 3,
received_at: start + 3,
timestamp: start + 3,
readStatus: ReadStatus.Unread,
storyId: getUuid(),
};
const message4: MessageAttributesType = {
id: getUuid(),
body: 'message 4',
type: 'incoming',
conversationId,
sent_at: start + 4,
received_at: start + 4,
timestamp: start + 4,
readStatus: ReadStatus.Unread,
storyId,
};
const message5: MessageAttributesType = {
id: getUuid(),
body: 'message 5',
type: 'incoming',
conversationId,
sent_at: start + 5,
received_at: start + 5,
timestamp: start + 5,
readStatus: ReadStatus.Unread,
storyId: getUuid(),
};
const message6: MessageAttributesType = {
id: getUuid(),
body: 'message 6',
type: 'incoming',
conversationId,
sent_at: start + 6,
received_at: start + 6,
timestamp: start + 6,
readStatus: ReadStatus.Unread,
storyId: getUuid(),
};
const message7: MessageAttributesType = {
id: getUuid(),
body: 'message 7',
type: 'incoming',
conversationId,
sent_at: start + 7,
received_at: start + 7,
timestamp: start + 7,
readStatus: ReadStatus.Unread,
storyId,
};
await saveMessages(
[message1, message2, message3, message4, message5, message6, message7],
{
forceSave: true,
}
);
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
7
);
const markedRead = await getUnreadByConversationAndMarkRead({
conversationId,
newestUnreadAt: message7.received_at,
readAt,
storyId,
});
assert.lengthOf(markedRead, 3, 'three messages marked read');
// Sorted in descending order
assert.strictEqual(
markedRead[0].id,
message7.id,
'first should be message7'
);
assert.strictEqual(
markedRead[1].id,
message4.id,
'first should be message4'
);
assert.strictEqual(
markedRead[2].id,
message2.id,
'second should be message2'
);
});
it('properly starts disappearing message timer, even if message is already read', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const start = Date.now();
const readAt = start + 20;
const conversationId = getUuid();
const expireTimer = 15;
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'incoming',
conversationId,
sent_at: start + 1,
received_at: start + 1,
timestamp: start + 1,
expireTimer,
expirationStartTimestamp: start + 1,
readStatus: ReadStatus.Read,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'incoming',
conversationId,
sent_at: start + 2,
received_at: start + 2,
timestamp: start + 2,
expireTimer,
readStatus: ReadStatus.Read,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'incoming',
conversationId: getUuid(),
sent_at: start + 3,
received_at: start + 3,
timestamp: start + 3,
expireTimer,
readStatus: ReadStatus.Unread,
};
const message4: MessageAttributesType = {
id: getUuid(),
body: 'message 4',
type: 'incoming',
conversationId,
sent_at: start + 4,
received_at: start + 4,
timestamp: start + 4,
expireTimer,
readStatus: ReadStatus.Unread,
};
const message5: MessageAttributesType = {
id: getUuid(),
body: 'message 5',
type: 'incoming',
conversationId,
sent_at: start + 5,
received_at: start + 5,
timestamp: start + 5,
readStatus: ReadStatus.Unread,
};
await saveMessages([message1, message2, message3, message4, message5], {
forceSave: true,
});
assert.strictEqual(
await getTotalUnreadForConversation(conversationId),
2,
'unread count'
);
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
5
);
const markedRead = await getUnreadByConversationAndMarkRead({
conversationId,
newestUnreadAt: message4.received_at,
readAt,
});
assert.lengthOf(markedRead, 1, 'one message marked read');
assert.strictEqual(
markedRead[0].id,
message4.id,
'first should be message4'
);
assert.strictEqual(
await getTotalUnreadForConversation(conversationId),
1,
'unread count'
);
const allMessages = await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
});
// Ascending order, since it's sorted by MessageCollection
assert.strictEqual(allMessages.at(1).id, message2.id);
assert.isAtMost(
allMessages.at(1).get('expirationStartTimestamp') ?? Infinity,
Date.now(),
'checking message 2 expirationStartTimestamp'
);
assert.strictEqual(allMessages.at(3).id, message4.id, 'checking message 4');
assert.isAtMost(
allMessages.at(3).get('expirationStartTimestamp') ?? Infinity,
Date.now(),
'checking message 4 expirationStartTimestamp'
);
});
it('properly finds and reads unread reactions in current conversation', async () => {
assert.lengthOf(await _getAllReactions(), 0);
const start = Date.now();
const conversationId = getUuid();
const storyId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'incoming',
conversationId,
sent_at: start + 1,
received_at: start + 1,
timestamp: start + 1,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'incoming',
conversationId,
sent_at: start + 2,
received_at: start + 2,
timestamp: start + 2,
storyId,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'incoming',
conversationId: getUuid(),
sent_at: start + 3,
received_at: start + 3,
timestamp: start + 3,
};
const message4: MessageAttributesType = {
id: getUuid(),
body: 'message 4',
type: 'incoming',
conversationId,
sent_at: start + 4,
received_at: start + 4,
timestamp: start + 4,
};
const message5: MessageAttributesType = {
id: getUuid(),
body: 'message 5',
type: 'incoming',
conversationId,
sent_at: start + 5,
received_at: start + 5,
timestamp: start + 5,
};
await saveMessages([message1, message2, message3, message4, message5], {
forceSave: true,
});
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
5
);
const reaction1: ReactionType = {
conversationId,
emoji: '🎉',
fromId: getUuid(),
messageId: message1.id,
messageReceivedAt: message1.received_at,
targetAuthorUuid: getUuid(),
targetTimestamp: start,
};
const reaction2: ReactionType = {
conversationId,
emoji: '🚀',
fromId: getUuid(),
messageId: message2.id,
messageReceivedAt: message2.received_at,
targetAuthorUuid: getUuid(),
targetTimestamp: start,
};
const reaction3: ReactionType = {
conversationId: getUuid(),
emoji: '☀️',
fromId: getUuid(),
messageId: message3.id,
messageReceivedAt: message3.received_at,
targetAuthorUuid: getUuid(),
targetTimestamp: start,
};
const reaction4: ReactionType = {
conversationId,
emoji: '❤️‍🔥',
fromId: getUuid(),
messageId: message4.id,
messageReceivedAt: message4.received_at,
targetAuthorUuid: getUuid(),
targetTimestamp: start,
};
const reaction5: ReactionType = {
conversationId,
emoji: '🆒',
fromId: getUuid(),
messageId: message5.id,
messageReceivedAt: message5.received_at,
targetAuthorUuid: getUuid(),
targetTimestamp: start,
};
await addReaction(reaction1);
await addReaction(reaction2);
await addReaction(reaction3);
await addReaction(reaction4);
await addReaction(reaction5);
assert.lengthOf(await _getAllReactions(), 5);
const markedRead = await getUnreadReactionsAndMarkRead({
conversationId,
newestUnreadAt: reaction4.messageReceivedAt,
});
assert.lengthOf(markedRead, 2, 'two reactions marked read');
// Sorted in descending order
assert.strictEqual(
markedRead[0].messageId,
reaction4.messageId,
'first should be reaction4'
);
assert.strictEqual(
markedRead[1].messageId,
reaction1.messageId,
'second should be reaction1'
);
const markedRead2 = await getUnreadReactionsAndMarkRead({
conversationId,
newestUnreadAt: reaction5.messageReceivedAt,
});
assert.lengthOf(markedRead2, 1);
assert.strictEqual(
markedRead2[0].messageId,
reaction5.messageId,
'should be reaction5'
);
});
it('properly finds and reads unread reactions in story', async () => {
assert.lengthOf(await _getAllReactions(), 0);
const start = Date.now();
const conversationId = getUuid();
const storyId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'incoming',
conversationId,
sent_at: start + 1,
received_at: start + 1,
timestamp: start + 1,
storyId,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'incoming',
conversationId,
sent_at: start + 2,
received_at: start + 2,
timestamp: start + 2,
storyId: getUuid(),
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'incoming',
conversationId: getUuid(),
sent_at: start + 3,
received_at: start + 3,
timestamp: start + 3,
};
const message4: MessageAttributesType = {
id: getUuid(),
body: 'message 4',
type: 'incoming',
conversationId,
sent_at: start + 4,
received_at: start + 4,
timestamp: start + 4,
storyId,
};
const message5: MessageAttributesType = {
id: getUuid(),
body: 'message 5',
type: 'incoming',
conversationId,
sent_at: start + 5,
received_at: start + 5,
timestamp: start + 5,
storyId,
};
await saveMessages([message1, message2, message3, message4, message5], {
forceSave: true,
});
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
5
);
const reaction1: ReactionType = {
conversationId,
emoji: '🎉',
fromId: getUuid(),
messageId: message1.id,
messageReceivedAt: message1.received_at,
targetAuthorUuid: getUuid(),
targetTimestamp: start,
};
const reaction2: ReactionType = {
conversationId,
emoji: '🚀',
fromId: getUuid(),
messageId: message2.id,
messageReceivedAt: message2.received_at,
targetAuthorUuid: getUuid(),
targetTimestamp: start,
};
const reaction3: ReactionType = {
conversationId: getUuid(),
emoji: '☀️',
fromId: getUuid(),
messageId: message3.id,
messageReceivedAt: message3.received_at,
targetAuthorUuid: getUuid(),
targetTimestamp: start,
};
const reaction4: ReactionType = {
conversationId,
emoji: '❤️‍🔥',
fromId: getUuid(),
messageId: message4.id,
messageReceivedAt: message4.received_at,
targetAuthorUuid: getUuid(),
targetTimestamp: start,
};
const reaction5: ReactionType = {
conversationId,
emoji: '🆒',
fromId: getUuid(),
messageId: message5.id,
messageReceivedAt: message5.received_at,
targetAuthorUuid: getUuid(),
targetTimestamp: start,
};
await addReaction(reaction1);
await addReaction(reaction2);
await addReaction(reaction3);
await addReaction(reaction4);
await addReaction(reaction5);
assert.lengthOf(await _getAllReactions(), 5);
const markedRead = await getUnreadReactionsAndMarkRead({
conversationId,
newestUnreadAt: reaction4.messageReceivedAt,
storyId,
});
assert.lengthOf(markedRead, 2, 'two reactions marked read');
// Sorted in descending order
assert.strictEqual(
markedRead[0].messageId,
reaction4.messageId,
'first should be reaction4'
);
assert.strictEqual(
markedRead[1].messageId,
reaction1.messageId,
'second should be reaction1'
);
const markedRead2 = await getUnreadReactionsAndMarkRead({
conversationId,
newestUnreadAt: reaction5.messageReceivedAt,
storyId,
});
assert.lengthOf(markedRead2, 1);
assert.strictEqual(
markedRead2[0].messageId,
reaction5.messageId,
'should be reaction5'
);
});
});

View file

@ -23,7 +23,7 @@ const {
saveMessage,
} = dataInterface;
describe('sendLog', () => {
describe('sql/sendLog', () => {
beforeEach(async () => {
await removeAllSentProtos();
});

View file

@ -0,0 +1,244 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import dataInterface from '../../sql/Client';
import { UUID } from '../../types/UUID';
import type { UUIDStringType } from '../../types/UUID';
import type { MessageAttributesType } from '../../model-types.d';
const { removeAll, _getAllMessages, saveMessages, getOlderStories } =
dataInterface;
function getUuid(): UUIDStringType {
return UUID.generate().toString();
}
describe('sql/stories', () => {
beforeEach(async () => {
await removeAll();
});
describe('getOlderStories', () => {
it('returns N most recent stories overall, or in converation, or by author', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const now = Date.now();
const conversationId = getUuid();
const sourceUuid = getUuid();
const story1: MessageAttributesType = {
id: getUuid(),
body: 'story 1',
type: 'story',
conversationId,
sent_at: now - 20,
received_at: now - 20,
timestamp: now - 20,
sourceUuid: getUuid(),
};
const story2: MessageAttributesType = {
id: getUuid(),
body: 'story 2',
type: 'story',
conversationId: getUuid(),
sent_at: now - 10,
received_at: now - 10,
timestamp: now - 10,
sourceUuid,
};
const story3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'incoming',
conversationId: getUuid(),
sent_at: now,
received_at: now,
timestamp: now,
sourceUuid,
};
const story4: MessageAttributesType = {
id: getUuid(),
body: 'story 4',
type: 'story',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
sourceUuid: getUuid(),
};
const story5: MessageAttributesType = {
id: getUuid(),
body: 'story 5',
type: 'story',
conversationId: getUuid(),
sent_at: now,
received_at: now,
timestamp: now,
sourceUuid,
};
await saveMessages([story1, story2, story3, story4, story5], {
forceSave: true,
});
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
5
);
const stories = await getOlderStories({
limit: 5,
});
assert.lengthOf(stories, 4, 'expect four total stories');
// They are in DESC order
assert.strictEqual(
stories[0].id,
story5.id,
'stories first should be story5'
);
assert.strictEqual(
stories[3].id,
story1.id,
'stories last should be story1'
);
const storiesInConversation = await getOlderStories({
conversationId,
limit: 5,
});
assert.lengthOf(
storiesInConversation,
2,
'expect two stories in conversaton'
);
// They are in DESC order
assert.strictEqual(
storiesInConversation[0].id,
story4.id,
'storiesInConversation first should be story4'
);
assert.strictEqual(
storiesInConversation[1].id,
story1.id,
'storiesInConversation last should be story1'
);
const storiesByAuthor = await getOlderStories({
sourceUuid,
limit: 5,
});
assert.lengthOf(storiesByAuthor, 2, 'expect two stories by author');
// They are in DESC order
assert.strictEqual(
storiesByAuthor[0].id,
story5.id,
'storiesByAuthor first should be story5'
);
assert.strictEqual(
storiesByAuthor[1].id,
story2.id,
'storiesByAuthor last should be story2'
);
});
it('returns N stories older than provided receivedAt/sentAt', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const start = Date.now();
const conversationId = getUuid();
const story1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'incoming',
conversationId,
sent_at: start - 2,
received_at: start - 2,
timestamp: start - 2,
};
const story2: MessageAttributesType = {
id: getUuid(),
body: 'story 2',
type: 'story',
conversationId,
sent_at: start - 1,
received_at: start - 1,
timestamp: start - 1,
};
const story3: MessageAttributesType = {
id: getUuid(),
body: 'story 3',
type: 'story',
conversationId,
sent_at: start - 1,
received_at: start,
timestamp: start,
};
const story4: MessageAttributesType = {
id: getUuid(),
body: 'story 4',
type: 'story',
conversationId,
sent_at: start,
received_at: start,
timestamp: start,
};
const story5: MessageAttributesType = {
id: getUuid(),
body: 'story 5',
type: 'story',
conversationId,
sent_at: start + 1,
received_at: start + 1,
timestamp: start + 1,
};
await saveMessages([story1, story2, story3, story4, story5], {
forceSave: true,
});
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
5
);
const stories = await getOlderStories({
receivedAt: story4.received_at,
sentAt: story4.sent_at,
limit: 5,
});
assert.lengthOf(stories, 2, 'expect two stories');
// They are in DESC order
assert.strictEqual(
stories[0].id,
story3.id,
'stories first should be story3'
);
assert.strictEqual(
stories[1].id,
story2.id,
'stories last should be story2'
);
});
});
});

View file

@ -0,0 +1,126 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import dataInterface from '../../sql/Client';
import { getRandomBytes } from '../../Crypto';
import { UUID } from '../../types/UUID';
import type { UUIDStringType } from '../../types/UUID';
import type { StoryDistributionWithMembersType } from '../../sql/Interface';
const {
_deleteAllStoryDistributions,
_getAllStoryDistributionMembers,
_getAllStoryDistributions,
createNewStoryDistribution,
deleteStoryDistribution,
getAllStoryDistributionsWithMembers,
modifyStoryDistributionMembers,
} = dataInterface;
function getUuid(): UUIDStringType {
return UUID.generate().toString();
}
describe('sql/storyDistribution', () => {
beforeEach(async () => {
await _deleteAllStoryDistributions();
});
it('roundtrips with create/fetch/delete', async () => {
const list: StoryDistributionWithMembersType = {
id: getUuid(),
name: 'My Story',
avatarUrlPath: getUuid(),
avatarKey: getRandomBytes(128),
members: [getUuid(), getUuid()],
senderKeyInfo: {
createdAtDate: Date.now(),
distributionId: getUuid(),
memberDevices: [],
},
};
await createNewStoryDistribution(list);
assert.lengthOf(await _getAllStoryDistributions(), 1);
assert.lengthOf(await _getAllStoryDistributionMembers(), 2);
const allHydratedLists = await getAllStoryDistributionsWithMembers();
assert.lengthOf(allHydratedLists, 1);
assert.deepEqual(allHydratedLists[0], list);
await deleteStoryDistribution(list.id);
assert.lengthOf(await _getAllStoryDistributions(), 0);
assert.lengthOf(await _getAllStoryDistributionMembers(), 0);
assert.lengthOf(await getAllStoryDistributionsWithMembers(), 0);
});
it('adds and removes with modifyStoryDistributionMembers', async () => {
const UUID_1 = getUuid();
const UUID_2 = getUuid();
const UUID_3 = getUuid();
const UUID_4 = getUuid();
const list: StoryDistributionWithMembersType = {
id: getUuid(),
name: 'My Story',
avatarUrlPath: getUuid(),
avatarKey: getRandomBytes(128),
members: [UUID_1, UUID_2],
senderKeyInfo: {
createdAtDate: Date.now(),
distributionId: getUuid(),
memberDevices: [],
},
};
await createNewStoryDistribution(list);
assert.lengthOf(await _getAllStoryDistributions(), 1);
assert.lengthOf(await _getAllStoryDistributionMembers(), 2);
await modifyStoryDistributionMembers(list.id, {
toAdd: [UUID_3, UUID_4],
toRemove: [UUID_1],
});
assert.lengthOf(await _getAllStoryDistributions(), 1);
assert.lengthOf(await _getAllStoryDistributionMembers(), 3);
const allHydratedLists = await getAllStoryDistributionsWithMembers();
assert.lengthOf(allHydratedLists, 1);
assert.deepEqual(allHydratedLists[0], {
...list,
members: [UUID_2, UUID_3, UUID_4],
});
});
it('eliminates duplicates without complaint in createNewStoryDistribution', async () => {
const UUID_1 = getUuid();
const UUID_2 = getUuid();
const list: StoryDistributionWithMembersType = {
id: getUuid(),
name: 'My Story',
avatarUrlPath: getUuid(),
avatarKey: getRandomBytes(128),
members: [UUID_1, UUID_1, UUID_2],
senderKeyInfo: {
createdAtDate: Date.now(),
distributionId: getUuid(),
memberDevices: [],
},
};
await createNewStoryDistribution(list);
assert.lengthOf(await _getAllStoryDistributions(), 1);
assert.lengthOf(await _getAllStoryDistributionMembers(), 2);
const allHydratedLists = await getAllStoryDistributionsWithMembers();
assert.lengthOf(allHydratedLists, 1);
assert.deepEqual(allHydratedLists[0].members, [UUID_1, UUID_2]);
});
});

View file

@ -0,0 +1,134 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import dataInterface from '../../sql/Client';
import { UUID } from '../../types/UUID';
import type { UUIDStringType } from '../../types/UUID';
import type { StoryReadType } from '../../sql/Interface';
const {
_getAllStoryReads,
_deleteAllStoryReads,
addNewStoryRead,
getLastStoryReadsForAuthor,
} = dataInterface;
function getUuid(): UUIDStringType {
return UUID.generate().toString();
}
describe('sql/storyReads', () => {
beforeEach(async () => {
await _deleteAllStoryReads();
});
it('roundtrips with create/fetch/delete', async () => {
assert.lengthOf(await _getAllStoryReads(), 0);
const read: StoryReadType = {
authorId: getUuid(),
conversationId: getUuid(),
storyId: getUuid(),
storyReadDate: Date.now(),
};
await addNewStoryRead(read);
const allReads = await _getAllStoryReads();
assert.lengthOf(allReads, 1);
assert.deepEqual(allReads[0], read);
});
describe('getLastStoryReadsForAuthor', () => {
it('returns n = limit items for author', async () => {
const now = Date.now();
const authorId = getUuid();
const read1: StoryReadType = {
authorId,
conversationId: getUuid(),
storyId: getUuid(),
storyReadDate: now - 20,
};
const read2: StoryReadType = {
authorId,
conversationId: getUuid(),
storyId: getUuid(),
storyReadDate: now - 10,
};
const read3: StoryReadType = {
authorId,
conversationId: getUuid(),
storyId: getUuid(),
storyReadDate: now,
};
const read4: StoryReadType = {
authorId: getUuid(),
conversationId: getUuid(),
storyId: getUuid(),
storyReadDate: now,
};
await addNewStoryRead(read1);
await addNewStoryRead(read2);
await addNewStoryRead(read3);
await addNewStoryRead(read4);
assert.lengthOf(await _getAllStoryReads(), 4);
const lastReads = await getLastStoryReadsForAuthor({
authorId,
limit: 2,
});
assert.lengthOf(lastReads, 2);
assert.deepEqual([read3, read2], lastReads);
});
it('returns only items in provided conversation', async () => {
const now = Date.now();
const authorId = getUuid();
const conversationId = getUuid();
const read1: StoryReadType = {
authorId,
conversationId,
storyId: getUuid(),
storyReadDate: now - 20,
};
const read2: StoryReadType = {
authorId,
conversationId,
storyId: getUuid(),
storyReadDate: now - 10,
};
const read3: StoryReadType = {
authorId,
conversationId: getUuid(),
storyId: getUuid(),
storyReadDate: now,
};
const read4: StoryReadType = {
authorId,
conversationId: getUuid(),
storyId: getUuid(),
storyReadDate: now,
};
await addNewStoryRead(read1);
await addNewStoryRead(read2);
await addNewStoryRead(read3);
await addNewStoryRead(read4);
assert.lengthOf(await _getAllStoryReads(), 4);
const lastReads = await getLastStoryReadsForAuthor({
authorId,
conversationId,
limit: 1,
});
assert.lengthOf(lastReads, 1);
assert.deepEqual([read2], lastReads);
});
});
});

View file

@ -0,0 +1,754 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import dataInterface from '../../sql/Client';
import { UUID } from '../../types/UUID';
import type { UUIDStringType } from '../../types/UUID';
import type { MessageAttributesType } from '../../model-types.d';
import { ReadStatus } from '../../messages/MessageReadStatus';
const {
removeAll,
_getAllMessages,
saveMessages,
getMessageMetricsForConversation,
getNewerMessagesByConversation,
getOlderMessagesByConversation,
} = dataInterface;
function getUuid(): UUIDStringType {
return UUID.generate().toString();
}
describe('sql/timelineFetches', () => {
beforeEach(async () => {
await removeAll();
});
describe('getOlderMessagesByConversation', () => {
it('returns N most recent messages', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const now = Date.now();
const conversationId = getUuid();
const storyId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId,
sent_at: now - 20,
received_at: now - 20,
timestamp: now - 20,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: now - 10,
received_at: now - 10,
timestamp: now - 10,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'outgoing',
conversationId: getUuid(),
sent_at: now,
received_at: now,
timestamp: now,
};
const message4: MessageAttributesType = {
id: getUuid(),
body: 'message 4',
type: 'story',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
storyId,
};
const message5: MessageAttributesType = {
id: getUuid(),
body: 'message 5',
type: 'outgoing',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
storyId,
};
await saveMessages([message1, message2, message3, message4, message5], {
forceSave: true,
});
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
5
);
const messages = await getOlderMessagesByConversation(conversationId, {
MessageCollection: window.Whisper.MessageCollection,
limit: 5,
});
assert.lengthOf(messages, 2);
// They are not in DESC order because MessageCollection is sorting them
assert.strictEqual(messages.at(0).attributes.id, message1.id);
assert.strictEqual(messages.at(1).attributes.id, message2.id);
});
it('returns N most recent messages for a given story', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const now = Date.now();
const conversationId = getUuid();
const storyId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'story',
type: 'story',
conversationId,
sent_at: now - 20,
received_at: now - 20,
timestamp: now - 20,
storyId,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'story reply 1',
type: 'outgoing',
conversationId,
sent_at: now - 10,
received_at: now - 10,
timestamp: now - 10,
storyId,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'normal message',
type: 'outgoing',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const messages = await getOlderMessagesByConversation(conversationId, {
MessageCollection: window.Whisper.MessageCollection,
limit: 5,
storyId,
});
assert.lengthOf(messages, 1);
assert.strictEqual(messages.at(0).attributes.id, message2.id);
});
it('returns N messages older than provided received_at', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const target = Date.now();
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId,
sent_at: target - 10,
received_at: target - 10,
timestamp: target - 10,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: target,
received_at: target,
timestamp: target,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'outgoing',
conversationId,
sent_at: target + 10,
received_at: target + 10,
timestamp: target + 10,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const messages = await getOlderMessagesByConversation(conversationId, {
MessageCollection: window.Whisper.MessageCollection,
limit: 5,
receivedAt: target,
sentAt: target,
});
assert.lengthOf(messages, 1);
assert.strictEqual(messages.at(0).attributes.id, message1.id);
});
it('returns N older messages with received_at, lesser sent_at', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const target = Date.now();
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId,
sent_at: target - 20,
received_at: target,
timestamp: target,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: target - 10,
received_at: target,
timestamp: target,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'outgoing',
conversationId,
sent_at: target,
received_at: target,
timestamp: target,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const messages = await getOlderMessagesByConversation(conversationId, {
MessageCollection: window.Whisper.MessageCollection,
limit: 5,
receivedAt: target,
sentAt: target,
});
assert.lengthOf(messages, 2);
// They are not in DESC order because MessageCollection is sorting them
assert.strictEqual(messages.at(0).attributes.id, message1.id);
assert.strictEqual(messages.at(1).attributes.id, message2.id);
});
it('returns N older messages, same received_at/sent_at but excludes messageId', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const target = Date.now();
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId,
sent_at: target - 10,
received_at: target,
timestamp: target,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: target - 10,
received_at: target,
timestamp: target,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'outgoing',
conversationId,
sent_at: target,
received_at: target,
timestamp: target,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const messages = await getOlderMessagesByConversation(conversationId, {
MessageCollection: window.Whisper.MessageCollection,
limit: 5,
receivedAt: target,
sentAt: target,
messageId: message2.id,
});
assert.lengthOf(messages, 1);
assert.strictEqual(messages.at(0).attributes.id, message1.id);
});
});
describe('getNewerMessagesByConversation', () => {
it('returns N oldest messages with no parameters', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const now = Date.now();
const conversationId = getUuid();
const storyId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId: getUuid(),
sent_at: now,
received_at: now,
timestamp: now,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'story',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
storyId,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'outgoing',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
storyId,
};
const message4: MessageAttributesType = {
id: getUuid(),
body: 'message 4',
type: 'outgoing',
conversationId,
sent_at: now + 10,
received_at: now + 10,
timestamp: now + 10,
};
const message5: MessageAttributesType = {
id: getUuid(),
body: 'message 5',
type: 'outgoing',
conversationId,
sent_at: now + 20,
received_at: now + 20,
timestamp: now + 20,
};
await saveMessages([message1, message2, message3, message4, message5], {
forceSave: true,
});
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
5
);
const messages = await getNewerMessagesByConversation(conversationId, {
MessageCollection: window.Whisper.MessageCollection,
limit: 5,
});
assert.lengthOf(messages, 2);
assert.strictEqual(messages.at(0).attributes.id, message4.id);
assert.strictEqual(messages.at(1).attributes.id, message5.id);
});
it('returns N oldest messages for a given story with no parameters', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const now = Date.now();
const conversationId = getUuid();
const storyId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'story',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
storyId,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: now + 10,
received_at: now + 10,
timestamp: now + 10,
storyId,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'outgoing',
conversationId,
sent_at: now + 20,
received_at: now + 20,
timestamp: now + 20,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const messages = await getNewerMessagesByConversation(conversationId, {
MessageCollection: window.Whisper.MessageCollection,
limit: 5,
storyId,
});
assert.lengthOf(messages, 1);
assert.strictEqual(messages.at(0).attributes.id, message2.id);
});
it('returns N messages newer than provided received_at', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const target = Date.now();
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId,
sent_at: target - 10,
received_at: target - 10,
timestamp: target - 10,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: target,
received_at: target,
timestamp: target,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'outgoing',
conversationId,
sent_at: target + 10,
received_at: target + 10,
timestamp: target + 10,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const messages = await getNewerMessagesByConversation(conversationId, {
MessageCollection: window.Whisper.MessageCollection,
limit: 5,
receivedAt: target,
sentAt: target,
});
assert.lengthOf(messages, 1);
assert.strictEqual(messages.at(0).attributes.id, message3.id);
});
it('returns N newer messages with same received_at, greater sent_at', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const target = Date.now();
const conversationId = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId,
sent_at: target,
received_at: target,
timestamp: target,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: target + 10,
received_at: target,
timestamp: target,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'outgoing',
conversationId,
sent_at: target + 20,
received_at: target,
timestamp: target,
};
await saveMessages([message1, message2, message3], { forceSave: true });
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
3
);
const messages = await getNewerMessagesByConversation(conversationId, {
MessageCollection: window.Whisper.MessageCollection,
limit: 5,
receivedAt: target,
sentAt: target,
});
assert.lengthOf(messages, 2);
// They are not in DESC order because MessageCollection is sorting them
assert.strictEqual(messages.at(0).attributes.id, message2.id);
assert.strictEqual(messages.at(1).attributes.id, message3.id);
});
});
describe('getMessageMetricsForConversation', () => {
it('returns metrics properly for story and non-story timelines', async () => {
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
0
);
const target = Date.now();
const conversationId = getUuid();
const storyId = getUuid();
const story: MessageAttributesType = {
id: getUuid(),
body: 'story',
type: 'story',
conversationId,
sent_at: target - 10,
received_at: target - 10,
timestamp: target - 10,
};
const oldestInStory: MessageAttributesType = {
id: getUuid(),
body: 'oldestInStory',
type: 'outgoing',
conversationId,
sent_at: target - 9,
received_at: target - 9,
timestamp: target - 9,
storyId,
};
const oldest: MessageAttributesType = {
id: getUuid(),
body: 'oldest',
type: 'outgoing',
conversationId,
sent_at: target - 8,
received_at: target - 8,
timestamp: target - 8,
};
const oldestUnread: MessageAttributesType = {
id: getUuid(),
body: 'oldestUnread',
type: 'incoming',
conversationId,
sent_at: target - 7,
received_at: target - 7,
timestamp: target - 7,
readStatus: ReadStatus.Unread,
};
const oldestStoryUnread: MessageAttributesType = {
id: getUuid(),
body: 'oldestStoryUnread',
type: 'incoming',
conversationId,
sent_at: target - 6,
received_at: target - 6,
timestamp: target - 6,
readStatus: ReadStatus.Unread,
storyId,
};
const anotherUnread: MessageAttributesType = {
id: getUuid(),
body: 'anotherUnread',
type: 'incoming',
conversationId,
sent_at: target - 5,
received_at: target - 5,
timestamp: target - 5,
readStatus: ReadStatus.Unread,
};
const newestInStory: MessageAttributesType = {
id: getUuid(),
body: 'newestStory',
type: 'outgoing',
conversationId,
sent_at: target - 4,
received_at: target - 4,
timestamp: target - 4,
storyId,
};
const newest: MessageAttributesType = {
id: getUuid(),
body: 'newest',
type: 'outgoing',
conversationId,
sent_at: target - 3,
received_at: target - 3,
timestamp: target - 3,
};
await saveMessages(
[
story,
oldestInStory,
oldest,
oldestUnread,
oldestStoryUnread,
anotherUnread,
newestInStory,
newest,
],
{ forceSave: true }
);
assert.lengthOf(
await _getAllMessages({
MessageCollection: window.Whisper.MessageCollection,
}),
8
);
const metricsInTimeline = await getMessageMetricsForConversation(
conversationId
);
assert.strictEqual(metricsInTimeline?.oldest?.id, oldest.id, 'oldest');
assert.strictEqual(metricsInTimeline?.newest?.id, newest.id, 'newest');
assert.strictEqual(
metricsInTimeline?.oldestUnread?.id,
oldestUnread.id,
'oldestUnread'
);
assert.strictEqual(metricsInTimeline?.totalUnread, 2, 'totalUnread');
const metricsInStory = await getMessageMetricsForConversation(
conversationId,
storyId
);
assert.strictEqual(
metricsInStory?.oldest?.id,
oldestInStory.id,
'oldestInStory'
);
assert.strictEqual(
metricsInStory?.newest?.id,
newestInStory.id,
'newestInStory'
);
assert.strictEqual(
metricsInStory?.oldestUnread?.id,
oldestStoryUnread.id,
'oldestStoryUnread'
);
assert.strictEqual(metricsInStory?.totalUnread, 1, 'totalUnread');
});
});
});