From b3a47c1f19afd0596d2f8881b07c8feae02ffd8d Mon Sep 17 00:00:00 2001 From: Sebastian Mahr Date: Mon, 10 Feb 2025 11:58:04 +0100 Subject: [PATCH] Deleting a SharedAttribute does not validate if it's possible to send the Notification (#398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: revoke relationship in status pending when the neded attribute is deleted * chore: remove focused test * feat: add error message when deleting an attribute while a relationship is still in Pending state * revert: unwanted change * chore: pr comments * chore: pr comments * Update packages/runtime/test/consumption/attributes.test.ts Co-authored-by: Britta Stallknecht <146106656+britsta@users.noreply.github.com> * Update packages/runtime/test/lib/testUtils.ts Co-authored-by: Milena Czierlinski <146972016+Milena-Czierlinski@users.noreply.github.com> * Update packages/runtime/test/consumption/attributes.test.ts Co-authored-by: Milena Czierlinski <146972016+Milena-Czierlinski@users.noreply.github.com> * chore: fix renaming * chore: fix renaming * chore: fix sending messsage when relationsip is not activatable anymore * chore: improve return type and variable naming * chore: pr comments * Update packages/transport/src/modules/messages/MessageController.ts Co-authored-by: Julian König <33655937+jkoenig134@users.noreply.github.com> * chore: make notificationId optional * chore: make notificationId optional * chore: fix tests * chore: beauty changes * chore: beauty changes * chore: beauty changes --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Britta Stallknecht <146106656+britsta@users.noreply.github.com> Co-authored-by: Milena Czierlinski <146972016+Milena-Czierlinski@users.noreply.github.com> Co-authored-by: Julian König <33655937+jkoenig134@users.noreply.github.com> --- .../src/useCases/common/RuntimeErrors.ts | 7 + .../DeleteOwnSharedAttributeAndNotifyPeer.ts | 15 +- ...DeletePeerSharedAttributeAndNotifyOwner.ts | 16 +- ...PartyRelationshipAttributeAndNotifyPeer.ts | 18 +- .../test/consumption/attributes.test.ts | 188 +++++++++++++++++- packages/runtime/test/lib/testUtils.ts | 41 +++- .../runtime/test/transport/messages.test.ts | 4 +- .../src/modules/messages/MessageController.ts | 14 +- 8 files changed, 277 insertions(+), 26 deletions(-) diff --git a/packages/runtime/src/useCases/common/RuntimeErrors.ts b/packages/runtime/src/useCases/common/RuntimeErrors.ts index 0e893a360..d90f1155a 100644 --- a/packages/runtime/src/useCases/common/RuntimeErrors.ts +++ b/packages/runtime/src/useCases/common/RuntimeErrors.ts @@ -267,6 +267,13 @@ class Attributes { public setDefaultRepositoryAttributesIsDisabled(): ApplicationError { return new ApplicationError("error.runtime.attributes.setDefaultRepositoryAttributesIsDisabled", "Setting default RepositoryAttributes is disabled for this Account."); } + + public cannotDeleteSharedAttributeWhileRelationshipIsPending(): ApplicationError { + return new ApplicationError( + "error.runtime.attributes.cannotDeleteSharedAttributeWhileRelationshipIsPending", + "The shared Attribute cannot be deleted while the Relationship to the peer is in status 'Pending'." + ); + } } class IdentityDeletionProcess { diff --git a/packages/runtime/src/useCases/consumption/attributes/DeleteOwnSharedAttributeAndNotifyPeer.ts b/packages/runtime/src/useCases/consumption/attributes/DeleteOwnSharedAttributeAndNotifyPeer.ts index 0454747b3..3f2b75471 100644 --- a/packages/runtime/src/useCases/consumption/attributes/DeleteOwnSharedAttributeAndNotifyPeer.ts +++ b/packages/runtime/src/useCases/consumption/attributes/DeleteOwnSharedAttributeAndNotifyPeer.ts @@ -2,7 +2,7 @@ import { Result } from "@js-soft/ts-utils"; import { AttributesController, ConsumptionIds, LocalAttribute } from "@nmshd/consumption"; import { Notification, OwnSharedAttributeDeletedByOwnerNotificationItem } from "@nmshd/content"; import { CoreId } from "@nmshd/core-types"; -import { AccountController, MessageController } from "@nmshd/transport"; +import { AccountController, MessageController, RelationshipsController, RelationshipStatus } from "@nmshd/transport"; import { Inject } from "@nmshd/typescript-ioc"; import { AttributeIdString, NotificationIdString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common"; @@ -11,7 +11,7 @@ export interface DeleteOwnSharedAttributeAndNotifyPeerRequest { } export interface DeleteOwnSharedAttributeAndNotifyPeerResponse { - notificationId: NotificationIdString; + notificationId?: NotificationIdString; } class Validator extends SchemaValidator { @@ -25,6 +25,7 @@ export class DeleteOwnSharedAttributeAndNotifyPeerUseCase extends UseCase { @@ -25,6 +26,7 @@ export class DeletePeerSharedAttributeAndNotifyOwnerUseCase extends UseCase { @@ -28,6 +28,7 @@ export class DeleteThirdPartyRelationshipAttributeAndNotifyPeerUseCase extends U @Inject private readonly attributesController: AttributesController, @Inject private readonly accountController: AccountController, @Inject private readonly messageController: MessageController, + @Inject private readonly relationshipsController: RelationshipsController, @Inject validator: Validator ) { super(validator); @@ -44,11 +45,24 @@ export class DeleteThirdPartyRelationshipAttributeAndNotifyPeerUseCase extends U return Result.fail(RuntimeErrors.attributes.isNotThirdPartyRelationshipAttribute(thirdPartyRelationshipAttributeId)); } + const relationshipWithStatusPending = await this.relationshipsController.getRelationshipToIdentity( + thirdPartyRelationshipAttribute.shareInfo.peer, + RelationshipStatus.Pending + ); + if (relationshipWithStatusPending) { + return Result.fail(RuntimeErrors.attributes.cannotDeleteSharedAttributeWhileRelationshipIsPending()); + } + const validationResult = await this.attributesController.validateFullAttributeDeletionProcess(thirdPartyRelationshipAttribute); if (validationResult.isError()) return Result.fail(validationResult.error); await this.attributesController.executeFullAttributeDeletionProcess(thirdPartyRelationshipAttribute); + const messageRecipientsValidationResult = await this.messageController.validateMessageRecipients([thirdPartyRelationshipAttribute.shareInfo.peer]); + if (messageRecipientsValidationResult.isError) { + return Result.ok({}); + } + const notificationId = await ConsumptionIds.notification.generate(); const notificationItem = ThirdPartyRelationshipAttributeDeletedByPeerNotificationItem.from({ attributeId: thirdPartyRelationshipAttributeId }); const notification = Notification.from({ diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index 4c9e97ea3..0ba632530 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -1,12 +1,16 @@ +import { AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON, AcceptRequestItemParametersJSON } from "@nmshd/consumption"; import { CityJSON, CountryJSON, DeleteAttributeRequestItem, HouseNumberJSON, ReadAttributeRequestItem, + ReadAttributeRequestItemJSON, RelationshipAttributeConfidentiality, + RelationshipTemplateContentJSON, RequestItemJSONDerivations, ShareAttributeRequestItem, + ShareAttributeRequestItemJSON, StreetJSON, ThirdPartyRelationshipAttributeQuery, ThirdPartyRelationshipAttributeQueryOwner, @@ -22,6 +26,7 @@ import { ChangeDefaultRepositoryAttributeUseCase, CreateAndShareRelationshipAttributeRequest, CreateAndShareRelationshipAttributeUseCase, + CreateOwnRelationshipTemplateRequest, CreateRepositoryAttributeRequest, CreateRepositoryAttributeUseCase, DeleteOwnSharedAttributeAndNotifyPeerUseCase, @@ -56,6 +61,7 @@ import { TestRuntimeServices, acceptIncomingShareAttributeRequest, cleanupAttributes, + createRelationshipWithStatusPending, establishRelationship, exchangeAndAcceptRequestByMessage, executeFullCreateAndShareRelationshipAttributeFlow, @@ -1362,7 +1368,7 @@ describe(ShareRepositoryAttributeUseCase.name, () => { const rPeerSharedIdentityAttribute = (await services2.consumption.attributes.getAttribute({ id: sOwnSharedIdentityAttribute.id })).value; const deleteResult = await services2.consumption.attributes.deletePeerSharedAttributeAndNotifyOwner({ attributeId: rPeerSharedIdentityAttribute.id }); - const notificationId = deleteResult.value.notificationId; + const notificationId = deleteResult.value.notificationId!; await syncUntilHasMessageWithNotification(services1.transport, notificationId); await services1.eventBus.waitForEvent(PeerSharedAttributeDeletedByPeerEvent, (e) => { @@ -1716,7 +1722,7 @@ describe(NotifyPeerAboutRepositoryAttributeSuccessionUseCase.name, () => { const rPeerSharedIdentityAttributeVersion1 = (await services2.consumption.attributes.getAttribute({ id: ownSharedIdentityAttributeVersion1.id })).value; const deleteResult = await services2.consumption.attributes.deletePeerSharedAttributeAndNotifyOwner({ attributeId: rPeerSharedIdentityAttributeVersion1.id }); - const notificationId = deleteResult.value.notificationId; + const notificationId = deleteResult.value.notificationId!; await syncUntilHasMessageWithNotification(services1.transport, notificationId); await services1.eventBus.waitForEvent(PeerSharedAttributeDeletedByPeerEvent, (e) => { @@ -1908,7 +1914,7 @@ describe(SucceedRelationshipAttributeAndNotifyPeerUseCase.name, () => { const rPeerSharedRelationshipAttribute = (await services2.consumption.attributes.getAttribute({ id: sOwnSharedRelationshipAttribute.id })).value; const deleteResult = await services2.consumption.attributes.deletePeerSharedAttributeAndNotifyOwner({ attributeId: rPeerSharedRelationshipAttribute.id }); - const notificationId = deleteResult.value.notificationId; + const notificationId = deleteResult.value.notificationId!; await syncUntilHasMessageWithNotification(services1.transport, notificationId); await services1.eventBus.waitForEvent(PeerSharedAttributeDeletedByPeerEvent, (e) => { @@ -2636,7 +2642,7 @@ describe("DeleteAttributeUseCases", () => { test("should notify about identity attribute deletion by owner", async () => { const notificationId = (await services1.consumption.attributes.deleteOwnSharedAttributeAndNotifyPeer({ attributeId: ownSharedIdentityAttributeVersion0.id })).value - .notificationId; + .notificationId!; const timeBeforeUpdate = CoreDate.utc(); await syncUntilHasMessageWithNotification(services2.transport, notificationId); await services2.eventBus.waitForEvent(OwnSharedAttributeDeletedByOwnerEvent, (e) => { @@ -2653,7 +2659,7 @@ describe("DeleteAttributeUseCases", () => { test("should notify about identity attribute deletion of succeeded attribute by owner", async () => { const notificationId = (await services1.consumption.attributes.deleteOwnSharedAttributeAndNotifyPeer({ attributeId: ownSharedIdentityAttributeVersion1.id })).value - .notificationId; + .notificationId!; const timeBeforeUpdate = CoreDate.utc(); await syncUntilHasMessageWithNotification(services2.transport, notificationId); await services2.eventBus.waitForEvent(OwnSharedAttributeDeletedByOwnerEvent, (e) => { @@ -2665,6 +2671,61 @@ describe("DeleteAttributeUseCases", () => { expect(updatedPredecessor.deletionInfo?.deletionStatus).toStrictEqual(LocalAttributeDeletionStatus.DeletedByOwner); expect(CoreDate.from(updatedPredecessor.deletionInfo!.deletionDate).isBetween(timeBeforeUpdate, timeAfterUpdate.add(1))).toBe(true); }); + + test("should throw an error trying to delete an own shared Attribute when the Relationship is in status Pending", async () => { + const [services1, services2] = await runtimeServiceProvider.launch(2, { + enableRequestModule: true, + enableDeciderModule: true, + enableNotificationModule: true + }); + + const repositoryAttribute = ( + await services2.consumption.attributes.createRepositoryAttribute({ + content: { + value: { + "@type": "GivenName", + value: "aGivenName" + } + } + }) + ).value; + + const item: ReadAttributeRequestItemJSON = { + "@type": "ReadAttributeRequestItem", + mustBeAccepted: true, + query: { + "@type": "IdentityAttributeQuery", + valueType: "GivenName" + } + }; + + const relationshipTemplateContent: RelationshipTemplateContentJSON = { + "@type": "RelationshipTemplateContent", + title: "aTitle", + onNewRelationship: { + items: [item], + "@type": "Request" + } + }; + await createRelationshipWithStatusPending(services1, services2, relationshipTemplateContent, [ + { + accept: true, + existingAttributeId: repositoryAttribute.id + } as AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON + ]); + + const ownSharedAttribute = await services2.consumption.attributes.getAttributes({ + query: { + "shareInfo.sourceAttribute": repositoryAttribute.id + } + }); + + const attributeDeletionResult = await services2.consumption.attributes.deleteOwnSharedAttributeAndNotifyPeer({ attributeId: ownSharedAttribute.value[0].id }); + expect(attributeDeletionResult).toBeAnError( + "The shared Attribute cannot be deleted while the Relationship to the peer is in status 'Pending'.", + "error.runtime.attributes.cannotDeleteSharedAttributeWhileRelationshipIsPending" + ); + }); }); describe(DeletePeerSharedAttributeAndNotifyOwnerUseCase.name, () => { @@ -2749,7 +2810,7 @@ describe("DeleteAttributeUseCases", () => { test("should notify about identity attribute deletion by peer", async () => { const notificationId = (await services2.consumption.attributes.deletePeerSharedAttributeAndNotifyOwner({ attributeId: ownSharedIdentityAttributeVersion0.id })).value - .notificationId; + .notificationId!; const timeBeforeUpdate = CoreDate.utc(); await syncUntilHasMessageWithNotification(services1.transport, notificationId); await services1.eventBus.waitForEvent(PeerSharedAttributeDeletedByPeerEvent, (e) => { @@ -2767,7 +2828,7 @@ describe("DeleteAttributeUseCases", () => { test("should notify about identity attribute deletion of succeeded attribute by peer", async () => { const notificationId = (await services2.consumption.attributes.deletePeerSharedAttributeAndNotifyOwner({ attributeId: ownSharedIdentityAttributeVersion1.id })).value - .notificationId; + .notificationId!; const timeBeforeUpdate = CoreDate.utc(); await syncUntilHasMessageWithNotification(services1.transport, notificationId); await services1.eventBus.waitForEvent(PeerSharedAttributeDeletedByPeerEvent, (e) => { @@ -2780,6 +2841,59 @@ describe("DeleteAttributeUseCases", () => { expect(updatedPredecessor.deletionInfo?.deletionStatus).toStrictEqual(LocalAttributeDeletionStatus.DeletedByPeer); expect(CoreDate.from(updatedPredecessor.deletionInfo!.deletionDate).isBetween(timeBeforeUpdate, timeAfterUpdate.add(1))).toBe(true); }); + + test("should throw an error trying to delete a peer shared Attribute when the Relationship is in status Pending", async () => { + const [services1, services2] = await runtimeServiceProvider.launch(2, { + enableRequestModule: true, + enableDeciderModule: true, + enableNotificationModule: true + }); + + const repositoryAttribute = ( + await services1.consumption.attributes.createRepositoryAttribute({ + content: { + value: { + "@type": "GivenName", + value: "aGivenName" + } + } + }) + ).value; + + const item: ShareAttributeRequestItemJSON = { + "@type": "ShareAttributeRequestItem", + mustBeAccepted: true, + attribute: repositoryAttribute.content, + sourceAttributeId: repositoryAttribute.id + }; + + const relationshipTemplateContent: CreateOwnRelationshipTemplateRequest["content"] = { + "@type": "RelationshipTemplateContent", + title: "aTitle", + onNewRelationship: { + items: [item], + "@type": "Request" + } + }; + + await createRelationshipWithStatusPending(services1, services2, relationshipTemplateContent, [ + { + accept: true + } as AcceptRequestItemParametersJSON + ]); + + const peerSharedAttribute = await services2.consumption.attributes.getAttributes({ + query: { + "shareInfo.peer": services1.address + } + }); + + const attributeDeletionResult = await services2.consumption.attributes.deletePeerSharedAttributeAndNotifyOwner({ attributeId: peerSharedAttribute.value[0].id }); + expect(attributeDeletionResult).toBeAnError( + "The shared Attribute cannot be deleted while the Relationship to the peer is in status 'Pending'.", + "error.runtime.attributes.cannotDeleteSharedAttributeWhileRelationshipIsPending" + ); + }); }); describe(DeleteThirdPartyRelationshipAttributeAndNotifyPeerUseCase.name, () => { @@ -2850,7 +2964,7 @@ describe("DeleteAttributeUseCases", () => { test("should notify about ThirdPartyRelationshipAttribute as the emitter of it", async () => { const notificationId = ( await services1.consumption.attributes.deleteThirdPartyRelationshipAttributeAndNotifyPeer({ attributeId: emittedThirdPartyRelationshipAttribute.id }) - ).value.notificationId; + ).value.notificationId!; const timeBeforeUpdate = CoreDate.utc(); await syncUntilHasMessageWithNotification(services2.transport, notificationId); await services2.eventBus.waitForEvent(ThirdPartyRelationshipAttributeDeletedByPeerEvent, (e) => { @@ -2869,7 +2983,7 @@ describe("DeleteAttributeUseCases", () => { test("should notify about ThirdPartyRelationshipAttribute as the recipient of it", async () => { const notificationId = ( await services2.consumption.attributes.deleteThirdPartyRelationshipAttributeAndNotifyPeer({ attributeId: emittedThirdPartyRelationshipAttribute.id }) - ).value.notificationId; + ).value.notificationId!; const timeBeforeUpdate = CoreDate.utc(); await syncUntilHasMessageWithNotification(services1.transport, notificationId); await services1.eventBus.waitForEvent(ThirdPartyRelationshipAttributeDeletedByPeerEvent, (e) => { @@ -2896,6 +3010,62 @@ describe("DeleteAttributeUseCases", () => { const getDeletedAttributeResult = await services1.consumption.attributes.getAttribute({ id: emittedThirdPartyRelationshipAttribute.id }); expect(getDeletedAttributeResult).toBeAnError(/.*/, "error.runtime.recordNotFound"); }); + + test("should throw an error trying to delete a ThirdPartyRelationshipAttribute when the Relationship is in status Pending", async () => { + const [services1, services2, services3] = await runtimeServiceProvider.launch(3, { + enableRequestModule: true, + enableDeciderModule: true, + enableNotificationModule: true + }); + await establishRelationship(services1.transport, services2.transport); + const peerSharedRelationshipAttribute = await executeFullCreateAndShareRelationshipAttributeFlow(services2, services1, { + content: { + value: { + "@type": "ProprietaryString", + value: "aString", + title: "aTitle" + }, + key: "aKey", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }); + + const item: ShareAttributeRequestItemJSON = { + "@type": "ShareAttributeRequestItem", + mustBeAccepted: true, + attribute: peerSharedRelationshipAttribute.content, + sourceAttributeId: peerSharedRelationshipAttribute.id, + thirdPartyAddress: services1.address + }; + + const relationshipTemplateContent: CreateOwnRelationshipTemplateRequest["content"] = { + "@type": "RelationshipTemplateContent", + title: "aTitle", + onNewRelationship: { + items: [item], + "@type": "Request" + } + }; + + await createRelationshipWithStatusPending(services2, services3, relationshipTemplateContent, [ + { + accept: true + } as AcceptRequestItemParametersJSON + ]); + + const thirdPartyRelationshipAttribute = await services3.consumption.attributes.getAttributes({ + query: { + "shareInfo.peer": services2.address + } + }); + const attributeDeletionResult = await services3.consumption.attributes.deletePeerSharedAttributeAndNotifyOwner({ + attributeId: thirdPartyRelationshipAttribute.value[0].id + }); + expect(attributeDeletionResult).toBeAnError( + "The shared Attribute cannot be deleted while the Relationship to the peer is in status 'Pending'.", + "error.runtime.attributes.cannotDeleteSharedAttributeWhileRelationshipIsPending" + ); + }); }); }); diff --git a/packages/runtime/test/lib/testUtils.ts b/packages/runtime/test/lib/testUtils.ts index 2e991bc8a..88ff5e5ec 100644 --- a/packages/runtime/test/lib/testUtils.ts +++ b/packages/runtime/test/lib/testUtils.ts @@ -4,7 +4,8 @@ import { AcceptRequestItemParametersJSON, ConsumptionIds, DecideRequestItemGroupParametersJSON, - DecideRequestItemParametersJSON + DecideRequestItemParametersJSON, + DecideRequestParametersJSON } from "@nmshd/consumption"; import { ArbitraryRelationshipCreationContent, @@ -23,7 +24,7 @@ import { ShareAttributeAcceptResponseItemJSON, ShareAttributeRequestItem } from "@nmshd/content"; -import { CoreAddress, CoreId } from "@nmshd/core-types"; +import { CoreAddress, CoreDate, CoreId } from "@nmshd/core-types"; import { CoreBuffer } from "@nmshd/crypto"; import { IdentityUtil } from "@nmshd/transport"; import fs from "fs"; @@ -54,6 +55,7 @@ import { RelationshipDTO, RelationshipStatus, RelationshipTemplateDTO, + RelationshipTemplateProcessedEvent, ShareRepositoryAttributeRequest, SucceedRepositoryAttributeRequest, SucceedRepositoryAttributeResponse, @@ -915,3 +917,38 @@ export async function cleanupAttributes(...services: TestRuntimeServices[]): Pro }) ); } + +export async function createRelationshipWithStatusPending( + templator: TestRuntimeServices, + requestor: TestRuntimeServices, + templateContent: RelationshipTemplateContentJSON, + acceptItems: DecideRequestParametersJSON["items"] +): Promise { + const relationshipTemplateResult = await templator.transport.relationshipTemplates.createOwnRelationshipTemplate({ + content: templateContent, + expiresAt: CoreDate.utc().add({ day: 1 }).toISOString() + }); + + const loadedPeerTemplateResult = await requestor.transport.relationshipTemplates.loadPeerRelationshipTemplate({ + reference: relationshipTemplateResult.value.truncatedReference + }); + + await requestor.eventBus.waitForEvent(RelationshipTemplateProcessedEvent, (event) => { + return event.data.template.id === loadedPeerTemplateResult.value.id; + }); + + const requestsForRelationship = await requestor.consumption.incomingRequests.getRequests({ + query: { + "source.reference": loadedPeerTemplateResult.value.id + } + }); + + await requestor.consumption.incomingRequests.accept({ + requestId: requestsForRelationship.value[0].id, + items: acceptItems + }); + + const relationships = await syncUntilHasRelationships(templator.transport); + expect(relationships).toHaveLength(1); + return relationships[0]; +} diff --git a/packages/runtime/test/transport/messages.test.ts b/packages/runtime/test/transport/messages.test.ts index 5832e8864..d94b4b38d 100644 --- a/packages/runtime/test/transport/messages.test.ts +++ b/packages/runtime/test/transport/messages.test.ts @@ -545,7 +545,7 @@ describe("Postponed Notifications via Messages", () => { const notifyAboutDeletionResult = (await client1.consumption.attributes.deleteOwnSharedAttributeAndNotifyPeer({ attributeId: ownSharedIdentityAttribute.id })).value; await client1.eventBus.waitForEvent(AttributeDeletedEvent); await client5.transport.account.syncEverything(); - const deletionNotificationNotYetReceived = await client5.consumption.notifications.getNotification({ id: notifyAboutDeletionResult.notificationId }); + const deletionNotificationNotYetReceived = await client5.consumption.notifications.getNotification({ id: notifyAboutDeletionResult.notificationId! }); expect(deletionNotificationNotYetReceived).toBeAnError(/.*/, "error.transport.recordNotFound"); await client1.transport.relationships.requestRelationshipReactivation({ relationshipId: relationshipId }); @@ -561,7 +561,7 @@ describe("Postponed Notifications via Messages", () => { await client5.eventBus.waitForRunningEventHandlers(); const postponedSuccessionNotification = await client5.consumption.notifications.getNotification({ id: notifyAboutSuccessionResult.notificationId }); expect(postponedSuccessionNotification).toBeSuccessful(); - const postponedDeletionNotification = await client5.consumption.notifications.getNotification({ id: notifyAboutDeletionResult.notificationId }); + const postponedDeletionNotification = await client5.consumption.notifications.getNotification({ id: notifyAboutDeletionResult.notificationId! }); expect(postponedDeletionNotification).toBeSuccessful(); const peerSharedIdentityAttribute = (await client5.consumption.attributes.getAttribute({ id: ownSharedIdentityAttribute.id })).value; diff --git a/packages/transport/src/modules/messages/MessageController.ts b/packages/transport/src/modules/messages/MessageController.ts index 88f819234..39e23b9f4 100644 --- a/packages/transport/src/modules/messages/MessageController.ts +++ b/packages/transport/src/modules/messages/MessageController.ts @@ -1,5 +1,5 @@ import { ISerializable } from "@js-soft/ts-serval"; -import { log } from "@js-soft/ts-utils"; +import { log, Result } from "@js-soft/ts-utils"; import { CoreAddress, CoreDate, CoreId, ICoreAddress, ICoreId } from "@nmshd/core-types"; import { CoreBuffer, CryptoCipher, CryptoSecretKey } from "@nmshd/crypto"; import { nameof } from "ts-simple-nameof"; @@ -292,8 +292,8 @@ export class MessageController extends TransportController { const parsedParams = SendMessageParameters.from(parameters); if (!parsedParams.attachments) parsedParams.attachments = []; - const validationError = await this.validateMessageRecipients(parsedParams.recipients); - if (validationError) throw validationError; + const validationResult = await this.validateMessageRecipients(parsedParams.recipients); + if (validationResult.isError) throw validationResult.error; const secret = await CoreCrypto.generateSecretKey(); const serializedSecret = secret.serialize(false); @@ -419,7 +419,7 @@ export class MessageController extends TransportController { return message; } - private async validateMessageRecipients(recipients: CoreAddress[]) { + public async validateMessageRecipients(recipients: CoreAddress[]): Promise> { const peersWithNeitherActiveNorTerminatedRelationship: string[] = []; const deletedPeers: string[] = []; @@ -437,12 +437,12 @@ export class MessageController extends TransportController { } if (peersWithNeitherActiveNorTerminatedRelationship.length > 0) { - return TransportCoreErrors.messages.hasNeitherActiveNorTerminatedRelationship(peersWithNeitherActiveNorTerminatedRelationship); + return Result.fail(TransportCoreErrors.messages.hasNeitherActiveNorTerminatedRelationship(peersWithNeitherActiveNorTerminatedRelationship)); } - if (deletedPeers.length > 0) return TransportCoreErrors.messages.peerIsDeleted(deletedPeers); + if (deletedPeers.length > 0) return Result.fail(TransportCoreErrors.messages.peerIsDeleted(deletedPeers)); - return; + return Result.ok(undefined); } private async decryptOwnEnvelope(envelope: MessageEnvelope, secretKey: CryptoSecretKey): Promise {