From 65aa8b2ace227dac0a757cabbe95d61bb09933de Mon Sep 17 00:00:00 2001 From: Milena Czierlinski Date: Tue, 12 Aug 2025 10:11:12 +0200 Subject: [PATCH 1/4] feat: validate message recipients allows pending Relationships --- .../src/modules/messages/MessageController.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/transport/src/modules/messages/MessageController.ts b/packages/transport/src/modules/messages/MessageController.ts index d5112cdc7..828abe5e1 100644 --- a/packages/transport/src/modules/messages/MessageController.ts +++ b/packages/transport/src/modules/messages/MessageController.ts @@ -387,14 +387,15 @@ export class MessageController extends TransportController { } public async validateMessageRecipients(recipients: CoreAddress[]): Promise> { - const peersWithNeitherActiveNorTerminatedRelationship: string[] = []; + const peersWithWrongRelationshipStatus: string[] = []; const deletedPeers: string[] = []; for (const recipient of recipients) { const relationship = await this.relationships.getRelationshipToIdentity(recipient); - if (!relationship || !(relationship.status === RelationshipStatus.Terminated || relationship.status === RelationshipStatus.Active)) { - peersWithNeitherActiveNorTerminatedRelationship.push(recipient.address); + const allowedRelationshipStatuses = [RelationshipStatus.Pending, RelationshipStatus.Active, RelationshipStatus.Terminated]; + if (!relationship || !allowedRelationshipStatuses.includes(relationship.status)) { + peersWithWrongRelationshipStatus.push(recipient.address); continue; } @@ -403,8 +404,9 @@ export class MessageController extends TransportController { } } - if (peersWithNeitherActiveNorTerminatedRelationship.length > 0) { - return Result.fail(TransportCoreErrors.messages.hasNeitherActiveNorTerminatedRelationship(peersWithNeitherActiveNorTerminatedRelationship)); + if (peersWithWrongRelationshipStatus.length > 0) { + // TODO: rename error + return Result.fail(TransportCoreErrors.messages.hasNeitherActiveNorTerminatedRelationship(peersWithWrongRelationshipStatus)); } if (deletedPeers.length > 0) return Result.fail(TransportCoreErrors.messages.peerIsDeleted(deletedPeers)); From 569f299bebb342b65a52d1b81e0b76013956c26f Mon Sep 17 00:00:00 2001 From: Milena Czierlinski Date: Tue, 12 Aug 2025 10:14:44 +0200 Subject: [PATCH 2/4] test: sending messages for pending Relationships --- .../messages/MessageController.test.ts | 43 +++++++++++++++---- .../transport/test/testHelpers/TestUtil.ts | 13 +++++- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/packages/transport/test/modules/messages/MessageController.test.ts b/packages/transport/test/modules/messages/MessageController.test.ts index 3889da708..e15d7f15b 100644 --- a/packages/transport/test/modules/messages/MessageController.test.ts +++ b/packages/transport/test/modules/messages/MessageController.test.ts @@ -57,7 +57,7 @@ describe("MessageController", function () { await connection.close(); }); - describe("Sending Messages requires existence of active or terminated Relationship", function () { + describe("Sending Messages requires existence of pending, active or terminated Relationship", function () { beforeEach(async () => { const relationshipBetweenSenderAndRecipient3 = await sender.relationships.getRelationshipToIdentity(recipient3.identity.address); @@ -85,13 +85,6 @@ describe("MessageController", function () { await expect(TestUtil.sendMessage(sender, recipient3)).rejects.toThrow("error.transport.messages.hasNeitherActiveNorTerminatedRelationship"); }); - test("cannot send Message for pending Relationship", async function () { - const pendingRelationship = await TestUtil.addPendingRelationship(sender, recipient3); - expect(pendingRelationship.status).toBe("Pending"); - - await expect(TestUtil.sendMessage(sender, recipient3)).rejects.toThrow("error.transport.messages.hasNeitherActiveNorTerminatedRelationship"); - }); - test("cannot send Message for Relationship whose deletion is proposed", async function () { await TestUtil.addRelationship(sender, recipient3); await TestUtil.terminateRelationship(recipient3, sender); @@ -267,6 +260,40 @@ describe("MessageController", function () { }); }); + // TODO: even though the tests pass, they log errors: error.transport.secrets.wrongSecretType: 'The given secret type is not supported!' + describe("Sending and decrypting Messages for pending Relationships", function () { + let recipientWithPendingRelationship: AccountController; + let pendingRelationship: Relationship; + + beforeAll(async function () { + recipientWithPendingRelationship = (await TestUtil.provideAccounts(transport, connection, 1))[0]; + pendingRelationship = await TestUtil.addPendingRelationship(sender, recipientWithPendingRelationship); + }); + + afterAll(async () => await recipientWithPendingRelationship.close()); + + test("should be able to send a Message on a pending Relationship", async function () { + await expect(TestUtil.sendMessage(sender, recipientWithPendingRelationship)).resolves.not.toThrow(); + }); + + // TODO: do we need such a test? + // test("should decrypt a Message on a terminated Relationship", async function () { + // const messageId = messageExchangedBeforeTermination.id.toString(); + // await expect(sender.messages.updateBackboneData([messageId])).resolves.not.toThrow(); + // await expect(recipientWithPendingRelationship.messages.updateBackboneData([messageId])).resolves.not.toThrow(); + // }); + + test("should be able to receive a Message sent on a pending Relationship after the Relationship was activated", async function () { + const idOfSentMessageDuringPendingRelationship = (await TestUtil.sendMessage(sender, recipientWithPendingRelationship)).id; + + await TestUtil.acceptPendingRelationship(sender, recipientWithPendingRelationship, pendingRelationship); + + const receivedMessages = await TestUtil.syncUntilHasMessages(recipientWithPendingRelationship); + const idOfReceivedMessageAfterActivation = receivedMessages[receivedMessages.length - 1].id; + expect(idOfReceivedMessageAfterActivation).toStrictEqual(idOfSentMessageDuringPendingRelationship); + }); + }); + describe("Sending and decrypting Messages for terminated Relationships", function () { let messageExchangedBeforeTermination: Message; diff --git a/packages/transport/test/testHelpers/TestUtil.ts b/packages/transport/test/testHelpers/TestUtil.ts index 6d9e02793..274ea2bb8 100644 --- a/packages/transport/test/testHelpers/TestUtil.ts +++ b/packages/transport/test/testHelpers/TestUtil.ts @@ -9,6 +9,7 @@ import { ISerializable, Serializable } from "@js-soft/ts-serval"; import { EventEmitter2EventBus, sleep } from "@js-soft/ts-utils"; import { CoreAddress, CoreDate, CoreId } from "@nmshd/core-types"; import { CoreBuffer } from "@nmshd/crypto"; +import { assert } from "console"; import fs from "fs"; import { DurationLike } from "luxon"; import path from "path"; @@ -394,7 +395,17 @@ export class TestUtil { ): Promise<{ acceptedRelationshipFromSelf: Relationship; acceptedRelationshipPeer: Relationship }> { const pendingRelationshipFromSelf = await TestUtil.addPendingRelationship(from, to, template); - const acceptedRelationshipFromSelf = await from.relationships.accept(pendingRelationshipFromSelf.id); + return await this.acceptPendingRelationship(from, to, pendingRelationshipFromSelf); + } + + public static async acceptPendingRelationship( + from: AccountController, + to: AccountController, + pendingRelationship: Relationship + ): Promise<{ acceptedRelationshipFromSelf: Relationship; acceptedRelationshipPeer: Relationship }> { + assert(pendingRelationship.status === RelationshipStatus.Pending); + + const acceptedRelationshipFromSelf = await from.relationships.accept(pendingRelationship.id); expect(acceptedRelationshipFromSelf.status).toStrictEqual(RelationshipStatus.Active); // Get accepted relationship From 807de0b6289f8e6b3265ff959dcdcec476c43147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= Date: Tue, 12 Aug 2025 10:30:29 +0200 Subject: [PATCH 3/4] chore: remove test --- .../test/modules/messages/MessageController.test.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/transport/test/modules/messages/MessageController.test.ts b/packages/transport/test/modules/messages/MessageController.test.ts index e15d7f15b..26a934d90 100644 --- a/packages/transport/test/modules/messages/MessageController.test.ts +++ b/packages/transport/test/modules/messages/MessageController.test.ts @@ -276,13 +276,6 @@ describe("MessageController", function () { await expect(TestUtil.sendMessage(sender, recipientWithPendingRelationship)).resolves.not.toThrow(); }); - // TODO: do we need such a test? - // test("should decrypt a Message on a terminated Relationship", async function () { - // const messageId = messageExchangedBeforeTermination.id.toString(); - // await expect(sender.messages.updateBackboneData([messageId])).resolves.not.toThrow(); - // await expect(recipientWithPendingRelationship.messages.updateBackboneData([messageId])).resolves.not.toThrow(); - // }); - test("should be able to receive a Message sent on a pending Relationship after the Relationship was activated", async function () { const idOfSentMessageDuringPendingRelationship = (await TestUtil.sendMessage(sender, recipientWithPendingRelationship)).id; From c5e9064b737375e4b75b29946f07808fbd26b7de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= Date: Tue, 12 Aug 2025 11:21:09 +0200 Subject: [PATCH 4/4] chore: split tests --- .../messages/MessageController.test.ts | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/transport/test/modules/messages/MessageController.test.ts b/packages/transport/test/modules/messages/MessageController.test.ts index 26a934d90..ee5b5eb13 100644 --- a/packages/transport/test/modules/messages/MessageController.test.ts +++ b/packages/transport/test/modules/messages/MessageController.test.ts @@ -1,4 +1,5 @@ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; +import { Serializable } from "@js-soft/ts-serval"; import { CoreDate, CoreId } from "@nmshd/core-types"; import { AccountController, IdentityDeletionProcess, IdentityDeletionProcessStatus, Message, Relationship, RelationshipStatus, Transport } from "../../../src"; import { TestUtil } from "../../testHelpers/TestUtil"; @@ -260,23 +261,30 @@ describe("MessageController", function () { }); }); - // TODO: even though the tests pass, they log errors: error.transport.secrets.wrongSecretType: 'The given secret type is not supported!' describe("Sending and decrypting Messages for pending Relationships", function () { let recipientWithPendingRelationship: AccountController; let pendingRelationship: Relationship; - beforeAll(async function () { + beforeEach(async function () { recipientWithPendingRelationship = (await TestUtil.provideAccounts(transport, connection, 1))[0]; pendingRelationship = await TestUtil.addPendingRelationship(sender, recipientWithPendingRelationship); }); - afterAll(async () => await recipientWithPendingRelationship.close()); + afterEach(async () => await recipientWithPendingRelationship.close()); - test("should be able to send a Message on a pending Relationship", async function () { - await expect(TestUtil.sendMessage(sender, recipientWithPendingRelationship)).resolves.not.toThrow(); + test("should be able to send a Message on a pending Relationship as the templator", async function () { + await expect( + sender.messages.sendMessage({ recipients: [recipientWithPendingRelationship.identity.address], content: Serializable.fromAny({ content: "TestContent" }) }) + ).resolves.not.toThrow(); }); - test("should be able to receive a Message sent on a pending Relationship after the Relationship was activated", async function () { + test("should be able to send a Message on a pending Relationship as the requestor", async function () { + await expect( + recipientWithPendingRelationship.messages.sendMessage({ recipients: [sender.identity.address], content: Serializable.fromAny({ content: "TestContent" }) }) + ).resolves.not.toThrow(); + }); + + test("should be able to receive a Message sent on a pending Relationship as a tempator after the Relationship was activated", async function () { const idOfSentMessageDuringPendingRelationship = (await TestUtil.sendMessage(sender, recipientWithPendingRelationship)).id; await TestUtil.acceptPendingRelationship(sender, recipientWithPendingRelationship, pendingRelationship); @@ -285,6 +293,16 @@ describe("MessageController", function () { const idOfReceivedMessageAfterActivation = receivedMessages[receivedMessages.length - 1].id; expect(idOfReceivedMessageAfterActivation).toStrictEqual(idOfSentMessageDuringPendingRelationship); }); + + test("should be able to receive a Message sent on a pending Relationship as a requestor after the Relationship was activated", async function () { + const idOfSentMessageDuringPendingRelationship2 = (await TestUtil.sendMessage(recipientWithPendingRelationship, sender)).id; + + await TestUtil.acceptPendingRelationship(sender, recipientWithPendingRelationship, pendingRelationship); + + const receivedMessages2 = await TestUtil.syncUntilHasMessages(sender); + const idOfReceivedMessageAfterActivation2 = receivedMessages2[receivedMessages2.length - 1].id; + expect(idOfReceivedMessageAfterActivation2).toStrictEqual(idOfSentMessageDuringPendingRelationship2); + }); }); describe("Sending and decrypting Messages for terminated Relationships", function () {