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)); diff --git a/packages/transport/test/modules/messages/MessageController.test.ts b/packages/transport/test/modules/messages/MessageController.test.ts index 3889da708..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"; @@ -57,7 +58,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 +86,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 +261,50 @@ describe("MessageController", function () { }); }); + describe("Sending and decrypting Messages for pending Relationships", function () { + let recipientWithPendingRelationship: AccountController; + let pendingRelationship: Relationship; + + beforeEach(async function () { + recipientWithPendingRelationship = (await TestUtil.provideAccounts(transport, connection, 1))[0]; + pendingRelationship = await TestUtil.addPendingRelationship(sender, recipientWithPendingRelationship); + }); + + afterEach(async () => await recipientWithPendingRelationship.close()); + + 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 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); + + const receivedMessages = await TestUtil.syncUntilHasMessages(recipientWithPendingRelationship); + 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 () { 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