Migration and data access functions for stories
This commit is contained in:
parent
9f4a01c535
commit
fdc9885baa
18 changed files with 3428 additions and 202 deletions
266
ts/test-electron/sql/allMedia_test.ts
Normal file
266
ts/test-electron/sql/allMedia_test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
210
ts/test-electron/sql/fullTextSearch_test.ts
Normal file
210
ts/test-electron/sql/fullTextSearch_test.ts
Normal 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);
|
||||
});
|
||||
});
|
738
ts/test-electron/sql/markRead_test.ts
Normal file
738
ts/test-electron/sql/markRead_test.ts
Normal 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'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -23,7 +23,7 @@ const {
|
|||
saveMessage,
|
||||
} = dataInterface;
|
||||
|
||||
describe('sendLog', () => {
|
||||
describe('sql/sendLog', () => {
|
||||
beforeEach(async () => {
|
||||
await removeAllSentProtos();
|
||||
});
|
||||
|
|
244
ts/test-electron/sql/stories_test.ts
Normal file
244
ts/test-electron/sql/stories_test.ts
Normal 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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
126
ts/test-electron/sql/storyDistribution_test.ts
Normal file
126
ts/test-electron/sql/storyDistribution_test.ts
Normal 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]);
|
||||
});
|
||||
});
|
134
ts/test-electron/sql/storyReads_test.ts
Normal file
134
ts/test-electron/sql/storyReads_test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
754
ts/test-electron/sql/timelineFetches_test.ts
Normal file
754
ts/test-electron/sql/timelineFetches_test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue