// Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; import type { Database } from '@signalapp/better-sqlite3'; import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; import { getMostRecentAddressableNondisappearingMessagesSync } from '../../sql/Server'; import { insertData, updateToVersion } from './helpers'; import type { MessageAttributesType } from '../../model-types'; import { DurationInSeconds } from '../../util/durations/duration-in-seconds'; /* eslint-disable camelcase */ function generateMessage(json: MessageAttributesType) { const { conversationId, expireTimer, received_at, sent_at, type } = json; return { conversationId, json, received_at, sent_at, expireTimer: Number(expireTimer), type, }; } describe('SQL/updateToSchemaVersion1080', () => { let db: Database; beforeEach(() => { db = new SQL(':memory:'); updateToVersion(db, 1080); }); afterEach(() => { db.close(); }); describe('Addressable Messages', () => { it('returns only incoming/outgoing messages', () => { const conversationId = generateGuid(); const otherConversationId = generateGuid(); insertData(db, 'messages', [ generateMessage({ id: '1', conversationId, type: 'incoming', received_at: 1, sent_at: 1, timestamp: 1, }), generateMessage({ id: '2', conversationId, type: 'story', received_at: 2, sent_at: 2, timestamp: 2, }), generateMessage({ id: '3', conversationId, type: 'outgoing', received_at: 3, sent_at: 3, timestamp: 3, }), generateMessage({ id: '4', conversationId, type: 'group-v1-migration', received_at: 4, sent_at: 4, timestamp: 4, }), generateMessage({ id: '5', conversationId, type: 'group-v2-change', received_at: 5, sent_at: 5, timestamp: 5, }), generateMessage({ id: '6', conversationId, type: 'incoming', received_at: 6, sent_at: 6, timestamp: 6, expireTimer: DurationInSeconds.fromMinutes(10), }), generateMessage({ id: '7', conversationId, type: 'profile-change', received_at: 7, sent_at: 7, timestamp: 7, }), generateMessage({ id: '8', conversationId: otherConversationId, type: 'incoming', received_at: 8, sent_at: 8, timestamp: 8, }), ]); const messages = getMostRecentAddressableNondisappearingMessagesSync( db, conversationId ); assert.lengthOf(messages, 2); assert.deepEqual(messages, [ { id: '3', conversationId, type: 'outgoing', received_at: 3, sent_at: 3, timestamp: 3, }, { id: '1', conversationId, type: 'incoming', received_at: 1, sent_at: 1, timestamp: 1, }, ]); }); it('ensures that index is used for getMostRecentAddressableNondisappearingMessagesSync, with storyId', () => { const { detail } = db .prepare( ` EXPLAIN QUERY PLAN SELECT json FROM messages INDEXED BY messages_by_date_addressable_nondisappearing WHERE expireTimer IS NULL AND conversationId IS 'not-important' AND isAddressableMessage = 1 ORDER BY received_at DESC, sent_at DESC LIMIT 5; ` ) .get(); assert.notInclude(detail, 'B-TREE'); assert.notInclude(detail, 'SCAN'); assert.include( detail, 'SEARCH messages USING INDEX messages_by_date_addressable_nondisappearing (conversationId=? AND isAddressableMessage=?)' ); }); }); });