diff --git a/packages/askar/tests/askar-sqlite.e2e.test.ts b/packages/askar/tests/askar-sqlite.e2e.test.ts index 3de47f3183..41280f0684 100644 --- a/packages/askar/tests/askar-sqlite.e2e.test.ts +++ b/packages/askar/tests/askar-sqlite.e2e.test.ts @@ -137,9 +137,14 @@ describeRunInNodeVersion([18], 'Askar SQLite agents', () => { await bobAgent.wallet.initialize({ id: backupWalletName, key: backupWalletName }) // Expect same basic message record to exist in new wallet - expect(await bobBasicMessageRepository.getById(bobAgent.context, basicMessageRecord.id)).toMatchObject( - basicMessageRecord - ) + expect(await bobBasicMessageRepository.getById(bobAgent.context, basicMessageRecord.id)).toMatchObject({ + id: basicMessageRecord.id, + connectionId: basicMessageRecord.connectionId, + content: basicMessageRecord.content, + createdAt: basicMessageRecord.createdAt, + updatedAt: basicMessageRecord.updatedAt, + type: basicMessageRecord.type, + }) }) test('changing wallet key', async () => { diff --git a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts index ff788e00fe..82e94cf9e4 100644 --- a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts +++ b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts @@ -41,13 +41,14 @@ export class BasicMessagesApi { * @throws {MessageSendingError} If message is undeliverable * @returns the created record */ - public async sendMessage(connectionId: string, message: string) { + public async sendMessage(connectionId: string, message: string, parentThreadId?: string) { const connection = await this.connectionService.getById(this.agentContext, connectionId) const { message: basicMessage, record: basicMessageRecord } = await this.basicMessageService.createMessage( this.agentContext, message, - connection + connection, + parentThreadId ) const outboundMessageContext = new OutboundMessageContext(basicMessage, { agentContext: this.agentContext, @@ -81,6 +82,18 @@ export class BasicMessagesApi { return this.basicMessageService.getById(this.agentContext, basicMessageRecordId) } + /** + * Retrieve a basic message record by thread id + * + * @param threadId The thread id + * @throws {RecordNotFoundError} If no record is found + * @throws {RecordDuplicateError} If multiple records are found + * @returns The connection record + */ + public async getByThreadId(basicMessageRecordId: string) { + return this.basicMessageService.getByThreadId(this.agentContext, basicMessageRecordId) + } + /** * Delete a basic message record by id * diff --git a/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts index 1ed5be6fb7..77f746030c 100644 --- a/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts +++ b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts @@ -84,6 +84,55 @@ describe('Basic Messages E2E', () => { }) }) + test('Alice and Faber exchange messages using threadId', async () => { + testLogger.test('Alice sends message to Faber') + const helloRecord = await aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello') + + expect(helloRecord.content).toBe('Hello') + + testLogger.test('Faber waits for message from Alice') + const helloMessage = await waitForBasicMessage(faberAgent, { + content: 'Hello', + }) + + testLogger.test('Faber sends message to Alice') + const replyRecord = await faberAgent.basicMessages.sendMessage(faberConnection.id, 'How are you?', helloMessage.id) + expect(replyRecord.content).toBe('How are you?') + expect(replyRecord.parentThreadId).toBe(helloMessage.id) + + testLogger.test('Alice waits until she receives message from faber') + const replyMessage = await waitForBasicMessage(aliceAgent, { + content: 'How are you?', + }) + expect(replyMessage.content).toBe('How are you?') + expect(replyMessage.thread?.parentThreadId).toBe(helloMessage.id) + + // Both sender and recipient shall be able to find the threaded messages + // Hello message + const aliceHelloMessage = await aliceAgent.basicMessages.getByThreadId(helloMessage.id) + const faberHelloMessage = await faberAgent.basicMessages.getByThreadId(helloMessage.id) + expect(aliceHelloMessage).toMatchObject({ + content: helloRecord.content, + threadId: helloRecord.threadId, + }) + expect(faberHelloMessage).toMatchObject({ + content: helloRecord.content, + threadId: helloRecord.threadId, + }) + + // Reply message + const aliceReplyMessages = await aliceAgent.basicMessages.findAllByQuery({ parentThreadId: helloMessage.id }) + const faberReplyMessages = await faberAgent.basicMessages.findAllByQuery({ parentThreadId: helloMessage.id }) + expect(aliceReplyMessages.length).toBe(1) + expect(aliceReplyMessages[0]).toMatchObject({ + content: replyRecord.content, + parentThreadId: replyRecord.parentThreadId, + threadId: replyRecord.threadId, + }) + expect(faberReplyMessages.length).toBe(1) + expect(faberReplyMessages[0]).toMatchObject(replyRecord) + }) + test('Alice is unable to send a message', async () => { testLogger.test('Alice sends message to Faber that is undeliverable') diff --git a/packages/core/src/modules/basic-messages/repository/BasicMessageRecord.ts b/packages/core/src/modules/basic-messages/repository/BasicMessageRecord.ts index 4a0de5decd..42199106c6 100644 --- a/packages/core/src/modules/basic-messages/repository/BasicMessageRecord.ts +++ b/packages/core/src/modules/basic-messages/repository/BasicMessageRecord.ts @@ -8,6 +8,8 @@ export type CustomBasicMessageTags = TagsBase export type DefaultBasicMessageTags = { connectionId: string role: BasicMessageRole + threadId?: string + parentThreadId?: string } export type BasicMessageTags = RecordTags @@ -18,7 +20,8 @@ export interface BasicMessageStorageProps { connectionId: string role: BasicMessageRole tags?: CustomBasicMessageTags - + threadId?: string + parentThreadId?: string content: string sentTime: string } @@ -28,6 +31,8 @@ export class BasicMessageRecord extends BaseRecord { await bobAgent.wallet.initialize({ id: backupWalletName, key: backupWalletName }) // Expect same basic message record to exist in new wallet - expect(await bobBasicMessageRepository.getById(bobAgent.context, basicMessageRecord.id)).toMatchObject( - basicMessageRecord - ) + expect(await bobBasicMessageRepository.getById(bobAgent.context, basicMessageRecord.id)).toMatchObject({ + id: basicMessageRecord.id, + connectionId: basicMessageRecord.connectionId, + content: basicMessageRecord.content, + createdAt: basicMessageRecord.createdAt, + updatedAt: basicMessageRecord.updatedAt, + type: basicMessageRecord.type, + }) }) test('changing wallet key', async () => {