From 36f9762855d0ef1e7d4c361dda8732d23b04600b Mon Sep 17 00:00:00 2001 From: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com> Date: Wed, 24 Apr 2024 18:17:18 +0200 Subject: [PATCH 01/40] Refactor/adapt runtime to new relationships api (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: adapt accept, reject, revoke runtime use cases * chore: change relationship classes * chore: adapt transmission types * chore: rename transmission request/response types * fix: relationships backbone response type * chore: adapt RelationshipsController * chore: adapt ExternalEventsProcessor * chore: adapt the relationship use cases * chore: adapt request response type * chore: adapt completing incoming requests * refactor: content type names * feat: get relationship with audit logs, fix types * chore: adapt outgoing request controller and use cases * chore: adapt relationship (change) dtos * chore: adapt request module * chore: adapt index file * refactor: auditLog is transport relationship member * chore: adapt relationship getter use cases * chore: rename use cases * chore: adapt transport validation * chore: adapt schemas and further renaming * chore: rename relation creation request content file * refactor: renaming types * chore: adapt request consumption tests * refactor: rename content to creation content in sendRelationship * fix: relationship creation request type name * chore: adapt final consumption and transport tests * chore: adapt runtime tests * fix: adapt changes in transport tests * chore: adapt the new external event processor * chore: use correct backbone version * fix: adapt to backbone signature * fix: await promise * fix: update status with relationship * refactor: move audit log to relationship cache * refactor: merge the relationship event handlers * fix: relationship controller flows * refactor: relationship types * fix: audit log deserializer * fix: relationship event payload * fix: get correct creation/acceptance content * fix: only decrypt relationship if secrets available * fix: don't use sync result in a test * chore: remove comments / debug code * fix: transport tests, sort audit log * fix: consumption tests, request controller type * refactor: remove auditLog flag in useCases * fix: assorted fixes * chore: update app-runtime * fix: exports * refactor: massively simplify DTO creation * fix: app runtime tests * fix: import * fix: mandatory audit log * fix: adapt relationship dto/dvo, remove null checks * refactor/fix: add audit log to test factory, remove redundant method * fix: de-duplicate functions * fix: redo old behaviour * fix: casing * fix: update event behaviour * fix: re-add some tests * fix: make peer an address again * refactor: mandatory payload in put * feat: add the relationship changes to the relationship dto * chore: add validation to outoing request controller * refactor: relationshipCreationContent instead of CreationRequestContent * refactor/fix: auditLog to relationshipAuditLog, add createdByDevice * refactor: simplify RelationshipMapper * chore: remove unused runtime error * fix: re-add check, remove throw * fix: add createdByDevice to relationship DTO * refactor: cleaner function call * test: add old relationship change tests; test for creation content * fix: add createdByDevice to audit log method * fix: wrong type annotations * fix: condition in createRequestFromTemplateResponse * fix: add createdBDevice to TestObjectFactory audit logs * refactor: rename auditLog file * refactor: cosmetic changes * refactor: correct audit log in test object factory * fix: add oldStatus * refactor: remove empty acceptanceContents * refactor: request/response to creation-/acceptanceContent * chore: adapt backbone return types, type check * fix: correctly use types * refactor: no type extension in backboneGetRelationships * fix: catch undefined creation content * refactor: fail fast undefined creation content * fix: this was supposed to be the previous commit * refactor: add relationship prefix to audit log * refactor: rename relationship event processor * refactor: change checks, use JSONWrapper for creation content * refactor: split audit log class * fix: update import * refactor: acceptanceContent -> creationResponseContent * refactor: combine events * fix: naming * chore: bump backbone * chore: add admin ui to compose * fix: naming * chore: naming * fix: Relationships * fix: re-add import * fix: update types and errors * chore: bump backbone * fix: add enum validators * chore: any is always nullable * fix: pass creation content * fix: throw error again * chore: remove unused content * fix: make publicCreationResponseContentCrypto required * chore: update validate annotation --------- Co-authored-by: Julian König Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .dev/compose.backbone.env | 2 +- .dev/compose.backbone.yml | 18 + .../events/OnboardingChangeReceivedEvent.ts | 8 +- .../facades/AppRelationshipFacade.ts | 62 +-- .../OnboardingChangeReceivedModule.ts | 17 +- .../RelationshipChangedModule.ts | 26 +- packages/app-runtime/test/lib/TestUtil.ts | 68 +-- .../RelationshipEventingAccept.test.ts | 8 +- .../RelationshipEventingReject.test.ts | 8 +- .../RelationshipEventingRevoke.test.ts | 8 +- .../incoming/IncomingRequestsController.ts | 4 +- .../CompleteIncomingRequestParameters.ts | 8 +- .../modules/requests/local/LocalResponse.ts | 4 +- .../outgoing/OutgoingRequestsController.ts | 16 +- ...mRelationshipTemplateResponseParameters.ts | 14 +- packages/consumption/test/core/TestUtil.ts | 6 +- .../IncomingRequestsController.test.ts | 14 +- .../OutgoingRequestsController.test.ts | 20 +- .../modules/requests/RequestEnd2End.test.ts | 25 +- .../requests/RequestsIntegrationTest.ts | 19 +- .../requests/testHelpers/TestObjectFactory.ts | 97 ++-- .../RelationshipCreationContent.ts | 27 + packages/content/src/relationships/index.ts | 1 + .../runtime/src/dataViews/DataViewExpander.ts | 57 +- .../dataViews/consumption/LocalRequestDVO.ts | 2 +- .../dataViews/transport/RelationshipDVO.ts | 33 +- packages/runtime/src/events/EventProxy.ts | 6 +- ...onshipCreationCreatedAndCompletedEvent.ts} | 6 +- .../runtime/src/events/consumption/index.ts | 2 +- .../facades/transport/RelationshipsFacade.ts | 32 +- packages/runtime/src/modules/RequestModule.ts | 29 +- .../src/types/consumption/LocalRequestDTO.ts | 2 +- .../src/types/transport/RelationshipDTO.ts | 20 + .../runtime/src/useCases/common/Schemas.ts | 74 +-- .../common/validation/ValidatableStrings.ts | 5 - .../requests/CompleteIncomingRequest.ts | 16 +- ...RequestFromRelationshipTemplateResponse.ts | 17 +- ...ionshipChange.ts => AcceptRelationship.ts} | 23 +- .../relationships/CreateRelationship.ts | 4 +- ...ionshipChange.ts => RejectRelationship.ts} | 23 +- .../relationships/RelationshipMapper.ts | 64 ++- ...ionshipChange.ts => RevokeRelationship.ts} | 23 +- .../useCases/transport/relationships/index.ts | 6 +- .../runtime/test/consumption/requests.test.ts | 15 +- .../test/dataViews/RelationshipDVO.test.ts | 42 +- .../dataViews/RelationshipTemplateDVO.test.ts | 4 +- packages/runtime/test/lib/testUtils.ts | 26 +- .../test/lib/testUtilsWithInactiveModules.ts | 8 +- .../test/modules/RequestModule.test.ts | 18 +- .../test/transport/relationships.test.ts | 9 +- packages/transport/src/core/CoreErrors.ts | 6 +- .../src/core/backbone/BackboneIds.ts | 1 - .../src/modules/devices/local/Device.ts | 4 +- packages/transport/src/modules/index.ts | 23 +- .../RelationshipSecretController.ts | 6 +- .../relationships/RelationshipsController.ts | 503 ++++++------------ .../backbone/BackboneGetRelationships.ts | 13 +- .../BackboneGetRelationshipsChanges.ts | 24 - .../backbone/BackbonePostRelationship.ts | 8 + .../BackbonePostRelationshipsChanges.ts | 10 - .../backbone/BackbonePutRelationship.ts | 7 + .../backbone/BackboneRelationship.ts | 15 + .../backbone/RelationshipClient.ts | 49 +- .../relationships/local/CachedRelationship.ts | 19 +- .../relationships/local/Relationship.ts | 57 +- .../local/RelationshipAuditLog.ts | 21 + .../local/RelationshipAuditLogEntry.ts | 51 ++ .../local/SendRelationshipParameters.ts | 4 +- .../transmission/RelationshipAuditLog.ts | 19 + .../changes/RelationshipChange.ts | 64 --- .../changes/RelationshipChangeRequest.ts | 44 -- .../changes/RelationshipChangeResponse.ts | 44 -- .../changes/RelationshipChangeStatus.ts | 6 - .../changes/RelationshipChangeType.ts | 5 - ...RelationshipCreationChangeRequestCipher.ts | 27 - .../RelationshipCreationContentCipher.ts | 27 + ...s => RelationshipCreationContentSigned.ts} | 12 +- ... => RelationshipCreationContentWrapper.ts} | 10 +- ...elationshipCreationChangeResponseCipher.ts | 27 - ...hipCreationChangeResponseContentWrapper.ts | 22 - ...lationshipCreationResponseContentCipher.ts | 27 + ...ationshipCreationResponseContentSigned.ts} | 12 +- ...ationshipCreationResponseContentWrapper.ts | 17 + .../ExternalEventProcessorRegistry.ts | 6 +- ...ipChangeCompletedExternalEventProcessor.ts | 17 - ...hipStatusChangedExternalEventProcessor.ts} | 6 +- .../sync/externalEventProcessors/index.ts | 3 +- .../transport/test/end2end/End2End.test.ts | 115 ++-- .../transport/test/modules/PublicAPI.test.ts | 12 +- .../RelationshipsController.test.ts | 36 +- .../RelationshipsCustomContent.test.ts | 17 +- .../modules/sync/SyncController.error.test.ts | 2 +- .../sync/SyncController.relationships.test.ts | 20 +- .../transport/test/testHelpers/TestUtil.ts | 14 +- .../transport/test/utils/Reflection.test.ts | 30 +- 95 files changed, 919 insertions(+), 1589 deletions(-) create mode 100644 packages/content/src/relationships/RelationshipCreationContent.ts rename packages/runtime/src/events/consumption/{OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent.ts => OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent.ts} (52%) rename packages/runtime/src/useCases/transport/relationships/{AcceptRelationshipChange.ts => AcceptRelationship.ts} (53%) rename packages/runtime/src/useCases/transport/relationships/{RejectRelationshipChange.ts => RejectRelationship.ts} (53%) rename packages/runtime/src/useCases/transport/relationships/{RevokeRelationshipChange.ts => RevokeRelationship.ts} (53%) delete mode 100644 packages/transport/src/modules/relationships/backbone/BackboneGetRelationshipsChanges.ts create mode 100644 packages/transport/src/modules/relationships/backbone/BackbonePostRelationship.ts delete mode 100644 packages/transport/src/modules/relationships/backbone/BackbonePostRelationshipsChanges.ts create mode 100644 packages/transport/src/modules/relationships/backbone/BackbonePutRelationship.ts create mode 100644 packages/transport/src/modules/relationships/backbone/BackboneRelationship.ts create mode 100644 packages/transport/src/modules/relationships/local/RelationshipAuditLog.ts create mode 100644 packages/transport/src/modules/relationships/local/RelationshipAuditLogEntry.ts create mode 100644 packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts delete mode 100644 packages/transport/src/modules/relationships/transmission/changes/RelationshipChange.ts delete mode 100644 packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeRequest.ts delete mode 100644 packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeResponse.ts delete mode 100644 packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeStatus.ts delete mode 100644 packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeType.ts delete mode 100644 packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationChangeRequestCipher.ts create mode 100644 packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationContentCipher.ts rename packages/transport/src/modules/relationships/transmission/requests/{RelationshipCreationChangeRequestSigned.ts => RelationshipCreationContentSigned.ts} (54%) rename packages/transport/src/modules/relationships/transmission/requests/{RelationshipCreationChangeRequestContentWrapper.ts => RelationshipCreationContentWrapper.ts} (50%) delete mode 100644 packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationChangeResponseCipher.ts delete mode 100644 packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationChangeResponseContentWrapper.ts create mode 100644 packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationResponseContentCipher.ts rename packages/transport/src/modules/relationships/transmission/responses/{RelationshipCreationChangeResponseSigned.ts => RelationshipCreationResponseContentSigned.ts} (51%) create mode 100644 packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationResponseContentWrapper.ts delete mode 100644 packages/transport/src/modules/sync/externalEventProcessors/RelationshipChangeCompletedExternalEventProcessor.ts rename packages/transport/src/modules/sync/externalEventProcessors/{RelationshipChangeCreatedExternalEventProcessor.ts => RelationshipStatusChangedExternalEventProcessor.ts} (76%) diff --git a/.dev/compose.backbone.env b/.dev/compose.backbone.env index b1bc18bf6..6a5adf787 100644 --- a/.dev/compose.backbone.env +++ b/.dev/compose.backbone.env @@ -1 +1 @@ -BACKBONE_VERSION=5.0.0 +BACKBONE_VERSION=6.0.0-alpha.4 diff --git a/.dev/compose.backbone.yml b/.dev/compose.backbone.yml index ea4ea6f49..10a49e131 100644 --- a/.dev/compose.backbone.yml +++ b/.dev/compose.backbone.yml @@ -32,6 +32,24 @@ services: - source: Config target: app/appsettings.override.json + admin-ui: + image: ghcr.io/nmshd/backbone-admin-ui:${BACKBONE_VERSION} + container_name: admin-ui + hostname: admin-ui + ports: + - "8091:8080" + depends_on: + database: + condition: service_started + rabbitmq: + condition: service_started + consumer-api: + condition: service_healthy + configs: + - source: Config + target: app/appsettings.override.json + profiles: [debug] + ### infrastructure ### database: diff --git a/packages/app-runtime/src/events/OnboardingChangeReceivedEvent.ts b/packages/app-runtime/src/events/OnboardingChangeReceivedEvent.ts index abc6ba1da..04cff56b2 100644 --- a/packages/app-runtime/src/events/OnboardingChangeReceivedEvent.ts +++ b/packages/app-runtime/src/events/OnboardingChangeReceivedEvent.ts @@ -1,16 +1,16 @@ -import { DataEvent, IdentityDVO, RelationshipChangeDTO, RelationshipDTO } from "@nmshd/runtime"; +import { DataEvent, IdentityDVO, RelationshipAuditLogEntryDTO, RelationshipDTO } from "@nmshd/runtime"; export class OnboardingChangeReceivedEvent extends DataEvent<{ - change: RelationshipChangeDTO; relationship: RelationshipDTO; + auditLogEntry: RelationshipAuditLogEntryDTO; identity: IdentityDVO; }> { public static readonly namespace: string = "app.onboardingChangeReceived"; - public constructor(address: string, change: RelationshipChangeDTO, relationship: RelationshipDTO, identity: IdentityDVO) { + public constructor(address: string, relationship: RelationshipDTO, auditLogEntry: RelationshipAuditLogEntryDTO, identity: IdentityDVO) { super(OnboardingChangeReceivedEvent.namespace, address, { - change, relationship, + auditLogEntry, identity }); } diff --git a/packages/app-runtime/src/extensibility/facades/AppRelationshipFacade.ts b/packages/app-runtime/src/extensibility/facades/AppRelationshipFacade.ts index fc0511833..d5393d0a8 100644 --- a/packages/app-runtime/src/extensibility/facades/AppRelationshipFacade.ts +++ b/packages/app-runtime/src/extensibility/facades/AppRelationshipFacade.ts @@ -1,12 +1,12 @@ import { - AcceptRelationshipChangeRequest, + AcceptRelationshipRequest, CreateRelationshipRequest, GetRelationshipByAddressRequest, GetRelationshipRequest, GetRelationshipsRequest, IdentityDVO, - RejectRelationshipChangeRequest, - RevokeRelationshipChangeRequest + RejectRelationshipRequest, + RevokeRelationshipRequest } from "@nmshd/runtime"; import { UserfriendlyApplicationError } from "../../UserfriendlyApplicationError"; import { UserfriendlyResult } from "../../UserfriendlyResult"; @@ -43,34 +43,24 @@ export class AppRelationshipFacade extends AppRuntimeFacade { return UserfriendlyResult.ok(dvo); } - public async acceptRelationshipCreationChange(relationshipId: string, content: any): Promise> { - const result = await this.transportServices.relationships.getRelationship({ id: relationshipId }); - if (result.isError) { - return await this.parseErrorResult(result); - } - - const changeId = result.value.changes[0].id; - return await this.acceptRelationshipChange({ relationshipId, changeId, content }); + public async createRelationship(request: CreateRelationshipRequest): Promise> { + const result = await this.transportServices.relationships.createRelationship(request); + return await this.handleResult(result, (v) => this.expander.expandRelationshipDTO(v)); } - public async rejectRelationshipCreationChange(relationshipId: string, content: any): Promise> { - const result = await this.transportServices.relationships.getRelationship({ id: relationshipId }); - if (result.isError) { - return await this.parseErrorResult(result); - } - - const changeId = result.value.changes[0].id; - return await this.rejectRelationshipChange({ relationshipId, changeId, content }); + public async acceptRelationship(request: AcceptRelationshipRequest): Promise> { + const result = await this.transportServices.relationships.acceptRelationship(request); + return await this.handleResult(result, (v) => this.expander.expandRelationshipDTO(v)); } - public async revokeRelationshipCreationChange(relationshipId: string, content: any): Promise> { - const result = await this.transportServices.relationships.getRelationship({ id: relationshipId }); - if (result.isError) { - return await this.parseErrorResult(result); - } + public async rejectRelationship(request: RejectRelationshipRequest): Promise> { + const result = await this.transportServices.relationships.rejectRelationship(request); + return await this.handleResult(result, (v) => this.expander.expandRelationshipDTO(v)); + } - const changeId = result.value.changes[0].id; - return await this.revokeRelationshipChange({ relationshipId, changeId, content }); + public async revokeRelationship(request: RevokeRelationshipRequest): Promise> { + const result = await this.transportServices.relationships.revokeRelationship(request); + return await this.handleResult(result, (v) => this.expander.expandRelationshipDTO(v)); } public async getRelationships(request: GetRelationshipsRequest): Promise> { @@ -87,24 +77,4 @@ export class AppRelationshipFacade extends AppRuntimeFacade { const result = await this.transportServices.relationships.getRelationshipByAddress(request); return await this.handleResult(result, (v) => this.expander.expandRelationshipDTO(v)); } - - public async createRelationship(request: CreateRelationshipRequest): Promise> { - const result = await this.transportServices.relationships.createRelationship(request); - return await this.handleResult(result, (v) => this.expander.expandRelationshipDTO(v)); - } - - public async acceptRelationshipChange(request: AcceptRelationshipChangeRequest): Promise> { - const result = await this.transportServices.relationships.acceptRelationshipChange(request); - return await this.handleResult(result, (v) => this.expander.expandRelationshipDTO(v)); - } - - public async rejectRelationshipChange(request: RejectRelationshipChangeRequest): Promise> { - const result = await this.transportServices.relationships.rejectRelationshipChange(request); - return await this.handleResult(result, (v) => this.expander.expandRelationshipDTO(v)); - } - - public async revokeRelationshipChange(request: RevokeRelationshipChangeRequest): Promise> { - const result = await this.transportServices.relationships.revokeRelationshipChange(request); - return await this.handleResult(result, (v) => this.expander.expandRelationshipDTO(v)); - } } diff --git a/packages/app-runtime/src/modules/appEvents/OnboardingChangeReceivedModule.ts b/packages/app-runtime/src/modules/appEvents/OnboardingChangeReceivedModule.ts index 3545ae476..7408b7c19 100644 --- a/packages/app-runtime/src/modules/appEvents/OnboardingChangeReceivedModule.ts +++ b/packages/app-runtime/src/modules/appEvents/OnboardingChangeReceivedModule.ts @@ -1,4 +1,4 @@ -import { RelationshipChangeStatus } from "@nmshd/runtime"; +import { RelationshipAuditLogEntryReason } from "@nmshd/runtime"; import { AppRuntimeError } from "../../AppRuntimeError"; import { OnboardingChangeReceivedEvent } from "../../events"; import { AppRuntimeModule, AppRuntimeModuleConfiguration } from "../AppRuntimeModule"; @@ -17,32 +17,35 @@ export class OnboardingChangeReceivedModule extends AppRuntimeModule { diff --git a/packages/app-runtime/src/modules/runtimeEvents/RelationshipChangedModule.ts b/packages/app-runtime/src/modules/runtimeEvents/RelationshipChangedModule.ts index b650b57ae..f366a5b5f 100644 --- a/packages/app-runtime/src/modules/runtimeEvents/RelationshipChangedModule.ts +++ b/packages/app-runtime/src/modules/runtimeEvents/RelationshipChangedModule.ts @@ -1,4 +1,4 @@ -import { RelationshipChangedEvent } from "@nmshd/runtime"; +import { RelationshipAuditLogEntryReason, RelationshipChangedEvent } from "@nmshd/runtime"; import { AppRuntimeError } from "../../AppRuntimeError"; import { OnboardingChangeReceivedEvent } from "../../events"; import { AppRuntimeModule, AppRuntimeModuleConfiguration } from "../AppRuntimeModule"; @@ -18,20 +18,28 @@ export class RelationshipChangedModule extends AppRuntimeModule { - const relRequest = await from.transportServices.relationships.createRelationship({ templateId, content }); + const relRequest = await from.transportServices.relationships.createRelationship({ templateId, creationContent: content }); return relRequest.value; } - public static async acceptRelationship( - session: LocalAccountSession, - relationshipId: string, - content: any = { - mycontent: "response" - } - ): Promise { - const relationship = ( - await session.transportServices.relationships.getRelationship({ - id: relationshipId - }) - ).value; - - const acceptedRelationship = ( - await session.transportServices.relationships.acceptRelationshipChange({ - changeId: relationship.changes[0].id, - content, - relationshipId - }) - ).value; + public static async acceptRelationship(session: LocalAccountSession, relationshipId: string): Promise { + const acceptedRelationship = (await session.transportServices.relationships.acceptRelationship({ relationshipId })).value; return acceptedRelationship; } - public static async rejectRelationship( - session: LocalAccountSession, - relationshipId: string, - content: any = { - mycontent: "rejection" - } - ): Promise { - const relationship = ( - await session.transportServices.relationships.getRelationship({ - id: relationshipId - }) - ).value; - - const rejectedRelationship = ( - await session.transportServices.relationships.rejectRelationshipChange({ - changeId: relationship.changes[0].id, - content, - relationshipId - }) - ).value; + public static async rejectRelationship(session: LocalAccountSession, relationshipId: string): Promise { + const rejectedRelationship = (await session.transportServices.relationships.rejectRelationship({ relationshipId })).value; return rejectedRelationship; } - public static async revokeRelationship( - session: LocalAccountSession, - relationshipId: string, - content: any = { - mycontent: "revokation" - } - ): Promise { - const relationship = ( - await session.transportServices.relationships.getRelationship({ - id: relationshipId - }) - ).value; - - const rejectedRelationship = ( - await session.transportServices.relationships.revokeRelationshipChange({ - changeId: relationship.changes[0].id, - content, - relationshipId - }) - ).value; + public static async revokeRelationship(session: LocalAccountSession, relationshipId: string): Promise { + const rejectedRelationship = (await session.transportServices.relationships.revokeRelationship({ relationshipId })).value; return rejectedRelationship; } diff --git a/packages/app-runtime/test/modules/RelationshipEventingAccept.test.ts b/packages/app-runtime/test/modules/RelationshipEventingAccept.test.ts index e93c52342..2435794e0 100644 --- a/packages/app-runtime/test/modules/RelationshipEventingAccept.test.ts +++ b/packages/app-runtime/test/modules/RelationshipEventingAccept.test.ts @@ -1,4 +1,4 @@ -import { RelationshipChangedEvent, RelationshipChangeStatus, RelationshipStatus } from "@nmshd/runtime"; +import { RelationshipChangedEvent, RelationshipStatus } from "@nmshd/runtime"; import { AppRuntime, LocalAccountSession, OnboardingChangeReceivedEvent } from "../../src"; import { EventListener, TestUtil } from "../lib"; @@ -42,8 +42,7 @@ describe("RelationshipEventingAcceptTest", function () { const onboardingChangeReceivedEvent = events[1].instance as OnboardingChangeReceivedEvent; expect(onboardingChangeReceivedEvent).toBeInstanceOf(OnboardingChangeReceivedEvent); expect(onboardingChangeReceivedEvent.data).toBeDefined(); - expect(onboardingChangeReceivedEvent.data.change.status).toBe(RelationshipChangeStatus.Pending); - expect(onboardingChangeReceivedEvent.data.change).toBe(relationshipChangedEvent.data.changes[0]); + expect(onboardingChangeReceivedEvent.data.auditLogEntry.newStatus).toBe(RelationshipStatus.Pending); expect(onboardingChangeReceivedEvent.data.identity).toBeDefined(); expect(onboardingChangeReceivedEvent.data.identity.id).toBe(sessionB.address.toString()); expect(onboardingChangeReceivedEvent.data.identity.name).toBe(sessionB.address.toString().substring(3, 9)); @@ -85,8 +84,7 @@ describe("RelationshipEventingAcceptTest", function () { const onboardingChangeReceivedEvent = events[1].instance as OnboardingChangeReceivedEvent; expect(onboardingChangeReceivedEvent).toBeInstanceOf(OnboardingChangeReceivedEvent); expect(onboardingChangeReceivedEvent.data).toBeDefined(); - expect(onboardingChangeReceivedEvent.data.change.status).toBe(RelationshipChangeStatus.Accepted); - expect(onboardingChangeReceivedEvent.data.change).toBe(relationshipChangedEvent.data.changes[0]); + expect(onboardingChangeReceivedEvent.data.auditLogEntry.newStatus).toBe(RelationshipStatus.Active); expect(onboardingChangeReceivedEvent.data.identity).toBeDefined(); expect(onboardingChangeReceivedEvent.data.identity.name).toBe(sessionA.accountController.identity.address.toString().substring(3, 9)); expect(onboardingChangeReceivedEvent.data.identity.id).toBe(sessionA.accountController.identity.address.toString()); diff --git a/packages/app-runtime/test/modules/RelationshipEventingReject.test.ts b/packages/app-runtime/test/modules/RelationshipEventingReject.test.ts index 152177267..d3b7ecc22 100644 --- a/packages/app-runtime/test/modules/RelationshipEventingReject.test.ts +++ b/packages/app-runtime/test/modules/RelationshipEventingReject.test.ts @@ -1,4 +1,4 @@ -import { RelationshipChangedEvent, RelationshipChangeStatus, RelationshipStatus } from "@nmshd/runtime"; +import { RelationshipChangedEvent, RelationshipStatus } from "@nmshd/runtime"; import { AppRuntime, LocalAccountSession, OnboardingChangeReceivedEvent } from "../../src"; import { EventListener, TestUtil } from "../lib"; @@ -42,8 +42,7 @@ describe("RelationshipEventingRejectTest", function () { const onboardingChangeReceivedEvent = events[1].instance as OnboardingChangeReceivedEvent; expect(onboardingChangeReceivedEvent).toBeInstanceOf(OnboardingChangeReceivedEvent); expect(onboardingChangeReceivedEvent.data).toBeDefined(); - expect(onboardingChangeReceivedEvent.data.change.status).toBe(RelationshipChangeStatus.Pending); - expect(onboardingChangeReceivedEvent.data.change).toBe(relationshipChangedEvent.data.changes[0]); + expect(onboardingChangeReceivedEvent.data.auditLogEntry.newStatus).toBe(RelationshipStatus.Pending); expect(onboardingChangeReceivedEvent.data.identity).toBeDefined(); expect(onboardingChangeReceivedEvent.data.identity.name).toBe(sessionB.accountController.identity.address.toString().substring(3, 9)); expect(onboardingChangeReceivedEvent.data.identity.id).toBe(sessionB.accountController.identity.address.toString()); @@ -84,8 +83,7 @@ describe("RelationshipEventingRejectTest", function () { const onboardingChangeReceivedEvent = events[1].instance as OnboardingChangeReceivedEvent; expect(onboardingChangeReceivedEvent).toBeInstanceOf(OnboardingChangeReceivedEvent); expect(onboardingChangeReceivedEvent.data).toBeDefined(); - expect(onboardingChangeReceivedEvent.data.change.status).toBe(RelationshipChangeStatus.Rejected); - expect(onboardingChangeReceivedEvent.data.change).toBe(relationshipChangedEvent.data.changes[0]); + expect(onboardingChangeReceivedEvent.data.auditLogEntry.newStatus).toBe(RelationshipStatus.Rejected); expect(onboardingChangeReceivedEvent.data.identity).toBeDefined(); expect(onboardingChangeReceivedEvent.data.identity.name).toBe(sessionA.accountController.identity.address.toString().substring(3, 9)); diff --git a/packages/app-runtime/test/modules/RelationshipEventingRevoke.test.ts b/packages/app-runtime/test/modules/RelationshipEventingRevoke.test.ts index fd2a0822a..92801ecb1 100644 --- a/packages/app-runtime/test/modules/RelationshipEventingRevoke.test.ts +++ b/packages/app-runtime/test/modules/RelationshipEventingRevoke.test.ts @@ -1,4 +1,4 @@ -import { RelationshipChangedEvent, RelationshipChangeStatus, RelationshipStatus } from "@nmshd/runtime"; +import { RelationshipChangedEvent, RelationshipStatus } from "@nmshd/runtime"; import { AppRuntime, LocalAccountSession, OnboardingChangeReceivedEvent } from "../../src"; import { EventListener, TestUtil } from "../lib"; @@ -43,8 +43,7 @@ describe("RelationshipEventingRevokeTest", function () { const onboardingChangeReceivedEvent = events[1].instance as OnboardingChangeReceivedEvent; expect(onboardingChangeReceivedEvent).toBeInstanceOf(OnboardingChangeReceivedEvent); expect(onboardingChangeReceivedEvent.data).toBeDefined(); - expect(onboardingChangeReceivedEvent.data.change.status).toBe(RelationshipChangeStatus.Pending); - expect(onboardingChangeReceivedEvent.data.change).toBe(relationshipChangedEvent.data.changes[0]); + expect(onboardingChangeReceivedEvent.data.auditLogEntry.newStatus).toBe(RelationshipStatus.Pending); expect(onboardingChangeReceivedEvent.data.identity).toBeDefined(); expect(onboardingChangeReceivedEvent.data.identity.name).toBe(sessionB.accountController.identity.address.toString().substring(3, 9)); @@ -86,8 +85,7 @@ describe("RelationshipEventingRevokeTest", function () { const onboardingChangeReceivedEvent = events[1].instance as OnboardingChangeReceivedEvent; expect(onboardingChangeReceivedEvent).toBeInstanceOf(OnboardingChangeReceivedEvent); expect(onboardingChangeReceivedEvent.data).toBeDefined(); - expect(onboardingChangeReceivedEvent.data.change.status).toBe(RelationshipChangeStatus.Revoked); - expect(onboardingChangeReceivedEvent.data.change).toBe(relationshipChangedEvent.data.changes[0]); + expect(onboardingChangeReceivedEvent.data.auditLogEntry.newStatus).toBe(RelationshipStatus.Revoked); expect(onboardingChangeReceivedEvent.data.identity).toBeDefined(); expect(onboardingChangeReceivedEvent.data.identity.name).toBe(sessionB.accountController.identity.address.toString().substring(3, 9)); diff --git a/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts b/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts index 5da529aff..6280af606 100644 --- a/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts +++ b/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts @@ -14,13 +14,13 @@ import { RequestItemProcessorRegistry } from "../itemProcessors/RequestItemProce import { ILocalRequestSource, LocalRequest } from "../local/LocalRequest"; import { LocalRequestStatus } from "../local/LocalRequestStatus"; import { LocalResponse, LocalResponseSource } from "../local/LocalResponse"; -import { DecideRequestParametersValidator } from "./DecideRequestParametersValidator"; import { CheckPrerequisitesOfIncomingRequestParameters, ICheckPrerequisitesOfIncomingRequestParameters } from "./checkPrerequisites/CheckPrerequisitesOfIncomingRequestParameters"; import { CompleteIncomingRequestParameters, ICompleteIncomingRequestParameters } from "./complete/CompleteIncomingRequestParameters"; import { DecideRequestItemGroupParametersJSON } from "./decide/DecideRequestItemGroupParameters"; import { DecideRequestItemParametersJSON } from "./decide/DecideRequestItemParameters"; import { DecideRequestParametersJSON } from "./decide/DecideRequestParameters"; import { InternalDecideRequestParameters, InternalDecideRequestParametersJSON } from "./decide/InternalDecideRequestParameters"; +import { DecideRequestParametersValidator } from "./DecideRequestParametersValidator"; import { IReceivedIncomingRequestParameters, ReceivedIncomingRequestParameters } from "./received/ReceivedIncomingRequestParameters"; import { IRequireManualDecisionOfIncomingRequestParameters, @@ -339,7 +339,7 @@ export class IncomingRequestsController extends ConsumptionBaseController { if (parsedParams.responseSourceObject) { request.response!.source = LocalResponseSource.from({ - type: parsedParams.responseSourceObject instanceof Message ? "Message" : "RelationshipChange", + type: parsedParams.responseSourceObject instanceof Message ? "Message" : "Relationship", reference: parsedParams.responseSourceObject.id }); } else if (!requestIsRejected || !requestIsFromTemplate) { diff --git a/packages/consumption/src/modules/requests/incoming/complete/CompleteIncomingRequestParameters.ts b/packages/consumption/src/modules/requests/incoming/complete/CompleteIncomingRequestParameters.ts index 56da95c66..c71cfdc72 100644 --- a/packages/consumption/src/modules/requests/incoming/complete/CompleteIncomingRequestParameters.ts +++ b/packages/consumption/src/modules/requests/incoming/complete/CompleteIncomingRequestParameters.ts @@ -1,9 +1,9 @@ import { ISerializable, Serializable, serialize, validate } from "@js-soft/ts-serval"; -import { CoreId, ICoreId, IMessage, IRelationshipChange, Message, RelationshipChange } from "@nmshd/transport"; +import { CoreId, ICoreId, IMessage, IRelationship, Message, Relationship } from "@nmshd/transport"; export interface ICompleteIncomingRequestParameters extends ISerializable { requestId: ICoreId; - responseSourceObject?: IMessage | IRelationshipChange; + responseSourceObject?: IMessage | IRelationship; } export class CompleteIncomingRequestParameters extends Serializable implements ICompleteIncomingRequestParameters { @@ -11,9 +11,9 @@ export class CompleteIncomingRequestParameters extends Serializable implements I @validate() public requestId: CoreId; - @serialize({ unionTypes: [Message, RelationshipChange] }) + @serialize({ unionTypes: [Message, Relationship] }) @validate({ nullable: true }) - public responseSourceObject?: Message | RelationshipChange; + public responseSourceObject?: Message | Relationship; public static from(value: ICompleteIncomingRequestParameters): CompleteIncomingRequestParameters { return this.fromAny(value); diff --git a/packages/consumption/src/modules/requests/local/LocalResponse.ts b/packages/consumption/src/modules/requests/local/LocalResponse.ts index d4943c0b4..6b28933c3 100644 --- a/packages/consumption/src/modules/requests/local/LocalResponse.ts +++ b/packages/consumption/src/modules/requests/local/LocalResponse.ts @@ -3,7 +3,7 @@ import { IResponse, Response } from "@nmshd/content"; import { CoreDate, CoreId, CoreSerializable, ICoreDate, ICoreId, ICoreSerializable } from "@nmshd/transport"; export interface ILocalResponseSource extends ICoreSerializable { - type: "Message" | "RelationshipChange"; + type: "Message" | "Relationship"; reference: ICoreId; } @@ -11,7 +11,7 @@ export interface ILocalResponseSource extends ICoreSerializable { export class LocalResponseSource extends CoreSerializable implements ILocalResponseSource { @serialize() @validate() - public type: "Message" | "RelationshipChange"; + public type: "Message" | "Relationship"; @serialize() @validate() diff --git a/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts b/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts index 6b2471405..e974e02e7 100644 --- a/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts +++ b/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts @@ -8,7 +8,6 @@ import { ICoreId, Message, Relationship, - RelationshipChange, RelationshipTemplate, SynchronizedCollection, CoreErrors as TransportCoreErrors @@ -127,7 +126,7 @@ export class OutgoingRequestsController extends ConsumptionBaseController { public async createAndCompleteFromRelationshipTemplateResponse(params: ICreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters): Promise { const parsedParams = CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters.from(params); - const peer = parsedParams.responseSource instanceof RelationshipChange ? parsedParams.responseSource.request.createdBy : parsedParams.responseSource.cache!.createdBy; + const peer = parsedParams.responseSource instanceof Relationship ? parsedParams.responseSource.peer.address : parsedParams.responseSource.cache!.createdBy; const response = parsedParams.response; const requestId = response.requestId; @@ -137,7 +136,7 @@ export class OutgoingRequestsController extends ConsumptionBaseController { } // checking for an active relationship is not secure as in the meantime the relationship could have been accepted - const isFromNewRelationship = parsedParams.responseSource instanceof RelationshipChange && parsedParams.responseSource.type === "Creation"; + const isFromNewRelationship = parsedParams.responseSource instanceof Relationship && parsedParams.responseSource.cache!.auditLog.length === 1; const requestContent = isFromNewRelationship ? templateContent.onNewRelationship : templateContent.onExistingRelationship; @@ -220,12 +219,13 @@ export class OutgoingRequestsController extends ConsumptionBaseController { return request; } - private async _complete(requestId: CoreId, responseSourceObject: Message | RelationshipChange, receivedResponse: Response): Promise { + private async _complete(requestId: CoreId, responseSourceObject: Message | Relationship, receivedResponse: Response): Promise { const request = await this.getOrThrow(requestId); this.assertRequestStatus(request, LocalRequestStatus.Open, LocalRequestStatus.Expired); - const responseSourceObjectCreationDate = responseSourceObject instanceof Message ? responseSourceObject.cache!.createdAt : responseSourceObject.request.createdAt; + const responseSourceObjectCreationDate = + responseSourceObject instanceof Message ? responseSourceObject.cache!.createdAt : responseSourceObject.cache!.auditLog[0].createdAt; if (request.status === LocalRequestStatus.Expired && request.isExpired(responseSourceObjectCreationDate)) { throw new ConsumptionError("Cannot complete an expired request with a response that was created before the expiration date"); } @@ -238,12 +238,12 @@ export class OutgoingRequestsController extends ConsumptionBaseController { await this.applyItems(request.content.items, receivedResponse.items, request); - let responseSource: "Message" | "RelationshipChange"; + let responseSource: "Message" | "Relationship"; if (responseSourceObject instanceof Message) { responseSource = "Message"; - } else if (responseSourceObject instanceof RelationshipChange) { - responseSource = "RelationshipChange"; + } else if (responseSourceObject instanceof Relationship) { + responseSource = "Relationship"; } else { throw new ConsumptionError("Invalid responseSourceObject"); } diff --git a/packages/consumption/src/modules/requests/outgoing/createAndCompleteFromRelationshipTemplateResponse/CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters.ts b/packages/consumption/src/modules/requests/outgoing/createAndCompleteFromRelationshipTemplateResponse/CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters.ts index 25b70f160..aee050f58 100644 --- a/packages/consumption/src/modules/requests/outgoing/createAndCompleteFromRelationshipTemplateResponse/CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters.ts +++ b/packages/consumption/src/modules/requests/outgoing/createAndCompleteFromRelationshipTemplateResponse/CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters.ts @@ -1,14 +1,14 @@ import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; import { IResponse, Response } from "@nmshd/content"; -import { IMessage, IRelationshipChange, IRelationshipTemplate, Message, RelationshipChange, RelationshipTemplate } from "@nmshd/transport"; +import { CoreDate, IMessage, IRelationship, IRelationshipTemplate, Message, Relationship, RelationshipTemplate } from "@nmshd/transport"; export interface ICreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters extends ISerializable { template: IRelationshipTemplate; - responseSource: IRelationshipChange | IMessage; + responseSource: IRelationship | IMessage; response: IResponse; } -@type("CreateAndCompleteOutgoingRequestFromRelationshipCreationChangeParameters") +@type("CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters") export class CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters extends Serializable implements ICreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters @@ -17,14 +17,18 @@ export class CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponsePar @validate() public template: RelationshipTemplate; - @serialize({ unionTypes: [RelationshipChange, Message] }) + @serialize({ unionTypes: [Relationship, Message] }) @validate() - public responseSource: RelationshipChange | Message; + public responseSource: Relationship | Message; @serialize() @validate() public response: Response; + @serialize() + @validate({ nullable: true }) + public responseCreationDate?: CoreDate; + public static from( value: ICreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters ): CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters { diff --git a/packages/consumption/test/core/TestUtil.ts b/packages/consumption/test/core/TestUtil.ts index 96f843e9c..f8350c09b 100644 --- a/packages/consumption/test/core/TestUtil.ts +++ b/packages/consumption/test/core/TestUtil.ts @@ -244,7 +244,7 @@ export class TestUtil { const relRequest = await to.relationships.sendRelationship({ template: templateTo, - content: requestContent ?? { + creationContent: requestContent ?? { metadata: { mycontent: "request" } } }); @@ -255,7 +255,7 @@ export class TestUtil { const pendingRelationship = syncedRelationships[0]; expect(pendingRelationship.status).toStrictEqual(RelationshipStatus.Pending); - const acceptedRelationshipFromSelf = await from.relationships.acceptChange(pendingRelationship.cache!.creationChange, {}); + const acceptedRelationshipFromSelf = await from.relationships.accept(pendingRelationship.id); expect(acceptedRelationshipFromSelf.status).toStrictEqual(RelationshipStatus.Active); // Get accepted relationship @@ -350,7 +350,7 @@ export class TestUtil { content: "request" }; - return await account.relationships.sendRelationship({ template, content }); + return await account.relationships.sendRelationship({ template, creationContent: content }); } public static async fetchRelationshipTemplateFromTokenReference(account: AccountController, tokenReference: string): Promise { diff --git a/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts b/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts index 3abc95467..e275cbdb2 100644 --- a/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts +++ b/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts @@ -1,6 +1,6 @@ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; import { IRequest, IRequestItemGroup, Request, RequestItemGroup, ResponseItem, ResponseItemGroup, ResponseItemResult } from "@nmshd/content"; -import { CoreDate, CoreId, RelationshipChangeType, TransportLoggerFactory } from "@nmshd/transport"; +import { CoreDate, CoreId, TransportLoggerFactory } from "@nmshd/transport"; import { ConsumptionIds, DecideRequestItemGroupParametersJSON, @@ -794,14 +794,14 @@ describe("IncomingRequestsController", function () { }); }); - test("can handle valid input with a RelationshipChange as responseSource", async function () { + test("can handle valid input with a Relationship as responseSource", async function () { await Given.anIncomingRequestInStatus(LocalRequestStatus.Decided); - const outgoingRelationshipCreationChange = TestObjectFactory.createOutgoingIRelationshipChange(RelationshipChangeType.Creation, context.currentIdentity); + const outgoingRelationship = TestObjectFactory.createIRelationship(); await When.iCompleteTheIncomingRequestWith({ - responseSourceObject: outgoingRelationshipCreationChange + responseSourceObject: outgoingRelationship }); await Then.theRequestMovesToStatus(LocalRequestStatus.Completed); - await Then.theResponseHasItsSourcePropertySetCorrectly({ responseSourceType: "RelationshipChange" }); + await Then.theResponseHasItsSourcePropertySetCorrectly({ responseSourceType: "Relationship" }); await Then.theChangesArePersistedInTheDatabase(); await Then.eventHasBeenPublished(IncomingRequestStatusChangedEvent, { newStatus: LocalRequestStatus.Completed @@ -981,11 +981,11 @@ describe("IncomingRequestsController", function () { ] }); - const relationshipChange = TestObjectFactory.createOutgoingIRelationshipChange(RelationshipChangeType.Creation, context.currentIdentity); + const relationship = TestObjectFactory.createIRelationship(); cnsRequest = await context.incomingRequestsController.complete({ requestId: cnsRequest.id, - responseSourceObject: relationshipChange + responseSourceObject: relationship }); expect(cnsRequest).toBeDefined(); diff --git a/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts b/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts index c75dc430c..b89d5aa8a 100644 --- a/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts +++ b/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts @@ -11,7 +11,7 @@ import { ResponseItemResult, ResponseResult } from "@nmshd/content"; -import { CoreAddress, CoreDate, CoreId, RelationshipChangeType, TransportLoggerFactory } from "@nmshd/transport"; +import { CoreAddress, CoreDate, CoreId, TransportLoggerFactory } from "@nmshd/transport"; import { ConsumptionIds, ErrorValidationResult, @@ -23,7 +23,7 @@ import { OutgoingRequestStatusChangedEvent, ValidationResult } from "../../../src"; -import { TestUtil, loggerFactory } from "../../core/TestUtil"; +import { loggerFactory, TestUtil } from "../../core/TestUtil"; import { RequestsGiven, RequestsTestsContext, RequestsThen, RequestsWhen } from "./RequestsIntegrationTest"; import { TestObjectFactory } from "./testHelpers/TestObjectFactory"; import { ITestRequestItem, TestRequestItem } from "./testHelpers/TestRequestItem"; @@ -232,23 +232,23 @@ describe("OutgoingRequestsController", function () { }); describe("CreateFromRelationshipTemplateResponse", function () { - describe("with a RelationshipCreationChange", function () { + describe("with a RelationshipCreation", function () { test("combines calls to create, sent and complete", async function () { - await When.iCreateAnOutgoingRequestFromRelationshipCreationChange(); + await When.iCreateAnOutgoingRequestFromRelationshipCreation(); await Then.theCreatedOutgoingRequestHasAllProperties(); await Then.theRequestIsInStatus(LocalRequestStatus.Completed); await Then.theRequestHasItsSourcePropertySet(); await Then.theRequestHasItsResponsePropertySetCorrectly(ResponseItemResult.Accepted); await Then.theResponseHasItsSourcePropertySetCorrectly({ - responseSourceType: "RelationshipChange" + responseSourceType: "Relationship" }); await Then.theNewRequestIsPersistedInTheDatabase(); await Then.eventsHaveBeenPublished(OutgoingRequestCreatedAndCompletedEvent); }); test("uses the id from the response for the created Local Request", async function () { - await When.iCreateAnOutgoingRequestFromRelationshipCreationChangeWith({ - responseSource: TestObjectFactory.createIncomingIRelationshipChange(RelationshipChangeType.Creation, "requestIdReceivedFromPeer"), + await When.iCreateAnOutgoingRequestFromRelationshipCreationWith({ + responseSource: TestObjectFactory.createIRelationship(), response: TestObjectFactory.createResponse("requestIdReceivedFromPeer") }); @@ -260,14 +260,14 @@ describe("OutgoingRequestsController", function () { test("create an outgoing request from relationship creation with an active relationship", async function () { await Given.anActiveRelationshipToIdentity(); - await When.iCreateAnOutgoingRequestFromRelationshipCreationChangeWith({ + await When.iCreateAnOutgoingRequestFromRelationshipCreationWith({ template: TestObjectFactory.createOutgoingIRelationshipTemplate( context.currentIdentity, RelationshipTemplateContent.from({ onNewRelationship: TestObjectFactory.createRequestWithOneItem() }) ), - responseSource: TestObjectFactory.createIncomingIRelationshipChange(RelationshipChangeType.Creation, "requestIdReceivedFromPeer"), + responseSource: TestObjectFactory.createIRelationship(), response: TestObjectFactory.createResponse("requestIdReceivedFromPeer") }); @@ -282,7 +282,7 @@ describe("OutgoingRequestsController", function () { }); test("uses the content from onExistingRelationship when the relationship exists", async function () { - await When.iCreateAnOutgoingRequestFromRelationshipCreationChangeWhenRelationshipExistsWith({ + await When.iCreateAnOutgoingRequestFromRelationshipCreationWhenRelationshipExistsWith({ responseSource: TestObjectFactory.createIncomingIMessageWithResponse(CoreAddress.from("id1"), "requestIdReceivedFromPeer"), response: TestObjectFactory.createResponse("requestIdReceivedFromPeer") }); diff --git a/packages/consumption/test/modules/requests/RequestEnd2End.test.ts b/packages/consumption/test/modules/requests/RequestEnd2End.test.ts index 5c178ba52..9b9cb89ad 100644 --- a/packages/consumption/test/modules/requests/RequestEnd2End.test.ts +++ b/packages/consumption/test/modules/requests/RequestEnd2End.test.ts @@ -1,6 +1,6 @@ /* eslint-disable jest/expect-expect */ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; -import { AcceptResponseItem, RelationshipCreationChangeRequestContent, RelationshipTemplateContent, Request, Response, ResponseWrapper } from "@nmshd/content"; +import { AcceptResponseItem, RelationshipCreationContent, RelationshipTemplateContent, Request, Response, ResponseWrapper } from "@nmshd/content"; import { AccountController, CoreDate, Message, Relationship, RelationshipTemplate, Transport } from "@nmshd/transport"; import { ConsumptionController, LocalRequest, LocalRequestStatus } from "../../../src"; import { TestUtil } from "../../core/TestUtil"; @@ -10,7 +10,7 @@ import { TestRequestItemProcessor } from "./testHelpers/TestRequestItemProcessor let connection: IDatabaseConnection; let transport: Transport; -describe("End2End Request/Response via Relationship Template/ChangeRequest", function () { +describe("End2End Request/Response via Relationship Template", function () { let sAccountController: AccountController; let sConsumptionController: ConsumptionController; let rAccountController: AccountController; @@ -23,6 +23,9 @@ describe("End2End Request/Response via Relationship Template/ChangeRequest", fun let sRelationship: Relationship; let sLocalRequest: LocalRequest; + let sCreationContent: RelationshipCreationContent; + let rCreationContent: RelationshipCreationContent; + beforeAll(async function () { connection = await TestUtil.createConnection(); transport = TestUtil.createTransport(connection); @@ -85,19 +88,18 @@ describe("End2End Request/Response via Relationship Template/ChangeRequest", fun }); }); - test("recipient: create Relationship with Response in Relationship Change", async function () { + test("recipient: create Relationship with Response in Relationship Creation Content", async function () { rRelationship = await rAccountController.relationships.sendRelationship({ template: rTemplate, - content: RelationshipCreationChangeRequestContent.from({ - response: rLocalRequest.response!.content - }) + creationContent: RelationshipCreationContent.from({ response: rLocalRequest.response!.content }) }); + rCreationContent = rRelationship.cache?.creationContent as RelationshipCreationContent; }); test("recipient: complete Local Request", async function () { rLocalRequest = await rConsumptionController.incomingRequests.complete({ requestId: rLocalRequest.id, - responseSourceObject: rRelationship.cache!.changes[0] + responseSourceObject: rRelationship }); }); @@ -107,11 +109,12 @@ describe("End2End Request/Response via Relationship Template/ChangeRequest", fun }); test("sender: create Local Request and Response from Relationship Change", async function () { - const response = (sRelationship.cache!.changes[0].request.content as RelationshipCreationChangeRequestContent).response; + sCreationContent = sRelationship.cache!.creationContent! as RelationshipCreationContent; + const response = sCreationContent.response; sLocalRequest = await sConsumptionController.outgoingRequests.createAndCompleteFromRelationshipTemplateResponse({ template: sTemplate, - responseSource: sRelationship.cache!.changes[0], + responseSource: sRelationship, response }); }); @@ -134,8 +137,8 @@ describe("End2End Request/Response via Relationship Template/ChangeRequest", fun expect(sLocalRequest.content.items[0]).toBeInstanceOf(TestRequestItem); expect(rLocalRequest.content.items[0]).toBeInstanceOf(TestRequestItem); - expect((sRelationship.cache!.changes[0].request.content as RelationshipCreationChangeRequestContent).response).toBeInstanceOf(Response); - expect((rRelationship.cache!.changes[0].request.content as RelationshipCreationChangeRequestContent).response).toBeInstanceOf(Response); + expect(sCreationContent.response).toBeInstanceOf(Response); + expect(rCreationContent.response).toBeInstanceOf(Response); expect(sLocalRequest.response!.content.items[0]).toBeInstanceOf(AcceptResponseItem); expect(rLocalRequest.response!.content.items[0]).toBeInstanceOf(AcceptResponseItem); diff --git a/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts b/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts index 635ea40d5..58b0953ed 100644 --- a/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts +++ b/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts @@ -8,12 +8,11 @@ import { CoreId, IConfigOverwrite, ICoreId, + IdentityController, IMessage, IRelationshipTemplate, - IdentityController, Message, Relationship, - RelationshipChangeType, RelationshipTemplate, SynchronizedCollection, Transport @@ -28,10 +27,10 @@ import { ICreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters, ICreateOutgoingRequestParameters, ILocalRequestSource, + IncomingRequestsController, IReceivedIncomingRequestParameters, IRequireManualDecisionOfIncomingRequestParameters, ISentOutgoingRequestParameters, - IncomingRequestsController, LocalRequest, LocalRequestSource, LocalRequestStatus, @@ -580,20 +579,18 @@ export class RequestsWhen { this.context.localRequestAfterAction = await this.context.outgoingRequestsController.create(params); } - public async iCreateAnOutgoingRequestFromRelationshipCreationChange(): Promise { - await this.iCreateAnOutgoingRequestFromRelationshipCreationChangeWith({}); + public async iCreateAnOutgoingRequestFromRelationshipCreation(): Promise { + await this.iCreateAnOutgoingRequestFromRelationshipCreationWith({}); } - public async iCreateAnOutgoingRequestFromRelationshipCreationChangeWhenRelationshipExistsWith( + public async iCreateAnOutgoingRequestFromRelationshipCreationWhenRelationshipExistsWith( params: Partial ): Promise { this.context.relationshipToReturnFromGetActiveRelationshipToIdentity = TestObjectFactory.createRelationship(); - await this.iCreateAnOutgoingRequestFromRelationshipCreationChangeWith(params); + await this.iCreateAnOutgoingRequestFromRelationshipCreationWith(params); } - public async iCreateAnOutgoingRequestFromRelationshipCreationChangeWith( - params: Partial - ): Promise { + public async iCreateAnOutgoingRequestFromRelationshipCreationWith(params: Partial): Promise { params.template ??= TestObjectFactory.createOutgoingIRelationshipTemplate( this.context.currentIdentity, RelationshipTemplateContent.from({ @@ -601,7 +598,7 @@ export class RequestsWhen { onExistingRelationship: TestObjectFactory.createRequestWithTwoItems() }) ); - params.responseSource ??= TestObjectFactory.createIncomingIRelationshipChange(RelationshipChangeType.Creation); + params.responseSource ??= TestObjectFactory.createIRelationship(); params.response ??= TestObjectFactory.createResponse(); this.context.localRequestAfterAction = await this.context.outgoingRequestsController.createAndCompleteFromRelationshipTemplateResponse( diff --git a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts index fa0eb0e8e..91ef1a7e9 100644 --- a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts +++ b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts @@ -5,7 +5,6 @@ import { IdentityAttribute, IIdentityAttribute, IRelationshipAttribute, - IRelationshipCreationChangeRequestContent, IRelationshipTemplateContent, IRequest, IResponse, @@ -28,13 +27,11 @@ import { Identity, IMessage, IRelationship, - IRelationshipChange, IRelationshipTemplate, Message, Realm, Relationship, - RelationshipChangeStatus, - RelationshipChangeType, + RelationshipAuditLogEntryReason, RelationshipStatus, RelationshipTemplate, RelationshipTemplatePublicKey @@ -58,21 +55,26 @@ export class TestObjectFactory { }), status: properties?.status ?? RelationshipStatus.Active, relationshipSecretId: properties?.relationshipSecretId ?? CoreId.from("RELSEC1"), + cachedAt: properties?.cachedAt ?? CoreDate.from("2020-01-02T00:00:00.000Z"), cache: properties?.cache ?? CachedRelationship.from({ - changes: [ + auditLog: [ { - id: CoreId.from("RELCH1"), - type: RelationshipChangeType.Creation, - status: RelationshipChangeStatus.Accepted, - relationshipId: CoreId.from("REL1"), - request: { - createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), - content: {}, - createdBy: CoreAddress.from("id1"), - createdByDevice: CoreId.from("DEV1") - } + createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), + createdBy: CoreAddress.from("id2"), + createdByDevice: CoreId.from("DVC1"), + reason: RelationshipAuditLogEntryReason.Creation, + newStatus: RelationshipStatus.Pending + }, + + { + createdAt: CoreDate.from("2020-01-02T00:00:00.000Z"), + createdBy: CoreAddress.from("id1"), + createdByDevice: CoreId.from("DVC1"), + reason: RelationshipAuditLogEntryReason.AcceptanceOfCreation, + oldStatus: RelationshipStatus.Pending, + newStatus: RelationshipStatus.Active } ], template: this.createIncomingRelationshipTemplate() @@ -277,49 +279,32 @@ export class TestObjectFactory { return RelationshipTemplate.from(this.createIncomingIRelationshipTemplate()); } - public static createIncomingIRelationshipChange(type: RelationshipChangeType, requestId?: string): IRelationshipChange { - return { - // @ts-expect-error - "@type": "RelationshipChange", - id: CoreId.from("RCH1"), - relationshipId: CoreId.from("REL1"), - type: type, - status: RelationshipChangeStatus.Pending, - request: { - createdAt: CoreDate.utc(), - createdBy: CoreAddress.from("id1"), - createdByDevice: CoreId.from("DVC1"), - content: { - "@type": "RelationshipCreationChangeRequestContent", - response: { - "@type": "Response", - result: ResponseResult.Accepted, - items: [ - { - // @ts-expect-error - "@type": "AcceptResponseItem", - result: ResponseItemResult.Accepted - } - ], - requestId: CoreId.from(requestId ?? "REQ1") - } as IResponse - } as IRelationshipCreationChangeRequestContent - } - }; - } - - public static createOutgoingIRelationshipChange(type: RelationshipChangeType, sender: CoreAddress): IRelationshipChange { + public static createIRelationship(): IRelationship { return { // @ts-expect-error - "@type": "RelationshipChange", - id: CoreId.from("RCH1"), - relationshipId: CoreId.from("REL1"), - type: type, - status: RelationshipChangeStatus.Pending, - request: { - createdAt: CoreDate.utc(), - createdBy: sender, - createdByDevice: CoreId.from("DVC1") + "@type": "Relationship", + id: CoreId.from("REL1"), + status: RelationshipStatus.Pending, + relationshipSecretId: CoreId.from("REL1"), + peer: { + address: CoreAddress.from("id2"), + publicKey: CryptoSignaturePublicKey.from({ + algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519, + publicKey: CoreBuffer.fromBase64URL("aS-A8ywidL00DfBlZySOG_1-NdSBW38uGD1il_Ymk5g") + }), + realm: Realm.Prod + }, + cache: { + template: this.createIncomingIRelationshipTemplate(), + auditLog: [ + { + createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), + createdBy: CoreAddress.from("id2"), + createdByDevice: CoreId.from("DVC1"), + reason: RelationshipAuditLogEntryReason.Creation, + newStatus: RelationshipStatus.Active + } + ] } }; } diff --git a/packages/content/src/relationships/RelationshipCreationContent.ts b/packages/content/src/relationships/RelationshipCreationContent.ts new file mode 100644 index 000000000..a1872ae5f --- /dev/null +++ b/packages/content/src/relationships/RelationshipCreationContent.ts @@ -0,0 +1,27 @@ +import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; +import { ContentJSON } from "../ContentJSON"; +import { IResponse, Response, ResponseJSON } from "../requests/response/Response"; + +export interface RelationshipCreationContentJSON extends ContentJSON { + "@type": "RelationshipCreationContent"; + response: ResponseJSON; +} + +export interface IRelationshipCreationContent extends ISerializable { + response: IResponse; +} + +@type("RelationshipCreationContent") +export class RelationshipCreationContent extends Serializable implements IRelationshipCreationContent { + @serialize() + @validate() + public response: Response; + + public static from(value: IRelationshipCreationContent | Omit): RelationshipCreationContent { + return this.fromAny(value); + } + + public override toJSON(verbose?: boolean | undefined, serializeAsString?: boolean | undefined): RelationshipCreationContentJSON { + return super.toJSON(verbose, serializeAsString) as RelationshipCreationContentJSON; + } +} diff --git a/packages/content/src/relationships/index.ts b/packages/content/src/relationships/index.ts index dcfeaed74..dba4e47a8 100644 --- a/packages/content/src/relationships/index.ts +++ b/packages/content/src/relationships/index.ts @@ -1,2 +1,3 @@ export * from "./RelationshipCreationChangeRequestContent"; +export * from "./RelationshipCreationContent"; export * from "./RelationshipTemplateContent"; diff --git a/packages/runtime/src/dataViews/DataViewExpander.ts b/packages/runtime/src/dataViews/DataViewExpander.ts index d90dc417b..3674a7a0a 100644 --- a/packages/runtime/src/dataViews/DataViewExpander.ts +++ b/packages/runtime/src/dataViews/DataViewExpander.ts @@ -79,7 +79,6 @@ import { MessageDTO, MessageWithAttachmentsDTO, RecipientDTO, - RelationshipChangeDTO, RelationshipDTO, RelationshipTemplateDTO } from "../types"; @@ -139,7 +138,7 @@ import { import { DataViewObject } from "./DataViewObject"; import { DataViewTranslateable } from "./DataViewTranslateable"; import { MessageDVO, MessageStatus, RecipientDVO } from "./transport/MessageDVO"; -import { RelationshipChangeDVO, RelationshipChangeResponseDVO, RelationshipDirection, RelationshipDVO } from "./transport/RelationshipDVO"; +import { RelationshipDirection, RelationshipDVO } from "./transport/RelationshipDVO"; export class DataViewExpander { public constructor( @@ -1563,48 +1562,6 @@ export class DataViewExpander { return await Promise.all(relationshipPromises); } - public expandRelationshipChangeDTO(relationship: RelationshipDTO, change: RelationshipChangeDTO): Promise { - const date = change.response ? change.response.createdAt : change.request.createdAt; - let isOwn = false; - if (this.identityController.isMe(CoreAddress.from(change.request.createdBy))) { - isOwn = true; - } - - let response: RelationshipChangeResponseDVO | undefined; - if (change.response) { - response = { - ...change.response, - id: `${change.id}_response`, - name: "i18n://dvo.relationshipChange.response.name", - type: "RelationshipChangeResponseDVO" - }; - } - - return Promise.resolve({ - type: "RelationshipChangeDVO", - id: change.id, - name: "", - date: date, - status: change.status, - statusText: `i18n://dvo.relationshipChange.${change.status}`, - changeType: change.type, - changeTypeText: `i18n://dvo.relationshipChange.${change.type}`, - isOwn: isOwn, - request: { - ...change.request, - id: `${change.id}_request`, - name: "i18n://dvo.relationshipChange.request.name", - type: "RelationshipChangeRequestDVO" - }, - response: response - }); - } - - public async expandRelationshipChangeDTOs(relationship: RelationshipDTO): Promise { - const changePromises = relationship.changes.map((change) => this.expandRelationshipChangeDTO(relationship, change)); - return await Promise.all(changePromises); - } - private async createRelationshipDVO(relationship: RelationshipDTO): Promise { let relationshipSetting: RelationshipSettingDVO; const settingResult = await this.consumption.settings.getSettings({ query: { reference: relationship.id } }); @@ -1642,7 +1599,7 @@ export class DataViewExpander { } let direction = RelationshipDirection.Incoming; - if (this.identityController.isMe(CoreAddress.from(relationship.changes[0].request.createdBy))) { + if (!relationship.template.isOwn) { direction = RelationshipDirection.Outgoing; } @@ -1659,7 +1616,7 @@ export class DataViewExpander { statusText = DataViewTranslateable.transport.relationshipActive; } - const changes = await this.expandRelationshipChangeDTOs(relationship); + const creationDate = relationship.auditLog[0].createdAt; let name; if (stringByType["DisplayName"]) { @@ -1680,7 +1637,7 @@ export class DataViewExpander { id: relationship.id, name: relationshipSetting.userTitle ?? name, description: relationshipSetting.userDescription ?? statusText, - date: relationship.changes[0].request.createdAt, + date: creationDate, image: "", type: "RelationshipDVO", status: relationship.status, @@ -1690,9 +1647,9 @@ export class DataViewExpander { attributeMap: attributesByType, items: expandedAttributes, nameMap: stringByType, - changes: changes, - changeCount: changes.length, - templateId: relationship.template.id + templateId: relationship.template.id, + auditLog: relationship.auditLog, + creationContent: relationship.creationContent }; } diff --git a/packages/runtime/src/dataViews/consumption/LocalRequestDVO.ts b/packages/runtime/src/dataViews/consumption/LocalRequestDVO.ts index 2d0893632..25a4c990c 100644 --- a/packages/runtime/src/dataViews/consumption/LocalRequestDVO.ts +++ b/packages/runtime/src/dataViews/consumption/LocalRequestDVO.ts @@ -32,6 +32,6 @@ export interface LocalResponseDVO extends DataViewObject { } export interface LocalResponseSourceDVO { - type: "Message" | "RelationshipChange"; + type: "Message" | "Relationship"; reference: string; } diff --git a/packages/runtime/src/dataViews/transport/RelationshipDVO.ts b/packages/runtime/src/dataViews/transport/RelationshipDVO.ts index cbbd6a36e..012c82679 100644 --- a/packages/runtime/src/dataViews/transport/RelationshipDVO.ts +++ b/packages/runtime/src/dataViews/transport/RelationshipDVO.ts @@ -1,4 +1,4 @@ -import { RelationshipChangeStatus, RelationshipChangeType } from "@nmshd/transport"; +import { RelationshipAuditLogDTO } from "../../types/transport/RelationshipDTO"; import { LocalAttributeDVO } from "../consumption"; import { DataViewObject } from "../DataViewObject"; @@ -14,8 +14,8 @@ export interface RelationshipDVO extends DataViewObject { statusText: string; isPinned: boolean; theme?: RelationshipTheme; - changes: RelationshipChangeDVO[]; - changeCount: number; + creationContent: any; + auditLog: RelationshipAuditLogDTO; items: LocalAttributeDVO[]; attributeMap: Record; nameMap: Record; @@ -28,30 +28,3 @@ export interface RelationshipTheme { backgroundColor?: string; foregroundColor?: string; } - -export interface RelationshipChangeDVO extends DataViewObject { - type: "RelationshipChangeDVO"; - request: RelationshipChangeRequestDVO; - response?: RelationshipChangeResponseDVO; - status: RelationshipChangeStatus; - statusText: string; - changeType: RelationshipChangeType; - changeTypeText: string; - isOwn: boolean; -} - -export interface RelationshipChangeRequestDVO extends DataViewObject { - type: "RelationshipChangeRequestDVO"; - createdBy: string; - createdByDevice: string; - createdAt: string; - content?: unknown; -} - -export interface RelationshipChangeResponseDVO extends DataViewObject { - type: "RelationshipChangeResponseDVO"; - createdBy: string; - createdByDevice: string; - createdAt: string; - content?: unknown; -} diff --git a/packages/runtime/src/events/EventProxy.ts b/packages/runtime/src/events/EventProxy.ts index 0d2f3cf99..19794094b 100644 --- a/packages/runtime/src/events/EventProxy.ts +++ b/packages/runtime/src/events/EventProxy.ts @@ -10,7 +10,7 @@ import { IncomingRequestStatusChangedEvent, OutgoingRequestCreatedAndCompletedEvent, OutgoingRequestCreatedEvent, - OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent, + OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent, OutgoingRequestStatusChangedEvent, OwnSharedAttributeDeletedByOwnerEvent, OwnSharedAttributeSucceededEvent, @@ -141,8 +141,8 @@ export class EventProxy { this.targetEventBus.publish(new OutgoingRequestCreatedAndCompletedEvent(event.eventTargetAddress, mappedRequest)); - if (event.data.response?.source?.type === "RelationshipChange") { - this.targetEventBus.publish(new OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent(event.eventTargetAddress, mappedRequest)); + if (event.data.response?.source?.type === "Relationship") { + this.targetEventBus.publish(new OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent(event.eventTargetAddress, mappedRequest)); } }); diff --git a/packages/runtime/src/events/consumption/OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent.ts b/packages/runtime/src/events/consumption/OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent.ts similarity index 52% rename from packages/runtime/src/events/consumption/OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent.ts rename to packages/runtime/src/events/consumption/OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent.ts index 05d5e99e7..461fe66c9 100644 --- a/packages/runtime/src/events/consumption/OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent.ts +++ b/packages/runtime/src/events/consumption/OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent.ts @@ -1,11 +1,11 @@ import { LocalRequestDTO } from "../../types"; import { DataEvent } from "../DataEvent"; -export class OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent extends DataEvent { - public static readonly namespace = "consumption.outgoingRequestFromRelationshipCreationChangeCreatedAndCompleted"; +export class OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent extends DataEvent { + public static readonly namespace = "consumption.outgoingRequestFromRelationshipCreationCreatedAndCompleted"; public constructor(eventTargetAddress: string, data: LocalRequestDTO) { - super(OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent.namespace, eventTargetAddress, data); + super(OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent.namespace, eventTargetAddress, data); if (!data.isOwn) throw new Error("Cannot create this event for an incoming Request"); } diff --git a/packages/runtime/src/events/consumption/index.ts b/packages/runtime/src/events/consumption/index.ts index 3dba1195e..6d47d61cc 100644 --- a/packages/runtime/src/events/consumption/index.ts +++ b/packages/runtime/src/events/consumption/index.ts @@ -8,7 +8,7 @@ export * from "./MailReceivedEvent"; export * from "./MessageProcessedEvent"; export * from "./OutgoingRequestCreatedAndCompletedEvent"; export * from "./OutgoingRequestCreatedEvent"; -export * from "./OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent"; +export * from "./OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent"; export * from "./OutgoingRequestStatusChangedEvent"; export * from "./OwnSharedAttributeDeletedByOwnerEvent"; export * from "./OwnSharedAttributeSucceededEvent"; diff --git a/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts b/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts index da032f63c..2de41a73e 100644 --- a/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts +++ b/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts @@ -2,8 +2,8 @@ import { ApplicationError, Result } from "@js-soft/ts-utils"; import { Inject } from "typescript-ioc"; import { RelationshipDTO } from "../../../types"; import { - AcceptRelationshipChangeRequest, - AcceptRelationshipChangeUseCase, + AcceptRelationshipRequest, + AcceptRelationshipUseCase, CreateRelationshipRequest, CreateRelationshipUseCase, GetAttributesForRelationshipRequest, @@ -12,13 +12,13 @@ import { GetRelationshipByAddressRequest, GetRelationshipByAddressUseCase, GetRelationshipRequest, - GetRelationshipUseCase, GetRelationshipsRequest, GetRelationshipsUseCase, - RejectRelationshipChangeRequest, - RejectRelationshipChangeUseCase, - RevokeRelationshipChangeRequest, - RevokeRelationshipChangeUseCase + GetRelationshipUseCase, + RejectRelationshipRequest, + RejectRelationshipUseCase, + RevokeRelationshipRequest, + RevokeRelationshipUseCase } from "../../../useCases"; export class RelationshipsFacade { @@ -27,9 +27,9 @@ export class RelationshipsFacade { @Inject private readonly getRelationshipUseCase: GetRelationshipUseCase, @Inject private readonly getRelationshipByAddressUseCase: GetRelationshipByAddressUseCase, @Inject private readonly createRelationshipUseCase: CreateRelationshipUseCase, - @Inject private readonly acceptRelationshipChangeUseCase: AcceptRelationshipChangeUseCase, - @Inject private readonly rejectRelationshipChangeUseCase: RejectRelationshipChangeUseCase, - @Inject private readonly revokeRelationshipChangeUseCase: RevokeRelationshipChangeUseCase, + @Inject private readonly acceptRelationshipUseCase: AcceptRelationshipUseCase, + @Inject private readonly rejectRelationshipUseCase: RejectRelationshipUseCase, + @Inject private readonly revokeRelationshipUseCase: RevokeRelationshipUseCase, @Inject private readonly getAttributesForRelationshipUseCase: GetAttributesForRelationshipUseCase ) {} @@ -49,16 +49,16 @@ export class RelationshipsFacade { return await this.createRelationshipUseCase.execute(request); } - public async acceptRelationshipChange(request: AcceptRelationshipChangeRequest): Promise> { - return await this.acceptRelationshipChangeUseCase.execute(request); + public async acceptRelationship(request: AcceptRelationshipRequest): Promise> { + return await this.acceptRelationshipUseCase.execute(request); } - public async rejectRelationshipChange(request: RejectRelationshipChangeRequest): Promise> { - return await this.rejectRelationshipChangeUseCase.execute(request); + public async rejectRelationship(request: RejectRelationshipRequest): Promise> { + return await this.rejectRelationshipUseCase.execute(request); } - public async revokeRelationshipChange(request: RevokeRelationshipChangeRequest): Promise> { - return await this.revokeRelationshipChangeUseCase.execute(request); + public async revokeRelationship(request: RevokeRelationshipRequest): Promise> { + return await this.revokeRelationshipUseCase.execute(request); } public async getAttributesForRelationship(request: GetAttributesForRelationshipRequest): Promise> { diff --git a/packages/runtime/src/modules/RequestModule.ts b/packages/runtime/src/modules/RequestModule.ts index 381ea0656..cef9c2f98 100644 --- a/packages/runtime/src/modules/RequestModule.ts +++ b/packages/runtime/src/modules/RequestModule.ts @@ -1,14 +1,5 @@ import { LocalRequestStatus } from "@nmshd/consumption"; -import { - RelationshipCreationChangeRequestContent, - RelationshipCreationChangeRequestContentJSON, - RelationshipTemplateContentJSON, - RequestJSON, - ResponseJSON, - ResponseResult, - ResponseWrapper, - ResponseWrapperJSON -} from "@nmshd/content"; +import { RelationshipCreationContent, RelationshipTemplateContentJSON, RequestJSON, ResponseJSON, ResponseResult, ResponseWrapper, ResponseWrapperJSON } from "@nmshd/content"; import { IncomingRequestStatusChangedEvent, MessageProcessedEvent, @@ -226,8 +217,8 @@ export class RequestModule extends RuntimeModule { return; } - const creationChangeContent = RelationshipCreationChangeRequestContent.from({ response: request.response!.content }); - const createRelationshipResult = await services.transportServices.relationships.createRelationship({ templateId, content: creationChangeContent }); + const creationContent = RelationshipCreationContent.from({ response: request.response!.content }); + const createRelationshipResult = await services.transportServices.relationships.createRelationship({ templateId, creationContent }); if (createRelationshipResult.isError) { this.logger.error(`Could not create relationship for templateId '${templateId}'. Root error:`, createRelationshipResult.error); return; @@ -236,7 +227,7 @@ export class RequestModule extends RuntimeModule { const requestId = request.id; const completeRequestResult = await services.consumptionServices.incomingRequests.complete({ requestId, - responseSourceId: createRelationshipResult.value.changes[0].id + responseSourceId: createRelationshipResult.value.id }); if (completeRequestResult.isError) { this.logger.error(`Could not complete the request '${requestId}'. Root error:`, completeRequestResult.error); @@ -291,19 +282,13 @@ export class RequestModule extends RuntimeModule { // do not trigger for templates without the correct content type if (template.content["@type"] !== "RelationshipTemplateContent") return; - const relationshipCreationChange = createdRelationship.changes[0]; - const relationshipChangeId = relationshipCreationChange.id; - // do not trigger for creation changes without the correct content type - if (relationshipCreationChange.request.content["@type"] !== "RelationshipCreationChangeRequestContent") return; - - const relationshipCreationChangeContent = relationshipCreationChange.request.content as RelationshipCreationChangeRequestContentJSON; const result = await services.consumptionServices.outgoingRequests.createAndCompleteFromRelationshipTemplateResponse({ templateId, - responseSourceId: relationshipChangeId, - response: relationshipCreationChangeContent.response + responseSourceId: createdRelationship.id, + response: createdRelationship.creationContent.response }); if (result.isError) { - this.logger.error(`Could not create and complete request for templateId '${templateId}' and changeId '${relationshipChangeId}'. Root error:`, result.error); + this.logger.error(`Could not create and complete request for templateId '${templateId}' and relationshipId '${createdRelationship.id}'. Root error:`, result.error); return; } } diff --git a/packages/runtime/src/types/consumption/LocalRequestDTO.ts b/packages/runtime/src/types/consumption/LocalRequestDTO.ts index f23d5f682..49510d8e9 100644 --- a/packages/runtime/src/types/consumption/LocalRequestDTO.ts +++ b/packages/runtime/src/types/consumption/LocalRequestDTO.ts @@ -18,7 +18,7 @@ export interface LocalRequestSourceDTO { } export interface LocalResponseSourceDTO { - type: "Message" | "RelationshipChange"; + type: "Message" | "Relationship"; reference: string; } diff --git a/packages/runtime/src/types/transport/RelationshipDTO.ts b/packages/runtime/src/types/transport/RelationshipDTO.ts index 657dfa64a..eb9e671a1 100644 --- a/packages/runtime/src/types/transport/RelationshipDTO.ts +++ b/packages/runtime/src/types/transport/RelationshipDTO.ts @@ -11,6 +11,24 @@ export enum RelationshipStatus { Terminated = "Terminated" } +export enum RelationshipAuditLogEntryReason { + Creation = "Creation", + AcceptanceOfCreation = "AcceptanceOfCreation", + RejectionOfCreation = "RejectionOfCreation", + RevocationOfCreation = "RevocationOfCreation" +} + +export interface RelationshipAuditLogEntryDTO { + createdAt: string; + createdBy: string; + createdByDevice: string; + reason: RelationshipAuditLogEntryReason; + oldStatus?: RelationshipStatus; + newStatus: RelationshipStatus; +} + +export interface RelationshipAuditLogDTO extends Array {} + export interface RelationshipDTO { id: string; template: RelationshipTemplateDTO; @@ -18,4 +36,6 @@ export interface RelationshipDTO { peer: string; peerIdentity: IdentityDTO; changes: RelationshipChangeDTO[]; + creationContent: any; + auditLog: RelationshipAuditLogDTO; } diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index 03de260a8..db108d62f 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -3054,7 +3054,7 @@ export const CompleteIncomingRequestRequest: any = { "$ref": "#/definitions/MessageIdString" }, { - "$ref": "#/definitions/RelationshipChangeIdString" + "$ref": "#/definitions/RelationshipIdString" } ] } @@ -3071,10 +3071,6 @@ export const CompleteIncomingRequestRequest: any = { "MessageIdString": { "type": "string", "pattern": "MSG[A-Za-z0-9]{17}" - }, - "RelationshipChangeIdString": { - "type": "string", - "pattern": "RCH[A-Za-z0-9]{17}" } } } @@ -5422,7 +5418,7 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq "responseSourceId": { "anyOf": [ { - "$ref": "#/definitions/RelationshipChangeIdString" + "$ref": "#/definitions/RelationshipIdString" }, { "$ref": "#/definitions/MessageIdString" @@ -5444,10 +5440,6 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq "type": "string", "pattern": "RLT[A-Za-z0-9]{17}" }, - "RelationshipChangeIdString": { - "type": "string", - "pattern": "RCH[A-Za-z0-9]{17}" - }, "MessageIdString": { "type": "string", "pattern": "MSG[A-Za-z0-9]{17}" @@ -21374,25 +21366,19 @@ export const SendMessageRequest: any = { } } -export const AcceptRelationshipChangeRequest: any = { +export const AcceptRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/AcceptRelationshipChangeRequest", + "$ref": "#/definitions/AcceptRelationshipRequest", "definitions": { - "AcceptRelationshipChangeRequest": { + "AcceptRelationshipRequest": { "type": "object", "properties": { "relationshipId": { "$ref": "#/definitions/RelationshipIdString" }, - "changeId": { - "$ref": "#/definitions/RelationshipChangeIdString" - }, - "content": {} }, "required": [ - "relationshipId", - "changeId", - "content" + "relationshipId" ], "additionalProperties": false }, @@ -21400,10 +21386,6 @@ export const AcceptRelationshipChangeRequest: any = { "type": "string", "pattern": "REL[A-Za-z0-9]{17}" }, - "RelationshipChangeIdString": { - "type": "string", - "pattern": "RCH[A-Za-z0-9]{17}" - } } } @@ -21417,11 +21399,11 @@ export const CreateRelationshipRequest: any = { "templateId": { "$ref": "#/definitions/RelationshipTemplateIdString" }, - "content": {} + "creationContent": {} }, "required": [ "templateId", - "content" + "creationContent" ], "additionalProperties": false }, @@ -21569,68 +21551,48 @@ export const GetRelationshipsRequest: any = { } } -export const RejectRelationshipChangeRequest: any = { +export const RejectRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/RejectRelationshipChangeRequest", + "$ref": "#/definitions/RejectRelationshipRequest", "definitions": { - "RejectRelationshipChangeRequest": { + "RejectRelationshipRequest": { "type": "object", "properties": { "relationshipId": { "$ref": "#/definitions/RelationshipIdString" - }, - "changeId": { - "$ref": "#/definitions/RelationshipChangeIdString" - }, - "content": {} + } }, "required": [ - "relationshipId", - "changeId", - "content" + "relationshipId" ], "additionalProperties": false }, "RelationshipIdString": { "type": "string", "pattern": "REL[A-Za-z0-9]{17}" - }, - "RelationshipChangeIdString": { - "type": "string", - "pattern": "RCH[A-Za-z0-9]{17}" } } } -export const RevokeRelationshipChangeRequest: any = { +export const RevokeRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/RevokeRelationshipChangeRequest", + "$ref": "#/definitions/RevokeRelationshipRequest", "definitions": { - "RevokeRelationshipChangeRequest": { + "RevokeRelationshipRequest": { "type": "object", "properties": { "relationshipId": { "$ref": "#/definitions/RelationshipIdString" - }, - "changeId": { - "$ref": "#/definitions/RelationshipChangeIdString" - }, - "content": {} + } }, "required": [ - "relationshipId", - "changeId", - "content" + "relationshipId" ], "additionalProperties": false }, "RelationshipIdString": { "type": "string", "pattern": "REL[A-Za-z0-9]{17}" - }, - "RelationshipChangeIdString": { - "type": "string", - "pattern": "RCH[A-Za-z0-9]{17}" } } } diff --git a/packages/runtime/src/useCases/common/validation/ValidatableStrings.ts b/packages/runtime/src/useCases/common/validation/ValidatableStrings.ts index af48ed7fa..8d3c35819 100644 --- a/packages/runtime/src/useCases/common/validation/ValidatableStrings.ts +++ b/packages/runtime/src/useCases/common/validation/ValidatableStrings.ts @@ -33,11 +33,6 @@ export type LocalDraftIdString = string; */ export type LocalSettingIdString = string; -/** - * @pattern RCH[A-Za-z0-9]{17} - */ -export type RelationshipChangeIdString = string; - /** * @pattern MSG[A-Za-z0-9]{17} */ diff --git a/packages/runtime/src/useCases/consumption/requests/CompleteIncomingRequest.ts b/packages/runtime/src/useCases/consumption/requests/CompleteIncomingRequest.ts index 593270a46..3f80156c6 100644 --- a/packages/runtime/src/useCases/consumption/requests/CompleteIncomingRequest.ts +++ b/packages/runtime/src/useCases/consumption/requests/CompleteIncomingRequest.ts @@ -1,14 +1,14 @@ import { ApplicationError, Result } from "@js-soft/ts-utils"; import { IncomingRequestsController } from "@nmshd/consumption"; -import { CoreId, IMessage, IRelationshipChange, Message, MessageController, RelationshipChange, RelationshipsController } from "@nmshd/transport"; +import { CoreId, IMessage, IRelationship, Message, MessageController, Relationship, RelationshipsController } from "@nmshd/transport"; import { Inject } from "typescript-ioc"; import { LocalRequestDTO } from "../../../types"; -import { MessageIdString, RelationshipChangeIdString, RequestIdString, RuntimeErrors, UseCase } from "../../common"; +import { MessageIdString, RelationshipIdString, RequestIdString, RuntimeErrors, UseCase } from "../../common"; import { RequestMapper } from "./RequestMapper"; export interface CompleteIncomingRequestRequest { requestId: RequestIdString; - responseSourceId?: MessageIdString | RelationshipChangeIdString; + responseSourceId?: MessageIdString | RelationshipIdString; } // class Validator extends SchemaValidator { @@ -33,7 +33,7 @@ export class CompleteIncomingRequestUseCase extends UseCase { + private async getResponseSourceObject(request: CompleteIncomingRequestRequest): Promise { if (!request.responseSourceId) return; if (request.responseSourceId.startsWith("MSG")) { @@ -43,11 +43,11 @@ export class CompleteIncomingRequestUseCase extends UseCase { +class Validator extends SchemaValidator { public constructor(@Inject schemaRepository: SchemaRepository) { - super(schemaRepository.getSchema("AcceptRelationshipChangeRequest")); + super(schemaRepository.getSchema("AcceptRelationshipRequest")); } } -export class AcceptRelationshipChangeUseCase extends UseCase { +export class AcceptRelationshipUseCase extends UseCase { public constructor( @Inject private readonly relationshipsController: RelationshipsController, @Inject private readonly accountController: AccountController, @@ -26,7 +24,7 @@ export class AcceptRelationshipChangeUseCase extends UseCase> { + protected async executeInternal(request: AcceptRelationshipRequest): Promise> { const relationship = await this.relationshipsController.getRelationship(CoreId.from(request.relationshipId)); if (!relationship) { return Result.fail(RuntimeErrors.general.recordNotFound(Relationship)); @@ -36,12 +34,7 @@ export class AcceptRelationshipChangeUseCase extends UseCase c.id.toString() === request.changeId); - if (!change) { - return Result.fail(RuntimeErrors.general.recordNotFound(RelationshipChange)); - } - - const updatedRelationship = await this.relationshipsController.acceptChange(change, request.content); + const updatedRelationship = await this.relationshipsController.accept(relationship.id); await this.accountController.syncDatawallet(); diff --git a/packages/runtime/src/useCases/transport/relationships/CreateRelationship.ts b/packages/runtime/src/useCases/transport/relationships/CreateRelationship.ts index b38b452ed..18e60b954 100644 --- a/packages/runtime/src/useCases/transport/relationships/CreateRelationship.ts +++ b/packages/runtime/src/useCases/transport/relationships/CreateRelationship.ts @@ -7,7 +7,7 @@ import { RelationshipMapper } from "./RelationshipMapper"; export interface CreateRelationshipRequest { templateId: RelationshipTemplateIdString; - content: any; + creationContent: any; } class Validator extends SchemaValidator { @@ -34,7 +34,7 @@ export class CreateRelationshipUseCase extends UseCase { +class Validator extends SchemaValidator { public constructor(@Inject schemaRepository: SchemaRepository) { - super(schemaRepository.getSchema("RejectRelationshipChangeRequest")); + super(schemaRepository.getSchema("RejectRelationshipRequest")); } } -export class RejectRelationshipChangeUseCase extends UseCase { +export class RejectRelationshipUseCase extends UseCase { public constructor( @Inject private readonly relationshipsController: RelationshipsController, @Inject private readonly accountController: AccountController, @@ -26,7 +24,7 @@ export class RejectRelationshipChangeUseCase extends UseCase> { + protected async executeInternal(request: RejectRelationshipRequest): Promise> { const relationship = await this.relationshipsController.getRelationship(CoreId.from(request.relationshipId)); if (!relationship) { return Result.fail(RuntimeErrors.general.recordNotFound(Relationship)); @@ -36,12 +34,7 @@ export class RejectRelationshipChangeUseCase extends UseCase c.id.toString() === request.changeId); - if (!change) { - return Result.fail(RuntimeErrors.general.recordNotFound(RelationshipChange)); - } - - const updatedRelationship = await this.relationshipsController.rejectChange(change, request.content); + const updatedRelationship = await this.relationshipsController.reject(relationship.id); await this.accountController.syncDatawallet(); diff --git a/packages/runtime/src/useCases/transport/relationships/RelationshipMapper.ts b/packages/runtime/src/useCases/transport/relationships/RelationshipMapper.ts index 685e4cff6..5ecdaab15 100644 --- a/packages/runtime/src/useCases/transport/relationships/RelationshipMapper.ts +++ b/packages/runtime/src/useCases/transport/relationships/RelationshipMapper.ts @@ -1,5 +1,5 @@ -import { Relationship, RelationshipChange, RelationshipChangeRequest, RelationshipChangeResponse } from "@nmshd/transport"; -import { RelationshipChangeDTO, RelationshipChangeRequestDTO, RelationshipChangeResponseDTO, RelationshipDTO } from "../../../types"; +import { Relationship, RelationshipAuditLogEntry } from "@nmshd/transport"; +import { RelationshipAuditLogEntryDTO, RelationshipAuditLogEntryReason, RelationshipChangeStatus, RelationshipChangeType, RelationshipDTO } from "../../../types"; import { RuntimeErrors } from "../../common"; import { RelationshipTemplateMapper } from "../relationshipTemplates/RelationshipTemplateMapper"; @@ -19,39 +19,49 @@ export class RelationshipMapper { publicKey: relationship.peer.publicKey.toBase64(false), realm: relationship.peer.realm }, - changes: relationship.cache.changes.map((c) => this.toRelationshipChangeDTO(c)) + auditLog: relationship.cache.auditLog.map((entry) => this.toAuditLogEntryDTO(entry)), + creationContent: relationship.cache.creationContent?.toJSON(), + changes: [ + { + id: "RCH00000000000000001", + request: { + createdAt: relationship.cache.auditLog[0].createdAt.toString(), + createdBy: relationship.cache.auditLog[0].createdBy.toString(), + createdByDevice: relationship.cache.auditLog[0].createdByDevice.toString(), + content: { ...relationship.cache.creationContent?.toJSON(), "@type": "RelationshipCreationChangeRequestContent" } + }, + status: this.getStatus(relationship.cache.auditLog), + type: RelationshipChangeType.Creation + } + ] }; } - public static toRelationshipDTOList(relationships: Relationship[]): RelationshipDTO[] { - return relationships.map((r) => this.toRelationshipDTO(r)); - } - - private static toRelationshipChangeRequestDTO(change: RelationshipChangeRequest): RelationshipChangeRequestDTO { - return { - createdBy: change.createdBy.toString(), - createdByDevice: change.createdByDevice.toString(), - createdAt: change.createdAt.toString(), - content: change.content?.toJSON() - }; + private static getStatus(auditLog: RelationshipAuditLogEntry[]): RelationshipChangeStatus { + switch (auditLog[1]?.reason) { + case RelationshipAuditLogEntryReason.AcceptanceOfCreation: + return RelationshipChangeStatus.Accepted; + case RelationshipAuditLogEntryReason.RejectionOfCreation: + return RelationshipChangeStatus.Rejected; + case RelationshipAuditLogEntryReason.RevocationOfCreation: + return RelationshipChangeStatus.Revoked; + default: + return RelationshipChangeStatus.Pending; + } } - private static toRelationshipChangeResponseDTO(change: RelationshipChangeResponse): RelationshipChangeResponseDTO { + private static toAuditLogEntryDTO(entry: RelationshipAuditLogEntry): RelationshipAuditLogEntryDTO { return { - createdBy: change.createdBy.toString(), - createdByDevice: change.createdByDevice.toString(), - createdAt: change.createdAt.toString(), - content: change.content?.toJSON() + createdAt: entry.createdAt.toString(), + createdBy: entry.createdBy.toString(), + createdByDevice: entry.createdByDevice.toString(), + reason: entry.reason, + oldStatus: entry.oldStatus, + newStatus: entry.newStatus }; } - private static toRelationshipChangeDTO(change: RelationshipChange): RelationshipChangeDTO { - return { - id: change.id.toString(), - request: this.toRelationshipChangeRequestDTO(change.request), - status: change.status, - type: change.type, - response: change.response ? this.toRelationshipChangeResponseDTO(change.response) : undefined - }; + public static toRelationshipDTOList(relationships: Relationship[]): RelationshipDTO[] { + return relationships.map((r) => this.toRelationshipDTO(r)); } } diff --git a/packages/runtime/src/useCases/transport/relationships/RevokeRelationshipChange.ts b/packages/runtime/src/useCases/transport/relationships/RevokeRelationship.ts similarity index 53% rename from packages/runtime/src/useCases/transport/relationships/RevokeRelationshipChange.ts rename to packages/runtime/src/useCases/transport/relationships/RevokeRelationship.ts index a562ed523..115e2e0f1 100644 --- a/packages/runtime/src/useCases/transport/relationships/RevokeRelationshipChange.ts +++ b/packages/runtime/src/useCases/transport/relationships/RevokeRelationship.ts @@ -1,23 +1,21 @@ import { Result } from "@js-soft/ts-utils"; -import { AccountController, CoreId, Relationship, RelationshipChange, RelationshipsController } from "@nmshd/transport"; +import { AccountController, CoreId, Relationship, RelationshipsController } from "@nmshd/transport"; import { Inject } from "typescript-ioc"; import { RelationshipDTO } from "../../../types"; -import { RelationshipChangeIdString, RelationshipIdString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common"; +import { RelationshipIdString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common"; import { RelationshipMapper } from "./RelationshipMapper"; -export interface RevokeRelationshipChangeRequest { +export interface RevokeRelationshipRequest { relationshipId: RelationshipIdString; - changeId: RelationshipChangeIdString; - content: any; } -class Validator extends SchemaValidator { +class Validator extends SchemaValidator { public constructor(@Inject schemaRepository: SchemaRepository) { - super(schemaRepository.getSchema("RevokeRelationshipChangeRequest")); + super(schemaRepository.getSchema("RevokeRelationshipRequest")); } } -export class RevokeRelationshipChangeUseCase extends UseCase { +export class RevokeRelationshipUseCase extends UseCase { public constructor( @Inject private readonly relationshipsController: RelationshipsController, @Inject private readonly accountController: AccountController, @@ -26,7 +24,7 @@ export class RevokeRelationshipChangeUseCase extends UseCase> { + protected async executeInternal(request: RevokeRelationshipRequest): Promise> { const relationship = await this.relationshipsController.getRelationship(CoreId.from(request.relationshipId)); if (!relationship) { return Result.fail(RuntimeErrors.general.recordNotFound(Relationship)); @@ -36,12 +34,7 @@ export class RevokeRelationshipChangeUseCase extends UseCase c.id.toString() === request.changeId); - if (!change) { - return Result.fail(RuntimeErrors.general.recordNotFound(RelationshipChange)); - } - - const updatedRelationship = await this.relationshipsController.revokeChange(change, request.content); + const updatedRelationship = await this.relationshipsController.revoke(relationship.id); await this.accountController.syncDatawallet(); diff --git a/packages/runtime/src/useCases/transport/relationships/index.ts b/packages/runtime/src/useCases/transport/relationships/index.ts index 3203254e0..4335a1a5d 100644 --- a/packages/runtime/src/useCases/transport/relationships/index.ts +++ b/packages/runtime/src/useCases/transport/relationships/index.ts @@ -1,9 +1,9 @@ -export * from "./AcceptRelationshipChange"; +export * from "./AcceptRelationship"; export * from "./CreateRelationship"; export * from "./GetAttributesForRelationship"; export * from "./GetRelationship"; export * from "./GetRelationshipByAddress"; export * from "./GetRelationships"; -export * from "./RejectRelationshipChange"; +export * from "./RejectRelationship"; export * from "./RelationshipMapper"; -export * from "./RevokeRelationshipChange"; +export * from "./RevokeRelationship"; diff --git a/packages/runtime/test/consumption/requests.test.ts b/packages/runtime/test/consumption/requests.test.ts index 87c6e30f1..5a0476cee 100644 --- a/packages/runtime/test/consumption/requests.test.ts +++ b/packages/runtime/test/consumption/requests.test.ts @@ -1,12 +1,11 @@ import { EventBus } from "@js-soft/ts-utils"; import { LocalRequestStatus } from "@nmshd/consumption"; -import { RelationshipCreationChangeRequestContentJSON } from "@nmshd/content"; import { CoreDate } from "@nmshd/transport"; import { ConsumptionServices, CreateOutgoingRequestRequest, OutgoingRequestCreatedEvent, - OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent, + OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent, OutgoingRequestStatusChangedEvent, TransportServices } from "../../src"; @@ -556,7 +555,7 @@ describe("Requests", () => { const result = await rConsumptionServices.incomingRequests.complete({ requestId: request.id, - responseSourceId: action === "Accept" ? relationship?.changes[0].id : undefined + responseSourceId: action === "Accept" ? relationship?.id : undefined }); expect(result).toBeSuccessful(); @@ -577,17 +576,17 @@ describe("Requests", () => { expect(syncResult).toHaveLength(1); - const sRelationshipChange = syncResult[0].changes[0]; + const sRelationship = syncResult[0]; // eslint-disable-next-line @typescript-eslint/no-unused-vars - let triggeredCompletionEvent: OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent | undefined; - sEventBus.subscribeOnce(OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent, (event) => { + let triggeredCompletionEvent: OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent | undefined; + sEventBus.subscribeOnce(OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent, (event) => { triggeredCompletionEvent = event; }); const completionResult = await sConsumptionServices.outgoingRequests.createAndCompleteFromRelationshipTemplateResponse({ - responseSourceId: sRelationshipChange.id, - response: (sRelationshipChange.request.content as RelationshipCreationChangeRequestContentJSON).response, + responseSourceId: sRelationship.id, + response: sRelationship.creationContent.response, templateId: relationship!.template.id }); diff --git a/packages/runtime/test/dataViews/RelationshipDVO.test.ts b/packages/runtime/test/dataViews/RelationshipDVO.test.ts index 709dc5856..5a641d429 100644 --- a/packages/runtime/test/dataViews/RelationshipDVO.test.ts +++ b/packages/runtime/test/dataViews/RelationshipDVO.test.ts @@ -65,30 +65,13 @@ describe("RelationshipDVO", () => { expect(dvo.name).toBe(dto.peer.substring(3, 9)); expect(dvo.description).toBe("i18n://dvo.relationship.Active"); expect(dvo.type).toBe("IdentityDVO"); - expect(dvo.date).toBe(dto.changes[0].request.createdAt); + expect(dvo.isSelf).toBe(false); expect(dvo.relationship!.id).toBe(dto.id); expect(dvo.relationship!.direction).toBe("Incoming"); expect(dvo.relationship!.status).toBe("Active"); expect(dvo.relationship!.statusText).toBe("i18n://dvo.relationship.Active"); - expect(dvo.relationship!.changeCount).toBe(1); - const change = dvo.relationship!.changes[0]; - expect(change.id).toBe(dto.changes[0].id); - expect(change.type).toBe("RelationshipChangeDVO"); - expect(change.status).toBe(dto.changes[0].status); - expect(change.statusText).toBe("i18n://dvo.relationshipChange.Accepted"); - expect(change.request.type).toBe("RelationshipChangeRequestDVO"); - expect(change.request.createdAt).toBe(dto.changes[0].request.createdAt); - expect(change.request.createdBy).toBe(dto.changes[0].request.createdBy); - expect(change.request.createdByDevice).toBe(dto.changes[0].request.createdByDevice); - expect(change.request.content).toBe(dto.changes[0].request.content); - expect(change.isOwn).toBe(false); - expect(change.response!.type).toBe("RelationshipChangeResponseDVO"); - expect(change.response!.createdAt).toBe(dto.changes[0].response!.createdAt); - expect(change.response!.createdBy).toBe(dto.changes[0].response!.createdBy); - expect(change.response!.createdByDevice).toBe(dto.changes[0].response!.createdByDevice); - expect(change.response!.content).toBe(dto.changes[0].response!.content); - expect(change.date).toBe(dto.changes[0].response!.createdAt); + expect(dvo.relationship!.templateId).toBe(dto.template.id); }); test("check the relationship dvo for the requestor", async () => { @@ -101,30 +84,13 @@ describe("RelationshipDVO", () => { expect(dvo.name).toBe(dto.peer.substring(3, 9)); expect(dvo.description).toBe("i18n://dvo.relationship.Active"); expect(dvo.type).toBe("IdentityDVO"); - expect(dvo.date).toBe(dto.changes[0].request.createdAt); + expect(dvo.isSelf).toBe(false); expect(dvo.relationship!.id).toBe(dto.id); expect(dvo.relationship!.direction).toBe("Outgoing"); expect(dvo.relationship!.status).toBe("Active"); expect(dvo.relationship!.statusText).toBe("i18n://dvo.relationship.Active"); - expect(dvo.relationship!.changeCount).toBe(1); - const change = dvo.relationship!.changes[0]; - expect(change.id).toBe(dto.changes[0].id); - expect(change.type).toBe("RelationshipChangeDVO"); - expect(change.status).toBe(dto.changes[0].status); - expect(change.statusText).toBe("i18n://dvo.relationshipChange.Accepted"); - expect(change.request.type).toBe("RelationshipChangeRequestDVO"); - expect(change.request.createdAt).toBe(dto.changes[0].request.createdAt); - expect(change.request.createdBy).toBe(dto.changes[0].request.createdBy); - expect(change.request.createdByDevice).toBe(dto.changes[0].request.createdByDevice); - expect(change.request.content).toBe(dto.changes[0].request.content); - expect(change.isOwn).toBe(true); - expect(change.response!.type).toBe("RelationshipChangeResponseDVO"); - expect(change.response!.createdAt).toBe(dto.changes[0].response!.createdAt); - expect(change.response!.createdBy).toBe(dto.changes[0].response!.createdBy); - expect(change.response!.createdByDevice).toBe(dto.changes[0].response!.createdByDevice); - expect(change.response!.content).toBe(dto.changes[0].response!.content); - expect(change.date).toBe(dto.changes[0].response!.createdAt); + expect(dvo.relationship!.templateId).toBe(dto.template.id); }); }); diff --git a/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts b/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts index a87608e16..2f40c69b7 100644 --- a/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts +++ b/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts @@ -12,7 +12,7 @@ import { import { CoreAddress } from "@nmshd/transport"; import { IncomingRequestStatusChangedEvent, - OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent, + OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent, PeerRelationshipTemplateDVO, PeerRelationshipTemplateLoadedEvent, RelationshipTemplateDTO, @@ -323,7 +323,7 @@ describe("RelationshipTemplateDVO", () => { expect(attributeResult.value).toHaveLength(4); await syncUntilHasRelationships(templator.transport); - await templator.eventBus.waitForEvent(OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent); + await templator.eventBus.waitForEvent(OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent); const requestResultTemplator = await templator.consumption.outgoingRequests.getRequests({ query: { "source.reference": requestorTemplate.id diff --git a/packages/runtime/test/lib/testUtils.ts b/packages/runtime/test/lib/testUtils.ts index a82c6893e..d59f0d61d 100644 --- a/packages/runtime/test/lib/testUtils.ts +++ b/packages/runtime/test/lib/testUtils.ts @@ -8,11 +8,11 @@ import { } from "@nmshd/consumption"; import { INotificationItem, - IRelationshipCreationChangeRequestContent, + IRelationshipCreationContent, IRelationshipTemplateContent, Notification, - RelationshipCreationChangeRequestContent, - RelationshipCreationChangeRequestContentJSON, + RelationshipCreationContent, + RelationshipCreationContentJSON, RelationshipTemplateContent, RelationshipTemplateContentJSON } from "@nmshd/content"; @@ -285,17 +285,15 @@ export async function establishRelationship(transportServices1: TransportService const createRelationshipResponse = await transportServices2.relationships.createRelationship({ templateId: template.id, - content: { a: "b" } + creationContent: { a: "b" } }); expect(createRelationshipResponse).toBeSuccessful(); const relationships = await syncUntilHasRelationships(transportServices1); expect(relationships).toHaveLength(1); - const acceptResponse = await transportServices1.relationships.acceptRelationshipChange({ - relationshipId: relationships[0].id, - changeId: relationships[0].changes[0].id, - content: { a: "b" } + const acceptResponse = await transportServices1.relationships.acceptRelationship({ + relationshipId: relationships[0].id }); expect(acceptResponse).toBeSuccessful(); @@ -308,23 +306,21 @@ export async function establishRelationshipWithContents( transportServices1: TransportServices, transportServices2: TransportServices, templateContent: RelationshipTemplateContentJSON | RelationshipTemplateContent | IRelationshipTemplateContent, - requestContent: RelationshipCreationChangeRequestContentJSON | RelationshipCreationChangeRequestContent | IRelationshipCreationChangeRequestContent + creationContent: RelationshipCreationContentJSON | RelationshipCreationContent | IRelationshipCreationContent ): Promise { const template = await exchangeTemplate(transportServices1, transportServices2, templateContent); const createRelationshipResponse = await transportServices2.relationships.createRelationship({ templateId: template.id, - content: requestContent + creationContent: creationContent }); expect(createRelationshipResponse).toBeSuccessful(); const relationships = await syncUntilHasRelationships(transportServices1); expect(relationships).toHaveLength(1); - const acceptResponse = await transportServices1.relationships.acceptRelationshipChange({ - relationshipId: relationships[0].id, - changeId: relationships[0].changes[0].id, - content: { a: "b" } + const acceptResponse = await transportServices1.relationships.acceptRelationship({ + relationshipId: relationships[0].id }); expect(acceptResponse).toBeSuccessful(); @@ -339,7 +335,7 @@ export async function ensureActiveRelationship(sTransportServices: TransportServ await establishRelationship(sTransportServices, rTransportServices); } else if (relationships[0].status === RelationshipStatus.Pending) { const relationship = relationships[0]; - await sTransportServices.relationships.acceptRelationshipChange({ relationshipId: relationship.id, changeId: relationship.changes[0].id, content: {} }); + await sTransportServices.relationships.acceptRelationship({ relationshipId: relationship.id }); await syncUntilHasRelationships(rTransportServices, 1); } diff --git a/packages/runtime/test/lib/testUtilsWithInactiveModules.ts b/packages/runtime/test/lib/testUtilsWithInactiveModules.ts index 7059b8f0f..ba2e547fc 100644 --- a/packages/runtime/test/lib/testUtilsWithInactiveModules.ts +++ b/packages/runtime/test/lib/testUtilsWithInactiveModules.ts @@ -1,4 +1,4 @@ -import { IResponse, RelationshipCreationChangeRequestContent } from "@nmshd/content"; +import { IResponse, RelationshipCreationContent } from "@nmshd/content"; import { CreateOutgoingRequestRequest, LocalRequestDTO, MessageDTO, RelationshipDTO } from "src"; import { TestRuntimeServices } from "./RuntimeServiceProvider"; import { exchangeMessageWithRequest, exchangeTemplate, syncUntilHasMessageWithResponse } from "./testUtils"; @@ -121,16 +121,16 @@ export async function exchangeTemplateAndReceiverSendsResponse( let relationship; if (actionLowerCase === "accept") { - const content = RelationshipCreationChangeRequestContent.from({ response: decidedRequest.response!.content as unknown as IResponse }); - const result = await rRuntimeServices.transport.relationships.createRelationship({ content, templateId }); + const creationContent = RelationshipCreationContent.from({ response: decidedRequest.response!.content as unknown as IResponse }); + const result = await rRuntimeServices.transport.relationships.createRelationship({ creationContent, templateId }); expect(result).toBeSuccessful(); relationship = result.value; - const rRelationshipChange = result.value.changes[0]; expect(rRelationshipChange.request.content["@type"]).toBe("RelationshipCreationChangeRequestContent"); + expect(relationship.creationContent["@type"]).toBe("RelationshipCreationContent"); } return { request, relationship }; } diff --git a/packages/runtime/test/modules/RequestModule.test.ts b/packages/runtime/test/modules/RequestModule.test.ts index 8e901d4a4..8ad5f40ab 100644 --- a/packages/runtime/test/modules/RequestModule.test.ts +++ b/packages/runtime/test/modules/RequestModule.test.ts @@ -3,6 +3,7 @@ import { GivenName, IdentityAttribute, RelationshipCreationChangeRequestContentJSON, + RelationshipCreationContentJSON, RelationshipTemplateContentJSON, ResponseItemJSON, ResponseItemResult, @@ -17,7 +18,7 @@ import { MessageProcessedEvent, MessageSentEvent, OutgoingRequestCreatedAndCompletedEvent, - OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent, + OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent, OutgoingRequestStatusChangedEvent, RelationshipStatus, RelationshipTemplateDTO, @@ -99,10 +100,8 @@ describe("RequestModule", () => { const requestId = (await rConsumptionServices.incomingRequests.getRequests({ query: { "source.reference": template.id } })).value[0].id; await rConsumptionServices.incomingRequests.accept({ requestId, items: [{ accept: true }] }); const relationship = (await syncUntilHasRelationships(sTransportServices, 1))[0]; - await sTransportServices.relationships.acceptRelationshipChange({ - relationshipId: relationship.id, - changeId: relationship.changes[0].id, - content: {} + await sTransportServices.relationships.acceptRelationship({ + relationshipId: relationship.id }); } } @@ -183,10 +182,13 @@ describe("RequestModule", () => { const relationship = relationships[0]; + const creationContent = relationship.creationContent as RelationshipCreationContentJSON; + expect(creationContent["@type"]).toBe("RelationshipCreationContent"); + const creationChangeRequestContent = relationship.changes[0].request.content as RelationshipCreationChangeRequestContentJSON; expect(creationChangeRequestContent["@type"]).toBe("RelationshipCreationChangeRequestContent"); - const response = creationChangeRequestContent.response; + const response = creationContent.response; const responseItems = response.items; expect(responseItems).toHaveLength(1); @@ -194,12 +196,12 @@ describe("RequestModule", () => { expect(responseItem["@type"]).toBe("AcceptResponseItem"); expect(responseItem.result).toBe(ResponseItemResult.Accepted); - await expect(sEventBus).toHavePublished(OutgoingRequestFromRelationshipCreationChangeCreatedAndCompletedEvent, (e) => e.data.id === response.requestId); + await expect(sEventBus).toHavePublished(OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent, (e) => e.data.id === response.requestId); const requestsResult = await sConsumptionServices.outgoingRequests.getRequest({ id: response.requestId }); expect(requestsResult).toBeSuccessful(); - await sTransportServices.relationships.acceptRelationshipChange({ relationshipId: relationship.id, changeId: relationship.changes[0].id, content: {} }); + await sTransportServices.relationships.acceptRelationship({ relationshipId: relationship.id }); await syncUntilHasRelationships(rTransportServices, 1); }); diff --git a/packages/runtime/test/transport/relationships.test.ts b/packages/runtime/test/transport/relationships.test.ts index f15f29692..034f46e42 100644 --- a/packages/runtime/test/transport/relationships.test.ts +++ b/packages/runtime/test/transport/relationships.test.ts @@ -39,19 +39,16 @@ describe("Create Relationship", () => { const createRelationshipResponse = await services2.transport.relationships.createRelationship({ templateId: templateId, - content: { a: "b" } + creationContent: { a: "b" } }); expect(createRelationshipResponse).toBeSuccessful(); const relationships1 = await syncUntilHasRelationships(services1.transport); expect(relationships1).toHaveLength(1); const relationshipId = relationships1[0].id; - const relationshipChangeId = relationships1[0].changes[0].id; - const acceptRelationshipResponse = await services1.transport.relationships.acceptRelationshipChange({ - relationshipId: relationshipId, - changeId: relationshipChangeId, - content: { a: "b" } + const acceptRelationshipResponse = await services1.transport.relationships.acceptRelationship({ + relationshipId }); expect(acceptRelationshipResponse).toBeSuccessful(); diff --git a/packages/transport/src/core/CoreErrors.ts b/packages/transport/src/core/CoreErrors.ts index df1831c10..f95a0ad61 100644 --- a/packages/transport/src/core/CoreErrors.ts +++ b/packages/transport/src/core/CoreErrors.ts @@ -1,10 +1,10 @@ import stringify from "json-stringify-safe"; -import { RelationshipChangeStatus } from "../modules/relationships/transmission/changes/RelationshipChangeStatus"; +import { RelationshipStatus } from "../modules"; import { CoreError } from "./CoreError"; class Relationships { - public wrongChangeStatus(status: RelationshipChangeStatus) { - return new CoreError("error.transport.relationships.wrongChangeStatus", `The relationship change has the wrong status (${status}) to run this operation`); + public wrongRelationshipStatus(status: RelationshipStatus) { + return new CoreError("error.transport.relationships.wrongRelationshipStatus", `The relationship has the wrong status (${status}) to run this operation`); } } diff --git a/packages/transport/src/core/backbone/BackboneIds.ts b/packages/transport/src/core/backbone/BackboneIds.ts index 4817cf031..7d8f30757 100644 --- a/packages/transport/src/core/backbone/BackboneIds.ts +++ b/packages/transport/src/core/backbone/BackboneIds.ts @@ -6,6 +6,5 @@ export class BackboneIds { public static readonly message = new CoreIdHelper("MSG", true); public static readonly relationshipTemplate = new CoreIdHelper("RLT", true); public static readonly token = new CoreIdHelper("TOK", true); - public static readonly relationshipChange = new CoreIdHelper("RCH", true); public static readonly device = new CoreIdHelper("DVC", true); } diff --git a/packages/transport/src/modules/devices/local/Device.ts b/packages/transport/src/modules/devices/local/Device.ts index d688fcf6b..a940d37aa 100644 --- a/packages/transport/src/modules/devices/local/Device.ts +++ b/packages/transport/src/modules/devices/local/Device.ts @@ -88,7 +88,9 @@ export class Device extends CoreSynchronizable implements IDevice { @serialize() public lastLoginAt?: CoreDate; - @validate() + @validate({ + customValidator: (v) => (!Object.values(DeviceType).includes(v) ? `must be one of: ${Object.values(DeviceType)}` : undefined) + }) @serialize() public type: DeviceType; diff --git a/packages/transport/src/modules/index.ts b/packages/transport/src/modules/index.ts index 8ae9cb5e5..706b7e84c 100644 --- a/packages/transport/src/modules/index.ts +++ b/packages/transport/src/modules/index.ts @@ -57,26 +57,23 @@ export * from "./messages/transmission/MessageEnvelopeRecipient"; export * from "./messages/transmission/MessageSignature"; export * from "./messages/transmission/MessageSigned"; export * from "./relationships/backbone/BackboneGetRelationships"; -export * from "./relationships/backbone/BackboneGetRelationshipsChanges"; -export * from "./relationships/backbone/BackbonePostRelationshipsChanges"; +export * from "./relationships/backbone/BackbonePostRelationship"; export * from "./relationships/backbone/RelationshipClient"; export * from "./relationships/local/CachedRelationship"; export * from "./relationships/local/Relationship"; +export * from "./relationships/local/RelationshipAuditLog"; +export * from "./relationships/local/RelationshipAuditLogEntry"; export * from "./relationships/local/SendRelationshipParameters"; export * from "./relationships/RelationshipsController"; export * from "./relationships/RelationshipSecretController"; -export * from "./relationships/transmission/changes/RelationshipChange"; -export * from "./relationships/transmission/changes/RelationshipChangeRequest"; -export * from "./relationships/transmission/changes/RelationshipChangeResponse"; -export * from "./relationships/transmission/changes/RelationshipChangeStatus"; -export * from "./relationships/transmission/changes/RelationshipChangeType"; +export * from "./relationships/transmission/RelationshipAuditLog"; export * from "./relationships/transmission/RelationshipStatus"; -export * from "./relationships/transmission/requests/RelationshipCreationChangeRequestCipher"; -export * from "./relationships/transmission/requests/RelationshipCreationChangeRequestContentWrapper"; -export * from "./relationships/transmission/requests/RelationshipCreationChangeRequestSigned"; -export * from "./relationships/transmission/responses/RelationshipCreationChangeResponseCipher"; -export * from "./relationships/transmission/responses/RelationshipCreationChangeResponseContentWrapper"; -export * from "./relationships/transmission/responses/RelationshipCreationChangeResponseSigned"; +export * from "./relationships/transmission/requests/RelationshipCreationContentCipher"; +export * from "./relationships/transmission/requests/RelationshipCreationContentSigned"; +export * from "./relationships/transmission/requests/RelationshipCreationContentWrapper"; +export * from "./relationships/transmission/responses/RelationshipCreationResponseContentCipher"; +export * from "./relationships/transmission/responses/RelationshipCreationResponseContentSigned"; +export * from "./relationships/transmission/responses/RelationshipCreationResponseContentWrapper"; export * from "./relationshipTemplates/backbone/BackboneGetRelationshipTemplates"; export * from "./relationshipTemplates/backbone/BackbonePostRelationshipTemplates"; export * from "./relationshipTemplates/backbone/RelationshipTemplateClient"; diff --git a/packages/transport/src/modules/relationships/RelationshipSecretController.ts b/packages/transport/src/modules/relationships/RelationshipSecretController.ts index 4ca286da7..8db400c60 100644 --- a/packages/transport/src/modules/relationships/RelationshipSecretController.ts +++ b/packages/transport/src/modules/relationships/RelationshipSecretController.ts @@ -88,7 +88,7 @@ export class RelationshipSecretController extends SecretController { } @log() - public async getPublicResponse(relationshipSecretId: CoreId): Promise { + public async getPublicCreationResponseContentCrypto(relationshipSecretId: CoreId): Promise { const secret = await this.loadActiveSecretByName(relationshipSecretId.toString()); if (!secret) { throw CoreErrors.general.recordNotFound(CryptoRelationshipSecrets, relationshipSecretId.toString()); @@ -134,7 +134,7 @@ export class RelationshipSecretController extends SecretController { } @log() - public async encryptRequest(relationshipSecretId: CoreId, content: Serializable | string | CoreBuffer): Promise { + public async encryptCreationContent(relationshipSecretId: CoreId, content: Serializable | string | CoreBuffer): Promise { const buffer = CoreUtil.toBuffer(content); const secrets = await this.getSecret(relationshipSecretId); @@ -158,7 +158,7 @@ export class RelationshipSecretController extends SecretController { } @log() - public async decryptRequest(relationshipSecretId: CoreId, cipher: CryptoCipher): Promise { + public async decryptCreationContent(relationshipSecretId: CoreId, cipher: CryptoCipher): Promise { const secrets = await this.getSecret(relationshipSecretId); if (!(secrets instanceof CryptoRelationshipRequestSecrets) && !(secrets instanceof CryptoRelationshipSecrets)) { diff --git a/packages/transport/src/modules/relationships/RelationshipsController.ts b/packages/transport/src/modules/relationships/RelationshipsController.ts index 412896634..a0f605bd5 100644 --- a/packages/transport/src/modules/relationships/RelationshipsController.ts +++ b/packages/transport/src/modules/relationships/RelationshipsController.ts @@ -2,7 +2,7 @@ import { ISerializable } from "@js-soft/ts-serval"; import { log } from "@js-soft/ts-utils"; import { CoreBuffer, CryptoSignature } from "@nmshd/crypto"; import { nameof } from "ts-simple-nameof"; -import { ControllerName, CoreAddress, CoreCrypto, CoreDate, CoreId, ICoreSerializable, TransportController, TransportError } from "../../core"; +import { ControllerName, CoreAddress, CoreCrypto, CoreDate, CoreId, TransportController, TransportError } from "../../core"; import { CoreErrors } from "../../core/CoreErrors"; import { CoreUtil } from "../../core/CoreUtil"; import { DbCollectionName } from "../../core/DbCollectionName"; @@ -12,24 +12,21 @@ import { AccountController } from "../accounts/AccountController"; import { Identity } from "../accounts/data/Identity"; import { RelationshipTemplate } from "../relationshipTemplates/local/RelationshipTemplate"; import { SynchronizedCollection } from "../sync/SynchronizedCollection"; -import { BackboneGetRelationshipsResponse } from "./backbone/BackboneGetRelationships"; -import { BackboneGetRelationshipsChangesResponse, BackboneGetRelationshipsChangesSingleChangeResponse } from "./backbone/BackboneGetRelationshipsChanges"; +import { BackbonePutRelationshipsResponse } from "./backbone/BackbonePutRelationship"; +import { BackboneRelationship } from "./backbone/BackboneRelationship"; import { RelationshipClient } from "./backbone/RelationshipClient"; import { CachedRelationship } from "./local/CachedRelationship"; import { Relationship } from "./local/Relationship"; +import { RelationshipAuditLog } from "./local/RelationshipAuditLog"; import { ISendRelationshipParameters, SendRelationshipParameters } from "./local/SendRelationshipParameters"; import { RelationshipSecretController } from "./RelationshipSecretController"; -import { RelationshipChange } from "./transmission/changes/RelationshipChange"; -import { RelationshipChangeResponse } from "./transmission/changes/RelationshipChangeResponse"; -import { RelationshipChangeStatus } from "./transmission/changes/RelationshipChangeStatus"; -import { RelationshipChangeType } from "./transmission/changes/RelationshipChangeType"; import { RelationshipStatus } from "./transmission/RelationshipStatus"; -import { RelationshipCreationChangeRequestCipher } from "./transmission/requests/RelationshipCreationChangeRequestCipher"; -import { RelationshipCreationChangeRequestContentWrapper } from "./transmission/requests/RelationshipCreationChangeRequestContentWrapper"; -import { RelationshipCreationChangeRequestSigned } from "./transmission/requests/RelationshipCreationChangeRequestSigned"; -import { RelationshipCreationChangeResponseCipher } from "./transmission/responses/RelationshipCreationChangeResponseCipher"; -import { RelationshipCreationChangeResponseContentWrapper } from "./transmission/responses/RelationshipCreationChangeResponseContentWrapper"; -import { RelationshipCreationChangeResponseSigned } from "./transmission/responses/RelationshipCreationChangeResponseSigned"; +import { RelationshipCreationContentCipher } from "./transmission/requests/RelationshipCreationContentCipher"; +import { RelationshipCreationContentSigned } from "./transmission/requests/RelationshipCreationContentSigned"; +import { RelationshipCreationContentWrapper } from "./transmission/requests/RelationshipCreationContentWrapper"; +import { RelationshipCreationResponseContentCipher } from "./transmission/responses/RelationshipCreationResponseContentCipher"; +import { RelationshipCreationResponseContentSigned } from "./transmission/responses/RelationshipCreationResponseContentSigned"; +import { RelationshipCreationResponseContentWrapper } from "./transmission/responses/RelationshipCreationResponseContentWrapper"; export class RelationshipsController extends TransportController { private client: RelationshipClient; @@ -52,7 +49,9 @@ export class RelationshipsController extends TransportController { public async getRelationships(query?: any): Promise { const relationshipDocs = await this.relationships.find(query); - return this.parseArray(relationshipDocs, Relationship); + const relationships = this.parseArray(relationshipDocs, Relationship); + + return relationships; } public async updateCache(ids: string[]): Promise { @@ -63,7 +62,7 @@ export class RelationshipsController extends TransportController { const resultItems = (await this.client.getRelationships({ ids })).value; const promises = []; for await (const resultItem of resultItems) { - promises.push(this.updateCacheOfExistingRelationshipInDb(resultItem.id, resultItem)); + promises.push(this.updateExistingRelationshipInDb(resultItem.id, resultItem)); } return await Promise.all(promises); } @@ -87,13 +86,14 @@ export class RelationshipsController extends TransportController { } @log() - private async updateCacheOfExistingRelationshipInDb(id: string, response?: BackboneGetRelationshipsResponse) { + private async updateExistingRelationshipInDb(id: string, response: BackboneRelationship) { const relationshipDoc = await this.relationships.read(id); if (!relationshipDoc) throw CoreErrors.general.recordNotFound(Relationship, id); const relationship = Relationship.from(relationshipDoc); await this.updateCacheOfRelationship(relationship, response); + relationship.status = response.status; await this.relationships.update(relationshipDoc, relationship); return relationship; } @@ -128,7 +128,9 @@ export class RelationshipsController extends TransportController { return; } - return Relationship.from(relationshipDoc); + const relationship = Relationship.from(relationshipDoc); + + return relationship; } public async sign(relationship: Relationship, content: CoreBuffer): Promise { @@ -152,22 +154,16 @@ export class RelationshipsController extends TransportController { const secretId = await TransportIds.relationshipSecret.generate(); - const { requestCipher, requestContent } = await this.prepareRequest(secretId, template, parameters.content); + const creationContentCipher = await this.prepareCreationContent(secretId, template, parameters.creationContent); const backboneResponse = ( await this.client.createRelationship({ - content: requestCipher.toBase64(), + creationContent: creationContentCipher.toBase64(), relationshipTemplateId: template.id.toString() }) ).value; - const newRelationship = Relationship.fromRequestSent( - CoreId.from(backboneResponse.id), - template, - template.cache.identity, - RelationshipChange.fromBackbone(backboneResponse.changes[0], requestContent.content), - secretId - ); + const newRelationship = Relationship.fromBackboneAndCreationContent(backboneResponse, template, template.cache.identity, parameters.creationContent, secretId); await this.relationships.create(newRelationship); @@ -190,19 +186,40 @@ export class RelationshipsController extends TransportController { return relationship; } - public async acceptChange(change: RelationshipChange, content?: ICoreSerializable): Promise { - return await this.completeChange(RelationshipChangeStatus.Accepted, change, content); + public async accept(relationshipId: CoreId): Promise { + const relationship = await this.getRelationship(relationshipId); + if (!relationship) { + throw CoreErrors.general.recordNotFound("Relationship", relationshipId.toString()); + } + if (relationship.status !== RelationshipStatus.Pending) { + throw CoreErrors.relationships.wrongRelationshipStatus(relationship.status); + } + return await this.completeStateTransition(RelationshipStatus.Active, relationshipId); } - public async rejectChange(change: RelationshipChange, content?: ICoreSerializable): Promise { - return await this.completeChange(RelationshipChangeStatus.Rejected, change, content); + public async reject(relationshipId: CoreId): Promise { + const relationship = await this.getRelationship(relationshipId); + if (!relationship) { + throw CoreErrors.general.recordNotFound("Relationship", relationshipId.toString()); + } + if (relationship.status !== RelationshipStatus.Pending) { + throw CoreErrors.relationships.wrongRelationshipStatus(relationship.status); + } + return await this.completeStateTransition(RelationshipStatus.Rejected, relationshipId); } - public async revokeChange(change: RelationshipChange, content?: ICoreSerializable): Promise { - return await this.completeChange(RelationshipChangeStatus.Revoked, change, content); + public async revoke(relationshipId: CoreId): Promise { + const relationship = await this.getRelationship(relationshipId); + if (!relationship) { + throw CoreErrors.general.recordNotFound("Relationship", relationshipId.toString()); + } + if (relationship.status !== RelationshipStatus.Pending) { + throw CoreErrors.relationships.wrongRelationshipStatus(relationship.status); + } + return await this.completeStateTransition(RelationshipStatus.Revoked, relationshipId); } - private async updateCacheOfRelationship(relationship: Relationship, response?: BackboneGetRelationshipsResponse) { + private async updateCacheOfRelationship(relationship: Relationship, response?: BackboneRelationship) { if (!response) { response = (await this.client.getRelationship(relationship.id.toString())).value; } @@ -212,7 +229,9 @@ export class RelationshipsController extends TransportController { relationship.setCache(cachedRelationship); } - private async decryptRelationship(response: BackboneGetRelationshipsResponse, relationshipSecretId: CoreId) { + private async decryptRelationship(response: BackboneRelationship, relationshipSecretId: CoreId) { + if (!response.creationContent) throw new TransportError("Creation content is missing"); + const templateId = CoreId.from(response.relationshipTemplateId); this._log.trace(`Parsing relationship template ${templateId} for ${response.id}...`); @@ -221,299 +240,168 @@ export class RelationshipsController extends TransportController { throw CoreErrors.general.recordNotFound(RelationshipTemplate, templateId.toString()); } - this._log.trace(`Parsing relationship changes of ${response.id}...`); + this._log.trace(`Parsing relationship creation content of ${response.id}...`); - const changesPromises = []; - for (const change of response.changes) { - switch (change.type) { - case RelationshipChangeType.Creation: - changesPromises.push(this.parseCreationChange(change, relationshipSecretId, templateId)); - break; - default: - break; - } - } - - const changes = await Promise.all(changesPromises); + const creationContent = await this.decryptCreationContent(response.creationContent, CoreAddress.from(response.from), relationshipSecretId); const cachedRelationship = CachedRelationship.from({ - changes: changes, - template: template + creationContent: creationContent.content, + template, + auditLog: RelationshipAuditLog.fromBackboneAuditLog(response.auditLog) }); return cachedRelationship; } - private async prepareRequest( - relationshipSecretId: CoreId, - template: RelationshipTemplate, - content: ISerializable - ): Promise<{ - requestCipher: RelationshipCreationChangeRequestCipher; - requestContent: RelationshipCreationChangeRequestContentWrapper; - }> { + private async prepareCreationContent(relationshipSecretId: CoreId, template: RelationshipTemplate, content: ISerializable): Promise { if (!template.cache) { throw this.newCacheEmptyError(RelationshipTemplate, template.id.toString()); } - const requestPublic = await this.secrets.createRequestorSecrets(template.cache, relationshipSecretId); + const publicCreationContentCrypto = await this.secrets.createRequestorSecrets(template.cache, relationshipSecretId); - const requestContent = RelationshipCreationChangeRequestContentWrapper.from({ - content: content, + const creationContent: RelationshipCreationContentWrapper = RelationshipCreationContentWrapper.from({ + content, identity: this.parent.identity.identity, templateId: template.id }); - const serializedRequest = requestContent.serialize(); - const buffer = CoreUtil.toBuffer(serializedRequest); + const serializedCreationContent = creationContent.serialize(); + const buffer = CoreUtil.toBuffer(serializedCreationContent); const [deviceSignature, relationshipSignature] = await Promise.all([this.parent.activeDevice.sign(buffer), this.secrets.sign(relationshipSecretId, buffer)]); - const signedRequest = RelationshipCreationChangeRequestSigned.from({ - serializedRequest: serializedRequest, - deviceSignature: deviceSignature, - relationshipSignature: relationshipSignature + const signedCreationContent = RelationshipCreationContentSigned.from({ + serializedCreationContent, + deviceSignature, + relationshipSignature }); - const cipher = await this.secrets.encryptRequest(relationshipSecretId, signedRequest); - const requestCipher = RelationshipCreationChangeRequestCipher.from({ - cipher: cipher, - publicRequestCrypto: requestPublic + const cipher = await this.secrets.encryptCreationContent(relationshipSecretId, signedCreationContent); + const creationContentCipher = RelationshipCreationContentCipher.from({ + cipher, + publicCreationContentCrypto }); - return { requestCipher, requestContent }; - } - - public async applyChangeById(changeId: string): Promise { - const relationshipChange = (await this.client.getRelationshipChange(changeId.toString())).value; - return await this.applyChange(relationshipChange); - } - - @log() - public async applyChange(change: BackboneGetRelationshipsChangesResponse): Promise { - switch (change.type) { - case RelationshipChangeType.Creation: - return await this.applyCreationChange(change); - case RelationshipChangeType.Termination: - case RelationshipChangeType.TerminationCancellation: - default: - throw CoreErrors.general.notSupported(); - } - } - - private async applyCreationChange(change: BackboneGetRelationshipsChangesResponse): Promise { - const relationshipDoc = await this.relationships.read(change.relationshipId); - if (relationshipDoc) { - // Incoming change from sync might still have an empty response - // This could happen if we've processed the change before (duplicate) - if (change.response) { - return await this.updatePendingRelationshipWithPeerResponse(relationshipDoc, change); - } - - // If we have a relationship already but no response, do nothing - return undefined; - } - - const newRelationship = await this.createNewRelationshipByIncomingCreationChange(change); - - if (change.response) { - // The request was revoked before we fetched the creation, - // thus the creation and revoke change is one - const relationshipDoc = await this.relationships.read(change.relationshipId); - return await this.updatePendingRelationshipWithPeerResponse(relationshipDoc, change); - } - return newRelationship; - } - - @log() - private async parseCreationChange(change: BackboneGetRelationshipsChangesResponse, relationshipSecretId: CoreId, templateId: CoreId) { - if (change.type !== RelationshipChangeType.Creation) this.throwWrongChangeType(change.type); - - const promises: any[] = []; - promises.push(this.decryptCreationChangeRequest(change.request, relationshipSecretId, templateId)); - - const hasRelationshipSecret = await this.secrets.hasCryptoRelationshipSecrets(relationshipSecretId); - - if (change.response && hasRelationshipSecret) { - promises.push(this.decryptCreationChangeResponse(change, relationshipSecretId)); - } - - const [requestContent, responseContent] = await Promise.all(promises); - - const creationChange = RelationshipChange.fromBackbone(change, requestContent.content, responseContent?.content); - return creationChange; + return creationContentCipher; } @log() - private async decryptCreationChangeRequest( - change: BackboneGetRelationshipsChangesSingleChangeResponse, - secretId: CoreId, - templateId: CoreId - ): Promise { - if (!change.content) throw this.newEmptyOrInvalidContentError(); - - const isOwnChange = this.parent.identity.isMe(CoreAddress.from(change.createdBy)); + private async updatePendingRelationshipWithPeerResponse(relationshipDoc: any): Promise { + const relationship = Relationship.from(relationshipDoc); - const requestCipher = RelationshipCreationChangeRequestCipher.fromBase64(change.content); - const signedRequestBuffer = await this.secrets.decryptRequest(secretId, requestCipher.cipher); - const signedRequest = RelationshipCreationChangeRequestSigned.deserialize(signedRequestBuffer.toUtf8()); + const backboneRelationship = (await this.client.getRelationship(relationship.id.toString())).value; - let relationshipSignatureValid; - if (isOwnChange) { - relationshipSignatureValid = await this.secrets.verifyOwn(secretId, CoreBuffer.fromUtf8(signedRequest.serializedRequest), signedRequest.relationshipSignature); - } else { - relationshipSignatureValid = await this.secrets.verifyPeer(secretId, CoreBuffer.fromUtf8(signedRequest.serializedRequest), signedRequest.relationshipSignature); - } - - if (!relationshipSignatureValid) { - throw CoreErrors.general.signatureNotValid("relationshipRequest"); - } + if (!(await this.secrets.hasCryptoRelationshipSecrets(relationship.relationshipSecretId)) && backboneRelationship.creationResponseContent) { + const creationResponseContent = backboneRelationship.creationResponseContent; + const cipher = RelationshipCreationResponseContentCipher.fromBase64(creationResponseContent); - const requestContent = RelationshipCreationChangeRequestContentWrapper.deserialize(signedRequest.serializedRequest); - if (!requestContent.templateId.equals(templateId)) { - throw new TransportError("The relationship request contains a wrong template id."); + await this.secrets.convertSecrets(relationship.relationshipSecretId, cipher.publicCreationResponseContentCrypto); } + relationship.cache!.auditLog = RelationshipAuditLog.fromBackboneAuditLog(backboneRelationship.auditLog); + relationship.status = backboneRelationship.status; - return requestContent; + await this.relationships.update(relationshipDoc, relationship); + return relationship; } @log() - private async decryptCreationChangeResponse( - change: BackboneGetRelationshipsChangesResponse, - relationshipSecretId: CoreId - ): Promise { - if (!change.response) throw this.newChangeResponseMissingError(change.id); - - if (change.type !== RelationshipChangeType.Creation) this.throwWrongChangeType(change.type); - - if (!change.response.content) { - throw this.newEmptyOrInvalidContentError(change); - } + private async decryptCreationContent(backboneCreationContent: string, creationContentCreator: CoreAddress, secretId: CoreId): Promise { + const isOwnContent = this.parent.identity.isMe(creationContentCreator); - const isOwnChange = this.parent.identity.isMe(CoreAddress.from(change.response.createdBy)); - - const cipher = RelationshipCreationChangeResponseCipher.fromBase64(change.response.content); - let signedResponseBuffer; - if (change.status !== RelationshipChangeStatus.Revoked) { - if (isOwnChange) { - signedResponseBuffer = await this.secrets.decryptOwn(relationshipSecretId, cipher.cipher); - } else { - signedResponseBuffer = await this.secrets.decryptPeer(relationshipSecretId, cipher.cipher, true); - } - } else { - signedResponseBuffer = await this.secrets.decryptRequest(relationshipSecretId, cipher.cipher); - } + const creationContentCipher = RelationshipCreationContentCipher.fromBase64(backboneCreationContent); + const signedCreationContentBuffer = await this.secrets.decryptCreationContent(secretId, creationContentCipher.cipher); + const signedCreationContent = RelationshipCreationContentSigned.deserialize(signedCreationContentBuffer.toUtf8()); - const signedResponse = RelationshipCreationChangeResponseSigned.deserialize(signedResponseBuffer.toUtf8()); let relationshipSignatureValid; - if (isOwnChange) { + if (isOwnContent) { relationshipSignatureValid = await this.secrets.verifyOwn( - relationshipSecretId, - CoreBuffer.fromUtf8(signedResponse.serializedResponse), - signedResponse.relationshipSignature + secretId, + CoreBuffer.fromUtf8(signedCreationContent.serializedCreationContent), + signedCreationContent.relationshipSignature ); } else { relationshipSignatureValid = await this.secrets.verifyPeer( - relationshipSecretId, - CoreBuffer.fromUtf8(signedResponse.serializedResponse), - signedResponse.relationshipSignature + secretId, + CoreBuffer.fromUtf8(signedCreationContent.serializedCreationContent), + signedCreationContent.relationshipSignature ); } if (!relationshipSignatureValid) { - throw CoreErrors.general.signatureNotValid("relationshipResponse"); + throw CoreErrors.general.signatureNotValid("relationshipCreationContent"); } - const responseContent = RelationshipCreationChangeResponseContentWrapper.deserialize(signedResponse.serializedResponse); + const creationContent = RelationshipCreationContentWrapper.deserialize(signedCreationContent.serializedCreationContent); - if (!responseContent.relationshipId.equals(change.relationshipId)) { - throw new TransportError("The relationship response contains a wrong relationship id."); - } - return responseContent; + return creationContent; } @log() - private async updatePendingRelationshipWithPeerResponse(relationshipDoc: any, change: BackboneGetRelationshipsChangesResponse): Promise { - const relationship = Relationship.from(relationshipDoc); + private async createNewRelationshipByIncomingCreation(relationshipId: string): Promise { + const backboneRelationship = (await this.client.getRelationship(relationshipId)).value; + if (!backboneRelationship.creationContent) throw new TransportError("Creation content is missing"); - if (relationship.status !== RelationshipStatus.Pending) { - this.log.debug("Trying to update non-pending relationship with creation change", change); - return; - } + const templateId = CoreId.from(backboneRelationship.relationshipTemplateId); + const template = await this.parent.relationshipTemplates.getRelationshipTemplate(templateId); - if (!relationship.cache) { - await this.updateCacheOfRelationship(relationship, undefined); - } + if (!template) throw CoreErrors.general.recordNotFound(RelationshipTemplate, templateId.toString()); + if (!template.cache) throw this.newCacheEmptyError(RelationshipTemplate, template.id.toString()); - if (!change.response) throw this.newChangeResponseMissingError(change.id); + const secretId = await TransportIds.relationshipSecret.generate(); + const creationContentCipher = RelationshipCreationContentCipher.fromBase64(backboneRelationship.creationContent); + await this.secrets.createTemplatorSecrets(secretId, template.cache, creationContentCipher.publicCreationContentCrypto); - if (!change.response.content) { - throw this.newEmptyOrInvalidContentError(change); - } + const creationContent = await this.decryptCreationContent(backboneRelationship.creationContent, CoreAddress.from(backboneRelationship.from), secretId); + const relationship = Relationship.fromBackboneAndCreationContent(backboneRelationship, template, creationContent.identity, creationContent.content, secretId); - const cipher = RelationshipCreationChangeResponseCipher.fromBase64(change.response.content); + await this.relationships.create(relationship); + return relationship; + } - if (change.status !== RelationshipChangeStatus.Revoked) { - if (!cipher.publicResponseCrypto) { - throw new TransportError("The response crypto is missing."); + public async applyRelationshipStatusChangedEvent(relationshipId: string): Promise { + let relationshipDoc = await this.relationships.read(relationshipId); + if (!relationshipDoc) { + const newRelationship = await this.createNewRelationshipByIncomingCreation(relationshipId); + if (newRelationship.status === RelationshipStatus.Pending) { + return newRelationship; } - await this.secrets.convertSecrets(relationship.relationshipSecretId, cipher.publicResponseCrypto); + // this path is for a revocation that is processed before its corresponding creation + relationshipDoc = await this.relationships.read(relationshipId); } - const responseContent = await this.decryptCreationChangeResponse(change, relationship.relationshipSecretId); - - const response = RelationshipChangeResponse.fromBackbone(change.response, responseContent.content); - - if (!relationship.cache) { - throw this.newCacheEmptyError(Relationship, relationship.id.toString()); - } - relationship.cache.changes[0].status = change.status; - switch (change.status) { - case RelationshipChangeStatus.Accepted: - relationship.toActive(response); - break; - case RelationshipChangeStatus.Rejected: - relationship.toRejected(response); - break; - case RelationshipChangeStatus.Revoked: - relationship.toRevoked(response); - break; - default: - throw CoreErrors.general.notSupported(); - } - - await this.relationships.update(relationshipDoc, relationship); - return relationship; + return await this.updatePendingRelationshipWithPeerResponse(relationshipDoc); } - @log() - private async createNewRelationshipByIncomingCreationChange(change: BackboneGetRelationshipsChangesResponse): Promise { - const backboneRelationship = (await this.client.getRelationship(change.relationshipId)).value; + private async prepareCreationResponseContent(relationship: Relationship) { + const publicCreationResponseContentCrypto = await this.secrets.getPublicCreationResponseContentCrypto(relationship.relationshipSecretId); - const templateId = CoreId.from(backboneRelationship.relationshipTemplateId); - const template = await this.parent.relationshipTemplates.getRelationshipTemplate(templateId); + const creationResponseContent = RelationshipCreationResponseContentWrapper.from({ relationshipId: relationship.id }); - if (!template) throw CoreErrors.general.recordNotFound(RelationshipTemplate, templateId.toString()); - if (!template.cache) throw this.newCacheEmptyError(RelationshipTemplate, template.id.toString()); - if (!change.request.content) throw this.newEmptyOrInvalidContentError(change); + const serializedCreationResponseContent = creationResponseContent.serialize(); + const buffer = CoreUtil.toBuffer(serializedCreationResponseContent); - const secretId = await TransportIds.relationshipSecret.generate(); - const requestCipher = RelationshipCreationChangeRequestCipher.fromBase64(change.request.content); - await this.secrets.createTemplatorSecrets(secretId, template.cache, requestCipher.publicRequestCrypto); + const [deviceSignature, relationshipSignature] = await Promise.all([this.parent.activeDevice.sign(buffer), this.secrets.sign(relationship.relationshipSecretId, buffer)]); - const requestContent = await this.decryptCreationChangeRequest(backboneRelationship.changes[0].request, secretId, templateId); - const relationshipChange = RelationshipChange.fromBackbone(change, requestContent.content); + const signedCreationResponseContent = RelationshipCreationResponseContentSigned.from({ + serializedCreationResponseContent, + deviceSignature, + relationshipSignature + }); - const relationship = Relationship.fromCreationChangeReceived(backboneRelationship, template, requestContent.identity, relationshipChange, secretId); + const cipher = await this.secrets.encrypt(relationship.relationshipSecretId, signedCreationResponseContent); + const creationResponseContentCipher = RelationshipCreationResponseContentCipher.from({ + cipher, + publicCreationResponseContentCrypto + }); - await this.relationships.create(relationship); - return relationship; + return creationResponseContentCipher.toBase64(); } @log() - private async completeChange(targetStatus: RelationshipChangeStatus, change: RelationshipChange, content?: ISerializable) { - const relationshipDoc = await this.relationships.read(change.relationshipId.toString()); + private async completeStateTransition(targetStatus: RelationshipStatus, id: CoreId) { + const relationshipDoc = await this.relationships.read(id.toString()); if (!relationshipDoc) { - throw CoreErrors.general.recordNotFound(Relationship, change.relationshipId.toString()); + throw CoreErrors.general.recordNotFound(Relationship, id.toString()); } const relationship = Relationship.from(relationshipDoc); @@ -523,48 +411,30 @@ export class RelationshipsController extends TransportController { } if (!relationship.cache) { - throw this.newCacheEmptyError(Relationship, relationship.id.toString()); - } - - const queriedChange = relationship.cache.changes.find((r) => r.id.toString() === change.id.toString()); - if (!queriedChange) { - throw CoreErrors.general.recordNotFound(RelationshipChange, change.id.toString()); + throw this.newCacheEmptyError(Relationship, id.toString()); } - if (queriedChange.status !== RelationshipChangeStatus.Pending) { - throw CoreErrors.relationships.wrongChangeStatus(queriedChange.status); - } - - let encryptedContent; - if (content) { - encryptedContent = - targetStatus === RelationshipChangeStatus.Revoked - ? await this.encryptRevokeContent(relationship, content) - : await this.encryptAcceptRejectContent(relationship, content); - } - - let backboneResponse: BackboneGetRelationshipsResponse; + let backboneResponse: BackbonePutRelationshipsResponse; switch (targetStatus) { - case RelationshipChangeStatus.Accepted: - backboneResponse = (await this.client.acceptRelationshipChange(relationship.id.toString(), change.id.toString(), encryptedContent)).value; + case RelationshipStatus.Active: + const encryptedContent = await this.prepareCreationResponseContent(relationship); + + backboneResponse = (await this.client.acceptRelationship(id.toString(), { creationResponseContent: encryptedContent })).value; break; - case RelationshipChangeStatus.Rejected: - backboneResponse = (await this.client.rejectRelationshipChange(relationship.id.toString(), change.id.toString(), encryptedContent)).value; + case RelationshipStatus.Rejected: + backboneResponse = (await this.client.rejectRelationship(id.toString())).value; break; - case RelationshipChangeStatus.Revoked: - backboneResponse = (await this.client.revokeRelationshipChange(relationship.id.toString(), change.id.toString(), encryptedContent)).value; + case RelationshipStatus.Revoked: + backboneResponse = (await this.client.revokeRelationship(id.toString())).value; break; default: throw new TransportError("target change status not supported"); } - const backboneChange = backboneResponse.changes[backboneResponse.changes.length - 1]; - - queriedChange.response = RelationshipChangeResponse.fromBackbone(backboneResponse.changes[backboneResponse.changes.length - 1].response!, content); - queriedChange.status = backboneChange.status; relationship.status = backboneResponse.status; + relationship.cache.auditLog = RelationshipAuditLog.fromBackboneAuditLog(backboneResponse.auditLog); await this.relationships.update(relationshipDoc, relationship); @@ -572,69 +442,4 @@ export class RelationshipsController extends TransportController { return relationship; } - - private async encryptRevokeContent(relationship: Relationship, content: ICoreSerializable) { - const responseContent = RelationshipCreationChangeResponseContentWrapper.from({ - relationshipId: relationship.id, - content: content - }); - - const serializedResponse = responseContent.serialize(); - const buffer = CoreUtil.toBuffer(serializedResponse); - - const [deviceSignature, relationshipSignature] = await Promise.all([this.parent.activeDevice.sign(buffer), this.secrets.sign(relationship.relationshipSecretId, buffer)]); - - const signedResponse = RelationshipCreationChangeResponseSigned.from({ - serializedResponse: serializedResponse, - deviceSignature: deviceSignature, - relationshipSignature: relationshipSignature - }); - - const cipher = await this.secrets.encryptRequest(relationship.relationshipSecretId, signedResponse); - const responseCipher = RelationshipCreationChangeResponseCipher.from({ - cipher: cipher - }); - - return responseCipher.toBase64(); - } - - private async encryptAcceptRejectContent(relationship: Relationship, content: ICoreSerializable) { - const publicResponseCrypto = await this.secrets.getPublicResponse(relationship.relationshipSecretId); - - const responseContent = RelationshipCreationChangeResponseContentWrapper.from({ - relationshipId: relationship.id, - content: content - }); - - const serializedResponse = responseContent.serialize(); - const buffer = CoreUtil.toBuffer(serializedResponse); - - const [deviceSignature, relationshipSignature] = await Promise.all([this.parent.activeDevice.sign(buffer), this.secrets.sign(relationship.relationshipSecretId, buffer)]); - - const signedResponse = RelationshipCreationChangeResponseSigned.from({ - serializedResponse: serializedResponse, - deviceSignature: deviceSignature, - relationshipSignature: relationshipSignature - }); - - const cipher = await this.secrets.encrypt(relationship.relationshipSecretId, signedResponse); - const responseCipher = RelationshipCreationChangeResponseCipher.from({ - cipher: cipher, - publicResponseCrypto: publicResponseCrypto - }); - - return responseCipher.toBase64(); - } - - private throwWrongChangeType(type: RelationshipChangeType) { - throw new TransportError(`The relationship change has the wrong type (${type}) to run this operation`); - } - - private newChangeResponseMissingError(changeId: string) { - return new TransportError(`The response of the relationship change (${changeId}) is missing`); - } - - private newEmptyOrInvalidContentError(change?: RelationshipChange | BackboneGetRelationshipsChangesResponse) { - return new TransportError(`The content property of the relationship change ${change?.id} is missing or invalid`); - } } diff --git a/packages/transport/src/modules/relationships/backbone/BackboneGetRelationships.ts b/packages/transport/src/modules/relationships/backbone/BackboneGetRelationships.ts index 11e002ece..752ac6fb3 100644 --- a/packages/transport/src/modules/relationships/backbone/BackboneGetRelationships.ts +++ b/packages/transport/src/modules/relationships/backbone/BackboneGetRelationships.ts @@ -1,19 +1,10 @@ -import { RelationshipStatus } from "../transmission/RelationshipStatus"; -import { BackboneGetRelationshipsChangesResponse } from "./BackboneGetRelationshipsChanges"; +import { BackboneRelationship } from "./BackboneRelationship"; export interface BackboneGetRelationshipsRequest { ids: string[]; } -export interface BackboneGetRelationshipsResponse { - id: string; - relationshipTemplateId: string; - from: string; - to: string; - changes: BackboneGetRelationshipsChangesResponse[]; - createdAt: string; - status: RelationshipStatus; -} +export type BackboneGetRelationshipResponse = BackboneRelationship; export interface BackboneGetRelationshipsDateRange { from?: T; diff --git a/packages/transport/src/modules/relationships/backbone/BackboneGetRelationshipsChanges.ts b/packages/transport/src/modules/relationships/backbone/BackboneGetRelationshipsChanges.ts deleted file mode 100644 index 0cb75f017..000000000 --- a/packages/transport/src/modules/relationships/backbone/BackboneGetRelationshipsChanges.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { RelationshipChangeStatus } from "../transmission/changes/RelationshipChangeStatus"; -import { RelationshipChangeType } from "../transmission/changes/RelationshipChangeType"; - -export interface BackboneGetRelationshipsChangesRequest { - ids: string[]; -} -export interface BackboneGetRelationshipsChangesResponse { - id: string; - relationshipId: string; - request: BackboneGetRelationshipsChangesSingleChangeResponse; - response: BackboneGetRelationshipsChangesSingleChangeResponse | null; - status: RelationshipChangeStatus; - type: RelationshipChangeType; -} -export interface BackboneGetRelationshipsChangesSingleChangeResponse { - createdBy: string; - createdByDevice: string; - createdAt: string; - content: string | null; -} -export interface BackboneGetRelationshipsChangesDateRange { - from?: T; - to?: T; -} diff --git a/packages/transport/src/modules/relationships/backbone/BackbonePostRelationship.ts b/packages/transport/src/modules/relationships/backbone/BackbonePostRelationship.ts new file mode 100644 index 000000000..ab03abe86 --- /dev/null +++ b/packages/transport/src/modules/relationships/backbone/BackbonePostRelationship.ts @@ -0,0 +1,8 @@ +import { BackboneRelationship } from "./BackboneRelationship"; + +export interface BackbonePostRelationshipsRequest { + relationshipTemplateId: string; + creationContent: string; +} + +export type BackbonePostRelationshipsResponse = Omit; diff --git a/packages/transport/src/modules/relationships/backbone/BackbonePostRelationshipsChanges.ts b/packages/transport/src/modules/relationships/backbone/BackbonePostRelationshipsChanges.ts deleted file mode 100644 index a3941dfbc..000000000 --- a/packages/transport/src/modules/relationships/backbone/BackbonePostRelationshipsChanges.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RelationshipChangeType } from "../transmission/changes/RelationshipChangeType"; - -export interface BackbonePostRelationshipsRequest { - relationshipTemplateId: string; - content: any; -} - -export interface BackbonePostRelationshipsChangesRequest { - type: RelationshipChangeType; -} diff --git a/packages/transport/src/modules/relationships/backbone/BackbonePutRelationship.ts b/packages/transport/src/modules/relationships/backbone/BackbonePutRelationship.ts new file mode 100644 index 000000000..f3a23cd55 --- /dev/null +++ b/packages/transport/src/modules/relationships/backbone/BackbonePutRelationship.ts @@ -0,0 +1,7 @@ +import { BackboneRelationship } from "./BackboneRelationship"; + +export interface BackboneAcceptRelationshipsRequest { + creationResponseContent: string; +} + +export type BackbonePutRelationshipsResponse = Omit; diff --git a/packages/transport/src/modules/relationships/backbone/BackboneRelationship.ts b/packages/transport/src/modules/relationships/backbone/BackboneRelationship.ts new file mode 100644 index 000000000..da60d34c6 --- /dev/null +++ b/packages/transport/src/modules/relationships/backbone/BackboneRelationship.ts @@ -0,0 +1,15 @@ +import { BackboneRelationshipAuditLog } from "../transmission/RelationshipAuditLog"; +import { RelationshipStatus } from "../transmission/RelationshipStatus"; + +export interface BackboneRelationship { + id: string; + relationshipTemplateId: string; + from: string; + to: string; + + createdAt: string; + status: RelationshipStatus; + auditLog: BackboneRelationshipAuditLog; + creationContent?: string; + creationResponseContent?: string; +} diff --git a/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts b/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts index 06cf5d1c0..5a58729d6 100644 --- a/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts +++ b/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts @@ -1,51 +1,32 @@ import { ClientResult } from "../../../core/backbone/ClientResult"; import { Paginator } from "../../../core/backbone/Paginator"; import { RESTClientAuthenticate } from "../../../core/backbone/RESTClientAuthenticate"; -import { BackboneGetRelationshipsRequest, BackboneGetRelationshipsResponse } from "./BackboneGetRelationships"; -import { BackboneGetRelationshipsChangesRequest, BackboneGetRelationshipsChangesResponse } from "./BackboneGetRelationshipsChanges"; -import { BackbonePostRelationshipsChangesRequest, BackbonePostRelationshipsRequest } from "./BackbonePostRelationshipsChanges"; +import { BackboneGetRelationshipResponse, BackboneGetRelationshipsRequest } from "./BackboneGetRelationships"; +import { BackbonePostRelationshipsRequest, BackbonePostRelationshipsResponse } from "./BackbonePostRelationship"; +import { BackboneAcceptRelationshipsRequest, BackbonePutRelationshipsResponse } from "./BackbonePutRelationship"; export class RelationshipClient extends RESTClientAuthenticate { - public async createRelationship(request: BackbonePostRelationshipsRequest): Promise> { - return await this.post("/api/v1/Relationships", request); + public async createRelationship(request: BackbonePostRelationshipsRequest): Promise> { + return await this.post("/api/v1/Relationships", request); } - public async createRelationshipChange(id: string, request: BackbonePostRelationshipsChangesRequest): Promise> { - return await this.post(`/api/v1/Relationships/${id}/Changes`, request); + public async acceptRelationship(relationshipId: string, request: BackboneAcceptRelationshipsRequest): Promise> { + return await this.put(`/api/v1/Relationships/${relationshipId}/Accept`, request); } - public async acceptRelationshipChange(relationshipId: string, changeId: string, content?: any): Promise> { - return await this.put(`/api/v1/Relationships/${relationshipId}/Changes/${changeId}/Accept`, { - content: content - }); + public async rejectRelationship(relationshipId: string): Promise> { + return await this.put(`/api/v1/Relationships/${relationshipId}/Reject`, {}); } - public async rejectRelationshipChange(relationshipId: string, changeId: string, content?: any): Promise> { - return await this.put(`/api/v1/Relationships/${relationshipId}/Changes/${changeId}/Reject`, { - content: content - }); + public async revokeRelationship(relationshipId: string): Promise> { + return await this.put(`/api/v1/Relationships/${relationshipId}/Revoke`, {}); } - public async revokeRelationshipChange(relationshipId: string, changeId: string, content?: any): Promise> { - return await this.put(`/api/v1/Relationships/${relationshipId}/Changes/${changeId}/Revoke`, { - content: content - }); + public async getRelationships(request?: BackboneGetRelationshipsRequest): Promise>> { + return await this.getPaged("/api/v1/Relationships", request); } - public async getRelationships(request?: BackboneGetRelationshipsRequest): Promise>> { - return await this.getPaged("/api/v1/Relationships", request); - } - - public async getRelationship(relationshipId: string): Promise> { - return await this.get(`/api/v1/Relationships/${relationshipId}`); - } - - public async getRelationshipChanges(request?: BackboneGetRelationshipsChangesRequest): Promise>> { - return await this.getPaged("/api/v1/Relationships/Changes", request); - } - - public async getRelationshipChange(relationshipChangeId: string): Promise> { - const change = await this.get(`/api/v1/Relationships/Changes/${relationshipChangeId}`); - return change; + public async getRelationship(relationshipId: string): Promise> { + return await this.get(`/api/v1/Relationships/${relationshipId}`); } } diff --git a/packages/transport/src/modules/relationships/local/CachedRelationship.ts b/packages/transport/src/modules/relationships/local/CachedRelationship.ts index 507b96949..142c5c808 100644 --- a/packages/transport/src/modules/relationships/local/CachedRelationship.ts +++ b/packages/transport/src/modules/relationships/local/CachedRelationship.ts @@ -1,14 +1,15 @@ -import { serialize, type, validate } from "@js-soft/ts-serval"; +import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; import { CoreDate, CoreSerializable, ICoreDate, ICoreSerializable } from "../../../core"; import { IRelationshipTemplate, RelationshipTemplate } from "../../relationshipTemplates/local/RelationshipTemplate"; -import { IRelationshipChange, RelationshipChange } from "../transmission/changes/RelationshipChange"; +import { IRelationshipAuditLogEntry, RelationshipAuditLogEntry } from "./RelationshipAuditLogEntry"; export interface ICachedRelationship extends ICoreSerializable { template: IRelationshipTemplate; - changes: IRelationshipChange[]; + creationContent?: ISerializable; lastMessageSentAt?: ICoreDate; lastMessageReceivedAt?: ICoreDate; + auditLog: IRelationshipAuditLogEntry[]; } @type("CachedRelationship") @@ -17,9 +18,9 @@ export class CachedRelationship extends CoreSerializable implements ICachedRelat @serialize() public template: RelationshipTemplate; - @validate() - @serialize({ type: RelationshipChange }) - public changes: RelationshipChange[]; + @validate({ nullable: true }) + @serialize() + public creationContent?: Serializable; @validate({ nullable: true }) @serialize() @@ -29,9 +30,9 @@ export class CachedRelationship extends CoreSerializable implements ICachedRelat @serialize() public lastMessageReceivedAt?: CoreDate; - public get creationChange(): RelationshipChange { - return this.changes[0]; - } + @validate() + @serialize({ type: RelationshipAuditLogEntry }) + public auditLog: RelationshipAuditLogEntry[]; public static from(value: ICachedRelationship): CachedRelationship { return this.fromAny(value); diff --git a/packages/transport/src/modules/relationships/local/Relationship.ts b/packages/transport/src/modules/relationships/local/Relationship.ts index 6ddaa5454..2cb95082d 100644 --- a/packages/transport/src/modules/relationships/local/Relationship.ts +++ b/packages/transport/src/modules/relationships/local/Relationship.ts @@ -1,13 +1,12 @@ -import { serialize, type, validate } from "@js-soft/ts-serval"; +import { ISerializable, serialize, type, validate } from "@js-soft/ts-serval"; import { nameof } from "ts-simple-nameof"; import { CoreDate, CoreId, CoreSynchronizable, ICoreId, ICoreSynchronizable, TransportError } from "../../../core"; -import { IIdentity, Identity } from "../../accounts/data/Identity"; +import { Identity, IIdentity } from "../../accounts/data/Identity"; import { IRelationshipTemplate } from "../../relationshipTemplates/local/RelationshipTemplate"; -import { BackboneGetRelationshipsResponse } from "../backbone/BackboneGetRelationships"; +import { BackboneGetRelationshipResponse } from "../backbone/BackboneGetRelationships"; import { RelationshipStatus } from "../transmission/RelationshipStatus"; -import { IRelationshipChange } from "../transmission/changes/RelationshipChange"; -import { RelationshipChangeResponse } from "../transmission/changes/RelationshipChangeResponse"; import { CachedRelationship, ICachedRelationship } from "./CachedRelationship"; +import { RelationshipAuditLog } from "./RelationshipAuditLog"; export interface IRelationship extends ICoreSynchronizable { relationshipSecretId: ICoreId; @@ -72,32 +71,17 @@ export class Relationship extends CoreSynchronizable implements IRelationship { return json; } - public static fromRequestSent(id: CoreId, template: IRelationshipTemplate, peer: IIdentity, creationChange: IRelationshipChange, relationshipSecretId: CoreId): Relationship { - const cache = CachedRelationship.from({ - changes: [creationChange], - template: template - }); - - return Relationship.from({ - id: id, - peer: peer, - status: RelationshipStatus.Pending, - cache: cache, - cachedAt: CoreDate.utc(), - relationshipSecretId: relationshipSecretId - }); - } - - public static fromCreationChangeReceived( - response: BackboneGetRelationshipsResponse, + public static fromBackboneAndCreationContent( + response: BackboneGetRelationshipResponse, template: IRelationshipTemplate, peer: IIdentity, - creationChange: IRelationshipChange, + creationContent: ISerializable, relationshipSecretId: CoreId ): Relationship { const cache = CachedRelationship.from({ - changes: [creationChange], - template: template + creationContent, + template: template, + auditLog: RelationshipAuditLog.fromBackboneAuditLog(response.auditLog) }); return Relationship.from({ id: CoreId.from(response.id), @@ -109,27 +93,6 @@ export class Relationship extends CoreSynchronizable implements IRelationship { }); } - public toActive(response: RelationshipChangeResponse): void { - if (!this.cache) throw this.newCacheEmptyError(); - - this.cache.changes[0].response = response; - this.status = RelationshipStatus.Active; - } - - public toRejected(response: RelationshipChangeResponse): void { - if (!this.cache) throw this.newCacheEmptyError(); - - this.cache.changes[0].response = response; - this.status = RelationshipStatus.Rejected; - } - - public toRevoked(response: RelationshipChangeResponse): void { - if (!this.cache) throw this.newCacheEmptyError(); - - this.cache.changes[0].response = response; - this.status = RelationshipStatus.Revoked; - } - public static from(value: IRelationship): Relationship { return this.fromAny(value); } diff --git a/packages/transport/src/modules/relationships/local/RelationshipAuditLog.ts b/packages/transport/src/modules/relationships/local/RelationshipAuditLog.ts new file mode 100644 index 000000000..5923d31eb --- /dev/null +++ b/packages/transport/src/modules/relationships/local/RelationshipAuditLog.ts @@ -0,0 +1,21 @@ +import _ from "lodash"; +import { CoreAddress, CoreDate, CoreId } from "../../../core"; +import { BackboneRelationshipAuditLog as BackboneAuditLog } from "../transmission/RelationshipAuditLog"; +import { RelationshipAuditLogEntry } from "./RelationshipAuditLogEntry"; + +export class RelationshipAuditLog { + public static fromBackboneAuditLog(backboneAuditLog: BackboneAuditLog): RelationshipAuditLogEntry[] { + const auditLog = backboneAuditLog.map((entry) => { + return RelationshipAuditLogEntry.from({ + createdAt: CoreDate.from(entry.createdAt), + createdBy: CoreAddress.from(entry.createdBy), + createdByDevice: CoreId.from(entry.createdByDevice), + reason: entry.reason, + oldStatus: entry.oldStatus, + newStatus: entry.newStatus + }); + }); + + return _.orderBy(auditLog, ["createdAt"], ["asc"]); + } +} diff --git a/packages/transport/src/modules/relationships/local/RelationshipAuditLogEntry.ts b/packages/transport/src/modules/relationships/local/RelationshipAuditLogEntry.ts new file mode 100644 index 000000000..114fa1fc3 --- /dev/null +++ b/packages/transport/src/modules/relationships/local/RelationshipAuditLogEntry.ts @@ -0,0 +1,51 @@ +import { serialize, type, validate } from "@js-soft/ts-serval"; +import { CoreAddress, CoreDate, CoreId, CoreSerializable, ICoreAddress, ICoreDate, ICoreId } from "../../../core"; +import { RelationshipAuditLogEntryReason } from "../transmission/RelationshipAuditLog"; +import { RelationshipStatus } from "../transmission/RelationshipStatus"; + +export interface IRelationshipAuditLogEntry { + createdAt: ICoreDate; + createdBy: ICoreAddress; + createdByDevice: ICoreId; + reason: RelationshipAuditLogEntryReason; + oldStatus?: RelationshipStatus; + newStatus: RelationshipStatus; +} + +@type("RelationshipAuditLogEntry") +export class RelationshipAuditLogEntry extends CoreSerializable implements IRelationshipAuditLogEntry { + @validate() + @serialize() + public createdAt: CoreDate; + + @validate() + @serialize() + public createdBy: CoreAddress; + + @validate() + @serialize() + public createdByDevice: CoreId; + + @validate({ + customValidator: (v) => (!Object.values(RelationshipAuditLogEntryReason).includes(v) ? `must be one of: ${Object.values(RelationshipAuditLogEntryReason)}` : undefined) + }) + @serialize() + public reason: RelationshipAuditLogEntryReason; + + @validate({ + nullable: true, + customValidator: (v) => (!Object.values(RelationshipStatus).includes(v) ? `must be one of: ${Object.values(RelationshipStatus)}` : undefined) + }) + @serialize() + public oldStatus?: RelationshipStatus; + + @validate({ + customValidator: (v) => (!Object.values(RelationshipStatus).includes(v) ? `must be one of: ${Object.values(RelationshipStatus)}` : undefined) + }) + @serialize() + public newStatus: RelationshipStatus; + + public static from(value: IRelationshipAuditLogEntry): RelationshipAuditLogEntry { + return this.fromAny({ ...value, oldStatus: value.oldStatus ?? undefined }); + } +} diff --git a/packages/transport/src/modules/relationships/local/SendRelationshipParameters.ts b/packages/transport/src/modules/relationships/local/SendRelationshipParameters.ts index 16c5d647a..7ac9af12c 100644 --- a/packages/transport/src/modules/relationships/local/SendRelationshipParameters.ts +++ b/packages/transport/src/modules/relationships/local/SendRelationshipParameters.ts @@ -3,7 +3,7 @@ import { CoreSerializable, ICoreSerializable } from "../../../core"; import { IRelationshipTemplate, RelationshipTemplate } from "../../relationshipTemplates/local/RelationshipTemplate"; export interface ISendRelationshipParameters extends ICoreSerializable { - content: ISerializable; + creationContent: ISerializable; template: IRelationshipTemplate; } @@ -11,7 +11,7 @@ export interface ISendRelationshipParameters extends ICoreSerializable { export class SendRelationshipParameters extends CoreSerializable implements ISendRelationshipParameters { @validate() @serialize() - public content: Serializable; + public creationContent: Serializable; @validate() @serialize() diff --git a/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts b/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts new file mode 100644 index 000000000..9473996f0 --- /dev/null +++ b/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts @@ -0,0 +1,19 @@ +import { RelationshipStatus } from "./RelationshipStatus"; + +export interface BackboneRelationshipAuditLog extends Array {} + +export interface BackboneRelationshipAuditLogEntry { + createdAt: string; + createdBy: string; + createdByDevice: string; + reason: RelationshipAuditLogEntryReason; + oldStatus?: RelationshipStatus; + newStatus: RelationshipStatus; +} + +export enum RelationshipAuditLogEntryReason { + Creation = "Creation", + AcceptanceOfCreation = "AcceptanceOfCreation", + RejectionOfCreation = "RejectionOfCreation", + RevocationOfCreation = "RevocationOfCreation" +} diff --git a/packages/transport/src/modules/relationships/transmission/changes/RelationshipChange.ts b/packages/transport/src/modules/relationships/transmission/changes/RelationshipChange.ts deleted file mode 100644 index f84f269e9..000000000 --- a/packages/transport/src/modules/relationships/transmission/changes/RelationshipChange.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { ISerializable, serialize, type, validate } from "@js-soft/ts-serval"; -import { CoreSerializable, ICoreSerializable } from "../../../../core"; -import { CoreId, ICoreId } from "../../../../core/types/CoreId"; -import { BackboneGetRelationshipsChangesResponse } from "../../backbone/BackboneGetRelationshipsChanges"; -import { IRelationshipChangeRequest, RelationshipChangeRequest } from "./RelationshipChangeRequest"; -import { IRelationshipChangeResponse, RelationshipChangeResponse } from "./RelationshipChangeResponse"; -import { RelationshipChangeStatus } from "./RelationshipChangeStatus"; -import { RelationshipChangeType } from "./RelationshipChangeType"; - -export interface IRelationshipChange extends ICoreSerializable { - id: ICoreId; - relationshipId: ICoreId; - request: IRelationshipChangeRequest; - response?: IRelationshipChangeResponse; - status: RelationshipChangeStatus; - type: RelationshipChangeType; -} - -@type("RelationshipChange") -export class RelationshipChange extends CoreSerializable implements IRelationshipChange { - @validate() - @serialize() - public id: CoreId; - - @validate() - @serialize() - public relationshipId: CoreId; - - @validate() - @serialize() - public request: RelationshipChangeRequest; - - @validate({ nullable: true }) - @serialize() - public response?: RelationshipChangeResponse; - - @validate() - @serialize() - public status: RelationshipChangeStatus; - - @validate() - @serialize() - public type: RelationshipChangeType; - - public static fromBackbone(backboneChange: BackboneGetRelationshipsChangesResponse, requestContent?: ISerializable, responseContent?: ISerializable): RelationshipChange { - const relationshipChange = this.from({ - id: CoreId.from(backboneChange.id), - relationshipId: CoreId.from(backboneChange.relationshipId), - type: backboneChange.type, - status: backboneChange.status, - request: RelationshipChangeRequest.fromBackbone(backboneChange.request, requestContent) - }); - - if (backboneChange.response) { - relationshipChange.response = RelationshipChangeResponse.fromBackbone(backboneChange.response, responseContent); - } - - return relationshipChange; - } - - public static from(value: IRelationshipChange): RelationshipChange { - return this.fromAny(value); - } -} diff --git a/packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeRequest.ts b/packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeRequest.ts deleted file mode 100644 index 2f84e8059..000000000 --- a/packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeRequest.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; -import { CoreAddress, CoreDate, CoreSerializable, ICoreAddress } from "../../../../core"; -import { ICoreDate } from "../../../../core/types/CoreDate"; -import { CoreId, ICoreId } from "../../../../core/types/CoreId"; -import { BackboneGetRelationshipsChangesSingleChangeResponse } from "../../backbone/BackboneGetRelationshipsChanges"; - -export interface IRelationshipChangeRequest { - createdBy: ICoreAddress; - createdByDevice: ICoreId; - createdAt: ICoreDate; - content?: ISerializable; -} - -@type("RelationshipChangeRequest") -export class RelationshipChangeRequest extends CoreSerializable implements IRelationshipChangeRequest { - @validate() - @serialize() - public createdBy: CoreAddress; - - @validate() - @serialize() - public createdByDevice: CoreId; - - @validate() - @serialize() - public createdAt: CoreDate; - - @validate({ nullable: true }) - @serialize() - public content?: Serializable; - - public static fromBackbone(backboneChange: BackboneGetRelationshipsChangesSingleChangeResponse, content?: ISerializable): RelationshipChangeRequest { - return this.from({ - createdBy: CoreAddress.from(backboneChange.createdBy), - createdByDevice: CoreId.from(backboneChange.createdByDevice), - createdAt: CoreDate.from(backboneChange.createdAt), - content: content - }); - } - - public static from(value: IRelationshipChangeRequest): RelationshipChangeRequest { - return this.fromAny(value); - } -} diff --git a/packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeResponse.ts b/packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeResponse.ts deleted file mode 100644 index 2a8200e0c..000000000 --- a/packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeResponse.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; -import { CoreAddress, CoreDate, CoreSerializable, ICoreAddress, ICoreSerializable } from "../../../../core"; -import { ICoreDate } from "../../../../core/types/CoreDate"; -import { CoreId, ICoreId } from "../../../../core/types/CoreId"; -import { BackboneGetRelationshipsChangesSingleChangeResponse } from "../../backbone/BackboneGetRelationshipsChanges"; - -export interface IRelationshipChangeResponse extends ICoreSerializable { - createdBy: ICoreAddress; - createdByDevice: ICoreId; - createdAt: ICoreDate; - content?: ISerializable; -} - -@type("RelationshipChangeResponse") -export class RelationshipChangeResponse extends CoreSerializable implements IRelationshipChangeResponse { - @validate() - @serialize() - public createdBy: CoreAddress; - - @validate() - @serialize() - public createdByDevice: CoreId; - - @validate() - @serialize() - public createdAt: CoreDate; - - @validate({ nullable: true }) - @serialize() - public content?: Serializable; - - public static fromBackbone(backboneChange: BackboneGetRelationshipsChangesSingleChangeResponse, content?: ISerializable): RelationshipChangeResponse { - return this.from({ - createdBy: CoreAddress.from(backboneChange.createdBy), - createdByDevice: CoreId.from(backboneChange.createdByDevice), - createdAt: CoreDate.from(backboneChange.createdAt), - content: content - }); - } - - public static from(value: IRelationshipChangeResponse): RelationshipChangeResponse { - return this.fromAny(value); - } -} diff --git a/packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeStatus.ts b/packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeStatus.ts deleted file mode 100644 index 3e207dd8b..000000000 --- a/packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeStatus.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum RelationshipChangeStatus { - Pending = "Pending", - Rejected = "Rejected", - Revoked = "Revoked", - Accepted = "Accepted" -} diff --git a/packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeType.ts b/packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeType.ts deleted file mode 100644 index 5ee635209..000000000 --- a/packages/transport/src/modules/relationships/transmission/changes/RelationshipChangeType.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum RelationshipChangeType { - Creation = "Creation", - Termination = "Termination", - TerminationCancellation = "TerminationCancellation" -} diff --git a/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationChangeRequestCipher.ts b/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationChangeRequestCipher.ts deleted file mode 100644 index ec8a4547e..000000000 --- a/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationChangeRequestCipher.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { serialize, type, validate } from "@js-soft/ts-serval"; -import { CryptoCipher, CryptoRelationshipPublicRequest, ICryptoCipher, ICryptoRelationshipPublicRequest } from "@nmshd/crypto"; -import { CoreSerializable, ICoreSerializable } from "../../../../core"; - -export interface IRelationshipCreationChangeRequestCipher extends ICoreSerializable { - cipher: ICryptoCipher; - publicRequestCrypto: ICryptoRelationshipPublicRequest; -} - -@type("RelationshipCreationChangeRequestCipher") -export class RelationshipCreationChangeRequestCipher extends CoreSerializable implements IRelationshipCreationChangeRequestCipher { - @validate() - @serialize() - public cipher: CryptoCipher; - - @validate() - @serialize() - public publicRequestCrypto: CryptoRelationshipPublicRequest; - - public static from(value: IRelationshipCreationChangeRequestCipher): RelationshipCreationChangeRequestCipher { - return this.fromAny(value); - } - - public static fromBase64(value: string): RelationshipCreationChangeRequestCipher { - return super.fromBase64T(value); - } -} diff --git a/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationContentCipher.ts b/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationContentCipher.ts new file mode 100644 index 000000000..7d7c32d42 --- /dev/null +++ b/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationContentCipher.ts @@ -0,0 +1,27 @@ +import { serialize, type, validate } from "@js-soft/ts-serval"; +import { CryptoCipher, CryptoRelationshipPublicRequest, ICryptoCipher, ICryptoRelationshipPublicRequest } from "@nmshd/crypto"; +import { CoreSerializable, ICoreSerializable } from "../../../../core"; + +export interface IRelationshipCreationContentCipher extends ICoreSerializable { + cipher: ICryptoCipher; + publicCreationContentCrypto: ICryptoRelationshipPublicRequest; +} + +@type("RelationshipCreationContentCipher") +export class RelationshipCreationContentCipher extends CoreSerializable implements IRelationshipCreationContentCipher { + @validate() + @serialize() + public cipher: CryptoCipher; + + @validate() + @serialize() + public publicCreationContentCrypto: CryptoRelationshipPublicRequest; + + public static from(value: IRelationshipCreationContentCipher): RelationshipCreationContentCipher { + return this.fromAny(value); + } + + public static fromBase64(value: string): RelationshipCreationContentCipher { + return super.fromBase64T(value); + } +} diff --git a/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationChangeRequestSigned.ts b/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationContentSigned.ts similarity index 54% rename from packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationChangeRequestSigned.ts rename to packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationContentSigned.ts index 5dab25686..8a8407f1c 100644 --- a/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationChangeRequestSigned.ts +++ b/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationContentSigned.ts @@ -2,17 +2,17 @@ import { serialize, type, validate } from "@js-soft/ts-serval"; import { CryptoSignature, ICryptoSignature } from "@nmshd/crypto"; import { CoreSerializable, ICoreSerializable } from "../../../../core"; -export interface IRelationshipCreationChangeRequestSigned extends ICoreSerializable { - serializedRequest: string; +export interface IRelationshipCreationContentSigned extends ICoreSerializable { + serializedCreationContent: string; deviceSignature: ICryptoSignature; relationshipSignature: ICryptoSignature; } -@type("RelationshipCreationChangeRequestSigned") -export class RelationshipCreationChangeRequestSigned extends CoreSerializable implements IRelationshipCreationChangeRequestSigned { +@type("RelationshipCreationContentSigned") +export class RelationshipCreationContentSigned extends CoreSerializable implements IRelationshipCreationContentSigned { @validate() @serialize() - public serializedRequest: string; + public serializedCreationContent: string; @validate() @serialize() @@ -22,7 +22,7 @@ export class RelationshipCreationChangeRequestSigned extends CoreSerializable im @serialize() public relationshipSignature: CryptoSignature; - public static from(value: IRelationshipCreationChangeRequestSigned): RelationshipCreationChangeRequestSigned { + public static from(value: IRelationshipCreationContentSigned): RelationshipCreationContentSigned { return this.fromAny(value); } } diff --git a/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationChangeRequestContentWrapper.ts b/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationContentWrapper.ts similarity index 50% rename from packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationChangeRequestContentWrapper.ts rename to packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationContentWrapper.ts index 3136c43d7..0250057f5 100644 --- a/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationChangeRequestContentWrapper.ts +++ b/packages/transport/src/modules/relationships/transmission/requests/RelationshipCreationContentWrapper.ts @@ -1,15 +1,15 @@ import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; import { CoreId, CoreSerializable, ICoreId, ICoreSerializable } from "../../../../core"; -import { IIdentity, Identity } from "../../../accounts/data/Identity"; +import { Identity, IIdentity } from "../../../accounts/data/Identity"; -export interface IRelationshipCreationChangeRequestContentWrapper extends ICoreSerializable { +export interface IRelationshipCreationContentWrapper extends ICoreSerializable { identity: IIdentity; content: ISerializable; templateId: ICoreId; } -@type("RelationshipCreationChangeRequestContentWrapper") -export class RelationshipCreationChangeRequestContentWrapper extends CoreSerializable implements IRelationshipCreationChangeRequestContentWrapper { +@type("RelationshipCreationContentWrapper") +export class RelationshipCreationContentWrapper extends CoreSerializable implements IRelationshipCreationContentWrapper { @validate() @serialize() public identity: Identity; @@ -22,7 +22,7 @@ export class RelationshipCreationChangeRequestContentWrapper extends CoreSeriali @serialize() public templateId: CoreId; - public static from(value: IRelationshipCreationChangeRequestContentWrapper): RelationshipCreationChangeRequestContentWrapper { + public static from(value: IRelationshipCreationContentWrapper): RelationshipCreationContentWrapper { return this.fromAny(value); } } diff --git a/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationChangeResponseCipher.ts b/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationChangeResponseCipher.ts deleted file mode 100644 index d6e1b0d3e..000000000 --- a/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationChangeResponseCipher.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { serialize, type, validate } from "@js-soft/ts-serval"; -import { CryptoCipher, CryptoRelationshipPublicResponse, ICryptoCipher, ICryptoRelationshipPublicResponse } from "@nmshd/crypto"; -import { CoreSerializable, ICoreSerializable } from "../../../../core"; - -export interface IRelationshipCreationChangeResponseCipher extends ICoreSerializable { - cipher: ICryptoCipher; - publicResponseCrypto?: ICryptoRelationshipPublicResponse; -} - -@type("RelationshipCreationChangeResponseCipher") -export class RelationshipCreationChangeResponseCipher extends CoreSerializable implements IRelationshipCreationChangeResponseCipher { - @validate() - @serialize() - public cipher: CryptoCipher; - - @validate({ nullable: true }) - @serialize() - public publicResponseCrypto?: CryptoRelationshipPublicResponse; - - public static from(value: IRelationshipCreationChangeResponseCipher): RelationshipCreationChangeResponseCipher { - return this.fromAny(value); - } - - public static fromBase64(value: string): RelationshipCreationChangeResponseCipher { - return super.fromBase64T(value); - } -} diff --git a/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationChangeResponseContentWrapper.ts b/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationChangeResponseContentWrapper.ts deleted file mode 100644 index e9c5ca0f2..000000000 --- a/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationChangeResponseContentWrapper.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; -import { CoreId, CoreSerializable, ICoreId, ICoreSerializable } from "../../../../core"; - -export interface IRelationshipCreationChangeResponseContentWrapper extends ICoreSerializable { - content: ISerializable; - relationshipId: ICoreId; -} - -@type("RelationshipCreationChangeResponseContentWrapper") -export class RelationshipCreationChangeResponseContentWrapper extends CoreSerializable implements IRelationshipCreationChangeResponseContentWrapper { - @validate() - @serialize() - public content: Serializable; - - @validate() - @serialize() - public relationshipId: CoreId; - - public static from(value: IRelationshipCreationChangeResponseContentWrapper): RelationshipCreationChangeResponseContentWrapper { - return this.fromAny(value); - } -} diff --git a/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationResponseContentCipher.ts b/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationResponseContentCipher.ts new file mode 100644 index 000000000..b7b6c5b4a --- /dev/null +++ b/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationResponseContentCipher.ts @@ -0,0 +1,27 @@ +import { serialize, type, validate } from "@js-soft/ts-serval"; +import { CryptoCipher, CryptoRelationshipPublicResponse, ICryptoCipher, ICryptoRelationshipPublicResponse } from "@nmshd/crypto"; +import { CoreSerializable, ICoreSerializable } from "../../../../core"; + +export interface IRelationshipCreationResponseContentCipher extends ICoreSerializable { + cipher: ICryptoCipher; + publicCreationResponseContentCrypto: ICryptoRelationshipPublicResponse; +} + +@type("RelationshipCreationResponseContentCipher") +export class RelationshipCreationResponseContentCipher extends CoreSerializable implements IRelationshipCreationResponseContentCipher { + @validate() + @serialize() + public cipher: CryptoCipher; + + @validate() + @serialize() + public publicCreationResponseContentCrypto: CryptoRelationshipPublicResponse; + + public static from(value: IRelationshipCreationResponseContentCipher): RelationshipCreationResponseContentCipher { + return this.fromAny(value); + } + + public static fromBase64(value: string): RelationshipCreationResponseContentCipher { + return super.fromBase64T(value); + } +} diff --git a/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationChangeResponseSigned.ts b/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationResponseContentSigned.ts similarity index 51% rename from packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationChangeResponseSigned.ts rename to packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationResponseContentSigned.ts index 35c7bc61c..75721bcad 100644 --- a/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationChangeResponseSigned.ts +++ b/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationResponseContentSigned.ts @@ -2,17 +2,17 @@ import { serialize, type, validate } from "@js-soft/ts-serval"; import { CryptoSignature, ICryptoSignature } from "@nmshd/crypto"; import { CoreSerializable, ICoreSerializable } from "../../../../core"; -export interface IRelationshipCreationChangeResponseSigned extends ICoreSerializable { - serializedResponse: string; +export interface IRelationshipCreationResponseContentSigned extends ICoreSerializable { + serializedCreationResponseContent: string; deviceSignature: ICryptoSignature; relationshipSignature: ICryptoSignature; } -@type("RelationshipCreationChangeResponseSigned") -export class RelationshipCreationChangeResponseSigned extends CoreSerializable implements IRelationshipCreationChangeResponseSigned { +@type("RelationshipCreationResponseContentSigned") +export class RelationshipCreationResponseContentSigned extends CoreSerializable implements IRelationshipCreationResponseContentSigned { @validate() @serialize() - public serializedResponse: string; + public serializedCreationResponseContent: string; @validate() @serialize() @@ -22,7 +22,7 @@ export class RelationshipCreationChangeResponseSigned extends CoreSerializable i @serialize() public relationshipSignature: CryptoSignature; - public static from(value: IRelationshipCreationChangeResponseSigned): RelationshipCreationChangeResponseSigned { + public static from(value: IRelationshipCreationResponseContentSigned): RelationshipCreationResponseContentSigned { return this.fromAny(value); } } diff --git a/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationResponseContentWrapper.ts b/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationResponseContentWrapper.ts new file mode 100644 index 000000000..f137a2eba --- /dev/null +++ b/packages/transport/src/modules/relationships/transmission/responses/RelationshipCreationResponseContentWrapper.ts @@ -0,0 +1,17 @@ +import { serialize, type, validate } from "@js-soft/ts-serval"; +import { CoreId, CoreSerializable, ICoreId, ICoreSerializable } from "../../../../core"; + +export interface IRelationshipCreationResponseContentWrapper extends ICoreSerializable { + relationshipId: ICoreId; +} + +@type("RelationshipCreationResponseContentWrapper") +export class RelationshipCreationResponseContentWrapper extends CoreSerializable implements IRelationshipCreationResponseContentWrapper { + @validate() + @serialize() + public relationshipId: CoreId; + + public static from(value: IRelationshipCreationResponseContentWrapper): RelationshipCreationResponseContentWrapper { + return this.fromAny(value); + } +} diff --git a/packages/transport/src/modules/sync/externalEventProcessors/ExternalEventProcessorRegistry.ts b/packages/transport/src/modules/sync/externalEventProcessors/ExternalEventProcessorRegistry.ts index 6d5644b8c..651cb6af9 100644 --- a/packages/transport/src/modules/sync/externalEventProcessors/ExternalEventProcessorRegistry.ts +++ b/packages/transport/src/modules/sync/externalEventProcessors/ExternalEventProcessorRegistry.ts @@ -2,16 +2,14 @@ import { TransportError } from "../../../core"; import { ExternalEventProcessorConstructor } from "./ExternalEventProcessor"; import { MessageDeliveredExternalEventProcessor } from "./MessageDeliveredExternalEventProcessor"; import { MessageReceivedExternalEventProcessor } from "./MessageReceivedExternalEventProcessor"; -import { RelationshipChangeCompletedExternalEventProcessor } from "./RelationshipChangeCompletedExternalEventProcessor"; -import { RelationshipChangeCreatedExternalEventProcessor } from "./RelationshipChangeCreatedExternalEventProcessor"; +import { RelationshipStatusChangedExternalEventProcessor } from "./RelationshipStatusChangedExternalEventProcessor"; export class ExternalEventProcessorRegistry { private readonly processors = new Map(); public constructor() { this.registerProcessor("MessageReceived", MessageReceivedExternalEventProcessor); this.registerProcessor("MessageDelivered", MessageDeliveredExternalEventProcessor); - this.registerProcessor("RelationshipChangeCreated", RelationshipChangeCreatedExternalEventProcessor); - this.registerProcessor("RelationshipChangeCompleted", RelationshipChangeCompletedExternalEventProcessor); + this.registerProcessor("RelationshipStatusChanged", RelationshipStatusChangedExternalEventProcessor); } public registerProcessor(externalEventName: string, externalEventProcessor: ExternalEventProcessorConstructor): void { diff --git a/packages/transport/src/modules/sync/externalEventProcessors/RelationshipChangeCompletedExternalEventProcessor.ts b/packages/transport/src/modules/sync/externalEventProcessors/RelationshipChangeCompletedExternalEventProcessor.ts deleted file mode 100644 index 7b50c595e..000000000 --- a/packages/transport/src/modules/sync/externalEventProcessors/RelationshipChangeCompletedExternalEventProcessor.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { RelationshipChangedEvent } from "../../../events"; -import { Relationship } from "../../relationships/local/Relationship"; -import { BackboneExternalEvent } from "../backbone/BackboneExternalEvent"; -import { ExternalEventProcessor } from "./ExternalEventProcessor"; - -export class RelationshipChangeCompletedExternalEventProcessor extends ExternalEventProcessor { - public override async execute(externalEvent: BackboneExternalEvent): Promise { - const payload = externalEvent.payload as { changeId: string }; - const relationship = await this.accountController.relationships.applyChangeById(payload.changeId); - - if (relationship) { - this.eventBus.publish(new RelationshipChangedEvent(this.ownAddress, relationship)); - return relationship; - } - return; - } -} diff --git a/packages/transport/src/modules/sync/externalEventProcessors/RelationshipChangeCreatedExternalEventProcessor.ts b/packages/transport/src/modules/sync/externalEventProcessors/RelationshipStatusChangedExternalEventProcessor.ts similarity index 76% rename from packages/transport/src/modules/sync/externalEventProcessors/RelationshipChangeCreatedExternalEventProcessor.ts rename to packages/transport/src/modules/sync/externalEventProcessors/RelationshipStatusChangedExternalEventProcessor.ts index 511a22b92..5b46af334 100644 --- a/packages/transport/src/modules/sync/externalEventProcessors/RelationshipChangeCreatedExternalEventProcessor.ts +++ b/packages/transport/src/modules/sync/externalEventProcessors/RelationshipStatusChangedExternalEventProcessor.ts @@ -3,10 +3,10 @@ import { Relationship } from "../../relationships/local/Relationship"; import { BackboneExternalEvent } from "../backbone/BackboneExternalEvent"; import { ExternalEventProcessor } from "./ExternalEventProcessor"; -export class RelationshipChangeCreatedExternalEventProcessor extends ExternalEventProcessor { +export class RelationshipStatusChangedExternalEventProcessor extends ExternalEventProcessor { public override async execute(externalEvent: BackboneExternalEvent): Promise { - const payload = externalEvent.payload as { changeId: string; relationshipId: string }; - const relationship = await this.accountController.relationships.applyChangeById(payload.changeId); + const payload = externalEvent.payload as { relationshipId: string }; + const relationship = await this.accountController.relationships.applyRelationshipStatusChangedEvent(payload.relationshipId); if (relationship) { this.eventBus.publish(new RelationshipChangedEvent(this.ownAddress, relationship)); diff --git a/packages/transport/src/modules/sync/externalEventProcessors/index.ts b/packages/transport/src/modules/sync/externalEventProcessors/index.ts index 9fcb7e88d..dcc78f771 100644 --- a/packages/transport/src/modules/sync/externalEventProcessors/index.ts +++ b/packages/transport/src/modules/sync/externalEventProcessors/index.ts @@ -1,5 +1,4 @@ export * from "./ExternalEventProcessorRegistry"; export * from "./MessageDeliveredExternalEventProcessor"; export * from "./MessageReceivedExternalEventProcessor"; -export * from "./RelationshipChangeCompletedExternalEventProcessor"; -export * from "./RelationshipChangeCreatedExternalEventProcessor"; +export * from "./RelationshipStatusChangedExternalEventProcessor"; diff --git a/packages/transport/test/end2end/End2End.test.ts b/packages/transport/test/end2end/End2End.test.ts index 333b5b169..481d82896 100644 --- a/packages/transport/test/end2end/End2End.test.ts +++ b/packages/transport/test/end2end/End2End.test.ts @@ -1,16 +1,7 @@ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; import { JSONWrapper, Serializable } from "@js-soft/ts-serval"; import { CoreBuffer } from "@nmshd/crypto"; -import { - AccountController, - CoreDate, - FileReference, - RelationshipChangeStatus, - RelationshipChangeType, - RelationshipStatus, - TokenContentRelationshipTemplate, - Transport -} from "../../src"; +import { AccountController, CoreDate, FileReference, RelationshipStatus, TokenContentRelationshipTemplate, Transport } from "../../src"; import { TestUtil } from "../testHelpers/TestUtil"; describe("AccountTest", function () { @@ -95,7 +86,7 @@ describe("RelationshipTest: Accept", function () { // Send Request const request = await to.relationships.sendRelationship({ template: templateTo, - content: { + creationContent: { mycontent: "request" } }); @@ -107,10 +98,11 @@ describe("RelationshipTest: Accept", function () { expect(request.cache!.template.id.toString()).toStrictEqual(templateTo.id.toString()); expect(request.cache!.template.isOwn).toBe(false); - expect(request.cache!.creationChange.type).toStrictEqual(RelationshipChangeType.Creation); - expect(request.cache!.creationChange.status).toStrictEqual(RelationshipChangeStatus.Pending); expect(request.status).toStrictEqual(RelationshipStatus.Pending); + expect(request.cache?.auditLog).toHaveLength(1); + expect(request.cache!.auditLog[0].newStatus).toBe(RelationshipStatus.Pending); + // Accept relationship const syncedRelationships = await TestUtil.syncUntilHasRelationships(from); expect(syncedRelationships).toHaveLength(1); @@ -123,22 +115,16 @@ describe("RelationshipTest: Accept", function () { expect(templateResponseContent.value).toHaveProperty("mycontent"); expect(templateResponseContent.value.mycontent).toBe("template"); - expect(pendingRelationship.cache!.creationChange.type).toStrictEqual(RelationshipChangeType.Creation); - expect(pendingRelationship.cache!.creationChange.status).toStrictEqual(RelationshipChangeStatus.Pending); expect(pendingRelationship.status).toStrictEqual(RelationshipStatus.Pending); - const acceptedRelationshipFromSelf = await from.relationships.acceptChange(pendingRelationship.cache!.creationChange, { - mycontent: "acceptContent" - }); + const acceptedRelationshipFromSelf = await from.relationships.accept(relationshipId); expect(acceptedRelationshipFromSelf.id.toString()).toStrictEqual(relationshipId.toString()); expect(acceptedRelationshipFromSelf.status).toStrictEqual(RelationshipStatus.Active); - expect(acceptedRelationshipFromSelf.cache!.creationChange.status).toStrictEqual(RelationshipChangeStatus.Accepted); - expect(acceptedRelationshipFromSelf.peer).toBeDefined(); - expect(acceptedRelationshipFromSelf.peer.address.toString()).toStrictEqual(acceptedRelationshipFromSelf.cache!.creationChange.request.createdBy.toString()); - const acceptedContentSelf = acceptedRelationshipFromSelf.cache!.creationChange.response?.content as any; - expect(acceptedContentSelf).toBeInstanceOf(JSONWrapper); - expect(acceptedContentSelf.value.mycontent).toBe("acceptContent"); + expect(acceptedRelationshipFromSelf.cache?.auditLog).toHaveLength(2); + expect(acceptedRelationshipFromSelf.cache!.auditLog[1].newStatus).toBe(RelationshipStatus.Active); + + expect(acceptedRelationshipFromSelf.peer).toBeDefined(); // Get accepted relationship const syncedRelationshipsPeer = await TestUtil.syncUntilHasRelationships(to); @@ -147,13 +133,11 @@ describe("RelationshipTest: Accept", function () { expect(acceptedRelationshipPeer.id.toString()).toStrictEqual(relationshipId.toString()); expect(acceptedRelationshipPeer.status).toStrictEqual(RelationshipStatus.Active); - expect(acceptedRelationshipPeer.cache!.creationChange.status).toStrictEqual(RelationshipChangeStatus.Accepted); + + expect(acceptedRelationshipPeer.cache?.auditLog).toHaveLength(2); + expect(acceptedRelationshipPeer.cache!.auditLog[1].newStatus).toBe(RelationshipStatus.Active); expect(acceptedRelationshipPeer.peer).toBeDefined(); expect(acceptedRelationshipPeer.peer.address.toString()).toStrictEqual(templateTo.cache?.identity.address.toString()); - - const acceptedContentPeer = acceptedRelationshipPeer.cache!.creationChange.response?.content as any; - expect(acceptedContentPeer).toBeInstanceOf(JSONWrapper); - expect(acceptedContentPeer.value.mycontent).toBe("acceptContent"); }); }); @@ -218,7 +202,7 @@ describe("RelationshipTest: Reject", function () { const request = await to.relationships.sendRelationship({ template: templateTo, - content: { + creationContent: { mycontent: "request" } }); @@ -230,8 +214,6 @@ describe("RelationshipTest: Reject", function () { expect(request.cache!.template.id.toString()).toStrictEqual(templateTo.id.toString()); expect(request.cache!.template.isOwn).toBe(false); - expect(request.cache!.creationChange.type).toStrictEqual(RelationshipChangeType.Creation); - expect(request.cache!.creationChange.status).toStrictEqual(RelationshipChangeStatus.Pending); expect(request.status).toStrictEqual(RelationshipStatus.Pending); // Reject relationship @@ -245,24 +227,17 @@ describe("RelationshipTest: Reject", function () { expect(templateResponseContent.value).toHaveProperty("mycontent"); expect(templateResponseContent.value.mycontent).toBe("template"); - expect(pendingRelationship.cache!.creationChange.type).toStrictEqual(RelationshipChangeType.Creation); - expect(pendingRelationship.cache!.creationChange.status).toStrictEqual(RelationshipChangeStatus.Pending); expect(pendingRelationship.status).toStrictEqual(RelationshipStatus.Pending); - const rejectedRelationshipFromSelf = await from.relationships.rejectChange(pendingRelationship.cache!.creationChange, { - mycontent: "rejectContent" - }); + const rejectedRelationshipFromSelf = await from.relationships.reject(relationshipId); expect(rejectedRelationshipFromSelf.id.toString()).toStrictEqual(relationshipId.toString()); expect(rejectedRelationshipFromSelf.status).toStrictEqual(RelationshipStatus.Rejected); - expect(rejectedRelationshipFromSelf.cache!.creationChange.status).toStrictEqual(RelationshipChangeStatus.Rejected); - expect(rejectedRelationshipFromSelf.peer).toBeDefined(); - expect(rejectedRelationshipFromSelf.peer.address.toString()).toStrictEqual(rejectedRelationshipFromSelf.cache!.creationChange.request.createdBy.toString()); + expect(rejectedRelationshipFromSelf.cache?.auditLog).toHaveLength(2); + expect(rejectedRelationshipFromSelf.cache!.auditLog[1].newStatus).toBe(RelationshipStatus.Rejected); - const rejectionContentSelf = rejectedRelationshipFromSelf.cache!.creationChange.response?.content as any; - expect(rejectionContentSelf).toBeInstanceOf(JSONWrapper); - expect(rejectionContentSelf.value.mycontent).toBe("rejectContent"); + expect(rejectedRelationshipFromSelf.peer).toBeDefined(); - // Get accepted relationship + // Get rejected relationship const syncedRelationshipsPeer = await TestUtil.syncUntilHasRelationships(to); expect(syncedRelationshipsPeer).toHaveLength(1); const rejectedRelationshipPeer = syncedRelationshipsPeer[0]; @@ -270,13 +245,10 @@ describe("RelationshipTest: Reject", function () { expect(rejectedRelationshipPeer.id.toString()).toStrictEqual(relationshipId.toString()); expect(rejectedRelationshipPeer.status).toStrictEqual(RelationshipStatus.Rejected); - expect(rejectedRelationshipPeer.cache!.creationChange.status).toStrictEqual(RelationshipChangeStatus.Rejected); + expect(rejectedRelationshipPeer.cache?.auditLog).toHaveLength(2); + expect(rejectedRelationshipPeer.cache!.auditLog[1].newStatus).toBe(RelationshipStatus.Rejected); expect(rejectedRelationshipPeer.peer).toBeDefined(); expect(rejectedRelationshipPeer.peer.address.toString()).toStrictEqual(templateTo.cache?.identity.address.toString()); - - const rejectionContentPeer = rejectedRelationshipPeer.cache!.creationChange.response?.content as any; - expect(rejectionContentPeer).toBeInstanceOf(JSONWrapper); - expect(rejectionContentPeer.value.mycontent).toBe("rejectContent"); }); }); @@ -344,7 +316,7 @@ describe("RelationshipTest: Revoke", function () { const request = await requestor.relationships.sendRelationship({ template: templateRequestor, - content: { + creationContent: { mycontent: "request" } }); @@ -357,8 +329,6 @@ describe("RelationshipTest: Revoke", function () { expect(request.cache!.template.id.toString()).toStrictEqual(templateRequestor.id.toString()); expect(request.cache!.template.isOwn).toBe(false); - expect(request.cache!.creationChange.type).toStrictEqual(RelationshipChangeType.Creation); - expect(request.cache!.creationChange.status).toStrictEqual(RelationshipChangeStatus.Pending); expect(request.status).toStrictEqual(RelationshipStatus.Pending); // Revoke relationship @@ -373,24 +343,17 @@ describe("RelationshipTest: Revoke", function () { const templateResponseContent = pendingRelationship.cache!.template.cache!.content as JSONWrapper; expect(templateResponseContent.value).toHaveProperty("mycontent"); expect(templateResponseContent.value.mycontent).toBe("template"); - - expect(pendingRelationship.cache!.creationChange.type).toStrictEqual(RelationshipChangeType.Creation); - expect(pendingRelationship.cache!.creationChange.status).toStrictEqual(RelationshipChangeStatus.Pending); expect(pendingRelationship.status).toStrictEqual(RelationshipStatus.Pending); - const revokedRelationshipSelf = await requestor.relationships.revokeChange(pendingRelationship.cache!.creationChange, { - mycontent: "revokeContent" - }); + const revokedRelationshipSelf = await requestor.relationships.revoke(relationshipId); expect(revokedRelationshipSelf.status).toStrictEqual(RelationshipStatus.Revoked); expect(revokedRelationshipSelf.id.toString()).toStrictEqual(relationshipId.toString()); expect(revokedRelationshipSelf.status).toStrictEqual(RelationshipStatus.Revoked); - expect(revokedRelationshipSelf.cache!.creationChange.status).toStrictEqual(RelationshipChangeStatus.Revoked); + expect(revokedRelationshipSelf.cache?.auditLog).toHaveLength(2); + expect(revokedRelationshipSelf.cache!.auditLog[1].newStatus).toBe(RelationshipStatus.Revoked); expect(revokedRelationshipSelf.peer).toBeDefined(); expect(revokedRelationshipSelf.peer.address.toString()).toStrictEqual(revokedRelationshipSelf.cache!.template.cache?.identity.address.toString()); - const revocationContentSelf = revokedRelationshipSelf.cache!.creationChange.response?.content as any; - expect(revocationContentSelf).toBeInstanceOf(JSONWrapper); - expect(revocationContentSelf.value.mycontent).toBe("revokeContent"); // Get revoked relationship const syncedRelationshipsPeer = await TestUtil.syncUntilHasRelationships(templator); @@ -399,13 +362,9 @@ describe("RelationshipTest: Revoke", function () { expect(revokedRelationshipPeer.status).toStrictEqual(RelationshipStatus.Revoked); expect(revokedRelationshipPeer.id.toString()).toStrictEqual(relationshipId.toString()); expect(revokedRelationshipPeer.status).toStrictEqual(RelationshipStatus.Revoked); - expect(revokedRelationshipPeer.cache!.creationChange.status).toStrictEqual(RelationshipChangeStatus.Revoked); + expect(revokedRelationshipPeer.cache?.auditLog).toHaveLength(2); + expect(revokedRelationshipPeer.cache!.auditLog[1].newStatus).toBe(RelationshipStatus.Revoked); expect(revokedRelationshipPeer.peer).toBeDefined(); - expect(revokedRelationshipPeer.peer.address.toString()).toStrictEqual(revokedRelationshipPeer.cache!.creationChange.request.createdBy.toString()); - - const revocationContentPeer = revokedRelationshipPeer.cache!.creationChange.response?.content as any; - expect(revocationContentPeer).toBeInstanceOf(JSONWrapper); - expect(revocationContentPeer.value.mycontent).toBe("revokeContent"); }); test("should handle an incoming relationship request which was already revoked by the sender", async function () { @@ -443,29 +402,21 @@ describe("RelationshipTest: Revoke", function () { const pendingRelationship = await requestor.relationships.sendRelationship({ template: templateRequestor, - content: { + creationContent: { mycontent: "request" } }); // Revoke relationship - const revokedRelationshipSelf = await requestor.relationships.revokeChange(pendingRelationship.cache!.creationChange, { - mycontent: "revokeContent" - }); + const revokedRelationshipSelf = await requestor.relationships.revoke(pendingRelationship.id); expect(revokedRelationshipSelf.status).toStrictEqual(RelationshipStatus.Revoked); - const revocationContentSelf = revokedRelationshipSelf.cache!.creationChange.response?.content as any; - expect(revocationContentSelf).toBeInstanceOf(JSONWrapper); - expect(revocationContentSelf.value.mycontent).toBe("revokeContent"); // Get revoked relationship - const syncedRelationshipsPeer = await TestUtil.syncUntilHasRelationships(templator); - expect(syncedRelationshipsPeer).toHaveLength(1); - const revokedRelationshipPeer = syncedRelationshipsPeer[0]; + await TestUtil.syncUntilHasRelationships(templator, 2); // wait for pending and revoked + const relationshipsPeer = await templator.relationships.getRelationships({}); + expect(relationshipsPeer).toHaveLength(1); + const revokedRelationshipPeer = relationshipsPeer[0]; expect(revokedRelationshipPeer.status).toStrictEqual(RelationshipStatus.Revoked); - - const revocationContentPeer = revokedRelationshipPeer.cache!.creationChange.response?.content as any; - expect(revocationContentPeer).toBeInstanceOf(JSONWrapper); - expect(revocationContentPeer.value.mycontent).toBe("revokeContent"); }); }); diff --git a/packages/transport/test/modules/PublicAPI.test.ts b/packages/transport/test/modules/PublicAPI.test.ts index 3303307e8..f6c2a901c 100644 --- a/packages/transport/test/modules/PublicAPI.test.ts +++ b/packages/transport/test/modules/PublicAPI.test.ts @@ -108,23 +108,23 @@ publicFunctions[RelationshipsController.name] = [ nameof((r) => r.verify), nameof((r) => r.verifyIdentity), nameof((r) => r.sendRelationship), - nameof((r) => r.acceptChange), - nameof((r) => r.rejectChange), - nameof((r) => r.revokeChange), + nameof((r) => r.accept), + nameof((r) => r.reject), + nameof((r) => r.revoke), nameof((r) => r.updateCache) ]; publicFunctions[RelationshipSecretController.name] = [ nameof((r) => r.init), nameof((r) => r.createRequestorSecrets), nameof((r) => r.createTemplatorSecrets), - nameof((r) => r.getPublicResponse), + nameof((r) => r.getPublicCreationResponseContentCrypto), nameof((r) => r.convertSecrets), nameof((r) => r.deleteSecretForRequest), nameof((r) => r.decryptTemplate), nameof((r) => r.verifyTemplate), - nameof((r) => r.encryptRequest), + nameof((r) => r.encryptCreationContent), nameof((r) => r.encrypt), - nameof((r) => r.decryptRequest), + nameof((r) => r.decryptCreationContent), nameof((r) => r.createTemplateKey), nameof((r) => r.decryptPeer), nameof((r) => r.verifyOwn), diff --git a/packages/transport/test/modules/relationships/RelationshipsController.test.ts b/packages/transport/test/modules/relationships/RelationshipsController.test.ts index bfda40207..a309993f8 100644 --- a/packages/transport/test/modules/relationships/RelationshipsController.test.ts +++ b/packages/transport/test/modules/relationships/RelationshipsController.test.ts @@ -1,19 +1,5 @@ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; -import { - AccountController, - CachedRelationship, - CoreDate, - CoreId, - Identity, - Relationship, - RelationshipChangeRequest, - RelationshipChangeResponse, - RelationshipChangeStatus, - RelationshipChangeType, - RelationshipStatus, - RelationshipTemplate, - Transport -} from "../../../src"; +import { AccountController, CachedRelationship, CoreDate, CoreId, Identity, Relationship, RelationshipStatus, RelationshipTemplate, Transport } from "../../../src"; import { TestUtil } from "../../testHelpers/TestUtil"; describe("RelationshipsController", function () { @@ -37,29 +23,11 @@ describe("RelationshipsController", function () { expect(relationship.peer.address).toStrictEqual(peerAccount.identity.address); expect(relationship.cache!.template).toBeInstanceOf(RelationshipTemplate); - expect(relationship.cache!.changes).toHaveLength(1); expect(relationship.cache).toBeInstanceOf(CachedRelationship); expect(relationship.cachedAt).toBeInstanceOf(CoreDate); expect(relationship.cachedAt!.isWithin(TestUtil.tempDateThreshold, TestUtil.tempDateThreshold, creationTime)).toBe(true); - - const creation = relationship.cache!.changes[0]; - expect(creation.relationshipId.toString()).toBe(relationship.id.toString()); - expect(creation.status).toStrictEqual(RelationshipChangeStatus.Accepted); - expect(creation.type).toStrictEqual(RelationshipChangeType.Creation); - - expect(creation.request).toBeInstanceOf(RelationshipChangeRequest); - expect(creation.request.createdAt.isWithin(TestUtil.tempDateThreshold, TestUtil.tempDateThreshold, creationTime)).toBe(true); - if (creation.request.createdBy.equals(ownAccount.identity.address)) { - expect(creation.request.createdBy.toString()).toBe(ownAccount.identity.address.toString()); - expect(creation.response?.createdBy.toString()).toBe(peerAccount.identity.address.toString()); - } else { - expect(creation.response?.createdBy.toString()).toBe(ownAccount.identity.address.toString()); - expect(creation.request.createdBy.toString()).toBe(peerAccount.identity.address.toString()); - } - - expect(creation.response).toBeInstanceOf(RelationshipChangeResponse); - expect(creation.response!.createdAt.isWithin(TestUtil.tempDateThreshold, TestUtil.tempDateThreshold, creationTime)).toBe(true); + expect(relationship.cache!.creationContent).toBeDefined(); expect(relationship.cache!.lastMessageReceivedAt).toBeUndefined(); expect(relationship.cache!.lastMessageSentAt).toBeUndefined(); diff --git a/packages/transport/test/modules/relationships/RelationshipsCustomContent.test.ts b/packages/transport/test/modules/relationships/RelationshipsCustomContent.test.ts index e5d90abbf..356647ade 100644 --- a/packages/transport/test/modules/relationships/RelationshipsCustomContent.test.ts +++ b/packages/transport/test/modules/relationships/RelationshipsCustomContent.test.ts @@ -1,6 +1,6 @@ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; import { JSONWrapper, Serializable } from "@js-soft/ts-serval"; -import { AccountController, RelationshipChangeRequest, Transport } from "../../../src"; +import { AccountController, Transport } from "../../../src"; import { TestUtil } from "../../testHelpers/TestUtil"; describe("Relationships Custom Content", function () { @@ -33,18 +33,15 @@ describe("Relationships Custom Content", function () { const template = await TestUtil.fetchRelationshipTemplateFromTokenReference(recipient, tokenReference); const customContent = Serializable.fromAny({ content: "TestToken" }); const relRecipient = await TestUtil.sendRelationship(recipient, template, customContent); - const relRecipientRequest = relRecipient.cache!.creationChange.request; + const relRecipientContent = relRecipient.cache!.creationContent; const relSender = await TestUtil.syncUntilHasRelationships(sender); - const relSenderRequest = relSender[0].cache!.creationChange.request; + const relSenderRequest = relSender[0].cache!.creationContent; - expect(relRecipientRequest).toBeInstanceOf(RelationshipChangeRequest); - expect(relSenderRequest).toBeInstanceOf(RelationshipChangeRequest); - - expect(relRecipientRequest.content).toBeInstanceOf(JSONWrapper); - const recipientToken = relRecipientRequest.content as JSONWrapper; - expect(relSenderRequest.content).toBeInstanceOf(JSONWrapper); - const senderToken = relSenderRequest.content as JSONWrapper; + expect(relRecipientContent).toBeInstanceOf(JSONWrapper); + const recipientToken = relRecipientContent as JSONWrapper; + expect(relSenderRequest).toBeInstanceOf(JSONWrapper); + const senderToken = relSenderRequest as JSONWrapper; expect((recipientToken.toJSON() as any).content).toBe("TestToken"); expect((senderToken.toJSON() as any).content).toBe("TestToken"); diff --git a/packages/transport/test/modules/sync/SyncController.error.test.ts b/packages/transport/test/modules/sync/SyncController.error.test.ts index 210ebdf1b..75232279a 100644 --- a/packages/transport/test/modules/sync/SyncController.error.test.ts +++ b/packages/transport/test/modules/sync/SyncController.error.test.ts @@ -35,7 +35,7 @@ describe("SyncController.error", function () { await requestorDevice.relationships.sendRelationship({ template: templateOnRequestorDevice, - content: { someMessageContent: "someMessageContent" } + creationContent: { someMessageContent: "someMessageContent" } }); const error = await TestUtil.syncUntilHasError(templatorDevice2); diff --git a/packages/transport/test/modules/sync/SyncController.relationships.test.ts b/packages/transport/test/modules/sync/SyncController.relationships.test.ts index c6f73e6a4..ba0abee2c 100644 --- a/packages/transport/test/modules/sync/SyncController.relationships.test.ts +++ b/packages/transport/test/modules/sync/SyncController.relationships.test.ts @@ -31,7 +31,7 @@ describe("RelationshipSync", function () { const createdRelationship = await requestorDevice1.relationships.sendRelationship({ template: templateOnRequestorDevice1, - content: { someMessageContent: "someMessageContent" } + creationContent: { someMessageContent: "someMessageContent" } }); await requestorDevice1.syncDatawallet(); @@ -49,10 +49,7 @@ describe("RelationshipSync", function () { await TestUtil.syncUntilHasRelationships(templatorDevice); - const relationshipOnTemplatorDevice = await templatorDevice.relationships.getRelationship(createdRelationship.id); - await templatorDevice.relationships.acceptChange(relationshipOnTemplatorDevice!.cache!.creationChange, { - someResponseContent: "someResponseContent" - }); + await templatorDevice.relationships.accept(createdRelationship.id); let relationshipOnRequestorDevice1 = (await requestorDevice2.relationships.getRelationship(createdRelationship.id))!; expect(relationshipOnRequestorDevice1.status).toStrictEqual(RelationshipStatus.Pending); @@ -89,7 +86,7 @@ describe("RelationshipSync", function () { const createdRelationship = await requestorDevice1.relationships.sendRelationship({ template: templateOnRequestorDevice1, - content: { someMessageContent: "someMessageContent" } + creationContent: { someMessageContent: "someMessageContent" } }); await requestorDevice1.syncDatawallet(); @@ -100,9 +97,7 @@ describe("RelationshipSync", function () { const relationships = await TestUtil.syncUntilHasRelationships(templatorDevice); const relationshipOnTemplatorDevice = relationships[0]; - await templatorDevice.relationships.acceptChange(relationshipOnTemplatorDevice.cache!.creationChange, { - someResponseContent: "someResponseContent" - }); + await templatorDevice.relationships.accept(relationshipOnTemplatorDevice.id); relationshipOnRequestorDevice2 = await requestorDevice2.relationships.getRelationship(createdRelationship.id); expect(relationshipOnRequestorDevice2).toBeUndefined(); @@ -140,7 +135,7 @@ describe("RelationshipSync", function () { const createdRelationship = await requestorDevice.relationships.sendRelationship({ template: templateOnRequestorDevice1, - content: { someMessageContent: "someMessageContent" } + creationContent: { someMessageContent: "someMessageContent" } }); await requestorDevice.syncDatawallet(); @@ -153,10 +148,7 @@ describe("RelationshipSync", function () { await TestUtil.syncUntilHasRelationships(templatorDevice2); - const relationshipOnTemplatorDevice = await templatorDevice2.relationships.getRelationship(createdRelationship.id); - await templatorDevice2.relationships.acceptChange(relationshipOnTemplatorDevice!.cache!.creationChange, { - someResponseContent: "someResponseContent" - }); + await templatorDevice2.relationships.accept(createdRelationship.id); await templatorDevice2.syncDatawallet(); diff --git a/packages/transport/test/testHelpers/TestUtil.ts b/packages/transport/test/testHelpers/TestUtil.ts index bcd6413be..32b1a530e 100644 --- a/packages/transport/test/testHelpers/TestUtil.ts +++ b/packages/transport/test/testHelpers/TestUtil.ts @@ -315,7 +315,7 @@ export class TestUtil { await to.relationships.sendRelationship({ template: templateTo, - content: { + creationContent: { mycontent: "request" } }); @@ -326,7 +326,7 @@ export class TestUtil { const pendingRelationship = syncedRelationships[0]; expect(pendingRelationship.status).toStrictEqual(RelationshipStatus.Pending); - const rejectedRelationshipFromSelf = await from.relationships.rejectChange(pendingRelationship.cache!.creationChange, {}); + const rejectedRelationshipFromSelf = await from.relationships.reject(pendingRelationship.id); expect(rejectedRelationshipFromSelf.status).toStrictEqual(RelationshipStatus.Rejected); // Get accepted relationship @@ -349,7 +349,7 @@ export class TestUtil { const relRequest = await to.relationships.sendRelationship({ template: templateTo, - content: { + creationContent: { mycontent: "request" } }); @@ -360,7 +360,7 @@ export class TestUtil { const pendingRelationship = syncedRelationships[0]; expect(pendingRelationship.status).toStrictEqual(RelationshipStatus.Pending); - const acceptedRelationshipFromSelf = await from.relationships.acceptChange(pendingRelationship.cache!.creationChange, {}); + const acceptedRelationshipFromSelf = await from.relationships.accept(pendingRelationship.id); expect(acceptedRelationshipFromSelf.status).toStrictEqual(RelationshipStatus.Active); // Get accepted relationship @@ -404,8 +404,8 @@ export class TestUtil { return syncResult; } - public static async syncUntilHasRelationships(accountController: AccountController): Promise { - const syncResult = await TestUtil.syncUntil(accountController, (syncResult) => syncResult.relationships.length > 0); + public static async syncUntilHasRelationships(accountController: AccountController, numberOfRelationships = 1): Promise { + const syncResult = await TestUtil.syncUntil(accountController, (syncResult) => syncResult.relationships.length > numberOfRelationships - 1); return syncResult.relationships; } @@ -481,7 +481,7 @@ export class TestUtil { } return await account.relationships.sendRelationship({ template: template, - content: body + creationContent: body }); } diff --git a/packages/transport/test/utils/Reflection.test.ts b/packages/transport/test/utils/Reflection.test.ts index b10b97366..1575c1454 100644 --- a/packages/transport/test/utils/Reflection.test.ts +++ b/packages/transport/test/utils/Reflection.test.ts @@ -31,15 +31,12 @@ import { MessageEnvelopeRecipient, MessageSigned, Relationship, - RelationshipChange, - RelationshipChangeRequest, - RelationshipChangeResponse, - RelationshipCreationChangeRequestCipher, - RelationshipCreationChangeRequestContentWrapper, - RelationshipCreationChangeRequestSigned, - RelationshipCreationChangeResponseCipher, - RelationshipCreationChangeResponseContentWrapper, - RelationshipCreationChangeResponseSigned, + RelationshipCreationContentCipher, + RelationshipCreationContentSigned, + RelationshipCreationContentWrapper, + RelationshipCreationResponseContentCipher, + RelationshipCreationResponseContentSigned, + RelationshipCreationResponseContentWrapper, RelationshipTemplate, RelationshipTemplateContentWrapper, RelationshipTemplatePublicKey, @@ -83,16 +80,13 @@ const transportClassNames: string[] = [ `${Message.name}@1`, `${CachedRelationshipTemplate.name}@1`, `${Relationship.name}@1`, - `${RelationshipChange.name}@1`, - `${RelationshipChangeRequest.name}@1`, - `${RelationshipChangeResponse.name}@1`, `${RelationshipTemplate.name}@1`, - `${RelationshipCreationChangeRequestCipher.name}@1`, - `${RelationshipCreationChangeRequestContentWrapper.name}@1`, - `${RelationshipCreationChangeRequestSigned.name}@1`, - `${RelationshipCreationChangeResponseCipher.name}@1`, - `${RelationshipCreationChangeResponseContentWrapper.name}@1`, - `${RelationshipCreationChangeResponseSigned.name}@1`, + `${RelationshipCreationContentCipher.name}@1`, + `${RelationshipCreationContentWrapper.name}@1`, + `${RelationshipCreationContentSigned.name}@1`, + `${RelationshipCreationResponseContentCipher.name}@1`, + `${RelationshipCreationResponseContentWrapper.name}@1`, + `${RelationshipCreationResponseContentSigned.name}@1`, `${RelationshipTemplateContentWrapper.name}@1`, `${RelationshipTemplatePublicKey.name}@1`, `${RelationshipTemplateSigned.name}@1`, From 27b9aec9a31e7024c0572d2153cf5eaefbb9ea7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Thu, 16 May 2024 15:10:21 +0200 Subject: [PATCH 02/40] Feature/backbone specific addresses (#78) * chore: comment out address check * chore: use backbone-specific identities * chore: complete the realm refactoring * fix: apply backbone-specific identities * fix: remove realm * fix: correct regex * chore: sort imports * fix: update vscode settings * fix: update schemas * fix: specify Application.InstanceUrl * fix: use hostname instead of baseurl * fix: better domain regex * fix: bump backbone to allow localhost as Application.InstanceUrl * fix: do not export enmeshedPrefix only for tests * fix: correct address * chore: rename variable --------- Co-authored-by: Timo Notheisen Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: mkuhn --- .dev/appsettings.override.json | 9 +++ .dev/compose.backbone.env | 2 +- .vscode/settings.json | 7 +- packages/app-runtime/src/AppConfig.ts | 3 +- packages/app-runtime/src/AppRuntimeErrors.ts | 7 -- .../src/multiAccount/AccountServices.ts | 14 +--- .../multiAccount/MultiAccountController.ts | 6 +- .../src/multiAccount/data/LocalAccount.ts | 7 +- .../src/multiAccount/data/LocalAccountDTO.ts | 1 - .../multiAccount/data/LocalAccountMapper.ts | 1 - .../test/extensibility/MessageFacade.test.ts | 3 +- packages/app-runtime/test/lib/TestUtil.ts | 6 +- .../test/runtime/AppStringProcessor.test.ts | 3 +- .../app-runtime/test/runtime/Startup.test.ts | 3 +- .../requests/testHelpers/TestObjectFactory.ts | 13 +-- .../runtime/src/dataViews/DataViewExpander.ts | 20 ++--- .../src/dataViews/transport/IdentityDVO.ts | 1 - .../src/types/transport/IdentityDTO.ts | 1 - .../runtime/src/useCases/common/Schemas.ts | 34 +++++--- .../common/validation/ValidatableStrings.ts | 2 +- .../transport/devices/DeviceMapper.ts | 8 +- .../relationships/RelationshipMapper.ts | 3 +- .../test/consumption/attributes.test.ts | 8 +- .../runtime/test/transport/account.test.ts | 5 +- packages/transport/src/core/CoreErrors.ts | 4 - packages/transport/src/core/Transport.ts | 8 -- .../src/modules/accounts/AccountController.ts | 3 +- .../modules/accounts/IdentityController.ts | 5 -- .../src/modules/accounts/IdentityUtil.ts | 57 +++++++------ .../src/modules/accounts/data/Identity.ts | 6 -- .../data/IdentitySecretCredentials.ts | 6 -- .../src/modules/accounts/data/Realm.ts | 5 -- packages/transport/src/modules/index.ts | 59 +++++++------- .../test/utils/IdentityGenerator.test.ts | 80 +++++++++---------- 34 files changed, 166 insertions(+), 234 deletions(-) delete mode 100644 packages/transport/src/modules/accounts/data/Realm.ts diff --git a/.dev/appsettings.override.json b/.dev/appsettings.override.json index 24ea68af4..82f311d44 100644 --- a/.dev/appsettings.override.json +++ b/.dev/appsettings.override.json @@ -38,6 +38,9 @@ "PushNotifications": { "Provider": "Dummy" } + }, + "Application": { + "InstanceUrl": "localhost" } }, "Files": { @@ -58,6 +61,9 @@ "Provider": "Postgres", "ConnectionString": "User ID=messages;Password=Passw0rd;Server=postgres;Port=5432;Database=enmeshed;" } + }, + "Application": { + "InstanceUrl": "localhost" } }, "Relationships": { @@ -66,6 +72,9 @@ "Provider": "Postgres", "ConnectionString": "User ID=relationships;Password=Passw0rd;Server=postgres;Port=5432;Database=enmeshed;" } + }, + "Application": { + "InstanceUrl": "localhost" } }, "Synchronization": { diff --git a/.dev/compose.backbone.env b/.dev/compose.backbone.env index f5588074d..02f944a85 100644 --- a/.dev/compose.backbone.env +++ b/.dev/compose.backbone.env @@ -1 +1 @@ -BACKBONE_VERSION=6.0.0-alpha.5 +BACKBONE_VERSION=6.0.0-alpha.7 diff --git a/.vscode/settings.json b/.vscode/settings.json index 8729810db..279e9fd77 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -49,10 +49,7 @@ "xmlTools.splitAttributesOnFormat": true, "xmlTools.enforcePrettySelfClosingTagOnFormat": true, "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" + "source.organizeImports": "always" }, - "files.eol": "\n", - "typescript.unstable": { - "organizeImportsIgnoreCase": true - } + "files.eol": "\n" } diff --git a/packages/app-runtime/src/AppConfig.ts b/packages/app-runtime/src/AppConfig.ts index cb02448b6..4a9c3862a 100644 --- a/packages/app-runtime/src/AppConfig.ts +++ b/packages/app-runtime/src/AppConfig.ts @@ -1,5 +1,5 @@ import { RuntimeConfig } from "@nmshd/runtime"; -import { IConfigOverwrite, Realm } from "@nmshd/transport"; +import { IConfigOverwrite } from "@nmshd/transport"; import { defaultsDeep } from "lodash"; export interface AppConfig extends RuntimeConfig { @@ -23,7 +23,6 @@ export function createAppConfig(...configs: AppConfigOverwrite[]): AppConfig { const appConfig = { accountsDbName: "accounts", transportLibrary: { - realm: Realm.Prod, datawalletEnabled: true }, modules: { diff --git a/packages/app-runtime/src/AppRuntimeErrors.ts b/packages/app-runtime/src/AppRuntimeErrors.ts index ccbf36639..14685caf5 100644 --- a/packages/app-runtime/src/AppRuntimeErrors.ts +++ b/packages/app-runtime/src/AppRuntimeErrors.ts @@ -83,12 +83,6 @@ class PushNotificationModule { } } -class MultiAccount { - public wrongRealm(): UserfriendlyApplicationError { - return new UserfriendlyApplicationError("error.runtime.MultiAccount.WrongRealm", "The given realm is invalid."); - } -} - class Modules { public readonly pushNotificationModule = new PushNotificationModule(); } @@ -97,5 +91,4 @@ export class AppRuntimeErrors { public static readonly general = new General(); public static readonly startup = new Startup(); public static readonly modules = new Modules(); - public static readonly multiAccount = new MultiAccount(); } diff --git a/packages/app-runtime/src/multiAccount/AccountServices.ts b/packages/app-runtime/src/multiAccount/AccountServices.ts index 0e1d90755..23ee46130 100644 --- a/packages/app-runtime/src/multiAccount/AccountServices.ts +++ b/packages/app-runtime/src/multiAccount/AccountServices.ts @@ -1,20 +1,14 @@ import { DeviceMapper, DeviceOnboardingInfoDTO } from "@nmshd/runtime"; -import { CoreId, Realm } from "@nmshd/transport"; -import { AppRuntimeErrors } from "../AppRuntimeErrors"; +import { CoreId } from "@nmshd/transport"; +import { MultiAccountController } from "./MultiAccountController"; import { LocalAccountDTO } from "./data/LocalAccountDTO"; import { LocalAccountMapper } from "./data/LocalAccountMapper"; -import { MultiAccountController } from "./MultiAccountController"; export class AccountServices { public constructor(protected readonly multiAccountController: MultiAccountController) {} - public async createAccount(realm: Realm, name: string): Promise { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (realm !== Realm.Dev && realm !== Realm.Prod && realm !== Realm.Stage) { - throw AppRuntimeErrors.multiAccount.wrongRealm(); - } - - const [localAccount] = await this.multiAccountController.createAccount(realm, name); + public async createAccount(name: string): Promise { + const [localAccount] = await this.multiAccountController.createAccount(name); return LocalAccountMapper.toLocalAccountDTO(localAccount); } diff --git a/packages/app-runtime/src/multiAccount/MultiAccountController.ts b/packages/app-runtime/src/multiAccount/MultiAccountController.ts index bd13a5f88..c87c25837 100644 --- a/packages/app-runtime/src/multiAccount/MultiAccountController.ts +++ b/packages/app-runtime/src/multiAccount/MultiAccountController.ts @@ -8,7 +8,6 @@ import { CoreError, CoreId, DeviceSharedSecret, - Realm, Transport, CoreErrors as TransportCoreErrors, TransportLoggerFactory @@ -162,7 +161,6 @@ export class MultiAccountController { id, address: deviceSharedSecret.identity.address, directory: ".", - realm: deviceSharedSecret.identity.realm, name: name ?? deviceSharedSecret.name ?? deviceSharedSecret.identity.address.toString(), order: -1 }); @@ -185,14 +183,12 @@ export class MultiAccountController { return [updatedLocalAccount, accountController]; } - public async createAccount(realm: Realm, name: string): Promise<[LocalAccount, AccountController]> { - this._log.trace(`Creating account for realm ${realm}.`); + public async createAccount(name: string): Promise<[LocalAccount, AccountController]> { const id = await CoreId.generate(); let localAccount = LocalAccount.from({ id, directory: ".", - realm, name, order: -1 }); diff --git a/packages/app-runtime/src/multiAccount/data/LocalAccount.ts b/packages/app-runtime/src/multiAccount/data/LocalAccount.ts index 04ab38b72..65bbd97fa 100644 --- a/packages/app-runtime/src/multiAccount/data/LocalAccount.ts +++ b/packages/app-runtime/src/multiAccount/data/LocalAccount.ts @@ -1,11 +1,10 @@ import { serialize, type, validate } from "@js-soft/ts-serval"; -import { CoreAddress, CoreDate, CoreId, CoreSerializable, ICoreDate, ICoreSerializable, Realm } from "@nmshd/transport"; +import { CoreAddress, CoreDate, CoreId, CoreSerializable, ICoreDate, ICoreSerializable } from "@nmshd/transport"; export interface ILocalAccount extends ICoreSerializable { id: CoreId; address?: CoreAddress; name: string; - realm: Realm; directory: string; order: number; lastAccessedAt?: ICoreDate; @@ -25,10 +24,6 @@ export class LocalAccount extends CoreSerializable implements ILocalAccount { @serialize() public name: string; - @validate() - @serialize() - public realm: Realm; - @validate() @serialize() public directory: string; diff --git a/packages/app-runtime/src/multiAccount/data/LocalAccountDTO.ts b/packages/app-runtime/src/multiAccount/data/LocalAccountDTO.ts index 7d1721d8a..f53c70e73 100644 --- a/packages/app-runtime/src/multiAccount/data/LocalAccountDTO.ts +++ b/packages/app-runtime/src/multiAccount/data/LocalAccountDTO.ts @@ -2,7 +2,6 @@ export interface LocalAccountDTO { id: string; address?: string; name: string; - realm: string; directory: string; order: number; lastAccessedAt?: string; diff --git a/packages/app-runtime/src/multiAccount/data/LocalAccountMapper.ts b/packages/app-runtime/src/multiAccount/data/LocalAccountMapper.ts index d81d99d4f..f1a4e0afc 100644 --- a/packages/app-runtime/src/multiAccount/data/LocalAccountMapper.ts +++ b/packages/app-runtime/src/multiAccount/data/LocalAccountMapper.ts @@ -7,7 +7,6 @@ export class LocalAccountMapper { id: localAccount.id.toString(), address: localAccount.address?.toString(), name: localAccount.name, - realm: localAccount.realm, directory: localAccount.directory.toString(), order: localAccount.order, lastAccessedAt: localAccount.lastAccessedAt?.toString() diff --git a/packages/app-runtime/test/extensibility/MessageFacade.test.ts b/packages/app-runtime/test/extensibility/MessageFacade.test.ts index 696782fe5..bded4f089 100644 --- a/packages/app-runtime/test/extensibility/MessageFacade.test.ts +++ b/packages/app-runtime/test/extensibility/MessageFacade.test.ts @@ -1,4 +1,3 @@ -import { Realm } from "@nmshd/transport"; import { AppRuntime, LocalAccountDTO } from "../../src"; import { TestUtil } from "../lib"; @@ -11,7 +10,7 @@ describe("MessageFacade", function () { runtime = await TestUtil.createRuntime(); await runtime.start(); - localAccount = await runtime.accountServices.createAccount(Realm.Prod, "Profil 1"); + localAccount = await runtime.accountServices.createAccount("Profil 1"); await runtime.selectAccount(localAccount.id); }); diff --git a/packages/app-runtime/test/lib/TestUtil.ts b/packages/app-runtime/test/lib/TestUtil.ts index 6edaa0202..60e499ee1 100644 --- a/packages/app-runtime/test/lib/TestUtil.ts +++ b/packages/app-runtime/test/lib/TestUtil.ts @@ -4,7 +4,7 @@ import { SimpleLoggerFactory } from "@js-soft/simple-logger"; import { Serializable } from "@js-soft/ts-serval"; import { Result, sleep, SubscriptionTarget } from "@js-soft/ts-utils"; import { FileDTO, MessageDTO, RelationshipDTO, RelationshipTemplateDTO, SyncEverythingResponse } from "@nmshd/runtime"; -import { CoreDate, IConfigOverwrite, Realm, TransportLoggerFactory } from "@nmshd/transport"; +import { CoreDate, IConfigOverwrite, TransportLoggerFactory } from "@nmshd/transport"; import { LogLevel } from "typescript-logging"; import { AppConfig, AppRuntime, LocalAccountDTO, LocalAccountSession, createAppConfig as runtime_createAppConfig } from "../../src"; import { NativeBootstrapperMock } from "../mocks/NativeBootstrapperMock"; @@ -55,7 +55,7 @@ export class TestUtil { } public static async createSession(runtime: AppRuntime): Promise { - const localAccount1 = await runtime.accountServices.createAccount(Realm.Prod, "Profil 1"); + const localAccount1 = await runtime.accountServices.createAccount("Profil 1"); return await runtime.selectAccount(localAccount1.id); } @@ -157,7 +157,7 @@ export class TestUtil { const accounts: LocalAccountDTO[] = []; for (let i = 0; i < count; i++) { - accounts.push(await runtime.accountServices.createAccount(Realm.Prod, `Account ${i}`)); + accounts.push(await runtime.accountServices.createAccount(`Account ${i}`)); } return accounts; diff --git a/packages/app-runtime/test/runtime/AppStringProcessor.test.ts b/packages/app-runtime/test/runtime/AppStringProcessor.test.ts index dee6cff63..2f1c1414f 100644 --- a/packages/app-runtime/test/runtime/AppStringProcessor.test.ts +++ b/packages/app-runtime/test/runtime/AppStringProcessor.test.ts @@ -1,4 +1,3 @@ -import { Realm } from "@nmshd/transport"; import { AppRuntime } from "../../src"; import { TestUtil } from "../lib"; @@ -14,7 +13,7 @@ describe("AppStringProcessor", function () { }); test("should process a URL", async function () { - const account = await runtime.accountServices.createAccount(Realm.Prod, Math.random().toString(36).substring(7)); + const account = await runtime.accountServices.createAccount(Math.random().toString(36).substring(7)); const result = await runtime.stringProcessor.processURL("nmshd://qr#", account); expect(result.isError).toBeDefined(); diff --git a/packages/app-runtime/test/runtime/Startup.test.ts b/packages/app-runtime/test/runtime/Startup.test.ts index c68b6b8eb..2649ea483 100644 --- a/packages/app-runtime/test/runtime/Startup.test.ts +++ b/packages/app-runtime/test/runtime/Startup.test.ts @@ -1,4 +1,3 @@ -import { Realm } from "@nmshd/transport"; import { AppRuntime, LocalAccountDTO } from "../../src"; import { EventListener, TestUtil } from "../lib"; @@ -54,7 +53,7 @@ describe("Runtime Startup", function () { }); test("should create an account", async function () { - localAccount = await runtime.accountServices.createAccount(Realm.Prod, "Profil 1"); + localAccount = await runtime.accountServices.createAccount("Profil 1"); expect(localAccount).toBeDefined(); }); diff --git a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts index ab04c6b40..451354d74 100644 --- a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts +++ b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts @@ -29,7 +29,6 @@ import { IRelationship, IRelationshipTemplate, Message, - Realm, Relationship, RelationshipAuditLogEntryReason, RelationshipStatus, @@ -50,8 +49,7 @@ export class TestObjectFactory { publicKey: CryptoSignaturePublicKey.from({ algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519, publicKey: CoreBuffer.from("L1sPFQgS5CxgGs1ejBcWCQLCpeFXbRc1TQnSpuHQqDQ") - }), - realm: Realm.Prod + }) }), status: properties?.status ?? RelationshipStatus.Active, relationshipSecretId: properties?.relationshipSecretId ?? CoreId.from("RELSEC1"), @@ -292,8 +290,7 @@ export class TestObjectFactory { publicKey: CryptoSignaturePublicKey.from({ algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519, publicKey: CoreBuffer.fromBase64URL("aS-A8ywidL00DfBlZySOG_1-NdSBW38uGD1il_Ymk5g") - }), - realm: Realm.Prod + }) }, cache: { template: this.createIncomingIRelationshipTemplate(), @@ -331,8 +328,7 @@ export class TestObjectFactory { publicKey: CryptoSignaturePublicKey.from({ algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519, publicKey: CoreBuffer.fromBase64URL("aS-A8ywidL00DfBlZySOG_1-NdSBW38uGD1il_Ymk5g") - }), - realm: Realm.Prod + }) }, templateKey: RelationshipTemplatePublicKey.from({ id: CoreId.from("b9uMR7u7lsKLzRfVJNYb"), @@ -368,8 +364,7 @@ export class TestObjectFactory { publicKey: CryptoSignaturePublicKey.from({ algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519, publicKey: CoreBuffer.fromBase64URL("aS-A8ywidL00DfBlZySOG_1-NdSBW38uGD1il_Ymk5g") - }), - realm: Realm.Prod + }) }, templateKey: RelationshipTemplatePublicKey.from({ id: CoreId.from("b9uMR7u7lsKLzRfVJNYb"), diff --git a/packages/runtime/src/dataViews/DataViewExpander.ts b/packages/runtime/src/dataViews/DataViewExpander.ts index 3674a7a0a..9a7deb373 100644 --- a/packages/runtime/src/dataViews/DataViewExpander.ts +++ b/packages/runtime/src/dataViews/DataViewExpander.ts @@ -11,10 +11,10 @@ import { ErrorResponseItemJSON, FreeTextRequestItemJSON, GivenNameJSON, + IQLQueryJSON, IdentityAttribute, IdentityAttributeJSON, IdentityAttributeQueryJSON, - IQLQueryJSON, MailJSON, MiddleNameJSON, ProposeAttributeAcceptResponseItemJSON, @@ -47,14 +47,14 @@ import { ValueHints, ValueHintsJSON } from "@nmshd/content"; -import { CoreAddress, CoreId, IdentityController, Realm, Relationship, RelationshipStatus } from "@nmshd/transport"; +import { CoreAddress, CoreId, IdentityController, Relationship, RelationshipStatus } from "@nmshd/transport"; import { Inject } from "typescript-ioc"; import { AuthenticationRequestItemDVO, ConsentRequestItemDVO, CreateAttributeRequestItemDVO, - DeleteAttributeRequestItemDVO, DVOError, + DeleteAttributeRequestItemDVO, FileDVO, FreeTextRequestItemDVO, IdentityDVO, @@ -83,6 +83,8 @@ import { RelationshipTemplateDTO } from "../types"; import { RuntimeErrors } from "../useCases"; +import { DataViewObject } from "./DataViewObject"; +import { DataViewTranslateable } from "./DataViewTranslateable"; import { LocalAttributeDVO, LocalAttributeListenerDVO, @@ -92,8 +94,8 @@ import { PeerAttributeDVO, PeerRelationshipAttributeDVO, ProcessedAttributeQueryDVO, - ProcessedIdentityAttributeQueryDVO, ProcessedIQLQueryDVO, + ProcessedIdentityAttributeQueryDVO, ProcessedRelationshipAttributeQueryDVO, ProcessedThirdPartyRelationshipAttributeQueryDVO, RelationshipSettingDVO, @@ -116,8 +118,8 @@ import { AttributeQueryDVO, DraftIdentityAttributeDVO, DraftRelationshipAttributeDVO, - IdentityAttributeQueryDVO, IQLQueryDVO, + IdentityAttributeQueryDVO, RelationshipAttributeQueryDVO, ThirdPartyRelationshipAttributeQueryDVO } from "./content/AttributeDVOs"; @@ -135,10 +137,8 @@ import { ResponseItemGroupDVO, ShareAttributeAcceptResponseItemDVO } from "./content/ResponseItemDVOs"; -import { DataViewObject } from "./DataViewObject"; -import { DataViewTranslateable } from "./DataViewTranslateable"; import { MessageDVO, MessageStatus, RecipientDVO } from "./transport/MessageDVO"; -import { RelationshipDirection, RelationshipDVO } from "./transport/RelationshipDVO"; +import { RelationshipDVO, RelationshipDirection } from "./transport/RelationshipDVO"; export class DataViewExpander { public constructor( @@ -1506,7 +1506,6 @@ export class DataViewExpander { type: "IdentityDVO", name: name, initials: initials, - realm: Realm.Prod, description: "i18n://dvo.identity.self.description", isSelf: true, hasRelationship: false @@ -1522,7 +1521,6 @@ export class DataViewExpander { type: "IdentityDVO", name: name, initials: initials, - realm: Realm.Prod, description: "i18n://dvo.identity.unknown.description", isSelf: false, hasRelationship: false @@ -1664,7 +1662,6 @@ export class DataViewExpander { date: relationshipDVO.date, description: relationshipDVO.description, publicKey: relationship.peerIdentity.publicKey, - realm: relationship.peerIdentity.realm, initials, isSelf: false, hasRelationship: true, @@ -1698,7 +1695,6 @@ export class DataViewExpander { name: name, initials: initials, publicKey: "i18n://dvo.identity.publicKey.unknown", - realm: this.identityController.realm.toString(), description: "i18n://dvo.identity.unknown", isSelf: false, hasRelationship: false diff --git a/packages/runtime/src/dataViews/transport/IdentityDVO.ts b/packages/runtime/src/dataViews/transport/IdentityDVO.ts index ce624be38..a42f6c112 100644 --- a/packages/runtime/src/dataViews/transport/IdentityDVO.ts +++ b/packages/runtime/src/dataViews/transport/IdentityDVO.ts @@ -5,7 +5,6 @@ export interface IdentityDVO extends DataViewObject { type: "IdentityDVO"; publicKey?: string; - realm: string; initials: string; isSelf: boolean; hasRelationship: boolean; diff --git a/packages/runtime/src/types/transport/IdentityDTO.ts b/packages/runtime/src/types/transport/IdentityDTO.ts index 586568dfa..2dc791cf1 100644 --- a/packages/runtime/src/types/transport/IdentityDTO.ts +++ b/packages/runtime/src/types/transport/IdentityDTO.ts @@ -1,5 +1,4 @@ export interface IdentityDTO { address: string; publicKey: string; - realm: string; } diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index a39558389..d56221428 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -2920,7 +2920,7 @@ export const CanCreateOutgoingRequestRequest: any = { }, "AddressString": { "type": "string", - "pattern": "id1[A-Za-z0-9]{32,33}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" } } } @@ -3071,6 +3071,10 @@ export const CompleteIncomingRequestRequest: any = { "MessageIdString": { "type": "string", "pattern": "MSG[A-Za-z0-9]{17}" + }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" } } } @@ -5440,6 +5444,10 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq "type": "string", "pattern": "RLT[A-Za-z0-9]{17}" }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" + }, "MessageIdString": { "type": "string", "pattern": "MSG[A-Za-z0-9]{17}" @@ -10510,7 +10518,7 @@ export const CreateOutgoingRequestRequest: any = { }, "AddressString": { "type": "string", - "pattern": "id1[A-Za-z0-9]{32,33}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" } } } @@ -14548,7 +14556,7 @@ export const CreateAndShareRelationshipAttributeRequest: any = { }, "AddressString": { "type": "string", - "pattern": "id1[A-Za-z0-9]{32,33}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" } } } @@ -16704,7 +16712,7 @@ export const GetOwnSharedAttributesRequest: any = { }, "AddressString": { "type": "string", - "pattern": "id1[A-Za-z0-9]{32,33}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" }, "GetOwnSharedAttributeRequestQuery": { "type": "object", @@ -16944,7 +16952,7 @@ export const GetPeerSharedAttributesRequest: any = { }, "AddressString": { "type": "string", - "pattern": "id1[A-Za-z0-9]{32,33}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" }, "GetPeerSharedAttributesRequestQuery": { "type": "object", @@ -17292,7 +17300,7 @@ export const GetSharedVersionsOfRepositoryAttributeRequest: any = { }, "AddressString": { "type": "string", - "pattern": "id1[A-Za-z0-9]{32,33}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" } } } @@ -17346,7 +17354,7 @@ export const NotifyPeerAboutRepositoryAttributeSuccessionRequest: any = { }, "AddressString": { "type": "string", - "pattern": "id1[A-Za-z0-9]{32,33}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" } } } @@ -17413,7 +17421,7 @@ export const ShareRepositoryAttributeRequest: any = { }, "AddressString": { "type": "string", - "pattern": "id1[A-Za-z0-9]{32,33}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" }, "ISO8601DateTimeString": { "type": "string", @@ -21080,7 +21088,7 @@ export const CheckIdentityRequest: any = { }, "AddressString": { "type": "string", - "pattern": "id1[A-Za-z0-9]{32,33}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" } } } @@ -21380,7 +21388,7 @@ export const SendMessageRequest: any = { }, "AddressString": { "type": "string", - "pattern": "id1[A-Za-z0-9]{32,33}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" }, "FileIdString": { "type": "string", @@ -21398,7 +21406,7 @@ export const AcceptRelationshipRequest: any = { "properties": { "relationshipId": { "$ref": "#/definitions/RelationshipIdString" - }, + } }, "required": [ "relationshipId" @@ -21408,7 +21416,7 @@ export const AcceptRelationshipRequest: any = { "RelationshipIdString": { "type": "string", "pattern": "REL[A-Za-z0-9]{17}" - }, + } } } @@ -21508,7 +21516,7 @@ export const GetRelationshipByAddressRequest: any = { }, "AddressString": { "type": "string", - "pattern": "id1[A-Za-z0-9]{32,33}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" } } } diff --git a/packages/runtime/src/useCases/common/validation/ValidatableStrings.ts b/packages/runtime/src/useCases/common/validation/ValidatableStrings.ts index a8e784884..26e342c4c 100644 --- a/packages/runtime/src/useCases/common/validation/ValidatableStrings.ts +++ b/packages/runtime/src/useCases/common/validation/ValidatableStrings.ts @@ -1,5 +1,5 @@ /** - * @pattern id1[A-Za-z0-9]{32,33} + * @pattern did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22} */ export type AddressString = string; diff --git a/packages/runtime/src/useCases/transport/devices/DeviceMapper.ts b/packages/runtime/src/useCases/transport/devices/DeviceMapper.ts index aa72362f4..a0278fb04 100644 --- a/packages/runtime/src/useCases/transport/devices/DeviceMapper.ts +++ b/packages/runtime/src/useCases/transport/devices/DeviceMapper.ts @@ -1,5 +1,5 @@ import { CryptoSecretKey, CryptoSignaturePrivateKey, CryptoSignaturePublicKey } from "@nmshd/crypto"; -import { CoreAddress, CoreDate, CoreId, Device, DeviceSharedSecret, Realm } from "@nmshd/transport"; +import { CoreAddress, CoreDate, CoreId, Device, DeviceSharedSecret } from "@nmshd/transport"; import { DeviceDTO, DeviceOnboardingInfoDTO } from "../../../types"; export class DeviceMapper { @@ -35,8 +35,7 @@ export class DeviceMapper { identityPrivateKey: deviceSharedSecret.identityPrivateKey ? deviceSharedSecret.identityPrivateKey.toBase64(false) : undefined, identity: { address: deviceSharedSecret.identity.address.toString(), - publicKey: deviceSharedSecret.identity.publicKey.toBase64(false), - realm: deviceSharedSecret.identity.realm.toString() + publicKey: deviceSharedSecret.identity.publicKey.toBase64(false) }, password: deviceSharedSecret.password, username: deviceSharedSecret.username, @@ -57,8 +56,7 @@ export class DeviceMapper { identityPrivateKey: deviceOnboardingDTO.identityPrivateKey ? CryptoSignaturePrivateKey.fromBase64(deviceOnboardingDTO.identityPrivateKey) : undefined, identity: { address: CoreAddress.from(deviceOnboardingDTO.identity.address), - publicKey: CryptoSignaturePublicKey.fromBase64(deviceOnboardingDTO.identity.publicKey), - realm: deviceOnboardingDTO.identity.realm as Realm + publicKey: CryptoSignaturePublicKey.fromBase64(deviceOnboardingDTO.identity.publicKey) }, password: deviceOnboardingDTO.password, username: deviceOnboardingDTO.username, diff --git a/packages/runtime/src/useCases/transport/relationships/RelationshipMapper.ts b/packages/runtime/src/useCases/transport/relationships/RelationshipMapper.ts index 5ecdaab15..ea3de362f 100644 --- a/packages/runtime/src/useCases/transport/relationships/RelationshipMapper.ts +++ b/packages/runtime/src/useCases/transport/relationships/RelationshipMapper.ts @@ -16,8 +16,7 @@ export class RelationshipMapper { peer: relationship.peer.address.toString(), peerIdentity: { address: relationship.peer.address.toString(), - publicKey: relationship.peer.publicKey.toBase64(false), - realm: relationship.peer.realm + publicKey: relationship.peer.publicKey.toBase64(false) }, auditLog: relationship.cache.auditLog.map((entry) => this.toAuditLogEntryDTO(entry)), creationContent: relationship.cache.creationContent?.toJSON(), diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index 9ac1fddaf..7446a5e44 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -23,8 +23,8 @@ import { DeleteThirdPartyOwnedRelationshipAttributeAndNotifyPeerUseCase, ExecuteIdentityAttributeQueryUseCase, ExecuteRelationshipAttributeQueryUseCase, - GetAttributesUseCase, GetAttributeUseCase, + GetAttributesUseCase, GetOwnSharedAttributesUseCase, GetPeerSharedAttributesUseCase, GetRepositoryAttributesUseCase, @@ -43,6 +43,8 @@ import { ThirdPartyOwnedRelationshipAttributeDeletedByPeerEvent } from "../../src"; import { + RuntimeServiceProvider, + TestRuntimeServices, acceptIncomingShareAttributeRequest, establishRelationship, executeFullCreateAndShareRelationshipAttributeFlow, @@ -51,9 +53,7 @@ import { executeFullRequestAndShareThirdPartyRelationshipAttributeFlow, executeFullShareRepositoryAttributeFlow, executeFullSucceedRepositoryAttributeAndNotifyPeerFlow, - RuntimeServiceProvider, syncUntilHasMessageWithNotification, - TestRuntimeServices, waitForRecipientToReceiveNotification } from "../lib"; @@ -1434,7 +1434,7 @@ describe("Get (shared) versions of attribute", () => { test("should return an empty list calling getSharedVersionsOfRepositoryAttribute with a nonexistent peer", async () => { const result = await services1.consumption.attributes.getSharedVersionsOfRepositoryAttribute({ attributeId: sRAVersion2.id, - peers: ["id1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] + peers: ["did:e:a:dids:00000000000000000000000"] }); expect(result.isSuccess).toBe(true); const returnedVersions = result.value; diff --git a/packages/runtime/test/transport/account.test.ts b/packages/runtime/test/transport/account.test.ts index bf4d60013..e83b1b2ac 100644 --- a/packages/runtime/test/transport/account.test.ts +++ b/packages/runtime/test/transport/account.test.ts @@ -72,9 +72,8 @@ describe("IdentityInfo", () => { expect(identityInfoResult).toBeSuccessful(); const identityInfo = identityInfoResult.value; - expect(identityInfo.address.length).toBeLessThanOrEqual(36); - expect(identityInfo.address.length).toBeGreaterThanOrEqual(35); - expect(identityInfo.address).toMatch(/^id1/); + expect(identityInfo.address.length).toBeGreaterThanOrEqual(30); + expect(identityInfo.address).toMatch(/^did:e:/); expect(identityInfo.publicKey).toHaveLength(82); }); }); diff --git a/packages/transport/src/core/CoreErrors.ts b/packages/transport/src/core/CoreErrors.ts index f95a0ad61..24d23e543 100644 --- a/packages/transport/src/core/CoreErrors.ts +++ b/packages/transport/src/core/CoreErrors.ts @@ -166,10 +166,6 @@ class General { return new CoreError("error.transport.notSupported", "The method is not yet supported."); } - public realmLength() { - return new CoreError("error.transport.identity.realmLength", "Realm must be of length 3."); - } - public invalidTruncatedReference() { return new CoreError("error.transport.files.invalidTruncatedReference", "invalid truncated reference"); } diff --git a/packages/transport/src/core/Transport.ts b/packages/transport/src/core/Transport.ts index 716f57013..de3663065 100644 --- a/packages/transport/src/core/Transport.ts +++ b/packages/transport/src/core/Transport.ts @@ -6,7 +6,6 @@ import { SodiumWrapper } from "@nmshd/crypto"; import { AgentOptions } from "http"; import { AgentOptions as HTTPSAgentOptions } from "https"; import _ from "lodash"; -import { Realm } from "../modules/accounts/data/Realm"; import { CoreErrors } from "./CoreErrors"; import { TransportContext } from "./TransportContext"; import { TransportError } from "./TransportError"; @@ -25,7 +24,6 @@ export interface IConfig { platformMaxUnencryptedFileSize: number; platformAdditionalHeaders?: Record; baseUrl: string; - realm: Realm; datawalletEnabled: boolean; httpAgent: AgentOptions; httpsAgent: HTTPSAgentOptions; @@ -41,7 +39,6 @@ export interface IConfigOverwrite { platformMaxUnencryptedFileSize?: number; platformAdditionalHeaders?: Record; baseUrl: string; - realm?: Realm; datawalletEnabled?: boolean; httpAgent?: AgentOptions; httpsAgent?: HTTPSAgentOptions; @@ -65,7 +62,6 @@ export class Transport { platformMaxRedirects: 10, platformMaxUnencryptedFileSize: 10 * 1024 * 1024, baseUrl: "", - realm: Realm.Prod, datawalletEnabled: false, httpAgent: { keepAlive: true, @@ -110,10 +106,6 @@ export class Transport { if (this._config.supportedIdentityVersion < 1) { throw new TransportError("The given supported identity version is invalid. The value must be 1 or higher."); } - - if (this._config.realm.length !== 3) { - throw CoreErrors.general.realmLength(); - } } public async init(): Promise { diff --git a/packages/transport/src/modules/accounts/AccountController.ts b/packages/transport/src/modules/accounts/AccountController.ts index 745d55692..f25da0aa0 100644 --- a/packages/transport/src/modules/accounts/AccountController.ts +++ b/packages/transport/src/modules/accounts/AccountController.ts @@ -278,7 +278,7 @@ export class AccountController { CoreCrypto.generateSecretKey(), // Generate address locally - IdentityUtil.createAddress(identityKeypair.publicKey, this._config.realm), + IdentityUtil.createAddress(identityKeypair.publicKey, new URL(this._config.baseUrl).hostname), this.fetchDeviceInfo() ]); @@ -299,7 +299,6 @@ export class AccountController { const identity = Identity.from({ address: CoreAddress.from(deviceResponse.address), - realm: this._config.realm, publicKey: identityKeypair.publicKey }); diff --git a/packages/transport/src/modules/accounts/IdentityController.ts b/packages/transport/src/modules/accounts/IdentityController.ts index 467da11cf..8242cda18 100644 --- a/packages/transport/src/modules/accounts/IdentityController.ts +++ b/packages/transport/src/modules/accounts/IdentityController.ts @@ -4,7 +4,6 @@ import { ControllerName, CoreAddress, CoreCrypto, CoreErrors, TransportControlle import { AccountController } from "../accounts/AccountController"; import { DeviceSecretType } from "../devices/DeviceSecretController"; import { Identity } from "./data/Identity"; -import { Realm } from "./data/Realm"; export class IdentityController extends TransportController { public get address(): CoreAddress { @@ -15,10 +14,6 @@ export class IdentityController extends TransportController { return this._identity.publicKey; } - public get realm(): Realm { - return this._identity.realm; - } - public get identity(): Identity { return this._identity; } diff --git a/packages/transport/src/modules/accounts/IdentityUtil.ts b/packages/transport/src/modules/accounts/IdentityUtil.ts index 9115e772c..a3afc945f 100644 --- a/packages/transport/src/modules/accounts/IdentityUtil.ts +++ b/packages/transport/src/modules/accounts/IdentityUtil.ts @@ -1,56 +1,53 @@ -import { CoreBuffer, CryptoHash, CryptoHashAlgorithm, ICryptoSignaturePublicKey } from "@nmshd/crypto"; -import { CoreAddress, CoreErrors } from "../../core"; +import { CoreBuffer, CryptoHash, CryptoHashAlgorithm, Encoding, ICryptoSignaturePublicKey } from "@nmshd/crypto"; +import { CoreAddress } from "../../core"; -export class IdentityUtil { - public static async createAddress(publicKey: ICryptoSignaturePublicKey, realm: string): Promise { - if (realm.length !== 3) throw CoreErrors.general.realmLength(); +const enmeshedAddressDIDPrefix = "did:e:"; +export class IdentityUtil { + public static async createAddress(publicKey: ICryptoSignaturePublicKey, backboneHostname: string): Promise { const sha512buffer = await CryptoHash.hash(publicKey.publicKey, CryptoHashAlgorithm.SHA512); const hash = await CryptoHash.hash(sha512buffer, CryptoHashAlgorithm.SHA256); - const hashedPublicKey = new CoreBuffer(hash.buffer.slice(0, 20)); - - const checksumSource = CoreBuffer.fromUtf8(realm); - checksumSource.append(hashedPublicKey); + const hashedPublicKey = new CoreBuffer(hash.buffer.slice(0, 10)); + const identityPart = hashedPublicKey.toString(Encoding.Hex); - const addressChecksum1 = await CryptoHash.hash(checksumSource, CryptoHashAlgorithm.SHA512); - const checksumHash = await CryptoHash.hash(addressChecksum1, CryptoHashAlgorithm.SHA256); - const checksum = new CoreBuffer(checksumHash.buffer.slice(0, 4)); + const checksumSource = CoreBuffer.fromUtf8(`${enmeshedAddressDIDPrefix}${backboneHostname}:dids:${hashedPublicKey.toString(Encoding.Hex)}`); + const checksumHash = await CryptoHash.hash(checksumSource, CryptoHashAlgorithm.SHA256); + const checksum = new CoreBuffer(checksumHash.buffer.slice(0, 1)); - const concatenation = hashedPublicKey; - concatenation.append(checksum); - - const addressString = realm + concatenation.toBase58(); + const addressString = `${enmeshedAddressDIDPrefix}${backboneHostname}:dids:${identityPart}${checksum.toString(Encoding.Hex)}`; const addressObj = CoreAddress.from({ address: addressString }); return addressObj; } - public static async checkAddress(address: CoreAddress, publicKey?: ICryptoSignaturePublicKey, realm = "id1"): Promise { + public static async checkAddress(address: CoreAddress, backboneHostname: string, publicKey?: ICryptoSignaturePublicKey): Promise { const str = address.toString(); - const strRealm = str.substr(0, 3); - if (realm && strRealm !== realm) { + + const prefixLength = enmeshedAddressDIDPrefix.length; + const strWithoutPrefix = str.substring(prefixLength); + if (!strWithoutPrefix.startsWith(backboneHostname)) { return false; } - const strAddress = str.substr(3); + const strAddress = str.substring(str.length - 22); + const strHashedPublicKey = strAddress.substring(0, 20); + const strPrefixRealm = str.substring(0, str.length - 22); - const addressBuffer = CoreBuffer.fromBase58(strAddress).buffer; + const addressBuffer = CoreBuffer.fromString(strAddress, Encoding.Hex).buffer; + const sha256Array = addressBuffer.slice(0, addressBuffer.byteLength - 1); + const checksumArray = addressBuffer.slice(addressBuffer.byteLength - 1, addressBuffer.byteLength); - const sha256Array = addressBuffer.slice(0, addressBuffer.byteLength - 4); - const checksumArray = addressBuffer.slice(addressBuffer.byteLength - 4, addressBuffer.byteLength); + const checksumBuffer = CoreBuffer.fromUtf8(strPrefixRealm + strHashedPublicKey); - const checksumBuffer = CoreBuffer.fromUtf8(strRealm); - checksumBuffer.append(new CoreBuffer(sha256Array)); - const addressChecksum1 = await CryptoHash.hash(checksumBuffer, CryptoHashAlgorithm.SHA512); - const addressChecksum2 = await CryptoHash.hash(addressChecksum1, CryptoHashAlgorithm.SHA256); - const firstBytesOfChecksum = new CoreBuffer(addressChecksum2.buffer.slice(0, 4)); - if (!firstBytesOfChecksum.equals(new CoreBuffer(checksumArray))) { + const addressChecksum = await CryptoHash.hash(checksumBuffer, CryptoHashAlgorithm.SHA256); + const firstByteOfChecksum = new CoreBuffer(addressChecksum.buffer.slice(0, 1)); + if (!firstByteOfChecksum.equals(new CoreBuffer(checksumArray))) { return false; } if (publicKey) { const sha512buffer = await CryptoHash.hash(publicKey.publicKey, CryptoHashAlgorithm.SHA512); let sha256buffer = await CryptoHash.hash(sha512buffer, CryptoHashAlgorithm.SHA256); - sha256buffer = new CoreBuffer(sha256buffer.buffer.slice(0, 20)); + sha256buffer = new CoreBuffer(sha256buffer.buffer.slice(0, 10)); if (!sha256buffer.equals(new CoreBuffer(sha256Array))) { // Hash doesn't match with given public key. return false; diff --git a/packages/transport/src/modules/accounts/data/Identity.ts b/packages/transport/src/modules/accounts/data/Identity.ts index db21127a4..796e67a9e 100644 --- a/packages/transport/src/modules/accounts/data/Identity.ts +++ b/packages/transport/src/modules/accounts/data/Identity.ts @@ -1,12 +1,10 @@ import { serialize, type, validate } from "@js-soft/ts-serval"; import { CryptoSignaturePublicKey, ICryptoSignaturePublicKey } from "@nmshd/crypto"; import { CoreAddress, CoreSerializable, ICoreSerializable } from "../../../core"; -import { Realm } from "./Realm"; export interface IIdentity extends ICoreSerializable { address: CoreAddress; publicKey: ICryptoSignaturePublicKey; - realm: Realm; } @type("Identity") @@ -19,10 +17,6 @@ export class Identity extends CoreSerializable implements IIdentity { @serialize() public publicKey: CryptoSignaturePublicKey; - @validate() - @serialize() - public realm: Realm; - public static from(value: IIdentity): Identity { return this.fromAny(value); } diff --git a/packages/transport/src/modules/accounts/data/IdentitySecretCredentials.ts b/packages/transport/src/modules/accounts/data/IdentitySecretCredentials.ts index 3101d1ceb..57b569885 100644 --- a/packages/transport/src/modules/accounts/data/IdentitySecretCredentials.ts +++ b/packages/transport/src/modules/accounts/data/IdentitySecretCredentials.ts @@ -1,21 +1,15 @@ import { serialize, type, validate } from "@js-soft/ts-serval"; import { CryptoSecretKey, CryptoSignaturePrivateKey, CryptoSignaturePublicKey, ICryptoSecretKey, ICryptoSignaturePrivateKey, ICryptoSignaturePublicKey } from "@nmshd/crypto"; import { CoreSerializable, ICoreSerializable } from "../../../core"; -import { Realm } from "./Realm"; export interface IIdentitySecretCredentials extends ICoreSerializable { publicKey?: ICryptoSignaturePublicKey; - realm: Realm; synchronizationKey: ICryptoSecretKey; privateKey?: ICryptoSignaturePrivateKey; } @type("IdentitySecretCredentials") export class IdentitySecretCredentials extends CoreSerializable implements IIdentitySecretCredentials { - @validate() - @serialize() - public realm: Realm; - @validate({ nullable: true }) @serialize() public publicKey?: CryptoSignaturePublicKey; diff --git a/packages/transport/src/modules/accounts/data/Realm.ts b/packages/transport/src/modules/accounts/data/Realm.ts deleted file mode 100644 index 07dd82402..000000000 --- a/packages/transport/src/modules/accounts/data/Realm.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum Realm { - Dev = "dev", - Stage = "id0", - Prod = "id1" -} diff --git a/packages/transport/src/modules/index.ts b/packages/transport/src/modules/index.ts index c26c68c52..4cb9e0609 100644 --- a/packages/transport/src/modules/index.ts +++ b/packages/transport/src/modules/index.ts @@ -1,13 +1,12 @@ export * from "./accounts/AccountController"; +export * from "./accounts/IdentityController"; +export * from "./accounts/IdentityDeletionProcessController"; +export * from "./accounts/IdentityUtil"; export * from "./accounts/backbone/IdentityClient"; export * from "./accounts/backbone/IdentityDeletionProcessClient"; export * from "./accounts/data/Identity"; export * from "./accounts/data/IdentityDeletionProcess"; export * from "./accounts/data/IdentityDeletionProcessStatus"; -export * from "./accounts/data/Realm"; -export * from "./accounts/IdentityController"; -export * from "./accounts/IdentityDeletionProcessController"; -export * from "./accounts/IdentityUtil"; export * from "./certificates/CertificateController"; export * from "./certificates/CertificateIssuer"; export * from "./certificates/CertificateValidator"; @@ -25,42 +24,55 @@ export * from "./certificates/data/items/CertificatePrivateAttributeItem"; export * from "./certificates/data/items/CertificatePrivateAttributeItemSource"; export * from "./certificates/data/items/CertificatePublicAttributeItem"; export * from "./certificates/data/items/CertificateRoleItem"; +export * from "./challenges/ChallengeController"; export * from "./challenges/backbone/ChallengeAuthClient"; export * from "./challenges/backbone/ChallengeClient"; -export * from "./challenges/ChallengeController"; export * from "./challenges/data/Challenge"; export * from "./challenges/data/ChallengeSigned"; +export * from "./devices/DeviceController"; +export * from "./devices/DeviceSecretController"; +export * from "./devices/DevicesController"; export * from "./devices/backbone/BackbonePostDevices"; export * from "./devices/backbone/DeviceAuthClient"; export * from "./devices/backbone/DeviceClient"; -export * from "./devices/DeviceController"; -export * from "./devices/DevicesController"; -export * from "./devices/DeviceSecretController"; export * from "./devices/local/Device"; export * from "./devices/local/DeviceSecretCredentials"; export * from "./devices/local/SendDeviceParameters"; export * from "./devices/transmission/DeviceSharedSecret"; +export * from "./files/FileController"; export * from "./files/backbone/BackboneGetFiles"; export * from "./files/backbone/BackbonePostFiles"; export * from "./files/backbone/FileClient"; -export * from "./files/FileController"; export * from "./files/local/CachedFile"; export * from "./files/local/File"; export * from "./files/local/SendFileParameters"; export * from "./files/transmission/FileMetadata"; export * from "./files/transmission/FileReference"; +export * from "./messages/MessageController"; export * from "./messages/backbone/BackboneGetMessages"; export * from "./messages/backbone/BackbonePostMessages"; export * from "./messages/backbone/MessageClient"; export * from "./messages/local/CachedMessage"; export * from "./messages/local/Message"; export * from "./messages/local/SendMessageParameters"; -export * from "./messages/MessageController"; export * from "./messages/transmission/MessageContentWrapper"; export * from "./messages/transmission/MessageEnvelope"; export * from "./messages/transmission/MessageEnvelopeRecipient"; export * from "./messages/transmission/MessageSignature"; export * from "./messages/transmission/MessageSigned"; +export * from "./relationshipTemplates/RelationshipTemplateController"; +export * from "./relationshipTemplates/backbone/BackboneGetRelationshipTemplates"; +export * from "./relationshipTemplates/backbone/BackbonePostRelationshipTemplates"; +export * from "./relationshipTemplates/backbone/RelationshipTemplateClient"; +export * from "./relationshipTemplates/local/CachedRelationshipTemplate"; +export * from "./relationshipTemplates/local/RelationshipTemplate"; +export * from "./relationshipTemplates/local/SendRelationshipTemplateParameters"; +export * from "./relationshipTemplates/transmission/RelationshipTemplateContentWrapper"; +export * from "./relationshipTemplates/transmission/RelationshipTemplatePublicKey"; +export * from "./relationshipTemplates/transmission/RelationshipTemplateReference"; +export * from "./relationshipTemplates/transmission/RelationshipTemplateSigned"; +export * from "./relationships/RelationshipSecretController"; +export * from "./relationships/RelationshipsController"; export * from "./relationships/backbone/BackboneGetRelationships"; export * from "./relationships/backbone/BackbonePostRelationship"; export * from "./relationships/backbone/RelationshipClient"; @@ -69,8 +81,6 @@ export * from "./relationships/local/Relationship"; export * from "./relationships/local/RelationshipAuditLog"; export * from "./relationships/local/RelationshipAuditLogEntry"; export * from "./relationships/local/SendRelationshipParameters"; -export * from "./relationships/RelationshipsController"; -export * from "./relationships/RelationshipSecretController"; export * from "./relationships/transmission/RelationshipAuditLog"; export * from "./relationships/transmission/RelationshipStatus"; export * from "./relationships/transmission/requests/RelationshipCreationContentCipher"; @@ -79,20 +89,14 @@ export * from "./relationships/transmission/requests/RelationshipCreationContent export * from "./relationships/transmission/responses/RelationshipCreationResponseContentCipher"; export * from "./relationships/transmission/responses/RelationshipCreationResponseContentSigned"; export * from "./relationships/transmission/responses/RelationshipCreationResponseContentWrapper"; -export * from "./relationshipTemplates/backbone/BackboneGetRelationshipTemplates"; -export * from "./relationshipTemplates/backbone/BackbonePostRelationshipTemplates"; -export * from "./relationshipTemplates/backbone/RelationshipTemplateClient"; -export * from "./relationshipTemplates/local/CachedRelationshipTemplate"; -export * from "./relationshipTemplates/local/RelationshipTemplate"; -export * from "./relationshipTemplates/local/SendRelationshipTemplateParameters"; -export * from "./relationshipTemplates/RelationshipTemplateController"; -export * from "./relationshipTemplates/transmission/RelationshipTemplateContentWrapper"; -export * from "./relationshipTemplates/transmission/RelationshipTemplatePublicKey"; -export * from "./relationshipTemplates/transmission/RelationshipTemplateReference"; -export * from "./relationshipTemplates/transmission/RelationshipTemplateSigned"; +export * from "./secrets/SecretController"; export * from "./secrets/data/SecretContainerCipher"; export * from "./secrets/data/SecretContainerPlain"; -export * from "./secrets/SecretController"; +export * from "./sync/ChangedItems"; +export * from "./sync/DatawalletModificationsProcessor"; +export { SyncProgressCallback as SyncPercentageCallback, SyncStep } from "./sync/SyncCallback"; +export * from "./sync/SyncController"; +export * from "./sync/SynchronizedCollection"; export * from "./sync/backbone/BackboneDatawalletModification"; export * from "./sync/backbone/BackboneExternalEvent"; export * from "./sync/backbone/CreateDatawalletModifications"; @@ -101,21 +105,16 @@ export * from "./sync/backbone/GetDatawallet"; export * from "./sync/backbone/GetDatawalletModifications"; export * from "./sync/backbone/StartSyncRun"; export * from "./sync/backbone/SyncClient"; -export * from "./sync/ChangedItems"; export * from "./sync/data/ExternalEvent"; -export * from "./sync/DatawalletModificationsProcessor"; export * from "./sync/local/DatawalletModification"; -export { SyncProgressCallback as SyncPercentageCallback, SyncStep } from "./sync/SyncCallback"; -export * from "./sync/SyncController"; -export * from "./sync/SynchronizedCollection"; export * from "./tokens/AnonymousTokenController"; +export * from "./tokens/TokenController"; export * from "./tokens/backbone/BackboneGetTokens"; export * from "./tokens/backbone/BackbonePostTokens"; export * from "./tokens/backbone/TokenClient"; export * from "./tokens/local/CachedToken"; export * from "./tokens/local/SendTokenParameters"; export * from "./tokens/local/Token"; -export * from "./tokens/TokenController"; export * from "./tokens/transmission/TokenContentDeviceSharedSecret"; export * from "./tokens/transmission/TokenContentFile"; export * from "./tokens/transmission/TokenContentRelationshipTemplate"; diff --git a/packages/transport/test/utils/IdentityGenerator.test.ts b/packages/transport/test/utils/IdentityGenerator.test.ts index 6a1517019..3e33896fd 100644 --- a/packages/transport/test/utils/IdentityGenerator.test.ts +++ b/packages/transport/test/utils/IdentityGenerator.test.ts @@ -9,10 +9,10 @@ describe("IdentityGeneratorTest", function () { }); test("should create a correct address object", async function () { - const address = await IdentityUtil.createAddress(kp.publicKey, "id1"); + const address = await IdentityUtil.createAddress(kp.publicKey, "prod.enmeshed.eu"); expect(address).toBeDefined(); expect(address.address).toBeDefined(); - expect(address.address.substr(0, 3)).toBe("id1"); + expect(address.address.startsWith("did:e:prod.enmeshed.eu")).toBe(true); }); test("should create a correct address object (test 0)", async function () { @@ -22,63 +22,63 @@ describe("IdentityGeneratorTest", function () { publicKey: buf, algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519 }); - const address = await IdentityUtil.createAddress(pk, "id1"); + const address = await IdentityUtil.createAddress(pk, "prod.enmeshed.eu"); expect(address).toBeDefined(); expect(address.address).toBeDefined(); - expect(address.address).toBe("id18uSgVGTSNqECvt1DJM3bZg6U8p6RSjott"); + expect(address.address).toBe("did:e:prod.enmeshed.eu:dids:56b3f2a0c202e27229aa87"); }); test("should create a correct address object (testcases)", async function () { const addresses = [ { - realm: "id1", + backboneHostname: "prod.enmeshed.eu", publicKey: "fj0o9eOiPRswTZL6j9lE9TRvpDDnPRMF0gJeahz/W2c=", - address: "id1QF24Gk2DfqCywRS7NpeH5iu7D4xvu6qv1" + address: "did:e:prod.enmeshed.eu:dids:fef1992c5e529adc413288" }, { - realm: "id1", + backboneHostname: "prod.enmeshed.eu", publicKey: "jRxGfZtQ8a90TmKCGk+dhuX1CBjgoXuldhNPwrjpWsw=", - address: "id1HwY1TuyVBp3CmY3h18yTt1CKyu5qwB9wj" + address: "did:e:prod.enmeshed.eu:dids:b9d25bd0a2bbd3aa48437c" }, { - realm: "id1", + backboneHostname: "prod.enmeshed.eu", publicKey: "PEODpwvi7KxIVa4qeUXia9apMFvPMktdDHiDitlfbjE=", - address: "id1LMp4k1XwxZ3WFXdAn9y12tv1ofe5so4kM" + address: "did:e:prod.enmeshed.eu:dids:d459ff2144f0eac7aff554" }, { - realm: "id1", + backboneHostname: "prod.enmeshed.eu", publicKey: "mJGmNbxiVZAPToRuk9O3NvdfsWl6V+7wzIc+/57bU08=", - address: "id1McegXycvRoiJppS2LG25phn3jNveckFUL" + address: "did:e:prod.enmeshed.eu:dids:e2208784ee2769c5d9684d" }, { - realm: "id1", + backboneHostname: "prod.enmeshed.eu", publicKey: "l68K/zdNp1VLoswcHAqN6QUFwCMU6Yvzf7XiW2m1hRY=", - address: "id193k6K5cJr94WJEWYb6Kei8zp5CGPyrQLS" + address: "did:e:prod.enmeshed.eu:dids:5845cf29fbda2897892a9a" }, { - realm: "id1", + backboneHostname: "prod.enmeshed.eu", publicKey: "Gl8XTo8qFuUM+ksXixwp4g/jf3H/hU1F8ETuYaHCM5I=", - address: "id1BLrHAgDpimtLcGJGssMSm7bJHsvVe7CN" + address: "did:e:prod.enmeshed.eu:dids:01f4bab09d757578bb4994" }, { - realm: "id1", + backboneHostname: "prod.enmeshed.eu", publicKey: "rIS4kAzHXT7GgCA6Qm1ANlwM3x12QMSkeprHb6tjPyc=", - address: "id1NjGvLfWPrQ34PXWRBNiTfXv9DFiDQHExx" + address: "did:e:prod.enmeshed.eu:dids:ee5966a158f1dc4de5bd5c" }, { - realm: "id1", + backboneHostname: "prod.enmeshed.eu", publicKey: "hg/cbeBvfNrMiJ0dW1AtWC4IQwG4gkuhzG2+z6bAoRU=", - address: "id1Gda4aTXiBX9Pyc8UnmLaG44cX46umjnea" + address: "did:e:prod.enmeshed.eu:dids:ab7475ba4070f29ce286fd" }, { - realm: "id1", + backboneHostname: "prod.enmeshed.eu", publicKey: "kId+qWen/lKeTdyxcIQhkzvvvTU8wIJECfWUWbmRQRY=", - address: "id17RDEphijMPFGLbhqLWWgJfatBANMruC8f" + address: "did:e:prod.enmeshed.eu:dids:4664f42d7ca6480db07fdb" }, { - realm: "id1", + backboneHostname: "prod.enmeshed.eu", publicKey: "NcqlzTEpSlKX9gmNBv41EjPRHpaNYwt0bxqh1bgyJzA=", - address: "id19meHs4Di7JYNXoRPx9bFD6FUcpHFo3mBi" + address: "did:e:prod.enmeshed.eu:dids:60326ff5075e0d7378990c" } ]; @@ -89,37 +89,37 @@ describe("IdentityGeneratorTest", function () { publicKey: buf, algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519 }); - const address = await IdentityUtil.createAddress(pk, testcase.realm); + const address = await IdentityUtil.createAddress(pk, testcase.backboneHostname); expect(address.toString()).toStrictEqual(testcase.address); } }); - test("should positively check a correct address object (without giving public key and realm)", async function () { - const address = await IdentityUtil.createAddress(kp.publicKey, "id1"); - const valid = await IdentityUtil.checkAddress(address); + test("should positively check a correct address object (without giving public key)", async function () { + const address = await IdentityUtil.createAddress(kp.publicKey, "prod.enmeshed.eu"); + const valid = await IdentityUtil.checkAddress(address, "prod.enmeshed.eu"); expect(valid).toBe(true); }); test("should positively check a correct address object (giving public key)", async function () { - const address = await IdentityUtil.createAddress(kp.publicKey, "id1"); - const valid = await IdentityUtil.checkAddress(address, kp.publicKey); + const address = await IdentityUtil.createAddress(kp.publicKey, "prod.enmeshed.eu"); + const valid = await IdentityUtil.checkAddress(address, "prod.enmeshed.eu", kp.publicKey); expect(valid).toBe(true); }); - test("should positively check a correct address object (giving public key and realm)", async function () { - const address = await IdentityUtil.createAddress(kp.publicKey, "id1"); - const valid = await IdentityUtil.checkAddress(address, kp.publicKey, "id1"); + test("should positively check a correct address object (giving public key and backboneHostname)", async function () { + const address = await IdentityUtil.createAddress(kp.publicKey, "prod.enmeshed.eu"); + const valid = await IdentityUtil.checkAddress(address, "prod.enmeshed.eu", kp.publicKey); expect(valid).toBe(true); }); - test("should negatively check an incorrect address object (wrong realm)", async function () { - const address = await IdentityUtil.createAddress(kp.publicKey, "id1"); - const valid = await IdentityUtil.checkAddress(address, kp.publicKey, "id2"); + test("should negatively check an incorrect address object (wrong backboneHostname)", async function () { + const address = await IdentityUtil.createAddress(kp.publicKey, "prod.enmeshed.eu"); + const valid = await IdentityUtil.checkAddress(address, "dev.enmeshed.eu", kp.publicKey); expect(valid).toBe(false); }); test("should negatively check an incorrect address object (wrong checksum)", async function () { - const address = await IdentityUtil.createAddress(kp.publicKey, "id1"); + const address = await IdentityUtil.createAddress(kp.publicKey, "prod.enmeshed.eu"); const index = 5; let replaceWith = "b"; const currentString = address.address.substr(index, replaceWith.length); @@ -128,14 +128,14 @@ describe("IdentityGeneratorTest", function () { } const wrongaddress = address.address.substr(0, index) + replaceWith + address.address.substr(index + replaceWith.length); address.address = wrongaddress; - const valid = await IdentityUtil.checkAddress(address, kp.publicKey, "id1"); + const valid = await IdentityUtil.checkAddress(address, "prod.enmeshed.eu", kp.publicKey); expect(valid).toBe(false); }); test("should negatively check an incorrect address object (wrong publicKey)", async function () { const kp2 = await CoreCrypto.generateSignatureKeypair(); - const address = await IdentityUtil.createAddress(kp.publicKey, "id1"); - const valid = await IdentityUtil.checkAddress(address, kp2.publicKey, "id1"); + const address = await IdentityUtil.createAddress(kp.publicKey, "prod.enmeshed.eu"); + const valid = await IdentityUtil.checkAddress(address, "prod.enmeshed.eu", kp2.publicKey); expect(valid).toBe(false); }); // eslint-disable-next-line jest/no-commented-out-tests From 36257c3369cfc1d9b5f09cbfcdc0f0b4d472893e Mon Sep 17 00:00:00 2001 From: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com> Date: Wed, 22 May 2024 15:09:33 +0200 Subject: [PATCH 03/40] Feature/relationship termination (#117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add backbone routes to client * feat: change relationship statuses * feat: change audit log statuses/relationship statuses * feat: add non-deleting routes to relationships controller * feat: add non-deleting relationship use cases * test: add non-deleting transport tests * feat: add relationshipDeleted transport event * feat: add deletion to attributes controller and relationships controller * refactor: name relationship operation instead of response * chore: check for an active relationship before challenge creation * chore: check for active relationship before creating request * refactor: relationshipDeletedEvent to ...DeletedBySelf... * fix: status check in relationships controller before deletion * feat: delete messages and message recipients * Revert "Merge branch 'release/v5' into feature/relationship-termination" This reverts commit d3f5f68167079b69dcb63d7df96706a0705965e8, reversing changes made to 39e81d99f67b3d3614305cd95543ef68c6e37f82. * fix: allow deciding a request on new relationship * fix: allow creating a draft request on no relationship * test: messaging etc. blocked after relationship termination * chore: add relationship deletion to checkIdentity use case * chore: apply the incoming requests controller constructor change * fix: complete non-deleting operations in relationships controller * chore: remove commits to relationship deletion "Simple" commits (e. g. type modifications) remain * test: use correct error for no active relationship for message * chore: check for pending/active relationship before request creation * refactor: use improved noMatchingRelationship error message * test: add request creation for terminated relationship test * chore: add schemas for use cases * fix: correct schema names in use cases * chore: updatebackbone * chore: use backbone-specific identities * fix: adapt variable renaming * fix: apply backbone-specific identities * fix: create relationship before request consumption tests * test: check for successful relationship termination * fix: correct reason name to ReactivationRequest * fix: sync relationship in test * chore: update backbone * fix: relationships runtime test * chore: remove reactivation * fix: remove some more * fix: relationshipsFacade * chore: remove reactivation schemas * test: more demanding address tests * fix: remove enmeshed prefix * refactor: rollback relationships controller changes * feat: re-add relationship termination in relationships controller * chore: remove reactivation core error * test: create pending/terminated relationship in test object factory * test: methods for creating pending/terminated relationships in integration tests * test: add terminated relationship message controller test * refactor: create relationship centrally in outgoing request controller describe blocks * test: add terminated relationship outgoing requests controller test * test: add terminated relationship incoming requests controller tests plus small fixes * refactor: incoming requests controller relationship check returns validation instead of throwing * test: canAccept / canReject return validation result * refactor: test util terminate relationship does not require relationship id * test: terminated relationship challenge test * fix: terminated relationship runtime test checks can decide validation * refactor: remove second pending relationship from factory correct the existing relationships * refactor: add/terminate relationship test utils return type * test: revert last relationships test change * refactor: replace noMatchingRelationship requests error * refactor: rename noMatchingRelationship messages error * refactor: error messages * fix: message controller test * fix: error message in message controller test * fix: error message in relationships test * refactor: inline relationship status check * chore: remove checkIdentity * fix: adapt transport services * test: "returns 'error'" in test names * refactor: improve decide validation comment * Fix runtime relationshipTemplateDVO test * refactor: remove comments --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Julian König --- .../src/consumption/ConsumptionController.ts | 3 +- .../consumption/src/consumption/CoreErrors.ts | 8 ++ .../incoming/IncomingRequestsController.ts | 32 ++++- .../outgoing/OutgoingRequestsController.ts | 20 +++- .../IncomingRequestsController.test.ts | 24 +++- .../OutgoingRequestsController.test.ts | 95 +++++++++++---- .../requests/RequestsIntegrationTest.ts | 53 +++++--- .../requests/testHelpers/TestObjectFactory.ts | 113 +++++++++++++----- .../src/extensibility/TransportServices.ts | 2 - .../facades/transport/IdentityFacade.ts | 11 -- .../facades/transport/RelationshipsFacade.ts | 27 +++-- .../extensibility/facades/transport/index.ts | 3 +- .../src/types/transport/RelationshipDTO.ts | 4 +- .../runtime/src/useCases/common/Schemas.ts | 69 +++++++---- .../transport/identity/CheckIdentity.ts | 75 ------------ .../src/useCases/transport/identity/index.ts | 1 - .../runtime/src/useCases/transport/index.ts | 3 +- .../relationships/TerminateRelationship.ts | 43 +++++++ .../useCases/transport/relationships/index.ts | 1 + .../dataViews/RelationshipTemplateDVO.test.ts | 32 +++-- .../runtime/test/transport/account.test.ts | 2 +- .../test/transport/relationships.test.ts | 92 +++++++++++++- packages/transport/src/core/CoreErrors.ts | 10 +- .../modules/challenges/ChallengeController.ts | 5 +- packages/transport/src/modules/index.ts | 58 ++++----- .../src/modules/messages/MessageController.ts | 12 +- .../relationships/RelationshipsController.ts | 17 ++- .../backbone/RelationshipClient.ts | 4 + .../transmission/RelationshipAuditLog.ts | 3 +- .../transmission/RelationshipStatus.ts | 1 - .../transport/test/end2end/End2End.test.ts | 53 ++++++-- .../modules/challenges/Challenges.test.ts | 7 ++ .../messages/MessageController.test.ts | 7 +- .../transport/test/testHelpers/TestUtil.ts | 18 ++- .../test/utils/IdentityGenerator.test.ts | 2 +- 35 files changed, 632 insertions(+), 278 deletions(-) delete mode 100644 packages/runtime/src/extensibility/facades/transport/IdentityFacade.ts delete mode 100644 packages/runtime/src/useCases/transport/identity/CheckIdentity.ts delete mode 100644 packages/runtime/src/useCases/transport/identity/index.ts create mode 100644 packages/runtime/src/useCases/transport/relationships/TerminateRelationship.ts diff --git a/packages/consumption/src/consumption/ConsumptionController.ts b/packages/consumption/src/consumption/ConsumptionController.ts index 6a00be256..42594bfb7 100644 --- a/packages/consumption/src/consumption/ConsumptionController.ts +++ b/packages/consumption/src/consumption/ConsumptionController.ts @@ -109,7 +109,8 @@ export class ConsumptionController { requestItemProcessorRegistry, this, this.transport.eventBus, - this.accountController.identity + this.accountController.identity, + this.accountController.relationships ).init(); const notificationItemProcessorRegistry = new NotificationItemProcessorRegistry(this, this.getDefaultNotificationItemProcessors()); diff --git a/packages/consumption/src/consumption/CoreErrors.ts b/packages/consumption/src/consumption/CoreErrors.ts index 107838cf4..9123ae954 100644 --- a/packages/consumption/src/consumption/CoreErrors.ts +++ b/packages/consumption/src/consumption/CoreErrors.ts @@ -268,6 +268,14 @@ class Requests { return new CoreError("error.consumption.requests.invalidRequestItem", message); } + public wrongRelationshipStatus(message: string) { + return new CoreError("error.consumption.requests.wrongRelationshipStatus", message); + } + + public missingRelationship(message: string) { + return new CoreError("error.consumption.requests.missingRelationship", message); + } + private static readonly _decideValidation = class { public invalidNumberOfItems(message: string) { return new ApplicationError("error.consumption.requests.decide.validation.invalidNumberOfItems", message); diff --git a/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts b/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts index 6280af606..041a1a8fe 100644 --- a/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts +++ b/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts @@ -1,7 +1,19 @@ import { ServalError } from "@js-soft/ts-serval"; import { EventBus } from "@js-soft/ts-utils"; import { RequestItem, RequestItemGroup, Response, ResponseItemDerivations, ResponseItemGroup, ResponseResult } from "@nmshd/content"; -import { CoreAddress, CoreDate, CoreId, ICoreAddress, ICoreId, Message, RelationshipTemplate, SynchronizedCollection, CoreErrors as TransportCoreErrors } from "@nmshd/transport"; +import { + CoreAddress, + CoreDate, + CoreId, + ICoreAddress, + ICoreId, + Message, + Relationship, + RelationshipStatus, + RelationshipTemplate, + SynchronizedCollection, + CoreErrors as TransportCoreErrors +} from "@nmshd/transport"; import { ConsumptionBaseController } from "../../../consumption/ConsumptionBaseController"; import { ConsumptionController } from "../../../consumption/ConsumptionController"; import { ConsumptionControllerName } from "../../../consumption/ConsumptionControllerName"; @@ -14,13 +26,13 @@ import { RequestItemProcessorRegistry } from "../itemProcessors/RequestItemProce import { ILocalRequestSource, LocalRequest } from "../local/LocalRequest"; import { LocalRequestStatus } from "../local/LocalRequestStatus"; import { LocalResponse, LocalResponseSource } from "../local/LocalResponse"; +import { DecideRequestParametersValidator } from "./DecideRequestParametersValidator"; import { CheckPrerequisitesOfIncomingRequestParameters, ICheckPrerequisitesOfIncomingRequestParameters } from "./checkPrerequisites/CheckPrerequisitesOfIncomingRequestParameters"; import { CompleteIncomingRequestParameters, ICompleteIncomingRequestParameters } from "./complete/CompleteIncomingRequestParameters"; import { DecideRequestItemGroupParametersJSON } from "./decide/DecideRequestItemGroupParameters"; import { DecideRequestItemParametersJSON } from "./decide/DecideRequestItemParameters"; import { DecideRequestParametersJSON } from "./decide/DecideRequestParameters"; import { InternalDecideRequestParameters, InternalDecideRequestParametersJSON } from "./decide/InternalDecideRequestParameters"; -import { DecideRequestParametersValidator } from "./DecideRequestParametersValidator"; import { IReceivedIncomingRequestParameters, ReceivedIncomingRequestParameters } from "./received/ReceivedIncomingRequestParameters"; import { IRequireManualDecisionOfIncomingRequestParameters, @@ -35,7 +47,10 @@ export class IncomingRequestsController extends ConsumptionBaseController { private readonly processorRegistry: RequestItemProcessorRegistry, parent: ConsumptionController, private readonly eventBus: EventBus, - private readonly identity: { address: CoreAddress } + private readonly identity: { address: CoreAddress }, + private readonly relationshipResolver: { + getRelationshipToIdentity(id: CoreAddress): Promise; + } ) { super(ConsumptionControllerName.RequestsController, parent); } @@ -165,9 +180,18 @@ export class IncomingRequestsController extends ConsumptionBaseController { private async canDecide(params: InternalDecideRequestParametersJSON): Promise { // syntactic validation InternalDecideRequestParameters.from(params); - const request = await this.getOrThrow(params.requestId); + const relationship = await this.relationshipResolver.getRelationshipToIdentity(request.peer); + // It is safe to decide an incoming Request when no Relationship is found as this is the case when the Request origins from onNewRelationship of the RelationshipTemplateContent + if (relationship && relationship.status !== RelationshipStatus.Active) { + return ValidationResult.error( + CoreErrors.requests.wrongRelationshipStatus( + `You cannot decide a request from '${request.peer.toString()}' since the relationship is in status '${relationship.status}'.` + ) + ); + } + this.assertRequestStatus(request, LocalRequestStatus.DecisionRequired, LocalRequestStatus.ManualDecisionRequired); const validationResult = this.decideRequestParamsValidator.validate(params, request); diff --git a/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts b/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts index e974e02e7..e02ef59ed 100644 --- a/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts +++ b/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts @@ -4,10 +4,10 @@ import { CoreAddress, CoreDate, CoreId, - ICoreAddress, ICoreId, Message, Relationship, + RelationshipStatus, RelationshipTemplate, SynchronizedCollection, CoreErrors as TransportCoreErrors @@ -17,6 +17,7 @@ import { ConsumptionController } from "../../../consumption/ConsumptionControlle import { ConsumptionControllerName } from "../../../consumption/ConsumptionControllerName"; import { ConsumptionError } from "../../../consumption/ConsumptionError"; import { ConsumptionIds } from "../../../consumption/ConsumptionIds"; +import { CoreErrors } from "../../../consumption/CoreErrors"; import { ValidationResult } from "../../common/ValidationResult"; import { OutgoingRequestCreatedAndCompletedEvent, OutgoingRequestCreatedEvent, OutgoingRequestStatusChangedEvent } from "../events"; import { RequestItemProcessorRegistry } from "../itemProcessors/RequestItemProcessorRegistry"; @@ -40,7 +41,7 @@ export class OutgoingRequestsController extends ConsumptionBaseController { private readonly eventBus: EventBus, private readonly identity: { address: CoreAddress }, private readonly relationshipResolver: { - getActiveRelationshipToIdentity(id: ICoreAddress): Promise; + getRelationshipToIdentity(id: CoreAddress): Promise; } ) { super(ConsumptionControllerName.RequestsController, parent); @@ -48,6 +49,21 @@ export class OutgoingRequestsController extends ConsumptionBaseController { public async canCreate(params: ICanCreateOutgoingRequestParameters): Promise { const parsedParams = CanCreateOutgoingRequestParameters.from(params); + if (parsedParams.peer) { + const relationship = await this.relationshipResolver.getRelationshipToIdentity(parsedParams.peer); + if (!relationship) { + return ValidationResult.error( + CoreErrors.requests.missingRelationship(`You cannot create a request to '${parsedParams.peer.toString()}' since you are not in a relationship.`) + ); + } + if (!(relationship.status === RelationshipStatus.Pending || relationship.status === RelationshipStatus.Active)) { + return ValidationResult.error( + CoreErrors.requests.wrongRelationshipStatus( + `You cannot create a request to '${parsedParams.peer.toString()}' since the relationship is in status '${relationship.status}'.` + ) + ); + } + } const innerResults = await this.canCreateItems(parsedParams.content, parsedParams.peer); diff --git a/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts b/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts index e275cbdb2..954e25341 100644 --- a/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts +++ b/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts @@ -383,6 +383,15 @@ describe("IncomingRequestsController", function () { expect(validationResult.items[1].items[1].isError()).toBe(false); expect(validationResult.items[1].items[2].isError()).toBe(true); }); + + test("returns 'error' on terminated relationship", async function () { + await Given.aTerminatedRelationshipToIdentity(); + await Given.anIncomingRequestInStatus(LocalRequestStatus.DecisionRequired); + const validationResult = await When.iCallCanAccept(); + expect(validationResult).errorValidationResult({ + code: "error.consumption.requests.wrongRelationshipStatus" + }); + }); }); describe("CanReject", function () { @@ -562,6 +571,15 @@ describe("IncomingRequestsController", function () { expect(validationResult.items[1].items[1].isError()).toBe(false); expect(validationResult.items[1].items[2].isError()).toBe(true); }); + + test("returns 'error' on terminated relationship", async function () { + await Given.aTerminatedRelationshipToIdentity(); + await Given.anIncomingRequestInStatus(LocalRequestStatus.DecisionRequired); + const validationResult = await When.iCallCanReject(); + expect(validationResult).errorValidationResult({ + code: "error.consumption.requests.wrongRelationshipStatus" + }); + }); }); describe("Accept", function () { @@ -796,7 +814,7 @@ describe("IncomingRequestsController", function () { test("can handle valid input with a Relationship as responseSource", async function () { await Given.anIncomingRequestInStatus(LocalRequestStatus.Decided); - const outgoingRelationship = TestObjectFactory.createIRelationship(); + const outgoingRelationship = TestObjectFactory.createPendingRelationship(); await When.iCompleteTheIncomingRequestWith({ responseSourceObject: outgoingRelationship }); @@ -847,6 +865,7 @@ describe("IncomingRequestsController", function () { }); test("returns undefined when the given id belongs to an outgoing Request", async function () { + await Given.anActiveRelationshipToIdentity(); const outgoingRequest = await Given.anOutgoingRequest(); await When.iGetTheIncomingRequestWith(outgoingRequest.id); await Then.iExpectUndefinedToBeReturned(); @@ -900,6 +919,7 @@ describe("IncomingRequestsController", function () { }); test("does not return outgoing Requests", async function () { + await Given.anActiveRelationshipToIdentity(); await Given.anIncomingRequest(); await Given.anOutgoingRequest(); await When.iGetIncomingRequestsWithTheQuery({}); @@ -981,7 +1001,7 @@ describe("IncomingRequestsController", function () { ] }); - const relationship = TestObjectFactory.createIRelationship(); + const relationship = TestObjectFactory.createPendingRelationship(); cnsRequest = await context.incomingRequestsController.complete({ requestId: cnsRequest.id, diff --git a/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts b/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts index b89d5aa8a..651088e44 100644 --- a/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts +++ b/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts @@ -23,7 +23,7 @@ import { OutgoingRequestStatusChangedEvent, ValidationResult } from "../../../src"; -import { loggerFactory, TestUtil } from "../../core/TestUtil"; +import { TestUtil, loggerFactory } from "../../core/TestUtil"; import { RequestsGiven, RequestsTestsContext, RequestsThen, RequestsWhen } from "./RequestsIntegrationTest"; import { TestObjectFactory } from "./testHelpers/TestObjectFactory"; import { ITestRequestItem, TestRequestItem } from "./testHelpers/TestRequestItem"; @@ -59,7 +59,11 @@ describe("OutgoingRequestsController", function () { context.reset(); }); - describe("CanCreate", function () { + describe("CanCreate (on active relationship)", function () { + beforeEach(async function () { + await Given.anActiveRelationshipToIdentity(); + }); + test("returns 'success' on valid parameters", async function () { await When.iCallCanCreateForAnOutgoingRequest(); await Then.itReturnsASuccessfulValidationResult(); @@ -197,7 +201,11 @@ describe("OutgoingRequestsController", function () { }); }); - describe("Create", function () { + describe("Create (on active relationship)", function () { + beforeEach(async function () { + await Given.anActiveRelationshipToIdentity(); + }); + test("can handle valid input", async function () { await When.iCreateAnOutgoingRequest(); await Then.theCreatedOutgoingRequestHasAllProperties(); @@ -248,7 +256,7 @@ describe("OutgoingRequestsController", function () { test("uses the id from the response for the created Local Request", async function () { await When.iCreateAnOutgoingRequestFromRelationshipCreationWith({ - responseSource: TestObjectFactory.createIRelationship(), + responseSource: TestObjectFactory.createPendingRelationship(), response: TestObjectFactory.createResponse("requestIdReceivedFromPeer") }); @@ -258,8 +266,6 @@ describe("OutgoingRequestsController", function () { }); test("create an outgoing request from relationship creation with an active relationship", async function () { - await Given.anActiveRelationshipToIdentity(); - await When.iCreateAnOutgoingRequestFromRelationshipCreationWith({ template: TestObjectFactory.createOutgoingIRelationshipTemplate( context.currentIdentity, @@ -267,7 +273,7 @@ describe("OutgoingRequestsController", function () { onNewRelationship: TestObjectFactory.createRequestWithOneItem() }) ), - responseSource: TestObjectFactory.createIRelationship(), + responseSource: TestObjectFactory.createPendingRelationship(), response: TestObjectFactory.createResponse("requestIdReceivedFromPeer") }); @@ -280,19 +286,23 @@ describe("OutgoingRequestsController", function () { await When.iTryToCreateAnOutgoingRequestFromRelationshipTemplateResponseWithoutResponseSource(); await Then.itThrowsAnErrorWithTheErrorMessage("*responseSource*Value is not defined*"); }); + }); - test("uses the content from onExistingRelationship when the relationship exists", async function () { - await When.iCreateAnOutgoingRequestFromRelationshipCreationWhenRelationshipExistsWith({ - responseSource: TestObjectFactory.createIncomingIMessageWithResponse(CoreAddress.from("id1"), "requestIdReceivedFromPeer"), - response: TestObjectFactory.createResponse("requestIdReceivedFromPeer") - }); - await Then.theRequestHasTheId("requestIdReceivedFromPeer"); - await Then.eventsHaveBeenPublished(OutgoingRequestCreatedAndCompletedEvent); - await Then.theRequestHasCorrectItemCount(2); + test("uses the content from onExistingRelationship when the relationship exists", async function () { + await When.iCreateAnOutgoingRequestFromRelationshipCreationWhenRelationshipExistsWith({ + responseSource: TestObjectFactory.createIncomingIMessageWithResponse(CoreAddress.from("id1"), "requestIdReceivedFromPeer"), + response: TestObjectFactory.createResponse("requestIdReceivedFromPeer") }); + await Then.theRequestHasTheId("requestIdReceivedFromPeer"); + await Then.eventsHaveBeenPublished(OutgoingRequestCreatedAndCompletedEvent); + await Then.theRequestHasCorrectItemCount(2); }); - describe("with a Message", function () { + describe("with a Message (on active relationship)", function () { + beforeEach(async function () { + await Given.anActiveRelationshipToIdentity(); + }); + test("combines calls to create, sent and complete", async function () { await When.iCreateAnOutgoingRequestFromMessage(); await Then.theCreatedOutgoingRequestHasAllProperties(); @@ -320,7 +330,11 @@ describe("OutgoingRequestsController", function () { }); }); - describe("Sent", function () { + describe("Sent (on active relationship)", function () { + beforeEach(async function () { + await Given.anActiveRelationshipToIdentity(); + }); + test("can handle valid input", async function () { await Given.anOutgoingRequestInStatus(LocalRequestStatus.Draft); await When.iCallSent(); @@ -346,7 +360,6 @@ describe("OutgoingRequestsController", function () { test("sets the source property depending on the given source", async function () { const source = TestObjectFactory.createOutgoingIMessage(context.currentIdentity); - await Given.anOutgoingRequestInStatus(LocalRequestStatus.Draft); await When.iCallSentWith({ requestSourceObject: source }); await Then.theRequestHasItsSourcePropertySetTo({ @@ -365,14 +378,17 @@ describe("OutgoingRequestsController", function () { test("throws when passing an incoming Message", async function () { const invalidSource = TestObjectFactory.createIncomingIMessage(context.currentIdentity); - await Given.anOutgoingRequestInStatus(LocalRequestStatus.Draft); await When.iTryToCallSentWith({ requestSourceObject: invalidSource }); await Then.itThrowsAnErrorWithTheErrorMessage("Cannot create outgoing Request from a peer*"); }); }); - describe("Complete", function () { + describe("Complete (on active relationship)", function () { + beforeEach(async function () { + await Given.anActiveRelationshipToIdentity(); + }); + test("can handle valid input with a Message as responseSourceObject", async function () { await Given.anOutgoingRequestInStatus(LocalRequestStatus.Open); const incomingMessage = TestObjectFactory.createIncomingIMessage(context.currentIdentity); @@ -573,7 +589,6 @@ describe("OutgoingRequestsController", function () { test("allows completing an expired Request with a Message that was created before the expiry date", async function () { const incomingMessage = TestObjectFactory.createIncomingIMessage(context.currentIdentity, CoreDate.utc().subtract({ days: 2 })); - await Given.anOutgoingRequestWith({ status: LocalRequestStatus.Expired, content: { @@ -588,7 +603,6 @@ describe("OutgoingRequestsController", function () { test("throws when trying to complete an expired Request with a Message that was created after the expiry date", async function () { const incomingMessage = TestObjectFactory.createIncomingIMessage(context.currentIdentity); - await Given.anOutgoingRequestWith({ status: LocalRequestStatus.Expired, content: { @@ -602,7 +616,11 @@ describe("OutgoingRequestsController", function () { }); }); - describe("Get", function () { + describe("Get (on active relationship)", function () { + beforeEach(async function () { + await Given.anActiveRelationshipToIdentity(); + }); + test("returns the Request with the given id if it exists", async function () { const outgoingRequest = await Given.anOutgoingRequest(); await When.iGetTheOutgoingRequest(); @@ -661,7 +679,11 @@ describe("OutgoingRequestsController", function () { }); }); - describe("GetOutgoingRequests", function () { + describe("GetOutgoingRequests (on active relationship)", function () { + beforeEach(async function () { + await Given.anActiveRelationshipToIdentity(); + }); + test("returns all outgoing Requests when invoked with no query", async function () { await Given.anOutgoingRequest(); await Given.anOutgoingRequest(); @@ -723,7 +745,11 @@ describe("OutgoingRequestsController", function () { }); }); - describe("Discard", function () { + describe("Discard (on active relationship)", function () { + beforeEach(async function () { + await Given.anActiveRelationshipToIdentity(); + }); + test("discards a Request in status Draft", async function () { await Given.anOutgoingRequestInStatus(LocalRequestStatus.Draft); await When.iDiscardTheOutgoingRequest(); @@ -742,4 +768,23 @@ describe("OutgoingRequestsController", function () { await Then.itThrowsAnErrorWithTheErrorMessage("*Local Request has to be in status 'Draft'*"); }); }); + + describe("CanCreate (on terminated relationship)", function () { + test("returns 'error' when the relationship is terminated", async function () { + await Given.aTerminatedRelationshipToIdentity(); + const validationResult = await When.iCallCanCreateForAnOutgoingRequest({ + content: { + items: [ + TestRequestItem.from({ + mustBeAccepted: false, + shouldFailAtCanCreateOutgoingRequestItem: true + }) + ] + } + }); + expect(validationResult).errorValidationResult({ + code: "error.consumption.requests.wrongRelationshipStatus" + }); + }); + }); }); diff --git a/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts b/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts index 2a15624c3..75ffc15c3 100644 --- a/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts +++ b/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts @@ -8,9 +8,9 @@ import { CoreId, IConfigOverwrite, ICoreId, - IdentityController, IMessage, IRelationshipTemplate, + IdentityController, Message, Relationship, RelationshipTemplate, @@ -27,10 +27,10 @@ import { ICreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters, ICreateOutgoingRequestParameters, ILocalRequestSource, - IncomingRequestsController, IReceivedIncomingRequestParameters, IRequireManualDecisionOfIncomingRequestParameters, ISentOutgoingRequestParameters, + IncomingRequestsController, LocalRequest, LocalRequestSource, LocalRequestStatus, @@ -53,7 +53,7 @@ export class RequestsTestsContext { public outgoingRequestsController: OutgoingRequestsController; public currentIdentity: CoreAddress; public mockEventBus = new MockEventBus(); - public relationshipToReturnFromGetActiveRelationshipToIdentity: Relationship | undefined; + public relationshipToReturnFromGetRelationshipToIdentity: Relationship | undefined; private constructor() { // hide constructor @@ -89,13 +89,22 @@ export class RequestsTestsContext { context.mockEventBus, { address: CoreAddress.from("anAddress") }, { - getActiveRelationshipToIdentity: () => Promise.resolve(context.relationshipToReturnFromGetActiveRelationshipToIdentity) + getRelationshipToIdentity: () => Promise.resolve(context.relationshipToReturnFromGetRelationshipToIdentity) } ); - context.incomingRequestsController = new IncomingRequestsController(collection, processorRegistry, undefined!, context.mockEventBus, { - address: CoreAddress.from("anAddress") - }); + context.incomingRequestsController = new IncomingRequestsController( + collection, + processorRegistry, + undefined!, + context.mockEventBus, + { + address: CoreAddress.from("anAddress") + }, + { + getRelationshipToIdentity: () => Promise.resolve(context.relationshipToReturnFromGetRelationshipToIdentity) + } + ); context.requestsCollection = context.incomingRequestsController["localRequests"]; const originalCanCreate = context.outgoingRequestsController.canCreate; @@ -113,7 +122,7 @@ export class RequestsTestsContext { this.localRequestAfterAction = undefined; this.validationResult = undefined; this.actionToTry = undefined; - this.relationshipToReturnFromGetActiveRelationshipToIdentity = undefined; + this.relationshipToReturnFromGetRelationshipToIdentity = undefined; TestRequestItemProcessor.numberOfApplyIncomingResponseItemCalls = 0; @@ -137,7 +146,19 @@ export class RequestsGiven { } public anActiveRelationshipToIdentity(): Promise { - this.context.relationshipToReturnFromGetActiveRelationshipToIdentity = TestObjectFactory.createRelationship(); + this.context.relationshipToReturnFromGetRelationshipToIdentity = TestObjectFactory.createActiveRelationship(); + + return Promise.resolve(); + } + + public aPendingRelationshipToIdentity(): Promise { + this.context.relationshipToReturnFromGetRelationshipToIdentity = TestObjectFactory.createPendingRelationship(); + + return Promise.resolve(); + } + + public aTerminatedRelationshipToIdentity(): Promise { + this.context.relationshipToReturnFromGetRelationshipToIdentity = TestObjectFactory.createTerminatedRelationship(); return Promise.resolve(); } @@ -283,8 +304,8 @@ export class RequestsGiven { } export class RequestsWhen { - public async iCallCanAccept(): Promise { - await this.iCallCanAcceptWith({}); + public async iCallCanAccept(): Promise { + return await this.iCallCanAcceptWith({}); } public async iTryToCallCanAccept(): Promise { @@ -318,8 +339,8 @@ export class RequestsWhen { return this.context.validationResult; } - public async iCallCanReject(): Promise { - await this.iCallCanRejectWith({}); + public async iCallCanReject(): Promise { + return await this.iCallCanRejectWith({}); } public async iTryToCallCanReject(): Promise { @@ -587,7 +608,7 @@ export class RequestsWhen { public async iCreateAnOutgoingRequestFromRelationshipCreationWhenRelationshipExistsWith( params: Partial ): Promise { - this.context.relationshipToReturnFromGetActiveRelationshipToIdentity = TestObjectFactory.createRelationship(); + this.context.relationshipToReturnFromGetRelationshipToIdentity = TestObjectFactory.createActiveRelationship(); await this.iCreateAnOutgoingRequestFromRelationshipCreationWith(params); } @@ -599,9 +620,11 @@ export class RequestsWhen { onExistingRelationship: TestObjectFactory.createRequestWithTwoItems() }) ); - params.responseSource ??= TestObjectFactory.createIRelationship(); + const relationship = TestObjectFactory.createPendingRelationship(); + params.responseSource ??= relationship; params.response ??= TestObjectFactory.createResponse(); + this.context.relationshipToReturnFromGetRelationshipToIdentity = relationship; this.context.localRequestAfterAction = await this.context.outgoingRequestsController.createAndCompleteFromRelationshipTemplateResponse( params as ICreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseParameters ); diff --git a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts index 451354d74..5264c0a68 100644 --- a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts +++ b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts @@ -39,7 +39,39 @@ import { ILocalRequest, LocalRequest, LocalRequestStatus, LocalRequestStatusLogE import { TestRequestItem } from "./TestRequestItem"; export class TestObjectFactory { - public static createRelationship(properties?: Partial): Relationship { + public static createPendingRelationship(properties?: Partial): Relationship { + return Relationship.from({ + id: properties?.id ?? CoreId.from("REL1"), + peer: + properties?.peer ?? + Identity.from({ + address: CoreAddress.from("id1"), + publicKey: CryptoSignaturePublicKey.from({ + algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519, + publicKey: CoreBuffer.from("L1sPFQgS5CxgGs1ejBcWCQLCpeFXbRc1TQnSpuHQqDQ") + }) + }), + status: properties?.status ?? RelationshipStatus.Pending, + relationshipSecretId: properties?.relationshipSecretId ?? CoreId.from("RELSEC1"), + cachedAt: properties?.cachedAt ?? CoreDate.from("2020-01-02T00:00:00.000Z"), + cache: + properties?.cache ?? + CachedRelationship.from({ + auditLog: [ + { + createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), + createdBy: CoreAddress.from("id2"), + createdByDevice: CoreId.from("DVC1"), + reason: RelationshipAuditLogEntryReason.Creation, + newStatus: RelationshipStatus.Pending + } + ], + template: this.createIncomingRelationshipTemplate() + }) + }); + } + + public static createActiveRelationship(properties?: Partial): Relationship { return Relationship.from({ id: properties?.id ?? CoreId.from("REL1"), peer: @@ -80,6 +112,56 @@ export class TestObjectFactory { }); } + public static createTerminatedRelationship(properties?: Partial): Relationship { + return Relationship.from({ + id: properties?.id ?? CoreId.from("REL1"), + peer: + properties?.peer ?? + Identity.from({ + address: CoreAddress.from("id1"), + publicKey: CryptoSignaturePublicKey.from({ + algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519, + publicKey: CoreBuffer.from("L1sPFQgS5CxgGs1ejBcWCQLCpeFXbRc1TQnSpuHQqDQ") + }) + }), + status: properties?.status ?? RelationshipStatus.Terminated, + relationshipSecretId: properties?.relationshipSecretId ?? CoreId.from("RELSEC1"), + cachedAt: properties?.cachedAt ?? CoreDate.from("2020-01-02T00:00:00.000Z"), + cache: + properties?.cache ?? + CachedRelationship.from({ + auditLog: [ + { + createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), + createdBy: CoreAddress.from("id2"), + createdByDevice: CoreId.from("DVC1"), + reason: RelationshipAuditLogEntryReason.Creation, + newStatus: RelationshipStatus.Pending + }, + + { + createdAt: CoreDate.from("2020-01-02T00:00:00.000Z"), + createdBy: CoreAddress.from("id1"), + createdByDevice: CoreId.from("DVC1"), + reason: RelationshipAuditLogEntryReason.AcceptanceOfCreation, + oldStatus: RelationshipStatus.Pending, + newStatus: RelationshipStatus.Active + }, + + { + createdAt: CoreDate.from("2020-01-03T00:00:00.000Z"), + createdBy: CoreAddress.from("id1"), + createdByDevice: CoreId.from("DVC1"), + reason: RelationshipAuditLogEntryReason.Termination, + oldStatus: RelationshipStatus.Active, + newStatus: RelationshipStatus.Terminated + } + ], + template: this.createIncomingRelationshipTemplate() + }) + }); + } + public static createIdentityAttribute(properties?: Partial): IdentityAttribute { return IdentityAttribute.from({ value: properties?.value ?? GivenName.fromAny({ value: "AGivenName" }), @@ -278,35 +360,6 @@ export class TestObjectFactory { return RelationshipTemplate.from(this.createIncomingIRelationshipTemplate()); } - public static createIRelationship(): IRelationship { - return { - // @ts-expect-error - "@type": "Relationship", - id: CoreId.from("REL1"), - status: RelationshipStatus.Pending, - relationshipSecretId: CoreId.from("REL1"), - peer: { - address: CoreAddress.from("id2"), - publicKey: CryptoSignaturePublicKey.from({ - algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519, - publicKey: CoreBuffer.fromBase64URL("aS-A8ywidL00DfBlZySOG_1-NdSBW38uGD1il_Ymk5g") - }) - }, - cache: { - template: this.createIncomingIRelationshipTemplate(), - auditLog: [ - { - createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), - createdBy: CoreAddress.from("id2"), - createdByDevice: CoreId.from("DVC1"), - reason: RelationshipAuditLogEntryReason.Creation, - newStatus: RelationshipStatus.Active - } - ] - } - }; - } - public static createIncomingIRelationshipTemplate(): IRelationshipTemplate { return { // @ts-expect-error diff --git a/packages/runtime/src/extensibility/TransportServices.ts b/packages/runtime/src/extensibility/TransportServices.ts index ea6cd37ce..a6eeabaa5 100644 --- a/packages/runtime/src/extensibility/TransportServices.ts +++ b/packages/runtime/src/extensibility/TransportServices.ts @@ -5,7 +5,6 @@ import { DevicesFacade, FilesFacade, IdentityDeletionProcessesFacade, - IdentityFacade, MessagesFacade, RelationshipsFacade, RelationshipTemplatesFacade, @@ -22,7 +21,6 @@ export class TransportServices { @Inject public readonly account: AccountFacade, @Inject public readonly devices: DevicesFacade, @Inject public readonly challenges: ChallengesFacade, - @Inject public readonly identity: IdentityFacade, @Inject public readonly identityDeletionProcesses: IdentityDeletionProcessesFacade ) {} } diff --git a/packages/runtime/src/extensibility/facades/transport/IdentityFacade.ts b/packages/runtime/src/extensibility/facades/transport/IdentityFacade.ts deleted file mode 100644 index 5c5954b88..000000000 --- a/packages/runtime/src/extensibility/facades/transport/IdentityFacade.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Result } from "@js-soft/ts-utils"; -import { Inject } from "typescript-ioc"; -import { CheckIdentityRequest, CheckIdentityResponse, CheckIdentityUseCase } from "../../../useCases"; - -export class IdentityFacade { - public constructor(@Inject private readonly checkIdentityUseCase: CheckIdentityUseCase) {} - - public async checkIdentity(request: CheckIdentityRequest): Promise> { - return await this.checkIdentityUseCase.execute(request); - } -} diff --git a/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts b/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts index 2de41a73e..fcb8fb01e 100644 --- a/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts +++ b/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts @@ -1,4 +1,4 @@ -import { ApplicationError, Result } from "@js-soft/ts-utils"; +import { Result } from "@js-soft/ts-utils"; import { Inject } from "typescript-ioc"; import { RelationshipDTO } from "../../../types"; import { @@ -18,7 +18,9 @@ import { RejectRelationshipRequest, RejectRelationshipUseCase, RevokeRelationshipRequest, - RevokeRelationshipUseCase + RevokeRelationshipUseCase, + TerminateRelationshipRequest, + TerminateRelationshipUseCase } from "../../../useCases"; export class RelationshipsFacade { @@ -30,38 +32,43 @@ export class RelationshipsFacade { @Inject private readonly acceptRelationshipUseCase: AcceptRelationshipUseCase, @Inject private readonly rejectRelationshipUseCase: RejectRelationshipUseCase, @Inject private readonly revokeRelationshipUseCase: RevokeRelationshipUseCase, + @Inject private readonly terminateRelationshipUseCase: TerminateRelationshipUseCase, @Inject private readonly getAttributesForRelationshipUseCase: GetAttributesForRelationshipUseCase ) {} - public async getRelationships(request: GetRelationshipsRequest): Promise> { + public async getRelationships(request: GetRelationshipsRequest): Promise> { return await this.getRelationshipsUseCase.execute(request); } - public async getRelationship(request: GetRelationshipRequest): Promise> { + public async getRelationship(request: GetRelationshipRequest): Promise> { return await this.getRelationshipUseCase.execute(request); } - public async getRelationshipByAddress(request: GetRelationshipByAddressRequest): Promise> { + public async getRelationshipByAddress(request: GetRelationshipByAddressRequest): Promise> { return await this.getRelationshipByAddressUseCase.execute(request); } - public async createRelationship(request: CreateRelationshipRequest): Promise> { + public async createRelationship(request: CreateRelationshipRequest): Promise> { return await this.createRelationshipUseCase.execute(request); } - public async acceptRelationship(request: AcceptRelationshipRequest): Promise> { + public async acceptRelationship(request: AcceptRelationshipRequest): Promise> { return await this.acceptRelationshipUseCase.execute(request); } - public async rejectRelationship(request: RejectRelationshipRequest): Promise> { + public async rejectRelationship(request: RejectRelationshipRequest): Promise> { return await this.rejectRelationshipUseCase.execute(request); } - public async revokeRelationship(request: RevokeRelationshipRequest): Promise> { + public async revokeRelationship(request: RevokeRelationshipRequest): Promise> { return await this.revokeRelationshipUseCase.execute(request); } - public async getAttributesForRelationship(request: GetAttributesForRelationshipRequest): Promise> { + public async terminateRelationship(request: TerminateRelationshipRequest): Promise> { + return await this.terminateRelationshipUseCase.execute(request); + } + + public async getAttributesForRelationship(request: GetAttributesForRelationshipRequest): Promise> { return await this.getAttributesForRelationshipUseCase.execute(request); } } diff --git a/packages/runtime/src/extensibility/facades/transport/index.ts b/packages/runtime/src/extensibility/facades/transport/index.ts index 23872c6a1..a14fc3df7 100644 --- a/packages/runtime/src/extensibility/facades/transport/index.ts +++ b/packages/runtime/src/extensibility/facades/transport/index.ts @@ -3,8 +3,7 @@ export * from "./ChallengesFacade"; export * from "./DevicesFacade"; export * from "./FilesFacade"; export * from "./IdentityDeletionProcessesFacade"; -export * from "./IdentityFacade"; export * from "./MessagesFacade"; -export * from "./RelationshipsFacade"; export * from "./RelationshipTemplatesFacade"; +export * from "./RelationshipsFacade"; export * from "./TokensFacade"; diff --git a/packages/runtime/src/types/transport/RelationshipDTO.ts b/packages/runtime/src/types/transport/RelationshipDTO.ts index eb9e671a1..98642382d 100644 --- a/packages/runtime/src/types/transport/RelationshipDTO.ts +++ b/packages/runtime/src/types/transport/RelationshipDTO.ts @@ -7,7 +7,6 @@ export enum RelationshipStatus { Active = "Active", Rejected = "Rejected", Revoked = "Revoked", - Terminating = "Terminating", Terminated = "Terminated" } @@ -15,7 +14,8 @@ export enum RelationshipAuditLogEntryReason { Creation = "Creation", AcceptanceOfCreation = "AcceptanceOfCreation", RejectionOfCreation = "RejectionOfCreation", - RevocationOfCreation = "RevocationOfCreation" + RevocationOfCreation = "RevocationOfCreation", + Termination = "Termination" } export interface RelationshipAuditLogEntryDTO { diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index d56221428..265c29b17 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -21397,29 +21397,6 @@ export const SendMessageRequest: any = { } } -export const AcceptRelationshipRequest: any = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/AcceptRelationshipRequest", - "definitions": { - "AcceptRelationshipRequest": { - "type": "object", - "properties": { - "relationshipId": { - "$ref": "#/definitions/RelationshipIdString" - } - }, - "required": [ - "relationshipId" - ], - "additionalProperties": false - }, - "RelationshipIdString": { - "type": "string", - "pattern": "REL[A-Za-z0-9]{17}" - } - } -} - export const CreateRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/CreateRelationshipRequest", @@ -21582,6 +21559,29 @@ export const GetRelationshipsRequest: any = { } } +export const AcceptRelationshipRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/AcceptRelationshipRequest", + "definitions": { + "AcceptRelationshipRequest": { + "type": "object", + "properties": { + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" + }, + }, + "required": [ + "relationshipId" + ], + "additionalProperties": false + }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" + }, + } +} + export const RejectRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/RejectRelationshipRequest", @@ -21628,6 +21628,29 @@ export const RevokeRelationshipRequest: any = { } } +export const TerminateRelationshipRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/TerminateRelationshipRequest", + "definitions": { + "TerminateRelationshipRequest": { + "type": "object", + "properties": { + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" + }, + }, + "required": [ + "relationshipId" + ], + "additionalProperties": false + }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" + }, + } +} + export const CreateOwnRelationshipTemplateRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/CreateOwnRelationshipTemplateRequest", diff --git a/packages/runtime/src/useCases/transport/identity/CheckIdentity.ts b/packages/runtime/src/useCases/transport/identity/CheckIdentity.ts deleted file mode 100644 index 27a1e5cf3..000000000 --- a/packages/runtime/src/useCases/transport/identity/CheckIdentity.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Result } from "@js-soft/ts-utils"; -import { CoreAddress, IdentityController, RelationshipsController, RelationshipStatus } from "@nmshd/transport"; -import { Inject } from "typescript-ioc"; -import { RelationshipDTO } from "../../../types"; -import { AddressString, SchemaRepository, SchemaValidator, UseCase } from "../../common"; -import { RelationshipMapper } from "../relationships/RelationshipMapper"; - -export interface CheckIdentityRequest { - address: AddressString; -} - -export interface CheckIdentityResponse { - unknown?: boolean; - self?: boolean; - peer?: boolean; - relationshipPending?: boolean; - relationshipActive?: boolean; - relationshipTerminated?: boolean; - relationship?: RelationshipDTO; -} - -class Validator extends SchemaValidator { - public constructor(@Inject schemaRepository: SchemaRepository) { - super(schemaRepository.getSchema("CheckIdentityRequest")); - } -} - -export class CheckIdentityUseCase extends UseCase { - public constructor( - @Inject private readonly identityController: IdentityController, - @Inject private readonly relationshipsController: RelationshipsController, - @Inject validator: Validator - ) { - super(validator); - } - - protected async executeInternal(request: CheckIdentityRequest): Promise> { - const address = CoreAddress.from(request.address); - const self = this.identityController.isMe(address); - - if (self) { - return Result.ok({ - self: true - }); - } - - const relationship = await this.relationshipsController.getRelationshipToIdentity(address); - if (relationship) { - const relationshipDTO = RelationshipMapper.toRelationshipDTO(relationship); - if (relationship.status === RelationshipStatus.Pending) { - return Result.ok({ - peer: true, - relationshipPending: true, - relationship: relationshipDTO - }); - } else if (relationship.status === RelationshipStatus.Active || relationship.status === RelationshipStatus.Terminating) { - return Result.ok({ - peer: true, - relationshipActive: true, - relationship: relationshipDTO - }); - } - - return Result.ok({ - peer: true, - relationshipTerminated: true, - relationship: relationshipDTO - }); - } - - return Result.ok({ - unknown: true - }); - } -} diff --git a/packages/runtime/src/useCases/transport/identity/index.ts b/packages/runtime/src/useCases/transport/identity/index.ts deleted file mode 100644 index b32aed2e6..000000000 --- a/packages/runtime/src/useCases/transport/identity/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./CheckIdentity"; diff --git a/packages/runtime/src/useCases/transport/index.ts b/packages/runtime/src/useCases/transport/index.ts index 276e033c8..0bce894c3 100644 --- a/packages/runtime/src/useCases/transport/index.ts +++ b/packages/runtime/src/useCases/transport/index.ts @@ -2,9 +2,8 @@ export * from "./account"; export * from "./challenges"; export * from "./devices"; export * from "./files"; -export * from "./identity"; export * from "./identityDeletionProcesses"; export * from "./messages"; -export * from "./relationships"; export * from "./relationshipTemplates"; +export * from "./relationships"; export * from "./tokens"; diff --git a/packages/runtime/src/useCases/transport/relationships/TerminateRelationship.ts b/packages/runtime/src/useCases/transport/relationships/TerminateRelationship.ts new file mode 100644 index 000000000..e07deb71b --- /dev/null +++ b/packages/runtime/src/useCases/transport/relationships/TerminateRelationship.ts @@ -0,0 +1,43 @@ +import { Result } from "@js-soft/ts-utils"; +import { AccountController, CoreId, Relationship, RelationshipsController } from "@nmshd/transport"; +import { Inject } from "typescript-ioc"; +import { RelationshipDTO } from "../../../types"; +import { RelationshipIdString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common"; +import { RelationshipMapper } from "./RelationshipMapper"; + +export interface TerminateRelationshipRequest { + relationshipId: RelationshipIdString; +} + +class Validator extends SchemaValidator { + public constructor(@Inject schemaRepository: SchemaRepository) { + super(schemaRepository.getSchema("TerminateRelationshipRequest")); + } +} + +export class TerminateRelationshipUseCase extends UseCase { + public constructor( + @Inject private readonly relationshipsController: RelationshipsController, + @Inject private readonly accountController: AccountController, + @Inject validator: Validator + ) { + super(validator); + } + + protected async executeInternal(request: TerminateRelationshipRequest): Promise> { + const relationship = await this.relationshipsController.getRelationship(CoreId.from(request.relationshipId)); + if (!relationship) { + return Result.fail(RuntimeErrors.general.recordNotFound(Relationship)); + } + + if (!relationship.cache) { + return Result.fail(RuntimeErrors.general.cacheEmpty(Relationship, relationship.id.toString())); + } + + const updatedRelationship = await this.relationshipsController.terminate(relationship.id); + + await this.accountController.syncDatawallet(); + + return Result.ok(RelationshipMapper.toRelationshipDTO(updatedRelationship)); + } +} diff --git a/packages/runtime/src/useCases/transport/relationships/index.ts b/packages/runtime/src/useCases/transport/relationships/index.ts index 4335a1a5d..2c6f0477d 100644 --- a/packages/runtime/src/useCases/transport/relationships/index.ts +++ b/packages/runtime/src/useCases/transport/relationships/index.ts @@ -7,3 +7,4 @@ export * from "./GetRelationships"; export * from "./RejectRelationship"; export * from "./RelationshipMapper"; export * from "./RevokeRelationship"; +export * from "./TerminateRelationship"; diff --git a/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts b/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts index 2f40c69b7..2f6c5dc2f 100644 --- a/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts +++ b/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts @@ -14,16 +14,16 @@ import { IncomingRequestStatusChangedEvent, OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent, PeerRelationshipTemplateDVO, - PeerRelationshipTemplateLoadedEvent, RelationshipTemplateDTO, RequestItemGroupDVO } from "../../src"; -import { createTemplate, RuntimeServiceProvider, syncUntilHasRelationships, TestRuntimeServices } from "../lib"; +import { RuntimeServiceProvider, TestRuntimeServices, createTemplate, syncUntilHasRelationships } from "../lib"; const serviceProvider = new RuntimeServiceProvider(); let templator: TestRuntimeServices; let requestor: TestRuntimeServices; let templatorTemplate: RelationshipTemplateDTO; +let templateId: string; let responseItems: DecideRequestItemGroupParametersJSON[]; beforeAll(async () => { @@ -141,6 +141,7 @@ describe("RelationshipTemplateDVO", () => { } ]; templatorTemplate = await createTemplate(templator.transport, templateContent); + templateId = templatorTemplate.id; }); test("TemplateDVO for templator", async () => { @@ -175,6 +176,7 @@ describe("RelationshipTemplateDVO", () => { test("TemplateDVO for requestor", async () => { const requestorTemplate = (await requestor.transport.relationshipTemplates.loadPeerRelationshipTemplate({ reference: templatorTemplate.truncatedReference })).value; + await requestor.eventBus.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const dto = requestorTemplate; const dvo = (await requestor.expander.expandRelationshipTemplateDTO(dto)) as PeerRelationshipTemplateDVO; @@ -206,13 +208,21 @@ describe("RelationshipTemplateDVO", () => { }); test("RequestDVO for requestor", async () => { - const requestorTemplate = (await requestor.transport.relationshipTemplates.loadPeerRelationshipTemplate({ reference: templatorTemplate.truncatedReference })).value; - await requestor.eventBus.waitForEvent(PeerRelationshipTemplateLoadedEvent); - const requestResult = await requestor.consumption.incomingRequests.getRequests({ + let requestResult; + requestResult = await requestor.consumption.incomingRequests.getRequests({ query: { - "source.reference": requestorTemplate.id + "source.reference": templateId } }); + await requestor.transport.relationshipTemplates.loadPeerRelationshipTemplate({ reference: templatorTemplate.truncatedReference }); + if (requestResult.value.length === 0) { + await requestor.eventBus.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); + requestResult = await requestor.consumption.incomingRequests.getRequests({ + query: { + "source.reference": templateId + } + }); + } expect(requestResult).toBeSuccessful(); expect(requestResult.value).toHaveLength(1); @@ -232,17 +242,17 @@ describe("RelationshipTemplateDVO", () => { let dto; let dvo; let requestResult; - const requestorTemplate = (await requestor.transport.relationshipTemplates.loadPeerRelationshipTemplate({ reference: templatorTemplate.truncatedReference })).value; requestResult = await requestor.consumption.incomingRequests.getRequests({ query: { - "source.reference": requestorTemplate.id + "source.reference": templateId } }); + const requestorTemplate = (await requestor.transport.relationshipTemplates.loadPeerRelationshipTemplate({ reference: templatorTemplate.truncatedReference })).value; if (requestResult.value.length === 0) { await requestor.eventBus.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); requestResult = await requestor.consumption.incomingRequests.getRequests({ query: { - "source.reference": requestorTemplate.id + "source.reference": templateId } }); } @@ -253,7 +263,7 @@ describe("RelationshipTemplateDVO", () => { const requestResultAfterAcceptance = await requestor.consumption.incomingRequests.getRequests({ query: { - "source.reference": requestorTemplate.id + "source.reference": templateId } }); expect(acceptResult).toBeSuccessful(); @@ -326,7 +336,7 @@ describe("RelationshipTemplateDVO", () => { await templator.eventBus.waitForEvent(OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent); const requestResultTemplator = await templator.consumption.outgoingRequests.getRequests({ query: { - "source.reference": requestorTemplate.id + "source.reference": templateId } }); expect(requestResultTemplator).toBeSuccessful(); diff --git a/packages/runtime/test/transport/account.test.ts b/packages/runtime/test/transport/account.test.ts index e83b1b2ac..25b7feb89 100644 --- a/packages/runtime/test/transport/account.test.ts +++ b/packages/runtime/test/transport/account.test.ts @@ -72,7 +72,7 @@ describe("IdentityInfo", () => { expect(identityInfoResult).toBeSuccessful(); const identityInfo = identityInfoResult.value; - expect(identityInfo.address.length).toBeGreaterThanOrEqual(30); + expect(identityInfo.address.length).toBeGreaterThanOrEqual(35); expect(identityInfo.address).toMatch(/^did:e:/); expect(identityInfo.publicKey).toHaveLength(82); }); diff --git a/packages/runtime/test/transport/relationships.test.ts b/packages/runtime/test/transport/relationships.test.ts index 034f46e42..f2a838948 100644 --- a/packages/runtime/test/transport/relationships.test.ts +++ b/packages/runtime/test/transport/relationships.test.ts @@ -1,8 +1,18 @@ +import { ApplicationError, Result } from "@js-soft/ts-utils"; import { RelationshipAttributeConfidentiality } from "@nmshd/content"; -import { GetRelationshipsQuery, LocalAttributeDTO, OwnSharedAttributeSucceededEvent, PeerSharedAttributeSucceededEvent } from "../../src"; +import { + GetRelationshipsQuery, + IncomingRequestReceivedEvent, + LocalAttributeDTO, + OwnSharedAttributeSucceededEvent, + PeerSharedAttributeSucceededEvent, + RelationshipDTO, + RelationshipStatus +} from "../../src"; import { createTemplate, ensureActiveRelationship, + exchangeMessageWithRequest, exchangeTemplate, executeFullCreateAndShareRelationshipAttributeFlow, executeFullCreateAndShareRepositoryAttributeFlow, @@ -281,3 +291,83 @@ describe("Attributes for the relationship", () => { ); }); }); + +describe("RelationshipTermination", () => { + let relationshipId: string; + let terminationResult: Result; + beforeAll(async () => { + relationshipId = (await ensureActiveRelationship(services1.transport, services2.transport)).id; + + const requestContent = { + content: { + items: [ + { + "@type": "TestRequestItem", + mustBeAccepted: false + } + ] + }, + peer: services2.address + }; + await exchangeMessageWithRequest(services1, services2, requestContent); + + terminationResult = await services1.transport.relationships.terminateRelationship({ relationshipId }); + }); + + test("relationship status is terminated", async () => { + expect(terminationResult).toBeSuccessful(); + const result = (await services1.transport.relationships.getRelationship({ id: relationshipId })).value; + expect(result.status).toBe(RelationshipStatus.Terminated); + }); + + test("should not send a message", async () => { + const result = await services1.transport.messages.sendMessage({ + recipients: [services2.address], + content: { + "@type": "Mail", + body: "b", + cc: [], + subject: "a", + to: [services2.address] + } + }); + expect(result).toBeAnError(/.*/, "error.transport.messages.missingOrInactiveRelationship"); + }); + + test("should not decide a request", async () => { + await syncUntilHasRelationships(services2.transport); + const incomingRequest = (await services2.eventBus.waitForEvent(IncomingRequestReceivedEvent)).data; + + const canAcceptResult = (await services2.consumption.incomingRequests.canAccept({ requestId: incomingRequest.id, items: [{ accept: true }] })).value; + expect(canAcceptResult.isSuccess).toBe(false); + expect(canAcceptResult.code).toBe("error.consumption.requests.wrongRelationshipStatus"); + + const canRejectResult = (await services2.consumption.incomingRequests.canReject({ requestId: incomingRequest.id, items: [{ accept: false }] })).value; + expect(canRejectResult.isSuccess).toBe(false); + expect(canRejectResult.code).toBe("error.consumption.requests.wrongRelationshipStatus"); + }); + + test("should not create a request", async () => { + const requestContent = { + content: { + items: [ + { + "@type": "TestRequestItem", + mustBeAccepted: false + } + ] + }, + peer: services2.address + }; + const result = await services1.consumption.outgoingRequests.create(requestContent); + expect(result).toBeAnError(/.*/, "error.consumption.requests.wrongRelationshipStatus"); + }); + + test("should not create a challenge for the relationship", async () => { + const result = await services1.transport.challenges.createChallenge({ + challengeType: "Relationship", + relationship: relationshipId + }); + expect(result).toBeAnError(/.*/, "error.transport.challenges.challengeTypeRequiresActiveRelationship"); + }); +}); diff --git a/packages/transport/src/core/CoreErrors.ts b/packages/transport/src/core/CoreErrors.ts index 24d23e543..958e3d82b 100644 --- a/packages/transport/src/core/CoreErrors.ts +++ b/packages/transport/src/core/CoreErrors.ts @@ -33,7 +33,7 @@ class Messages { public signatureNotValid() { return new CoreError( "error.transport.messages.signatureNotValid", - "The digital signature on this message for peer key is invalid. An impersonination attack might be the cause of this." + "The digital signature on this message for peer key is invalid. An impersonation attack might be the cause of this." ); } @@ -44,8 +44,8 @@ class Messages { ); } - public noMatchingRelationship(address: string) { - return new CoreError("error.transport.messages.noMatchingRelationship", `A Relationship with the given address '${address}' does not exist.`); + public missingOrInactiveRelationship(address: string) { + return new CoreError("error.transport.messages.missingOrInactiveRelationship", `An active Relationship with the given address '${address}' does not exist.`); } } @@ -62,8 +62,8 @@ class Secrets { } class Challenges { - public challengeTypeRequiresRelationship() { - return new CoreError("error.transport.challenges.challengeTypeRequiresRelationship", "The challenge type 'Relationship' requires a relationship."); + public challengeTypeRequiresActiveRelationship() { + return new CoreError("error.transport.challenges.challengeTypeRequiresActiveRelationship", "The challenge type 'Relationship' requires an active relationship."); } } diff --git a/packages/transport/src/modules/challenges/ChallengeController.ts b/packages/transport/src/modules/challenges/ChallengeController.ts index 43a5807a7..17dd3dd80 100644 --- a/packages/transport/src/modules/challenges/ChallengeController.ts +++ b/packages/transport/src/modules/challenges/ChallengeController.ts @@ -4,6 +4,7 @@ import { CoreAddress, CoreCrypto, CoreDate, CoreErrors, CoreId } from "../../cor import { ControllerName, TransportController } from "../../core/TransportController"; import { AccountController } from "../accounts/AccountController"; import { Relationship } from "../relationships/local/Relationship"; +import { RelationshipStatus } from "../relationships/transmission/RelationshipStatus"; import { ChallengeAuthClient } from "./backbone/ChallengeAuthClient"; import { ChallengeClient } from "./backbone/ChallengeClient"; import { Challenge, ChallengeType } from "./data/Challenge"; @@ -87,8 +88,8 @@ export class ChallengeController extends TransportController { @log() public async createChallenge(type: ChallengeType = ChallengeType.Identity, relationship?: Relationship): Promise { - if (type === ChallengeType.Relationship && !relationship) { - throw CoreErrors.challenges.challengeTypeRequiresRelationship(); + if (type === ChallengeType.Relationship && relationship?.status !== RelationshipStatus.Active) { + throw CoreErrors.challenges.challengeTypeRequiresActiveRelationship(); } const backboneResponse = (await this.authClient.createChallenge()).value; diff --git a/packages/transport/src/modules/index.ts b/packages/transport/src/modules/index.ts index 4cb9e0609..24952d583 100644 --- a/packages/transport/src/modules/index.ts +++ b/packages/transport/src/modules/index.ts @@ -1,12 +1,12 @@ export * from "./accounts/AccountController"; -export * from "./accounts/IdentityController"; -export * from "./accounts/IdentityDeletionProcessController"; -export * from "./accounts/IdentityUtil"; export * from "./accounts/backbone/IdentityClient"; export * from "./accounts/backbone/IdentityDeletionProcessClient"; export * from "./accounts/data/Identity"; export * from "./accounts/data/IdentityDeletionProcess"; export * from "./accounts/data/IdentityDeletionProcessStatus"; +export * from "./accounts/IdentityController"; +export * from "./accounts/IdentityDeletionProcessController"; +export * from "./accounts/IdentityUtil"; export * from "./certificates/CertificateController"; export * from "./certificates/CertificateIssuer"; export * from "./certificates/CertificateValidator"; @@ -24,55 +24,42 @@ export * from "./certificates/data/items/CertificatePrivateAttributeItem"; export * from "./certificates/data/items/CertificatePrivateAttributeItemSource"; export * from "./certificates/data/items/CertificatePublicAttributeItem"; export * from "./certificates/data/items/CertificateRoleItem"; -export * from "./challenges/ChallengeController"; export * from "./challenges/backbone/ChallengeAuthClient"; export * from "./challenges/backbone/ChallengeClient"; +export * from "./challenges/ChallengeController"; export * from "./challenges/data/Challenge"; export * from "./challenges/data/ChallengeSigned"; -export * from "./devices/DeviceController"; -export * from "./devices/DeviceSecretController"; -export * from "./devices/DevicesController"; export * from "./devices/backbone/BackbonePostDevices"; export * from "./devices/backbone/DeviceAuthClient"; export * from "./devices/backbone/DeviceClient"; +export * from "./devices/DeviceController"; +export * from "./devices/DevicesController"; +export * from "./devices/DeviceSecretController"; export * from "./devices/local/Device"; export * from "./devices/local/DeviceSecretCredentials"; export * from "./devices/local/SendDeviceParameters"; export * from "./devices/transmission/DeviceSharedSecret"; -export * from "./files/FileController"; export * from "./files/backbone/BackboneGetFiles"; export * from "./files/backbone/BackbonePostFiles"; export * from "./files/backbone/FileClient"; +export * from "./files/FileController"; export * from "./files/local/CachedFile"; export * from "./files/local/File"; export * from "./files/local/SendFileParameters"; export * from "./files/transmission/FileMetadata"; export * from "./files/transmission/FileReference"; -export * from "./messages/MessageController"; export * from "./messages/backbone/BackboneGetMessages"; export * from "./messages/backbone/BackbonePostMessages"; export * from "./messages/backbone/MessageClient"; export * from "./messages/local/CachedMessage"; export * from "./messages/local/Message"; export * from "./messages/local/SendMessageParameters"; +export * from "./messages/MessageController"; export * from "./messages/transmission/MessageContentWrapper"; export * from "./messages/transmission/MessageEnvelope"; export * from "./messages/transmission/MessageEnvelopeRecipient"; export * from "./messages/transmission/MessageSignature"; export * from "./messages/transmission/MessageSigned"; -export * from "./relationshipTemplates/RelationshipTemplateController"; -export * from "./relationshipTemplates/backbone/BackboneGetRelationshipTemplates"; -export * from "./relationshipTemplates/backbone/BackbonePostRelationshipTemplates"; -export * from "./relationshipTemplates/backbone/RelationshipTemplateClient"; -export * from "./relationshipTemplates/local/CachedRelationshipTemplate"; -export * from "./relationshipTemplates/local/RelationshipTemplate"; -export * from "./relationshipTemplates/local/SendRelationshipTemplateParameters"; -export * from "./relationshipTemplates/transmission/RelationshipTemplateContentWrapper"; -export * from "./relationshipTemplates/transmission/RelationshipTemplatePublicKey"; -export * from "./relationshipTemplates/transmission/RelationshipTemplateReference"; -export * from "./relationshipTemplates/transmission/RelationshipTemplateSigned"; -export * from "./relationships/RelationshipSecretController"; -export * from "./relationships/RelationshipsController"; export * from "./relationships/backbone/BackboneGetRelationships"; export * from "./relationships/backbone/BackbonePostRelationship"; export * from "./relationships/backbone/RelationshipClient"; @@ -81,6 +68,8 @@ export * from "./relationships/local/Relationship"; export * from "./relationships/local/RelationshipAuditLog"; export * from "./relationships/local/RelationshipAuditLogEntry"; export * from "./relationships/local/SendRelationshipParameters"; +export * from "./relationships/RelationshipsController"; +export * from "./relationships/RelationshipSecretController"; export * from "./relationships/transmission/RelationshipAuditLog"; export * from "./relationships/transmission/RelationshipStatus"; export * from "./relationships/transmission/requests/RelationshipCreationContentCipher"; @@ -89,14 +78,20 @@ export * from "./relationships/transmission/requests/RelationshipCreationContent export * from "./relationships/transmission/responses/RelationshipCreationResponseContentCipher"; export * from "./relationships/transmission/responses/RelationshipCreationResponseContentSigned"; export * from "./relationships/transmission/responses/RelationshipCreationResponseContentWrapper"; -export * from "./secrets/SecretController"; +export * from "./relationshipTemplates/backbone/BackboneGetRelationshipTemplates"; +export * from "./relationshipTemplates/backbone/BackbonePostRelationshipTemplates"; +export * from "./relationshipTemplates/backbone/RelationshipTemplateClient"; +export * from "./relationshipTemplates/local/CachedRelationshipTemplate"; +export * from "./relationshipTemplates/local/RelationshipTemplate"; +export * from "./relationshipTemplates/local/SendRelationshipTemplateParameters"; +export * from "./relationshipTemplates/RelationshipTemplateController"; +export * from "./relationshipTemplates/transmission/RelationshipTemplateContentWrapper"; +export * from "./relationshipTemplates/transmission/RelationshipTemplatePublicKey"; +export * from "./relationshipTemplates/transmission/RelationshipTemplateReference"; +export * from "./relationshipTemplates/transmission/RelationshipTemplateSigned"; export * from "./secrets/data/SecretContainerCipher"; export * from "./secrets/data/SecretContainerPlain"; -export * from "./sync/ChangedItems"; -export * from "./sync/DatawalletModificationsProcessor"; -export { SyncProgressCallback as SyncPercentageCallback, SyncStep } from "./sync/SyncCallback"; -export * from "./sync/SyncController"; -export * from "./sync/SynchronizedCollection"; +export * from "./secrets/SecretController"; export * from "./sync/backbone/BackboneDatawalletModification"; export * from "./sync/backbone/BackboneExternalEvent"; export * from "./sync/backbone/CreateDatawalletModifications"; @@ -105,16 +100,21 @@ export * from "./sync/backbone/GetDatawallet"; export * from "./sync/backbone/GetDatawalletModifications"; export * from "./sync/backbone/StartSyncRun"; export * from "./sync/backbone/SyncClient"; +export * from "./sync/ChangedItems"; export * from "./sync/data/ExternalEvent"; +export * from "./sync/DatawalletModificationsProcessor"; export * from "./sync/local/DatawalletModification"; +export { SyncProgressCallback as SyncPercentageCallback, SyncStep } from "./sync/SyncCallback"; +export * from "./sync/SyncController"; +export * from "./sync/SynchronizedCollection"; export * from "./tokens/AnonymousTokenController"; -export * from "./tokens/TokenController"; export * from "./tokens/backbone/BackboneGetTokens"; export * from "./tokens/backbone/BackbonePostTokens"; export * from "./tokens/backbone/TokenClient"; export * from "./tokens/local/CachedToken"; export * from "./tokens/local/SendTokenParameters"; export * from "./tokens/local/Token"; +export * from "./tokens/TokenController"; export * from "./tokens/transmission/TokenContentDeviceSharedSecret"; export * from "./tokens/transmission/TokenContentFile"; export * from "./tokens/transmission/TokenContentRelationshipTemplate"; diff --git a/packages/transport/src/modules/messages/MessageController.ts b/packages/transport/src/modules/messages/MessageController.ts index bd0ab0c98..2b8b2d60b 100644 --- a/packages/transport/src/modules/messages/MessageController.ts +++ b/packages/transport/src/modules/messages/MessageController.ts @@ -9,9 +9,9 @@ import { MessageSentEvent, MessageWasReadAtChangedEvent } from "../../events"; import { AccountController } from "../accounts/AccountController"; import { File } from "../files/local/File"; import { FileReference } from "../files/transmission/FileReference"; -import { Relationship } from "../relationships/local/Relationship"; -import { RelationshipsController } from "../relationships/RelationshipsController"; import { RelationshipSecretController } from "../relationships/RelationshipSecretController"; +import { RelationshipsController } from "../relationships/RelationshipsController"; +import { Relationship } from "../relationships/local/Relationship"; import { SynchronizedCollection } from "../sync/SynchronizedCollection"; import { BackboneGetMessagesResponse } from "./backbone/BackboneGetMessages"; import { BackbonePostMessagesRecipientRequest } from "./backbone/BackbonePostMessages"; @@ -66,7 +66,7 @@ export class MessageController extends TransportController { public async getMessagesByAddress(address: CoreAddress): Promise { const relationship = await this.parent.relationships.getActiveRelationshipToIdentity(address); if (!relationship) { - throw CoreErrors.messages.noMatchingRelationship(address.toString()); + throw CoreErrors.messages.missingOrInactiveRelationship(address.toString()); } return await this.getMessagesByRelationshipId(relationship.id); } @@ -250,7 +250,7 @@ export class MessageController extends TransportController { for (const recipient of parsedParams.recipients) { const relationship = await this.relationships.getActiveRelationshipToIdentity(recipient); if (!relationship) { - throw CoreErrors.general.recordNotFound(Relationship, recipient.toString()); + throw CoreErrors.messages.missingOrInactiveRelationship(recipient.toString()); } const cipherForRecipient = await this.secrets.encrypt(relationship.relationshipSecretId, serializedSecret); @@ -286,7 +286,7 @@ export class MessageController extends TransportController { for (const recipient of parsedParams.recipients) { const relationship = await this.relationships.getActiveRelationshipToIdentity(CoreAddress.from(recipient)); if (!relationship) { - throw CoreErrors.general.recordNotFound(Relationship, recipient.toString()); + throw CoreErrors.messages.missingOrInactiveRelationship(recipient.toString()); } const signature = await this.secrets.sign(relationship.relationshipSecretId, plaintextBuffer); @@ -427,7 +427,7 @@ export class MessageController extends TransportController { relationship = await this.relationships.getActiveRelationshipToIdentity(envelope.createdBy); if (!relationship) { - throw CoreErrors.messages.noMatchingRelationship(envelope.createdBy.toString()); + throw CoreErrors.messages.missingOrInactiveRelationship(envelope.createdBy.toString()); } const [peerMessage, peerKey] = await this.decryptPeerEnvelope(envelope, relationship); diff --git a/packages/transport/src/modules/relationships/RelationshipsController.ts b/packages/transport/src/modules/relationships/RelationshipsController.ts index a0f605bd5..18f69889d 100644 --- a/packages/transport/src/modules/relationships/RelationshipsController.ts +++ b/packages/transport/src/modules/relationships/RelationshipsController.ts @@ -12,6 +12,7 @@ import { AccountController } from "../accounts/AccountController"; import { Identity } from "../accounts/data/Identity"; import { RelationshipTemplate } from "../relationshipTemplates/local/RelationshipTemplate"; import { SynchronizedCollection } from "../sync/SynchronizedCollection"; +import { RelationshipSecretController } from "./RelationshipSecretController"; import { BackbonePutRelationshipsResponse } from "./backbone/BackbonePutRelationship"; import { BackboneRelationship } from "./backbone/BackboneRelationship"; import { RelationshipClient } from "./backbone/RelationshipClient"; @@ -19,7 +20,6 @@ import { CachedRelationship } from "./local/CachedRelationship"; import { Relationship } from "./local/Relationship"; import { RelationshipAuditLog } from "./local/RelationshipAuditLog"; import { ISendRelationshipParameters, SendRelationshipParameters } from "./local/SendRelationshipParameters"; -import { RelationshipSecretController } from "./RelationshipSecretController"; import { RelationshipStatus } from "./transmission/RelationshipStatus"; import { RelationshipCreationContentCipher } from "./transmission/requests/RelationshipCreationContentCipher"; import { RelationshipCreationContentSigned } from "./transmission/requests/RelationshipCreationContentSigned"; @@ -219,6 +219,17 @@ export class RelationshipsController extends TransportController { return await this.completeStateTransition(RelationshipStatus.Revoked, relationshipId); } + public async terminate(relationshipId: CoreId): Promise { + const relationship = await this.getRelationship(relationshipId); + if (!relationship) { + throw CoreErrors.general.recordNotFound("Relationship", relationshipId.toString()); + } + if (relationship.status !== RelationshipStatus.Active) { + throw CoreErrors.relationships.wrongRelationshipStatus(relationship.status); + } + return await this.completeStateTransition(RelationshipStatus.Terminated, relationshipId); + } + private async updateCacheOfRelationship(relationship: Relationship, response?: BackboneRelationship) { if (!response) { response = (await this.client.getRelationship(relationship.id.toString())).value; @@ -430,6 +441,10 @@ export class RelationshipsController extends TransportController { backboneResponse = (await this.client.revokeRelationship(id.toString())).value; break; + case RelationshipStatus.Terminated: + backboneResponse = (await this.client.terminateRelationship(id.toString())).value; + break; + default: throw new TransportError("target change status not supported"); } diff --git a/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts b/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts index 5a58729d6..53cd80edd 100644 --- a/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts +++ b/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts @@ -22,6 +22,10 @@ export class RelationshipClient extends RESTClientAuthenticate { return await this.put(`/api/v1/Relationships/${relationshipId}/Revoke`, {}); } + public async terminateRelationship(relationshipId: string): Promise> { + return await this.put(`/api/v1/Relationships/${relationshipId}/Terminate`, {}); + } + public async getRelationships(request?: BackboneGetRelationshipsRequest): Promise>> { return await this.getPaged("/api/v1/Relationships", request); } diff --git a/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts b/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts index 9473996f0..bf2451829 100644 --- a/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts +++ b/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts @@ -15,5 +15,6 @@ export enum RelationshipAuditLogEntryReason { Creation = "Creation", AcceptanceOfCreation = "AcceptanceOfCreation", RejectionOfCreation = "RejectionOfCreation", - RevocationOfCreation = "RevocationOfCreation" + RevocationOfCreation = "RevocationOfCreation", + Termination = "Termination" } diff --git a/packages/transport/src/modules/relationships/transmission/RelationshipStatus.ts b/packages/transport/src/modules/relationships/transmission/RelationshipStatus.ts index 4931732d9..a9a13c363 100644 --- a/packages/transport/src/modules/relationships/transmission/RelationshipStatus.ts +++ b/packages/transport/src/modules/relationships/transmission/RelationshipStatus.ts @@ -3,6 +3,5 @@ export enum RelationshipStatus { Active = "Active", Rejected = "Rejected", Revoked = "Revoked", - Terminating = "Terminating", Terminated = "Terminated" } diff --git a/packages/transport/test/end2end/End2End.test.ts b/packages/transport/test/end2end/End2End.test.ts index 481d82896..1ac81f5c6 100644 --- a/packages/transport/test/end2end/End2End.test.ts +++ b/packages/transport/test/end2end/End2End.test.ts @@ -83,7 +83,6 @@ describe("RelationshipTest: Accept", function () { expect(templateContent.value).toHaveProperty("mycontent"); expect(templateContent.value.mycontent).toBe("template"); - // Send Request const request = await to.relationships.sendRelationship({ template: templateTo, creationContent: { @@ -103,7 +102,6 @@ describe("RelationshipTest: Accept", function () { expect(request.cache?.auditLog).toHaveLength(1); expect(request.cache!.auditLog[0].newStatus).toBe(RelationshipStatus.Pending); - // Accept relationship const syncedRelationships = await TestUtil.syncUntilHasRelationships(from); expect(syncedRelationships).toHaveLength(1); const pendingRelationship = syncedRelationships[0]; @@ -126,7 +124,6 @@ describe("RelationshipTest: Accept", function () { expect(acceptedRelationshipFromSelf.peer).toBeDefined(); - // Get accepted relationship const syncedRelationshipsPeer = await TestUtil.syncUntilHasRelationships(to); expect(syncedRelationshipsPeer).toHaveLength(1); const acceptedRelationshipPeer = syncedRelationshipsPeer[0]; @@ -216,7 +213,6 @@ describe("RelationshipTest: Reject", function () { expect(request.cache!.template.isOwn).toBe(false); expect(request.status).toStrictEqual(RelationshipStatus.Pending); - // Reject relationship const syncedRelationships = await TestUtil.syncUntilHasRelationships(from); expect(syncedRelationships).toHaveLength(1); const pendingRelationship = syncedRelationships[0]; @@ -237,7 +233,6 @@ describe("RelationshipTest: Reject", function () { expect(rejectedRelationshipFromSelf.peer).toBeDefined(); - // Get rejected relationship const syncedRelationshipsPeer = await TestUtil.syncUntilHasRelationships(to); expect(syncedRelationshipsPeer).toHaveLength(1); const rejectedRelationshipPeer = syncedRelationshipsPeer[0]; @@ -331,7 +326,6 @@ describe("RelationshipTest: Revoke", function () { expect(request.cache!.template.isOwn).toBe(false); expect(request.status).toStrictEqual(RelationshipStatus.Pending); - // Revoke relationship const syncedRelationships = await TestUtil.syncUntilHasRelationships(templator); expect(syncedRelationships).toHaveLength(1); const pendingRelationship = syncedRelationships[0]; @@ -355,7 +349,6 @@ describe("RelationshipTest: Revoke", function () { expect(revokedRelationshipSelf.peer).toBeDefined(); expect(revokedRelationshipSelf.peer.address.toString()).toStrictEqual(revokedRelationshipSelf.cache!.template.cache?.identity.address.toString()); - // Get revoked relationship const syncedRelationshipsPeer = await TestUtil.syncUntilHasRelationships(templator); expect(syncedRelationshipsPeer).toHaveLength(1); const revokedRelationshipPeer = syncedRelationshipsPeer[0]; @@ -407,11 +400,9 @@ describe("RelationshipTest: Revoke", function () { } }); - // Revoke relationship const revokedRelationshipSelf = await requestor.relationships.revoke(pendingRelationship.id); expect(revokedRelationshipSelf.status).toStrictEqual(RelationshipStatus.Revoked); - // Get revoked relationship await TestUtil.syncUntilHasRelationships(templator, 2); // wait for pending and revoked const relationshipsPeer = await templator.relationships.getRelationships({}); expect(relationshipsPeer).toHaveLength(1); @@ -420,6 +411,50 @@ describe("RelationshipTest: Revoke", function () { }); }); +describe("RelationshipTest: Terminate", function () { + let connection: IDatabaseConnection; + let transport: Transport; + + let from: AccountController; + let to: AccountController; + + beforeAll(async function () { + connection = await TestUtil.createDatabaseConnection(); + transport = TestUtil.createTransport(connection); + + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 2); + from = accounts[0]; + to = accounts[1]; + }); + + afterAll(async function () { + await from.close(); + await to.close(); + await connection.close(); + }); + + test("should terminate a relationship between two accounts", async function () { + const relationshipId = (await TestUtil.addRelationship(from, to)).acceptedRelationshipFromSelf.id; + + const terminatedRelationshipFromSelf = await from.relationships.terminate(relationshipId); + expect(terminatedRelationshipFromSelf.id.toString()).toStrictEqual(relationshipId.toString()); + expect(terminatedRelationshipFromSelf.status).toStrictEqual(RelationshipStatus.Terminated); + expect(terminatedRelationshipFromSelf.cache?.auditLog).toHaveLength(3); + expect(terminatedRelationshipFromSelf.cache!.auditLog[2].newStatus).toBe(RelationshipStatus.Terminated); + + const syncedRelationshipsPeer = await TestUtil.syncUntilHasRelationships(to); + expect(syncedRelationshipsPeer).toHaveLength(1); + const terminatedRelationshipPeer = syncedRelationshipsPeer[0]; + + expect(terminatedRelationshipPeer.id.toString()).toStrictEqual(relationshipId.toString()); + expect(terminatedRelationshipPeer.status).toStrictEqual(RelationshipStatus.Terminated); + expect(terminatedRelationshipPeer.cache?.auditLog).toHaveLength(3); + expect(terminatedRelationshipPeer.cache!.auditLog[2].newStatus).toBe(RelationshipStatus.Terminated); + }); +}); + describe("MessageTest", function () { let connection: IDatabaseConnection; let transport: Transport; diff --git a/packages/transport/test/modules/challenges/Challenges.test.ts b/packages/transport/test/modules/challenges/Challenges.test.ts index 99a39c64a..44ad045cf 100644 --- a/packages/transport/test/modules/challenges/Challenges.test.ts +++ b/packages/transport/test/modules/challenges/Challenges.test.ts @@ -54,4 +54,11 @@ describe("ChallengeTest", function () { expect(validationResult.isValid).toBe(true); expect(validationResult.correspondingRelationship).toBeDefined(); }); + + test("should not create a relationship challenge on terminated relationship", async function () { + const terminatedRelationship = (await TestUtil.terminateRelationship(recipient, sender)).terminatedRelationshipPeer; + await expect(sender.challenges.createChallenge(ChallengeType.Relationship, terminatedRelationship)).rejects.toThrow( + "error.transport.challenges.challengeTypeRequiresActiveRelationship" + ); + }); }); diff --git a/packages/transport/test/modules/messages/MessageController.test.ts b/packages/transport/test/modules/messages/MessageController.test.ts index ca1427a28..da0e3958a 100644 --- a/packages/transport/test/modules/messages/MessageController.test.ts +++ b/packages/transport/test/modules/messages/MessageController.test.ts @@ -45,7 +45,7 @@ describe("MessageController", function () { sender = accounts[0]; recipient = accounts[1]; const rels = await TestUtil.addRelationship(sender, recipient); - relationshipId = rels[0].id; + relationshipId = rels.acceptedRelationshipFromSelf.id; }); afterAll(async function () { @@ -179,4 +179,9 @@ describe("MessageController", function () { const unreadMessage = await recipient.messages.markMessageAsUnread(readMessage.id); expect(unreadMessage.wasReadAt).toBeUndefined(); }); + + test("should not send a message on a terminated relationship", async function () { + await TestUtil.terminateRelationship(sender, recipient); + await expect(TestUtil.sendMessage(sender, recipient)).rejects.toThrow("error.transport.messages.missingOrInactiveRelationship"); + }); }); diff --git a/packages/transport/test/testHelpers/TestUtil.ts b/packages/transport/test/testHelpers/TestUtil.ts index 708488967..a1571f0c7 100644 --- a/packages/transport/test/testHelpers/TestUtil.ts +++ b/packages/transport/test/testHelpers/TestUtil.ts @@ -339,7 +339,10 @@ export class TestUtil { expect(acceptedRelationshipPeer.status).toStrictEqual(RelationshipStatus.Rejected); } - public static async addRelationship(from: AccountController, to: AccountController): Promise { + public static async addRelationship( + from: AccountController, + to: AccountController + ): Promise<{ acceptedRelationshipFromSelf: Relationship; acceptedRelationshipPeer: Relationship }> { const templateFrom = await from.relationshipTemplates.sendRelationshipTemplate({ content: { mycontent: "template" @@ -378,7 +381,18 @@ export class TestUtil { expect(relRequest.id.toString()).toBe(acceptedRelationshipFromSelf.id.toString()); expect(relRequest.id.toString()).toBe(acceptedRelationshipPeer.id.toString()); - return [acceptedRelationshipFromSelf, acceptedRelationshipPeer]; + return { acceptedRelationshipFromSelf, acceptedRelationshipPeer }; + } + + public static async terminateRelationship( + from: AccountController, + to: AccountController + ): Promise<{ terminatedRelationshipFromSelf: Relationship; terminatedRelationshipPeer: Relationship }> { + const relationshipId = (await from.relationships.getRelationshipToIdentity(to.identity.address))!.id; + const terminatedRelationshipFromSelf = await from.relationships.terminate(relationshipId); + const terminatedRelationshipPeer = (await TestUtil.syncUntil(to, (syncResult) => syncResult.relationships.length > 0)).relationships[0]; + + return { terminatedRelationshipFromSelf, terminatedRelationshipPeer }; } /** diff --git a/packages/transport/test/utils/IdentityGenerator.test.ts b/packages/transport/test/utils/IdentityGenerator.test.ts index 3e33896fd..867379145 100644 --- a/packages/transport/test/utils/IdentityGenerator.test.ts +++ b/packages/transport/test/utils/IdentityGenerator.test.ts @@ -12,7 +12,7 @@ describe("IdentityGeneratorTest", function () { const address = await IdentityUtil.createAddress(kp.publicKey, "prod.enmeshed.eu"); expect(address).toBeDefined(); expect(address.address).toBeDefined(); - expect(address.address.startsWith("did:e:prod.enmeshed.eu")).toBe(true); + expect(address.address.startsWith("did:e:prod.enmeshed.eu:dids:")).toBe(true); }); test("should create a correct address object (test 0)", async function () { From 83b955a59548f27f398a271d2d8e91e8005312c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Fri, 24 May 2024 18:04:30 +0200 Subject: [PATCH 04/40] Refactor/remove all remaining relationship changes (#137) * chore: remove RelationshipCreationChangeRequestContent * refactor: remove RelationshipChangeDTO * refactor: adapt tests * fix: adapt more tests * chore: update schemas * refactor: use ATT for attribute ids * refactor: use did format for address strings --- ...edByOwnerNotificationItemProcessor.test.ts | 2 +- ...tedByPeerNotificationItemProcessor.test.ts | 2 +- ...tedByPeerNotificationItemProcessor.test.ts | 2 +- .../DecideRequestParamsValidator.test.ts | 2 +- .../OutgoingRequestsController.test.ts | 9 +- .../requests/RequestsIntegrationTest.ts | 14 +- ...oposeAttributeRequestItemProcessor.test.ts | 18 +- .../ReadAttributeRequestItemProcessor.test.ts | 22 +- ...ShareAttributeRequestItemProcessor.test.ts | 2 +- .../requests/testHelpers/TestObjectFactory.ts | 26 +- ...elationshipCreationChangeRequestContent.ts | 27 - packages/content/src/relationships/index.ts | 1 - .../StatementValueTest.test.ts | 56 +- packages/content/test/messages/Mail.test.ts | 4 +- .../types/transport/RelationshipChangeDTO.ts | 34 - .../src/types/transport/RelationshipDTO.ts | 2 - packages/runtime/src/types/transport/index.ts | 3 +- .../runtime/src/useCases/common/Schemas.ts | 603 +++++++++--------- .../relationships/RelationshipMapper.ts | 30 +- .../test/consumption/attributes.test.ts | 2 +- .../test/lib/testUtilsWithInactiveModules.ts | 19 +- .../test/modules/RequestModule.test.ts | 15 +- .../runtime/test/transport/challenges.test.ts | 4 +- .../runtime/test/transport/messages.test.ts | 8 +- 24 files changed, 397 insertions(+), 510 deletions(-) delete mode 100644 packages/content/src/relationships/RelationshipCreationChangeRequestContent.ts delete mode 100644 packages/runtime/src/types/transport/RelationshipChangeDTO.ts diff --git a/packages/consumption/test/modules/notifications/itemProcessors/attributeDeleted/OwnSharedAttributeDeletedByOwnerNotificationItemProcessor.test.ts b/packages/consumption/test/modules/notifications/itemProcessors/attributeDeleted/OwnSharedAttributeDeletedByOwnerNotificationItemProcessor.test.ts index 3b71dc849..8e2890aba 100644 --- a/packages/consumption/test/modules/notifications/itemProcessors/attributeDeleted/OwnSharedAttributeDeletedByOwnerNotificationItemProcessor.test.ts +++ b/packages/consumption/test/modules/notifications/itemProcessors/attributeDeleted/OwnSharedAttributeDeletedByOwnerNotificationItemProcessor.test.ts @@ -299,7 +299,7 @@ describe("OwnSharedAttributeDeletedByPeerNotificationItemProcessor", function () }); test("runs all processor methods for an unknown attribute", async function () { - const unknownAttributeId = CoreId.from("id1xxxxxxxxxxxxxxxxx"); + const unknownAttributeId = CoreId.from("ATT"); const notificationItem = OwnSharedAttributeDeletedByOwnerNotificationItem.from({ attributeId: unknownAttributeId diff --git a/packages/consumption/test/modules/notifications/itemProcessors/attributeDeleted/PeerSharedAttributeDeletedByPeerNotificationItemProcessor.test.ts b/packages/consumption/test/modules/notifications/itemProcessors/attributeDeleted/PeerSharedAttributeDeletedByPeerNotificationItemProcessor.test.ts index 868cbf090..33c402d0f 100644 --- a/packages/consumption/test/modules/notifications/itemProcessors/attributeDeleted/PeerSharedAttributeDeletedByPeerNotificationItemProcessor.test.ts +++ b/packages/consumption/test/modules/notifications/itemProcessors/attributeDeleted/PeerSharedAttributeDeletedByPeerNotificationItemProcessor.test.ts @@ -249,7 +249,7 @@ describe("PeerSharedAttributeDeletedByPeerNotificationItemProcessor", function ( }); test("runs all processor methods for an unknown attribute", async function () { - const unknownAttributeId = CoreId.from("id1xxxxxxxxxxxxxxxxx"); + const unknownAttributeId = CoreId.from("ATT"); const notificationItem = PeerSharedAttributeDeletedByPeerNotificationItem.from({ attributeId: unknownAttributeId diff --git a/packages/consumption/test/modules/notifications/itemProcessors/attributeDeleted/ThirdPartyOwnedRelationshipAttributeDeletedByPeerNotificationItemProcessor.test.ts b/packages/consumption/test/modules/notifications/itemProcessors/attributeDeleted/ThirdPartyOwnedRelationshipAttributeDeletedByPeerNotificationItemProcessor.test.ts index 5c72abbaa..963337ec6 100644 --- a/packages/consumption/test/modules/notifications/itemProcessors/attributeDeleted/ThirdPartyOwnedRelationshipAttributeDeletedByPeerNotificationItemProcessor.test.ts +++ b/packages/consumption/test/modules/notifications/itemProcessors/attributeDeleted/ThirdPartyOwnedRelationshipAttributeDeletedByPeerNotificationItemProcessor.test.ts @@ -113,7 +113,7 @@ describe("ThirdPartyOwnedRelationshipAttributeDeletedByPeerNotificationItemProce }); test("runs all processor methods for an unknown attribute", async function () { - const unknownAttributeId = CoreId.from("id1xxxxxxxxxxxxxxxxx"); + const unknownAttributeId = CoreId.from("ATT"); const notificationItem = ThirdPartyOwnedRelationshipAttributeDeletedByPeerNotificationItem.from({ attributeId: unknownAttributeId diff --git a/packages/consumption/test/modules/requests/DecideRequestParamsValidator.test.ts b/packages/consumption/test/modules/requests/DecideRequestParamsValidator.test.ts index 29dedefab..d6f333413 100644 --- a/packages/consumption/test/modules/requests/DecideRequestParamsValidator.test.ts +++ b/packages/consumption/test/modules/requests/DecideRequestParamsValidator.test.ts @@ -323,7 +323,7 @@ describe("DecideRequestParametersValidator", function () { content: data.input.request, createdAt: CoreDate.utc(), isOwn: true, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), source: { reference: await CoreId.generate(), type: "Message" }, status: LocalRequestStatus.Open, statusLog: [] diff --git a/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts b/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts index b6c98d741..d98e69687 100644 --- a/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts +++ b/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts @@ -23,7 +23,7 @@ import { OutgoingRequestStatusChangedEvent, ValidationResult } from "../../../src"; -import { loggerFactory, TestUtil } from "../../core/TestUtil"; +import { TestUtil, loggerFactory } from "../../core/TestUtil"; import { RequestsGiven, RequestsTestsContext, RequestsThen, RequestsWhen } from "./RequestsIntegrationTest"; import { TestObjectFactory } from "./testHelpers/TestObjectFactory"; import { ITestRequestItem, TestRequestItem } from "./testHelpers/TestRequestItem"; @@ -290,7 +290,7 @@ describe("OutgoingRequestsController", function () { test("uses the content from onExistingRelationship when the relationship exists", async function () { await When.iCreateAnOutgoingRequestFromRelationshipCreationWhenRelationshipExistsWith({ - responseSource: TestObjectFactory.createIncomingIMessageWithResponse(CoreAddress.from("id1"), "requestIdReceivedFromPeer"), + responseSource: TestObjectFactory.createIncomingIMessageWithResponse(CoreAddress.from("did:e:a-domain:dids:anidentity"), "requestIdReceivedFromPeer"), response: TestObjectFactory.createResponse("requestIdReceivedFromPeer") }); await Then.theRequestHasTheId("requestIdReceivedFromPeer"); @@ -316,7 +316,10 @@ describe("OutgoingRequestsController", function () { test("uses the id from the Message content for the created Local Request", async function () { await When.iCreateAnOutgoingRequestFromMessageWith({ - responseSource: TestObjectFactory.createIncomingIMessageWithResponse(CoreAddress.from("id1"), CoreId.from("requestIdReceivedFromPeer")), + responseSource: TestObjectFactory.createIncomingIMessageWithResponse( + CoreAddress.from("did:e:a-domain:dids:anidentity"), + CoreId.from("requestIdReceivedFromPeer") + ), response: TestObjectFactory.createResponse("requestIdReceivedFromPeer") }); await Then.theRequestHasTheId("requestIdReceivedFromPeer"); diff --git a/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts b/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts index 75ffc15c3..ad202498c 100644 --- a/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts +++ b/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts @@ -80,7 +80,7 @@ export class RequestsTestsContext { } as ConsumptionController; const processorRegistry = new RequestItemProcessorRegistry(fakeConsumptionController, new Map([[TestRequestItem, TestRequestItemProcessor]])); - context.currentIdentity = CoreAddress.from("id12345"); + context.currentIdentity = CoreAddress.from("did:e:a-domain:dids:anidentity"); context.outgoingRequestsController = new OutgoingRequestsController( collection, @@ -283,7 +283,7 @@ export class RequestsGiven { this.context.givenLocalRequest = await this.context.outgoingRequestsController.create({ content: params.content, - peer: CoreAddress.from("id1") + peer: CoreAddress.from("did:e:a-domain:dids:anidentity") }); await this.moveOutgoingRequestToStatus(this.context.givenLocalRequest, params.status); @@ -576,7 +576,7 @@ export class RequestsWhen { }) ] }, - peer: params?.peer ?? CoreAddress.from("id1") + peer: params?.peer ?? CoreAddress.from("did:e:a-domain:dids:anidentity") }; this.context.validationResult = await this.context.outgoingRequestsController.canCreate(realParams); @@ -595,7 +595,7 @@ export class RequestsWhen { }) ] }, - peer: CoreAddress.from("id1") + peer: CoreAddress.from("did:e:a-domain:dids:anidentity") }; this.context.localRequestAfterAction = await this.context.outgoingRequestsController.create(params); @@ -642,7 +642,7 @@ export class RequestsWhen { onExistingRelationship: TestObjectFactory.createRequestWithOneItem() }) ); - params.responseSource ??= TestObjectFactory.createIncomingIMessageWithResponse(CoreAddress.from("id1"), CoreId.from("REQ1")); + params.responseSource ??= TestObjectFactory.createIncomingIMessageWithResponse(CoreAddress.from("did:e:a-domain:dids:anidentity"), CoreId.from("REQ1")); params.response ??= TestObjectFactory.createResponse(); this.context.localRequestAfterAction = await this.context.outgoingRequestsController.createAndCompleteFromRelationshipTemplateResponse( @@ -819,7 +819,7 @@ export class RequestsWhen { }) ] }, - peer: CoreAddress.from("id1") + peer: CoreAddress.from("did:e:a-domain:dids:anidentity") }; this.context.actionToTry = async () => { @@ -831,7 +831,7 @@ export class RequestsWhen { public iTryToCreateAnOutgoingRequestWithoutContent(): Promise { const paramsWithoutItems: Omit = { - peer: CoreAddress.from("id1") + peer: CoreAddress.from("did:e:a-domain:dids:anidentity") }; this.context.actionToTry = async () => { diff --git a/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts index 57b251bab..bfc184dcd 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts @@ -257,7 +257,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -298,7 +298,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -328,7 +328,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -350,7 +350,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { }); test("returns an error when the given Attribute id belongs to a peer Attribute", async function () { - const someOtherIdentity = CoreAddress.from("id1"); + const someOtherIdentity = CoreAddress.from("did:e:a-domain:dids:anidentity"); const idOfAttributeOfOtherIdentity = await ConsumptionIds.attribute.generate(); @@ -396,7 +396,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { }); test("returns an error when the given Attribute's owner is not the current identity", async function () { - const someOtherIdentity = CoreAddress.from("id1"); + const someOtherIdentity = CoreAddress.from("did:e:a-domain:dids:anidentity"); const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: true, @@ -449,7 +449,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -486,7 +486,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -519,7 +519,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -729,7 +729,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { attribute: TestObjectFactory.createIdentityAttribute() }); const requestId = await ConsumptionIds.request.generate(); - const peer = CoreAddress.from("id1"); + const peer = CoreAddress.from("did:e:a-domain:dids:anidentity"); const incomingRequest = LocalRequest.from({ id: requestId, diff --git a/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts index 22ba54461..fbb3b99b2 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts @@ -227,7 +227,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -256,7 +256,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -285,7 +285,7 @@ describe("ReadAttributeRequestItemProcessor", function () { test("can be called with an existing RelationshipAttribute by a third party", async function () { const attribute = await consumptionController.attributes.createLocalAttribute({ content: TestObjectFactory.createRelationshipAttribute({ - owner: CoreAddress.from("id1") + owner: CoreAddress.from("did:e:a-domain:dids:anidentity") }) }); @@ -293,8 +293,8 @@ describe("ReadAttributeRequestItemProcessor", function () { mustBeAccepted: true, query: ThirdPartyRelationshipAttributeQuery.from({ key: "aKey", - owner: "id1", - thirdParty: ["id1"] + owner: "did:e:a-domain:dids:anidentity", + thirdParty: ["did:e:a-domain:dids:anidentity"] }) }); const requestId = await ConsumptionIds.request.generate(); @@ -302,7 +302,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -331,7 +331,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -353,7 +353,7 @@ describe("ReadAttributeRequestItemProcessor", function () { }); test("returns an error when the given Attribute id belongs to a peer Attribute", async function () { - const peer = CoreAddress.from("id1"); + const peer = CoreAddress.from("did:e:a-domain:dids:anidentity"); const peerAttributeId = await ConsumptionIds.attribute.generate(); @@ -415,7 +415,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -448,7 +448,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("id1"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -808,7 +808,7 @@ describe("ReadAttributeRequestItemProcessor", function () { query: IdentityAttributeQuery.from({ valueType: "GivenName" }) }); const requestId = await ConsumptionIds.request.generate(); - const peer = CoreAddress.from("id1"); + const peer = CoreAddress.from("did:e:a-domain:dids:anidentity"); const incomingRequest = LocalRequest.from({ id: requestId, diff --git a/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts index 3f57e269e..3423df852 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts @@ -353,7 +353,7 @@ describe("ShareAttributeRequestItemProcessor", function () { sourceAttributeId: sourceAttribute.id }); const requestId = await ConsumptionIds.request.generate(); - const peer = CoreAddress.from("id1"); + const peer = CoreAddress.from("did:e:a-domain:dids:anidentity"); const localRequest = LocalRequest.from({ id: requestId, createdAt: CoreDate.utc(), diff --git a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts index 5264c0a68..a84159a4b 100644 --- a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts +++ b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts @@ -45,7 +45,7 @@ export class TestObjectFactory { peer: properties?.peer ?? Identity.from({ - address: CoreAddress.from("id1"), + address: CoreAddress.from("did:e:a-domain:dids:anidentity"), publicKey: CryptoSignaturePublicKey.from({ algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519, publicKey: CoreBuffer.from("L1sPFQgS5CxgGs1ejBcWCQLCpeFXbRc1TQnSpuHQqDQ") @@ -77,7 +77,7 @@ export class TestObjectFactory { peer: properties?.peer ?? Identity.from({ - address: CoreAddress.from("id1"), + address: CoreAddress.from("did:e:a-domain:dids:anidentity"), publicKey: CryptoSignaturePublicKey.from({ algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519, publicKey: CoreBuffer.from("L1sPFQgS5CxgGs1ejBcWCQLCpeFXbRc1TQnSpuHQqDQ") @@ -100,7 +100,7 @@ export class TestObjectFactory { { createdAt: CoreDate.from("2020-01-02T00:00:00.000Z"), - createdBy: CoreAddress.from("id1"), + createdBy: CoreAddress.from("did:e:a-domain:dids:anidentity"), createdByDevice: CoreId.from("DVC1"), reason: RelationshipAuditLogEntryReason.AcceptanceOfCreation, oldStatus: RelationshipStatus.Pending, @@ -118,7 +118,7 @@ export class TestObjectFactory { peer: properties?.peer ?? Identity.from({ - address: CoreAddress.from("id1"), + address: CoreAddress.from("did:e:a-domain:dids:anidentity"), publicKey: CryptoSignaturePublicKey.from({ algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519, publicKey: CoreBuffer.from("L1sPFQgS5CxgGs1ejBcWCQLCpeFXbRc1TQnSpuHQqDQ") @@ -141,7 +141,7 @@ export class TestObjectFactory { { createdAt: CoreDate.from("2020-01-02T00:00:00.000Z"), - createdBy: CoreAddress.from("id1"), + createdBy: CoreAddress.from("did:e:a-domain:dids:anidentity"), createdByDevice: CoreId.from("DVC1"), reason: RelationshipAuditLogEntryReason.AcceptanceOfCreation, oldStatus: RelationshipStatus.Pending, @@ -150,7 +150,7 @@ export class TestObjectFactory { { createdAt: CoreDate.from("2020-01-03T00:00:00.000Z"), - createdBy: CoreAddress.from("id1"), + createdBy: CoreAddress.from("did:e:a-domain:dids:anidentity"), createdByDevice: CoreId.from("DVC1"), reason: RelationshipAuditLogEntryReason.Termination, oldStatus: RelationshipStatus.Active, @@ -165,7 +165,7 @@ export class TestObjectFactory { public static createIdentityAttribute(properties?: Partial): IdentityAttribute { return IdentityAttribute.from({ value: properties?.value ?? GivenName.fromAny({ value: "AGivenName" }), - owner: properties?.owner ?? CoreAddress.from("id1") + owner: properties?.owner ?? CoreAddress.from("did:e:a-domain:dids:anidentity") }); } @@ -175,7 +175,7 @@ export class TestObjectFactory { confidentiality: RelationshipAttributeConfidentiality.Public, key: "aKey", isTechnical: false, - owner: properties?.owner ?? CoreAddress.from("id1") + owner: properties?.owner ?? CoreAddress.from("did:e:a-domain:dids:anidentity") }); } @@ -191,7 +191,7 @@ export class TestObjectFactory { const requestJSON: ILocalRequest = { id: CoreId.from("REQ1"), isOwn: true, - peer: CoreAddress.from("id11"), + peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), content: TestObjectFactory.createRequestWithOneItem(params.contentProperties), source: { type: "Message", reference: CoreId.from("MSG1") }, @@ -289,7 +289,7 @@ export class TestObjectFactory { cache: { content: content, createdAt: creationDate, - createdBy: CoreAddress.from("id1"), + createdBy: CoreAddress.from("did:e:a-domain:dids:anidentity"), createdByDevice: { id: "senderDeviceId" }, receivedByEveryone: false, recipients: [ @@ -344,7 +344,7 @@ export class TestObjectFactory { receivedByEveryone: false, recipients: [ { - address: CoreAddress.from("id1"), + address: CoreAddress.from("did:e:a-domain:dids:anidentity"), encryptedKey: CryptoCipher.from({ cipher: CoreBuffer.fromUtf8("test"), algorithm: CryptoEncryptionAlgorithm.XCHACHA20_POLY1305, @@ -373,11 +373,11 @@ export class TestObjectFactory { cache: { content: {}, createdAt: CoreDate.utc(), - createdBy: CoreAddress.from("id1"), + createdBy: CoreAddress.from("did:e:a-domain:dids:anidentity"), createdByDevice: { id: "senderDeviceId" }, maxNumberOfAllocations: 1, identity: { - address: CoreAddress.from("id1"), + address: CoreAddress.from("did:e:a-domain:dids:anidentity"), publicKey: CryptoSignaturePublicKey.from({ algorithm: CryptoSignatureAlgorithm.ECDSA_ED25519, publicKey: CoreBuffer.fromBase64URL("aS-A8ywidL00DfBlZySOG_1-NdSBW38uGD1il_Ymk5g") diff --git a/packages/content/src/relationships/RelationshipCreationChangeRequestContent.ts b/packages/content/src/relationships/RelationshipCreationChangeRequestContent.ts deleted file mode 100644 index ecfebd8d8..000000000 --- a/packages/content/src/relationships/RelationshipCreationChangeRequestContent.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; -import { ContentJSON } from "../ContentJSON"; -import { IResponse, Response, ResponseJSON } from "../requests/response/Response"; - -export interface RelationshipCreationChangeRequestContentJSON extends ContentJSON { - "@type": "RelationshipCreationChangeRequestContent"; - response: ResponseJSON; -} - -export interface IRelationshipCreationChangeRequestContent extends ISerializable { - response: IResponse; -} - -@type("RelationshipCreationChangeRequestContent") -export class RelationshipCreationChangeRequestContent extends Serializable implements IRelationshipCreationChangeRequestContent { - @serialize() - @validate() - public response: Response; - - public static from(value: IRelationshipCreationChangeRequestContent | Omit): RelationshipCreationChangeRequestContent { - return this.fromAny(value); - } - - public override toJSON(verbose?: boolean | undefined, serializeAsString?: boolean | undefined): RelationshipCreationChangeRequestContentJSON { - return super.toJSON(verbose, serializeAsString) as RelationshipCreationChangeRequestContentJSON; - } -} diff --git a/packages/content/src/relationships/index.ts b/packages/content/src/relationships/index.ts index dba4e47a8..57d92728d 100644 --- a/packages/content/src/relationships/index.ts +++ b/packages/content/src/relationships/index.ts @@ -1,3 +1,2 @@ -export * from "./RelationshipCreationChangeRequestContent"; export * from "./RelationshipCreationContent"; export * from "./RelationshipTemplateContent"; diff --git a/packages/content/test/attributeValues/StatementValueTest.test.ts b/packages/content/test/attributeValues/StatementValueTest.test.ts index abffbc453..fbb2e5d34 100644 --- a/packages/content/test/attributeValues/StatementValueTest.test.ts +++ b/packages/content/test/attributeValues/StatementValueTest.test.ts @@ -8,13 +8,13 @@ new GenericValueTest().runParametrized({ expectedJSON: { "@type": "Statement", subject: { - address: "id1234" + address: "did:e:a-domain:dids:anidentity" }, predicate: { value: "hasAttribute" }, object: { - address: "id1235", + address: "did:e:a-domain:dids:anidentity", attributes: [ { givenName: "Max", @@ -23,7 +23,7 @@ new GenericValueTest().runParametrized({ ] }, issuer: { - address: "id1236" + address: "did:e:a-domain:dids:anidentity" }, issuerConditions: { validFrom: "2023-06-01T00:00:00.000Z", @@ -39,11 +39,11 @@ new GenericValueTest().runParametrized({ valueJSON: { "@type": "Statement", subject: { - address: "id1234" + address: "did:e:a-domain:dids:anidentity" }, predicate: "hasAttribute", object: { - address: "id1235", + address: "did:e:a-domain:dids:anidentity", attributes: [ { givenName: "Max", @@ -52,7 +52,7 @@ new GenericValueTest().runParametrized({ ] }, issuer: { - address: "id1236" + address: "did:e:a-domain:dids:anidentity" }, issuerConditions: { validFrom: "2023-06-01T00:00:00.000Z", @@ -68,11 +68,11 @@ new GenericValueTest().runParametrized({ valueVerboseJSON: { "@type": "Statement", subject: { - address: "id1234" + address: "did:e:a-domain:dids:anidentity" }, predicate: "hasAttribute", object: { - address: "id1235", + address: "did:e:a-domain:dids:anidentity", attributes: [ { givenName: "Max", @@ -81,7 +81,7 @@ new GenericValueTest().runParametrized({ ] }, issuer: { - address: "id1236" + address: "did:e:a-domain:dids:anidentity" }, issuerConditions: { validFrom: "2023-06-01T00:00:00.000Z", @@ -97,10 +97,10 @@ new GenericValueTest().runParametrized({ } }, valueInterface: { - subject: StatementSubject.fromAny({ address: "id1234" }), + subject: StatementSubject.fromAny({ address: "did:e:a-domain:dids:anidentity" }), predicate: StatementPredicate.fromAny("hasAttribute"), object: StatementObject.fromAny({ - address: "id1235", + address: "did:e:a-domain:dids:anidentity", attributes: [ { givenName: "Max", @@ -108,7 +108,7 @@ new GenericValueTest().runParametrized({ } ] }), - issuer: DigitalIdentityDescriptor.fromAny({ address: "id1236" }), + issuer: DigitalIdentityDescriptor.fromAny({ address: "did:e:a-domain:dids:anidentity" }), issuerConditions: StatementIssuerConditions.fromAny({ validTo: "2023-06-01T00:00:00.000Z", validFrom: "2023-06-01T00:00:00.000Z", @@ -130,16 +130,16 @@ new GenericValueTest().runParametrized({ expectedJSON: { "@type": "Statement", subject: { - address: "id1234" + address: "did:e:a-domain:dids:anidentity" }, predicate: { value: "z-isTeacher" }, object: { - address: "id1235" + address: "did:e:a-domain:dids:anidentity" }, issuer: { - address: "id1236" + address: "did:e:a-domain:dids:anidentity" }, issuerConditions: { validFrom: "2023-06-01T00:00:00.000Z", @@ -155,14 +155,14 @@ new GenericValueTest().runParametrized({ valueJSON: { "@type": "Statement", subject: { - address: "id1234" + address: "did:e:a-domain:dids:anidentity" }, predicate: "z-isTeacher", object: { - address: "id1235" + address: "did:e:a-domain:dids:anidentity" }, issuer: { - address: "id1236" + address: "did:e:a-domain:dids:anidentity" }, issuerConditions: { validFrom: "2023-06-01T00:00:00.000Z", @@ -178,14 +178,14 @@ new GenericValueTest().runParametrized({ valueVerboseJSON: { "@type": "Statement", subject: { - address: "id1234" + address: "did:e:a-domain:dids:anidentity" }, predicate: "z-isTeacher", object: { - address: "id1235" + address: "did:e:a-domain:dids:anidentity" }, issuer: { - address: "id1236" + address: "did:e:a-domain:dids:anidentity" }, issuerConditions: { validFrom: "2023-06-01T00:00:00.000Z", @@ -201,10 +201,10 @@ new GenericValueTest().runParametrized({ } }, valueInterface: { - subject: StatementSubject.fromAny({ address: "id1234" }), + subject: StatementSubject.fromAny({ address: "did:e:a-domain:dids:anidentity" }), predicate: StatementPredicate.fromAny("z-isTeacher"), - object: StatementObject.fromAny({ address: "id1235" }), - issuer: DigitalIdentityDescriptor.fromAny({ address: "id1236" }), + object: StatementObject.fromAny({ address: "did:e:a-domain:dids:anidentity" }), + issuer: DigitalIdentityDescriptor.fromAny({ address: "did:e:a-domain:dids:anidentity" }), issuerConditions: StatementIssuerConditions.fromAny({ validTo: "2023-06-01T00:00:00.000Z", validFrom: "2023-06-01T00:00:00.000Z", @@ -224,22 +224,22 @@ describe("Statement negative Test", function () { describe("Statement set to 'hasPredicate'", function () { test.each([ { - address: "id1235", + address: "did:e:a-domain:dids:anidentity", attributes: [] }, { - address: "id1235" + address: "did:e:a-domain:dids:anidentity" } ])("should throw ValidationError for invalid input", function (value: any) { const invalidStatement = { "@type": "Statement", subject: { - address: "id1234" + address: "did:e:a-domain:dids:anidentity" }, predicate: "hasAttribute", object: value, issuer: { - address: "id1236" + address: "did:e:a-domain:dids:anidentity" }, issuerConditions: { validFrom: "2023-06-01T00:00:00.000Z", diff --git a/packages/content/test/messages/Mail.test.ts b/packages/content/test/messages/Mail.test.ts index cdd6d5364..14717a295 100644 --- a/packages/content/test/messages/Mail.test.ts +++ b/packages/content/test/messages/Mail.test.ts @@ -5,7 +5,7 @@ describe("Mail", function () { test("should create a Mail from JSON", function () { const mail = Serializable.fromUnknown({ "@type": "Mail", - to: ["id1PNvUP4jHD74qo6usnWNoaFGFf33MXZi6c"], + to: ["did:e:a-domain:dids:anidentity"], cc: [], subject: "A Subject", body: "A Body" @@ -17,7 +17,7 @@ describe("Mail", function () { test("should create a Mail from JSON if cc is missing", function () { const mail = Serializable.fromUnknown({ "@type": "Mail", - to: ["id1PNvUP4jHD74qo6usnWNoaFGFf33MXZi6c"], + to: ["did:e:a-domain:dids:anidentity"], subject: "A Subject", body: "A Body" }); diff --git a/packages/runtime/src/types/transport/RelationshipChangeDTO.ts b/packages/runtime/src/types/transport/RelationshipChangeDTO.ts deleted file mode 100644 index 80b126618..000000000 --- a/packages/runtime/src/types/transport/RelationshipChangeDTO.ts +++ /dev/null @@ -1,34 +0,0 @@ -export enum RelationshipChangeStatus { - Pending = "Pending", - Rejected = "Rejected", - Revoked = "Revoked", - Accepted = "Accepted" -} - -export enum RelationshipChangeType { - Creation = "Creation", - Termination = "Termination", - TerminationCancellation = "TerminationCancellation" -} - -export interface RelationshipChangeRequestDTO { - createdBy: string; - createdByDevice: string; - createdAt: string; - content?: any; -} - -export interface RelationshipChangeResponseDTO { - createdBy: string; - createdByDevice: string; - createdAt: string; - content?: any; -} - -export interface RelationshipChangeDTO { - id: string; - request: RelationshipChangeRequestDTO; - status: RelationshipChangeStatus; - type: RelationshipChangeType; - response?: RelationshipChangeResponseDTO; -} diff --git a/packages/runtime/src/types/transport/RelationshipDTO.ts b/packages/runtime/src/types/transport/RelationshipDTO.ts index 98642382d..42c3ba57e 100644 --- a/packages/runtime/src/types/transport/RelationshipDTO.ts +++ b/packages/runtime/src/types/transport/RelationshipDTO.ts @@ -1,5 +1,4 @@ import { IdentityDTO } from "./IdentityDTO"; -import { RelationshipChangeDTO } from "./RelationshipChangeDTO"; import { RelationshipTemplateDTO } from "./RelationshipTemplateDTO"; export enum RelationshipStatus { @@ -35,7 +34,6 @@ export interface RelationshipDTO { status: RelationshipStatus; peer: string; peerIdentity: IdentityDTO; - changes: RelationshipChangeDTO[]; creationContent: any; auditLog: RelationshipAuditLogDTO; } diff --git a/packages/runtime/src/types/transport/index.ts b/packages/runtime/src/types/transport/index.ts index 650f6ff6e..2235cf6e4 100644 --- a/packages/runtime/src/types/transport/index.ts +++ b/packages/runtime/src/types/transport/index.ts @@ -2,12 +2,11 @@ export * from "./ChallengeDTO"; export * from "./DeviceDTO"; export * from "./DeviceOnboardingInfoDTO"; export * from "./FileDTO"; -export * from "./IdentityDeletionProcessDTO"; export * from "./IdentityDTO"; +export * from "./IdentityDeletionProcessDTO"; export * from "./MessageDTO"; export * from "./MessageWithAttachmentsDTO"; export * from "./RecipientDTO"; -export * from "./RelationshipChangeDTO"; export * from "./RelationshipDTO"; export * from "./RelationshipTemplateDTO"; export * from "./TokenDTO"; diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index 3fc275314..96035627b 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -17300,7 +17300,7 @@ export const GetSharedVersionsOfAttributeRequest: any = { }, "AddressString": { "type": "string", - "pattern": "id1[A-Za-z0-9]{32,33}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" } } } @@ -21107,29 +21107,6 @@ export const UploadOwnFileValidatableRequest: any = { } } -export const CheckIdentityRequest: any = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/CheckIdentityRequest", - "definitions": { - "CheckIdentityRequest": { - "type": "object", - "properties": { - "address": { - "$ref": "#/definitions/AddressString" - } - }, - "required": [ - "address" - ], - "additionalProperties": false - }, - "AddressString": { - "type": "string", - "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" - } - } -} - export const GetAttachmentMetadataRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/GetAttachmentMetadataRequest", @@ -21434,21 +21411,49 @@ export const SendMessageRequest: any = { } } -export const CreateRelationshipRequest: any = { +export const CreateOwnRelationshipTemplateRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/CreateRelationshipRequest", + "$ref": "#/definitions/CreateOwnRelationshipTemplateRequest", "definitions": { - "CreateRelationshipRequest": { + "CreateOwnRelationshipTemplateRequest": { + "type": "object", + "properties": { + "expiresAt": { + "$ref": "#/definitions/ISO8601DateTimeString" + }, + "content": {}, + "maxNumberOfAllocations": { + "type": "number", + "minimum": 1 + } + }, + "required": [ + "expiresAt", + "content" + ], + "additionalProperties": false + }, + "ISO8601DateTimeString": { + "type": "string", + "errorMessage": "must match ISO8601 datetime format", + "pattern": "^([+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24:?00)([.,]\\d+(?!:))?)?(\\17[0-5]\\d([.,]\\d+)?)?([zZ]|([+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$" + } + } +} + +export const CreateQRCodeForOwnTemplateRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/CreateQRCodeForOwnTemplateRequest", + "definitions": { + "CreateQRCodeForOwnTemplateRequest": { "type": "object", "properties": { "templateId": { "$ref": "#/definitions/RelationshipTemplateIdString" - }, - "creationContent": {} + } }, "required": [ - "templateId", - "creationContent" + "templateId" ], "additionalProperties": false }, @@ -21459,99 +21464,114 @@ export const CreateRelationshipRequest: any = { } } -export const GetAttributesForRelationshipRequest: any = { +export const CreateTokenForOwnTemplateRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/GetAttributesForRelationshipRequest", + "$ref": "#/definitions/CreateTokenForOwnTemplateRequest", "definitions": { - "GetAttributesForRelationshipRequest": { + "CreateTokenForOwnTemplateRequest": { "type": "object", "properties": { - "id": { - "$ref": "#/definitions/RelationshipIdString" + "templateId": { + "$ref": "#/definitions/RelationshipTemplateIdString" }, - "hideTechnical": { - "type": "boolean" + "expiresAt": { + "$ref": "#/definitions/ISO8601DateTimeString" }, - "onlyLatestVersions": { - "type": "boolean", - "description": "default: true" + "ephemeral": { + "type": "boolean" } }, "required": [ - "id" + "templateId" ], "additionalProperties": false }, - "RelationshipIdString": { + "RelationshipTemplateIdString": { "type": "string", - "pattern": "REL[A-Za-z0-9]{17}" + "pattern": "RLT[A-Za-z0-9]{17}" + }, + "ISO8601DateTimeString": { + "type": "string", + "errorMessage": "must match ISO8601 datetime format", + "pattern": "^([+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24:?00)([.,]\\d+(?!:))?)?(\\17[0-5]\\d([.,]\\d+)?)?([zZ]|([+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$" } } } -export const GetRelationshipRequest: any = { +export const CreateTokenQRCodeForOwnTemplateRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/GetRelationshipRequest", + "$ref": "#/definitions/CreateTokenQRCodeForOwnTemplateRequest", "definitions": { - "GetRelationshipRequest": { + "CreateTokenQRCodeForOwnTemplateRequest": { "type": "object", "properties": { - "id": { - "$ref": "#/definitions/RelationshipIdString" + "templateId": { + "$ref": "#/definitions/RelationshipTemplateIdString" + }, + "expiresAt": { + "$ref": "#/definitions/ISO8601DateTimeString" } }, "required": [ - "id" + "templateId" ], "additionalProperties": false }, - "RelationshipIdString": { + "RelationshipTemplateIdString": { "type": "string", - "pattern": "REL[A-Za-z0-9]{17}" + "pattern": "RLT[A-Za-z0-9]{17}" + }, + "ISO8601DateTimeString": { + "type": "string", + "errorMessage": "must match ISO8601 datetime format", + "pattern": "^([+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24:?00)([.,]\\d+(?!:))?)?(\\17[0-5]\\d([.,]\\d+)?)?([zZ]|([+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$" } } } -export const GetRelationshipByAddressRequest: any = { +export const GetRelationshipTemplateRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/GetRelationshipByAddressRequest", + "$ref": "#/definitions/GetRelationshipTemplateRequest", "definitions": { - "GetRelationshipByAddressRequest": { + "GetRelationshipTemplateRequest": { "type": "object", "properties": { - "address": { - "$ref": "#/definitions/AddressString" + "id": { + "$ref": "#/definitions/RelationshipTemplateIdString" } }, "required": [ - "address" + "id" ], "additionalProperties": false }, - "AddressString": { + "RelationshipTemplateIdString": { "type": "string", - "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" + "pattern": "RLT[A-Za-z0-9]{17}" } } } -export const GetRelationshipsRequest: any = { +export const GetRelationshipTemplatesRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/GetRelationshipsRequest", + "$ref": "#/definitions/GetRelationshipTemplatesRequest", "definitions": { - "GetRelationshipsRequest": { + "GetRelationshipTemplatesRequest": { "type": "object", "properties": { "query": { - "$ref": "#/definitions/GetRelationshipsQuery" + "$ref": "#/definitions/GetRelationshipTemplatesQuery" + }, + "ownerRestriction": { + "$ref": "#/definitions/OwnerRestriction" } }, "additionalProperties": false }, - "GetRelationshipsQuery": { + "GetRelationshipTemplatesQuery": { "type": "object", "properties": { - "peer": { + "isOwn": { "anyOf": [ { "type": "string" @@ -21564,7 +21584,7 @@ export const GetRelationshipsRequest: any = { } ] }, - "status": { + "createdAt": { "anyOf": [ { "type": "string" @@ -21577,7 +21597,46 @@ export const GetRelationshipsRequest: any = { } ] }, - "template.id": { + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "createdBy": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "createdByDevice": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "maxNumberOfAllocations": { "anyOf": [ { "type": "string" @@ -21592,145 +21651,184 @@ export const GetRelationshipsRequest: any = { } }, "additionalProperties": false + }, + "OwnerRestriction": { + "type": "string", + "enum": [ + "o", + "p" + ] } } } -export const AcceptRelationshipRequest: any = { +export const LoadPeerRelationshipTemplateViaSecretRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/AcceptRelationshipRequest", + "$ref": "#/definitions/LoadPeerRelationshipTemplateViaSecretRequest", "definitions": { - "AcceptRelationshipRequest": { + "LoadPeerRelationshipTemplateViaSecretRequest": { "type": "object", "properties": { - "relationshipId": { - "$ref": "#/definitions/RelationshipIdString" + "id": { + "$ref": "#/definitions/RelationshipTemplateIdString" }, + "secretKey": { + "type": "string", + "minLength": 10 + } }, "required": [ - "relationshipId" + "id", + "secretKey" ], "additionalProperties": false }, - "RelationshipIdString": { + "RelationshipTemplateIdString": { "type": "string", - "pattern": "REL[A-Za-z0-9]{17}" - }, + "pattern": "RLT[A-Za-z0-9]{17}" + } } } -export const RejectRelationshipRequest: any = { +export const LoadPeerRelationshipTemplateViaReferenceRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/RejectRelationshipRequest", + "$ref": "#/definitions/LoadPeerRelationshipTemplateViaReferenceRequest", "definitions": { - "RejectRelationshipRequest": { + "LoadPeerRelationshipTemplateViaReferenceRequest": { "type": "object", "properties": { - "relationshipId": { - "$ref": "#/definitions/RelationshipIdString" + "reference": { + "anyOf": [ + { + "$ref": "#/definitions/TokenReferenceString" + }, + { + "$ref": "#/definitions/RelationshipTemplateReferenceString" + } + ] } }, "required": [ - "relationshipId" + "reference" ], - "additionalProperties": false + "additionalProperties": false, + "errorMessage": "token / relationship template reference invalid" }, - "RelationshipIdString": { + "TokenReferenceString": { "type": "string", - "pattern": "REL[A-Za-z0-9]{17}" + "pattern": "VE9L.{84}" + }, + "RelationshipTemplateReferenceString": { + "type": "string", + "pattern": "UkxU.{84}" } } } -export const RevokeRelationshipRequest: any = { +export const LoadPeerRelationshipTemplateRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/RevokeRelationshipRequest", + "$ref": "#/definitions/LoadPeerRelationshipTemplateRequest", "definitions": { - "RevokeRelationshipRequest": { + "LoadPeerRelationshipTemplateRequest": { + "anyOf": [ + { + "$ref": "#/definitions/LoadPeerRelationshipTemplateViaSecretRequest" + }, + { + "$ref": "#/definitions/LoadPeerRelationshipTemplateViaReferenceRequest" + } + ] + }, + "LoadPeerRelationshipTemplateViaSecretRequest": { "type": "object", "properties": { - "relationshipId": { - "$ref": "#/definitions/RelationshipIdString" + "id": { + "$ref": "#/definitions/RelationshipTemplateIdString" + }, + "secretKey": { + "type": "string", + "minLength": 10 } }, "required": [ - "relationshipId" + "id", + "secretKey" ], "additionalProperties": false }, - "RelationshipIdString": { + "RelationshipTemplateIdString": { "type": "string", - "pattern": "REL[A-Za-z0-9]{17}" - } - } -} - -export const TerminateRelationshipRequest: any = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/TerminateRelationshipRequest", - "definitions": { - "TerminateRelationshipRequest": { + "pattern": "RLT[A-Za-z0-9]{17}" + }, + "LoadPeerRelationshipTemplateViaReferenceRequest": { "type": "object", "properties": { - "relationshipId": { - "$ref": "#/definitions/RelationshipIdString" - }, + "reference": { + "anyOf": [ + { + "$ref": "#/definitions/TokenReferenceString" + }, + { + "$ref": "#/definitions/RelationshipTemplateReferenceString" + } + ] + } }, "required": [ - "relationshipId" + "reference" ], - "additionalProperties": false + "additionalProperties": false, + "errorMessage": "token / relationship template reference invalid" }, - "RelationshipIdString": { + "TokenReferenceString": { "type": "string", - "pattern": "REL[A-Za-z0-9]{17}" + "pattern": "VE9L.{84}" }, + "RelationshipTemplateReferenceString": { + "type": "string", + "pattern": "UkxU.{84}" + } } } -export const CreateOwnRelationshipTemplateRequest: any = { +export const AcceptRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/CreateOwnRelationshipTemplateRequest", + "$ref": "#/definitions/AcceptRelationshipRequest", "definitions": { - "CreateOwnRelationshipTemplateRequest": { + "AcceptRelationshipRequest": { "type": "object", "properties": { - "expiresAt": { - "$ref": "#/definitions/ISO8601DateTimeString" - }, - "content": {}, - "maxNumberOfAllocations": { - "type": "number", - "minimum": 1 + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" } }, "required": [ - "expiresAt", - "content" + "relationshipId" ], "additionalProperties": false }, - "ISO8601DateTimeString": { + "RelationshipIdString": { "type": "string", - "errorMessage": "must match ISO8601 datetime format", - "pattern": "^([+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24:?00)([.,]\\d+(?!:))?)?(\\17[0-5]\\d([.,]\\d+)?)?([zZ]|([+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$" + "pattern": "REL[A-Za-z0-9]{17}" } } } -export const CreateQRCodeForOwnTemplateRequest: any = { +export const CreateRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/CreateQRCodeForOwnTemplateRequest", + "$ref": "#/definitions/CreateRelationshipRequest", "definitions": { - "CreateQRCodeForOwnTemplateRequest": { + "CreateRelationshipRequest": { "type": "object", "properties": { "templateId": { "$ref": "#/definitions/RelationshipTemplateIdString" - } + }, + "creationContent": {} }, "required": [ - "templateId" + "templateId", + "creationContent" ], "additionalProperties": false }, @@ -21741,153 +21839,99 @@ export const CreateQRCodeForOwnTemplateRequest: any = { } } -export const CreateTokenForOwnTemplateRequest: any = { +export const GetAttributesForRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/CreateTokenForOwnTemplateRequest", + "$ref": "#/definitions/GetAttributesForRelationshipRequest", "definitions": { - "CreateTokenForOwnTemplateRequest": { + "GetAttributesForRelationshipRequest": { "type": "object", "properties": { - "templateId": { - "$ref": "#/definitions/RelationshipTemplateIdString" - }, - "expiresAt": { - "$ref": "#/definitions/ISO8601DateTimeString" + "id": { + "$ref": "#/definitions/RelationshipIdString" }, - "ephemeral": { + "hideTechnical": { "type": "boolean" + }, + "onlyLatestVersions": { + "type": "boolean", + "description": "default: true" } }, "required": [ - "templateId" + "id" ], "additionalProperties": false }, - "RelationshipTemplateIdString": { - "type": "string", - "pattern": "RLT[A-Za-z0-9]{17}" - }, - "ISO8601DateTimeString": { + "RelationshipIdString": { "type": "string", - "errorMessage": "must match ISO8601 datetime format", - "pattern": "^([+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24:?00)([.,]\\d+(?!:))?)?(\\17[0-5]\\d([.,]\\d+)?)?([zZ]|([+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$" + "pattern": "REL[A-Za-z0-9]{17}" } } } -export const CreateTokenQRCodeForOwnTemplateRequest: any = { +export const GetRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/CreateTokenQRCodeForOwnTemplateRequest", + "$ref": "#/definitions/GetRelationshipRequest", "definitions": { - "CreateTokenQRCodeForOwnTemplateRequest": { + "GetRelationshipRequest": { "type": "object", "properties": { - "templateId": { - "$ref": "#/definitions/RelationshipTemplateIdString" - }, - "expiresAt": { - "$ref": "#/definitions/ISO8601DateTimeString" + "id": { + "$ref": "#/definitions/RelationshipIdString" } }, "required": [ - "templateId" + "id" ], "additionalProperties": false }, - "RelationshipTemplateIdString": { - "type": "string", - "pattern": "RLT[A-Za-z0-9]{17}" - }, - "ISO8601DateTimeString": { + "RelationshipIdString": { "type": "string", - "errorMessage": "must match ISO8601 datetime format", - "pattern": "^([+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24:?00)([.,]\\d+(?!:))?)?(\\17[0-5]\\d([.,]\\d+)?)?([zZ]|([+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$" + "pattern": "REL[A-Za-z0-9]{17}" } } } -export const GetRelationshipTemplateRequest: any = { +export const GetRelationshipByAddressRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/GetRelationshipTemplateRequest", + "$ref": "#/definitions/GetRelationshipByAddressRequest", "definitions": { - "GetRelationshipTemplateRequest": { + "GetRelationshipByAddressRequest": { "type": "object", "properties": { - "id": { - "$ref": "#/definitions/RelationshipTemplateIdString" + "address": { + "$ref": "#/definitions/AddressString" } }, "required": [ - "id" + "address" ], "additionalProperties": false }, - "RelationshipTemplateIdString": { + "AddressString": { "type": "string", - "pattern": "RLT[A-Za-z0-9]{17}" + "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" } } } -export const GetRelationshipTemplatesRequest: any = { +export const GetRelationshipsRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/GetRelationshipTemplatesRequest", + "$ref": "#/definitions/GetRelationshipsRequest", "definitions": { - "GetRelationshipTemplatesRequest": { + "GetRelationshipsRequest": { "type": "object", "properties": { "query": { - "$ref": "#/definitions/GetRelationshipTemplatesQuery" - }, - "ownerRestriction": { - "$ref": "#/definitions/OwnerRestriction" + "$ref": "#/definitions/GetRelationshipsQuery" } }, "additionalProperties": false }, - "GetRelationshipTemplatesQuery": { + "GetRelationshipsQuery": { "type": "object", "properties": { - "isOwn": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "createdAt": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "expiresAt": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "createdBy": { + "peer": { "anyOf": [ { "type": "string" @@ -21900,7 +21944,7 @@ export const GetRelationshipTemplatesRequest: any = { } ] }, - "createdByDevice": { + "status": { "anyOf": [ { "type": "string" @@ -21913,7 +21957,7 @@ export const GetRelationshipTemplatesRequest: any = { } ] }, - "maxNumberOfAllocations": { + "template.id": { "anyOf": [ { "type": "string" @@ -21928,142 +21972,75 @@ export const GetRelationshipTemplatesRequest: any = { } }, "additionalProperties": false - }, - "OwnerRestriction": { - "type": "string", - "enum": [ - "o", - "p" - ] } } } -export const LoadPeerRelationshipTemplateViaSecretRequest: any = { +export const RejectRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/LoadPeerRelationshipTemplateViaSecretRequest", + "$ref": "#/definitions/RejectRelationshipRequest", "definitions": { - "LoadPeerRelationshipTemplateViaSecretRequest": { + "RejectRelationshipRequest": { "type": "object", "properties": { - "id": { - "$ref": "#/definitions/RelationshipTemplateIdString" - }, - "secretKey": { - "type": "string", - "minLength": 10 + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" } }, "required": [ - "id", - "secretKey" + "relationshipId" ], "additionalProperties": false }, - "RelationshipTemplateIdString": { + "RelationshipIdString": { "type": "string", - "pattern": "RLT[A-Za-z0-9]{17}" + "pattern": "REL[A-Za-z0-9]{17}" } } } -export const LoadPeerRelationshipTemplateViaReferenceRequest: any = { +export const RevokeRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/LoadPeerRelationshipTemplateViaReferenceRequest", + "$ref": "#/definitions/RevokeRelationshipRequest", "definitions": { - "LoadPeerRelationshipTemplateViaReferenceRequest": { + "RevokeRelationshipRequest": { "type": "object", "properties": { - "reference": { - "anyOf": [ - { - "$ref": "#/definitions/TokenReferenceString" - }, - { - "$ref": "#/definitions/RelationshipTemplateReferenceString" - } - ] + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" } }, "required": [ - "reference" + "relationshipId" ], - "additionalProperties": false, - "errorMessage": "token / relationship template reference invalid" - }, - "TokenReferenceString": { - "type": "string", - "pattern": "VE9L.{84}" + "additionalProperties": false }, - "RelationshipTemplateReferenceString": { + "RelationshipIdString": { "type": "string", - "pattern": "UkxU.{84}" + "pattern": "REL[A-Za-z0-9]{17}" } } } -export const LoadPeerRelationshipTemplateRequest: any = { +export const TerminateRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/LoadPeerRelationshipTemplateRequest", + "$ref": "#/definitions/TerminateRelationshipRequest", "definitions": { - "LoadPeerRelationshipTemplateRequest": { - "anyOf": [ - { - "$ref": "#/definitions/LoadPeerRelationshipTemplateViaSecretRequest" - }, - { - "$ref": "#/definitions/LoadPeerRelationshipTemplateViaReferenceRequest" - } - ] - }, - "LoadPeerRelationshipTemplateViaSecretRequest": { + "TerminateRelationshipRequest": { "type": "object", "properties": { - "id": { - "$ref": "#/definitions/RelationshipTemplateIdString" - }, - "secretKey": { - "type": "string", - "minLength": 10 + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" } }, "required": [ - "id", - "secretKey" + "relationshipId" ], "additionalProperties": false }, - "RelationshipTemplateIdString": { - "type": "string", - "pattern": "RLT[A-Za-z0-9]{17}" - }, - "LoadPeerRelationshipTemplateViaReferenceRequest": { - "type": "object", - "properties": { - "reference": { - "anyOf": [ - { - "$ref": "#/definitions/TokenReferenceString" - }, - { - "$ref": "#/definitions/RelationshipTemplateReferenceString" - } - ] - } - }, - "required": [ - "reference" - ], - "additionalProperties": false, - "errorMessage": "token / relationship template reference invalid" - }, - "TokenReferenceString": { - "type": "string", - "pattern": "VE9L.{84}" - }, - "RelationshipTemplateReferenceString": { + "RelationshipIdString": { "type": "string", - "pattern": "UkxU.{84}" + "pattern": "REL[A-Za-z0-9]{17}" } } } diff --git a/packages/runtime/src/useCases/transport/relationships/RelationshipMapper.ts b/packages/runtime/src/useCases/transport/relationships/RelationshipMapper.ts index ea3de362f..2242ff3fc 100644 --- a/packages/runtime/src/useCases/transport/relationships/RelationshipMapper.ts +++ b/packages/runtime/src/useCases/transport/relationships/RelationshipMapper.ts @@ -1,5 +1,5 @@ import { Relationship, RelationshipAuditLogEntry } from "@nmshd/transport"; -import { RelationshipAuditLogEntryDTO, RelationshipAuditLogEntryReason, RelationshipChangeStatus, RelationshipChangeType, RelationshipDTO } from "../../../types"; +import { RelationshipAuditLogEntryDTO, RelationshipDTO } from "../../../types"; import { RuntimeErrors } from "../../common"; import { RelationshipTemplateMapper } from "../relationshipTemplates/RelationshipTemplateMapper"; @@ -19,36 +19,10 @@ export class RelationshipMapper { publicKey: relationship.peer.publicKey.toBase64(false) }, auditLog: relationship.cache.auditLog.map((entry) => this.toAuditLogEntryDTO(entry)), - creationContent: relationship.cache.creationContent?.toJSON(), - changes: [ - { - id: "RCH00000000000000001", - request: { - createdAt: relationship.cache.auditLog[0].createdAt.toString(), - createdBy: relationship.cache.auditLog[0].createdBy.toString(), - createdByDevice: relationship.cache.auditLog[0].createdByDevice.toString(), - content: { ...relationship.cache.creationContent?.toJSON(), "@type": "RelationshipCreationChangeRequestContent" } - }, - status: this.getStatus(relationship.cache.auditLog), - type: RelationshipChangeType.Creation - } - ] + creationContent: relationship.cache.creationContent?.toJSON() }; } - private static getStatus(auditLog: RelationshipAuditLogEntry[]): RelationshipChangeStatus { - switch (auditLog[1]?.reason) { - case RelationshipAuditLogEntryReason.AcceptanceOfCreation: - return RelationshipChangeStatus.Accepted; - case RelationshipAuditLogEntryReason.RejectionOfCreation: - return RelationshipChangeStatus.Rejected; - case RelationshipAuditLogEntryReason.RevocationOfCreation: - return RelationshipChangeStatus.Revoked; - default: - return RelationshipChangeStatus.Pending; - } - } - private static toAuditLogEntryDTO(entry: RelationshipAuditLogEntry): RelationshipAuditLogEntryDTO { return { createdAt: entry.createdAt.toString(), diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index 223843b68..1fc2edb4d 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -1467,7 +1467,7 @@ describe("Get (shared) versions of attribute", () => { test("should return an empty list calling getSharedVersionsOfAttribute with a nonexistent peer", async () => { const result = await services1.consumption.attributes.getSharedVersionsOfAttribute({ attributeId: sRAVersion2.id, - peers: ["id1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] + peers: ["did:e:a-domain:dids:0000000000000000000000"] }); expect(result.isSuccess).toBe(true); const returnedVersions = result.value; diff --git a/packages/runtime/test/lib/testUtilsWithInactiveModules.ts b/packages/runtime/test/lib/testUtilsWithInactiveModules.ts index ba2e547fc..a0a009695 100644 --- a/packages/runtime/test/lib/testUtilsWithInactiveModules.ts +++ b/packages/runtime/test/lib/testUtilsWithInactiveModules.ts @@ -119,18 +119,17 @@ export async function exchangeTemplateAndReceiverSendsResponse( }) ).value; - let relationship; - if (actionLowerCase === "accept") { - const creationContent = RelationshipCreationContent.from({ response: decidedRequest.response!.content as unknown as IResponse }); - const result = await rRuntimeServices.transport.relationships.createRelationship({ creationContent, templateId }); + if (actionLowerCase !== "accept") return { request, relationship: undefined }; - expect(result).toBeSuccessful(); + const creationContent = RelationshipCreationContent.from({ response: decidedRequest.response!.content as unknown as IResponse }); + const result = await rRuntimeServices.transport.relationships.createRelationship({ creationContent, templateId }); - relationship = result.value; - const rRelationshipChange = result.value.changes[0]; + expect(result).toBeSuccessful(); + + const relationship = result.value; + const receivedCreationContent = relationship.creationContent; + + expect(receivedCreationContent["@type"]).toBe("RelationshipCreationContent"); - expect(rRelationshipChange.request.content["@type"]).toBe("RelationshipCreationChangeRequestContent"); - expect(relationship.creationContent["@type"]).toBe("RelationshipCreationContent"); - } return { request, relationship }; } diff --git a/packages/runtime/test/modules/RequestModule.test.ts b/packages/runtime/test/modules/RequestModule.test.ts index 8ad5f40ab..84cc999c9 100644 --- a/packages/runtime/test/modules/RequestModule.test.ts +++ b/packages/runtime/test/modules/RequestModule.test.ts @@ -2,7 +2,6 @@ import { DecideRequestItemParametersJSON, LocalRequestStatus } from "@nmshd/cons import { GivenName, IdentityAttribute, - RelationshipCreationChangeRequestContentJSON, RelationshipCreationContentJSON, RelationshipTemplateContentJSON, ResponseItemJSON, @@ -27,18 +26,18 @@ import { TransportServices } from "../../src"; import { + MockEventBus, + RuntimeServiceProvider, + TestRuntimeServices, ensureActiveRelationship, exchangeAndAcceptRequestByMessage, exchangeMessageWithRequest, exchangeTemplate, - MockEventBus, - RuntimeServiceProvider, sendMessage, sendMessageWithRequest, - syncUntilHasMessages, syncUntilHasMessageWithResponse, - syncUntilHasRelationships, - TestRuntimeServices + syncUntilHasMessages, + syncUntilHasRelationships } from "../lib"; const runtimeServiceProvider = new RuntimeServiceProvider(); @@ -185,8 +184,8 @@ describe("RequestModule", () => { const creationContent = relationship.creationContent as RelationshipCreationContentJSON; expect(creationContent["@type"]).toBe("RelationshipCreationContent"); - const creationChangeRequestContent = relationship.changes[0].request.content as RelationshipCreationChangeRequestContentJSON; - expect(creationChangeRequestContent["@type"]).toBe("RelationshipCreationChangeRequestContent"); + const creationChangeRequestContent = relationship.creationContent as RelationshipCreationContentJSON; + expect(creationChangeRequestContent["@type"]).toBe("RelationshipCreationContent"); const response = creationContent.response; const responseItems = response.items; diff --git a/packages/runtime/test/transport/challenges.test.ts b/packages/runtime/test/transport/challenges.test.ts index 8f2eea941..c1a84f31b 100644 --- a/packages/runtime/test/transport/challenges.test.ts +++ b/packages/runtime/test/transport/challenges.test.ts @@ -138,7 +138,7 @@ describe("Validate Challenge", () => { }); test("should return an error when the signature is invalid", async () => { - const validChallenge = { createdBy: "id1...", createdByDevice: "DVC...", expiresAt: "2022", id: "CHL...", type: "Identity" }; + const validChallenge = { createdBy: "did:e:a-domain:dids:anidentity", createdByDevice: "DVC...", expiresAt: "2022", id: "CHL...", type: "Identity" }; const validationResult = await transportServices2.challenges.validateChallenge({ challengeString: JSON.stringify(validChallenge), signature: "invalid-signature" @@ -147,7 +147,7 @@ describe("Validate Challenge", () => { }); test("should return an error when the challenge is an invalid json string", async () => { - const validChallenge = { createdBy: "id1...", createdByDevice: "DVC...", expiresAt: "2022", id: "CHL...", type: "Identity" }; + const validChallenge = { createdBy: "did:e:a-domain:dids:anidentity", createdByDevice: "DVC...", expiresAt: "2022", id: "CHL...", type: "Identity" }; const validationResult = await transportServices2.challenges.validateChallenge({ challengeString: `${JSON.stringify(validChallenge)}a`, signature: randomValidSignature diff --git a/packages/runtime/test/transport/messages.test.ts b/packages/runtime/test/transport/messages.test.ts index 14a6dc6c4..12a99399b 100644 --- a/packages/runtime/test/transport/messages.test.ts +++ b/packages/runtime/test/transport/messages.test.ts @@ -2,15 +2,15 @@ import { ConsentRequestItemJSON } from "@nmshd/content"; import { CoreDate, CoreId } from "@nmshd/transport"; import { GetMessagesQuery, MessageReceivedEvent, MessageSentEvent, MessageWasReadAtChangedEvent } from "../../src"; import { + QueryParamConditions, + RuntimeServiceProvider, + TestRuntimeServices, ensureActiveRelationship, establishRelationship, exchangeMessage, exchangeMessageWithAttachment, - QueryParamConditions, - RuntimeServiceProvider, sendMessage, syncUntilHasMessage, - TestRuntimeServices, uploadFile } from "../lib"; @@ -298,7 +298,7 @@ describe("Message query", () => { .addStringSet("recipients.relationshipId") .addSingleCondition({ key: "participant", - value: [updatedMessage.createdBy, "id111111111111111111111111111111111"], + value: [updatedMessage.createdBy, "did:e:a-domain:dids:0000000000000000000000"], expectedResult: true }); From cd97a589f4d9331dcf4f7d8d45f4ac5701a74ae7 Mon Sep 17 00:00:00 2001 From: Britta Stallknecht <146106656+britsta@users.noreply.github.com> Date: Tue, 28 May 2024 10:03:40 +0200 Subject: [PATCH 05/40] Feature/add validation for requests (#32) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add invalidAttributeOwner error for canAccept of ReadAttributeRequestItemProcessor * refactor: avoid duplicate error message * add IdentityAttribute Value Type validation * feat: add RelationshipAttribute Value Type validation * feat: add RelationshipAttribute owner validation * feat: add "only unshared IdentityAttributes can be shared" validation * feat: add key and confidentiality validation for RelationshipAttributes * feat: add validation for time frames * feat: add "tags" validation for IdentityAttributeQuery * refactor: move validation functions to validateAnswerToQuery file * fix: IQLQuery test * feat: add tests for attributeCreationHints of IQLQuery * fix: IQLQuery test * feat: add ThirdPartyRelationshipAttributeQuery Attribute Value Type and key tests * feat: add owner validation for IdentityAttributes queried with an IQLQuery * feat: add owner validation for ThirdPartyRelationshipAttributeQuery * feat: add validTo and validFrom validation for ThirdPartyRelationshipAttributeQuery * feat: add validation for thirdParty property of ThirdPartyRelationshipAttributeQuery * fix: modify a test so that it fulfils the new validation criteria * feat: add validation for title and description of RelationshipAttributeQuery * feat: avoid re-sharing of RelationshipAttributes when using a RelationshipAttributeQuery * feat: only allow to specify the Sender itself or an empty string as the owner for the RelationshipAttributeQuery * feat: empty string not allowed as third party in query * feat: avoid specifying same third party multiple times * feat: remove validation for multiple third parties again * feat: remove validation for empty string as third party * feat: use enum ThirdPartyRelationshipAttributeQueryOwner * fix: executeThirdPartyRelationshipAttributeQuery and content tests * fix: runtime tests for ThirdPartyRelationshipAttributeQueryOwner * feat: improve `owner` query for execute(ThirdParty)RelationshipAttributeQuery * fix: usage of $or in executeThirdPartyRelationshipAttributeQuery * feat: add validation for RelationshipAttributes with sourceAttribute * refactor: use strings instead of regex for error messages in tests * refactor: standardised use of Sender and Recipient in the tests * refactor: use strings instead of regex for error messages in tests * fix: create empty string as Attribute owner with ProposeAttributeRequestItem * refactor: standardised use of Sender and Recipient in the tests for ProposeAttributeRequestItemProcessor * fix: order of errors for validation of ProposeAttributeRequestItem and missing tests * feat: add validation for mismatching RelationshipAttribute value type (Propose) * feat: prevent sharing of outdated IdentityAttribute * feat: prevent sharing of private ThirdPartyRelationshipAttributes * feat: use validateAnswerToQuery function in canAccept of ProposeAttributeRequestItemProcessor * feat: prevent sharing of outdated RepositoryAttributes and own shared IdentityAttributes (Propose) * feat: prevent re-sharing of RelationshipAttribute (Propose) * feat: allow empty owner and change it to Recipient (Read) * feat: add validation for matching of attribute and query (propose) * refactor: create file for testing the utility function * refactor: move tests for validateAttributeMatchesWithQuery to separate file * refactor: location of tests for utility function * feat: change validation for tags appropriately * feat: add validation for owner of `attribute` of CreateAttributeRequestItem * fix: RequestModule.test for onExistingRelationship * feat: add "only repository IdentityAttributes can be shared" validation and error-message-update * feat: adjust error message for copies of RepositoryAttributes * feat: changed "only repository IdentityAttributes can be shared" validation * refactor: "only repository IdentityAttributes can be shared" validation (shareAttribute) * feat: add ShareIdentityAttribute already shared validation * fix: change test scenario because of new validations * feat: ShareAttributeRequestItem a Successor (IdentityAttribute) is already shared. * refactor: remove invalidlyAnsweredQuery error code * feat: add ShareIdentityAttribute Predecessor is already shared validation * feat: add ShareRelationshipAttribute validation to avoid sharing copies * refactor: ShareIdentityAttribute change error message * refactor: change validation test of ShareAttributeRequest * refactor: redundant declarations for ShareAttributeRequestItemDVO tests * feat: add RelationshipAttribute tests for ShareAttributeRequestItemProcessor * feat: add RelationshipAttribute test for ShareAttributeRequestItem * refactor: variable usage in tests of AttributeRequestItemProcessor * refactor: remove unneccessary comments * refactor: Errormessage and code refactoring * refactor: incorporate comments * fix: error with prettier because of blank line * feat: include test for ThirdPartyRelationshipAttributeQuery * feat: add more verbose error messages * refactor: change Attribute Value Type to lower case * refactor: move test to more appropriate file * feat: add validation for IQLQuery in ProposeAttributeRequestItem * refactor: only generate needed processors * fix: error caused by merging * feat: incorporate comments * fix: prettier whitespace error * feat: incorporate review comments * refactor: remove duplicated tests with ProposeAttributeRequestItemProcessor * fix: prettier blank line error * refactor: make private helpers private where possible * fix: merge conflicts * fix: merge conflicts again * fix: prettier errors with blank line * fix: thirdPartyRelationshipAttributeQueryOwner error * fix: ThirdPartyRelationshipAttributeFlow test * refactor: omit validation for attributeCreationHints for now --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Ruth Di Giacomo Co-authored-by: Julian König --- .../consumption/src/consumption/CoreErrors.ts | 4 + .../attributes/AttributesController.ts | 31 +- .../CreateAttributeRequestItemProcessor.ts | 30 +- .../ProposeAttributeRequestItemProcessor.ts | 115 +- .../ReadAttributeRequestItemProcessor.ts | 166 ++- .../ShareAttributeRequestItemProcessor.ts | 90 +- .../validateAttributeMatchesWithQuery.ts | 196 +++ .../itemProcessors/utility/validateQuery.ts | 4 - .../AttributeListenersController.test.ts | 4 +- .../attributes/AttributesController.test.ts | 636 +++++---- ...reateAttributeRequestItemProcessor.test.ts | 40 +- ...oposeAttributeRequestItemProcessor.test.ts | 255 +++- .../ReadAttributeRequestItemProcessor.test.ts | 823 ++++++++++-- ...ributeListenerRequestItemProcessor.test.ts | 4 +- ...ShareAttributeRequestItemProcessor.test.ts | 358 ++++- .../validateAttributeMatchesWithQuery.test.ts | 1155 +++++++++++++++++ .../requests/testHelpers/TestObjectFactory.ts | 4 +- .../src/attributes/AttributeValueTypes.ts | 2 +- .../attributes/RelationshipAttributeQuery.ts | 4 +- .../ThirdPartyRelationshipAttributeQuery.ts | 17 +- .../ProposeAttributeRequestItem.ts | 52 +- .../content/test/attributes/BirthDate.test.ts | 4 +- .../attributes/RelationshipAttribute.test.ts | 6 +- ...irdPartyRelationshipAttributeQuery.test.ts | 8 +- .../items/ProposeAttributeRequestItem.test.ts | 158 +++ .../test/consumption/attributes.test.ts | 82 +- .../dataViews/RelationshipTemplateDVO.test.ts | 8 +- .../ShareAttributeRequestItemDVO.test.ts | 31 +- .../modules/AttributeListenerModule.test.ts | 10 +- .../test/modules/RequestModule.test.ts | 3 +- ...shipStatusChangedExternalEventProcessor.ts | 1 + 31 files changed, 3668 insertions(+), 633 deletions(-) create mode 100644 packages/consumption/src/modules/requests/itemProcessors/utility/validateAttributeMatchesWithQuery.ts create mode 100644 packages/consumption/test/modules/requests/itemProcessors/utility/validateAttributeMatchesWithQuery.test.ts create mode 100644 packages/content/test/requests/items/ProposeAttributeRequestItem.test.ts diff --git a/packages/consumption/src/consumption/CoreErrors.ts b/packages/consumption/src/consumption/CoreErrors.ts index 4d39f850f..4c929767c 100644 --- a/packages/consumption/src/consumption/CoreErrors.ts +++ b/packages/consumption/src/consumption/CoreErrors.ts @@ -276,6 +276,10 @@ class Requests { return new CoreError("error.consumption.requests.invalidRequestItem", message); } + public attributeQueryMismatch(message: string) { + return new CoreError("error.consumption.requests.attributeQueryMismatch", message); + } + public wrongRelationshipStatus(message: string) { return new CoreError("error.consumption.requests.wrongRelationshipStatus", message); } diff --git a/packages/consumption/src/modules/attributes/AttributesController.ts b/packages/consumption/src/modules/attributes/AttributesController.ts index 4408daa3a..2fce409d6 100644 --- a/packages/consumption/src/modules/attributes/AttributesController.ts +++ b/packages/consumption/src/modules/attributes/AttributesController.ts @@ -11,7 +11,8 @@ import { IThirdPartyRelationshipAttributeQuery, RelationshipAttributeJSON, RelationshipAttributeQuery, - ThirdPartyRelationshipAttributeQuery + ThirdPartyRelationshipAttributeQuery, + ThirdPartyRelationshipAttributeQueryOwner } from "@nmshd/content"; import * as iql from "@nmshd/iql"; import { CoreAddress, CoreDate, CoreId, ICoreDate, ICoreId, SynchronizedCollection, CoreErrors as TransportCoreErrors } from "@nmshd/transport"; @@ -177,6 +178,10 @@ export class AttributesController extends ConsumptionBaseController { const dbQuery = RelationshipAttributeQueryTranslator.translate(parsedQuery); dbQuery["content.confidentiality"] = { $ne: "private" }; + if (parsedQuery.owner.equals("")) { + dbQuery["content.owner"] = { $eq: this.identity.address.toString() }; + } + const attributes = await this.attributes.find(dbQuery); const attribute = attributes.length > 0 ? LocalAttribute.from(attributes[0]) : undefined; @@ -186,9 +191,31 @@ export class AttributesController extends ConsumptionBaseController { public async executeThirdPartyRelationshipAttributeQuery(query: IThirdPartyRelationshipAttributeQuery): Promise { const parsedQuery = ThirdPartyRelationshipAttributeQuery.from(query); - const dbQuery = ThirdPartyRelationshipAttributeQueryTranslator.translate(parsedQuery); + let dbQuery = ThirdPartyRelationshipAttributeQueryTranslator.translate(parsedQuery); dbQuery["content.confidentiality"] = { $ne: "private" }; + switch (parsedQuery.owner) { + case ThirdPartyRelationshipAttributeQueryOwner.Recipient: + dbQuery["content.owner"] = { $eq: this.identity.address.toString() }; + break; + case ThirdPartyRelationshipAttributeQueryOwner.ThirdParty: + dbQuery["content.owner"] = { $in: parsedQuery.thirdParty.map((aThirdParty) => aThirdParty.toString()) }; + break; + case ThirdPartyRelationshipAttributeQueryOwner.Empty: + const recipientOrThirdPartyIsOwnerQuery = { + $or: [ + { + ["content.owner"]: { $eq: this.identity.address.toString() } + }, + { + ["content.owner"]: { $in: parsedQuery.thirdParty.map((aThirdParty) => aThirdParty.toString()) } + } + ] + }; + dbQuery = { $and: [dbQuery, recipientOrThirdPartyIsOwnerQuery] }; + break; + } + const attributes = await this.attributes.find(dbQuery); return this.parseArray(attributes, LocalAttribute); diff --git a/packages/consumption/src/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.ts index 3aa8ee2a3..09a5096ad 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.ts @@ -18,26 +18,46 @@ export class CreateAttributeRequestItemProcessor extends GenericRequestItemProce const ownerIsEmptyString = requestItem.attribute.owner.toString() === ""; if (requestItem.attribute instanceof IdentityAttribute) { + if (recipientIsAttributeOwner || ownerIsEmptyString) { + return ValidationResult.success(); + } + if (senderIsAttributeOwner) { return ValidationResult.error( - CoreErrors.requests.invalidRequestItem("Cannot create own Attributes with a CreateAttributeRequestItem. Use a ShareAttributeRequestItem instead.") + CoreErrors.requests.invalidRequestItem("Cannot create own IdentityAttributes with a CreateAttributeRequestItem. Use a ShareAttributeRequestItem instead.") ); } - if (recipientIsAttributeOwner || ownerIsEmptyString || !recipient) return ValidationResult.success(); + if (typeof recipient !== "undefined") { + return ValidationResult.error( + CoreErrors.requests.invalidRequestItem( + "The owner of the provided IdentityAttribute for the `attribute` property can only be the Recipient's Address or an empty string. The latter will default to the Recipient's Address." + ) + ); + } return ValidationResult.error( CoreErrors.requests.invalidRequestItem( - "The owner of the given `attribute` can only be the recipient's address or an empty string. The latter will default to the recipient's address." + "The owner of the provided IdentityAttribute for the `attribute` property can only be an empty string. It will default to the Recipient's Address." ) ); } - if (recipientIsAttributeOwner || senderIsAttributeOwner || ownerIsEmptyString || !recipient) return ValidationResult.success(); + if (recipientIsAttributeOwner || senderIsAttributeOwner || ownerIsEmptyString) { + return ValidationResult.success(); + } + + if (typeof recipient !== "undefined") { + return ValidationResult.error( + CoreErrors.requests.invalidRequestItem( + "The owner of the provided RelationshipAttribute for the `attribute` property can only be the Sender's Address, the Recipient's Address or an empty string. The latter will default to the Recipient's Address." + ) + ); + } return ValidationResult.error( CoreErrors.requests.invalidRequestItem( - "The owner of the given 'attribute' can only be the sender's address, the recipient's address or an empty string. The latter will default to the recipient's address." + "The owner of the provided RelationshipAttribute for the `attribute` property can only be the Sender's Address or an empty string. The latter will default to the Recipient's Address." ) ); } diff --git a/packages/consumption/src/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.ts index ca1360d51..ade980907 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.ts @@ -2,6 +2,7 @@ import { AttributeAlreadySharedAcceptResponseItem, AttributeSuccessionAcceptResponseItem, IdentityAttribute, + IdentityAttributeQuery, ProposeAttributeAcceptResponseItem, ProposeAttributeRequestItem, RejectResponseItem, @@ -11,12 +12,14 @@ import { ResponseItemResult } from "@nmshd/content"; import { CoreAddress, CoreId, CoreErrors as TransportCoreErrors } from "@nmshd/transport"; +import { nameof } from "ts-simple-nameof"; import { CoreErrors } from "../../../../consumption/CoreErrors"; import { AttributeSuccessorParams, LocalAttributeShareInfo, PeerSharedAttributeSucceededEvent } from "../../../attributes"; import { LocalAttribute } from "../../../attributes/local/LocalAttribute"; import { ValidationResult } from "../../../common/ValidationResult"; import { GenericRequestItemProcessor } from "../GenericRequestItemProcessor"; import { LocalRequestInfo } from "../IRequestItemProcessor"; +import validateAttributeMatchesWithQuery from "../utility/validateAttributeMatchesWithQuery"; import validateQuery from "../utility/validateQuery"; import { AcceptProposeAttributeRequestItemParameters, AcceptProposeAttributeRequestItemParametersJSON } from "./AcceptProposeAttributeRequestItemParameters"; @@ -27,19 +30,29 @@ export class ProposeAttributeRequestItemProcessor extends GenericRequestItemProc return queryValidationResult; } - const attributeValidationResult = this.validateAttribute(requestItem.attribute); + const attributeValidationResult = ProposeAttributeRequestItemProcessor.validateAttribute(requestItem.attribute); if (attributeValidationResult.isError()) { return attributeValidationResult; } + const proposedAttributeMatchesWithQueryValidationResult = validateAttributeMatchesWithQuery( + requestItem.query, + requestItem.attribute, + CoreAddress.from(""), + this.currentIdentityAddress + ); + if (proposedAttributeMatchesWithQueryValidationResult.isError()) { + return proposedAttributeMatchesWithQueryValidationResult; + } + return ValidationResult.success(); } - private validateAttribute(attribute: IdentityAttribute | RelationshipAttribute) { + private static validateAttribute(attribute: IdentityAttribute | RelationshipAttribute) { if (attribute.owner.toString() !== "") { return ValidationResult.error( CoreErrors.requests.invalidRequestItem( - "The owner of the given `attribute` can only be an empty string. This is because you can only propose Attributes where the recipient of the Request is the owner anyway. And in order to avoid mistakes, the owner will be automatically filled for you." + "The owner of the given `attribute` can only be an empty string. This is because you can only propose Attributes where the Recipient of the Request is the owner anyway. And in order to avoid mistakes, the owner will be automatically filled for you." ) ); } @@ -56,7 +69,7 @@ export class ProposeAttributeRequestItemProcessor extends GenericRequestItemProc if (requestItem.query instanceof RelationshipAttributeQuery && requestItem.query.owner.toString() !== "") { return ValidationResult.error( CoreErrors.requests.invalidRequestItem( - "The owner of the given `query` can only be an empty string. This is because you can only propose Attributes where the recipient of the Request is the owner anyway. And in order to avoid mistakes, the owner will be automatically filled for you." + "The owner of the given `query` can only be an empty string. This is because you can only propose Attributes where the Recipient of the Request is the owner anyway. And in order to avoid mistakes, the owner will be automatically filled for you." ) ); } @@ -65,44 +78,84 @@ export class ProposeAttributeRequestItemProcessor extends GenericRequestItemProc } public override async canAccept( - _requestItem: ProposeAttributeRequestItem, + requestItem: ProposeAttributeRequestItem, params: AcceptProposeAttributeRequestItemParametersJSON, requestInfo: LocalRequestInfo ): Promise { const parsedParams = AcceptProposeAttributeRequestItemParameters.from(params); - - let attribute = parsedParams.attribute; + let attribute; if (parsedParams.isWithExistingAttribute()) { - const foundAttribute = await this.consumptionController.attributes.getLocalAttribute(parsedParams.attributeId); + if (requestItem.query instanceof RelationshipAttributeQuery) { + return ValidationResult.error( + CoreErrors.requests.invalidAcceptParameters("When responding to a RelationshipAttributeQuery, only new RelationshipAttributes may be provided.") + ); + } - if (!foundAttribute) return ValidationResult.error(TransportCoreErrors.general.recordNotFound(LocalAttribute, requestInfo.id.toString())); + const foundLocalAttribute = await this.consumptionController.attributes.getLocalAttribute(parsedParams.attributeId); - const latestSharedVersion = await this.consumptionController.attributes.getSharedVersionsOfAttribute(parsedParams.attributeId, [requestInfo.peer], true); - if (latestSharedVersion.length > 0) { - if (!latestSharedVersion[0].shareInfo?.sourceAttribute) { - throw new Error( - `The Attribute ${latestSharedVersion[0].id} doesn't have a 'shareInfo.sourceAttribute', even though it was found as shared version of an Attribute.` + if (!foundLocalAttribute) { + return ValidationResult.error(TransportCoreErrors.general.recordNotFound(LocalAttribute, requestInfo.id.toString())); + } + + attribute = foundLocalAttribute.content; + + if (requestItem.query instanceof IdentityAttributeQuery && attribute instanceof IdentityAttribute && this.accountController.identity.isMe(attribute.owner)) { + if (foundLocalAttribute.isShared()) { + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch( + "The provided IdentityAttribute is a shared copy of a RepositoryAttribute. You can only share RepositoryAttributes." + ) ); } - const latestSharedVersionSourceAttribute = await this.consumptionController.attributes.getLocalAttribute(latestSharedVersion[0].shareInfo.sourceAttribute); - if (!latestSharedVersionSourceAttribute) throw new Error(`The Attribute ${latestSharedVersion[0].shareInfo.sourceAttribute} was not found.`); + const ownSharedIdentityAttributeSuccessors = await this.consumptionController.attributes.getSharedSuccessorsOfAttribute(foundLocalAttribute, { + "shareInfo.peer": requestInfo.peer.toString() + }); + + if (ownSharedIdentityAttributeSuccessors.length > 0) { + if (!ownSharedIdentityAttributeSuccessors[0].shareInfo?.sourceAttribute) { + throw new Error( + `The LocalAttribute ${ownSharedIdentityAttributeSuccessors[0].id} does not have a 'shareInfo.sourceAttribute', even though it was found as a shared version of a LocalAttribute.` + ); + } - if (await this.consumptionController.attributes.isSubsequentInSuccession(foundAttribute, latestSharedVersionSourceAttribute)) { - return ValidationResult.error(CoreErrors.requests.invalidAcceptParameters("You cannot share the predecessor of an already shared Attribute version.")); + const successorRepositorySourceAttribute = await this.consumptionController.attributes.getLocalAttribute( + ownSharedIdentityAttributeSuccessors[0].shareInfo.sourceAttribute + ); + if (!successorRepositorySourceAttribute) { + throw new Error(`The RepositoryAttribute ${ownSharedIdentityAttributeSuccessors[0].shareInfo.sourceAttribute} was not found.`); + } + + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch( + `The provided IdentityAttribute is outdated. You have already shared the successor '${ownSharedIdentityAttributeSuccessors[0].shareInfo.sourceAttribute}' of it.` + ) + ); } } + } else if (parsedParams.isWithNewAttribute()) { + attribute = parsedParams.attribute; - attribute = foundAttribute.content; + const ownerIsEmpty = attribute.owner.equals(""); + if (ownerIsEmpty) { + attribute.owner = this.currentIdentityAddress; + } } - const ownerIsEmpty = attribute!.owner.equals(""); - const ownerIsCurrentIdentity = attribute!.owner.equals(this.currentIdentityAddress); - if (!ownerIsEmpty && !ownerIsCurrentIdentity) { - return ValidationResult.error(CoreErrors.requests.invalidAcceptParameters("The given Attribute belongs to someone else. You can only share own Attributes.")); + if (typeof attribute === "undefined") { + return ValidationResult.error( + CoreErrors.requests.invalidAcceptParameters( + `You have to specify either ${nameof( + (x) => x.attribute + )} or ${nameof((x) => x.attributeId)}.` + ) + ); } + const answerToQueryValidationResult = validateAttributeMatchesWithQuery(requestItem.query, attribute, this.currentIdentityAddress, requestInfo.peer); + if (answerToQueryValidationResult.isError()) return answerToQueryValidationResult; + return ValidationResult.success(); } @@ -112,8 +165,8 @@ export class ProposeAttributeRequestItemProcessor extends GenericRequestItemProc requestInfo: LocalRequestInfo ): Promise { const parsedParams = AcceptProposeAttributeRequestItemParameters.from(params); + let sharedLocalAttribute; - let sharedLocalAttribute: LocalAttribute; if (parsedParams.isWithExistingAttribute()) { const existingSourceAttribute = await this.consumptionController.attributes.getLocalAttribute(parsedParams.attributeId); if (!existingSourceAttribute) throw TransportCoreErrors.general.recordNotFound(LocalAttribute, parsedParams.attributeId.toString()); @@ -161,8 +214,20 @@ export class ProposeAttributeRequestItemProcessor extends GenericRequestItemProc }); } } + } else if (parsedParams.isWithNewAttribute()) { + if (parsedParams.attribute.owner.equals("")) { + parsedParams.attribute.owner = this.currentIdentityAddress; + } + sharedLocalAttribute = await this.createNewAttribute(parsedParams.attribute, requestInfo); + } + + if (!sharedLocalAttribute) { + throw new Error( + `You have to specify either ${nameof( + (x) => x.attribute + )} or ${nameof((x) => x.attributeId)}.` + ); } - sharedLocalAttribute = await this.createNewAttribute(parsedParams.attribute!, requestInfo); return ProposeAttributeAcceptResponseItem.from({ result: ResponseItemResult.Accepted, diff --git a/packages/consumption/src/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.ts index bcf405af7..c13d2b2af 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.ts @@ -2,26 +2,32 @@ import { AttributeAlreadySharedAcceptResponseItem, AttributeSuccessionAcceptResponseItem, IdentityAttribute, + IdentityAttributeQuery, ReadAttributeAcceptResponseItem, ReadAttributeRequestItem, RejectResponseItem, RelationshipAttribute, + RelationshipAttributeConfidentiality, + RelationshipAttributeQuery, Request, - ResponseItemResult + ResponseItemResult, + ThirdPartyRelationshipAttributeQuery } from "@nmshd/content"; import { CoreAddress, CoreId, CoreErrors as TransportCoreErrors } from "@nmshd/transport"; +import { nameof } from "ts-simple-nameof"; import { CoreErrors } from "../../../../consumption/CoreErrors"; import { AttributeSuccessorParams, LocalAttributeShareInfo, PeerSharedAttributeSucceededEvent } from "../../../attributes"; import { LocalAttribute } from "../../../attributes/local/LocalAttribute"; import { ValidationResult } from "../../../common/ValidationResult"; import { GenericRequestItemProcessor } from "../GenericRequestItemProcessor"; import { LocalRequestInfo } from "../IRequestItemProcessor"; +import validateAttributeMatchesWithQuery from "../utility/validateAttributeMatchesWithQuery"; import validateQuery from "../utility/validateQuery"; import { AcceptReadAttributeRequestItemParameters, AcceptReadAttributeRequestItemParametersJSON } from "./AcceptReadAttributeRequestItemParameters"; export class ReadAttributeRequestItemProcessor extends GenericRequestItemProcessor { public override canCreateOutgoingRequestItem(requestItem: ReadAttributeRequestItem, _request: Request, recipient?: CoreAddress): ValidationResult { - const queryValidationResult = validateQuery(requestItem.query, this.currentIdentityAddress, recipient); + const queryValidationResult = this.validateQuery(requestItem, recipient); if (queryValidationResult.isError()) { return queryValidationResult; } @@ -29,37 +35,148 @@ export class ReadAttributeRequestItemProcessor extends GenericRequestItemProcess return ValidationResult.success(); } + private validateQuery(requestItem: ReadAttributeRequestItem, recipient?: CoreAddress) { + const commonQueryValidationResult = validateQuery(requestItem.query, this.currentIdentityAddress, recipient); + if (commonQueryValidationResult.isError()) { + return commonQueryValidationResult; + } + + if (requestItem.query instanceof RelationshipAttributeQuery && !["", this.currentIdentityAddress.toString()].includes(requestItem.query.owner.toString())) { + return ValidationResult.error( + CoreErrors.requests.invalidRequestItem( + "The owner of the given `query` can only be an empty string or yourself. This is because you can only request RelationshipAttributes using a ReadAttributeRequestitem with a RelationshipAttributeQuery where the Recipient of the Request or yourself is the owner. And in order to avoid mistakes, the Recipient automatically will become the owner of the RelationshipAttribute later on if the owner of the `query` is an empty string." + ) + ); + } + + return ValidationResult.success(); + } + public override async canAccept( - _requestItem: ReadAttributeRequestItem, + requestItem: ReadAttributeRequestItem, params: AcceptReadAttributeRequestItemParametersJSON, requestInfo: LocalRequestInfo ): Promise { const parsedParams = AcceptReadAttributeRequestItemParameters.from(params); + let attribute; if (parsedParams.isWithExistingAttribute()) { - const foundAttribute = await this.consumptionController.attributes.getLocalAttribute(parsedParams.existingAttributeId); - if (!foundAttribute) return ValidationResult.error(TransportCoreErrors.general.recordNotFound(LocalAttribute, requestInfo.id.toString())); + if (requestItem.query instanceof RelationshipAttributeQuery) { + return ValidationResult.error( + CoreErrors.requests.invalidAcceptParameters("When responding to a RelationshipAttributeQuery, only new RelationshipAttributes may be provided.") + ); + } + + const foundLocalAttribute = await this.consumptionController.attributes.getLocalAttribute(parsedParams.existingAttributeId); - const ownerIsCurrentIdentity = this.accountController.identity.isMe(foundAttribute.content.owner); - if (!ownerIsCurrentIdentity && foundAttribute.content instanceof IdentityAttribute) { - return ValidationResult.error(CoreErrors.requests.invalidAcceptParameters("The given Attribute belongs to someone else. You can only share own Attributes.")); + if (!foundLocalAttribute) { + return ValidationResult.error(TransportCoreErrors.general.recordNotFound(LocalAttribute, requestInfo.id.toString())); } - const latestSharedVersion = await this.consumptionController.attributes.getSharedVersionsOfAttribute(parsedParams.existingAttributeId, [requestInfo.peer], true); - if (latestSharedVersion.length > 0) { - if (!latestSharedVersion[0].shareInfo?.sourceAttribute) { + attribute = foundLocalAttribute.content; + + if (requestItem.query instanceof IdentityAttributeQuery && attribute instanceof IdentityAttribute && this.accountController.identity.isMe(attribute.owner)) { + if (foundLocalAttribute.isShared()) { + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch( + "The provided IdentityAttribute is a shared copy of a RepositoryAttribute. You can only share RepositoryAttributes." + ) + ); + } + + const ownSharedIdentityAttributeSuccessors = await this.consumptionController.attributes.getSharedSuccessorsOfAttribute(foundLocalAttribute, { + "shareInfo.peer": requestInfo.peer.toString() + }); + + if (ownSharedIdentityAttributeSuccessors.length > 0) { + if (!ownSharedIdentityAttributeSuccessors[0].shareInfo?.sourceAttribute) { + throw new Error( + `The LocalAttribute ${ownSharedIdentityAttributeSuccessors[0].id} does not have a 'shareInfo.sourceAttribute', even though it was found as a shared version of a LocalAttribute.` + ); + } + + const successorSourceRepositoryAttribute = await this.consumptionController.attributes.getLocalAttribute( + ownSharedIdentityAttributeSuccessors[0].shareInfo.sourceAttribute + ); + if (!successorSourceRepositoryAttribute) { + throw new Error(`The RepositoryAttribute ${ownSharedIdentityAttributeSuccessors[0].shareInfo.sourceAttribute} was not found.`); + } + + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch( + `The provided IdentityAttribute is outdated. You have already shared the successor '${ownSharedIdentityAttributeSuccessors[0].shareInfo.sourceAttribute}' of it.` + ) + ); + } + } + + if (requestItem.query instanceof ThirdPartyRelationshipAttributeQuery && attribute instanceof RelationshipAttribute) { + if (!foundLocalAttribute.isShared()) { throw new Error( - `The Attribute ${latestSharedVersion[0].id} doesn't have a 'shareInfo.sourceAttribute', even though it was found as shared version of an Attribute.` + "The LocalAttribute found is faulty because its shareInfo is undefined, although its content is given by a RelationshipAttribute. Since RelationshipAttributes only make sense in the context of Relationships, they must always be shared." + ); + } + + if (typeof foundLocalAttribute.shareInfo.sourceAttribute !== "undefined") { + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch( + "When responding to a ThirdPartyRelationshipAttributeQuery, only RelationshipAttributes that are not a copy of a sourceAttribute may be provided." + ) ); } - const latestSharedVersionSourceAttribute = await this.consumptionController.attributes.getLocalAttribute(latestSharedVersion[0].shareInfo.sourceAttribute); - if (!latestSharedVersionSourceAttribute) throw new Error(`The Attribute ${latestSharedVersion[0].shareInfo.sourceAttribute} was not found.`); + const queriedThirdParties = requestItem.query.thirdParty.map((aThirdParty) => aThirdParty.toString()); - if (await this.consumptionController.attributes.isSubsequentInSuccession(foundAttribute, latestSharedVersionSourceAttribute)) { - return ValidationResult.error(CoreErrors.requests.invalidAcceptParameters("You cannot share the predecessor of an already shared Attribute version.")); + if ( + (this.accountController.identity.isMe(attribute.owner) || queriedThirdParties.includes(attribute.owner.toString())) && + !queriedThirdParties.includes("") && + !queriedThirdParties.includes(foundLocalAttribute.shareInfo.peer.toString()) + ) { + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch( + "The provided RelationshipAttribute exists in the context of a Relationship with a third party that should not be involved." + ) + ); } } + } else if (parsedParams.isWithNewAttribute()) { + if (requestItem.query instanceof ThirdPartyRelationshipAttributeQuery) { + return ValidationResult.error( + CoreErrors.requests.invalidAcceptParameters( + "When responding to a ThirdPartyRelationshipAttributeQuery, only RelationshipAttributes that already exist may be provided." + ) + ); + } + + attribute = parsedParams.newAttribute; + + const ownerIsEmpty = attribute.owner.equals(""); + if (ownerIsEmpty) { + attribute.owner = this.currentIdentityAddress; + } + } + + if (typeof attribute === "undefined") { + return ValidationResult.error( + CoreErrors.requests.invalidAcceptParameters( + `You have to specify either ${nameof( + (x) => x.newAttribute + )} or ${nameof((x) => x.existingAttributeId)}.` + ) + ); + } + + const answerToQueryValidationResult = validateAttributeMatchesWithQuery(requestItem.query, attribute, this.currentIdentityAddress, requestInfo.peer); + if (answerToQueryValidationResult.isError()) return answerToQueryValidationResult; + + if ( + requestItem.query instanceof ThirdPartyRelationshipAttributeQuery && + attribute instanceof RelationshipAttribute && + attribute.confidentiality === RelationshipAttributeConfidentiality.Private + ) { + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch("The confidentiality of the provided RelationshipAttribute is private. Therefore you are not allowed to share it.") + ); } return ValidationResult.success(); @@ -71,8 +188,8 @@ export class ReadAttributeRequestItemProcessor extends GenericRequestItemProcess requestInfo: LocalRequestInfo ): Promise { const parsedParams = AcceptReadAttributeRequestItemParameters.from(params); + let sharedLocalAttribute; - let sharedLocalAttribute: LocalAttribute; if (parsedParams.isWithExistingAttribute()) { const existingSourceAttribute = await this.consumptionController.attributes.getLocalAttribute(parsedParams.existingAttributeId); if (!existingSourceAttribute) { @@ -128,8 +245,21 @@ export class ReadAttributeRequestItemProcessor extends GenericRequestItemProcess predecessorId: latestSharedAttribute.id }); } + } else if (parsedParams.isWithNewAttribute()) { + if (parsedParams.newAttribute.owner.equals("")) { + parsedParams.newAttribute.owner = this.currentIdentityAddress; + } + sharedLocalAttribute = await this.createNewAttribute(parsedParams.newAttribute, requestInfo); } - sharedLocalAttribute = await this.createNewAttribute(parsedParams.newAttribute!, requestInfo); + + if (!sharedLocalAttribute) { + throw new Error( + `You have to specify either ${nameof( + (x) => x.newAttribute + )} or ${nameof((x) => x.existingAttributeId)}.` + ); + } + return ReadAttributeAcceptResponseItem.from({ result: ResponseItemResult.Accepted, attributeId: sharedLocalAttribute.id, diff --git a/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts index c388887b3..14d6706c6 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts @@ -18,8 +18,9 @@ import { LocalRequestInfo } from "../IRequestItemProcessor"; export class ShareAttributeRequestItemProcessor extends GenericRequestItemProcessor { public override async canCreateOutgoingRequestItem(requestItem: ShareAttributeRequestItem, _request: Request, recipient?: CoreAddress): Promise { - const attribute = await this.consumptionController.attributes.getLocalAttribute(requestItem.sourceAttributeId); - if (!attribute) { + const foundAttribute = await this.consumptionController.attributes.getLocalAttribute(requestItem.sourceAttributeId); + + if (typeof foundAttribute === "undefined") { return ValidationResult.error( CoreErrors.requests.invalidRequestItem(`The Attribute with the given sourceAttributeId '${requestItem.sourceAttributeId.toString()}' could not be found.`) ); @@ -30,46 +31,105 @@ export class ShareAttributeRequestItemProcessor extends GenericRequestItemProces requestItemAttributeJSON.owner = this.currentIdentityAddress.toString(); } - if (!_.isEqual(attribute.content.toJSON(), requestItemAttributeJSON)) { + if (!_.isEqual(foundAttribute.content.toJSON(), requestItemAttributeJSON)) { return ValidationResult.error( CoreErrors.requests.invalidRequestItem( - `The Attribute with the given sourceAttributeId '${requestItem.sourceAttributeId.toString()}' does not match the given attribute.` + `The Attribute with the given sourceAttributeId '${requestItem.sourceAttributeId.toString()}' does not match the given Attribute.` ) ); } + if (requestItem.attribute instanceof IdentityAttribute && this.accountController.identity.isMe(requestItem.attribute.owner)) { + if (foundAttribute.isShared()) { + return ValidationResult.error( + CoreErrors.requests.invalidRequestItem("The provided IdentityAttribute is a shared copy of a RepositoryAttribute. You can only share RepositoryAttributes.") + ); + } + + if (typeof recipient !== "undefined") { + const query: any = { "shareInfo.sourceAttribute": requestItem.sourceAttributeId.toString(), "shareInfo.peer": recipient.toString() }; + + if ((await this.consumptionController.attributes.getLocalAttributes(query)).length > 0) { + return ValidationResult.error( + CoreErrors.requests.invalidRequestItem( + `The IdentityAttribute with the given sourceAttributeId '${requestItem.sourceAttributeId.toString()}' has already been shared with the peer.` + ) + ); + } + + const ownSharedIdentityAttributeSuccessors = await this.consumptionController.attributes.getSharedSuccessorsOfAttribute(foundAttribute, { + "shareInfo.peer": recipient.toString() + }); + + if (ownSharedIdentityAttributeSuccessors.length > 0) { + return ValidationResult.error( + CoreErrors.requests.invalidRequestItem( + `The provided IdentityAttribute is outdated. You have already shared the successor '${ownSharedIdentityAttributeSuccessors[0].shareInfo?.sourceAttribute}' of it.` + ) + ); + } + + const ownSharedIdentityAttributePredecessors = await this.consumptionController.attributes.getSharedPredecessorsOfAttribute(foundAttribute, { + "shareInfo.peer": recipient.toString() + }); + + if (ownSharedIdentityAttributePredecessors.length > 0) { + return ValidationResult.error( + CoreErrors.requests.invalidRequestItem( + `You have already shared the predecessor '${ownSharedIdentityAttributePredecessors[0].shareInfo?.sourceAttribute}' of the IdentityAttribute. Instead of sharing it, you should notify the peer about the Attribute succession.` + ) + ); + } + } + } + + if (requestItem.attribute instanceof RelationshipAttribute) { + if (!foundAttribute.isShared()) { + throw new Error( + "The LocalAttribute found is faulty because its shareInfo is undefined, although its content is given by a RelationshipAttribute. Since RelationshipAttributes only make sense in the context of Relationships, they must always be shared." + ); + } + + if (typeof foundAttribute.shareInfo.sourceAttribute !== "undefined") { + return ValidationResult.error(CoreErrors.requests.invalidRequestItem("You can only share RelationshipAttributes that are not a copy of a sourceAttribute.")); + } + + if (typeof recipient !== "undefined" && foundAttribute.shareInfo.peer.equals(recipient)) { + return ValidationResult.error( + CoreErrors.requests.invalidRequestItem("The provided RelationshipAttribute already exists in the context of the Relationship with the peer.") + ); + } + } + if (requestItem.attribute instanceof IdentityAttribute) { return this.canCreateWithIdentityAttribute(requestItem); } - return this.canCreateWithRelationshipAttribute(requestItem.attribute, recipient); + return ShareAttributeRequestItemProcessor.canCreateWithRelationshipAttribute(requestItem.attribute, recipient); } private canCreateWithIdentityAttribute(requestItem: ShareAttributeRequestItem) { - const ownerIsEmpty = requestItem.attribute.owner.toString() === ""; const ownerIsCurrentIdentity = requestItem.attribute.owner.equals(this.currentIdentityAddress); - if (!ownerIsEmpty && !ownerIsCurrentIdentity) { + if (!ownerIsCurrentIdentity) { return ValidationResult.error( - CoreErrors.requests.invalidRequestItem( - "The owner of the given `attribute` can only be an empty string. This is because you can only send Attributes where the recipient of the Request is the owner anyway. And in order to avoid mistakes, the owner will be automatically filled for you." - ) + CoreErrors.requests.invalidRequestItem("The provided IdentityAttribute belongs to someone else. You can only share own IdentityAttributes.") ); } return ValidationResult.success(); } - private canCreateWithRelationshipAttribute(attribute: RelationshipAttribute, recipient?: CoreAddress) { + private static canCreateWithRelationshipAttribute(attribute: RelationshipAttribute, recipient?: CoreAddress) { + if (attribute.owner.equals(recipient)) { + return ValidationResult.error(CoreErrors.requests.invalidRequestItem("It doesn't make sense to share a RelationshipAttribute with its owner.")); + } + if (attribute.confidentiality === RelationshipAttributeConfidentiality.Private) { return ValidationResult.error( CoreErrors.requests.invalidRequestItem("The confidentiality of the given `attribute` is private. Therefore you are not allowed to share it.") ); } - if (attribute.owner.equals(recipient)) { - return ValidationResult.error(CoreErrors.requests.invalidRequestItem("It doesn't make sense to share a RelationshipAttribute with its owner.")); - } - return ValidationResult.success(); } diff --git a/packages/consumption/src/modules/requests/itemProcessors/utility/validateAttributeMatchesWithQuery.ts b/packages/consumption/src/modules/requests/itemProcessors/utility/validateAttributeMatchesWithQuery.ts new file mode 100644 index 000000000..bc8173162 --- /dev/null +++ b/packages/consumption/src/modules/requests/itemProcessors/utility/validateAttributeMatchesWithQuery.ts @@ -0,0 +1,196 @@ +import { + Consent, + IdentityAttribute, + IdentityAttributeQuery, + IQLQuery, + RelationshipAttribute, + RelationshipAttributeQuery, + ThirdPartyRelationshipAttributeQuery +} from "@nmshd/content"; +import { CoreAddress } from "@nmshd/transport"; +import { CoreErrors } from "../../../../consumption/CoreErrors"; +import { ValidationResult } from "../../../common/ValidationResult"; + +export default function validateAttributeMatchesWithQuery( + query: IdentityAttributeQuery | IQLQuery | RelationshipAttributeQuery | ThirdPartyRelationshipAttributeQuery, + attribute: IdentityAttribute | RelationshipAttribute, + recipient: CoreAddress, + sender: CoreAddress +): ValidationResult { + if (query instanceof IdentityAttributeQuery) { + const result = validateAttributeMatchesWithIdentityAttributeQuery(query, attribute, recipient); + if (result.isError()) return result; + } else if (query instanceof IQLQuery) { + const result = validateAttributeMatchesWithIQLQuery(query, attribute, recipient); + if (result.isError()) return result; + } else if (query instanceof RelationshipAttributeQuery) { + const result = validateAttributeMatchesWithRelationshipAttributeQuery(query, attribute, recipient); + if (result.isError()) return result; + } else if (query instanceof ThirdPartyRelationshipAttributeQuery) { + const result = validateAttributeMatchesWithThirdPartyRelationshipAttributeQuery(query, attribute, recipient, sender); + if (result.isError()) return result; + } else { + return ValidationResult.error( + CoreErrors.requests.unexpectedErrorDuringRequestItemProcessing( + "The query is not of a known type. Only the IdentityAttributeQuery, IQLQuery, RelationshipAttributeQuery or ThirdPartyRelationshipAttributeQuery can be used." + ) + ); + } + + if (query instanceof IdentityAttributeQuery || query instanceof RelationshipAttributeQuery || query instanceof ThirdPartyRelationshipAttributeQuery) { + if ( + (typeof query.validFrom === "undefined" && typeof attribute.validFrom !== "undefined") || + (typeof query.validFrom !== "undefined" && typeof attribute.validFrom !== "undefined" && query.validFrom.isBefore(attribute.validFrom)) + ) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The provided Attribute is not valid in the queried time frame.")); + } + + if ( + (typeof query.validTo === "undefined" && typeof attribute.validTo !== "undefined") || + (typeof query.validTo !== "undefined" && typeof attribute.validTo !== "undefined" && query.validTo.isAfter(attribute.validTo)) + ) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The provided Attribute is not valid in the queried time frame.")); + } + } + + return ValidationResult.success(); +} + +function validateAttributeMatchesWithIdentityAttributeQuery( + query: IdentityAttributeQuery, + attribute: IdentityAttribute | RelationshipAttribute, + recipient: CoreAddress +): ValidationResult { + if (!(attribute instanceof IdentityAttribute)) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The provided Attribute is not an IdentityAttribute, but an IdentityAttribute was queried.")); + } + + const recipientIsAttributeOwner = recipient.equals(attribute.owner); + + if (!recipientIsAttributeOwner) { + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch("The provided IdentityAttribute belongs to someone else. You can only share own IdentityAttributes.") + ); + } + + if (query.valueType !== attribute.value.constructor.name) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The provided IdentityAttribute is not of the queried IdentityAttribute value type.")); + } + + if (typeof query.tags !== "undefined" && query.tags.length !== 0) { + if (attribute.tags === undefined || attribute.tags.length === 0 || !query.tags.some((aQueriedTag) => attribute.tags!.includes(aQueriedTag))) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The tags of the provided IdentityAttribute do not contain at least one queried tag.")); + } + } + + return ValidationResult.success(); +} + +function validateAttributeMatchesWithIQLQuery(query: IQLQuery, attribute: IdentityAttribute | RelationshipAttribute, recipient: CoreAddress): ValidationResult { + if (!(attribute instanceof IdentityAttribute)) { + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch("The provided Attribute is not an IdentityAttribute. Currently, only IdentityAttributes can be queried by an IQLQuery.") + ); + } + + const recipientIsAttributeOwner = recipient.equals(attribute.owner); + + if (!recipientIsAttributeOwner) { + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch("The provided IdentityAttribute belongs to someone else. You can only share own IdentityAttributes.") + ); + } + + return ValidationResult.success(); +} + +function validateAttributeMatchesWithRelationshipAttributeQuery( + query: RelationshipAttributeQuery, + attribute: IdentityAttribute | RelationshipAttribute, + recipient: CoreAddress +): ValidationResult { + if (!(attribute instanceof RelationshipAttribute)) { + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch("The provided Attribute is not a RelationshipAttribute, but a RelationshipAttribute was queried.") + ); + } + + const recipientIsAttributeOwner = recipient.equals(attribute.owner); + const queriedOwnerIsEmpty = query.owner.equals(""); + + if (!queriedOwnerIsEmpty && !query.owner.equals(attribute.owner)) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The provided RelationshipAttribute does not belong to the queried owner.")); + } + + if (queriedOwnerIsEmpty && !recipientIsAttributeOwner) { + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch("You are not the owner of the provided RelationshipAttribute, but an empty string was specified for the owner of the query.") + ); + } + + if (query.key !== attribute.key) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The provided RelationshipAttribute does not have the queried key.")); + } + + if (query.attributeCreationHints.confidentiality !== attribute.confidentiality) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The provided RelationshipAttribute does not have the queried confidentiality.")); + } + + if (query.attributeCreationHints.valueType !== attribute.value.constructor.name) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The provided RelationshipAttribute is not of the queried RelationshipAttribute value type.")); + } + + if (!(attribute.value instanceof Consent)) { + if (query.attributeCreationHints.title !== attribute.value.title) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The provided RelationshipAttribute does not have the queried title.")); + } + + if (query.attributeCreationHints.description !== attribute.value.description) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The provided RelationshipAttribute does not have the queried description.")); + } + } + + return ValidationResult.success(); +} + +function validateAttributeMatchesWithThirdPartyRelationshipAttributeQuery( + query: ThirdPartyRelationshipAttributeQuery, + attribute: IdentityAttribute | RelationshipAttribute, + recipient: CoreAddress, + sender: CoreAddress +): ValidationResult { + if (!(attribute instanceof RelationshipAttribute)) { + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch("The provided Attribute is not a RelationshipAttribute, but a RelationshipAttribute was queried.") + ); + } + + const recipientIsAttributeOwner = recipient.equals(attribute.owner); + const senderIsAttributeOwner = sender.equals(attribute.owner); + + const queriedThirdParties = query.thirdParty.map((aThirdParty) => aThirdParty.toString()); + + if ( + senderIsAttributeOwner || + (query.owner === "recipient" && !recipientIsAttributeOwner) || + (query.owner === "thirdParty" && + !queriedThirdParties.includes(attribute.owner.toString()) && + (!queriedThirdParties.includes("") || (recipientIsAttributeOwner && !queriedThirdParties.includes(recipient.toString())))) + ) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The provided RelationshipAttribute does not belong to a queried owner.")); + } + + if (query.owner === "" && !recipientIsAttributeOwner && !queriedThirdParties.includes("") && !queriedThirdParties.includes(attribute.owner.toString())) { + return ValidationResult.error( + CoreErrors.requests.attributeQueryMismatch( + "Neither you nor one of the involved third parties is the owner of the provided RelationshipAttribute, but an empty string was specified for the owner of the query." + ) + ); + } + + if (query.key !== attribute.key) { + return ValidationResult.error(CoreErrors.requests.attributeQueryMismatch("The provided RelationshipAttribute does not have the queried key.")); + } + + return ValidationResult.success(); +} diff --git a/packages/consumption/src/modules/requests/itemProcessors/utility/validateQuery.ts b/packages/consumption/src/modules/requests/itemProcessors/utility/validateQuery.ts index eb01de8ed..1c8458e15 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/utility/validateQuery.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/utility/validateQuery.ts @@ -14,10 +14,6 @@ export default function validateQuery( const result = validateThirdParty(thirdParty, sender, recipient); if (result.isError()) return result; } - - if (query.owner.equals(sender)) { - return ValidationResult.error(CoreErrors.requests.invalidRequestItem("Cannot query own Attributes from a third party.")); - } } else if (query instanceof IQLQuery) { const validationResult = iqlValidate(query.queryString); if (!validationResult.isValid) { diff --git a/packages/consumption/test/modules/attributeListeners/AttributeListenersController.test.ts b/packages/consumption/test/modules/attributeListeners/AttributeListenersController.test.ts index 85770b712..3b2b04c88 100644 --- a/packages/consumption/test/modules/attributeListeners/AttributeListenersController.test.ts +++ b/packages/consumption/test/modules/attributeListeners/AttributeListenersController.test.ts @@ -12,8 +12,8 @@ describe("AttributeListenersController", function () { let transport: Transport; const dummyQuery = ThirdPartyRelationshipAttributeQuery.from({ - key: "aKey", - owner: "anOwner", + key: "AKey", + owner: "", thirdParty: ["aThirdParty"] }); const dummyPeer = CoreAddress.from("aPeer"); diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index 40b7a8054..8f92c0ed6 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -15,6 +15,7 @@ import { RelationshipAttributeConfidentiality, Street, StreetAddress, + ThirdPartyRelationshipAttributeQueryOwner, ZipCode } from "@nmshd/content"; import { AccountController, CoreAddress, CoreDate, CoreId, Transport } from "@nmshd/transport"; @@ -216,293 +217,428 @@ describe("AttributesController", function () { mockEventBus.expectLastPublishedEvent(SharedAttributeCopyCreatedEvent); }); - test("should allow to query public relationship attributes", async function () { - const relationshipAttributeParams: ICreateLocalAttributeParams = { - content: RelationshipAttribute.from({ - key: "customerId", - value: { - "@type": "ProprietaryString", - value: "0815", - title: "Customer ID" - }, - owner: testAccount.identity.address, - confidentiality: RelationshipAttributeConfidentiality.Public - }) - }; - await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); - - const query: IRelationshipAttributeQuery = { - key: "customerId", - owner: testAccount.identity.address, - attributeCreationHints: { - valueType: "ProprietaryString", - title: "someHintTitle", - confidentiality: "public" as RelationshipAttributeConfidentiality - } - }; + describe("Attribute queries", function () { + test("should allow to query relationship attributes with empty owner", async function () { + const relationshipAttributeParams: ICreateLocalAttributeParams = { + content: RelationshipAttribute.from({ + key: "AKey", + value: { + "@type": "ProprietaryString", + value: "AStringValue", + title: "ATtitle" + }, + owner: testAccount.identity.address, + confidentiality: RelationshipAttributeConfidentiality.Public + }) + }; + await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); + + const query: IRelationshipAttributeQuery = { + key: "AKey", + owner: CoreAddress.from(""), + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: "public" as RelationshipAttributeConfidentiality + } + }; - const attribute = await consumptionController.attributes.executeRelationshipAttributeQuery(query); - expect(attribute).toBeDefined(); - expect(attribute!.content).toBeInstanceOf(RelationshipAttribute); - expect((attribute!.content as RelationshipAttribute).key).toBe("customerId"); - }); + const attribute = await consumptionController.attributes.executeRelationshipAttributeQuery(query); + expect(attribute).toBeDefined(); + }); - test("should allow to query protected relationship attributes", async function () { - const relationshipAttributeParams: ICreateLocalAttributeParams = { - content: RelationshipAttribute.from({ + test("should allow to query public relationship attributes", async function () { + const relationshipAttributeParams: ICreateLocalAttributeParams = { + content: RelationshipAttribute.from({ + key: "customerId", + value: { + "@type": "ProprietaryString", + value: "0815", + title: "Customer ID" + }, + owner: testAccount.identity.address, + confidentiality: RelationshipAttributeConfidentiality.Public + }) + }; + await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); + + const query: IRelationshipAttributeQuery = { key: "customerId", - value: { - "@type": "ProprietaryString", - value: "0815", - title: "Customer ID" - }, owner: testAccount.identity.address, - confidentiality: RelationshipAttributeConfidentiality.Protected - }) - }; - await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); - - const query: IRelationshipAttributeQuery = { - key: "customerId", - owner: testAccount.identity.address, - attributeCreationHints: { - valueType: "ProprietaryString", - title: "someHintTitle", - confidentiality: RelationshipAttributeConfidentiality.Protected - } - }; + attributeCreationHints: { + valueType: "ProprietaryString", + title: "someHintTitle", + confidentiality: "public" as RelationshipAttributeConfidentiality + } + }; - const attribute = await consumptionController.attributes.executeRelationshipAttributeQuery(query); - expect(attribute).toBeDefined(); - expect(attribute!.content).toBeInstanceOf(RelationshipAttribute); - expect((attribute!.content as RelationshipAttribute).key).toBe("customerId"); - }); + const attribute = await consumptionController.attributes.executeRelationshipAttributeQuery(query); + expect(attribute).toBeDefined(); + expect(attribute!.content).toBeInstanceOf(RelationshipAttribute); + expect((attribute!.content as RelationshipAttribute).key).toBe("customerId"); + }); + + test("should allow to query protected relationship attributes", async function () { + const relationshipAttributeParams: ICreateLocalAttributeParams = { + content: RelationshipAttribute.from({ + key: "customerId", + value: { + "@type": "ProprietaryString", + value: "0815", + title: "Customer ID" + }, + owner: testAccount.identity.address, + confidentiality: RelationshipAttributeConfidentiality.Protected + }) + }; + await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); - test("should not allow to query private relationship attributes", async function () { - const relationshipAttributeParams: ICreateLocalAttributeParams = { - content: RelationshipAttribute.from({ + const query: IRelationshipAttributeQuery = { key: "customerId", - value: { - "@type": "ProprietaryString", - value: "0815", - title: "Customer ID" - }, owner: testAccount.identity.address, - confidentiality: RelationshipAttributeConfidentiality.Private - }) - }; - await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); - - const query: IRelationshipAttributeQuery = { - key: "customerId", - owner: testAccount.identity.address, - attributeCreationHints: { - valueType: "ProprietaryString", - title: "someHintTitle", - confidentiality: RelationshipAttributeConfidentiality.Private - } - }; + attributeCreationHints: { + valueType: "ProprietaryString", + title: "someHintTitle", + confidentiality: RelationshipAttributeConfidentiality.Protected + } + }; - const attribute = await consumptionController.attributes.executeRelationshipAttributeQuery(query); - expect(attribute).toBeUndefined(); - }); + const attribute = await consumptionController.attributes.executeRelationshipAttributeQuery(query); + expect(attribute).toBeDefined(); + expect(attribute!.content).toBeInstanceOf(RelationshipAttribute); + expect((attribute!.content as RelationshipAttribute).key).toBe("customerId"); + }); - test("should query relationship attributes using the ThirdPartyRelationshipAttributeQuery", async function () { - const relationshipAttribute = await consumptionController.attributes.createPeerLocalAttribute({ - content: RelationshipAttribute.from({ + test("should not allow to query private relationship attributes", async function () { + const relationshipAttributeParams: ICreateLocalAttributeParams = { + content: RelationshipAttribute.from({ + key: "customerId", + value: { + "@type": "ProprietaryString", + value: "0815", + title: "Customer ID" + }, + owner: testAccount.identity.address, + confidentiality: RelationshipAttributeConfidentiality.Private + }) + }; + await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); + + const query: IRelationshipAttributeQuery = { key: "customerId", - value: { - "@type": "ProprietaryString", - value: "0815", - title: "Customer ID" - }, owner: testAccount.identity.address, - confidentiality: RelationshipAttributeConfidentiality.Protected - }), - peer: CoreAddress.from("peerAddress"), - requestReference: CoreId.from("requestId") - }); + attributeCreationHints: { + valueType: "ProprietaryString", + title: "someHintTitle", + confidentiality: RelationshipAttributeConfidentiality.Private + } + }; - const attributes = await consumptionController.attributes.executeThirdPartyRelationshipAttributeQuery({ - key: "customerId", - owner: testAccount.identity.address, - thirdParty: [CoreAddress.from("peerAddress")] + const attribute = await consumptionController.attributes.executeRelationshipAttributeQuery(query); + expect(attribute).toBeUndefined(); }); - expect(attributes).toHaveLength(1); - expect(attributes[0].id.toString()).toStrictEqual(relationshipAttribute.id.toString()); - }); - test("should not query relationship attributes with confidentiality set to `Private` using the ThirdPartyRelationshipAttributeQuery", async function () { - await consumptionController.attributes.createPeerLocalAttribute({ - content: RelationshipAttribute.from({ + test("should query relationship attributes using the ThirdPartyRelationshipAttributeQuery", async function () { + const relationshipAttribute = await consumptionController.attributes.createPeerLocalAttribute({ + content: RelationshipAttribute.from({ + key: "customerId", + value: { + "@type": "ProprietaryString", + value: "0815", + title: "Customer ID" + }, + owner: CoreAddress.from("peerAddress"), + confidentiality: RelationshipAttributeConfidentiality.Protected + }), + peer: CoreAddress.from("peerAddress"), + requestReference: CoreId.from("requestId") + }); + + const attributes = await consumptionController.attributes.executeThirdPartyRelationshipAttributeQuery({ key: "customerId", - value: { - "@type": "ProprietaryString", - value: "0815", - title: "Customer ID" - }, - owner: testAccount.identity.address, - confidentiality: RelationshipAttributeConfidentiality.Private - }), - peer: CoreAddress.from("peerAddress"), - requestReference: CoreId.from("requestId") + owner: ThirdPartyRelationshipAttributeQueryOwner.ThirdParty, + thirdParty: [CoreAddress.from("peerAddress")] + }); + expect(attributes).toHaveLength(1); + expect(attributes[0].id.toString()).toStrictEqual(relationshipAttribute.id.toString()); }); - const attributes = await consumptionController.attributes.executeThirdPartyRelationshipAttributeQuery({ - key: "customerId", - owner: testAccount.identity.address, - thirdParty: [CoreAddress.from("peerAddress")] - }); - expect(attributes).toHaveLength(0); - }); + test("should not query relationship attributes with confidentiality set to `Private` using the ThirdPartyRelationshipAttributeQuery", async function () { + await consumptionController.attributes.createPeerLocalAttribute({ + content: RelationshipAttribute.from({ + key: "customerId", + value: { + "@type": "ProprietaryString", + value: "0815", + title: "Customer ID" + }, + owner: CoreAddress.from("peerAddress"), + confidentiality: RelationshipAttributeConfidentiality.Private + }), + peer: CoreAddress.from("peerAddress"), + requestReference: CoreId.from("requestId") + }); - test("should not query relationship attributes with not matching key using the ThirdPartyRelationshipAttributeQuery", async function () { - await consumptionController.attributes.createPeerLocalAttribute({ - content: RelationshipAttribute.from({ + const attributes = await consumptionController.attributes.executeThirdPartyRelationshipAttributeQuery({ key: "customerId", - value: { - "@type": "ProprietaryString", - value: "0815", - title: "Customer ID" - }, - owner: testAccount.identity.address, - confidentiality: RelationshipAttributeConfidentiality.Private - }), - peer: CoreAddress.from("peerAddress"), - requestReference: CoreId.from("requestId") + owner: ThirdPartyRelationshipAttributeQueryOwner.Empty, + thirdParty: [CoreAddress.from("peerAddress")] + }); + expect(attributes).toHaveLength(0); }); - const attributes = await consumptionController.attributes.executeThirdPartyRelationshipAttributeQuery({ - key: "notMatchingKey", - owner: testAccount.identity.address, - thirdParty: [CoreAddress.from("peerAddress")] - }); - expect(attributes).toHaveLength(0); - }); + test("should not query relationship attributes with not matching key using the ThirdPartyRelationshipAttributeQuery", async function () { + await consumptionController.attributes.createPeerLocalAttribute({ + content: RelationshipAttribute.from({ + key: "customerId", + value: { + "@type": "ProprietaryString", + value: "0815", + title: "Customer ID" + }, + owner: CoreAddress.from("peerAddress"), + confidentiality: RelationshipAttributeConfidentiality.Private + }), + peer: CoreAddress.from("peerAddress"), + requestReference: CoreId.from("requestId") + }); - test("should allow to query identity attributes", async function () { - const identityAttributeParams: ICreateLocalAttributeParams = { - content: IdentityAttribute.from({ - value: { - "@type": "Nationality", - value: "DE" - }, - owner: testAccount.identity.address - }) - }; - const identityAttribute = await consumptionController.attributes.createLocalAttribute(identityAttributeParams); + const attributes = await consumptionController.attributes.executeThirdPartyRelationshipAttributeQuery({ + key: "notMatchingKey", + owner: ThirdPartyRelationshipAttributeQueryOwner.Empty, + thirdParty: [CoreAddress.from("peerAddress")] + }); + expect(attributes).toHaveLength(0); + }); - const relationshipAttributeParams: ICreateLocalAttributeParams = { - content: RelationshipAttribute.from({ - key: "customerId", - value: { - "@type": "ProprietaryString", - value: "0815", - title: "Customer Id" - }, - owner: testAccount.identity.address, - confidentiality: "public" as RelationshipAttributeConfidentiality - }) - }; - const relationshipAttribute = await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); + test("can call executeThirdPartyRelationshipAttributeQuery with ThirdPartyRelationshipAttributeQueryOwner.Recipient", async function () { + const recipient = testAccount.identity.address; + await consumptionController.attributes.createPeerLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + value: { + "@type": "ProprietaryString", + value: "AStringValue", + title: "ATitle" + }, + owner: recipient, + confidentiality: RelationshipAttributeConfidentiality.Public + }), + peer: CoreAddress.from("thirdPartyAddress"), + requestReference: CoreId.from("requestId") + }); - const query: IIdentityAttributeQuery = { - valueType: "Nationality" - }; + const attributes = await consumptionController.attributes.executeThirdPartyRelationshipAttributeQuery({ + key: "AKey", + owner: ThirdPartyRelationshipAttributeQueryOwner.Recipient, + thirdParty: [CoreAddress.from("thirdPartyAddress")] + }); + expect(attributes).toHaveLength(1); + }); - const attributes = await consumptionController.attributes.executeIdentityAttributeQuery(query); - const attributesId = attributes.map((v) => v.id.toString()); - expect(attributesId).not.toContain(relationshipAttribute.id.toString()); - expect(attributesId).toContain(identityAttribute.id.toString()); - }); + test("can call executeThirdPartyRelationshipAttributeQuery with ThirdPartyRelationshipAttributeQueryOwner.ThirdParty", async function () { + await consumptionController.attributes.createPeerLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + value: { + "@type": "ProprietaryString", + value: "AStringValue", + title: "ATitle" + }, + owner: CoreAddress.from("thirdPartyAddress"), + confidentiality: RelationshipAttributeConfidentiality.Public + }), + peer: CoreAddress.from("thirdPartyAddress"), + requestReference: CoreId.from("requestId") + }); - test("should successfully execute IQL queries only with repository attributes", async function () { - const identityAttributeParams: ICreateLocalAttributeParams = { - content: IdentityAttribute.from({ - value: { - "@type": "Nationality", - value: "DE" - }, - owner: testAccount.identity.address - }) - }; - const identityAttribute = await consumptionController.attributes.createLocalAttribute(identityAttributeParams); - await consumptionController.attributes.createSharedLocalAttributeCopy({ - peer: CoreAddress.from("a-fake-peer"), - requestReference: CoreId.from("a-fake-reference"), - sourceAttributeId: identityAttribute.id, - attributeId: CoreId.from("fake-attribute-id") + const attributes = await consumptionController.attributes.executeThirdPartyRelationshipAttributeQuery({ + key: "AKey", + owner: ThirdPartyRelationshipAttributeQueryOwner.ThirdParty, + thirdParty: [CoreAddress.from("thirdPartyAddress")] + }); + expect(attributes).toHaveLength(1); }); - const iqlQuery: IIQLQuery = { queryString: "Nationality=DE" }; - const matchedAttributes = await consumptionController.attributes.executeIQLQuery(iqlQuery); - expect(matchedAttributes).toHaveLength(1); - const matchedAttributeIds = matchedAttributes.map((v) => v.id.toString()); - expect(matchedAttributeIds).toContain(identityAttribute.id.toString()); - }); + test("can call executeThirdPartyRelationshipAttributeQuery with ThirdPartyRelationshipAttributeQueryOwner.Empty", async function () { + const recipient = testAccount.identity.address; + await consumptionController.attributes.createPeerLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + value: { + "@type": "ProprietaryString", + value: "AStringValue", + title: "ATitle" + }, + owner: recipient, + confidentiality: RelationshipAttributeConfidentiality.Public + }), + peer: CoreAddress.from("thirdPartyAddress"), + requestReference: CoreId.from("requestId") + }); - test("should only return repository attributes on IdentityAttributeQuery", async function () { - const identityAttributeParams: ICreateLocalAttributeParams = { - content: IdentityAttribute.from({ - value: { - "@type": "DisplayName", - value: "Dis Play" - }, - owner: testAccount.identity.address - }) - }; - const identityAttribute = await consumptionController.attributes.createLocalAttribute(identityAttributeParams); + await consumptionController.attributes.createPeerLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + value: { + "@type": "ProprietaryString", + value: "AStringValue", + title: "ATitle" + }, + owner: CoreAddress.from("thirdPartyAddress"), + confidentiality: RelationshipAttributeConfidentiality.Public + }), + peer: CoreAddress.from("anotherThirdPartyAddress"), + requestReference: CoreId.from("requestId") + }); - const relationshipAttributeParams: ICreateLocalAttributeParams = { - content: RelationshipAttribute.from({ - key: "displayName", - value: { - "@type": "ProprietaryString", - title: "A Title", - value: "DE" - }, - owner: testAccount.identity.address, - confidentiality: "public" as RelationshipAttributeConfidentiality - }) - }; - const relationshipAttribute = await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); + await consumptionController.attributes.createPeerLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + value: { + "@type": "ProprietaryString", + value: "AStringValue", + title: "ATitle" + }, + owner: CoreAddress.from("uninvolvedThirdPartyAddress"), + confidentiality: RelationshipAttributeConfidentiality.Public + }), + peer: CoreAddress.from("thirdPartyAddress"), + requestReference: CoreId.from("requestId") + }); - const peerAttributeParams: ICreateLocalAttributeParams = { - content: IdentityAttribute.from({ - value: { - "@type": "DisplayName", - value: "DE" - }, - owner: CoreAddress.from("peer") - }) - }; - const peerAttribute = await consumptionController.attributes.createLocalAttribute(peerAttributeParams); + const attributes = await consumptionController.attributes.executeThirdPartyRelationshipAttributeQuery({ + key: "AKey", + owner: ThirdPartyRelationshipAttributeQueryOwner.Empty, + thirdParty: [CoreAddress.from("thirdPartyAddress"), CoreAddress.from("anotherThirdPartyAddress")] + }); + expect(attributes).toHaveLength(2); + }); + + test("should allow to query identity attributes", async function () { + const identityAttributeParams: ICreateLocalAttributeParams = { + content: IdentityAttribute.from({ + value: { + "@type": "Nationality", + value: "DE" + }, + owner: testAccount.identity.address + }) + }; + const identityAttribute = await consumptionController.attributes.createLocalAttribute(identityAttributeParams); + + const relationshipAttributeParams: ICreateLocalAttributeParams = { + content: RelationshipAttribute.from({ + key: "customerId", + value: { + "@type": "ProprietaryString", + value: "0815", + title: "Customer Id" + }, + owner: testAccount.identity.address, + confidentiality: "public" as RelationshipAttributeConfidentiality + }) + }; + const relationshipAttribute = await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); - const sentAttribute = await consumptionController.attributes.createSharedLocalAttributeCopy({ - peer: CoreAddress.from("peer"), - requestReference: CoreId.from("ref"), - sourceAttributeId: identityAttribute.id + const query: IIdentityAttributeQuery = { + valueType: "Nationality" + }; + + const attributes = await consumptionController.attributes.executeIdentityAttributeQuery(query); + const attributesId = attributes.map((v) => v.id.toString()); + expect(attributesId).not.toContain(relationshipAttribute.id.toString()); + expect(attributesId).toContain(identityAttribute.id.toString()); }); - const receivedAttribute = await consumptionController.attributes.createSharedLocalAttributeCopy({ - peer: CoreAddress.from("peer"), - requestReference: CoreId.from("ref2"), - sourceAttributeId: peerAttribute.id, - attributeId: CoreId.from("attr1") + test("should successfully execute IQL queries only with repository attributes", async function () { + const identityAttributeParams: ICreateLocalAttributeParams = { + content: IdentityAttribute.from({ + value: { + "@type": "Nationality", + value: "DE" + }, + owner: testAccount.identity.address + }) + }; + const identityAttribute = await consumptionController.attributes.createLocalAttribute(identityAttributeParams); + await consumptionController.attributes.createSharedLocalAttributeCopy({ + peer: CoreAddress.from("a-fake-peer"), + requestReference: CoreId.from("a-fake-reference"), + sourceAttributeId: identityAttribute.id, + attributeId: CoreId.from("fake-attribute-id") + }); + + const iqlQuery: IIQLQuery = { queryString: "Nationality=DE" }; + const matchedAttributes = await consumptionController.attributes.executeIQLQuery(iqlQuery); + expect(matchedAttributes).toHaveLength(1); + const matchedAttributeIds = matchedAttributes.map((v) => v.id.toString()); + expect(matchedAttributeIds).toContain(identityAttribute.id.toString()); }); - const query: IIdentityAttributeQuery = { - valueType: "DisplayName" - }; + test("should only return repository attributes on IdentityAttributeQuery", async function () { + const identityAttributeParams: ICreateLocalAttributeParams = { + content: IdentityAttribute.from({ + value: { + "@type": "DisplayName", + value: "Dis Play" + }, + owner: testAccount.identity.address + }) + }; + const identityAttribute = await consumptionController.attributes.createLocalAttribute(identityAttributeParams); + + const relationshipAttributeParams: ICreateLocalAttributeParams = { + content: RelationshipAttribute.from({ + key: "displayName", + value: { + "@type": "ProprietaryString", + title: "ATitle", + value: "DE" + }, + owner: testAccount.identity.address, + confidentiality: "public" as RelationshipAttributeConfidentiality + }) + }; + const relationshipAttribute = await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); - const attributes = await consumptionController.attributes.executeIdentityAttributeQuery(query); - const attributesId = attributes.map((v) => v.id.toString()); - expect(attributes).toHaveLength(1); - expect(attributesId).not.toContain(relationshipAttribute.id.toString()); - expect(attributesId).not.toContain(peerAttribute.id.toString()); - expect(attributesId).not.toContain(sentAttribute.id.toString()); - expect(attributesId).not.toContain(receivedAttribute.id.toString()); - expect(attributesId).toContain(identityAttribute.id.toString()); + const peerAttributeParams: ICreateLocalAttributeParams = { + content: IdentityAttribute.from({ + value: { + "@type": "DisplayName", + value: "DE" + }, + owner: CoreAddress.from("peer") + }) + }; + const peerAttribute = await consumptionController.attributes.createLocalAttribute(peerAttributeParams); + + const sentAttribute = await consumptionController.attributes.createSharedLocalAttributeCopy({ + peer: CoreAddress.from("peer"), + requestReference: CoreId.from("ref"), + sourceAttributeId: identityAttribute.id + }); + + const receivedAttribute = await consumptionController.attributes.createSharedLocalAttributeCopy({ + peer: CoreAddress.from("peer"), + requestReference: CoreId.from("ref2"), + sourceAttributeId: peerAttribute.id, + attributeId: CoreId.from("attr1") + }); + + const query: IIdentityAttributeQuery = { + valueType: "DisplayName" + }; + + const attributes = await consumptionController.attributes.executeIdentityAttributeQuery(query); + const attributesId = attributes.map((v) => v.id.toString()); + expect(attributes).toHaveLength(1); + expect(attributesId).not.toContain(relationshipAttribute.id.toString()); + expect(attributesId).not.toContain(peerAttribute.id.toString()); + expect(attributesId).not.toContain(sentAttribute.id.toString()); + expect(attributesId).not.toContain(receivedAttribute.id.toString()); + expect(attributesId).toContain(identityAttribute.id.toString()); + }); }); test("should allow to create an attribute shared by a peer", async function () { @@ -2413,7 +2549,7 @@ describe("AttributesController", function () { test("should return an empty list if a relationship attribute without associated third party relationship attributes is queried", async function () { const relationshipAttribute = await consumptionController.attributes.createLocalAttribute({ content: RelationshipAttribute.from({ - key: "Some key", + key: "AKey", value: { "@type": "ProprietaryString", value: "Some value", diff --git a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts index be56ca69d..b4e00b8dd 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts @@ -45,7 +45,7 @@ describe("CreateAttributeRequestItemProcessor", function () { await When.iCallCanCreateOutgoingRequestItemWith({ attribute: identityAttributeOfSender }); await Then.theResultShouldBeAnErrorWith({ - message: /Cannot create own Attributes with a CreateAttributeRequestItem. Use a ShareAttributeRequestItem instead./ + message: "Cannot create own IdentityAttributes with a CreateAttributeRequestItem. Use a ShareAttributeRequestItem instead." }); }); @@ -65,65 +65,71 @@ describe("CreateAttributeRequestItemProcessor", function () { await When.iCallCanCreateOutgoingRequestItemWith({ attribute: identityAttributeOfSomeoneElse }); await Then.theResultShouldBeAnErrorWith({ - message: /The owner of the given `attribute` can only be the recipient's address or an empty string. The latter will default to the recipient's address./ + message: + "The owner of the provided IdentityAttribute for the `attribute` property can only be the Recipient's Address or an empty string. The latter will default to the Recipient's Address." }); }); - test("returns Success when passing an Identity Attribute with owner={{SomeoneElse}}, but no recipient", async function () { + test("returns Success when passing an Identity Attribute with owner={{SomeoneElse}}, but no Recipient", async function () { const identityAttributeOfSomeoneElse = TestObjectFactory.createIdentityAttribute({ owner: TestIdentity.SOMEONE_ELSE }); await When.iCallCanCreateOutgoingRequestItemWith({ attribute: identityAttributeOfSomeoneElse }, TestIdentity.UNDEFINED); - await Then.theResultShouldBeASuccess(); + await Then.theResultShouldBeAnErrorWith({ + message: "The owner of the provided IdentityAttribute for the `attribute` property can only be an empty string. It will default to the Recipient's Address." + }); }); test("returns Success when passing a Relationship Attribute with owner={{Recipient}}", async function () { - const identityAttributeOfRecipient = TestObjectFactory.createRelationshipAttribute({ + const relationshipAttributeOfRecipient = TestObjectFactory.createRelationshipAttribute({ owner: TestIdentity.RECIPIENT }); - await When.iCallCanCreateOutgoingRequestItemWith({ attribute: identityAttributeOfRecipient }); + await When.iCallCanCreateOutgoingRequestItemWith({ attribute: relationshipAttributeOfRecipient }); await Then.theResultShouldBeASuccess(); }); test("returns Success when passing a Relationship Attribute with owner={{Sender}}", async function () { - const identityAttributeOfSender = TestObjectFactory.createRelationshipAttribute({ + const relationshipAttributeOfSender = TestObjectFactory.createRelationshipAttribute({ owner: TestIdentity.SENDER }); - await When.iCallCanCreateOutgoingRequestItemWith({ attribute: identityAttributeOfSender }); + await When.iCallCanCreateOutgoingRequestItemWith({ attribute: relationshipAttributeOfSender }); await Then.theResultShouldBeASuccess(); }); test("returns Success when passing a Relationship Attribute with owner={{Empty}}", async function () { - const identityAttributeWithEmptyOwner = TestObjectFactory.createRelationshipAttribute({ + const relationshipAttributeWithEmptyOwner = TestObjectFactory.createRelationshipAttribute({ owner: TestIdentity.EMPTY }); - await When.iCallCanCreateOutgoingRequestItemWith({ attribute: identityAttributeWithEmptyOwner }); + await When.iCallCanCreateOutgoingRequestItemWith({ attribute: relationshipAttributeWithEmptyOwner }); await Then.theResultShouldBeASuccess(); }); test("returns an Error when passing a Relationship Attribute with owner={{SomeoneElse}}", async function () { - const identityAttributeOfSomeoneElse = TestObjectFactory.createRelationshipAttribute({ + const relationshipAttributeOfSomeoneElse = TestObjectFactory.createRelationshipAttribute({ owner: TestIdentity.SOMEONE_ELSE }); - await When.iCallCanCreateOutgoingRequestItemWith({ attribute: identityAttributeOfSomeoneElse }); + await When.iCallCanCreateOutgoingRequestItemWith({ attribute: relationshipAttributeOfSomeoneElse }); await Then.theResultShouldBeAnErrorWith({ message: - /The owner of the given 'attribute' can only be the sender's address, the recipient's address or an empty string. The latter will default to the recipient's address./ + "The owner of the provided RelationshipAttribute for the `attribute` property can only be the Sender's Address, the Recipient's Address or an empty string. The latter will default to the Recipient's Address." }); }); - test("returns Success when passing a Relationship Attribute with owner={{SomeoneElse}}, but no recipient", async function () { - const identityAttributeOfSomeoneElse = TestObjectFactory.createRelationshipAttribute({ + test("returns Success when passing a Relationship Attribute with owner={{SomeoneElse}}, but no Recipient", async function () { + const relationshipAttributeOfSomeoneElse = TestObjectFactory.createRelationshipAttribute({ owner: TestIdentity.SOMEONE_ELSE }); - await When.iCallCanCreateOutgoingRequestItemWith({ attribute: identityAttributeOfSomeoneElse }, TestIdentity.UNDEFINED); - await Then.theResultShouldBeASuccess(); + await When.iCallCanCreateOutgoingRequestItemWith({ attribute: relationshipAttributeOfSomeoneElse }, TestIdentity.UNDEFINED); + await Then.theResultShouldBeAnErrorWith({ + message: + "The owner of the provided RelationshipAttribute for the `attribute` property can only be the Sender's Address or an empty string. The latter will default to the Recipient's Address." + }); }); }); diff --git a/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts index bfc184dcd..fcfc85690 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts @@ -8,6 +8,7 @@ import { ProposeAttributeAcceptResponseItem, ProposeAttributeRequestItem, ProprietaryString, + RelationshipAttribute, RelationshipAttributeConfidentiality, RelationshipAttributeQuery, Request, @@ -57,7 +58,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { describe("canCreateOutgoingRequestItem", function () { test("returns success when proposing an Identity Attribute", function () { - const recipientAddress = CoreAddress.from("recipientAddress"); + const recipient = CoreAddress.from("Recipient"); const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: false, @@ -70,57 +71,57 @@ describe("ProposeAttributeRequestItemProcessor", function () { }) }); - const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), recipientAddress); + const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), recipient); expect(result).successfulValidationResult(); }); test("returns success when proposing a Relationship Attribute", function () { - const recipientAddress = CoreAddress.from("recipientAddress"); + const recipient = CoreAddress.from("Recipient"); const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: false, attribute: TestObjectFactory.createRelationshipAttribute({ - value: ProprietaryString.from({ title: "A Title", value: "AGivenName" }), + value: ProprietaryString.from({ title: "ATitle", value: "AGivenName" }), owner: CoreAddress.from("") }), query: RelationshipAttributeQuery.from({ - key: "aKey", + key: "AKey", owner: CoreAddress.from(""), attributeCreationHints: { valueType: "ProprietaryString", - title: "aTitle", + title: "ATitle", confidentiality: RelationshipAttributeConfidentiality.Public } }) }); - const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), recipientAddress); + const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), recipient); expect(result).successfulValidationResult(); }); test("returns an error when passing anything other than an empty string as an owner into 'attribute'", function () { - const recipientAddress = CoreAddress.from("recipientAddress"); + const recipient = CoreAddress.from("Recipient"); const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: false, attribute: TestObjectFactory.createRelationshipAttribute({ - value: ProprietaryString.from({ title: "A Title", value: "AGivenName" }), - owner: recipientAddress + value: ProprietaryString.from({ title: "ATitle", value: "AGivenName" }), + owner: recipient }), query: RelationshipAttributeQuery.from({ - key: "aKey", + key: "AKey", owner: CoreAddress.from(""), attributeCreationHints: { valueType: "ProprietaryString", - title: "aTitle", + title: "ATitle", confidentiality: RelationshipAttributeConfidentiality.Public } }) }); - const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), recipientAddress); + const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), recipient); expect(result).errorValidationResult({ code: "error.consumption.requests.invalidRequestItem" @@ -128,26 +129,26 @@ describe("ProposeAttributeRequestItemProcessor", function () { }); test("returns an error when passing anything other than an empty string as an owner into 'query'", function () { - const recipientAddress = CoreAddress.from("recipientAddress"); + const recipient = CoreAddress.from("Recipient"); const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: false, attribute: TestObjectFactory.createRelationshipAttribute({ - value: ProprietaryString.from({ title: "A Title", value: "AGivenName" }), + value: ProprietaryString.from({ title: "ATitle", value: "AGivenName" }), owner: CoreAddress.from("") }), query: RelationshipAttributeQuery.from({ - key: "aKey", - owner: recipientAddress, + key: "AKey", + owner: recipient, attributeCreationHints: { valueType: "ProprietaryString", - title: "aTitle", + title: "ATitle", confidentiality: RelationshipAttributeConfidentiality.Public } }) }); - const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), recipientAddress); + const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), recipient); expect(result).errorValidationResult({ code: "error.consumption.requests.invalidRequestItem" @@ -157,6 +158,8 @@ describe("ProposeAttributeRequestItemProcessor", function () { describe("query", function () { describe("IdentityAttributeQuery", function () { test("simple query", function () { + const recipient = CoreAddress.from("Recipient"); + const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: false, attribute: TestObjectFactory.createIdentityAttribute({ @@ -167,7 +170,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { }) }); - const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), CoreAddress.from("recipientAddress")); + const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), recipient); expect(result).successfulValidationResult(); }); @@ -175,9 +178,11 @@ describe("ProposeAttributeRequestItemProcessor", function () { describe("RelationshipAttributeQuery", function () { test("simple query", function () { + const recipient = CoreAddress.from("Recipient"); + const query = RelationshipAttributeQuery.from({ owner: "", - key: "aKey", + key: "AKey", attributeCreationHints: { valueType: "ProprietaryString", title: "ATitle", @@ -189,12 +194,12 @@ describe("ProposeAttributeRequestItemProcessor", function () { mustBeAccepted: false, query: query, attribute: TestObjectFactory.createRelationshipAttribute({ - value: ProprietaryString.fromAny({ title: "aTitle", value: "AGivenName" }), + value: ProprietaryString.fromAny({ title: "ATitle", value: "AGivenName" }), owner: CoreAddress.from("") }) }); - const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), CoreAddress.from("recipientAddress")); + const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), recipient); expect(result).successfulValidationResult(); }); @@ -203,12 +208,12 @@ describe("ProposeAttributeRequestItemProcessor", function () { }); describe("canAccept", function () { test("returns success when called with the id of an existing own LocalAttribute", async function () { - const senderAddress = CoreAddress.from("recipientAddress"); - const recipientAddress = accountController.identity.address; + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; const existingLocalAttribute = await consumptionController.attributes.createLocalAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: recipient }) }); @@ -216,7 +221,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { mustBeAccepted: false, attribute: TestObjectFactory.createIdentityAttribute({ value: GivenName.fromAny({ value: "AGivenName" }), - owner: recipientAddress + owner: CoreAddress.from("") }), query: IdentityAttributeQuery.from({ valueType: "GivenName" @@ -227,7 +232,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: senderAddress, + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -247,6 +252,9 @@ describe("ProposeAttributeRequestItemProcessor", function () { }); test("returns success when called with a new own Attribute", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: true, query: IdentityAttributeQuery.from({ valueType: "GivenName" }), @@ -257,7 +265,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -270,7 +278,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { accept: true, attribute: { "@type": "IdentityAttribute", - owner: accountController.identity.address.toString(), + owner: recipient.toString(), value: { "@type": "GivenName", value: "AGivenName" @@ -283,7 +291,9 @@ describe("ProposeAttributeRequestItemProcessor", function () { expect(result).successfulValidationResult(); }); - test("returns success when called with a the proposed Attribute", async function () { + test("returns success when called with the proposed Attribute", async function () { + const sender = CoreAddress.from("Sender"); + const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: true, query: IdentityAttributeQuery.from({ valueType: "GivenName" }), @@ -298,7 +308,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -318,6 +328,8 @@ describe("ProposeAttributeRequestItemProcessor", function () { }); test("returns an error when the given Attribute id does not exist", async function () { + const sender = CoreAddress.from("Sender"); + const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: true, query: IdentityAttributeQuery.from({ valueType: "GivenName" }), @@ -328,7 +340,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -349,31 +361,31 @@ describe("ProposeAttributeRequestItemProcessor", function () { }); }); - test("returns an error when the given Attribute id belongs to a peer Attribute", async function () { - const someOtherIdentity = CoreAddress.from("did:e:a-domain:dids:anidentity"); - - const idOfAttributeOfOtherIdentity = await ConsumptionIds.attribute.generate(); + test("returns an error when the existing IdentityAttribute is already shared", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; - await consumptionController.attributes.createPeerLocalAttribute({ - id: idOfAttributeOfOtherIdentity, + const attribute = await consumptionController.attributes.createPeerLocalAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: someOtherIdentity + owner: recipient }), - peer: someOtherIdentity, + peer: sender, requestReference: await ConsumptionIds.request.generate() }); const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: true, query: IdentityAttributeQuery.from({ valueType: "GivenName" }), - attribute: TestObjectFactory.createIdentityAttribute() + attribute: TestObjectFactory.createIdentityAttribute({ + owner: CoreAddress.from("") + }) }); const requestId = await ConsumptionIds.request.generate(); const request = LocalRequest.from({ id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: accountController.identity.address, + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -382,33 +394,59 @@ describe("ProposeAttributeRequestItemProcessor", function () { statusLog: [] }); - const acceptParams: AcceptProposeAttributeRequestItemParametersJSON = { + const acceptParams: AcceptProposeAttributeRequestItemParametersWithExistingAttributeJSON = { accept: true, - attributeId: idOfAttributeOfOtherIdentity.toString() + attributeId: attribute.id.toString() }; const result = await processor.canAccept(requestItem, acceptParams, request); expect(result).errorValidationResult({ - code: "error.consumption.requests.invalidAcceptParameters", - message: /The given Attribute belongs to someone else. You can only share own Attributes./ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided IdentityAttribute is a shared copy of a RepositoryAttribute. You can only share RepositoryAttributes." }); }); - test("returns an error when the given Attribute's owner is not the current identity", async function () { - const someOtherIdentity = CoreAddress.from("did:e:a-domain:dids:anidentity"); + test("returns an error when a successor of the existing IdentityAttribute is already shared", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + + const repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + content: TestObjectFactory.createIdentityAttribute({ + owner: recipient + }) + }); + + const { successor: successorOfRepositoryAttribute } = await consumptionController.attributes.succeedRepositoryAttribute(repositoryAttribute.id, { + content: { + "@type": "IdentityAttribute", + owner: recipient.toString(), + value: { + "@type": "GivenName", + value: "AnotherGivenName" + } + } + }); + + const ownSharedCopyOfSuccessor = await consumptionController.attributes.createSharedLocalAttributeCopy({ + sourceAttributeId: successorOfRepositoryAttribute.id, + peer: sender, + requestReference: await ConsumptionIds.request.generate() + }); const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: true, query: IdentityAttributeQuery.from({ valueType: "GivenName" }), - attribute: TestObjectFactory.createIdentityAttribute() + attribute: TestObjectFactory.createIdentityAttribute({ + owner: CoreAddress.from("") + }) }); const requestId = await ConsumptionIds.request.generate(); const request = LocalRequest.from({ id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: accountController.identity.address, + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -417,25 +455,96 @@ describe("ProposeAttributeRequestItemProcessor", function () { statusLog: [] }); - const acceptParams: AcceptProposeAttributeRequestItemParametersJSON = { + const acceptParams: AcceptProposeAttributeRequestItemParametersWithExistingAttributeJSON = { accept: true, - attribute: TestObjectFactory.createIdentityAttribute({ owner: someOtherIdentity }).toJSON() + attributeId: repositoryAttribute.id.toString() + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: `The provided IdentityAttribute is outdated. You have already shared the successor '${ownSharedCopyOfSuccessor.shareInfo?.sourceAttribute?.toString()}' of it.` + }); + }); + + test("returns an error when a RelationshipAttribute was queried and the Recipient tries to respond with an existing RelationshipAttribute", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + + const requestItem = ProposeAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: recipient.toString(), + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }), + attribute: RelationshipAttribute.from({ + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: recipient, + value: ProprietaryString.from({ + title: "ATitle", + value: "AStringValue" + }) + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: recipient, + value: ProprietaryString.from({ + title: "ATitle", + value: "AnotherStringValue" + }) + }), + shareInfo: { + peer: sender, + requestReference: await ConsumptionIds.request.generate() + } + }); + + const acceptParams: AcceptProposeAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + attributeId: localAttribute.id.toString() }; const result = await processor.canAccept(requestItem, acceptParams, request); expect(result).errorValidationResult({ code: "error.consumption.requests.invalidAcceptParameters", - message: /The given Attribute belongs to someone else. You can only share own Attributes./ + message: "When responding to a RelationshipAttributeQuery, only new RelationshipAttributes may be provided." }); }); }); describe("accept", function () { - test("accept with existing Attribute that wasn't shared before", async function () { + test("accept with existing RepositoryAttribute", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + const attribute = await consumptionController.attributes.createLocalAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: recipient }) }); @@ -449,7 +558,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -471,7 +580,10 @@ describe("ProposeAttributeRequestItemProcessor", function () { expect(createdAttribute!.shareInfo!.peer.toString()).toStrictEqual(incomingRequest.peer.toString()); }); - test("accept proposed Attribute", async function () { + test("accept proposed IdentityAttribute", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: true, query: IdentityAttributeQuery.from({ valueType: "GivenName" }), @@ -486,7 +598,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -506,9 +618,13 @@ describe("ProposeAttributeRequestItemProcessor", function () { expect(createdAttribute).toBeDefined(); expect(createdAttribute!.shareInfo).toBeDefined(); expect(createdAttribute!.shareInfo!.peer.toString()).toStrictEqual(incomingRequest.peer.toString()); + expect(createdAttribute!.content.owner.toString()).toStrictEqual(recipient.toString()); }); - test("accept with new IdentityAttribute", async function () { + test("in case of accepting with a new IdentityAttribute, create a new RepositoryAttribute as well as a copy of it for the Recipient", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: true, query: IdentityAttributeQuery.from({ valueType: "GivenName" }), @@ -519,7 +635,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -532,7 +648,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { accept: true, attribute: { "@type": "IdentityAttribute", - owner: accountController.identity.address.toString(), + owner: recipient.toString(), value: { "@type": "GivenName", value: "AGivenName" @@ -553,12 +669,14 @@ describe("ProposeAttributeRequestItemProcessor", function () { }); test("accept with new RelationshipAttribute", async function () { - const senderAddress = accountController.identity.address; + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + const requestItem = ProposeAttributeRequestItem.from({ mustBeAccepted: true, query: RelationshipAttributeQuery.from({ - key: "aKey", - owner: senderAddress, + key: "AKey", + owner: "", attributeCreationHints: { valueType: "ProprietaryString", title: "ATitle", @@ -572,7 +690,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: senderAddress, + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -587,10 +705,10 @@ describe("ProposeAttributeRequestItemProcessor", function () { "@type": "RelationshipAttribute", key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, - owner: senderAddress.toString(), + owner: "", value: { "@type": "ProprietaryString", - title: "aTitle", + title: "ATitle", value: "AStringValue" } } @@ -603,6 +721,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { expect(createdSharedAttribute!.shareInfo).toBeDefined(); expect(createdSharedAttribute!.shareInfo!.peer.toString()).toStrictEqual(incomingRequest.peer.toString()); expect(createdSharedAttribute!.shareInfo!.sourceAttribute).toBeUndefined(); + expect(createdSharedAttribute!.content.owner.toString()).toStrictEqual(recipient.toString()); }); test("accept with existing IdentityAttribute whose predecessor was already shared", async function () { diff --git a/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts index fbb3b99b2..f80a12d8a 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts @@ -4,6 +4,8 @@ import { AttributeSuccessionAcceptResponseItem, IdentityAttribute, IdentityAttributeQuery, + IQLQuery, + ProprietaryString, ReadAttributeAcceptResponseItem, ReadAttributeRequestItem, RelationshipAttribute, @@ -11,7 +13,8 @@ import { RelationshipAttributeQuery, Request, ResponseItemResult, - ThirdPartyRelationshipAttributeQuery + ThirdPartyRelationshipAttributeQuery, + ThirdPartyRelationshipAttributeQueryOwner } from "@nmshd/content"; import { AccountController, CoreAddress, CoreDate, CoreId, Transport } from "@nmshd/transport"; import { @@ -67,7 +70,7 @@ describe("ReadAttributeRequestItemProcessor", function () { query: query }); - const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), CoreAddress.from("recipientAddress")); + const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), CoreAddress.from("recipient")); expect(result).successfulValidationResult(); }); @@ -77,6 +80,7 @@ describe("ReadAttributeRequestItemProcessor", function () { enum TestIdentity { Self, Recipient, + Empty, OtherWithRelationship, OtherWithoutRelationship, ThirdParty @@ -105,6 +109,37 @@ describe("ReadAttributeRequestItemProcessor", function () { success: true } }, + { + description: "query with owner=empty string, owner will become the Recipient later on", + input: { + owner: TestIdentity.Empty + }, + expectedOutput: { + success: true + } + }, + { + description: "cannot query with owner=Recipient", + input: { + owner: TestIdentity.Recipient + }, + expectedOutput: { + errorCode: "error.consumption.requests.invalidRequestItem", + errorMessage: + "The owner of the given `query` can only be an empty string or yourself. This is because you can only request RelationshipAttributes using a ReadAttributeRequestitem with a RelationshipAttributeQuery where the Recipient of the Request or yourself is the owner. And in order to avoid mistakes, the Recipient automatically will become the owner of the RelationshipAttribute later on if the owner of the `query` is an empty string." + } + }, + { + description: "cannot query with owner=thirdParty", + input: { + owner: TestIdentity.ThirdParty + }, + expectedOutput: { + errorCode: "error.consumption.requests.invalidRequestItem", + errorMessage: + "The owner of the given `query` can only be an empty string or yourself. This is because you can only request RelationshipAttributes using a ReadAttributeRequestitem with a RelationshipAttributeQuery where the Recipient of the Request or yourself is the owner. And in order to avoid mistakes, the Recipient automatically will become the owner of the RelationshipAttribute later on if the owner of the `query` is an empty string." + } + }, { description: "query with owner=thirdParty=someThirdParty, used for e.g. the bonuscard-number of a different company", input: { @@ -116,20 +151,19 @@ describe("ReadAttributeRequestItemProcessor", function () { } }, { - description: "cannot query own attributes from third party", + description: "can query with thirdParty = empty string", input: { - owner: TestIdentity.Self, - thirdParty: TestIdentity.ThirdParty + owner: TestIdentity.ThirdParty, + thirdParty: TestIdentity.Empty }, expectedOutput: { - errorCode: "error.consumption.requests.invalidRequestItem", - errorMessage: "Cannot query own Attributes from a third party." + success: true } }, { description: "cannot query with thirdParty = self", input: { - owner: TestIdentity.Self, + owner: TestIdentity.Recipient, thirdParty: TestIdentity.Self }, expectedOutput: { @@ -155,13 +189,15 @@ describe("ReadAttributeRequestItemProcessor", function () { case TestIdentity.Self: return accountController.identity.address.toString(); case TestIdentity.Recipient: - return CoreAddress.from("recipientAddress").toString(); + return CoreAddress.from("recipient").toString(); + case TestIdentity.Empty: + return CoreAddress.from("").toString(); case TestIdentity.OtherWithRelationship: - return CoreAddress.from("recipientAddress").toString(); + return CoreAddress.from("recipient").toString(); case TestIdentity.OtherWithoutRelationship: return "someAddressWithoutRelationship"; case TestIdentity.ThirdParty: - return "someThirdPartyAddress"; + return "thirdParty"; default: throw new Error("Given TestIdentity does not exist"); } @@ -171,13 +207,13 @@ describe("ReadAttributeRequestItemProcessor", function () { if (testParams.input.thirdParty !== undefined) { query = ThirdPartyRelationshipAttributeQuery.from({ owner: translateTestIdentityToAddress(testParams.input.owner), - key: "aKey", + key: "AKey", thirdParty: [translateTestIdentityToAddress(testParams.input.thirdParty)] }); } else { query = RelationshipAttributeQuery.from({ owner: translateTestIdentityToAddress(testParams.input.owner), - key: "aKey", + key: "AKey", attributeCreationHints: { valueType: "ProprietaryString", title: "ATitle", @@ -191,7 +227,7 @@ describe("ReadAttributeRequestItemProcessor", function () { query: query }); - const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), CoreAddress.from("recipientAddress")); + const result = processor.canCreateOutgoingRequestItem(requestItem, Request.from({ items: [requestItem] }), CoreAddress.from("recipient")); if (testParams.expectedOutput.hasOwnProperty("success")) { // eslint-disable-next-line jest/no-conditional-expect @@ -210,12 +246,13 @@ describe("ReadAttributeRequestItemProcessor", function () { describe("canAccept", function () { test("can be called with the id of an existing own LocalAttribute", async function () { - const attribute = await consumptionController.attributes.createPeerLocalAttribute({ + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + + const attribute = await consumptionController.attributes.createLocalAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) - }), - peer: CoreAddress.from(accountController.identity.address), - requestReference: CoreId.from("someRequestReference") + owner: recipient + }) }); const requestItem = ReadAttributeRequestItem.from({ @@ -227,7 +264,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -247,6 +284,9 @@ describe("ReadAttributeRequestItemProcessor", function () { }); test("can be called with a new Attribute", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + const requestItem = ReadAttributeRequestItem.from({ mustBeAccepted: true, query: IdentityAttributeQuery.from({ valueType: "GivenName" }) @@ -256,7 +296,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -269,11 +309,12 @@ describe("ReadAttributeRequestItemProcessor", function () { accept: true, newAttribute: { "@type": "IdentityAttribute", - owner: accountController.identity.address.toString(), + owner: recipient.toString(), value: { "@type": "GivenName", value: "AGivenName" - } + }, + tags: ["ATag"] } }; @@ -283,18 +324,31 @@ describe("ReadAttributeRequestItemProcessor", function () { }); test("can be called with an existing RelationshipAttribute by a third party", async function () { + const sender = CoreAddress.from("Sender"); + const aThirdParty = CoreAddress.from("AThirdParty"); + const attribute = await consumptionController.attributes.createLocalAttribute({ - content: TestObjectFactory.createRelationshipAttribute({ - owner: CoreAddress.from("did:e:a-domain:dids:anidentity") - }) + content: RelationshipAttribute.from({ + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: aThirdParty, + value: ProprietaryString.from({ + title: "ATitle", + value: "AStringValue" + }) + }), + shareInfo: { + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() + } }); const requestItem = ReadAttributeRequestItem.from({ mustBeAccepted: true, query: ThirdPartyRelationshipAttributeQuery.from({ - key: "aKey", - owner: "did:e:a-domain:dids:anidentity", - thirdParty: ["did:e:a-domain:dids:anidentity"] + key: "AKey", + owner: ThirdPartyRelationshipAttributeQueryOwner.ThirdParty, + thirdParty: [aThirdParty.toString()] }) }); const requestId = await ConsumptionIds.request.generate(); @@ -302,7 +356,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -322,6 +376,8 @@ describe("ReadAttributeRequestItemProcessor", function () { }); test("returns an error when the given Attribute id does not exist", async function () { + const sender = CoreAddress.from("Sender"); + const requestItem = ReadAttributeRequestItem.from({ mustBeAccepted: true, query: IdentityAttributeQuery.from({ valueType: "GivenName" }) @@ -331,7 +387,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -352,57 +408,638 @@ describe("ReadAttributeRequestItemProcessor", function () { }); }); - test("returns an error when the given Attribute id belongs to a peer Attribute", async function () { - const peer = CoreAddress.from("did:e:a-domain:dids:anidentity"); + describe("canAccept ReadAttributeRequestitem with IdentityAttributeQuery", function () { + test("returns an error when the existing IdentityAttribute is already shared", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + + const attribute = await consumptionController.attributes.createPeerLocalAttribute({ + content: TestObjectFactory.createIdentityAttribute({ + owner: recipient + }), + peer: sender, + requestReference: await ConsumptionIds.request.generate() + }); - const peerAttributeId = await ConsumptionIds.attribute.generate(); + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ valueType: "GivenName" }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); - await consumptionController.attributes.createPeerLocalAttribute({ - id: peerAttributeId, - content: TestObjectFactory.createIdentityAttribute({ - owner: peer - }), - peer: peer, - requestReference: await ConsumptionIds.request.generate() + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: attribute.id.toString() + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided IdentityAttribute is a shared copy of a RepositoryAttribute. You can only share RepositoryAttributes." + }); }); - const requestItem = ReadAttributeRequestItem.from({ - mustBeAccepted: true, - query: IdentityAttributeQuery.from({ valueType: "GivenName" }) + test("returns an error when a successor of the existing IdentityAttribute is already shared", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + + const repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + content: TestObjectFactory.createIdentityAttribute({ + owner: recipient + }) + }); + + const { successor: successorOfRepositoryAttribute } = await consumptionController.attributes.succeedRepositoryAttribute(repositoryAttribute.id, { + content: { + "@type": "IdentityAttribute", + owner: recipient.toString(), + value: { + "@type": "GivenName", + value: "AnotherGivenName" + } + } + }); + + const ownSharedCopyOfSuccessor = await consumptionController.attributes.createSharedLocalAttributeCopy({ + sourceAttributeId: successorOfRepositoryAttribute.id, + peer: sender, + requestReference: await ConsumptionIds.request.generate() + }); + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ valueType: "GivenName" }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: repositoryAttribute.id.toString() + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: `The provided IdentityAttribute is outdated. You have already shared the successor '${ownSharedCopyOfSuccessor.shareInfo?.sourceAttribute?.toString()}' of it.` + }); }); - const requestId = await ConsumptionIds.request.generate(); - const request = LocalRequest.from({ - id: requestId, - createdAt: CoreDate.utc(), - isOwn: false, - peer: peer, - status: LocalRequestStatus.DecisionRequired, - content: Request.from({ + + test("can be called with property tags used in the IdentityAttributeQuery", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ tags: ["ATag"], valueType: "GivenName" }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ id: requestId, - items: [requestItem] - }), - statusLog: [] + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "IdentityAttribute", + owner: recipient.toString(), + tags: ["ATag", "AnotherTag"], + value: { + "@type": "GivenName", + value: "AGivenName" + } + } + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).successfulValidationResult(); }); + }); - const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { - accept: true, - existingAttributeId: peerAttributeId.toString() - }; + describe("canAccept ReadAttributeRequestitem with IQLQuery", function () { + test("can be called with property tags used in the IQLQuery", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; - const result = await processor.canAccept(requestItem, acceptParams, request); + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IQLQuery.from({ queryString: "GivenName", attributeCreationHints: { valueType: "GivenName", tags: ["tagA", "tagB", "tagC"] } }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); - expect(result).errorValidationResult({ - code: "error.consumption.requests.invalidAcceptParameters", - message: /The given Attribute belongs to someone else. You can only share own Attributes./ + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "IdentityAttribute", + owner: recipient.toString(), + tags: ["tagA", "tagD", "tagE"], + value: { + "@type": "GivenName", + value: "AGivenName" + } + } + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).successfulValidationResult(); + }); + }); + + describe("canAccept ReadAttributeRequestitem with RelationshipAttributeQuery", function () { + test("returns an error when a RelationshipAttribute was queried using a RelationshipAttributeQuery and the Recipient tries to respond with an existing RelationshipAttribute", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: recipient.toString(), + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: recipient, + value: ProprietaryString.from({ + title: "ATitle", + value: "AStringValue" + }) + }), + shareInfo: { + peer: sender, + requestReference: await ConsumptionIds.request.generate() + } + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: localAttribute.id.toString() + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.invalidAcceptParameters", + message: "When responding to a RelationshipAttributeQuery, only new RelationshipAttributes may be provided." + }); + }); + + test("can be called when a RelationshipAttribute of Value Type Consent is queried even though title and description is specified", async function () { + const sender = CoreAddress.from("Sender"); + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: sender.toString(), + key: "AKey", + attributeCreationHints: { + valueType: "Consent", + title: "ATitle", + description: "ADescription", + confidentiality: RelationshipAttributeConfidentiality.Private + } + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Private, + owner: sender.toString(), + value: { + "@type": "Consent", + consent: "AConsent" + } + } + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).successfulValidationResult(); + }); + + test("can be called with properties validFrom and validTo used in the query", async function () { + const sender = CoreAddress.from("Sender"); + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: sender.toString(), + key: "AKey", + validFrom: "2024-02-14T08:47:35.077Z", + validTo: "2024-02-14T09:35:12.824Z", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: sender.toString(), + validFrom: "2024-02-14T08:40:35.077Z", + validTo: "2024-02-14T09:35:12.824Z", + value: { + "@type": "ProprietaryString", + title: "ATitle", + value: "AStringValue" + } + } + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).successfulValidationResult(); + }); + }); + + describe("canAccept ReadAttributeRequestitem with ThirdPartyRelationshipAttributeQuery", function () { + test("returns an error when a RelationshipAttribute is a copy of a sourceAttribute that was queried using a ThirdPartyRelationshipAttributeQuery", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + const aThirdParty = CoreAddress.from("AThirdParty"); + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: ThirdPartyRelationshipAttributeQuery.from({ + owner: ThirdPartyRelationshipAttributeQueryOwner.Recipient, + key: "AKey", + thirdParty: [aThirdParty.toString()] + }) + }); + + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: recipient, + value: ProprietaryString.from({ + title: "ATitle", + value: "AStringValue" + }) + }), + shareInfo: { + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: CoreId.from("sourceAttributeId") + } + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: localAttribute.id.toString() + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "When responding to a ThirdPartyRelationshipAttributeQuery, only RelationshipAttributes that are not a copy of a sourceAttribute may be provided." + }); + }); + + test("returns an error when a RelationshipAttribute is not shared with one of the third parties that were queried using a ThirdPartyRelationshipAttributeQuery", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + const aThirdParty = CoreAddress.from("AThirdParty"); + const anUninvolvedThirdParty = CoreAddress.from("AnUninvolvedThirdParty"); + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: ThirdPartyRelationshipAttributeQuery.from({ + owner: ThirdPartyRelationshipAttributeQueryOwner.Recipient, + key: "AKey", + thirdParty: [aThirdParty.toString()] + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: recipient, + value: ProprietaryString.from({ + title: "ATitle", + value: "AStringValue" + }) + }), + shareInfo: { + peer: anUninvolvedThirdParty, + requestReference: await ConsumptionIds.request.generate() + } + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: localAttribute.id.toString() + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided RelationshipAttribute exists in the context of a Relationship with a third party that should not be involved." + }); + }); + + test("returns an error when a RelationshipAttribute was queried using a ThirdPartyRelationshipAttributeQuery and the Recipient tries to respond with a new RelationshipAttribute", async function () { + const sender = CoreAddress.from("Sender"); + const aThirdParty = CoreAddress.from("AThirdParty"); + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: ThirdPartyRelationshipAttributeQuery.from({ + owner: ThirdPartyRelationshipAttributeQueryOwner.ThirdParty, + key: "AKey", + thirdParty: [aThirdParty.toString()] + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: aThirdParty.toString(), + value: { + "@type": "ProprietaryString", + title: "ATitle", + value: "AStringValue" + } + } + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.invalidAcceptParameters", + message: "When responding to a ThirdPartyRelationshipAttributeQuery, only RelationshipAttributes that already exist may be provided." + }); + }); + + test("can be called with an arbitrary third party if the thirdParty string array of the ThirdPartyRelationshipAttributeQuery contains an empty string", async function () { + const sender = CoreAddress.from("Sender"); + const aThirdParty = CoreAddress.from("AThirdParty"); + const anUninvolvedThirdParty = CoreAddress.from("AnUninvolvedThirdParty"); + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: ThirdPartyRelationshipAttributeQuery.from({ + owner: "", + key: "AKey", + thirdParty: ["", aThirdParty.toString()] + }) + }); + + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: anUninvolvedThirdParty, + value: ProprietaryString.from({ + title: "ATitle", + value: "AStringValue" + }) + }), + shareInfo: { + peer: anUninvolvedThirdParty, + requestReference: await ConsumptionIds.request.generate() + } + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: localAttribute.id.toString() + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).successfulValidationResult(); + }); + + test("returns an error when the confidentiality of the existing RelationshipAttribute to be shared is private", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + const aThirdParty = CoreAddress.from("AThirdParty"); + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: ThirdPartyRelationshipAttributeQuery.from({ + owner: ThirdPartyRelationshipAttributeQueryOwner.Recipient, + key: "AKey", + thirdParty: [aThirdParty.toString()] + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Private, + owner: recipient, + value: ProprietaryString.from({ + title: "ATitle", + value: "AStringValue" + }) + }), + shareInfo: { + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() + } + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: localAttribute.id.toString() + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The confidentiality of the provided RelationshipAttribute is private. Therefore you are not allowed to share it." + }); }); }); }); describe("accept", function () { - test("accept with existing Attribute that wasn't shared before", async function () { + test("accept with existing RepositoryAttribute", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; const attribute = await consumptionController.attributes.createLocalAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: recipient }) }); @@ -415,7 +1052,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -439,6 +1076,8 @@ describe("ReadAttributeRequestItemProcessor", function () { }); test("accept with new IdentityAttribute", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; const requestItem = ReadAttributeRequestItem.from({ mustBeAccepted: true, query: IdentityAttributeQuery.from({ valueType: "GivenName" }) @@ -448,7 +1087,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: CoreAddress.from("did:e:a-domain:dids:anidentity"), + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -461,7 +1100,7 @@ describe("ReadAttributeRequestItemProcessor", function () { accept: true, newAttribute: { "@type": "IdentityAttribute", - owner: accountController.identity.address.toString(), + owner: recipient.toString(), value: { "@type": "GivenName", value: "AGivenName" @@ -483,12 +1122,13 @@ describe("ReadAttributeRequestItemProcessor", function () { }); test("accept with new RelationshipAttribute", async function () { - const senderAddress = accountController.identity.address; + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; const requestItem = ReadAttributeRequestItem.from({ mustBeAccepted: true, query: RelationshipAttributeQuery.from({ - key: "aKey", - owner: senderAddress, + key: "AKey", + owner: recipient, attributeCreationHints: { valueType: "ProprietaryString", title: "ATitle", @@ -501,7 +1141,7 @@ describe("ReadAttributeRequestItemProcessor", function () { id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: senderAddress, + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -516,10 +1156,10 @@ describe("ReadAttributeRequestItemProcessor", function () { "@type": "RelationshipAttribute", key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, - owner: senderAddress.toString(), + owner: recipient.toString(), value: { "@type": "ProprietaryString", - title: "aTitle", + title: "ATitle", value: "AStringValue" } } @@ -666,11 +1306,11 @@ describe("ReadAttributeRequestItemProcessor", function () { content: RelationshipAttribute.from({ value: { "@type": "ProprietaryString", - title: "A new title", - value: "A new value" + title: "ANewTitle", + value: "ANewValue" }, confidentiality: RelationshipAttributeConfidentiality.Public, - key: "aKey", + key: "AKey", owner: CoreAddress.from(accountController.identity.address) }), shareInfo: LocalAttributeShareInfo.from({ @@ -682,9 +1322,9 @@ describe("ReadAttributeRequestItemProcessor", function () { const requestItem = ReadAttributeRequestItem.from({ mustBeAccepted: true, query: ThirdPartyRelationshipAttributeQuery.from({ - key: "aKey", - owner: accountController.identity.address, - thirdParty: [thirdPartyAddress] + key: "AKey", + owner: ThirdPartyRelationshipAttributeQueryOwner.Recipient, + thirdParty: [thirdPartyAddress.toString()] }) }); @@ -745,11 +1385,11 @@ describe("ReadAttributeRequestItemProcessor", function () { content: RelationshipAttribute.from({ value: { "@type": "ProprietaryString", - title: "A new title", - value: "A new value" + title: "ANewTitle", + value: "ANewValue" }, confidentiality: RelationshipAttributeConfidentiality.Public, - key: "aKey", + key: "AKey", owner: thirdPartyAddress }), shareInfo: LocalAttributeShareInfo.from({ @@ -761,9 +1401,9 @@ describe("ReadAttributeRequestItemProcessor", function () { const requestItem = ReadAttributeRequestItem.from({ mustBeAccepted: true, query: ThirdPartyRelationshipAttributeQuery.from({ - key: "aKey", - owner: accountController.identity.address, - thirdParty: [thirdPartyAddress] + key: "AKey", + owner: ThirdPartyRelationshipAttributeQueryOwner.Recipient, + thirdParty: [thirdPartyAddress.toString()] }) }); @@ -808,13 +1448,12 @@ describe("ReadAttributeRequestItemProcessor", function () { query: IdentityAttributeQuery.from({ valueType: "GivenName" }) }); const requestId = await ConsumptionIds.request.generate(); - const peer = CoreAddress.from("did:e:a-domain:dids:anidentity"); const incomingRequest = LocalRequest.from({ id: requestId, createdAt: CoreDate.utc(), isOwn: false, - peer: peer, + peer: CoreAddress.from("Recipient"), status: LocalRequestStatus.DecisionRequired, content: Request.from({ id: requestId, @@ -828,7 +1467,7 @@ describe("ReadAttributeRequestItemProcessor", function () { result: ResponseItemResult.Accepted, attributeId: attributeId, attribute: TestObjectFactory.createIdentityAttribute({ - owner: peer + owner: CoreAddress.from("Recipient") }) }); @@ -914,9 +1553,9 @@ describe("ReadAttributeRequestItemProcessor", function () { const requestItem = ReadAttributeRequestItem.from({ mustBeAccepted: true, query: ThirdPartyRelationshipAttributeQuery.from({ - key: "aKey", - owner: thirdPartyAddress, - thirdParty: [thirdPartyAddress] + key: "AKey", + owner: ThirdPartyRelationshipAttributeQueryOwner.ThirdParty, + thirdParty: [thirdPartyAddress.toString()] }) }); const requestId = await ConsumptionIds.request.generate(); diff --git a/packages/consumption/test/modules/requests/itemProcessors/registerAttributeListener/RegisterAttributeListenerRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/registerAttributeListener/RegisterAttributeListenerRequestItemProcessor.test.ts index a4cee700e..e547b5a07 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/registerAttributeListener/RegisterAttributeListenerRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/registerAttributeListener/RegisterAttributeListenerRequestItemProcessor.test.ts @@ -40,8 +40,8 @@ describe("CreateAttributeRequestItemProcessor", function () { test("creates an AttributeListener and persists it to the DB", async function () { const requestItem = RegisterAttributeListenerRequestItem.from({ query: ThirdPartyRelationshipAttributeQuery.from({ - key: "aKey", - owner: "anOwner", + key: "AKey", + owner: "", thirdParty: ["aThirdParty"] }), mustBeAccepted: true diff --git a/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts index 3423df852..49079c989 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts @@ -1,9 +1,9 @@ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; import { GivenName, + IdentityAttribute, IIdentityAttribute, IRelationshipAttribute, - IdentityAttribute, ProprietaryString, RelationshipAttribute, RelationshipAttributeConfidentiality, @@ -52,7 +52,7 @@ describe("ShareAttributeRequestItemProcessor", function () { result: "success", attribute: IdentityAttribute.from({ value: GivenName.fromAny({ value: "AGivenName" }), - owner: CoreAddress.from("{{sender}}") + owner: CoreAddress.from("Sender") }) }, { @@ -68,8 +68,7 @@ describe("ShareAttributeRequestItemProcessor", function () { result: "error", expectedError: { code: "error.consumption.requests.invalidRequestItem", - message: - /The owner of the given `attribute` can only be an empty string. This is because you can only send Attributes where the recipient of the Request is the owner anyway. And in order to avoid mistakes, the owner will be automatically filled for you./ + message: "The provided IdentityAttribute belongs to someone else. You can only share own IdentityAttributes." }, attribute: IdentityAttribute.from({ value: GivenName.fromAny({ value: "AGivenName" }), @@ -80,78 +79,95 @@ describe("ShareAttributeRequestItemProcessor", function () { scenario: "a Relationship Attribute with owner=sender", result: "success", attribute: RelationshipAttribute.from({ - value: ProprietaryString.fromAny({ value: "AGivenName", title: "aTitle" }), - owner: CoreAddress.from("{{sender}}"), + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + owner: CoreAddress.from("Sender"), confidentiality: RelationshipAttributeConfidentiality.Public, - key: "aKey" + key: "AKey" }) }, { scenario: "a Relationship Attribute with owner=", result: "success", attribute: RelationshipAttribute.from({ - value: ProprietaryString.fromAny({ value: "AGivenName", title: "aTitle" }), - owner: CoreAddress.from("{{sender}}"), + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + owner: CoreAddress.from("Sender"), confidentiality: RelationshipAttributeConfidentiality.Public, - key: "aKey" + key: "AKey" }) }, { scenario: "a Relationship Attribute with owner=someOtherOwner", result: "success", attribute: RelationshipAttribute.from({ - value: ProprietaryString.fromAny({ value: "AGivenName", title: "aTitle" }), - owner: CoreAddress.from("{{sender}}"), + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + owner: CoreAddress.from("Sender"), confidentiality: RelationshipAttributeConfidentiality.Public, - key: "aKey" + key: "AKey" }) }, { scenario: "a Relationship Attribute with confidentiality=private", result: "error", attribute: RelationshipAttribute.from({ - value: ProprietaryString.fromAny({ value: "AGivenName", title: "aTitle" }), - owner: CoreAddress.from("{{sender}}"), + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + owner: CoreAddress.from("Sender"), confidentiality: RelationshipAttributeConfidentiality.Private, - key: "aKey" + key: "AKey" }), expectedError: { code: "error.consumption.requests.invalidRequestItem", - message: /The confidentiality of the given `attribute` is private. Therefore you are not allowed to share it./ + message: "The confidentiality of the given `attribute` is private. Therefore you are not allowed to share it." } }, { scenario: "a Relationship Attribute with owner=recipient", result: "error", attribute: RelationshipAttribute.from({ - value: ProprietaryString.fromAny({ value: "AGivenName", title: "aTitle" }), - owner: CoreAddress.from("{{recipient}}"), + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + owner: CoreAddress.from("Recipient"), confidentiality: RelationshipAttributeConfidentiality.Public, - key: "aKey" + key: "AKey" }), expectedError: { code: "error.consumption.requests.invalidRequestItem", - message: /It doesn't make sense to share a RelationshipAttribute with its owner./ + message: "It doesn't make sense to share a RelationshipAttribute with its owner." } } ])("returns ${value.result} when passing ${value.scenario}", async function (testParams) { - const senderAddress = testAccount.identity.address; - const recipientAddress = CoreAddress.from("recipientAddress"); + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + const aThirdParty = CoreAddress.from("AThirdParty"); - if (testParams.attribute.owner.address === "{{sender}}") { - testParams.attribute.owner = senderAddress; + if (testParams.attribute.owner.address === "Sender") { + testParams.attribute.owner = sender; } - if (testParams.attribute.owner.address === "{{recipient}}") { - testParams.attribute.owner = recipientAddress; + if (testParams.attribute.owner.address === "Recipient") { + testParams.attribute.owner = recipient; + } + + let sourceAttribute; + + if (testParams.attribute instanceof IdentityAttribute) { + sourceAttribute = await consumptionController.attributes.createLocalAttribute({ + content: { + ...testParams.attribute.toJSON(), + owner: testParams.attribute.owner.equals("") ? sender : testParams.attribute.owner + } as IIdentityAttribute + }); + } else { + sourceAttribute = await consumptionController.attributes.createLocalAttribute({ + content: { + ...testParams.attribute.toJSON(), + owner: testParams.attribute.owner.equals("") ? sender : testParams.attribute.owner + } as IRelationshipAttribute, + shareInfo: { + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() + } + }); } - const sourceAttribute = await consumptionController.attributes.createLocalAttribute({ - content: { - ...testParams.attribute.toJSON(), - owner: testParams.attribute.owner.equals("") ? senderAddress : testParams.attribute.owner - } as IIdentityAttribute | IRelationshipAttribute - }); const requestItem = ShareAttributeRequestItem.from({ mustBeAccepted: false, attribute: sourceAttribute.content, @@ -159,7 +175,7 @@ describe("ShareAttributeRequestItemProcessor", function () { }); const request = Request.from({ items: [requestItem] }); - const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipientAddress); + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); if (testParams.result === "success") { // eslint-disable-next-line jest/no-conditional-expect @@ -171,19 +187,20 @@ describe("ShareAttributeRequestItemProcessor", function () { }); test("returns error when the attribute doesn't exists", async function () { - const recipientAddress = CoreAddress.from("recipientAddress"); + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); const requestItem = ShareAttributeRequestItem.from({ mustBeAccepted: false, attribute: IdentityAttribute.from({ value: GivenName.fromAny({ value: "AGivenName" }), - owner: CoreAddress.from("{{sender}}") + owner: sender }), sourceAttributeId: CoreId.from("anIdThatDoesntExist") }); const request = Request.from({ items: [requestItem] }); - const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipientAddress); + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); expect(result).errorValidationResult({ code: "error.consumption.requests.invalidRequestItem", @@ -192,11 +209,12 @@ describe("ShareAttributeRequestItemProcessor", function () { }); test("returns error when the attribute content is not equal to the content persisted in the attribute collection", async function () { - const recipientAddress = CoreAddress.from("recipientAddress"); + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); const attribute = IdentityAttribute.from({ value: GivenName.fromAny({ value: "AGivenName" }), - owner: CoreAddress.from("{{sender}}") + owner: sender }); const sourceAttribute = await consumptionController.attributes.createLocalAttribute({ @@ -206,24 +224,253 @@ describe("ShareAttributeRequestItemProcessor", function () { mustBeAccepted: false, attribute: IdentityAttribute.from({ ...sourceAttribute.content.toJSON(), - value: Surname.from("aSurname").toJSON() + value: Surname.from("ASurname").toJSON() }), sourceAttributeId: sourceAttribute.id }); const request = Request.from({ items: [requestItem] }); - const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipientAddress); + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.invalidRequestItem", + message: `The Attribute with the given sourceAttributeId '${sourceAttribute.id.toString()}' does not match the given Attribute.` + }); + }); + + test("returns error when the IdentityAttribute is a shared copy of a RepositoryAttribute", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + const aThirdParty = CoreAddress.from("AThirdParty"); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: IdentityAttribute.from({ + owner: sender, + value: GivenName.from({ + value: "AGivenName" + }) + }), + shareInfo: { + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: CoreId.from("sourceAttributeId") + } + }); + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: false, + attribute: localAttribute.content, + sourceAttributeId: localAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.invalidRequestItem", + message: "The provided IdentityAttribute is a shared copy of a RepositoryAttribute. You can only share RepositoryAttributes." + }); + }); + + test("returns error when the IdentityAttribute is already shared with the peer", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: IdentityAttribute.from({ + owner: sender, + value: GivenName.from({ + value: "AGivenName" + }) + }) + }); + + const localAttributeCopy = await consumptionController.attributes.createLocalAttribute({ + content: IdentityAttribute.from({ + owner: sender, + value: GivenName.from({ + value: "AGivenName" + }) + }), + shareInfo: { + peer: recipient, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: localAttribute.id + } + }); + expect(localAttributeCopy.isShared()).toBe(true); + + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: false, + attribute: localAttribute.content, + sourceAttributeId: localAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.invalidRequestItem", + message: `The IdentityAttribute with the given sourceAttributeId '${requestItem.sourceAttributeId.toString()}' has already been shared with the peer.` + }); + }); + + test("returns an error when a successor of the existing IdentityAttribute is already shared", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + + const repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + content: TestObjectFactory.createIdentityAttribute({ + owner: sender + }) + }); + + const { successor: successorOfRepositoryAttribute } = await consumptionController.attributes.succeedRepositoryAttribute(repositoryAttribute.id, { + content: { + "@type": "IdentityAttribute", + owner: sender.toString(), + value: { + "@type": "GivenName", + value: "AnotherGivenName" + } + } + }); + + const ownSharedCopyOfSuccessor = await consumptionController.attributes.createSharedLocalAttributeCopy({ + sourceAttributeId: successorOfRepositoryAttribute.id, + peer: recipient, + requestReference: await ConsumptionIds.request.generate() + }); + + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: repositoryAttribute.content, + sourceAttributeId: repositoryAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); expect(result).errorValidationResult({ code: "error.consumption.requests.invalidRequestItem", - message: `The Attribute with the given sourceAttributeId '${sourceAttribute.id.toString()}' does not match the given attribute.` + message: `The provided IdentityAttribute is outdated. You have already shared the successor '${ownSharedCopyOfSuccessor.shareInfo?.sourceAttribute?.toString()}' of it.` + }); + }); + + test("returns an error when a predecessor of the existing IdentityAttribute is already shared and therefore the user should notify about the Attribute succession instead of share it.", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + + const repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + content: TestObjectFactory.createIdentityAttribute({ + owner: sender + }) + }); + + const ownSharedCopyOfPredecessor = await consumptionController.attributes.createSharedLocalAttributeCopy({ + sourceAttributeId: repositoryAttribute.id, + peer: recipient, + requestReference: await ConsumptionIds.request.generate() + }); + + expect(ownSharedCopyOfPredecessor.isShared()).toBe(true); + + const { successor: successorOfRepositoryAttribute } = await consumptionController.attributes.succeedRepositoryAttribute(repositoryAttribute.id, { + content: { + "@type": "IdentityAttribute", + owner: sender.toString(), + value: { + "@type": "GivenName", + value: "AnotherGivenName" + } + } + }); + + expect(successorOfRepositoryAttribute.isShared()).toBe(false); + + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: successorOfRepositoryAttribute.content, + sourceAttributeId: successorOfRepositoryAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.invalidRequestItem", + message: `You have already shared the predecessor '${ownSharedCopyOfPredecessor.shareInfo?.sourceAttribute?.toString()}' of the IdentityAttribute. Instead of sharing it, you should notify the peer about the Attribute succession.` + }); + }); + + test("returns error when the RelationshipAttribute is a copy of another RelationshipAttribute", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + const aThirdParty = CoreAddress.from("AThirdParty"); + + const relationshipAttribute = await consumptionController.attributes.createLocalAttribute({ + content: RelationshipAttribute.from({ + owner: sender, + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + confidentiality: RelationshipAttributeConfidentiality.Public, + key: "AKey" + }), + shareInfo: { + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: CoreId.from("sourceAttributeId") + } + }); + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: false, + attribute: relationshipAttribute.content, + sourceAttributeId: relationshipAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.invalidRequestItem", + message: "You can only share RelationshipAttributes that are not a copy of a sourceAttribute." + }); + }); + + test("returns error when the RelationshipAttribute exists in the context of the Relationship with the peer", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + + const relationshipAttribute = await consumptionController.attributes.createLocalAttribute({ + content: RelationshipAttribute.from({ + owner: sender, + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + confidentiality: RelationshipAttributeConfidentiality.Public, + key: "AKey" + }), + shareInfo: { + peer: recipient, + requestReference: await ConsumptionIds.request.generate() + } + }); + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: false, + attribute: relationshipAttribute.content, + sourceAttributeId: relationshipAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.invalidRequestItem", + message: "The provided RelationshipAttribute already exists in the context of the Relationship with the peer." }); }); }); describe("accept", function () { - test("in case of an IdentityAttribute with 'owner=', creates a Local Attribute for the sender of the Request", async function () { - const senderAddress = CoreAddress.from("SenderAddress"); + test("in case of an IdentityAttribute with 'owner=', creates a Local Attribute for the Sender of the Request", async function () { + const sender = CoreAddress.from("Sender"); + const requestItem = ShareAttributeRequestItem.from({ mustBeAccepted: true, sourceAttributeId: CoreId.from("aSourceAttributeId"), @@ -235,7 +482,7 @@ describe("ShareAttributeRequestItemProcessor", function () { id: await ConsumptionIds.request.generate(), createdAt: CoreDate.utc(), isOwn: false, - peer: senderAddress, + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ items: [requestItem] @@ -252,13 +499,14 @@ describe("ShareAttributeRequestItemProcessor", function () { const createdAttribute = await consumptionController.attributes.getLocalAttribute(result.attributeId); expect(createdAttribute).toBeDefined(); expect(createdAttribute!.shareInfo).toBeDefined(); - expect(createdAttribute!.shareInfo!.peer.toString()).toStrictEqual(senderAddress.toString()); + expect(createdAttribute!.shareInfo!.peer.toString()).toStrictEqual(sender.toString()); expect(createdAttribute!.shareInfo!.sourceAttribute).toBeUndefined(); - expect(createdAttribute!.content.owner.toString()).toStrictEqual(senderAddress.toString()); + expect(createdAttribute!.content.owner.toString()).toStrictEqual(sender.toString()); }); - test("in case of a RelationshipAttribute with 'owner=', creates a Local Attribute for the sender of the Request", async function () { - const senderAddress = CoreAddress.from("SenderAddress"); + test("in case of a RelationshipAttribute with 'owner=', creates a Local Attribute for the Sender of the Request", async function () { + const sender = CoreAddress.from("Sender"); + const requestItem = ShareAttributeRequestItem.from({ mustBeAccepted: true, sourceAttributeId: CoreId.from("aSourceAttributeId"), @@ -270,7 +518,7 @@ describe("ShareAttributeRequestItemProcessor", function () { id: await ConsumptionIds.request.generate(), createdAt: CoreDate.utc(), isOwn: false, - peer: senderAddress, + peer: sender, status: LocalRequestStatus.DecisionRequired, content: Request.from({ items: [requestItem] @@ -287,9 +535,9 @@ describe("ShareAttributeRequestItemProcessor", function () { const createdAttribute = await consumptionController.attributes.getLocalAttribute(result.attributeId); expect(createdAttribute).toBeDefined(); expect(createdAttribute!.shareInfo).toBeDefined(); - expect(createdAttribute!.shareInfo!.peer.toString()).toStrictEqual(senderAddress.toString()); + expect(createdAttribute!.shareInfo!.peer.toString()).toStrictEqual(sender.toString()); expect(createdAttribute!.shareInfo!.sourceAttribute).toBeUndefined(); - expect(createdAttribute!.content.owner.toString()).toStrictEqual(senderAddress.toString()); + expect(createdAttribute!.content.owner.toString()).toStrictEqual(sender.toString()); }); }); @@ -301,7 +549,7 @@ describe("ShareAttributeRequestItemProcessor", function () { }, { attributeType: "IdentityAttribute", - attributeOwner: "{{sender}}" + attributeOwner: "Sender" }, { attributeType: "RelationshipAttribute", @@ -309,7 +557,7 @@ describe("ShareAttributeRequestItemProcessor", function () { }, { attributeType: "RelationshipAttribute", - attributeOwner: "{{sender}}" + attributeOwner: "Sender" } ])( "in case of a ${value.attributeType}, creates a LocalAttribute with the Attribute from the RequestItem and the attributeId from the ResponseItem for the peer of the Request", @@ -324,7 +572,7 @@ describe("ShareAttributeRequestItemProcessor", function () { content: sourceAttributeContent }); - testParams.attributeOwner = testParams.attributeOwner.replace("{{sender}}", testAccount.identity.address.toString()); + testParams.attributeOwner = testParams.attributeOwner.replace("Sender", testAccount.identity.address.toString()); sourceAttribute.content.owner = CoreAddress.from(testParams.attributeOwner); diff --git a/packages/consumption/test/modules/requests/itemProcessors/utility/validateAttributeMatchesWithQuery.test.ts b/packages/consumption/test/modules/requests/itemProcessors/utility/validateAttributeMatchesWithQuery.test.ts new file mode 100644 index 000000000..74346575c --- /dev/null +++ b/packages/consumption/test/modules/requests/itemProcessors/utility/validateAttributeMatchesWithQuery.test.ts @@ -0,0 +1,1155 @@ +import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; +import { + IdentityAttributeQuery, + IQLQuery, + ProprietaryString, + ReadAttributeRequestItem, + RelationshipAttribute, + RelationshipAttributeConfidentiality, + RelationshipAttributeQuery, + Request, + ThirdPartyRelationshipAttributeQuery, + ThirdPartyRelationshipAttributeQueryOwner +} from "@nmshd/content"; +import { AccountController, CoreAddress, CoreDate, Transport } from "@nmshd/transport"; +import { + AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON, + AcceptReadAttributeRequestItemParametersWithNewAttributeJSON, + ConsumptionController, + ConsumptionIds, + LocalRequest, + LocalRequestStatus, + ReadAttributeRequestItemProcessor +} from "../../../../../src"; +import { TestUtil } from "../../../../core/TestUtil"; +import { TestObjectFactory } from "../../testHelpers/TestObjectFactory"; + +describe("validateAttributeMatchesWithQuery", function () { + let connection: IDatabaseConnection; + let transport: Transport; + + let consumptionController: ConsumptionController; + let accountController: AccountController; + + let readProcessor: ReadAttributeRequestItemProcessor; + + let recipient: CoreAddress; + const sender = CoreAddress.from("Sender"); + const aThirdParty = CoreAddress.from("AThirdParty"); + const anUninvolvedThirdParty = CoreAddress.from("AnUninvolvedThirdParty"); + beforeAll(async function () { + connection = await TestUtil.createConnection(); + transport = TestUtil.createTransport(connection); + + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 1); + ({ accountController, consumptionController } = accounts[0]); + recipient = accountController.identity.address; + }); + + afterAll(async function () { + await connection.close(); + }); + + describe("IdentityAttributeQuery", function () { + beforeEach(function () { + readProcessor = new ReadAttributeRequestItemProcessor(consumptionController); + }); + + test("returns an error when an IdentityAttribute was queried by an IdentityAttributeQuery and the peer tries to respond with a RelationshipAttribute", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ valueType: "GivenName" }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: recipient.toString(), + value: { + "@type": "ProprietaryString", + title: "ATitle", + value: "AStringValue" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided Attribute is not an IdentityAttribute, but an IdentityAttribute was queried." + }); + }); + + test("returns an error when the given Attribute id belongs to a peer Attribute", async function () { + const thirdPartyAttributeId = await ConsumptionIds.attribute.generate(); + await consumptionController.attributes.createPeerLocalAttribute({ + id: thirdPartyAttributeId, + content: TestObjectFactory.createIdentityAttribute({ + owner: aThirdParty + }), + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() + }); + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ valueType: "GivenName" }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: thirdPartyAttributeId.toString() + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided IdentityAttribute belongs to someone else. You can only share own IdentityAttributes." + }); + }); + + test("returns an error when the new IdentityAttribute to be created and shared belongs to a third party", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ valueType: "GivenName" }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "IdentityAttribute", + owner: aThirdParty.toString(), + value: { + "@type": "GivenName", + value: "AGivenName" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided IdentityAttribute belongs to someone else. You can only share own IdentityAttributes." + }); + }); + + test("returns an error when an IdentityAttribute of a specific type was queried by an IdentityAttributeQuery and the peer tries to respond with an IdentityAttribute of another type", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ valueType: "GivenName" }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "IdentityAttribute", + owner: recipient.toString(), + value: { + "@type": "DisplayName", + value: "ADisplayName" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided IdentityAttribute is not of the queried IdentityAttribute value type." + }); + }); + + test("returns an error when an IdentityAttribute has no tag but at least one tag was queried by IdentityAttributeQuery", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ valueType: "GivenName", tags: ["ATag"] }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "IdentityAttribute", + owner: recipient.toString(), + value: { + "@type": "GivenName", + value: "AGivenName" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The tags of the provided IdentityAttribute do not contain at least one queried tag." + }); + }); + + test("returns an error when the tags of the IdentityAttribute do not match the tags queried by IdentityAttributeQuery", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ tags: ["tagA", "tagB", "tagC"], valueType: "GivenName" }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "IdentityAttribute", + owner: recipient.toString(), + tags: ["tagD", "tagE", "tagF"], + value: { + "@type": "GivenName", + value: "AGivenName" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The tags of the provided IdentityAttribute do not contain at least one queried tag." + }); + }); + + test("returns an error when an IdentityAttribute is not valid in the queried time frame", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ validTo: "2024-02-14T09:35:12.824Z", valueType: "GivenName" }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "IdentityAttribute", + owner: recipient.toString(), + validFrom: "2024-02-14T08:47:35.077Z", + validTo: "2024-02-14T09:35:12.824Z", + value: { + "@type": "GivenName", + value: "AGivenName" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided Attribute is not valid in the queried time frame." + }); + }); + }); + + describe("IQLQuery", function () { + beforeEach(function () { + readProcessor = new ReadAttributeRequestItemProcessor(consumptionController); + }); + + test("returns an error when an IdentityAttribute was queried by an IQLQuery and the peer tries to respond with a RelationshipAttribute", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IQLQuery.from({ queryString: "GivenName", attributeCreationHints: { valueType: "GivenName" } }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: recipient.toString(), + value: { + "@type": "ProprietaryString", + title: "ATitle", + value: "AStringValue" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided Attribute is not an IdentityAttribute. Currently, only IdentityAttributes can be queried by an IQLQuery." + }); + }); + + test("returns an error when the IdentityAttribute queried by an IQLQuery is not owned by the Recipient", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IQLQuery.from({ queryString: "GivenName", attributeCreationHints: { valueType: "GivenName" } }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "IdentityAttribute", + owner: sender.toString(), + value: { + "@type": "GivenName", + value: "AGivenName" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided IdentityAttribute belongs to someone else. You can only share own IdentityAttributes." + }); + }); + }); + + describe("RelationshipAttributeQuery", function () { + beforeEach(function () { + readProcessor = new ReadAttributeRequestItemProcessor(consumptionController); + }); + + test("returns an error when a RelationshipAttribute was queried and the Recipient tries to respond with an IdentityAttribute", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: recipient.toString(), + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "IdentityAttribute", + owner: recipient.toString(), + value: { + "@type": "GivenName", + value: "AGivenName" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided Attribute is not a RelationshipAttribute, but a RelationshipAttribute was queried." + }); + }); + + test("returns an error when a RelationshipAttribute of a specific type was queried and the Recipient tries to respond with a RelationshipAttribute of another type", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: recipient.toString(), + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: recipient.toString(), + value: { + "@type": "ProprietaryInteger", + title: "ATitle", + value: 1 + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided RelationshipAttribute is not of the queried RelationshipAttribute value type." + }); + }); + + test("returns an error when a RelationshipAttribute does not belong to the queried owner", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: sender.toString(), + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: recipient.toString(), + value: { + "@type": "ProprietaryString", + title: "ATitle", + value: "AStringValue" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided RelationshipAttribute does not belong to the queried owner." + }); + }); + + test("returns an error when a RelationshipAttribute does not belong to the Recipient, but an empty string was specified for the owner of the query", async function () { + const sender = CoreAddress.from("Sender"); + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: "", + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: sender.toString(), + value: { + "@type": "ProprietaryString", + title: "ATitle", + value: "AStringValue" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "You are not the owner of the provided RelationshipAttribute, but an empty string was specified for the owner of the query." + }); + }); + + test("returns an error when a RelationshipAttribute does not have the queried key", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: sender.toString(), + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AnotherKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: sender.toString(), + value: { + "@type": "ProprietaryString", + title: "ATitle", + value: "AStringValue" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided RelationshipAttribute does not have the queried key." + }); + }); + + test("returns an error when a RelationshipAttribute does not have the queried confidentiality", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: sender.toString(), + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Protected + } + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Private, + owner: sender.toString(), + value: { + "@type": "ProprietaryString", + title: "ATitle", + value: "AStringValue" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided RelationshipAttribute does not have the queried confidentiality." + }); + }); + + test("returns an error when a RelationshipAttribute does not have the queried title", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: sender.toString(), + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + description: "ADescription", + confidentiality: RelationshipAttributeConfidentiality.Private + } + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Private, + owner: sender.toString(), + value: { + "@type": "ProprietaryString", + title: "AnotherTitle", + description: "ADescription", + value: "AStringValue" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided RelationshipAttribute does not have the queried title." + }); + }); + + test("returns an error when a RelationshipAttribute does not have the queried description", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: sender.toString(), + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + description: "ADescription", + confidentiality: RelationshipAttributeConfidentiality.Private + } + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Private, + owner: sender.toString(), + value: { + "@type": "ProprietaryString", + title: "ATitle", + value: "AStringValue" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided RelationshipAttribute does not have the queried description." + }); + }); + + test("returns an error when a RelationshipAttribute is not valid in the queried time frame", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: RelationshipAttributeQuery.from({ + owner: sender.toString(), + key: "AKey", + validFrom: "2024-02-14T08:47:35.077Z", + validTo: "2024-02-14T09:35:12.824Z", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "RelationshipAttribute", + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: sender.toString(), + validFrom: "2024-02-14T08:47:35.077Z", + validTo: "2024-02-14T09:30:00.000Z", + value: { + "@type": "ProprietaryString", + title: "ATitle", + value: "AStringValue" + } + } + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided Attribute is not valid in the queried time frame." + }); + }); + }); + + describe("ThirdPartyRelationshipAttributeQuery", function () { + beforeEach(function () { + readProcessor = new ReadAttributeRequestItemProcessor(consumptionController); + }); + + test("returns an error when a RelationshipAttribute was queried using a ThirdPartyRelationshipAttributeQuery and the Recipient tries to respond with an IdentityAttribute", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: ThirdPartyRelationshipAttributeQuery.from({ + owner: ThirdPartyRelationshipAttributeQueryOwner.Recipient, + key: "AKey", + thirdParty: [aThirdParty.toString()] + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: TestObjectFactory.createIdentityAttribute({ + owner: recipient + }) + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: localAttribute.id.toString() + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided Attribute is not a RelationshipAttribute, but a RelationshipAttribute was queried." + }); + }); + + test("returns an error when a RelationshipAttribute does not belong to the owner that was queried using a ThirdPartyRelationshipAttributeQuery", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: ThirdPartyRelationshipAttributeQuery.from({ + owner: ThirdPartyRelationshipAttributeQueryOwner.Recipient, + key: "AKey", + thirdParty: [aThirdParty.toString()] + }) + }); + + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: aThirdParty, + value: ProprietaryString.from({ + title: "ATitle", + value: "AStringValue" + }) + }), + shareInfo: { + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() + } + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: localAttribute.id.toString() + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided RelationshipAttribute does not belong to a queried owner." + }); + }); + + test("returns an error when a RelationshipAttribute that was queried by a ThirdPartyRelationshipAttributeQuery does not belong to the Recipient or one of the involved third parties, but an empty string was specified for the owner of the query", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: ThirdPartyRelationshipAttributeQuery.from({ + owner: ThirdPartyRelationshipAttributeQueryOwner.Empty, + key: "AKey", + thirdParty: [aThirdParty.toString()] + }) + }); + + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: anUninvolvedThirdParty, + value: ProprietaryString.from({ + title: "ATitle", + value: "AStringValue" + }) + }), + shareInfo: { + peer: anUninvolvedThirdParty, + requestReference: await ConsumptionIds.request.generate() + } + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: localAttribute.id.toString() + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: + "Neither you nor one of the involved third parties is the owner of the provided RelationshipAttribute, but an empty string was specified for the owner of the query." + }); + }); + + test("returns an error when a RelationshipAttribute does not have the key that was queried using a ThirdPartyRelationshipAttributeQuery", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: ThirdPartyRelationshipAttributeQuery.from({ + owner: ThirdPartyRelationshipAttributeQueryOwner.Recipient, + key: "AKey", + thirdParty: [aThirdParty.toString()] + }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AnotherKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: recipient, + value: ProprietaryString.from({ + title: "ATitle", + value: "AStringValue" + }) + }), + shareInfo: { + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() + } + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: localAttribute.id.toString() + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided RelationshipAttribute does not have the queried key." + }); + }); + + test("returns an error when a RelationshipAttribute is not valid in the time frame that was queried using a ThirdPartyRelationshipAttributeQuery", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: ThirdPartyRelationshipAttributeQuery.from({ + owner: ThirdPartyRelationshipAttributeQueryOwner.Recipient, + validFrom: "2024-02-14T08:47:35.077Z", + validTo: "2024-02-14T09:35:12.824Z", + key: "AKey", + thirdParty: [aThirdParty.toString()] + }) + }); + + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + confidentiality: RelationshipAttributeConfidentiality.Public, + owner: recipient, + validFrom: CoreDate.from("2024-02-14T08:47:35.077Z"), + validTo: CoreDate.from("2024-02-14T09:30:00.000Z"), + value: ProprietaryString.from({ + title: "ATitle", + value: "AStringValue" + }) + }), + shareInfo: { + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() + } + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { + accept: true, + existingAttributeId: localAttribute.id.toString() + }; + + const result = await readProcessor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.attributeQueryMismatch", + message: "The provided Attribute is not valid in the queried time frame." + }); + }); + }); +}); diff --git a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts index a84159a4b..a5c936bd9 100644 --- a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts +++ b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts @@ -171,9 +171,9 @@ export class TestObjectFactory { public static createRelationshipAttribute(properties?: Partial): RelationshipAttribute { return RelationshipAttribute.from({ - value: properties?.value ?? ProprietaryString.from({ title: "A Title", value: "AGivenName" }), + value: properties?.value ?? ProprietaryString.from({ title: "ATitle", value: "AProprietaryStringValue" }), confidentiality: RelationshipAttributeConfidentiality.Public, - key: "aKey", + key: "AKey", isTechnical: false, owner: properties?.owner ?? CoreAddress.from("did:e:a-domain:dids:anidentity") }); diff --git a/packages/content/src/attributes/AttributeValueTypes.ts b/packages/content/src/attributes/AttributeValueTypes.ts index fc962d68d..e8c63c020 100644 --- a/packages/content/src/attributes/AttributeValueTypes.ts +++ b/packages/content/src/attributes/AttributeValueTypes.ts @@ -166,7 +166,7 @@ import { ZipCodeJSON } from "./types"; -// ################################################ Editable Identity Attribute Value Types ################################################################### +// ################################################ Editable IdentityAttribute Value Types ################################################################### export module AttributeValues { export module Identity { diff --git a/packages/content/src/attributes/RelationshipAttributeQuery.ts b/packages/content/src/attributes/RelationshipAttributeQuery.ts index 7bde74088..ee7c4cbb4 100644 --- a/packages/content/src/attributes/RelationshipAttributeQuery.ts +++ b/packages/content/src/attributes/RelationshipAttributeQuery.ts @@ -4,7 +4,7 @@ import { AbstractAttributeQuery, AbstractAttributeQueryJSON, IAbstractAttributeQ import { AttributeValues } from "./AttributeValueTypes"; import { IValueHints, ValueHints, ValueHintsJSON } from "./hints"; import { RelationshipAttributeConfidentiality } from "./RelationshipAttributeConfidentiality"; -import { PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH } from "./types/proprietary/ProprietaryAttributeValue"; +import { PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH, PROPRIETARY_ATTRIBUTE_MAX_TITLE_LENGTH } from "./types/proprietary/ProprietaryAttributeValue"; export interface RelationshipAttributeCreationHintsJSON { title: string; @@ -30,7 +30,7 @@ export interface IRelationshipAttributeCreationHints extends ISerializable { @type("RelationshipAttributeCreationHints") export class RelationshipAttributeCreationHints extends Serializable implements IRelationshipAttributeCreationHints { @serialize() - @validate({ max: 100 }) + @validate({ max: PROPRIETARY_ATTRIBUTE_MAX_TITLE_LENGTH }) public title: string; @serialize() diff --git a/packages/content/src/attributes/ThirdPartyRelationshipAttributeQuery.ts b/packages/content/src/attributes/ThirdPartyRelationshipAttributeQuery.ts index 6347800be..2e7dc748a 100644 --- a/packages/content/src/attributes/ThirdPartyRelationshipAttributeQuery.ts +++ b/packages/content/src/attributes/ThirdPartyRelationshipAttributeQuery.ts @@ -12,12 +12,18 @@ export interface ThirdPartyRelationshipAttributeQueryJSON extends AbstractAttrib export interface IThirdPartyRelationshipAttributeQuery extends IAbstractAttributeQuery { key: string; - owner: ICoreAddress; + owner: ThirdPartyRelationshipAttributeQueryOwner; thirdParty: ICoreAddress[]; validFrom?: ICoreDate; validTo?: ICoreDate; } +export enum ThirdPartyRelationshipAttributeQueryOwner { + ThirdParty = "thirdParty", + Recipient = "recipient", + Empty = "" +} + @type("ThirdPartyRelationshipAttributeQuery") export class ThirdPartyRelationshipAttributeQuery extends AbstractAttributeQuery implements IThirdPartyRelationshipAttributeQuery { @serialize() @@ -25,8 +31,13 @@ export class ThirdPartyRelationshipAttributeQuery extends AbstractAttributeQuery public key: string; @serialize() - @validate() - public owner: CoreAddress; + @validate({ + customValidator: (v) => + !Object.values(ThirdPartyRelationshipAttributeQueryOwner).includes(v) + ? `must be one of: ${Object.values(ThirdPartyRelationshipAttributeQueryOwner).map((o) => `"${o}"`)}` + : undefined + }) + public owner: ThirdPartyRelationshipAttributeQueryOwner; @serialize({ type: CoreAddress }) @validate({ customValidator: (v) => (v.length < 1 ? "may not be empty" : undefined) }) diff --git a/packages/content/src/requests/items/proposeAttribute/ProposeAttributeRequestItem.ts b/packages/content/src/requests/items/proposeAttribute/ProposeAttributeRequestItem.ts index 7f376b2bc..e71b80bf3 100644 --- a/packages/content/src/requests/items/proposeAttribute/ProposeAttributeRequestItem.ts +++ b/packages/content/src/requests/items/proposeAttribute/ProposeAttributeRequestItem.ts @@ -47,6 +47,23 @@ export class ProposeAttributeRequestItem extends RequestItem implements IPropose protected static override postFrom(value: T): T { if (!(value instanceof ProposeAttributeRequestItem)) throw new Error("this should never happen"); + // IQL queries might also be possible for Relationship Attributes in the future + if (value.attribute instanceof RelationshipAttribute && !(value.query instanceof RelationshipAttributeQuery)) { + throw new ValidationError( + ProposeAttributeRequestItem.name, + "", + "When proposing a RelationshipAttribute, the corresponding query has to be a RelationshipAttributeQuery." + ); + } + + if (value.attribute instanceof IdentityAttribute && !(value.query instanceof IdentityAttributeQuery || value.query instanceof IQLQuery)) { + throw new ValidationError( + ProposeAttributeRequestItem.name, + "", + "When proposing an IdentityAttribute, the corresponding query has to be a IdentityAttributeQuery or IQLQuery." + ); + } + if (value.query instanceof IdentityAttributeQuery) { const attributeValueType = (value.attribute.value.toJSON() as any)["@type"]; const queryValueType = value.query.valueType; @@ -60,21 +77,30 @@ export class ProposeAttributeRequestItem extends RequestItem implements IPropose } } - // IQL queries might also be possible for Relationship Attributes in the future - if (value.attribute instanceof RelationshipAttribute && !(value.query instanceof RelationshipAttributeQuery)) { - throw new ValidationError( - ProposeAttributeRequestItem.name, - "", - "When proposing a RelationshipAttribute, the corresponding query has to be a RelationshipAttributeQuery." - ); + if (value.query instanceof IQLQuery && typeof value.query.attributeCreationHints !== "undefined") { + const attributeValueType = (value.attribute.value.toJSON() as any)["@type"]; + const queryValueType = value.query.attributeCreationHints.valueType; + + if (attributeValueType !== queryValueType) { + throw new ValidationError( + ProposeAttributeRequestItem.name, + `${nameof((x) => x.query)}.${nameof((x) => x.attributeCreationHints!.valueType)}`, + `You cannot propose an Attribute whose type of the value ('${attributeValueType}') is different from the value type of the query ('${queryValueType}').` + ); + } } - if (value.attribute instanceof IdentityAttribute && !(value.query instanceof IdentityAttributeQuery || value.query instanceof IQLQuery)) { - throw new ValidationError( - ProposeAttributeRequestItem.name, - "", - "When proposing an IdentityAttribute, the corresponding query has to be a IdentityAttributeQuery or IQLQuery" - ); + if (value.query instanceof RelationshipAttributeQuery) { + const attributeValueType = (value.attribute.value.toJSON() as any)["@type"]; + const queryValueType = value.query.attributeCreationHints.valueType; + + if (attributeValueType !== queryValueType) { + throw new ValidationError( + ProposeAttributeRequestItem.name, + `${nameof((x) => x.query)}.${nameof((x) => x.attributeCreationHints.valueType)}`, + `You cannot propose an Attribute whose type of the value ('${attributeValueType}') is different from the value type of the query ('${queryValueType}').` + ); + } } return value; diff --git a/packages/content/test/attributes/BirthDate.test.ts b/packages/content/test/attributes/BirthDate.test.ts index 28b563bb2..d237f17bb 100644 --- a/packages/content/test/attributes/BirthDate.test.ts +++ b/packages/content/test/attributes/BirthDate.test.ts @@ -1,8 +1,8 @@ import { BirthDate, ValidationErrorWithoutProperty } from "@nmshd/content"; import { DateTime } from "luxon"; -describe("creation of RepositoryAttributes of Attribute Value Type BirthDate", () => { - test("can create a RepositoryAttribute of Attribute Value Type BirthDate", function () { +describe("creation of RepositoryAttributes of Attribute value type BirthDate", () => { + test("can create a RepositoryAttribute of Attribute value type BirthDate", function () { const validBirthDate = BirthDate.from({ day: 1, month: 12, year: 1990 }); expect(validBirthDate.constructor.name).toBe("BirthDate"); expect(validBirthDate.day.value).toBe(1); diff --git a/packages/content/test/attributes/RelationshipAttribute.test.ts b/packages/content/test/attributes/RelationshipAttribute.test.ts index 5b9f4bb5b..8d9fd824f 100644 --- a/packages/content/test/attributes/RelationshipAttribute.test.ts +++ b/packages/content/test/attributes/RelationshipAttribute.test.ts @@ -21,7 +21,7 @@ describe("RelationshipAttribute", function () { test("should create a RelationshipAttribute (isTechnical: true)", function () { const attribute = RelationshipAttribute.from({ - key: "aKey", + key: "AKey", value: attributeValue, owner: CoreAddress.from("address"), isTechnical: true, @@ -33,7 +33,7 @@ describe("RelationshipAttribute", function () { test("should create a RelationshipAttribute (isTechnical: false)", function () { const attribute = RelationshipAttribute.from({ - key: "aKey", + key: "AKey", value: attributeValue, owner: CoreAddress.from("address"), isTechnical: false, @@ -45,7 +45,7 @@ describe("RelationshipAttribute", function () { test("should create a RelationshipAttribute (isTechnical: undefined)", function () { const attribute = RelationshipAttribute.from({ - key: "aKey", + key: "AKey", value: attributeValue, owner: CoreAddress.from("address"), confidentiality: RelationshipAttributeConfidentiality.Public diff --git a/packages/content/test/attributes/ThirdPartyRelationshipAttributeQuery.test.ts b/packages/content/test/attributes/ThirdPartyRelationshipAttributeQuery.test.ts index ff8fe182e..51158322b 100644 --- a/packages/content/test/attributes/ThirdPartyRelationshipAttributeQuery.test.ts +++ b/packages/content/test/attributes/ThirdPartyRelationshipAttributeQuery.test.ts @@ -37,7 +37,7 @@ describe("ThirdPartyRelationshipAttributeQuery", function () { ])("accepts '$in' as thirdParty", function (params) { const serialized = ThirdPartyRelationshipAttributeQuery.from({ key: "test", - owner: "test", + owner: "thirdParty", // casting as any to test backwards compatibility thirdParty: params.in as unknown as any @@ -53,7 +53,7 @@ describe("ThirdPartyRelationshipAttributeQuery", function () { expect(() => { ThirdPartyRelationshipAttributeQuery.from({ key: "test", - owner: "test", + owner: "", thirdParty: thirdParty }); // eslint-disable-next-line jest/require-to-throw-message @@ -71,7 +71,7 @@ describe("ThirdPartyRelationshipAttributeQuery", function () { query: { "@type": "ThirdPartyRelationshipAttributeQuery", key: "test", - owner: "test", + owner: "thirdParty", // casting as any to test backwards compatibility thirdParty: params.in as unknown as any @@ -81,7 +81,7 @@ describe("ThirdPartyRelationshipAttributeQuery", function () { const json = test.toJSON(); expect(json.query).toStrictEqual({ key: "test", - owner: "test", + owner: "thirdParty", thirdParty: params.out }); } diff --git a/packages/content/test/requests/items/ProposeAttributeRequestItem.test.ts b/packages/content/test/requests/items/ProposeAttributeRequestItem.test.ts new file mode 100644 index 000000000..44b7a784a --- /dev/null +++ b/packages/content/test/requests/items/ProposeAttributeRequestItem.test.ts @@ -0,0 +1,158 @@ +import { ValidationError } from "@js-soft/ts-serval"; +import { TestObjectFactory } from "@nmshd/consumption/test/modules/requests/testHelpers/TestObjectFactory"; +import { IdentityAttributeQuery, IQLQuery, ProposeAttributeRequestItem, RelationshipAttributeConfidentiality, RelationshipAttributeQuery } from "@nmshd/content"; +import { CoreAddress } from "@nmshd/transport"; +import { nameof } from "ts-simple-nameof"; + +describe("creation of ProposeAttributeRequestItem", () => { + describe("creation of ProposeAttributeRequestItem with IdentityAttributeQuery", () => { + test("can create a ProposeAttributeRequestItem with IdentityAttributeQuery", function () { + const validProposeAttributeRequestItem = ProposeAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: TestObjectFactory.createIdentityAttribute({ + owner: CoreAddress.from("") + }), + query: IdentityAttributeQuery.from({ valueType: "GivenName" }) + }); + + expect(validProposeAttributeRequestItem.constructor.name).toBe("ProposeAttributeRequestItem"); + expect(validProposeAttributeRequestItem.attribute.constructor.name).toBe("IdentityAttribute"); + expect(validProposeAttributeRequestItem.attribute.value.constructor.name).toBe("GivenName"); + expect(validProposeAttributeRequestItem.query.constructor.name).toBe("IdentityAttributeQuery"); + }); + + test("returns an error when trying to create an invalid ProposeAttributeRequestItem with IdentityAttributeQuery and RelationshipAttribute", function () { + const invalidProposeAttributeRequestItemCall = () => { + ProposeAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: TestObjectFactory.createRelationshipAttribute({ + owner: CoreAddress.from("") + }), + query: IdentityAttributeQuery.from({ valueType: "GivenName" }) + }); + }; + expect(invalidProposeAttributeRequestItemCall).toThrow( + new ValidationError(ProposeAttributeRequestItem.name, "", "When proposing a RelationshipAttribute, the corresponding query has to be a RelationshipAttributeQuery.") + ); + }); + + test("returns an error when trying to create an invalid ProposeAttributeRequestItem with mismatching IdentityAttribute value type", function () { + const invalidProposeAttributeRequestItemCall = () => { + ProposeAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: TestObjectFactory.createIdentityAttribute({ + owner: CoreAddress.from("") + }), + query: IdentityAttributeQuery.from({ valueType: "DisplayName" }) + }); + }; + expect(invalidProposeAttributeRequestItemCall).toThrow( + new ValidationError( + ProposeAttributeRequestItem.name, + `${nameof((x) => x.query)}.${nameof((x) => x.valueType)}`, + "You cannot propose an Attribute whose type of the value ('GivenName') is different from the value type of the query ('DisplayName')." + ) + ); + }); + }); + + describe("creation of ProposeAttributeRequestItem with IQLQuery", () => { + test("returns an error when trying to create an invalid ProposeAttributeRequestItem with IQLQuery and mismatching IdentityAttribute value type", function () { + const invalidProposeAttributeRequestItemCall = () => { + ProposeAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: TestObjectFactory.createIdentityAttribute({ + owner: CoreAddress.from("") + }), + query: IQLQuery.from({ queryString: "DisplayName", attributeCreationHints: { valueType: "DisplayName" } }) + }); + }; + expect(invalidProposeAttributeRequestItemCall).toThrow( + new ValidationError( + ProposeAttributeRequestItem.name, + `${nameof((x) => x.query)}.${nameof((x) => x.attributeCreationHints!.valueType)}`, + "You cannot propose an Attribute whose type of the value ('GivenName') is different from the value type of the query ('DisplayName')." + ) + ); + }); + }); + + describe("creation of ProposeAttributeRequestItem with RelationshipAttributeQuery", () => { + test("can create a ProposeAttributeRequestItem with RelationshipAttributeQuery", function () { + const validProposeAttributeRequestItem = ProposeAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: TestObjectFactory.createRelationshipAttribute({ + owner: CoreAddress.from("") + }), + query: RelationshipAttributeQuery.from({ + owner: "", + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }) + }); + + expect(validProposeAttributeRequestItem.constructor.name).toBe("ProposeAttributeRequestItem"); + expect(validProposeAttributeRequestItem.attribute.constructor.name).toBe("RelationshipAttribute"); + expect(validProposeAttributeRequestItem.attribute.value.constructor.name).toBe("ProprietaryString"); + expect(validProposeAttributeRequestItem.query.constructor.name).toBe("RelationshipAttributeQuery"); + }); + + test("returns an error when trying to create an invalid ProposeAttributeRequestItem with RelationshipAttributeQuery and IdentityAttribute", function () { + const invalidProposeAttributeRequestItemCall = () => { + ProposeAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: TestObjectFactory.createIdentityAttribute({ + owner: CoreAddress.from("") + }), + query: RelationshipAttributeQuery.from({ + owner: "", + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryString", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }) + }); + }; + expect(invalidProposeAttributeRequestItemCall).toThrow( + new ValidationError( + ProposeAttributeRequestItem.name, + "", + "When proposing an IdentityAttribute, the corresponding query has to be a IdentityAttributeQuery or IQLQuery." + ) + ); + }); + + test("returns an error when trying to create an invalid ProposeAttributeRequestItem with mismatching RelationshipAttribute value type", function () { + const invalidProposeAttributeRequestItemCall = () => { + ProposeAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: TestObjectFactory.createRelationshipAttribute({ + owner: CoreAddress.from("") + }), + query: RelationshipAttributeQuery.from({ + owner: "", + key: "AKey", + attributeCreationHints: { + valueType: "ProprietaryInteger", + title: "ATitle", + confidentiality: RelationshipAttributeConfidentiality.Public + } + }) + }); + }; + expect(invalidProposeAttributeRequestItemCall).toThrow( + new ValidationError( + ProposeAttributeRequestItem.name, + `${nameof((x) => x.query)}.${nameof((x) => x.attributeCreationHints.valueType)}`, + "You cannot propose an Attribute whose type of the value ('ProprietaryString') is different from the value type of the query ('ProprietaryInteger')." + ) + ); + }); + }); +}); diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index 1fc2edb4d..7ca7563db 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -8,6 +8,7 @@ import { RequestItemJSONDerivations, StreetJSON, ThirdPartyRelationshipAttributeQuery, + ThirdPartyRelationshipAttributeQueryOwner, ZipCodeJSON } from "@nmshd/content"; import { CoreAddress, CoreDate, CoreId } from "@nmshd/transport"; @@ -23,6 +24,7 @@ import { DeleteThirdPartyOwnedRelationshipAttributeAndNotifyPeerUseCase, ExecuteIdentityAttributeQueryUseCase, ExecuteRelationshipAttributeQueryUseCase, + ExecuteThirdPartyRelationshipAttributeQueryUseCase, GetAttributeUseCase, GetAttributesUseCase, GetOwnSharedAttributesUseCase, @@ -129,12 +131,12 @@ describe("get attribute(s)", () => { const relationshipAttribute = await executeFullCreateAndShareRelationshipAttributeFlow(services1, services2, { content: { - key: "a key", + key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, value: { "@type": "ProprietaryString", - value: "a String", - title: "a title" + value: "AString", + title: "ATitle" }, isTechnical: true } @@ -231,8 +233,8 @@ describe("attribute queries", () => { content: { value: { "@type": "ProprietaryString", - title: "aTitle", - value: "aProprietaryStringValue" + title: "ATitle", + value: "AProprietaryStringValue" }, key: "website", confidentiality: RelationshipAttributeConfidentiality.Protected @@ -270,13 +272,13 @@ describe("attribute queries", () => { }); }); - describe(ExecuteRelationshipAttributeQueryUseCase.name, () => { + describe(ExecuteThirdPartyRelationshipAttributeQueryUseCase.name, () => { test("should allow to execute a thirdPartyRelationshipAttributeQuery", async function () { const result = await services2.consumption.attributes.executeThirdPartyRelationshipAttributeQuery({ query: { "@type": "ThirdPartyRelationshipAttributeQuery", key: "website", - owner: services1.address, + owner: ThirdPartyRelationshipAttributeQueryOwner.ThirdParty, thirdParty: [services1.address] } }); @@ -364,12 +366,12 @@ describe("get repository, own shared and peer shared attributes", () => { // own shared succeeded relationship attribute services1SharedRelationshipAttributeV0 = await executeFullCreateAndShareRelationshipAttributeFlow(services1, services2, { content: { - key: "a key", + key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, value: { "@type": "ProprietaryString", - value: "a String", - title: "a title" + value: "AString", + title: "ATitle" }, isTechnical: false } @@ -744,8 +746,8 @@ describe(ShareRepositoryAttributeUseCase.name, () => { key: "test", value: { "@type": "ProprietaryString", - value: "a String", - title: "a title" + value: "AString", + title: "ATitle" }, confidentiality: RelationshipAttributeConfidentiality.Public }, @@ -1011,8 +1013,8 @@ describe(CreateAndShareRelationshipAttributeUseCase.name, () => { key: "test", value: { "@type": "ProprietaryString", - value: "a String", - title: "a title" + value: "AString", + title: "ATitle" }, confidentiality: RelationshipAttributeConfidentiality.Public }, @@ -1036,8 +1038,8 @@ describe(CreateAndShareRelationshipAttributeUseCase.name, () => { key: "test", value: { "@type": "ProprietaryString", - value: "a String", - title: "a title" + value: "AString", + title: "ATitle" }, confidentiality: RelationshipAttributeConfidentiality.Public }, @@ -1080,8 +1082,8 @@ describe(SucceedRelationshipAttributeAndNotifyPeerUseCase.name, () => { key: "test", value: { "@type": "ProprietaryString", - value: "a String", - title: "a title" + value: "AString", + title: "ATitle" }, confidentiality: RelationshipAttributeConfidentiality.Public } @@ -1244,7 +1246,7 @@ describe("Get (shared) versions of attribute", () => { async function createAndShareRelationshipAttributeVersion0(): Promise { sOSRAVersion0 = await executeFullCreateAndShareRelationshipAttributeFlow(services1, services2, { content: { - key: "Some key", + key: "AKey", value: { "@type": "ProprietaryInteger", title: "Version", @@ -1437,7 +1439,11 @@ describe("Get (shared) versions of attribute", () => { content: { items: [ ReadAttributeRequestItem.from({ - query: ThirdPartyRelationshipAttributeQuery.from({ key: "a key", owner: services1.address, thirdParty: [services2.address] }), + query: ThirdPartyRelationshipAttributeQuery.from({ + key: "AKey", + owner: ThirdPartyRelationshipAttributeQueryOwner.Recipient, + thirdParty: [services2.address] + }), mustBeAccepted: true }).toJSON() ] @@ -1692,10 +1698,10 @@ describe("DeleteAttributeUseCases", () => { content: { value: { "@type": "ProprietaryString", - value: "a value", - title: "a title" + value: "AStringValue", + title: "ATitle" }, - key: "a key", + key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public } }); @@ -1705,7 +1711,11 @@ describe("DeleteAttributeUseCases", () => { content: { items: [ ReadAttributeRequestItem.from({ - query: ThirdPartyRelationshipAttributeQuery.from({ key: "a key", owner: services1.address, thirdParty: [services3.address] }), + query: ThirdPartyRelationshipAttributeQuery.from({ + key: "AKey", + owner: ThirdPartyRelationshipAttributeQueryOwner.Recipient, + thirdParty: [services3.address] + }), mustBeAccepted: true }).toJSON() ] @@ -1796,10 +1806,10 @@ describe("DeleteAttributeUseCases", () => { content: { value: { "@type": "ProprietaryString", - value: "a value", - title: "a title" + value: "AStringValue", + title: "ATitle" }, - key: "a key", + key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public } }); @@ -1809,7 +1819,11 @@ describe("DeleteAttributeUseCases", () => { content: { items: [ ReadAttributeRequestItem.from({ - query: ThirdPartyRelationshipAttributeQuery.from({ key: "a key", owner: services3.address, thirdParty: [services3.address] }), + query: ThirdPartyRelationshipAttributeQuery.from({ + key: "AKey", + owner: ThirdPartyRelationshipAttributeQueryOwner.ThirdParty, + thirdParty: [services3.address] + }), mustBeAccepted: true }).toJSON() ] @@ -1887,10 +1901,10 @@ describe("DeleteAttributeUseCases", () => { content: { value: { "@type": "ProprietaryString", - value: "A proprietary string", - title: "A title" + value: "AString", + title: "ATitle" }, - key: "A key", + key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public } }); @@ -1900,7 +1914,11 @@ describe("DeleteAttributeUseCases", () => { content: { items: [ ReadAttributeRequestItem.from({ - query: ThirdPartyRelationshipAttributeQuery.from({ key: "A key", owner: services3.address, thirdParty: [services3.address] }), + query: ThirdPartyRelationshipAttributeQuery.from({ + key: "AKey", + owner: ThirdPartyRelationshipAttributeQueryOwner.ThirdParty, + thirdParty: [services3.address] + }), mustBeAccepted: true }).toJSON() ] diff --git a/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts b/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts index 2f6c5dc2f..7dc8aaaed 100644 --- a/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts +++ b/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts @@ -46,8 +46,8 @@ describe("RelationshipTemplateDVO", () => { owner: templator.address, value: { "@type": "ProprietaryString", - title: "aTitle", - value: "aProprietaryStringValue" + title: "ATitle", + value: "AProprietaryStringValue" }, key: "givenName", confidentiality: "protected" as RelationshipAttributeConfidentiality @@ -57,8 +57,8 @@ describe("RelationshipTemplateDVO", () => { owner: templator.address, value: { "@type": "ProprietaryString", - title: "aTitle", - value: "aProprietaryStringValue" + title: "ATitle", + value: "AProprietaryStringValue" }, key: "surname", confidentiality: "protected" as RelationshipAttributeConfidentiality diff --git a/packages/runtime/test/dataViews/requestItems/ShareAttributeRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/ShareAttributeRequestItemDVO.test.ts index 18e17e74c..ee021eef9 100644 --- a/packages/runtime/test/dataViews/requestItems/ShareAttributeRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/ShareAttributeRequestItemDVO.test.ts @@ -1,5 +1,6 @@ -import { DecideRequestItemParametersJSON, LocalRequestStatus } from "@nmshd/consumption"; +import { AttributesController, DecideRequestItemParametersJSON, LocalRequestStatus } from "@nmshd/consumption"; import { AbstractStringJSON, DisplayNameJSON, ShareAttributeRequestItemJSON } from "@nmshd/content"; +import { CoreId } from "@nmshd/transport"; import { AcceptResponseItemDVO, ConsumptionServices, @@ -56,6 +57,12 @@ beforeAll(async () => { sAddress = (await sTransportServices.account.getIdentityInfo()).value.address; rAddress = (await rTransportServices.account.getIdentityInfo()).value.address; + responseItems = [{ accept: true }]; +}, 30000); + +afterAll(() => serviceProvider.stop()); + +beforeEach(async () => { const senderAttribute = await sConsumptionServices.attributes.createRepositoryAttribute({ content: { value: { @@ -78,16 +85,28 @@ beforeAll(async () => { }, peer: rAddress }; - responseItems = [{ accept: true }]; -}, 30000); -afterAll(() => serviceProvider.stop()); - -beforeEach(function () { rEventBus.reset(); sEventBus.reset(); }); +afterEach(async () => { + await cleanupAttributes(); +}); + +async function cleanupAttributes() { + await Promise.all( + [sRuntimeServices, rRuntimeServices].map(async (services) => { + const servicesAttributeController = (rRuntimeServices.consumption.attributes as any).getAttributeUseCase.attributeController as AttributesController; + + const servicesAttributesResult = await services.consumption.attributes.getAttributes({}); + for (const attribute of servicesAttributesResult.value) { + await servicesAttributeController.deleteAttributeUnsafe(CoreId.from(attribute.id)); + } + }) + ); +} + describe("ShareAttributeRequestItemDVO", () => { test("check the MessageDVO for the sender", async () => { const senderMessage = await sendMessageWithRequest(sRuntimeServices, rRuntimeServices, requestContent); diff --git a/packages/runtime/test/modules/AttributeListenerModule.test.ts b/packages/runtime/test/modules/AttributeListenerModule.test.ts index ae98376ed..847711fbf 100644 --- a/packages/runtime/test/modules/AttributeListenerModule.test.ts +++ b/packages/runtime/test/modules/AttributeListenerModule.test.ts @@ -36,8 +36,8 @@ beforeAll(async () => { "@type": "RegisterAttributeListenerRequestItem", query: { "@type": "ThirdPartyRelationshipAttributeQuery", - key: "aKey", - owner: thirdParty.address, + key: "AKey", + owner: "thirdParty", thirdParty: [thirdParty.address] }, mustBeAccepted: true @@ -73,13 +73,13 @@ describe("AttributeListenerModule", () => { const attributeContent: RelationshipAttributeJSON = { "@type": "RelationshipAttribute", owner: thirdParty.address, - key: "aKey", + key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, isTechnical: false, value: { "@type": "ProprietaryString", - title: "aTitle", - value: "aProprietaryStringValue" + title: "ATitle", + value: "AProprietaryStringValue" } }; diff --git a/packages/runtime/test/modules/RequestModule.test.ts b/packages/runtime/test/modules/RequestModule.test.ts index 84cc999c9..c952c081e 100644 --- a/packages/runtime/test/modules/RequestModule.test.ts +++ b/packages/runtime/test/modules/RequestModule.test.ts @@ -8,6 +8,7 @@ import { ResponseItemResult, ResponseResult } from "@nmshd/content"; +import { CoreAddress } from "@nmshd/transport"; import { ConsumptionServices, CreateOutgoingRequestRequest, @@ -331,7 +332,7 @@ describe("RequestModule", () => { "@type": "CreateAttributeRequestItem", mustBeAccepted: false, attribute: IdentityAttribute.from({ - owner: (await rTransportServices.account.getIdentityInfo()).value.address, + owner: CoreAddress.from(""), value: GivenName.from("AGivenName").toJSON() }).toJSON() } diff --git a/packages/transport/src/modules/sync/externalEventProcessors/RelationshipStatusChangedExternalEventProcessor.ts b/packages/transport/src/modules/sync/externalEventProcessors/RelationshipStatusChangedExternalEventProcessor.ts index 1b4d39242..8c0386573 100644 --- a/packages/transport/src/modules/sync/externalEventProcessors/RelationshipStatusChangedExternalEventProcessor.ts +++ b/packages/transport/src/modules/sync/externalEventProcessors/RelationshipStatusChangedExternalEventProcessor.ts @@ -19,6 +19,7 @@ export class RelationshipStatusChangedExternalEventProcessor extends ExternalEve this.eventBus.publish(new RelationshipChangedEvent(this.ownAddress, relationship)); return relationship; } + return; } } From b09651583bd497d69655cf7e4a908fd30b270ff3 Mon Sep 17 00:00:00 2001 From: Milena Czierlinski <146972016+Milena-Czierlinski@users.noreply.github.com> Date: Wed, 29 May 2024 09:21:30 +0200 Subject: [PATCH 06/40] Chore/Remove GetSharedVersionsOfRepositoryAttribute (#144) --- .../facades/consumption/AttributesFacade.ts | 16 +-- .../runtime/src/useCases/common/Schemas.ts | 135 +++++++----------- .../GetSharedVersionsOfRepositoryAttribute.ts | 51 ------- .../useCases/consumption/attributes/index.ts | 3 +- .../test/consumption/attributes.test.ts | 87 ----------- 5 files changed, 53 insertions(+), 239 deletions(-) delete mode 100644 packages/runtime/src/useCases/consumption/attributes/GetSharedVersionsOfRepositoryAttribute.ts diff --git a/packages/runtime/src/extensibility/facades/consumption/AttributesFacade.ts b/packages/runtime/src/extensibility/facades/consumption/AttributesFacade.ts index bbacf8004..a4b3be08a 100644 --- a/packages/runtime/src/extensibility/facades/consumption/AttributesFacade.ts +++ b/packages/runtime/src/extensibility/facades/consumption/AttributesFacade.ts @@ -17,18 +17,18 @@ import { DeleteThirdPartyOwnedRelationshipAttributeAndNotifyPeerRequest, DeleteThirdPartyOwnedRelationshipAttributeAndNotifyPeerResponse, DeleteThirdPartyOwnedRelationshipAttributeAndNotifyPeerUseCase, - ExecuteIdentityAttributeQueryRequest, - ExecuteIdentityAttributeQueryUseCase, ExecuteIQLQueryRequest, ExecuteIQLQueryUseCase, + ExecuteIdentityAttributeQueryRequest, + ExecuteIdentityAttributeQueryUseCase, ExecuteRelationshipAttributeQueryRequest, ExecuteRelationshipAttributeQueryUseCase, ExecuteThirdPartyRelationshipAttributeQueryRequest, ExecuteThirdPartyRelationshipAttributeQueryUseCase, GetAttributeRequest, + GetAttributeUseCase, GetAttributesRequest, GetAttributesUseCase, - GetAttributeUseCase, GetOwnSharedAttributesRequest, GetOwnSharedAttributesUseCase, GetPeerSharedAttributesRequest, @@ -37,8 +37,6 @@ import { GetRepositoryAttributesUseCase, GetSharedVersionsOfAttributeRequest, GetSharedVersionsOfAttributeUseCase, - GetSharedVersionsOfRepositoryAttributeRequest, - GetSharedVersionsOfRepositoryAttributeUseCase, GetVersionsOfAttributeRequest, GetVersionsOfAttributeUseCase, NotifyPeerAboutRepositoryAttributeSuccessionRequest, @@ -68,7 +66,6 @@ export class AttributesFacade { @Inject private readonly getAttributesUseCase: GetAttributesUseCase, @Inject private readonly getVersionsOfAttributeUseCase: GetVersionsOfAttributeUseCase, @Inject private readonly getSharedVersionsOfAttributeUseCase: GetSharedVersionsOfAttributeUseCase, - @Inject private readonly getSharedVersionsOfRepositoryAttributeUseCase: GetSharedVersionsOfRepositoryAttributeUseCase, @Inject private readonly succeedRepositoryAttributeUseCase: SucceedRepositoryAttributeUseCase, @Inject private readonly executeIdentityAttributeQueryUseCase: ExecuteIdentityAttributeQueryUseCase, @Inject private readonly executeRelationshipAttributeQueryUseCase: ExecuteRelationshipAttributeQueryUseCase, @@ -116,13 +113,6 @@ export class AttributesFacade { return await this.getSharedVersionsOfAttributeUseCase.execute(request); } - /** - * @deprecated getSharedVersionsOfRepositoryAttribute won't be available in version 5 anymore. Use getSharedVersionsOfAttribute instead. - */ - public async getSharedVersionsOfRepositoryAttribute(request: GetSharedVersionsOfRepositoryAttributeRequest): Promise> { - return await this.getSharedVersionsOfRepositoryAttributeUseCase.execute(request); - } - public async executeIdentityAttributeQuery(request: ExecuteIdentityAttributeQueryRequest): Promise> { return await this.executeIdentityAttributeQueryUseCase.execute(request); } diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index 96035627b..cc73c7c2d 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -15902,15 +15902,37 @@ export const DeleteThirdPartyOwnedRelationshipAttributeAndNotifyPeerRequest: any } } -export const ExecuteIdentityAttributeQueryRequest: any = { +export const ExecuteIQLQueryRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/ExecuteIdentityAttributeQueryRequest", + "$ref": "#/definitions/ExecuteIQLQueryRequest", "definitions": { - "ExecuteIdentityAttributeQueryRequest": { + "ExecuteIQLQueryRequest": { "type": "object", "properties": { "query": { - "$ref": "#/definitions/IdentityAttributeQueryJSON" + "type": "object", + "additionalProperties": false, + "properties": { + "@type": { + "type": "string", + "const": "IQLQuery" + }, + "queryString": { + "type": "string" + }, + "attributeCreationHints": { + "$ref": "#/definitions/IQLQueryCreationHintsJSON" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + } + }, + "required": [ + "queryString" + ] } }, "required": [ @@ -15918,19 +15940,9 @@ export const ExecuteIdentityAttributeQueryRequest: any = { ], "additionalProperties": false }, - "IdentityAttributeQueryJSON": { + "IQLQueryCreationHintsJSON": { "type": "object", "properties": { - "@type": { - "type": "string", - "const": "IdentityAttributeQuery" - }, - "@context": { - "type": "string" - }, - "@version": { - "type": "string" - }, "valueType": { "$ref": "#/definitions/AttributeValues.Identity.TypeName" }, @@ -15939,16 +15951,9 @@ export const ExecuteIdentityAttributeQueryRequest: any = { "items": { "type": "string" } - }, - "validFrom": { - "type": "string" - }, - "validTo": { - "type": "string" } }, "required": [ - "@type", "valueType" ], "additionalProperties": false @@ -16018,37 +16023,15 @@ export const ExecuteIdentityAttributeQueryRequest: any = { } } -export const ExecuteIQLQueryRequest: any = { +export const ExecuteIdentityAttributeQueryRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/ExecuteIQLQueryRequest", + "$ref": "#/definitions/ExecuteIdentityAttributeQueryRequest", "definitions": { - "ExecuteIQLQueryRequest": { + "ExecuteIdentityAttributeQueryRequest": { "type": "object", "properties": { "query": { - "type": "object", - "additionalProperties": false, - "properties": { - "@type": { - "type": "string", - "const": "IQLQuery" - }, - "queryString": { - "type": "string" - }, - "attributeCreationHints": { - "$ref": "#/definitions/IQLQueryCreationHintsJSON" - }, - "@context": { - "type": "string" - }, - "@version": { - "type": "string" - } - }, - "required": [ - "queryString" - ] + "$ref": "#/definitions/IdentityAttributeQueryJSON" } }, "required": [ @@ -16056,9 +16039,19 @@ export const ExecuteIQLQueryRequest: any = { ], "additionalProperties": false }, - "IQLQueryCreationHintsJSON": { + "IdentityAttributeQueryJSON": { "type": "object", "properties": { + "@type": { + "type": "string", + "const": "IdentityAttributeQuery" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, "valueType": { "$ref": "#/definitions/AttributeValues.Identity.TypeName" }, @@ -16067,9 +16060,16 @@ export const ExecuteIQLQueryRequest: any = { "items": { "type": "string" } + }, + "validFrom": { + "type": "string" + }, + "validTo": { + "type": "string" } }, "required": [ + "@type", "valueType" ], "additionalProperties": false @@ -17305,43 +17305,6 @@ export const GetSharedVersionsOfAttributeRequest: any = { } } -export const GetSharedVersionsOfRepositoryAttributeRequest: any = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/GetSharedVersionsOfRepositoryAttributeRequest", - "definitions": { - "GetSharedVersionsOfRepositoryAttributeRequest": { - "type": "object", - "properties": { - "attributeId": { - "$ref": "#/definitions/AttributeIdString" - }, - "peers": { - "type": "array", - "items": { - "$ref": "#/definitions/AddressString" - } - }, - "onlyLatestVersions": { - "type": "boolean", - "description": "default: true" - } - }, - "required": [ - "attributeId" - ], - "additionalProperties": false - }, - "AttributeIdString": { - "type": "string", - "pattern": "ATT[A-Za-z0-9]{17}" - }, - "AddressString": { - "type": "string", - "pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}" - } - } -} - export const GetVersionsOfAttributeRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/GetVersionsOfAttributeRequest", diff --git a/packages/runtime/src/useCases/consumption/attributes/GetSharedVersionsOfRepositoryAttribute.ts b/packages/runtime/src/useCases/consumption/attributes/GetSharedVersionsOfRepositoryAttribute.ts deleted file mode 100644 index 2a1126eab..000000000 --- a/packages/runtime/src/useCases/consumption/attributes/GetSharedVersionsOfRepositoryAttribute.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Result } from "@js-soft/ts-utils"; -import { AttributesController, LocalAttribute } from "@nmshd/consumption"; -import { AccountController, CoreAddress, CoreId } from "@nmshd/transport"; -import { Inject } from "typescript-ioc"; -import { LocalAttributeDTO } from "../../../types"; -import { AddressString, AttributeIdString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common"; -import { AttributeMapper } from "./AttributeMapper"; - -export interface GetSharedVersionsOfRepositoryAttributeRequest { - attributeId: AttributeIdString; - peers?: AddressString[]; - /** - * default: true - */ - onlyLatestVersions?: boolean; -} - -class Validator extends SchemaValidator { - public constructor(@Inject schemaRepository: SchemaRepository) { - super(schemaRepository.getSchema("GetSharedVersionsOfRepositoryAttributeRequest")); - } -} - -export class GetSharedVersionsOfRepositoryAttributeUseCase extends UseCase { - public constructor( - @Inject private readonly accountController: AccountController, - @Inject private readonly attributeController: AttributesController, - @Inject validator: Validator - ) { - super(validator); - } - - protected async executeInternal(request: GetSharedVersionsOfRepositoryAttributeRequest): Promise> { - const repositoryAttributeId = CoreId.from(request.attributeId); - const repositoryAttribute = await this.attributeController.getLocalAttribute(repositoryAttributeId); - if (!repositoryAttribute) return Result.fail(RuntimeErrors.general.recordNotFound(LocalAttribute)); - - if (!repositoryAttribute.isRepositoryAttribute(this.accountController.identity.address)) { - return Result.fail(RuntimeErrors.attributes.isNotRepositoryAttribute(repositoryAttributeId)); - } - - if (request.peers?.length === 0) { - return Result.fail(RuntimeErrors.general.invalidPropertyValue("The `peers` property may not be an empty array.")); - } - - const peers = request.peers?.map((address) => CoreAddress.from(address)); - const sharedVersions = await this.attributeController.getSharedVersionsOfAttribute(repositoryAttributeId, peers, request.onlyLatestVersions); - - return Result.ok(AttributeMapper.toAttributeDTOList(sharedVersions)); - } -} diff --git a/packages/runtime/src/useCases/consumption/attributes/index.ts b/packages/runtime/src/useCases/consumption/attributes/index.ts index 6a82f6d99..e09e8df57 100644 --- a/packages/runtime/src/useCases/consumption/attributes/index.ts +++ b/packages/runtime/src/useCases/consumption/attributes/index.ts @@ -5,8 +5,8 @@ export * from "./DeleteOwnSharedAttributeAndNotifyPeer"; export * from "./DeletePeerSharedAttributeAndNotifyOwner"; export * from "./DeleteRepositoryAttribute"; export * from "./DeleteThirdPartyOwnedRelationshipAttributeAndNotifyPeer"; -export * from "./ExecuteIdentityAttributeQuery"; export * from "./ExecuteIQLQuery"; +export * from "./ExecuteIdentityAttributeQuery"; export * from "./ExecuteRelationshipAttributeQuery"; export * from "./ExecuteThirdPartyRelationshipAttributeQuery"; export * from "./GetAttribute"; @@ -15,7 +15,6 @@ export * from "./GetOwnSharedAttributes"; export * from "./GetPeerSharedAttributes"; export * from "./GetRepositoryAttributes"; export * from "./GetSharedVersionsOfAttribute"; -export * from "./GetSharedVersionsOfRepositoryAttribute"; export * from "./GetVersionsOfAttribute"; export * from "./NotifyPeerAboutRepositoryAttributeSuccession"; export * from "./ShareRepositoryAttribute"; diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index 7ca7563db..8c3764a1d 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -31,7 +31,6 @@ import { GetPeerSharedAttributesUseCase, GetRepositoryAttributesUseCase, GetSharedVersionsOfAttributeUseCase, - GetSharedVersionsOfRepositoryAttributeUseCase, GetVersionsOfAttributeUseCase, LocalAttributeDTO, NotifyPeerAboutRepositoryAttributeSuccessionUseCase, @@ -1485,92 +1484,6 @@ describe("Get (shared) versions of attribute", () => { expect(result2).toBeAnError(/.*/, "error.runtime.recordNotFound"); }); }); - - describe(GetSharedVersionsOfRepositoryAttributeUseCase.name, () => { - beforeAll(async () => { - await setUpIdentityAttributeVersions(); - }); - test("should get only latest shared version per peer of a repository attribute", async () => { - for (const version of sRAVersions) { - const result1 = await services1.consumption.attributes.getSharedVersionsOfRepositoryAttribute({ attributeId: version.id }); - expect(result1.isSuccess).toBe(true); - const returnedVersions1 = result1.value; - expect(returnedVersions1).toStrictEqual(expect.arrayContaining([sOSIAVersion2, sOSIAVersion2FurtherPeer])); - - const result2 = await services1.consumption.attributes.getSharedVersionsOfRepositoryAttribute({ attributeId: version.id, onlyLatestVersions: true }); - expect(result2.isSuccess).toBe(true); - const returnedVersions2 = result2.value; - expect(returnedVersions2).toStrictEqual(expect.arrayContaining([sOSIAVersion2, sOSIAVersion2FurtherPeer])); - } - }); - - test("should get all shared versions of a repository attribute", async () => { - for (const version of sRAVersions) { - const result = await services1.consumption.attributes.getSharedVersionsOfRepositoryAttribute({ attributeId: version.id, onlyLatestVersions: false }); - expect(result.isSuccess).toBe(true); - - const returnedVersions = result.value; - expect(returnedVersions).toStrictEqual(expect.arrayContaining([sOSIAVersion2, sOSIAVersion2FurtherPeer, sOSIAVersion0])); - } - }); - - test("should get only latest shared version of a repository attribute for a specific peer", async () => { - for (const version of sRAVersions) { - const result1 = await services1.consumption.attributes.getSharedVersionsOfRepositoryAttribute({ attributeId: version.id, peers: [services2.address] }); - expect(result1.isSuccess).toBe(true); - const returnedVersions1 = result1.value; - expect(returnedVersions1).toStrictEqual([sOSIAVersion2]); - - const result2 = await services1.consumption.attributes.getSharedVersionsOfRepositoryAttribute({ attributeId: version.id, peers: [services3.address] }); - expect(result2.isSuccess).toBe(true); - const returnedVersions2 = result2.value; - expect(returnedVersions2).toStrictEqual([sOSIAVersion2FurtherPeer]); - } - }); - - test("should get all shared versions of a repository attribute for a specific peer", async () => { - for (const version of sRAVersions) { - const result1 = await services1.consumption.attributes.getSharedVersionsOfRepositoryAttribute({ - attributeId: version.id, - peers: [services2.address], - onlyLatestVersions: false - }); - expect(result1.isSuccess).toBe(true); - const returnedVersions1 = result1.value; - expect(returnedVersions1).toStrictEqual([sOSIAVersion2, sOSIAVersion0]); - - const result2 = await services1.consumption.attributes.getSharedVersionsOfRepositoryAttribute({ - attributeId: version.id, - peers: [services3.address], - onlyLatestVersions: false - }); - expect(result2.isSuccess).toBe(true); - const returnedVersions2 = result2.value; - expect(returnedVersions2).toStrictEqual([sOSIAVersion2FurtherPeer]); - } - }); - - test("should return an empty list calling getSharedVersionsOfRepositoryAttribute with a nonexistent peer", async () => { - const result = await services1.consumption.attributes.getSharedVersionsOfRepositoryAttribute({ - attributeId: sRAVersion2.id, - peers: ["did:e:a:dids:00000000000000000000000"] - }); - expect(result.isSuccess).toBe(true); - const returnedVersions = result.value; - expect(returnedVersions).toStrictEqual([]); - }); - - test("should throw trying to call getSharedVersionsOfRepositoryAttribute with a nonexistent attributeId", async () => { - const result2 = await services1.consumption.attributes.getSharedVersionsOfRepositoryAttribute({ attributeId: "ATTxxxxxxxxxxxxxxxxx" }); - expect(result2).toBeAnError(/.*/, "error.runtime.recordNotFound"); - }); - - test("should throw trying to call getSharedVersionsOfRepositoryAttribute with a relationship attribute", async () => { - await createAndShareRelationshipAttributeVersion0(); - const result = await services1.consumption.attributes.getSharedVersionsOfRepositoryAttribute({ attributeId: sOSRAVersion0.id }); - expect(result).toBeAnError(/.*/, "error.runtime.attributes.isNotRepositoryAttribute"); - }); - }); }); describe("DeleteAttributeUseCases", () => { From 3c40c368b6aedfa6b71d9e383a8293c229ac6edf Mon Sep 17 00:00:00 2001 From: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:08:21 +0200 Subject: [PATCH 07/40] Feature/relationship reactivation (#133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add backbone routes to client * feat: change relationship statuses * feat: change audit log statuses/relationship statuses * feat: add non-deleting routes to relationships controller * feat: add non-deleting relationship use cases * test: add non-deleting transport tests * feat: add relationshipDeleted transport event * feat: add deletion to attributes controller and relationships controller * refactor: name relationship operation instead of response * chore: check for an active relationship before challenge creation * chore: check for active relationship before creating request * refactor: relationshipDeletedEvent to ...DeletedBySelf... * fix: status check in relationships controller before deletion * feat: delete messages and message recipients * Revert "Merge branch 'release/v5' into feature/relationship-termination" This reverts commit d3f5f68167079b69dcb63d7df96706a0705965e8, reversing changes made to 39e81d99f67b3d3614305cd95543ef68c6e37f82. * fix: allow deciding a request on new relationship * fix: allow creating a draft request on no relationship * test: messaging etc. blocked after relationship termination * chore: add relationship deletion to checkIdentity use case * chore: apply the incoming requests controller constructor change * fix: complete non-deleting operations in relationships controller * chore: remove commits to relationship deletion "Simple" commits (e. g. type modifications) remain * test: use correct error for no active relationship for message * chore: check for pending/active relationship before request creation * refactor: use improved noMatchingRelationship error message * test: add request creation for terminated relationship test * chore: add schemas for use cases * fix: correct schema names in use cases * chore: updatebackbone * chore: use backbone-specific identities * fix: adapt variable renaming * fix: apply backbone-specific identities * fix: create relationship before request consumption tests * test: check for successful relationship termination * fix: correct reason name to ReactivationRequest * fix: sync relationship in test * chore: update backbone * fix: relationships runtime test * chore: remove reactivation * Revert "chore: remove reactivation" This reverts commit 67d0133bfc2c27ebca137916ec3583a6fab9626c. * fix: remove some more * fix: relationshipsFacade * chore: remove reactivation schemas * test: more demanding address tests * fix: remove enmeshed prefix * refactor: rollback relationships controller changes * feat: re-add relationship termination in relationships controller * chore: remove reactivation core error * test: create pending/terminated relationship in test object factory * test: methods for creating pending/terminated relationships in integration tests * test: add terminated relationship message controller test * refactor: create relationship centrally in outgoing request controller describe blocks * test: add terminated relationship outgoing requests controller test * test: add terminated relationship incoming requests controller tests plus small fixes * refactor: incoming requests controller relationship check returns validation instead of throwing * test: canAccept / canReject return validation result * refactor: test util terminate relationship does not require relationship id * test: terminated relationship challenge test * fix: terminated relationship runtime test checks can decide validation * Revert "chore: remove reactivation schemas" This reverts commit 20c42d091fa8fc3cc28708ee6ef9784beaa47269. * Revert "chore: remove reactivation core error" This reverts commit 421eb79eac1c85c9c7d10c42cd981916ee0a4c8d. * Revert "fix: remove some more" This reverts commit 143c6486d3585482bf8abb9405d4b5caf1b471a6. * Revert "fix: relationshipsFacade" This reverts commit ac6f8fdc426aef5e9438b8ba72ef0c574a48a5ea. * refactor: remove second pending relationship from factory correct the existing relationships * refactor: add/terminate relationship test utils return type * test: revert last relationships test change * refactor: replace noMatchingRelationship requests error * refactor: rename noMatchingRelationship messages error * refactor: error messages * fix: message controller test * fix: error message in message controller test * fix: error message in relationships test * refactor: inline relationship status check * chore: backbone version bump * fix: end2end test relationship adding * test: add reactivation runtime tests * feat: add reactivation/validation to relationships controller * refactor: update not only pending relationship * test: extend End2End test * refactor: remove comments * chore: add new backbone event processors * refactor: remove comments * fix: relationship client method names * refactor: method name completeOperationWithBackboneCall * fix: remove optional return types * chore: sort imports * fix: bump backbone * test: add tests for added relationship validation * chore: upgrade backbone (again) * fix: End2End relationship tests * chore: remove decompose * fix: wrong ! in relationships controller * test: add another event validation to runtime test * refactor: add ) * test: add toBeSuccessful checks, some fixes * test: add transport test for relationship reactivation request * refactor: name requestRelationshipReactivation * chore: add status checks to relationship use cases * test: check audit log reason instead of new status * refactor: codeshare * chore: upgrade backbone again * refactor: add relationshipId to error messages * refactor: remove duplicate relationship validations * test: remove additional relationship status checks * test: add noRecordFound relationship tests * Revert "refactor: remove duplicate relationship validations" This reverts commit 3d01a429170d59fd573935d7538b710d761ed412. * refactor: remove status checks * test: add relationship tests * fix: errors in tests * refactor: move relationship termination sync in test * refactor: separate negative tests * refactor: new events for relationship reactivation * fix: follow-up to new events * refactor: rename relationship tests * refactor: newline * fix: re-add relationship changed event * test: re-add relationship changed events to tests --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Julian König --- .dev/compose.backbone.env | 2 +- packages/runtime/src/events/EventProxy.ts | 12 +- .../RelationshipReactivationCompletedEvent.ts | 10 + .../RelationshipReactivationRequestedEvent.ts | 10 + .../runtime/src/events/transport/index.ts | 2 + .../facades/transport/RelationshipsFacade.ts | 30 +- .../src/types/transport/RelationshipDTO.ts | 6 +- .../src/useCases/common/RuntimeErrors.ts | 10 + .../runtime/src/useCases/common/Schemas.ts | 92 ++++ .../AcceptRelationshipReactivation.ts | 43 ++ .../RejectRelationshipReactivation.ts | 43 ++ .../RequestRelationshipReactivation.ts | 43 ++ .../RevokeRelationshipReactivation.ts | 43 ++ .../useCases/transport/relationships/index.ts | 4 + packages/runtime/test/lib/testUtils.ts | 19 + .../test/transport/relationships.test.ts | 345 ++++++++++++- packages/transport/src/core/CoreErrors.ts | 19 +- .../RelationshipReactivationCompletedEvent.ts | 10 + .../RelationshipReactivationRequestedEvent.ts | 10 + packages/transport/src/events/index.ts | 2 + .../relationships/RelationshipsController.ts | 186 +++++-- .../backbone/RelationshipClient.ts | 16 + .../transmission/RelationshipAuditLog.ts | 6 +- .../src/modules/sync/SyncController.ts | 10 +- .../ExternalEventProcessorRegistry.ts | 4 + ...tivationCompletedExternalEventProcessor.ts | 22 + ...tivationRequestedExternalEventProcessor.ts | 22 + ...shipStatusChangedExternalEventProcessor.ts | 10 +- .../sync/externalEventProcessors/index.ts | 2 + .../transport/test/end2end/End2End.test.ts | 456 +++++++++++++++++- 30 files changed, 1401 insertions(+), 88 deletions(-) create mode 100644 packages/runtime/src/events/transport/RelationshipReactivationCompletedEvent.ts create mode 100644 packages/runtime/src/events/transport/RelationshipReactivationRequestedEvent.ts create mode 100644 packages/runtime/src/useCases/transport/relationships/AcceptRelationshipReactivation.ts create mode 100644 packages/runtime/src/useCases/transport/relationships/RejectRelationshipReactivation.ts create mode 100644 packages/runtime/src/useCases/transport/relationships/RequestRelationshipReactivation.ts create mode 100644 packages/runtime/src/useCases/transport/relationships/RevokeRelationshipReactivation.ts create mode 100644 packages/transport/src/events/RelationshipReactivationCompletedEvent.ts create mode 100644 packages/transport/src/events/RelationshipReactivationRequestedEvent.ts create mode 100644 packages/transport/src/modules/sync/externalEventProcessors/RelationshipReactivationCompletedExternalEventProcessor.ts create mode 100644 packages/transport/src/modules/sync/externalEventProcessors/RelationshipReactivationRequestedExternalEventProcessor.ts diff --git a/.dev/compose.backbone.env b/.dev/compose.backbone.env index 02f944a85..e07b43d71 100644 --- a/.dev/compose.backbone.env +++ b/.dev/compose.backbone.env @@ -1 +1 @@ -BACKBONE_VERSION=6.0.0-alpha.7 +BACKBONE_VERSION=6.0.0-alpha.11 diff --git a/packages/runtime/src/events/EventProxy.ts b/packages/runtime/src/events/EventProxy.ts index fcad3142f..811d866c3 100644 --- a/packages/runtime/src/events/EventProxy.ts +++ b/packages/runtime/src/events/EventProxy.ts @@ -27,7 +27,9 @@ import { MessageSentEvent, MessageWasReadAtChangedEvent, PeerRelationshipTemplateLoadedEvent, - RelationshipChangedEvent + RelationshipChangedEvent, + RelationshipReactivationCompletedEvent, + RelationshipReactivationRequestedEvent } from "./transport"; export class EventProxy { @@ -71,6 +73,14 @@ export class EventProxy { this.targetEventBus.publish(new RelationshipChangedEvent(event.eventTargetAddress, RelationshipMapper.toRelationshipDTO(event.data))); }); + this.subscribeToSourceEvent(transport.RelationshipReactivationRequestedEvent, (event) => { + this.targetEventBus.publish(new RelationshipReactivationRequestedEvent(event.eventTargetAddress, RelationshipMapper.toRelationshipDTO(event.data))); + }); + + this.subscribeToSourceEvent(transport.RelationshipReactivationCompletedEvent, (event) => { + this.targetEventBus.publish(new RelationshipReactivationCompletedEvent(event.eventTargetAddress, RelationshipMapper.toRelationshipDTO(event.data))); + }); + this.subscribeToSourceEvent(transport.IdentityDeletionProcessStatusChangedEvent, (event) => { this.targetEventBus.publish( new IdentityDeletionProcessStatusChangedEvent(event.eventTargetAddress, IdentityDeletionProcessMapper.toIdentityDeletionProcessDTO(event.data)) diff --git a/packages/runtime/src/events/transport/RelationshipReactivationCompletedEvent.ts b/packages/runtime/src/events/transport/RelationshipReactivationCompletedEvent.ts new file mode 100644 index 000000000..16ddfd67c --- /dev/null +++ b/packages/runtime/src/events/transport/RelationshipReactivationCompletedEvent.ts @@ -0,0 +1,10 @@ +import { RelationshipDTO } from "../../types"; +import { DataEvent } from "../DataEvent"; + +export class RelationshipReactivationCompletedEvent extends DataEvent { + public static readonly namespace = "transport.relationshipReactivationCompleted"; + + public constructor(eventTargetAddress: string, data: RelationshipDTO) { + super(RelationshipReactivationCompletedEvent.namespace, eventTargetAddress, data); + } +} diff --git a/packages/runtime/src/events/transport/RelationshipReactivationRequestedEvent.ts b/packages/runtime/src/events/transport/RelationshipReactivationRequestedEvent.ts new file mode 100644 index 000000000..71fd9de1d --- /dev/null +++ b/packages/runtime/src/events/transport/RelationshipReactivationRequestedEvent.ts @@ -0,0 +1,10 @@ +import { RelationshipDTO } from "../../types"; +import { DataEvent } from "../DataEvent"; + +export class RelationshipReactivationRequestedEvent extends DataEvent { + public static readonly namespace = "transport.relationshipReactivationRequested"; + + public constructor(eventTargetAddress: string, data: RelationshipDTO) { + super(RelationshipReactivationRequestedEvent.namespace, eventTargetAddress, data); + } +} diff --git a/packages/runtime/src/events/transport/index.ts b/packages/runtime/src/events/transport/index.ts index 3db45d1c8..eaa670f04 100644 --- a/packages/runtime/src/events/transport/index.ts +++ b/packages/runtime/src/events/transport/index.ts @@ -5,3 +5,5 @@ export * from "./MessageSentEvent"; export * from "./MessageWasReadAtChangedEvent"; export * from "./PeerRelationshipTemplateLoadedEvent"; export * from "./RelationshipChangedEvent"; +export * from "./RelationshipReactivationCompletedEvent"; +export * from "./RelationshipReactivationRequestedEvent"; diff --git a/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts b/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts index fcb8fb01e..1b56000bf 100644 --- a/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts +++ b/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts @@ -2,6 +2,8 @@ import { Result } from "@js-soft/ts-utils"; import { Inject } from "typescript-ioc"; import { RelationshipDTO } from "../../../types"; import { + AcceptRelationshipReactivationRequest, + AcceptRelationshipReactivationUseCase, AcceptRelationshipRequest, AcceptRelationshipUseCase, CreateRelationshipRequest, @@ -12,11 +14,17 @@ import { GetRelationshipByAddressRequest, GetRelationshipByAddressUseCase, GetRelationshipRequest, + GetRelationshipUseCase, GetRelationshipsRequest, GetRelationshipsUseCase, - GetRelationshipUseCase, + RejectRelationshipReactivationRequest, + RejectRelationshipReactivationUseCase, RejectRelationshipRequest, RejectRelationshipUseCase, + RequestRelationshipReactivationRequest, + RequestRelationshipReactivationUseCase, + RevokeRelationshipReactivationRequest, + RevokeRelationshipReactivationUseCase, RevokeRelationshipRequest, RevokeRelationshipUseCase, TerminateRelationshipRequest, @@ -33,6 +41,10 @@ export class RelationshipsFacade { @Inject private readonly rejectRelationshipUseCase: RejectRelationshipUseCase, @Inject private readonly revokeRelationshipUseCase: RevokeRelationshipUseCase, @Inject private readonly terminateRelationshipUseCase: TerminateRelationshipUseCase, + @Inject private readonly requestRelationshipReactivationUseCase: RequestRelationshipReactivationUseCase, + @Inject private readonly acceptRelationshipReactivationUseCase: AcceptRelationshipReactivationUseCase, + @Inject private readonly rejectRelationshipReactivationUseCase: RejectRelationshipReactivationUseCase, + @Inject private readonly revokeRelationshipReactivationUseCase: RevokeRelationshipReactivationUseCase, @Inject private readonly getAttributesForRelationshipUseCase: GetAttributesForRelationshipUseCase ) {} @@ -68,6 +80,22 @@ export class RelationshipsFacade { return await this.terminateRelationshipUseCase.execute(request); } + public async requestRelationshipReactivation(request: RequestRelationshipReactivationRequest): Promise> { + return await this.requestRelationshipReactivationUseCase.execute(request); + } + + public async acceptRelationshipReactivation(request: AcceptRelationshipReactivationRequest): Promise> { + return await this.acceptRelationshipReactivationUseCase.execute(request); + } + + public async rejectRelationshipReactivation(request: RejectRelationshipReactivationRequest): Promise> { + return await this.rejectRelationshipReactivationUseCase.execute(request); + } + + public async revokeRelationshipReactivation(request: RevokeRelationshipReactivationRequest): Promise> { + return await this.revokeRelationshipReactivationUseCase.execute(request); + } + public async getAttributesForRelationship(request: GetAttributesForRelationshipRequest): Promise> { return await this.getAttributesForRelationshipUseCase.execute(request); } diff --git a/packages/runtime/src/types/transport/RelationshipDTO.ts b/packages/runtime/src/types/transport/RelationshipDTO.ts index 42c3ba57e..7a05ba159 100644 --- a/packages/runtime/src/types/transport/RelationshipDTO.ts +++ b/packages/runtime/src/types/transport/RelationshipDTO.ts @@ -14,7 +14,11 @@ export enum RelationshipAuditLogEntryReason { AcceptanceOfCreation = "AcceptanceOfCreation", RejectionOfCreation = "RejectionOfCreation", RevocationOfCreation = "RevocationOfCreation", - Termination = "Termination" + Termination = "Termination", + ReactivationRequested = "ReactivationRequested", + AcceptanceOfReactivation = "AcceptanceOfReactivation", + RejectionOfReactivation = "RejectionOfReactivation", + RevocationOfReactivation = "RevocationOfReactivation" } export interface RelationshipAuditLogEntryDTO { diff --git a/packages/runtime/src/useCases/common/RuntimeErrors.ts b/packages/runtime/src/useCases/common/RuntimeErrors.ts index 2be4b75a9..a6e45dee4 100644 --- a/packages/runtime/src/useCases/common/RuntimeErrors.ts +++ b/packages/runtime/src/useCases/common/RuntimeErrors.ts @@ -92,6 +92,15 @@ class RelationshipTemplates { } } +class Relationships { + public wrongRelationshipStatus(relationshipId: string, status: string): ApplicationError { + return new ApplicationError( + "error.runtime.relationships.wrongRelationshipStatus", + `The relationship '${relationshipId}' has the wrong status (${status}) to run this operation` + ); + } +} + class Messages { public fileNotFoundInMessage(attachmentId: string) { return new ApplicationError("error.runtime.messages.fileNotFoundInMessage", `The requested file '${attachmentId}' was not found in the given message.`); @@ -216,6 +225,7 @@ export class RuntimeErrors { public static readonly startup = new Startup(); public static readonly files = new Files(); public static readonly relationshipTemplates = new RelationshipTemplates(); + public static readonly relationships = new Relationships(); public static readonly messages = new Messages(); public static readonly challenges = new Challenges(); public static readonly notifications = new Notifications(); diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index cc73c7c2d..b6fac8f9d 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -21754,6 +21754,98 @@ export const LoadPeerRelationshipTemplateRequest: any = { } } +export const RequestRelationshipReactivationRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/RequestRelationshipReactivationRequest", + "definitions": { + "RequestRelationshipReactivationRequest": { + "type": "object", + "properties": { + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" + }, + }, + "required": [ + "relationshipId" + ], + "additionalProperties": false + }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" + }, + } +} + +export const AcceptRelationshipReactivationRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/AcceptRelationshipReactivationRequest", + "definitions": { + "AcceptRelationshipReactivationRequest": { + "type": "object", + "properties": { + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" + }, + }, + "required": [ + "relationshipId" + ], + "additionalProperties": false + }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" + }, + } +} + +export const RejectRelationshipReactivationRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/RejectRelationshipReactivationRequest", + "definitions": { + "RejectRelationshipReactivationRequest": { + "type": "object", + "properties": { + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" + }, + }, + "required": [ + "relationshipId" + ], + "additionalProperties": false + }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" + }, + } +} + +export const RevokeRelationshipReactivationRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/RevokeRelationshipReactivationRequest", + "definitions": { + "RevokeRelationshipReactivationRequest": { + "type": "object", + "properties": { + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" + }, + }, + "required": [ + "relationshipId" + ], + "additionalProperties": false + }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" + }, + } +} + export const AcceptRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/AcceptRelationshipRequest", diff --git a/packages/runtime/src/useCases/transport/relationships/AcceptRelationshipReactivation.ts b/packages/runtime/src/useCases/transport/relationships/AcceptRelationshipReactivation.ts new file mode 100644 index 000000000..f16c2cdfe --- /dev/null +++ b/packages/runtime/src/useCases/transport/relationships/AcceptRelationshipReactivation.ts @@ -0,0 +1,43 @@ +import { Result } from "@js-soft/ts-utils"; +import { AccountController, CoreId, Relationship, RelationshipsController } from "@nmshd/transport"; +import { Inject } from "typescript-ioc"; +import { RelationshipDTO } from "../../../types"; +import { RelationshipIdString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common"; +import { RelationshipMapper } from "./RelationshipMapper"; + +export interface AcceptRelationshipReactivationRequest { + relationshipId: RelationshipIdString; +} + +class Validator extends SchemaValidator { + public constructor(@Inject schemaRepository: SchemaRepository) { + super(schemaRepository.getSchema("AcceptRelationshipReactivationRequest")); + } +} + +export class AcceptRelationshipReactivationUseCase extends UseCase { + public constructor( + @Inject private readonly relationshipsController: RelationshipsController, + @Inject private readonly accountController: AccountController, + @Inject validator: Validator + ) { + super(validator); + } + + protected async executeInternal(request: AcceptRelationshipReactivationRequest): Promise> { + const relationship = await this.relationshipsController.getRelationship(CoreId.from(request.relationshipId)); + if (!relationship) { + return Result.fail(RuntimeErrors.general.recordNotFound(Relationship)); + } + + if (!relationship.cache) { + return Result.fail(RuntimeErrors.general.cacheEmpty(Relationship, relationship.id.toString())); + } + + const updatedRelationship = await this.relationshipsController.acceptReactivation(relationship.id); + + await this.accountController.syncDatawallet(); + + return Result.ok(RelationshipMapper.toRelationshipDTO(updatedRelationship)); + } +} diff --git a/packages/runtime/src/useCases/transport/relationships/RejectRelationshipReactivation.ts b/packages/runtime/src/useCases/transport/relationships/RejectRelationshipReactivation.ts new file mode 100644 index 000000000..9d044a6f7 --- /dev/null +++ b/packages/runtime/src/useCases/transport/relationships/RejectRelationshipReactivation.ts @@ -0,0 +1,43 @@ +import { Result } from "@js-soft/ts-utils"; +import { AccountController, CoreId, Relationship, RelationshipsController } from "@nmshd/transport"; +import { Inject } from "typescript-ioc"; +import { RelationshipDTO } from "../../../types"; +import { RelationshipIdString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common"; +import { RelationshipMapper } from "./RelationshipMapper"; + +export interface RejectRelationshipReactivationRequest { + relationshipId: RelationshipIdString; +} + +class Validator extends SchemaValidator { + public constructor(@Inject schemaRepository: SchemaRepository) { + super(schemaRepository.getSchema("RejectRelationshipReactivationRequest")); + } +} + +export class RejectRelationshipReactivationUseCase extends UseCase { + public constructor( + @Inject private readonly relationshipsController: RelationshipsController, + @Inject private readonly accountController: AccountController, + @Inject validator: Validator + ) { + super(validator); + } + + protected async executeInternal(request: RejectRelationshipReactivationRequest): Promise> { + const relationship = await this.relationshipsController.getRelationship(CoreId.from(request.relationshipId)); + if (!relationship) { + return Result.fail(RuntimeErrors.general.recordNotFound(Relationship)); + } + + if (!relationship.cache) { + return Result.fail(RuntimeErrors.general.cacheEmpty(Relationship, relationship.id.toString())); + } + + const updatedRelationship = await this.relationshipsController.rejectReactivation(relationship.id); + + await this.accountController.syncDatawallet(); + + return Result.ok(RelationshipMapper.toRelationshipDTO(updatedRelationship)); + } +} diff --git a/packages/runtime/src/useCases/transport/relationships/RequestRelationshipReactivation.ts b/packages/runtime/src/useCases/transport/relationships/RequestRelationshipReactivation.ts new file mode 100644 index 000000000..021731fcd --- /dev/null +++ b/packages/runtime/src/useCases/transport/relationships/RequestRelationshipReactivation.ts @@ -0,0 +1,43 @@ +import { Result } from "@js-soft/ts-utils"; +import { AccountController, CoreId, Relationship, RelationshipsController } from "@nmshd/transport"; +import { Inject } from "typescript-ioc"; +import { RelationshipDTO } from "../../../types"; +import { RelationshipIdString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common"; +import { RelationshipMapper } from "./RelationshipMapper"; + +export interface RequestRelationshipReactivationRequest { + relationshipId: RelationshipIdString; +} + +class Validator extends SchemaValidator { + public constructor(@Inject schemaRepository: SchemaRepository) { + super(schemaRepository.getSchema("RequestRelationshipReactivationRequest")); + } +} + +export class RequestRelationshipReactivationUseCase extends UseCase { + public constructor( + @Inject private readonly relationshipsController: RelationshipsController, + @Inject private readonly accountController: AccountController, + @Inject validator: Validator + ) { + super(validator); + } + + protected async executeInternal(request: RequestRelationshipReactivationRequest): Promise> { + const relationship = await this.relationshipsController.getRelationship(CoreId.from(request.relationshipId)); + if (!relationship) { + return Result.fail(RuntimeErrors.general.recordNotFound(Relationship)); + } + + if (!relationship.cache) { + return Result.fail(RuntimeErrors.general.cacheEmpty(Relationship, relationship.id.toString())); + } + + const updatedRelationship = await this.relationshipsController.requestReactivation(relationship.id); + + await this.accountController.syncDatawallet(); + + return Result.ok(RelationshipMapper.toRelationshipDTO(updatedRelationship)); + } +} diff --git a/packages/runtime/src/useCases/transport/relationships/RevokeRelationshipReactivation.ts b/packages/runtime/src/useCases/transport/relationships/RevokeRelationshipReactivation.ts new file mode 100644 index 000000000..b42e865a1 --- /dev/null +++ b/packages/runtime/src/useCases/transport/relationships/RevokeRelationshipReactivation.ts @@ -0,0 +1,43 @@ +import { Result } from "@js-soft/ts-utils"; +import { AccountController, CoreId, Relationship, RelationshipsController } from "@nmshd/transport"; +import { Inject } from "typescript-ioc"; +import { RelationshipDTO } from "../../../types"; +import { RelationshipIdString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common"; +import { RelationshipMapper } from "./RelationshipMapper"; + +export interface RevokeRelationshipReactivationRequest { + relationshipId: RelationshipIdString; +} + +class Validator extends SchemaValidator { + public constructor(@Inject schemaRepository: SchemaRepository) { + super(schemaRepository.getSchema("RevokeRelationshipReactivationRequest")); + } +} + +export class RevokeRelationshipReactivationUseCase extends UseCase { + public constructor( + @Inject private readonly relationshipsController: RelationshipsController, + @Inject private readonly accountController: AccountController, + @Inject validator: Validator + ) { + super(validator); + } + + protected async executeInternal(request: RevokeRelationshipReactivationRequest): Promise> { + const relationship = await this.relationshipsController.getRelationship(CoreId.from(request.relationshipId)); + if (!relationship) { + return Result.fail(RuntimeErrors.general.recordNotFound(Relationship)); + } + + if (!relationship.cache) { + return Result.fail(RuntimeErrors.general.cacheEmpty(Relationship, relationship.id.toString())); + } + + const updatedRelationship = await this.relationshipsController.revokeReactivation(relationship.id); + + await this.accountController.syncDatawallet(); + + return Result.ok(RelationshipMapper.toRelationshipDTO(updatedRelationship)); + } +} diff --git a/packages/runtime/src/useCases/transport/relationships/index.ts b/packages/runtime/src/useCases/transport/relationships/index.ts index 2c6f0477d..0350a03bd 100644 --- a/packages/runtime/src/useCases/transport/relationships/index.ts +++ b/packages/runtime/src/useCases/transport/relationships/index.ts @@ -1,10 +1,14 @@ export * from "./AcceptRelationship"; +export * from "./AcceptRelationshipReactivation"; export * from "./CreateRelationship"; export * from "./GetAttributesForRelationship"; export * from "./GetRelationship"; export * from "./GetRelationshipByAddress"; export * from "./GetRelationships"; export * from "./RejectRelationship"; +export * from "./RejectRelationshipReactivation"; export * from "./RelationshipMapper"; +export * from "./RequestRelationshipReactivation"; export * from "./RevokeRelationship"; +export * from "./RevokeRelationshipReactivation"; export * from "./TerminateRelationship"; diff --git a/packages/runtime/test/lib/testUtils.ts b/packages/runtime/test/lib/testUtils.ts index fb5da9ec0..c4c741ee5 100644 --- a/packages/runtime/test/lib/testUtils.ts +++ b/packages/runtime/test/lib/testUtils.ts @@ -346,6 +346,25 @@ export async function ensureActiveRelationship(sTransportServices: TransportServ return (await sTransportServices.relationships.getRelationships({})).value[0]; } +export async function ensurePendingRelationship(sTransportServices: TransportServices, rTransportServices: TransportServices): Promise { + const rTransportServicesAddress = (await rTransportServices.account.getIdentityInfo()).value.address; + const relationships = (await sTransportServices.relationships.getRelationships({ query: { peer: rTransportServicesAddress } })).value; + if (relationships.length === 0) { + const template = await exchangeTemplate(sTransportServices, rTransportServices, {}); + + const createRelationshipResponse = await rTransportServices.relationships.createRelationship({ + templateId: template.id, + creationContent: { a: "b" } + }); + expect(createRelationshipResponse).toBeSuccessful(); + + const relationships = await syncUntilHasRelationships(sTransportServices); + expect(relationships).toHaveLength(1); + } + + return (await sTransportServices.relationships.getRelationships({})).value[0]; +} + export async function exchangeAndAcceptRequestByMessage( sender: TestRuntimeServices, recipient: TestRuntimeServices, diff --git a/packages/runtime/test/transport/relationships.test.ts b/packages/runtime/test/transport/relationships.test.ts index f2a838948..43c29a1b3 100644 --- a/packages/runtime/test/transport/relationships.test.ts +++ b/packages/runtime/test/transport/relationships.test.ts @@ -6,10 +6,17 @@ import { LocalAttributeDTO, OwnSharedAttributeSucceededEvent, PeerSharedAttributeSucceededEvent, + RelationshipAuditLogEntryReason, + RelationshipChangedEvent, RelationshipDTO, + RelationshipReactivationCompletedEvent, + RelationshipReactivationRequestedEvent, RelationshipStatus } from "../../src"; import { + QueryParamConditions, + RuntimeServiceProvider, + TestRuntimeServices, createTemplate, ensureActiveRelationship, exchangeMessageWithRequest, @@ -18,11 +25,8 @@ import { executeFullCreateAndShareRepositoryAttributeFlow, executeFullSucceedRepositoryAttributeAndNotifyPeerFlow, getRelationship, - QueryParamConditions, - RuntimeServiceProvider, syncUntilHasMessageWithNotification, - syncUntilHasRelationships, - TestRuntimeServices + syncUntilHasRelationships } from "../lib"; const serviceProvider = new RuntimeServiceProvider(); @@ -37,6 +41,8 @@ beforeAll(async () => { afterAll(() => serviceProvider.stop()); describe("Create Relationship", () => { + let relationshipId: string; + test("load relationship Template in connector 2", async () => { const template = await createTemplate(services1.transport); @@ -44,7 +50,7 @@ describe("Create Relationship", () => { expect(response).toBeSuccessful(); }); - test("execute relationship creation flow", async () => { + test("create pending relationship", async () => { const templateId = (await exchangeTemplate(services1.transport, services2.transport)).id; const createRelationshipResponse = await services2.transport.relationships.createRelationship({ @@ -55,31 +61,94 @@ describe("Create Relationship", () => { const relationships1 = await syncUntilHasRelationships(services1.transport); expect(relationships1).toHaveLength(1); - const relationshipId = relationships1[0].id; + relationshipId = relationships1[0].id; + }); - const acceptRelationshipResponse = await services1.transport.relationships.acceptRelationship({ - relationshipId + describe("tests on pending relationship", () => { + test("should not accept relationship sent by yourself", async () => { + expect(await services2.transport.relationships.acceptRelationship({ relationshipId })).toBeAnError(/.*/, "error.transport.relationships.operationOnlyAllowedForPeer"); + }); + test("should not reject relationship sent by yourself", async () => { + expect(await services2.transport.relationships.rejectRelationship({ relationshipId })).toBeAnError(/.*/, "error.transport.relationships.operationOnlyAllowedForPeer"); }); - expect(acceptRelationshipResponse).toBeSuccessful(); + test("should not revoke relationship sent by yourself", async () => { + expect(await services1.transport.relationships.revokeRelationship({ relationshipId })).toBeAnError(/.*/, "error.transport.relationships.operationOnlyAllowedForPeer"); + }); + + test("execute further relationship creation flow", async () => { + const acceptRelationshipResponse = await services1.transport.relationships.acceptRelationship({ + relationshipId + }); + expect(acceptRelationshipResponse).toBeSuccessful(); - const relationships1Response = await services1.transport.relationships.getRelationships({}); - expect(relationships1Response).toBeSuccessful(); - expect(relationships1Response.value).toHaveLength(1); + const relationships1Response = await services1.transport.relationships.getRelationships({}); + expect(relationships1Response).toBeSuccessful(); + expect(relationships1Response.value).toHaveLength(1); - const relationships2 = await syncUntilHasRelationships(services2.transport); - expect(relationships2).toHaveLength(1); + const relationships2 = await syncUntilHasRelationships(services2.transport); + expect(relationships2).toHaveLength(1); - const relationships2Response = await services2.transport.relationships.getRelationships({}); - expect(relationships2Response).toBeSuccessful(); - expect(relationships2Response.value).toHaveLength(1); + const relationships2Response = await services2.transport.relationships.getRelationships({}); + expect(relationships2Response).toBeSuccessful(); + expect(relationships2Response.value).toHaveLength(1); - const relationship1Response = await services1.transport.relationships.getRelationship({ id: relationshipId }); - expect(relationship1Response).toBeSuccessful(); - expect(relationship1Response.value.status).toBe("Active"); + const relationship1Response = await services1.transport.relationships.getRelationship({ id: relationshipId }); + expect(relationship1Response).toBeSuccessful(); + expect(relationship1Response.value.status).toBe("Active"); - const relationship2Response = await services2.transport.relationships.getRelationship({ id: relationshipId }); - expect(relationship2Response).toBeSuccessful(); - expect(relationship2Response.value.status).toBe("Active"); + const relationship2Response = await services2.transport.relationships.getRelationship({ id: relationshipId }); + expect(relationship2Response).toBeSuccessful(); + expect(relationship2Response.value.status).toBe("Active"); + }); + }); +}); + +describe("Relationship status validations on active relationship", () => { + let relationshipId: string; + beforeAll(async () => { + await ensureActiveRelationship(services1.transport, services2.transport); + const relationship = await getRelationship(services1.transport); + relationshipId = relationship.id; + }); + + test("should not request a relationship reactivation", async () => { + expect(await services1.transport.relationships.requestRelationshipReactivation({ relationshipId })).toBeAnError( + /.*/, + "error.transport.relationships.wrongRelationshipStatus" + ); + }); + + test("should not accept a relationship reactivation", async () => { + expect(await services1.transport.relationships.acceptRelationshipReactivation({ relationshipId })).toBeAnError( + /.*/, + "error.transport.relationships.wrongRelationshipStatus" + ); + }); + + test("should not reject a relationship reactivation", async () => { + expect(await services1.transport.relationships.rejectRelationshipReactivation({ relationshipId })).toBeAnError( + /.*/, + "error.transport.relationships.wrongRelationshipStatus" + ); + }); + + test("should not revoke a relationship reactivation", async () => { + expect(await services1.transport.relationships.revokeRelationshipReactivation({ relationshipId })).toBeAnError( + /.*/, + "error.transport.relationships.wrongRelationshipStatus" + ); + }); + + test("should not accept a relationship", async () => { + expect(await services1.transport.relationships.acceptRelationship({ relationshipId })).toBeAnError(/.*/, "error.transport.relationships.wrongRelationshipStatus"); + }); + + test("should not reject a relationship", async () => { + expect(await services1.transport.relationships.rejectRelationship({ relationshipId })).toBeAnError(/.*/, "error.transport.relationships.wrongRelationshipStatus"); + }); + + test("should not revoke a relationship", async () => { + expect(await services1.transport.relationships.rejectRelationship({ relationshipId })).toBeAnError(/.*/, "error.transport.relationships.wrongRelationshipStatus"); }); }); @@ -316,8 +385,18 @@ describe("RelationshipTermination", () => { test("relationship status is terminated", async () => { expect(terminationResult).toBeSuccessful(); - const result = (await services1.transport.relationships.getRelationship({ id: relationshipId })).value; - expect(result.status).toBe(RelationshipStatus.Terminated); + await expect(services1.eventBus).toHavePublished( + RelationshipChangedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.Termination + ); + expect(terminationResult.value.status).toBe(RelationshipStatus.Terminated); + + const syncedRelationship = (await syncUntilHasRelationships(services2.transport))[0]; + expect(syncedRelationship.status).toBe(RelationshipStatus.Terminated); + await expect(services2.eventBus).toHavePublished( + RelationshipChangedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.Termination + ); }); test("should not send a message", async () => { @@ -335,7 +414,6 @@ describe("RelationshipTermination", () => { }); test("should not decide a request", async () => { - await syncUntilHasRelationships(services2.transport); const incomingRequest = (await services2.eventBus.waitForEvent(IncomingRequestReceivedEvent)).data; const canAcceptResult = (await services2.consumption.incomingRequests.canAccept({ requestId: incomingRequest.id, items: [{ accept: true }] })).value; @@ -370,4 +448,219 @@ describe("RelationshipTermination", () => { }); expect(result).toBeAnError(/.*/, "error.transport.challenges.challengeTypeRequiresActiveRelationship"); }); + + describe("Validating relationship operations on terminated relationship", () => { + test("should not terminate a relationship in status terminated again", async () => { + expect(await services1.transport.relationships.terminateRelationship({ relationshipId })).toBeAnError(/.*/, "error.transport.relationships.wrongRelationshipStatus"); + }); + + test("reactivation acceptance should fail without reactivation request", async () => { + expect(await services1.transport.relationships.acceptRelationshipReactivation({ relationshipId })).toBeAnError( + /.*/, + "error.transport.relationships.reactivationNotRequested" + ); + }); + + test("reactivation revocation should fail without reactivation request", async () => { + expect(await services1.transport.relationships.revokeRelationshipReactivation({ relationshipId })).toBeAnError( + /.*/, + "error.transport.relationships.reactivationNotRequested" + ); + }); + + test("reactivation rejection should fail without reactivation request", async () => { + expect(await services1.transport.relationships.rejectRelationshipReactivation({ relationshipId })).toBeAnError( + /.*/, + "error.transport.relationships.reactivationNotRequested" + ); + }); + }); + + describe("Validating relationship operations on terminated relationship with requested reactivation", () => { + beforeAll(async () => { + await services1.transport.relationships.requestRelationshipReactivation({ relationshipId }); + await syncUntilHasRelationships(services2.transport); + }); + + afterAll(async () => { + await services1.transport.relationships.revokeRelationshipReactivation({ relationshipId }); + await syncUntilHasRelationships(services2.transport); + }); + + test("reactivation acceptance should fail when the wrong side accepts it", async () => { + expect(await services1.transport.relationships.acceptRelationshipReactivation({ relationshipId })).toBeAnError( + /.*/, + "error.transport.relationships.operationOnlyAllowedForPeer" + ); + }); + + test("reactivation rejection should fail when the wrong side rejects it", async () => { + expect(await services1.transport.relationships.rejectRelationshipReactivation({ relationshipId })).toBeAnError( + /.*/, + "error.transport.relationships.operationOnlyAllowedForPeer" + ); + }); + + test("reactivation revocation should fail when the wrong side revokes it", async () => { + expect(await services2.transport.relationships.revokeRelationshipReactivation({ relationshipId })).toBeAnError( + /.*/, + "error.transport.relationships.operationOnlyAllowedForPeer" + ); + }); + + test("requesting reactivation twice should fail", async () => { + expect(await services1.transport.relationships.requestRelationshipReactivation({ relationshipId })).toBeAnError( + "You have already requested the reactivation", + "error.transport.relationships.reactivationAlreadyRequested" + ); + expect(await services2.transport.relationships.requestRelationshipReactivation({ relationshipId })).toBeAnError( + "Your peer has already requested the reactivation", + "error.transport.relationships.reactivationAlreadyRequested" + ); + }); + }); + + test("should request the relationship reactivation and then revoke it", async () => { + const reactivationRequestResult = await services1.transport.relationships.requestRelationshipReactivation({ relationshipId }); + expect(reactivationRequestResult).toBeSuccessful(); + await expect(services1.eventBus).toHavePublished( + RelationshipChangedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.ReactivationRequested + ); + await expect(services1.eventBus).toHavePublished( + RelationshipReactivationRequestedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.ReactivationRequested + ); + + const revocationResult = await services1.transport.relationships.revokeRelationshipReactivation({ relationshipId }); + expect(revocationResult).toBeSuccessful(); + expect(revocationResult.value.status).toBe(RelationshipStatus.Terminated); + await expect(services1.eventBus).toHavePublished( + RelationshipChangedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.RevocationOfReactivation + ); + await expect(services1.eventBus).toHavePublished( + RelationshipReactivationCompletedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.RevocationOfReactivation + ); + + const relationship2 = (await syncUntilHasRelationships(services2.transport))[0]; + expect(relationship2.status).toBe(RelationshipStatus.Terminated); + await expect(services2.eventBus).toHavePublished( + RelationshipChangedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.RevocationOfReactivation + ); + await expect(services2.eventBus).toHavePublished( + RelationshipReactivationCompletedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.RevocationOfReactivation + ); + }); + + test("should get the relationship reactivation request from the peer and reject it", async () => { + await services1.transport.relationships.requestRelationshipReactivation({ relationshipId }); + + await syncUntilHasRelationships(services2.transport); + await expect(services2.eventBus).toHavePublished( + RelationshipChangedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.ReactivationRequested + ); + await expect(services2.eventBus).toHavePublished( + RelationshipReactivationRequestedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.ReactivationRequested + ); + const rejectionResult = await services2.transport.relationships.rejectRelationshipReactivation({ relationshipId }); + expect(rejectionResult).toBeSuccessful(); + expect(rejectionResult.value.status).toBe(RelationshipStatus.Terminated); + await expect(services2.eventBus).toHavePublished( + RelationshipChangedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.RejectionOfReactivation + ); + await expect(services2.eventBus).toHavePublished( + RelationshipReactivationCompletedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.RejectionOfReactivation + ); + + const relationship1 = (await syncUntilHasRelationships(services1.transport))[0]; + expect(relationship1.status).toBe(RelationshipStatus.Terminated); + await expect(services1.eventBus).toHavePublished( + RelationshipChangedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.RejectionOfReactivation + ); + await expect(services1.eventBus).toHavePublished( + RelationshipReactivationCompletedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.RejectionOfReactivation + ); + }); + + test("should get the relationship reactivation request from the peer and accept it", async () => { + await services1.transport.relationships.requestRelationshipReactivation({ relationshipId }); + + await syncUntilHasRelationships(services2.transport); + await expect(services2.eventBus).toHavePublished( + RelationshipChangedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.ReactivationRequested + ); + await expect(services2.eventBus).toHavePublished( + RelationshipReactivationRequestedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.ReactivationRequested + ); + const acceptanceResult = await services2.transport.relationships.acceptRelationshipReactivation({ relationshipId }); + expect(acceptanceResult).toBeSuccessful(); + expect(acceptanceResult.value.status).toBe(RelationshipStatus.Active); + await expect(services2.eventBus).toHavePublished( + RelationshipChangedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.AcceptanceOfReactivation + ); + await expect(services2.eventBus).toHavePublished( + RelationshipReactivationCompletedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.AcceptanceOfReactivation + ); + + const relationship1 = (await syncUntilHasRelationships(services1.transport))[0]; + expect(relationship1.status).toBe(RelationshipStatus.Active); + await expect(services1.eventBus).toHavePublished( + RelationshipChangedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.AcceptanceOfReactivation + ); + await expect(services1.eventBus).toHavePublished( + RelationshipReactivationCompletedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.AcceptanceOfReactivation + ); + }); +}); + +describe("Relationship existence check", () => { + const fakeRelationshipId = "REL00000000000000000"; + + test("should not accept a relationship", async function () { + expect(await services1.transport.relationships.acceptRelationship({ relationshipId: fakeRelationshipId })).toBeAnError(/.*/, "error.runtime.recordNotFound"); + }); + + test("should not reject a relationship", async function () { + expect(await services1.transport.relationships.rejectRelationship({ relationshipId: fakeRelationshipId })).toBeAnError(/.*/, "error.runtime.recordNotFound"); + }); + + test("should not revoke a relationship", async function () { + expect(await services1.transport.relationships.revokeRelationship({ relationshipId: fakeRelationshipId })).toBeAnError(/.*/, "error.runtime.recordNotFound"); + }); + + test("should not terminate a relationship", async function () { + expect(await services1.transport.relationships.terminateRelationship({ relationshipId: fakeRelationshipId })).toBeAnError(/.*/, "error.runtime.recordNotFound"); + }); + + test("should not request a relationship reactivation", async function () { + expect(await services1.transport.relationships.requestRelationshipReactivation({ relationshipId: fakeRelationshipId })).toBeAnError(/.*/, "error.runtime.recordNotFound"); + }); + + test("should not accept a relationship reactivation", async function () { + expect(await services1.transport.relationships.acceptRelationshipReactivation({ relationshipId: fakeRelationshipId })).toBeAnError(/.*/, "error.runtime.recordNotFound"); + }); + + test("should not reject a relationship reactivation", async function () { + expect(await services1.transport.relationships.rejectRelationshipReactivation({ relationshipId: fakeRelationshipId })).toBeAnError(/.*/, "error.runtime.recordNotFound"); + }); + + test("should not revoke a relationship reactivation", async function () { + expect(await services1.transport.relationships.revokeRelationshipReactivation({ relationshipId: fakeRelationshipId })).toBeAnError(/.*/, "error.runtime.recordNotFound"); + }); }); diff --git a/packages/transport/src/core/CoreErrors.ts b/packages/transport/src/core/CoreErrors.ts index 958e3d82b..d8774be87 100644 --- a/packages/transport/src/core/CoreErrors.ts +++ b/packages/transport/src/core/CoreErrors.ts @@ -3,8 +3,23 @@ import { RelationshipStatus } from "../modules"; import { CoreError } from "./CoreError"; class Relationships { - public wrongRelationshipStatus(status: RelationshipStatus) { - return new CoreError("error.transport.relationships.wrongRelationshipStatus", `The relationship has the wrong status (${status}) to run this operation`); + public operationOnlyAllowedForPeer(message: string) { + return new CoreError("error.transport.relationships.operationOnlyAllowedForPeer", message); + } + + public wrongRelationshipStatus(relationshipId: string, status: RelationshipStatus) { + return new CoreError( + "error.transport.relationships.wrongRelationshipStatus", + `The relationship '${relationshipId}' has the wrong status (${status}) to run this operation` + ); + } + + public reactivationNotRequested(relationshipId: string) { + return new CoreError("error.transport.relationships.reactivationNotRequested", `The relationship '${relationshipId}' has no reactivation request to respond to.`); + } + + public reactivationAlreadyRequested(message: string) { + return new CoreError("error.transport.relationships.reactivationAlreadyRequested", message); } } diff --git a/packages/transport/src/events/RelationshipReactivationCompletedEvent.ts b/packages/transport/src/events/RelationshipReactivationCompletedEvent.ts new file mode 100644 index 000000000..258bc221d --- /dev/null +++ b/packages/transport/src/events/RelationshipReactivationCompletedEvent.ts @@ -0,0 +1,10 @@ +import { Relationship } from "../modules/relationships/local/Relationship"; +import { TransportDataEvent } from "./TransportDataEvent"; + +export class RelationshipReactivationCompletedEvent extends TransportDataEvent { + public static readonly namespace = "transport.relationshipReactivationCompleted"; + + public constructor(eventTargetAddress: string, data: Relationship) { + super(RelationshipReactivationCompletedEvent.namespace, eventTargetAddress, data); + } +} diff --git a/packages/transport/src/events/RelationshipReactivationRequestedEvent.ts b/packages/transport/src/events/RelationshipReactivationRequestedEvent.ts new file mode 100644 index 000000000..5fdecadbf --- /dev/null +++ b/packages/transport/src/events/RelationshipReactivationRequestedEvent.ts @@ -0,0 +1,10 @@ +import { Relationship } from "../modules/relationships/local/Relationship"; +import { TransportDataEvent } from "./TransportDataEvent"; + +export class RelationshipReactivationRequestedEvent extends TransportDataEvent { + public static readonly namespace = "transport.relationshipReactivationRequested"; + + public constructor(eventTargetAddress: string, data: Relationship) { + super(RelationshipReactivationRequestedEvent.namespace, eventTargetAddress, data); + } +} diff --git a/packages/transport/src/events/index.ts b/packages/transport/src/events/index.ts index f4bd5ef95..893681fd9 100644 --- a/packages/transport/src/events/index.ts +++ b/packages/transport/src/events/index.ts @@ -5,4 +5,6 @@ export * from "./MessageSentEvent"; export * from "./MessageWasReadAtChangedEvent"; export * from "./PeerRelationshipTemplateLoadedEvent"; export * from "./RelationshipChangedEvent"; +export * from "./RelationshipReactivationCompletedEvent"; +export * from "./RelationshipReactivationRequestedEvent"; export * from "./TransportDataEvent"; diff --git a/packages/transport/src/modules/relationships/RelationshipsController.ts b/packages/transport/src/modules/relationships/RelationshipsController.ts index 18f69889d..50ea65069 100644 --- a/packages/transport/src/modules/relationships/RelationshipsController.ts +++ b/packages/transport/src/modules/relationships/RelationshipsController.ts @@ -7,7 +7,7 @@ import { CoreErrors } from "../../core/CoreErrors"; import { CoreUtil } from "../../core/CoreUtil"; import { DbCollectionName } from "../../core/DbCollectionName"; import { TransportIds } from "../../core/TransportIds"; -import { RelationshipChangedEvent } from "../../events"; +import { RelationshipChangedEvent, RelationshipReactivationCompletedEvent, RelationshipReactivationRequestedEvent } from "../../events"; import { AccountController } from "../accounts/AccountController"; import { Identity } from "../accounts/data/Identity"; import { RelationshipTemplate } from "../relationshipTemplates/local/RelationshipTemplate"; @@ -20,6 +20,7 @@ import { CachedRelationship } from "./local/CachedRelationship"; import { Relationship } from "./local/Relationship"; import { RelationshipAuditLog } from "./local/RelationshipAuditLog"; import { ISendRelationshipParameters, SendRelationshipParameters } from "./local/SendRelationshipParameters"; +import { RelationshipAuditLogEntryReason } from "./transmission/RelationshipAuditLog"; import { RelationshipStatus } from "./transmission/RelationshipStatus"; import { RelationshipCreationContentCipher } from "./transmission/requests/RelationshipCreationContentCipher"; import { RelationshipCreationContentSigned } from "./transmission/requests/RelationshipCreationContentSigned"; @@ -187,47 +188,123 @@ export class RelationshipsController extends TransportController { } public async accept(relationshipId: CoreId): Promise { - const relationship = await this.getRelationship(relationshipId); - if (!relationship) { - throw CoreErrors.general.recordNotFound("Relationship", relationshipId.toString()); - } - if (relationship.status !== RelationshipStatus.Pending) { - throw CoreErrors.relationships.wrongRelationshipStatus(relationship.status); + const relationship = await this.getRelationshipWithCache(relationshipId); + this.assertRelationshipStatus(relationship, RelationshipStatus.Pending); + + const lastAuditLogEntry = relationship.cache.auditLog[relationship.cache.auditLog.length - 1]; + if (!lastAuditLogEntry.createdBy.equals(relationship.peer.address)) { + throw CoreErrors.relationships.operationOnlyAllowedForPeer(`Only your peer can accept the relationship ${relationshipId.toString()}`); } - return await this.completeStateTransition(RelationshipStatus.Active, relationshipId); + return await this.completeOperationWithBackboneCall(RelationshipAuditLogEntryReason.AcceptanceOfCreation, relationshipId); } public async reject(relationshipId: CoreId): Promise { - const relationship = await this.getRelationship(relationshipId); - if (!relationship) { - throw CoreErrors.general.recordNotFound("Relationship", relationshipId.toString()); - } - if (relationship.status !== RelationshipStatus.Pending) { - throw CoreErrors.relationships.wrongRelationshipStatus(relationship.status); + const relationship = await this.getRelationshipWithCache(relationshipId); + this.assertRelationshipStatus(relationship, RelationshipStatus.Pending); + + const lastAuditLogEntry = relationship.cache.auditLog[relationship.cache.auditLog.length - 1]; + if (!lastAuditLogEntry.createdBy.equals(relationship.peer.address)) { + throw CoreErrors.relationships.operationOnlyAllowedForPeer(`Only your peer can reject the relationship ${relationshipId.toString()}. Revoke the relationship instead.`); } - return await this.completeStateTransition(RelationshipStatus.Rejected, relationshipId); + return await this.completeOperationWithBackboneCall(RelationshipAuditLogEntryReason.RejectionOfCreation, relationshipId); } public async revoke(relationshipId: CoreId): Promise { - const relationship = await this.getRelationship(relationshipId); - if (!relationship) { - throw CoreErrors.general.recordNotFound("Relationship", relationshipId.toString()); - } - if (relationship.status !== RelationshipStatus.Pending) { - throw CoreErrors.relationships.wrongRelationshipStatus(relationship.status); + const relationship = await this.getRelationshipWithCache(relationshipId); + this.assertRelationshipStatus(relationship, RelationshipStatus.Pending); + + const lastAuditLogEntry = relationship.cache.auditLog[relationship.cache.auditLog.length - 1]; + if (lastAuditLogEntry.createdBy.equals(relationship.peer.address)) { + throw CoreErrors.relationships.operationOnlyAllowedForPeer(`Only your peer can revoke the relationship ${relationshipId.toString()}. Reject the relationship instead.`); } - return await this.completeStateTransition(RelationshipStatus.Revoked, relationshipId); + return await this.completeOperationWithBackboneCall(RelationshipAuditLogEntryReason.RevocationOfCreation, relationshipId); } public async terminate(relationshipId: CoreId): Promise { - const relationship = await this.getRelationship(relationshipId); - if (!relationship) { - throw CoreErrors.general.recordNotFound("Relationship", relationshipId.toString()); + const relationship = await this.getRelationshipWithCache(relationshipId); + this.assertRelationshipStatus(relationship, RelationshipStatus.Active); + + return await this.completeOperationWithBackboneCall(RelationshipAuditLogEntryReason.Termination, relationshipId); + } + + public async requestReactivation(relationshipId: CoreId): Promise { + const relationship = await this.getRelationshipWithCache(relationshipId); + this.assertRelationshipStatus(relationship, RelationshipStatus.Terminated); + + const lastAuditLogEntry = relationship.cache.auditLog[relationship.cache.auditLog.length - 1]; + if (lastAuditLogEntry.reason === RelationshipAuditLogEntryReason.ReactivationRequested) { + if (lastAuditLogEntry.createdBy.equals(relationship.peer.address)) { + throw CoreErrors.relationships.reactivationAlreadyRequested( + `Your peer has already requested the reactivation of the relationship ${relationshipId.toString()}. You can accept the reactivation instead.` + ); + } + throw CoreErrors.relationships.reactivationAlreadyRequested(`You have already requested the reactivation of the relationship ${relationshipId.toString()}.`); + } + + return await this.completeOperationWithBackboneCall(RelationshipAuditLogEntryReason.ReactivationRequested, relationshipId); + } + + public async rejectReactivation(relationshipId: CoreId): Promise { + const relationship = await this.getRelationshipWithCache(relationshipId); + this.assertRelationshipStatus(relationship, RelationshipStatus.Terminated); + + const lastAuditLogEntry = relationship.cache.auditLog[relationship.cache.auditLog.length - 1]; + if (lastAuditLogEntry.reason !== RelationshipAuditLogEntryReason.ReactivationRequested) { + throw CoreErrors.relationships.reactivationNotRequested(relationshipId.toString()); + } + + if (!lastAuditLogEntry.createdBy.equals(relationship.peer.address)) { + throw CoreErrors.relationships.operationOnlyAllowedForPeer( + `Only your peer can reject the reactivation of the relationship ${relationshipId.toString()}. Revoke the relationship reactivation instead.` + ); + } + return await this.completeOperationWithBackboneCall(RelationshipAuditLogEntryReason.RejectionOfReactivation, relationshipId); + } + + public async revokeReactivation(relationshipId: CoreId): Promise { + const relationship = await this.getRelationshipWithCache(relationshipId); + this.assertRelationshipStatus(relationship, RelationshipStatus.Terminated); + + const lastAuditLogEntry = relationship.cache.auditLog[relationship.cache.auditLog.length - 1]; + if (lastAuditLogEntry.reason !== RelationshipAuditLogEntryReason.ReactivationRequested) { + throw CoreErrors.relationships.reactivationNotRequested(relationshipId.toString()); + } + if (lastAuditLogEntry.createdBy.equals(relationship.peer.address)) { + throw CoreErrors.relationships.operationOnlyAllowedForPeer( + `Only your peer can revoke the reactivation of the relationship ${relationshipId.toString()}. Reject the relationship reactivation instead.` + ); + } + return await this.completeOperationWithBackboneCall(RelationshipAuditLogEntryReason.RevocationOfReactivation, relationshipId); + } + + public async acceptReactivation(relationshipId: CoreId): Promise { + const relationship = await this.getRelationshipWithCache(relationshipId); + this.assertRelationshipStatus(relationship, RelationshipStatus.Terminated); + + const lastAuditLogEntry = relationship.cache.auditLog[relationship.cache.auditLog.length - 1]; + if (lastAuditLogEntry.reason !== RelationshipAuditLogEntryReason.ReactivationRequested) { + throw CoreErrors.relationships.reactivationNotRequested(relationshipId.toString()); } - if (relationship.status !== RelationshipStatus.Active) { - throw CoreErrors.relationships.wrongRelationshipStatus(relationship.status); + if (!lastAuditLogEntry.createdBy.equals(relationship.peer.address)) { + throw CoreErrors.relationships.operationOnlyAllowedForPeer(`Only your peer can accept the reactivation of the relationship ${relationshipId.toString()}.`); } - return await this.completeStateTransition(RelationshipStatus.Terminated, relationshipId); + + return await this.completeOperationWithBackboneCall(RelationshipAuditLogEntryReason.AcceptanceOfReactivation, relationshipId); + } + + private async getRelationshipWithCache(id: CoreId): Promise { + const relationship = await this.getRelationship(id); + if (!relationship) throw CoreErrors.general.recordNotFound(Relationship, id.toString()); + if (!relationship.cache) await this.updateCacheOfRelationship(relationship); + if (!relationship.cache) throw this.newCacheEmptyError(Relationship, id.toString()); + + return relationship as Relationship & { cache: CachedRelationship }; + } + + private assertRelationshipStatus(relationship: Relationship, status: RelationshipStatus) { + if (relationship.status === status) return; + + throw CoreErrors.relationships.wrongRelationshipStatus(relationship.id.toString(), relationship.status); } private async updateCacheOfRelationship(relationship: Relationship, response?: BackboneRelationship) { @@ -297,7 +374,7 @@ export class RelationshipsController extends TransportController { } @log() - private async updatePendingRelationshipWithPeerResponse(relationshipDoc: any): Promise { + private async updateRelationshipWithPeerResponse(relationshipDoc: any): Promise { const relationship = Relationship.from(relationshipDoc); const backboneRelationship = (await this.client.getRelationship(relationship.id.toString())).value; @@ -369,7 +446,7 @@ export class RelationshipsController extends TransportController { return relationship; } - public async applyRelationshipStatusChangedEvent(relationshipId: string): Promise { + public async applyRelationshipChangedEvent(relationshipId: string): Promise { let relationshipDoc = await this.relationships.read(relationshipId); if (!relationshipDoc) { const newRelationship = await this.createNewRelationshipByIncomingCreation(relationshipId); @@ -380,7 +457,7 @@ export class RelationshipsController extends TransportController { relationshipDoc = await this.relationships.read(relationshipId); } - return await this.updatePendingRelationshipWithPeerResponse(relationshipDoc); + return await this.updateRelationshipWithPeerResponse(relationshipDoc); } private async prepareCreationResponseContent(relationship: Relationship) { @@ -409,7 +486,7 @@ export class RelationshipsController extends TransportController { } @log() - private async completeStateTransition(targetStatus: RelationshipStatus, id: CoreId) { + private async completeOperationWithBackboneCall(operation: RelationshipAuditLogEntryReason, id: CoreId) { const relationshipDoc = await this.relationships.read(id.toString()); if (!relationshipDoc) { throw CoreErrors.general.recordNotFound(Relationship, id.toString()); @@ -426,35 +503,64 @@ export class RelationshipsController extends TransportController { } let backboneResponse: BackbonePutRelationshipsResponse; - switch (targetStatus) { - case RelationshipStatus.Active: + switch (operation) { + case RelationshipAuditLogEntryReason.AcceptanceOfCreation: const encryptedContent = await this.prepareCreationResponseContent(relationship); backboneResponse = (await this.client.acceptRelationship(id.toString(), { creationResponseContent: encryptedContent })).value; break; - case RelationshipStatus.Rejected: + case RelationshipAuditLogEntryReason.RejectionOfCreation: backboneResponse = (await this.client.rejectRelationship(id.toString())).value; break; - case RelationshipStatus.Revoked: + case RelationshipAuditLogEntryReason.RevocationOfCreation: backboneResponse = (await this.client.revokeRelationship(id.toString())).value; break; - case RelationshipStatus.Terminated: + case RelationshipAuditLogEntryReason.Termination: backboneResponse = (await this.client.terminateRelationship(id.toString())).value; break; + case RelationshipAuditLogEntryReason.ReactivationRequested: + backboneResponse = (await this.client.reactivateRelationship(id.toString())).value; + break; + + case RelationshipAuditLogEntryReason.AcceptanceOfReactivation: + backboneResponse = (await this.client.acceptRelationshipReactivation(id.toString())).value; + break; + + case RelationshipAuditLogEntryReason.RejectionOfReactivation: + backboneResponse = (await this.client.rejectRelationshipReactivation(id.toString())).value; + break; + + case RelationshipAuditLogEntryReason.RevocationOfReactivation: + backboneResponse = (await this.client.revokeRelationshipReactivation(id.toString())).value; + break; + default: - throw new TransportError("target change status not supported"); + throw new TransportError("operation not supported"); } relationship.status = backboneResponse.status; relationship.cache.auditLog = RelationshipAuditLog.fromBackboneAuditLog(backboneResponse.auditLog); await this.relationships.update(relationshipDoc, relationship); + this.publishEventAfterCompletedOperation(operation, relationship); + return relationship; + } + private publishEventAfterCompletedOperation(operation: RelationshipAuditLogEntryReason, relationship: Relationship) { this.eventBus.publish(new RelationshipChangedEvent(this.parent.identity.address.toString(), relationship)); - - return relationship; + switch (operation) { + case RelationshipAuditLogEntryReason.ReactivationRequested: + this.eventBus.publish(new RelationshipReactivationRequestedEvent(this.parent.identity.address.toString(), relationship)); + break; + case RelationshipAuditLogEntryReason.RevocationOfReactivation: + case RelationshipAuditLogEntryReason.AcceptanceOfReactivation: + case RelationshipAuditLogEntryReason.RejectionOfReactivation: + this.eventBus.publish(new RelationshipReactivationCompletedEvent(this.parent.identity.address.toString(), relationship)); + break; + default: + } } } diff --git a/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts b/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts index 53cd80edd..a05ddda60 100644 --- a/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts +++ b/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts @@ -26,6 +26,22 @@ export class RelationshipClient extends RESTClientAuthenticate { return await this.put(`/api/v1/Relationships/${relationshipId}/Terminate`, {}); } + public async reactivateRelationship(relationshipId: string): Promise> { + return await this.put(`/api/v1/Relationships/${relationshipId}/Reactivate`, {}); + } + + public async acceptRelationshipReactivation(relationshipId: string): Promise> { + return await this.put(`/api/v1/Relationships/${relationshipId}/Reactivate/Accept`, {}); + } + + public async rejectRelationshipReactivation(relationshipId: string): Promise> { + return await this.put(`/api/v1/Relationships/${relationshipId}/Reactivate/Reject`, {}); + } + + public async revokeRelationshipReactivation(relationshipId: string): Promise> { + return await this.put(`/api/v1/Relationships/${relationshipId}/Reactivate/Revoke`, {}); + } + public async getRelationships(request?: BackboneGetRelationshipsRequest): Promise>> { return await this.getPaged("/api/v1/Relationships", request); } diff --git a/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts b/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts index bf2451829..92297e817 100644 --- a/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts +++ b/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts @@ -16,5 +16,9 @@ export enum RelationshipAuditLogEntryReason { AcceptanceOfCreation = "AcceptanceOfCreation", RejectionOfCreation = "RejectionOfCreation", RevocationOfCreation = "RevocationOfCreation", - Termination = "Termination" + Termination = "Termination", + ReactivationRequested = "ReactivationRequested", + AcceptanceOfReactivation = "AcceptanceOfReactivation", + RejectionOfReactivation = "RejectionOfReactivation", + RevocationOfReactivation = "RevocationOfReactivation" } diff --git a/packages/transport/src/modules/sync/SyncController.ts b/packages/transport/src/modules/sync/SyncController.ts index 4b6d00e84..33263fbbe 100644 --- a/packages/transport/src/modules/sync/SyncController.ts +++ b/packages/transport/src/modules/sync/SyncController.ts @@ -3,22 +3,22 @@ import { log } from "@js-soft/ts-utils"; import { ControllerName, CoreDate, CoreError, CoreErrors, CoreId, RequestError, TransportController, TransportError, TransportLoggerFactory } from "../../core"; import { DependencyOverrides } from "../../core/DependencyOverrides"; import { AccountController } from "../accounts/AccountController"; +import { ChangedItems } from "./ChangedItems"; +import { DatawalletModificationMapper } from "./DatawalletModificationMapper"; +import { CacheFetcher, DatawalletModificationsProcessor } from "./DatawalletModificationsProcessor"; +import { SyncProgressReporter, SyncStep } from "./SyncCallback"; +import { WhatToSync } from "./WhatToSync"; import { BackboneDatawalletModification } from "./backbone/BackboneDatawalletModification"; import { BackboneSyncRun } from "./backbone/BackboneSyncRun"; import { CreateDatawalletModificationsRequestItem } from "./backbone/CreateDatawalletModifications"; import { FinalizeSyncRunRequestExternalEventResult } from "./backbone/FinalizeSyncRun"; import { StartSyncRunStatus, SyncRunType } from "./backbone/StartSyncRun"; import { ISyncClient, SyncClient } from "./backbone/SyncClient"; -import { ChangedItems } from "./ChangedItems"; import { ExternalEvent } from "./data/ExternalEvent"; -import { DatawalletModificationMapper } from "./DatawalletModificationMapper"; -import { CacheFetcher, DatawalletModificationsProcessor } from "./DatawalletModificationsProcessor"; import { ExternalEventProcessorRegistry } from "./externalEventProcessors"; import { DatawalletModification } from "./local/DatawalletModification"; import { DeviceMigrations } from "./migrations/DeviceMigrations"; import { IdentityMigrations } from "./migrations/IdentityMigrations"; -import { SyncProgressReporter, SyncStep } from "./SyncCallback"; -import { WhatToSync } from "./WhatToSync"; export class SyncController extends TransportController { private syncInfo: IDatabaseMap; diff --git a/packages/transport/src/modules/sync/externalEventProcessors/ExternalEventProcessorRegistry.ts b/packages/transport/src/modules/sync/externalEventProcessors/ExternalEventProcessorRegistry.ts index 9f2237646..0383239c4 100644 --- a/packages/transport/src/modules/sync/externalEventProcessors/ExternalEventProcessorRegistry.ts +++ b/packages/transport/src/modules/sync/externalEventProcessors/ExternalEventProcessorRegistry.ts @@ -4,6 +4,8 @@ import { IdentityDeletionProcessChangedEventProcessor } from "./IdentityDeletion import { IdentityDeletionProcessStartedEventProcessor } from "./IdentityDeletionProcessStartedEventProcessor"; import { MessageDeliveredExternalEventProcessor } from "./MessageDeliveredExternalEventProcessor"; import { MessageReceivedExternalEventProcessor } from "./MessageReceivedExternalEventProcessor"; +import { RelationshipReactivationCompletedExternalEventProcessor } from "./RelationshipReactivationCompletedExternalEventProcessor"; +import { RelationshipReactivationRequestedExternalEventProcessor } from "./RelationshipReactivationRequestedExternalEventProcessor"; import { RelationshipStatusChangedExternalEventProcessor } from "./RelationshipStatusChangedExternalEventProcessor"; export class ExternalEventProcessorRegistry { @@ -12,6 +14,8 @@ export class ExternalEventProcessorRegistry { this.registerProcessor("MessageReceived", MessageReceivedExternalEventProcessor); this.registerProcessor("MessageDelivered", MessageDeliveredExternalEventProcessor); this.registerProcessor("RelationshipStatusChanged", RelationshipStatusChangedExternalEventProcessor); + this.registerProcessor("RelationshipReactivationRequested", RelationshipReactivationRequestedExternalEventProcessor); + this.registerProcessor("RelationshipReactivationCompleted", RelationshipReactivationCompletedExternalEventProcessor); this.registerProcessor("IdentityDeletionProcessStarted", IdentityDeletionProcessStartedEventProcessor); this.registerProcessor("IdentityDeletionProcessStatusChanged", IdentityDeletionProcessChangedEventProcessor); } diff --git a/packages/transport/src/modules/sync/externalEventProcessors/RelationshipReactivationCompletedExternalEventProcessor.ts b/packages/transport/src/modules/sync/externalEventProcessors/RelationshipReactivationCompletedExternalEventProcessor.ts new file mode 100644 index 000000000..0a1328227 --- /dev/null +++ b/packages/transport/src/modules/sync/externalEventProcessors/RelationshipReactivationCompletedExternalEventProcessor.ts @@ -0,0 +1,22 @@ +import { Serializable, serialize, validate } from "@js-soft/ts-serval"; +import { RelationshipChangedEvent, RelationshipReactivationCompletedEvent } from "../../../events"; +import { Relationship } from "../../relationships/local/Relationship"; +import { ExternalEvent } from "../data/ExternalEvent"; +import { ExternalEventProcessor } from "./ExternalEventProcessor"; + +class RelationshipReactivationCompletedExternalEventData extends Serializable { + @serialize() + @validate() + public relationshipId: string; +} + +export class RelationshipReactivationCompletedExternalEventProcessor extends ExternalEventProcessor { + public override async execute(externalEvent: ExternalEvent): Promise { + const payload = RelationshipReactivationCompletedExternalEventData.fromAny(externalEvent.payload); + const relationship = await this.accountController.relationships.applyRelationshipChangedEvent(payload.relationshipId); + + this.eventBus.publish(new RelationshipReactivationCompletedEvent(this.ownAddress, relationship)); + this.eventBus.publish(new RelationshipChangedEvent(this.ownAddress, relationship)); + return relationship; + } +} diff --git a/packages/transport/src/modules/sync/externalEventProcessors/RelationshipReactivationRequestedExternalEventProcessor.ts b/packages/transport/src/modules/sync/externalEventProcessors/RelationshipReactivationRequestedExternalEventProcessor.ts new file mode 100644 index 000000000..9815ac52e --- /dev/null +++ b/packages/transport/src/modules/sync/externalEventProcessors/RelationshipReactivationRequestedExternalEventProcessor.ts @@ -0,0 +1,22 @@ +import { Serializable, serialize, validate } from "@js-soft/ts-serval"; +import { RelationshipChangedEvent, RelationshipReactivationRequestedEvent } from "../../../events"; +import { Relationship } from "../../relationships/local/Relationship"; +import { ExternalEvent } from "../data/ExternalEvent"; +import { ExternalEventProcessor } from "./ExternalEventProcessor"; + +class RelationshipReactivationRequestedExternalEventData extends Serializable { + @serialize() + @validate() + public relationshipId: string; +} + +export class RelationshipReactivationRequestedExternalEventProcessor extends ExternalEventProcessor { + public override async execute(externalEvent: ExternalEvent): Promise { + const payload = RelationshipReactivationRequestedExternalEventData.fromAny(externalEvent.payload); + const relationship = await this.accountController.relationships.applyRelationshipChangedEvent(payload.relationshipId); + + this.eventBus.publish(new RelationshipReactivationRequestedEvent(this.ownAddress, relationship)); + this.eventBus.publish(new RelationshipChangedEvent(this.ownAddress, relationship)); + return relationship; + } +} diff --git a/packages/transport/src/modules/sync/externalEventProcessors/RelationshipStatusChangedExternalEventProcessor.ts b/packages/transport/src/modules/sync/externalEventProcessors/RelationshipStatusChangedExternalEventProcessor.ts index 8c0386573..bedbb0453 100644 --- a/packages/transport/src/modules/sync/externalEventProcessors/RelationshipStatusChangedExternalEventProcessor.ts +++ b/packages/transport/src/modules/sync/externalEventProcessors/RelationshipStatusChangedExternalEventProcessor.ts @@ -13,13 +13,9 @@ class RelationshipStatusChangedExternalEventData extends Serializable { export class RelationshipStatusChangedExternalEventProcessor extends ExternalEventProcessor { public override async execute(externalEvent: ExternalEvent): Promise { const payload = RelationshipStatusChangedExternalEventData.fromAny(externalEvent.payload); - const relationship = await this.accountController.relationships.applyRelationshipStatusChangedEvent(payload.relationshipId); + const relationship = await this.accountController.relationships.applyRelationshipChangedEvent(payload.relationshipId); - if (relationship) { - this.eventBus.publish(new RelationshipChangedEvent(this.ownAddress, relationship)); - return relationship; - } - - return; + this.eventBus.publish(new RelationshipChangedEvent(this.ownAddress, relationship)); + return relationship; } } diff --git a/packages/transport/src/modules/sync/externalEventProcessors/index.ts b/packages/transport/src/modules/sync/externalEventProcessors/index.ts index c0529110f..0ef994097 100644 --- a/packages/transport/src/modules/sync/externalEventProcessors/index.ts +++ b/packages/transport/src/modules/sync/externalEventProcessors/index.ts @@ -2,4 +2,6 @@ export * from "./ExternalEventProcessorRegistry"; export * from "./IdentityDeletionProcessStartedEventProcessor"; export * from "./MessageDeliveredExternalEventProcessor"; export * from "./MessageReceivedExternalEventProcessor"; +export * from "./RelationshipReactivationCompletedExternalEventProcessor"; +export * from "./RelationshipReactivationRequestedExternalEventProcessor"; export * from "./RelationshipStatusChangedExternalEventProcessor"; diff --git a/packages/transport/test/end2end/End2End.test.ts b/packages/transport/test/end2end/End2End.test.ts index 1ac81f5c6..605ccfcc4 100644 --- a/packages/transport/test/end2end/End2End.test.ts +++ b/packages/transport/test/end2end/End2End.test.ts @@ -1,7 +1,7 @@ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; import { JSONWrapper, Serializable } from "@js-soft/ts-serval"; import { CoreBuffer } from "@nmshd/crypto"; -import { AccountController, CoreDate, FileReference, RelationshipStatus, TokenContentRelationshipTemplate, Transport } from "../../src"; +import { AccountController, CoreDate, CoreId, FileReference, RelationshipAuditLogEntryReason, RelationshipStatus, TokenContentRelationshipTemplate, Transport } from "../../src"; import { TestUtil } from "../testHelpers/TestUtil"; describe("AccountTest", function () { @@ -442,7 +442,7 @@ describe("RelationshipTest: Terminate", function () { expect(terminatedRelationshipFromSelf.id.toString()).toStrictEqual(relationshipId.toString()); expect(terminatedRelationshipFromSelf.status).toStrictEqual(RelationshipStatus.Terminated); expect(terminatedRelationshipFromSelf.cache?.auditLog).toHaveLength(3); - expect(terminatedRelationshipFromSelf.cache!.auditLog[2].newStatus).toBe(RelationshipStatus.Terminated); + expect(terminatedRelationshipFromSelf.cache!.auditLog[2].reason).toBe(RelationshipAuditLogEntryReason.Termination); const syncedRelationshipsPeer = await TestUtil.syncUntilHasRelationships(to); expect(syncedRelationshipsPeer).toHaveLength(1); @@ -451,7 +451,457 @@ describe("RelationshipTest: Terminate", function () { expect(terminatedRelationshipPeer.id.toString()).toStrictEqual(relationshipId.toString()); expect(terminatedRelationshipPeer.status).toStrictEqual(RelationshipStatus.Terminated); expect(terminatedRelationshipPeer.cache?.auditLog).toHaveLength(3); - expect(terminatedRelationshipPeer.cache!.auditLog[2].newStatus).toBe(RelationshipStatus.Terminated); + expect(terminatedRelationshipPeer.cache!.auditLog[2].reason).toBe(RelationshipAuditLogEntryReason.Termination); + }); +}); + +describe("RelationshipTest: Request Reactivation", function () { + let connection: IDatabaseConnection; + let transport: Transport; + + let from: AccountController; + let to: AccountController; + let relationshipId: CoreId; + + beforeAll(async function () { + connection = await TestUtil.createDatabaseConnection(); + transport = TestUtil.createTransport(connection); + + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 2); + from = accounts[0]; + to = accounts[1]; + relationshipId = (await TestUtil.addRelationship(from, to)).acceptedRelationshipFromSelf.id; + await from.relationships.terminate(relationshipId); + await TestUtil.syncUntilHasRelationships(to); + }); + + afterAll(async function () { + await from.close(); + await to.close(); + await connection.close(); + }); + + test("should request reactivating a relationship between two accounts", async function () { + const reactivationRequestedRelationshipFromSelf = await from.relationships.requestReactivation(relationshipId); + + expect(reactivationRequestedRelationshipFromSelf.id.toString()).toStrictEqual(relationshipId.toString()); + expect(reactivationRequestedRelationshipFromSelf.status).toStrictEqual(RelationshipStatus.Terminated); + expect(reactivationRequestedRelationshipFromSelf.cache?.auditLog).toHaveLength(4); + expect(reactivationRequestedRelationshipFromSelf.cache!.auditLog[3].reason).toBe(RelationshipAuditLogEntryReason.ReactivationRequested); + + const syncedRelationshipsPeer = await TestUtil.syncUntilHasRelationships(to); + expect(syncedRelationshipsPeer).toHaveLength(1); + const reactivationRequestedRelationshipPeer = syncedRelationshipsPeer[0]; + + expect(reactivationRequestedRelationshipPeer.id.toString()).toStrictEqual(relationshipId.toString()); + expect(reactivationRequestedRelationshipPeer.status).toStrictEqual(RelationshipStatus.Terminated); + expect(reactivationRequestedRelationshipPeer.cache?.auditLog).toHaveLength(4); + expect(reactivationRequestedRelationshipPeer.cache!.auditLog[3].reason).toBe(RelationshipAuditLogEntryReason.ReactivationRequested); + }); +}); + +describe("RelationshipTest: Accept Reactivation", function () { + let connection: IDatabaseConnection; + let transport: Transport; + + let from: AccountController; + let to: AccountController; + let relationshipId: CoreId; + + beforeAll(async function () { + connection = await TestUtil.createDatabaseConnection(); + transport = TestUtil.createTransport(connection); + + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 2); + from = accounts[0]; + to = accounts[1]; + relationshipId = (await TestUtil.addRelationship(from, to)).acceptedRelationshipFromSelf.id; + await from.relationships.terminate(relationshipId); + await TestUtil.syncUntilHasRelationships(to); + await from.relationships.requestReactivation(relationshipId); + await TestUtil.syncUntilHasRelationships(to); + }); + + afterAll(async function () { + await from.close(); + await to.close(); + await connection.close(); + }); + + test("should accept the reactivation of a relationship between two accounts", async function () { + const acceptedReactivatedRelationshipPeer = await to.relationships.acceptReactivation(relationshipId); + expect(acceptedReactivatedRelationshipPeer.id.toString()).toStrictEqual(relationshipId.toString()); + expect(acceptedReactivatedRelationshipPeer.status).toStrictEqual(RelationshipStatus.Active); + expect(acceptedReactivatedRelationshipPeer.cache?.auditLog).toHaveLength(5); + expect(acceptedReactivatedRelationshipPeer.cache!.auditLog[4].reason).toBe(RelationshipAuditLogEntryReason.AcceptanceOfReactivation); + + const syncedRelationshipsFromSelf = await TestUtil.syncUntilHasRelationships(from); + expect(syncedRelationshipsFromSelf).toHaveLength(1); + const acceptedReactivatedRelationshipFromSelf = syncedRelationshipsFromSelf[0]; + + expect(acceptedReactivatedRelationshipFromSelf.id.toString()).toStrictEqual(relationshipId.toString()); + expect(acceptedReactivatedRelationshipFromSelf.status).toStrictEqual(RelationshipStatus.Active); + expect(acceptedReactivatedRelationshipFromSelf.cache?.auditLog).toHaveLength(5); + expect(acceptedReactivatedRelationshipFromSelf.cache!.auditLog[4].reason).toBe(RelationshipAuditLogEntryReason.AcceptanceOfReactivation); + }); +}); + +describe("RelationshipTest: Reject Reactivation", function () { + let connection: IDatabaseConnection; + let transport: Transport; + + let from: AccountController; + let to: AccountController; + let relationshipId: CoreId; + + beforeAll(async function () { + connection = await TestUtil.createDatabaseConnection(); + transport = TestUtil.createTransport(connection); + + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 2); + from = accounts[0]; + to = accounts[1]; + relationshipId = (await TestUtil.addRelationship(from, to)).acceptedRelationshipFromSelf.id; + await from.relationships.terminate(relationshipId); + await TestUtil.syncUntilHasRelationships(to); + await from.relationships.requestReactivation(relationshipId); + await TestUtil.syncUntilHasRelationships(to); + }); + + afterAll(async function () { + await from.close(); + await to.close(); + await connection.close(); + }); + + test("should reject the reactivation of a relationship between two accounts", async function () { + const rejectedReactivatedRelationshipPeer = await to.relationships.rejectReactivation(relationshipId); + expect(rejectedReactivatedRelationshipPeer.id.toString()).toStrictEqual(relationshipId.toString()); + expect(rejectedReactivatedRelationshipPeer.status).toStrictEqual(RelationshipStatus.Terminated); + expect(rejectedReactivatedRelationshipPeer.cache?.auditLog).toHaveLength(5); + expect(rejectedReactivatedRelationshipPeer.cache!.auditLog[4].reason).toBe(RelationshipAuditLogEntryReason.RejectionOfReactivation); + + const syncedRelationshipsFromSelf = await TestUtil.syncUntilHasRelationships(from); + expect(syncedRelationshipsFromSelf).toHaveLength(1); + const rejectedReactivatedRelationshipFromSelf = syncedRelationshipsFromSelf[0]; + + expect(rejectedReactivatedRelationshipFromSelf.id.toString()).toStrictEqual(relationshipId.toString()); + expect(rejectedReactivatedRelationshipFromSelf.status).toStrictEqual(RelationshipStatus.Terminated); + expect(rejectedReactivatedRelationshipFromSelf.cache?.auditLog).toHaveLength(5); + expect(rejectedReactivatedRelationshipFromSelf.cache!.auditLog[4].reason).toBe(RelationshipAuditLogEntryReason.RejectionOfReactivation); + }); +}); + +describe("RelationshipTest: Revoke Reactivation", function () { + let connection: IDatabaseConnection; + let transport: Transport; + + let from: AccountController; + let to: AccountController; + let relationshipId: CoreId; + + beforeAll(async function () { + connection = await TestUtil.createDatabaseConnection(); + transport = TestUtil.createTransport(connection); + + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 2); + from = accounts[0]; + to = accounts[1]; + relationshipId = (await TestUtil.addRelationship(from, to)).acceptedRelationshipFromSelf.id; + await from.relationships.terminate(relationshipId); + await TestUtil.syncUntilHasRelationships(to); + await from.relationships.requestReactivation(relationshipId); + await TestUtil.syncUntilHasRelationships(to); + }); + + afterAll(async function () { + await from.close(); + await to.close(); + await connection.close(); + }); + + test("should revoke the reactivation of a relationship between two accounts", async function () { + const revokedReactivatedRelationshipFromSelf = await from.relationships.revokeReactivation(relationshipId); + expect(revokedReactivatedRelationshipFromSelf.id.toString()).toStrictEqual(relationshipId.toString()); + expect(revokedReactivatedRelationshipFromSelf.status).toStrictEqual(RelationshipStatus.Terminated); + expect(revokedReactivatedRelationshipFromSelf.cache?.auditLog).toHaveLength(5); + expect(revokedReactivatedRelationshipFromSelf.cache!.auditLog[4].reason).toBe(RelationshipAuditLogEntryReason.RevocationOfReactivation); + + const syncedRelationshipsPeer = await TestUtil.syncUntilHasRelationships(to); + expect(syncedRelationshipsPeer).toHaveLength(1); + const revokedReactivatedRelationshipPeer = syncedRelationshipsPeer[0]; + + expect(revokedReactivatedRelationshipPeer.id.toString()).toStrictEqual(relationshipId.toString()); + expect(revokedReactivatedRelationshipPeer.status).toStrictEqual(RelationshipStatus.Terminated); + expect(revokedReactivatedRelationshipPeer.cache?.auditLog).toHaveLength(5); + expect(revokedReactivatedRelationshipPeer.cache!.auditLog[4].reason).toBe(RelationshipAuditLogEntryReason.RevocationOfReactivation); + }); +}); + +describe("RelationshipTest: validations for non-existent record", function () { + let connection: IDatabaseConnection; + let transport: Transport; + + let from: AccountController; + const fakeRelationshipId = CoreId.from("REL00000000000000000"); + + beforeAll(async function () { + connection = await TestUtil.createDatabaseConnection(); + transport = TestUtil.createTransport(connection); + + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 2); + from = accounts[0]; + }); + + afterAll(async function () { + await from.close(); + await connection.close(); + }); + + test("should not accept a relationship", async function () { + await expect(from.relationships.accept(fakeRelationshipId)).rejects.toThrow("error.transport.recordNotFound"); + }); + + test("should not reject a relationship", async function () { + await expect(from.relationships.reject(fakeRelationshipId)).rejects.toThrow("error.transport.recordNotFound"); + }); + + test("should not revoke a relationship", async function () { + await expect(from.relationships.revoke(fakeRelationshipId)).rejects.toThrow("error.transport.recordNotFound"); + }); + + test("should not terminate a relationship", async function () { + await expect(from.relationships.terminate(fakeRelationshipId)).rejects.toThrow("error.transport.recordNotFound"); + }); + + test("should not request a relationship reactivation", async function () { + await expect(from.relationships.requestReactivation(fakeRelationshipId)).rejects.toThrow("error.transport.recordNotFound"); + }); + + test("should not accept a relationship reactivation", async function () { + await expect(from.relationships.acceptReactivation(fakeRelationshipId)).rejects.toThrow("error.transport.recordNotFound"); + }); + + test("should not reject a relationship reactivation", async function () { + await expect(from.relationships.rejectReactivation(fakeRelationshipId)).rejects.toThrow("error.transport.recordNotFound"); + }); + + test("should not revoke a relationship reactivation", async function () { + await expect(from.relationships.revokeReactivation(fakeRelationshipId)).rejects.toThrow("error.transport.recordNotFound"); + }); +}); + +describe("RelationshipTest: validations (on terminated relationship)", function () { + let connection: IDatabaseConnection; + let transport: Transport; + + let from: AccountController; + let to: AccountController; + let relationshipId: CoreId; + + beforeAll(async function () { + connection = await TestUtil.createDatabaseConnection(); + transport = TestUtil.createTransport(connection); + + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 2); + from = accounts[0]; + to = accounts[1]; + relationshipId = (await TestUtil.addRelationship(from, to)).acceptedRelationshipFromSelf.id; + await from.relationships.terminate(relationshipId); + await TestUtil.syncUntilHasRelationships(to); + }); + + afterAll(async function () { + await from.close(); + await to.close(); + await connection.close(); + }); + + test("should not terminate a relationship in status terminated again", async function () { + await expect(from.relationships.terminate(relationshipId)).rejects.toThrow("error.transport.relationships.wrongRelationshipStatus"); + }); + + test("reactivation revocation should fail without reactivation request", async function () { + await expect(from.relationships.rejectReactivation(relationshipId)).rejects.toThrow("error.transport.relationships.reactivationNotRequested"); + }); + + test("reactivation acceptance should fail without reactivation request", async function () { + await expect(from.relationships.acceptReactivation(relationshipId)).rejects.toThrow("error.transport.relationships.reactivationNotRequested"); + }); + + test("reactivation rejection should fail without reactivation request", async function () { + await expect(from.relationships.rejectReactivation(relationshipId)).rejects.toThrow("error.transport.relationships.reactivationNotRequested"); + }); + + describe("after reactivation request", function () { + beforeAll(async function () { + await from.relationships.requestReactivation(relationshipId); + await TestUtil.syncUntilHasRelationships(to); + }); + + test("reactivation revocation should fail when the wrong side revokes it", async function () { + await expect(to.relationships.revokeReactivation(relationshipId)).rejects.toThrow("error.transport.relationships.operationOnlyAllowedForPeer"); + }); + + test("reactivation acceptance should fail when the wrong side accepts it", async function () { + await expect(from.relationships.acceptReactivation(relationshipId)).rejects.toThrow("error.transport.relationships.operationOnlyAllowedForPeer"); + }); + + test("reactivation rejection should fail when the wrong side rejects it", async function () { + await expect(from.relationships.rejectReactivation(relationshipId)).rejects.toThrow("error.transport.relationships.operationOnlyAllowedForPeer"); + }); + + test("requesting reactivation twice should fail", async function () { + await expect(from.relationships.requestReactivation(relationshipId)).rejects.toThrow( + "error.transport.relationships.reactivationAlreadyRequested: 'You have already requested the reactivation" + ); + + await expect(to.relationships.requestReactivation(relationshipId)).rejects.toThrow( + "error.transport.relationships.reactivationAlreadyRequested: 'Your peer has already requested the reactivation" + ); + }); + }); +}); + +describe("RelationshipTest: operation executioner validation (on pending relationship)", function () { + let connection: IDatabaseConnection; + let transport: Transport; + let from: AccountController; + let to: AccountController; + let relationshipId: CoreId; + + beforeAll(async function () { + connection = await TestUtil.createDatabaseConnection(); + transport = TestUtil.createTransport(connection); + + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 2); + from = accounts[0]; + to = accounts[1]; + + const templateFrom = await from.relationshipTemplates.sendRelationshipTemplate({ + content: { + mycontent: "template" + }, + expiresAt: CoreDate.utc().add({ minutes: 5 }), + maxNumberOfAllocations: 1 + }); + + const templateToken = TokenContentRelationshipTemplate.from({ + templateId: templateFrom.id, + secretKey: templateFrom.secretKey + }); + + const token = await from.tokens.sendToken({ + content: templateToken, + expiresAt: CoreDate.utc().add({ hours: 12 }), + ephemeral: false + }); + + const tokenRef = token.truncate(); + + const receivedToken = await to.tokens.loadPeerTokenByTruncated(tokenRef, false); + + if (!(receivedToken.cache!.content instanceof TokenContentRelationshipTemplate)) { + throw new Error("token content not instanceof TokenContentRelationshipTemplate"); + } + + const templateTo = await to.relationshipTemplates.loadPeerRelationshipTemplate(receivedToken.cache!.content.templateId, receivedToken.cache!.content.secretKey); + const request = await to.relationships.sendRelationship({ + template: templateTo, + creationContent: { + mycontent: "request" + } + }); + relationshipId = request.id; + await TestUtil.syncUntilHasRelationships(from); + }); + + afterAll(async function () { + await from.close(); + await to.close(); + + await connection.close(); + }); + + test("you should not accept your own relationship", async function () { + await expect(to.relationships.accept(relationshipId)).rejects.toThrow("error.transport.relationships.operationOnlyAllowedForPeer"); + }); + + test("you should not reject your own relationship", async function () { + await expect(to.relationships.reject(relationshipId)).rejects.toThrow("error.transport.relationships.operationOnlyAllowedForPeer"); + }); + + test("you should not revoke your peer's relationship", async function () { + await expect(from.relationships.revoke(relationshipId)).rejects.toThrow("error.transport.relationships.operationOnlyAllowedForPeer"); + }); +}); + +describe("RelationshipTest: relationship status validation (on active relationship)", function () { + let connection: IDatabaseConnection; + let transport: Transport; + + let from: AccountController; + let to: AccountController; + let relationshipId: CoreId; + + beforeAll(async function () { + connection = await TestUtil.createDatabaseConnection(); + transport = TestUtil.createTransport(connection); + + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 2); + from = accounts[0]; + to = accounts[1]; + relationshipId = (await TestUtil.addRelationship(from, to)).acceptedRelationshipFromSelf.id; + }); + + afterAll(async function () { + await from.close(); + await to.close(); + await connection.close(); + }); + + test("should not reactivate a relationship", async function () { + await expect(from.relationships.requestReactivation(relationshipId)).rejects.toThrow("error.transport.relationships.wrongRelationshipStatus"); + }); + + test("should not accept a relationship reactivation", async function () { + await expect(from.relationships.acceptReactivation(relationshipId)).rejects.toThrow("error.transport.relationships.wrongRelationshipStatus"); + }); + + test("should not reject a relationship reactivation", async function () { + await expect(from.relationships.rejectReactivation(relationshipId)).rejects.toThrow("error.transport.relationships.wrongRelationshipStatus"); + }); + + test("should not revoke a relationship reactivation", async function () { + await expect(from.relationships.revokeReactivation(relationshipId)).rejects.toThrow("error.transport.relationships.wrongRelationshipStatus"); + }); + + test("should not accept a relationship", async function () { + await expect(from.relationships.accept(relationshipId)).rejects.toThrow("error.transport.relationships.wrongRelationshipStatus"); + }); + + test("should not reject a relationship", async function () { + await expect(from.relationships.reject(relationshipId)).rejects.toThrow("error.transport.relationships.wrongRelationshipStatus"); + }); + + test("should not revoke a relationship", async function () { + await expect(from.relationships.revoke(relationshipId)).rejects.toThrow("error.transport.relationships.wrongRelationshipStatus"); }); }); From a20aa9857fd85d8c4e59d8f76effd45647882140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Thu, 6 Jun 2024 09:18:25 +0200 Subject: [PATCH 08/40] Chore/release packages (#146) * chore: bump all versions to 5.0.0-alpha.1 * chore: update package.lock * chore: undo the iql breaking bump --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- package-lock.json | 23 ++++++++++++++--------- packages/app-runtime/package.json | 4 ++-- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- packages/runtime/package.json | 8 ++++---- packages/transport/package.json | 2 +- 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8f8394180..a4ad913fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12264,7 +12264,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "3.2.1", + "version": "5.0.0-alpha.1", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12278,12 +12278,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^4.4.1" + "@nmshd/runtime": "^5.0.0-alpha.1" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "3.11.0", + "version": "5.0.0-alpha.1", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12305,7 +12305,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "2.10.1", + "version": "5.0.0-alpha.1", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12318,6 +12318,11 @@ "@types/luxon": "^3.4.2" } }, + "packages/content/node_modules/@nmshd/iql": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@nmshd/iql/-/iql-1.0.2.tgz", + "integrity": "sha512-fRUIDoZeAKDJ99/yjbjlKryMv1poNaiRDTC8eNltZJSPSkQgchlt0yrWHBDl+CZEPF2Ae0hDj7vpo2n0c6R6JA==" + }, "packages/iql": { "name": "@nmshd/iql", "version": "1.0.2", @@ -12330,17 +12335,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "4.10.6", + "version": "5.0.0-alpha.1", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "3.11.0", - "@nmshd/content": "2.10.1", + "@nmshd/consumption": "5.0.0-alpha.1", + "@nmshd/content": "5.0.0-alpha.1", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "2.7.5", + "@nmshd/transport": "5.0.0-alpha.1", "ajv": "^8.13.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12390,7 +12395,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "2.7.5", + "version": "5.0.0-alpha.1", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index 927740966..c7c5eb4a8 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "3.2.1", + "version": "5.0.0-alpha.1", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^4.4.1" + "@nmshd/runtime": "^5.0.0-alpha.1" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 0f088bff8..77eeb9d07 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "3.11.0", + "version": "5.0.0-alpha.1", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index f908cd341..d72fa2d4f 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "2.10.1", + "version": "5.0.0-alpha.1", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index f506ea832..92d534477 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "4.10.6", + "version": "5.0.0-alpha.1", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "3.11.0", - "@nmshd/content": "2.10.1", + "@nmshd/consumption": "5.0.0-alpha.1", + "@nmshd/content": "5.0.0-alpha.1", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "2.7.5", + "@nmshd/transport": "5.0.0-alpha.1", "ajv": "^8.13.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/transport/package.json b/packages/transport/package.json index 55c8122c7..45488321b 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "2.7.5", + "version": "5.0.0-alpha.1", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { From 9dacd8d1e8e53a67458eb1d7a6886f0682089151 Mon Sep 17 00:00:00 2001 From: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com> Date: Tue, 18 Jun 2024 07:24:05 +0200 Subject: [PATCH 09/40] Chore/merge main into v5 (#159) * Refactor/refactor validation for emailAdress and website (#122) * refactor: email validation * fix: Email-Validation and add a test for it * fix: Website Validation and add a test for it * feat: change regexp from Url * chore: version bump * refactor: test for website * refactor: changed wording of the test * refactor: Tests for Email and Website * refactor: change string in regexp * refactor: test for Website and AbstractURL * refactor: name and AbstractURL without escape * refactor: Change Tests and RegExp * chore: version bump * feat: make regexp more readable * fix: changed an error * fix: error and last changes * feat: last changes * chore: version bump * fix: delete unnecessary file --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * Chore(deps): bump the update-npm-dependencies group with 4 updates (#157) * Feature/Add more deletionStatus (#150) * feat: add deletionStatus DeletionRequestSent and DeletionRequestRejected * fix: remove deletionInfo from GetRepositoryAttributesRequestQuery * feat: add deleteAttributeRequestItem to RequestItemDerivations * test: deletionInfo is set * fix: set deletionInfo * feat: set DeletionRequestRejected * chore: version bump * fix: make test more precise * refactor: renamse RequestIntegration.test.ts * feat: check for deletionStatus of predecessors * feat: only set deletionInfo after Request is sent, also set deletionInfo for predecessors * feat: undo renaming * fix: stabilize test * fix: stabilize all tests * fix: npm audit * feat: integrate comments * feat: integrate all comments * refactor: give more descriptive names * chore: version bump --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * Fix/import https proxy package only when needed (#158) --------- Co-authored-by: Ruth Di Giacomo <150357721+RuthDiG@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Milena Czierlinski <146972016+Milena-Czierlinski@users.noreply.github.com> Co-authored-by: Sebastian Mahr --- package-lock.json | 67 ++- package.json | 4 +- packages/app-runtime/package.json | 2 +- packages/consumption/package.json | 2 +- .../consumption/src/consumption/CoreErrors.ts | 2 +- .../attributes/local/LocalAttribute.ts | 7 +- .../local/LocalAttributeDeletionInfo.ts | 2 + .../DeleteAttributeRequestItemProcessor.ts | 36 +- .../outgoing/OutgoingRequestsController.ts | 35 +- .../LocalAttributeDeletionInfo.test.ts | 72 ++- .../OutgoingRequestsController.test.ts | 5 + .../requests/RequestsIntegrationTest.ts | 142 ++++- ...eleteAttributeRequestItemProcessor.test.ts | 498 +++++++++++++++++- .../types/strings/AbstractEMailAddress.ts | 11 +- .../attributes/types/strings/AbstractURL.ts | 12 +- packages/content/src/requests/RequestItem.ts | 6 + .../test/attributes/EMailAddress.test.ts | 38 ++ .../content/test/attributes/Website.test.ts | 45 ++ packages/runtime/package.json | 2 +- .../runtime/src/useCases/common/Schemas.ts | 39 -- .../attributes/GetRepositoryAttributes.ts | 3 - packages/transport/package.json | 4 +- .../transport/src/core/backbone/RESTClient.ts | 11 +- 23 files changed, 901 insertions(+), 144 deletions(-) create mode 100644 packages/content/test/attributes/EMailAddress.test.ts create mode 100644 packages/content/test/attributes/Website.test.ts diff --git a/package-lock.json b/package-lock.json index 1a610c359..a37e206b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,14 +18,14 @@ "@js-soft/eslint-config-ts": "^1.6.8", "@js-soft/license-check": "^1.0.9", "@types/jest": "^29.5.12", - "@types/node": "^20.14.0", + "@types/node": "^20.14.2", "enhanced-publish": "^1.1.3", "eslint": "^8.57.0", "jest": "^29.7.0", "jest-expect-message": "^1.1.3", "madge": "^7.0.0", "npm-check-updates": "^16.14.20", - "prettier": "^3.3.0", + "prettier": "^3.3.1", "ts-jest": "^29.1.4", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", @@ -1399,6 +1399,18 @@ "uuid": "9.0.1" } }, + "node_modules/@nmshd/crypto/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@nmshd/iql": { "resolved": "packages/iql", "link": true @@ -2183,9 +2195,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==", + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==", "dev": true }, "node_modules/@types/lokijs": { @@ -2198,9 +2210,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.14.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.0.tgz", - "integrity": "sha512-5cHBxFGJx6L4s56Bubp4fglrEpmyJypsqI6RgzMfBHWUJQGWAAi8cWcgetEbZXHYXo9C2Fa4EEds/uSyS4cxmA==", + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -3127,11 +3139,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -4928,9 +4941,10 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -5935,8 +5949,9 @@ }, "node_modules/is-number": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -9475,9 +9490,9 @@ } }, "node_modules/prettier": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.0.tgz", - "integrity": "sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.1.tgz", + "integrity": "sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -11207,8 +11222,9 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -11816,12 +11832,13 @@ } }, "node_modules/uuid": { - "version": "9.0.1", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], - "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -12367,7 +12384,7 @@ }, "devDependencies": { "@js-soft/web-logger": "^1.0.4", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.5", "@types/lokijs": "^1.5.14", "@types/luxon": "^3.4.2" }, @@ -12392,7 +12409,7 @@ "@js-soft/ts-utils": "2.3.3", "@js-soft/web-logger": "^1.0.4", "@nmshd/crypto": "2.0.6", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.5", "@types/luxon": "^3.4.2", "leaked-handles": "^5.2.0" } @@ -12456,7 +12473,7 @@ "@js-soft/docdb-access-mongo": "1.1.8", "@js-soft/node-logger": "1.1.1", "@types/json-stringify-safe": "^5.0.3", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.5", "@types/luxon": "^3.4.2", "@types/qrcode": "^1.5.5", "ts-json-schema-generator": "2.3.0" @@ -12488,7 +12505,7 @@ "qs": "^6.12.1", "reflect-metadata": "^0.2.2", "ts-simple-nameof": "^1.3.1", - "uuid": "^9.0.1" + "uuid": "^10.0.0" }, "devDependencies": { "@js-soft/docdb-access-loki": "1.1.0", @@ -12498,7 +12515,7 @@ "@js-soft/web-logger": "^1.0.4", "@nmshd/crypto": "2.0.6", "@types/json-stringify-safe": "^5.0.3", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.5", "@types/luxon": "^3.4.2", "@types/qs": "^6.9.15", "@types/uuid": "^9.0.8", diff --git a/package.json b/package.json index 96aaae845..b21c9ce58 100644 --- a/package.json +++ b/package.json @@ -29,14 +29,14 @@ "@js-soft/eslint-config-ts": "^1.6.8", "@js-soft/license-check": "^1.0.9", "@types/jest": "^29.5.12", - "@types/node": "^20.14.0", + "@types/node": "^20.14.2", "enhanced-publish": "^1.1.3", "eslint": "^8.57.0", "jest": "^29.7.0", "jest-expect-message": "^1.1.3", "madge": "^7.0.0", "npm-check-updates": "^16.14.20", - "prettier": "^3.3.0", + "prettier": "^3.3.1", "ts-jest": "^29.1.4", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index c7c5eb4a8..72d4a480f 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -49,7 +49,7 @@ }, "devDependencies": { "@js-soft/web-logger": "^1.0.4", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.5", "@types/lokijs": "^1.5.14", "@types/luxon": "^3.4.2" }, diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 77eeb9d07..d152e0d8b 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -63,7 +63,7 @@ "@js-soft/ts-utils": "2.3.3", "@js-soft/web-logger": "^1.0.4", "@nmshd/crypto": "2.0.6", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.5", "@types/luxon": "^3.4.2", "leaked-handles": "^5.2.0" }, diff --git a/packages/consumption/src/consumption/CoreErrors.ts b/packages/consumption/src/consumption/CoreErrors.ts index 4c929767c..3d9d7bda9 100644 --- a/packages/consumption/src/consumption/CoreErrors.ts +++ b/packages/consumption/src/consumption/CoreErrors.ts @@ -198,7 +198,7 @@ class Attributes { public invalidDeletionInfoOfOwnSharedAttribute() { return new CoreError( "error.consumption.attributes.invalidDeletionInfoOfOwnSharedAttribute", - "The only valid deletionStatuses for own shared Attributes are 'DeletedByPeer' or 'ToBeDeletedByPeer'." + "The only valid deletionStatuses for own shared Attributes are 'DeletionRequestSent', 'DeletionRequestRejected', 'DeletedByPeer' or 'ToBeDeletedByPeer'." ); } diff --git a/packages/consumption/src/modules/attributes/local/LocalAttribute.ts b/packages/consumption/src/modules/attributes/local/LocalAttribute.ts index a40b1c35c..21349a4df 100644 --- a/packages/consumption/src/modules/attributes/local/LocalAttribute.ts +++ b/packages/consumption/src/modules/attributes/local/LocalAttribute.ts @@ -203,7 +203,12 @@ export class LocalAttribute extends CoreSynchronizable implements ILocalAttribut } private isOwnSharedAttributeDeletionInfo(deletionInfo: LocalAttributeDeletionInfo): boolean { - return deletionInfo.deletionStatus === DeletionStatus.DeletedByPeer || deletionInfo.deletionStatus === DeletionStatus.ToBeDeletedByPeer; + return ( + deletionInfo.deletionStatus === DeletionStatus.DeletedByPeer || + deletionInfo.deletionStatus === DeletionStatus.ToBeDeletedByPeer || + deletionInfo.deletionStatus === DeletionStatus.DeletionRequestSent || + deletionInfo.deletionStatus === DeletionStatus.DeletionRequestRejected + ); } private isThirdPartyOwnedRelationshipAttributeDeletionInfo(deletionInfo: LocalAttributeDeletionInfo): boolean { diff --git a/packages/consumption/src/modules/attributes/local/LocalAttributeDeletionInfo.ts b/packages/consumption/src/modules/attributes/local/LocalAttributeDeletionInfo.ts index b8c217450..43ce3748c 100644 --- a/packages/consumption/src/modules/attributes/local/LocalAttributeDeletionInfo.ts +++ b/packages/consumption/src/modules/attributes/local/LocalAttributeDeletionInfo.ts @@ -2,6 +2,8 @@ import { serialize, validate } from "@js-soft/ts-serval"; import { CoreDate, CoreSerializable, ICoreDate } from "@nmshd/transport"; export enum DeletionStatus { + DeletionRequestSent = "DeletionRequestSent", + DeletionRequestRejected = "DeletionRequestRejected", ToBeDeleted = "ToBeDeleted", ToBeDeletedByPeer = "ToBeDeletedByPeer", DeletedByPeer = "DeletedByPeer", diff --git a/packages/consumption/src/modules/requests/itemProcessors/deleteAttribute/DeleteAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/deleteAttribute/DeleteAttributeRequestItemProcessor.ts index d45bd604e..b18afe77b 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/deleteAttribute/DeleteAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/deleteAttribute/DeleteAttributeRequestItemProcessor.ts @@ -89,7 +89,7 @@ export class DeleteAttributeRequestItemProcessor extends GenericRequestItemProce requestItem: DeleteAttributeRequestItem, _requestInfo: LocalRequestInfo ): Promise { - if (!(responseItem instanceof DeleteAttributeAcceptResponseItem)) { + if (responseItem instanceof AcceptResponseItem && !(responseItem instanceof DeleteAttributeAcceptResponseItem)) { return; } @@ -98,17 +98,33 @@ export class DeleteAttributeRequestItemProcessor extends GenericRequestItemProce if (attribute.deletionInfo?.deletionStatus === DeletionStatus.DeletedByPeer) return; - const deletionInfo = LocalAttributeDeletionInfo.from({ - deletionStatus: DeletionStatus.ToBeDeletedByPeer, - deletionDate: responseItem.deletionDate - }); - const predecessors = await this.consumptionController.attributes.getPredecessorsOfAttribute(attribute.id); - for (const attr of [attribute, ...predecessors]) { - if (attr.deletionInfo?.deletionStatus !== DeletionStatus.DeletedByPeer) { - attr.setDeletionInfo(deletionInfo, this.accountController.identity.address); - await this.consumptionController.attributes.updateAttributeUnsafe(attr); + if (responseItem instanceof DeleteAttributeAcceptResponseItem) { + const deletionInfo = LocalAttributeDeletionInfo.from({ + deletionStatus: DeletionStatus.ToBeDeletedByPeer, + deletionDate: responseItem.deletionDate + }); + + for (const attr of [attribute, ...predecessors]) { + if (attr.deletionInfo?.deletionStatus !== DeletionStatus.DeletedByPeer) { + attr.setDeletionInfo(deletionInfo, this.accountController.identity.address); + await this.consumptionController.attributes.updateAttributeUnsafe(attr); + } + } + } + + if (responseItem instanceof RejectResponseItem) { + const deletionInfo = LocalAttributeDeletionInfo.from({ + deletionStatus: DeletionStatus.DeletionRequestRejected, + deletionDate: CoreDate.utc() + }); + + for (const attr of [attribute, ...predecessors]) { + if (attr.deletionInfo?.deletionStatus !== DeletionStatus.ToBeDeletedByPeer && attr.deletionInfo?.deletionStatus !== DeletionStatus.DeletedByPeer) { + attr.setDeletionInfo(deletionInfo, this.accountController.identity.address); + await this.consumptionController.attributes.updateAttributeUnsafe(attr); + } } } } diff --git a/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts b/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts index 1fbe151d7..4a0bc3661 100644 --- a/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts +++ b/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts @@ -1,5 +1,5 @@ import { EventBus } from "@js-soft/ts-utils"; -import { RelationshipTemplateContent, Request, RequestItem, RequestItemGroup, Response, ResponseItem, ResponseItemGroup } from "@nmshd/content"; +import { DeleteAttributeRequestItem, RelationshipTemplateContent, Request, RequestItem, RequestItemGroup, Response, ResponseItem, ResponseItemGroup } from "@nmshd/content"; import { CoreAddress, CoreDate, @@ -18,6 +18,7 @@ import { ConsumptionControllerName } from "../../../consumption/ConsumptionContr import { ConsumptionError } from "../../../consumption/ConsumptionError"; import { ConsumptionIds } from "../../../consumption/ConsumptionIds"; import { CoreErrors } from "../../../consumption/CoreErrors"; +import { DeletionStatus, LocalAttributeDeletionInfo } from "../../attributes"; import { ValidationResult } from "../../common/ValidationResult"; import { OutgoingRequestCreatedAndCompletedEvent, OutgoingRequestCreatedEvent, OutgoingRequestStatusChangedEvent } from "../events"; import { RequestItemProcessorRegistry } from "../itemProcessors/RequestItemProcessorRegistry"; @@ -173,6 +174,8 @@ export class OutgoingRequestsController extends ConsumptionBaseController { const parsedParams = SentOutgoingRequestParameters.from(params); const request = await this._sent(parsedParams.requestId, parsedParams.requestSourceObject); + await this._setDeletionInfo(request.content); + this.eventBus.publish( new OutgoingRequestStatusChangedEvent(this.identity.address.toString(), { request: request, @@ -200,6 +203,36 @@ export class OutgoingRequestsController extends ConsumptionBaseController { return request; } + private async _setDeletionInfo(request: Request) { + const requestItemsFromRequest = request.items.filter((item) => item instanceof RequestItem) as RequestItem[]; + const requestItemGroupsFromRequest = request.items.filter((item) => item instanceof RequestItemGroup) as RequestItemGroup[]; + const requestItemsFromGroups = requestItemGroupsFromRequest.map((group) => group.items).flat(); + const requestItems = [...requestItemsFromRequest, ...requestItemsFromGroups]; + const deleteAttributeRequestItems = requestItems.filter((item) => item instanceof DeleteAttributeRequestItem) as DeleteAttributeRequestItem[]; + if (deleteAttributeRequestItems.length === 0) return; + + const ownSharedAttributeIds = deleteAttributeRequestItems.map((item) => item.attributeId); + for (const ownSharedAttributeId of ownSharedAttributeIds) { + const ownSharedAttribute = await this.parent.attributes.getLocalAttribute(ownSharedAttributeId); + if (!ownSharedAttribute) { + throw new ConsumptionError(`The own shared Attribute ${ownSharedAttributeId} of a created DeleteAttributeRequestItem was not found.`); + } + + const deletionInfo = LocalAttributeDeletionInfo.from({ + deletionStatus: DeletionStatus.DeletionRequestSent, + deletionDate: CoreDate.utc() + }); + + const predecessors = await this.parent.attributes.getPredecessorsOfAttribute(ownSharedAttributeId); + for (const attr of [ownSharedAttribute, ...predecessors]) { + if (attr.deletionInfo?.deletionStatus !== DeletionStatus.ToBeDeletedByPeer && attr.deletionInfo?.deletionStatus !== DeletionStatus.DeletedByPeer) { + attr.setDeletionInfo(deletionInfo, this.identity.address); + await this.parent.attributes.updateAttributeUnsafe(attr); + } + } + } + } + private getSourceType(sourceObject: Message | RelationshipTemplate): "Message" | "RelationshipTemplate" { if (sourceObject instanceof Message) { if (!sourceObject.isOwn) { diff --git a/packages/consumption/test/modules/attributes/LocalAttributeDeletionInfo.test.ts b/packages/consumption/test/modules/attributes/LocalAttributeDeletionInfo.test.ts index 9423a34cb..caa93d432 100644 --- a/packages/consumption/test/modules/attributes/LocalAttributeDeletionInfo.test.ts +++ b/packages/consumption/test/modules/attributes/LocalAttributeDeletionInfo.test.ts @@ -83,6 +83,26 @@ describe("LocalAttributeDeletionInfo", function () { }); describe("DeletionInfo of own shared Attributes", function () { + test("should set the deletionInfo of an own shared Attribute to DeletionRequestSent", async function () { + const deletionInfo = LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletionRequestSent, deletionDate: CoreDate.utc() }); + + ownSharedIdentityAttribute.setDeletionInfo(deletionInfo, testAccount.identity.address); + await consumptionController.attributes.updateAttributeUnsafe(ownSharedIdentityAttribute); + + const updatedOwnSharedIdentityAttribute = await consumptionController.attributes.getLocalAttribute(ownSharedIdentityAttribute.id); + expect(updatedOwnSharedIdentityAttribute!.deletionInfo!.deletionStatus).toBe(DeletionStatus.DeletionRequestSent); + }); + + test("should set the deletionInfo of an own shared Attribute to DeletionRequestRejected", async function () { + const deletionInfo = LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletionRequestRejected, deletionDate: CoreDate.utc() }); + + ownSharedIdentityAttribute.setDeletionInfo(deletionInfo, testAccount.identity.address); + await consumptionController.attributes.updateAttributeUnsafe(ownSharedIdentityAttribute); + + const updatedOwnSharedIdentityAttribute = await consumptionController.attributes.getLocalAttribute(ownSharedIdentityAttribute.id); + expect(updatedOwnSharedIdentityAttribute!.deletionInfo!.deletionStatus).toBe(DeletionStatus.DeletionRequestRejected); + }); + test("should set the deletionInfo of an own shared Attribute to ToBeDeletedByPeer", async function () { const deletionInfo = LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.ToBeDeletedByPeer, deletionDate: CoreDate.utc() }); @@ -108,7 +128,7 @@ describe("LocalAttributeDeletionInfo", function () { expect(() => { ownSharedIdentityAttribute.setDeletionInfo(deletionInfo, testAccount.identity.address); - }).toThrow("The only valid deletionStatuses for own shared Attributes are 'DeletedByPeer' or 'ToBeDeletedByPeer'."); + }).toThrow("The only valid deletionStatuses for own shared Attributes are 'DeletionRequestSent', 'DeletionRequestRejected', 'DeletedByPeer' or 'ToBeDeletedByPeer'."); }); test("should throw trying to set the deletionInfo of an own shared Attribute to DeletedByOwner", function () { @@ -116,7 +136,7 @@ describe("LocalAttributeDeletionInfo", function () { expect(() => { ownSharedIdentityAttribute.setDeletionInfo(deletionInfo, testAccount.identity.address); - }).toThrow("The only valid deletionStatuses for own shared Attributes are 'DeletedByPeer' or 'ToBeDeletedByPeer'."); + }).toThrow("The only valid deletionStatuses for own shared Attributes are 'DeletionRequestSent', 'DeletionRequestRejected', 'DeletedByPeer' or 'ToBeDeletedByPeer'."); }); }); @@ -141,6 +161,22 @@ describe("LocalAttributeDeletionInfo", function () { expect(updatedPeerSharedIdentityAttribute!.deletionInfo!.deletionStatus).toBe(DeletionStatus.DeletedByOwner); }); + test("should throw trying to set the deletionInfo of a peer shared Attribute to DeletionRequestSent", function () { + const deletionInfo = LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletionRequestSent, deletionDate: CoreDate.utc() }); + + expect(() => { + peerSharedIdentityAttribute.setDeletionInfo(deletionInfo, testAccount.identity.address); + }).toThrow("The only valid deletionStatuses for peer shared Attributes are 'DeletedByOwner' or 'ToBeDeleted'."); + }); + + test("should throw trying to set the deletionInfo of a peer shared Attribute to DeletionRequestRejected", function () { + const deletionInfo = LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletionRequestRejected, deletionDate: CoreDate.utc() }); + + expect(() => { + peerSharedIdentityAttribute.setDeletionInfo(deletionInfo, testAccount.identity.address); + }).toThrow("The only valid deletionStatuses for peer shared Attributes are 'DeletedByOwner' or 'ToBeDeleted'."); + }); + test("should throw trying to set the deletionInfo of a peer shared Attribute to ToBeDeletedByPeer", function () { const deletionInfo = LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.ToBeDeletedByPeer, deletionDate: CoreDate.utc() }); @@ -177,6 +213,22 @@ describe("LocalAttributeDeletionInfo", function () { }).toThrow("The only valid deletionStatus for third party owned RelationshipAttributes is 'DeletedByPeer'."); }); + test("should throw trying to set the deletionInfo of a third party owned RelationshipAttribute to DeletionRequestSent", function () { + const deletionInfo = LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletionRequestSent, deletionDate: CoreDate.utc() }); + + expect(() => { + thirdPartyOwnedRelationshipAttribute.setDeletionInfo(deletionInfo, testAccount.identity.address); + }).toThrow("The only valid deletionStatus for third party owned RelationshipAttributes is 'DeletedByPeer'."); + }); + + test("should throw trying to set the deletionInfo of a third party owned RelationshipAttribute to DeletionRequestRejected", function () { + const deletionInfo = LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletionRequestRejected, deletionDate: CoreDate.utc() }); + + expect(() => { + thirdPartyOwnedRelationshipAttribute.setDeletionInfo(deletionInfo, testAccount.identity.address); + }).toThrow("The only valid deletionStatus for third party owned RelationshipAttributes is 'DeletedByPeer'."); + }); + test("should throw trying to set the deletionInfo of a third party owned RelationshipAttribute to ToBeDeletedByPeer", function () { const deletionInfo = LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.ToBeDeletedByPeer, deletionDate: CoreDate.utc() }); @@ -195,6 +247,22 @@ describe("LocalAttributeDeletionInfo", function () { }); describe("DeletionInfo of RepositoryAttributes", function () { + test("should throw trying to set the deletionInfo of a RepositoryAttribute to DeletionRequestSent", function () { + const deletionInfo = LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletionRequestSent, deletionDate: CoreDate.utc() }); + + expect(() => { + repositoryAttribute.setDeletionInfo(deletionInfo, testAccount.identity.address); + }).toThrow("RepositoryAttributes can not have a deletionInfo, since they are not shared with a peer and you can delete them directly."); + }); + + test("should throw trying to set the deletionInfo of a RepositoryAttribute to DeletionRequestRejected", function () { + const deletionInfo = LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletionRequestRejected, deletionDate: CoreDate.utc() }); + + expect(() => { + repositoryAttribute.setDeletionInfo(deletionInfo, testAccount.identity.address); + }).toThrow("RepositoryAttributes can not have a deletionInfo, since they are not shared with a peer and you can delete them directly."); + }); + test("should throw trying to set the deletionInfo of a RepositoryAttribute to DeletedByPeer", function () { const deletionInfo = LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletedByPeer, deletionDate: CoreDate.utc() }); diff --git a/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts b/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts index d98e69687..7adc93db0 100644 --- a/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts +++ b/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts @@ -385,6 +385,11 @@ describe("OutgoingRequestsController", function () { await When.iTryToCallSentWith({ requestSourceObject: invalidSource }); await Then.itThrowsAnErrorWithTheErrorMessage("Cannot create outgoing Request from a peer*"); }); + + test("sets deletionInfo in case of DeleteAttributeRequestItems", async function () { + await When.iSentAnOutgoingRequestWithDeleteAttributeRequestItems(); + await Then.theDeletionInfoOfTheAssociatedAttributesAndPredecessorsIsSet(); + }); }); describe("Complete (on active relationship)", function () { diff --git a/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts b/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts index ad202498c..dbfae9130 100644 --- a/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts +++ b/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts @@ -1,16 +1,26 @@ /* eslint-disable jest/no-standalone-expect */ import { IDatabaseCollection, IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; import { DataEvent, EventEmitter2EventBus } from "@js-soft/ts-utils"; -import { AcceptResponseItem, IRequest, IResponse, RelationshipTemplateContent, Request, RequestItemGroup, Response, ResponseItemResult, ResponseResult } from "@nmshd/content"; import { - AccountController, + AcceptResponseItem, + DeleteAttributeRequestItem, + IRequest, + IResponse, + IdentityAttribute, + RelationshipTemplateContent, + Request, + RequestItemGroup, + Response, + ResponseItemResult, + ResponseResult +} from "@nmshd/content"; +import { CoreAddress, CoreId, IConfigOverwrite, ICoreId, IMessage, IRelationshipTemplate, - IdentityController, Message, Relationship, RelationshipTemplate, @@ -21,6 +31,8 @@ import { ConsumptionController, ConsumptionIds, DecideRequestParametersJSON, + DeleteAttributeRequestItemProcessor, + DeletionStatus, ICheckPrerequisitesOfIncomingRequestParameters, ICompleteIncomingRequestParameters, ICompleteOutgoingRequestParameters, @@ -37,6 +49,8 @@ import { LocalResponse, OutgoingRequestsController, ReceivedIncomingRequestParameters, + RequestItemConstructor, + RequestItemProcessorConstructor, RequestItemProcessorRegistry, ValidationResult } from "../../../src"; @@ -54,6 +68,7 @@ export class RequestsTestsContext { public currentIdentity: CoreAddress; public mockEventBus = new MockEventBus(); public relationshipToReturnFromGetRelationshipToIdentity: Relationship | undefined; + public consumptionController: ConsumptionController; private constructor() { // hide constructor @@ -72,22 +87,26 @@ export class RequestsTestsContext { const database = await dbConnection.getDatabase(`x${Math.random().toString(36).substring(7)}`); const collection = new SynchronizedCollection(await database.getCollection("Requests"), 0); - const fakeConsumptionController = { - transport, - accountController: { - identity: { address: CoreAddress.from("anAddress") } as IdentityController - } as AccountController - } as ConsumptionController; - const processorRegistry = new RequestItemProcessorRegistry(fakeConsumptionController, new Map([[TestRequestItem, TestRequestItemProcessor]])); - context.currentIdentity = CoreAddress.from("did:e:a-domain:dids:anidentity"); + const account = (await TestUtil.provideAccounts(transport, 1))[0]; + context.consumptionController = account.consumptionController; + + const processorRegistry = new RequestItemProcessorRegistry( + context.consumptionController, + new Map([ + [TestRequestItem, TestRequestItemProcessor], + [DeleteAttributeRequestItem, DeleteAttributeRequestItemProcessor] + ]) + ); + + context.currentIdentity = account.accountController.identity.address; context.outgoingRequestsController = new OutgoingRequestsController( collection, processorRegistry, - undefined!, + context.consumptionController, context.mockEventBus, - { address: CoreAddress.from("anAddress") }, + { address: account.accountController.identity.address }, { getRelationshipToIdentity: () => Promise.resolve(context.relationshipToReturnFromGetRelationshipToIdentity) } @@ -96,15 +115,16 @@ export class RequestsTestsContext { context.incomingRequestsController = new IncomingRequestsController( collection, processorRegistry, - undefined!, + context.consumptionController, context.mockEventBus, { - address: CoreAddress.from("anAddress") + address: account.accountController.identity.address }, { getRelationshipToIdentity: () => Promise.resolve(context.relationshipToReturnFromGetRelationshipToIdentity) } ); + context.requestsCollection = context.incomingRequestsController["localRequests"]; const originalCanCreate = context.outgoingRequestsController.canCreate; @@ -650,6 +670,88 @@ export class RequestsWhen { ); } + public async iSentAnOutgoingRequestWithDeleteAttributeRequestItems(): Promise { + const sOwnSharedIdentityAttribute1 = await this.context.consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A first name" + }, + owner: this.context.currentIdentity + }), + shareInfo: { + peer: CoreAddress.from("peer"), + requestReference: CoreId.from("shareRequestReference1") + } + }); + + const predecessorId = await ConsumptionIds.attribute.generate(); + const sOwnSharedIdentityAttribute2 = await this.context.consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A second name" + }, + owner: this.context.currentIdentity + }), + shareInfo: { + peer: CoreAddress.from("peer"), + requestReference: CoreId.from("shareRequestReference2a") + }, + succeeds: predecessorId + }); + + await this.context.consumptionController.attributes.createAttributeUnsafe({ + id: predecessorId, + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A former second name" + }, + owner: this.context.currentIdentity + }), + shareInfo: { + peer: CoreAddress.from("peer"), + requestReference: CoreId.from("shareRequestReference2b") + }, + succeededBy: sOwnSharedIdentityAttribute2.id + }); + + const createParams: ICreateOutgoingRequestParameters = { + content: { + items: [ + RequestItemGroup.from({ + mustBeAccepted: false, + items: [ + DeleteAttributeRequestItem.from({ + attributeId: sOwnSharedIdentityAttribute1.id.toString(), + mustBeAccepted: false + }), + TestRequestItem.from({ + mustBeAccepted: false + }) + ] + }), + DeleteAttributeRequestItem.from({ + attributeId: sOwnSharedIdentityAttribute2.id.toString(), + mustBeAccepted: false + }), + TestRequestItem.from({ + mustBeAccepted: false + }) + ] + }, + peer: CoreAddress.from("peer") + }; + const localRequest = await this.context.outgoingRequestsController.create(createParams); + + const sentParams: ISentOutgoingRequestParameters = { + requestId: localRequest.id, + requestSourceObject: TestObjectFactory.createOutgoingMessage(this.context.currentIdentity) + }; + this.context.localRequestAfterAction = await this.context.outgoingRequestsController.sent(sentParams); + } + public async iCreateAnIncomingRequestWithSource(sourceObject: Message | RelationshipTemplate): Promise { const request = TestObjectFactory.createRequestWithOneItem(); @@ -936,6 +1038,16 @@ export class RequestsThen { return Promise.resolve(); } + public async theDeletionInfoOfTheAssociatedAttributesAndPredecessorsIsSet(): Promise { + const sUpdatedOwnSharedIdentityAttributes = await this.context.consumptionController.attributes.getLocalAttributes(); + expect(sUpdatedOwnSharedIdentityAttributes).toBeDefined(); + expect(sUpdatedOwnSharedIdentityAttributes).toHaveLength(3); + for (const sUpdatedOwnSharedIdentityAttribute of sUpdatedOwnSharedIdentityAttributes) { + expect(sUpdatedOwnSharedIdentityAttribute.deletionInfo).toBeDefined(); + expect(sUpdatedOwnSharedIdentityAttribute.deletionInfo!.deletionStatus).toBe(DeletionStatus.DeletionRequestSent); + } + } + public theRequestHasItsResponsePropertySetCorrectly(expectedResult: ResponseItemResult): Promise { expect(this.context.localRequestAfterAction!.response).toBeDefined(); expect(this.context.localRequestAfterAction!.response).toBeInstanceOf(LocalResponse); diff --git a/packages/consumption/test/modules/requests/itemProcessors/deleteAttribute/DeleteAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/deleteAttribute/DeleteAttributeRequestItemProcessor.test.ts index dda86ebe2..aa30d5973 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/deleteAttribute/DeleteAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/deleteAttribute/DeleteAttributeRequestItemProcessor.test.ts @@ -4,6 +4,7 @@ import { DeleteAttributeAcceptResponseItem, DeleteAttributeRequestItem, IdentityAttribute, + RejectResponseItem, RelationshipAttribute, RelationshipAttributeConfidentiality, Request, @@ -58,7 +59,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: accountController.identity.address }), @@ -112,7 +113,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: accountController.identity.address }) @@ -136,7 +137,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: peerAddress }), @@ -195,7 +196,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: peerAddress }), @@ -223,7 +224,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: accountController.identity.address }), @@ -252,7 +253,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: accountController.identity.address }), @@ -285,7 +286,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: accountController.identity.address }), @@ -365,7 +366,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: peerAddress }), @@ -410,7 +411,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: peerAddress }), @@ -458,7 +459,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: peerAddress }), @@ -508,7 +509,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: peerAddress }), @@ -556,7 +557,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: peerAddress }), @@ -684,12 +685,114 @@ describe("DeleteAttributeRequestItemProcessor", function () { }); describe("applyIncomingResponseItem", function () { - test("sets the deletionInfo of an own shared Identity Attribute", async function () { + test("doesn't change the deletionInfo if a simple AcceptResponseItem is returned", async function () { + const deletionDate = CoreDate.utc().subtract({ days: 1 }); + const deletedByPeerAttribute = await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A birth name" + }, + owner: accountController.identity.address + }), + shareInfo: { + peer: peerAddress, + requestReference: CoreId.from("reqRef") + }, + deletionInfo: { + deletionStatus: DeletionStatus.DeletedByPeer, + deletionDate: deletionDate + } + }); + + const requestItem = DeleteAttributeRequestItem.from({ + mustBeAccepted: false, + attributeId: deletedByPeerAttribute.id + }); + + const requestId = await ConsumptionIds.request.generate(); + const incomingRequest = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: peerAddress, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const responseItem = AcceptResponseItem.from({ result: ResponseItemResult.Accepted }); + + await processor.applyIncomingResponseItem(responseItem, requestItem, incomingRequest); + + const unchangedAttribute = await consumptionController.attributes.getLocalAttribute(deletedByPeerAttribute.id); + expect(unchangedAttribute!.deletionInfo).toBeDefined(); + expect(unchangedAttribute!.deletionInfo!.deletionStatus).toStrictEqual(DeletionStatus.DeletedByPeer); + expect(unchangedAttribute!.deletionInfo!.deletionDate).toStrictEqual(deletionDate); + }); + + test("doesn't change the deletionInfo if the Attribute is DeletedByPeer", async function () { + const deletionDate = CoreDate.utc().subtract({ days: 1 }); + const deletedByPeerAttribute = await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A birth name" + }, + owner: accountController.identity.address + }), + shareInfo: { + peer: peerAddress, + requestReference: CoreId.from("reqRef") + }, + deletionInfo: { + deletionStatus: DeletionStatus.DeletedByPeer, + deletionDate: deletionDate + } + }); + + const requestItem = DeleteAttributeRequestItem.from({ + mustBeAccepted: false, + attributeId: deletedByPeerAttribute.id + }); + + const requestId = await ConsumptionIds.request.generate(); + const incomingRequest = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: peerAddress, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const responseDeletionDate = CoreDate.utc().add({ days: 1 }); + const responseItem = DeleteAttributeAcceptResponseItem.from({ + deletionDate: responseDeletionDate, + result: ResponseItemResult.Accepted + }); + + await processor.applyIncomingResponseItem(responseItem, requestItem, incomingRequest); + + const unchangedAttribute = await consumptionController.attributes.getLocalAttribute(deletedByPeerAttribute.id); + expect(unchangedAttribute!.deletionInfo).toBeDefined(); + expect(unchangedAttribute!.deletionInfo!.deletionStatus).toStrictEqual(DeletionStatus.DeletedByPeer); + expect(unchangedAttribute!.deletionInfo!.deletionDate).toStrictEqual(deletionDate); + }); + + test("sets the deletionInfo to ToBeDeletedByPeer of an own shared Identity Attribute", async function () { const sOwnSharedIdentityAttribute = await consumptionController.attributes.createAttributeUnsafe({ content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A birth name" }, owner: accountController.identity.address }), @@ -732,7 +835,7 @@ describe("DeleteAttributeRequestItemProcessor", function () { expect(updatedOwnSharedIdentityAttribute!.deletionInfo!.deletionDate).toStrictEqual(deletionDate); }); - test("sets the deletionInfo of an own shared Relationship Attribute", async function () { + test("sets the deletionInfo to ToBeDeletedByPeer of an own shared Relationship Attribute", async function () { const sOwnSharedRelationshipAttribute = await consumptionController.attributes.createAttributeUnsafe({ content: RelationshipAttribute.from({ key: "customerId", @@ -783,13 +886,29 @@ describe("DeleteAttributeRequestItemProcessor", function () { expect(updatedOwnSharedRelationshipAttribute!.deletionInfo!.deletionDate).toStrictEqual(deletionDate); }); - test("doesn't change the deletionInfo if the Attribute is already deleted by peer", async function () { - const deletionDate = CoreDate.utc().subtract({ days: 1 }); - const deletedByPeerAttribute = await consumptionController.attributes.createAttributeUnsafe({ + test("sets the deletionInfo to ToBeDeletedByPeer of the predecessor of an own shared Identity Attribute", async function () { + const sOwnSharedIdentityAttributeId = await ConsumptionIds.attribute.generate(); + const sPredecessorOwnSharedIdentityAttribute = await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A birth name" + }, + owner: accountController.identity.address + }), + shareInfo: { + peer: peerAddress, + requestReference: CoreId.from("reqRef") + }, + succeededBy: sOwnSharedIdentityAttributeId + }); + + const sOwnSharedIdentityAttribute = await consumptionController.attributes.createAttributeUnsafe({ + id: sOwnSharedIdentityAttributeId, content: IdentityAttribute.from({ value: { "@type": "BirthName", - value: "Schenkel" + value: "A succeeded birth name" }, owner: accountController.identity.address }), @@ -797,15 +916,84 @@ describe("DeleteAttributeRequestItemProcessor", function () { peer: peerAddress, requestReference: CoreId.from("reqRef") }, + succeeds: sPredecessorOwnSharedIdentityAttribute.id + }); + + const requestItem = DeleteAttributeRequestItem.from({ + mustBeAccepted: false, + attributeId: sOwnSharedIdentityAttribute.id + }); + + const requestId = await ConsumptionIds.request.generate(); + const incomingRequest = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: peerAddress, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const deletionDate = CoreDate.utc().add({ days: 1 }); + const responseItem = DeleteAttributeAcceptResponseItem.from({ + deletionDate: deletionDate, + result: ResponseItemResult.Accepted + }); + + await processor.applyIncomingResponseItem(responseItem, requestItem, incomingRequest); + + const updatedPredecessorOwnSharedIdentityAttribute = await consumptionController.attributes.getLocalAttribute(sPredecessorOwnSharedIdentityAttribute.id); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo).toBeDefined(); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo!.deletionStatus).toStrictEqual(DeletionStatus.ToBeDeletedByPeer); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo!.deletionDate).toStrictEqual(deletionDate); + }); + + test("doesn't change the deletionInfo to ToBeDeletedByPeer of a DeletedByPeer predecessor of an own shared Identity Attribute", async function () { + const sOwnSharedIdentityAttributeId = await ConsumptionIds.attribute.generate(); + const predecessorDeletionDate = CoreDate.utc().subtract({ days: 1 }); + + const sPredecessorOwnSharedIdentityAttribute = await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A birth name" + }, + owner: accountController.identity.address + }), + shareInfo: { + peer: peerAddress, + requestReference: CoreId.from("reqRef") + }, + succeededBy: sOwnSharedIdentityAttributeId, deletionInfo: { deletionStatus: DeletionStatus.DeletedByPeer, - deletionDate: deletionDate + deletionDate: predecessorDeletionDate } }); + const sOwnSharedIdentityAttribute = await consumptionController.attributes.createAttributeUnsafe({ + id: sOwnSharedIdentityAttributeId, + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A succeeded birth name" + }, + owner: accountController.identity.address + }), + shareInfo: { + peer: peerAddress, + requestReference: CoreId.from("reqRef") + }, + succeeds: sPredecessorOwnSharedIdentityAttribute.id + }); + const requestItem = DeleteAttributeRequestItem.from({ mustBeAccepted: false, - attributeId: deletedByPeerAttribute.id + attributeId: sOwnSharedIdentityAttribute.id }); const requestId = await ConsumptionIds.request.generate(); @@ -822,18 +1010,274 @@ describe("DeleteAttributeRequestItemProcessor", function () { statusLog: [] }); - const responseDeletionDate = CoreDate.utc().add({ days: 1 }); + const deletionDate = CoreDate.utc().add({ days: 1 }); const responseItem = DeleteAttributeAcceptResponseItem.from({ - deletionDate: responseDeletionDate, + deletionDate: deletionDate, result: ResponseItemResult.Accepted }); await processor.applyIncomingResponseItem(responseItem, requestItem, incomingRequest); - const unchangedAttribute = await consumptionController.attributes.getLocalAttribute(deletedByPeerAttribute.id); - expect(unchangedAttribute!.deletionInfo).toBeDefined(); - expect(unchangedAttribute!.deletionInfo!.deletionStatus).toStrictEqual(DeletionStatus.DeletedByPeer); - expect(unchangedAttribute!.deletionInfo!.deletionDate).toStrictEqual(deletionDate); + const updatedPredecessorOwnSharedIdentityAttribute = await consumptionController.attributes.getLocalAttribute(sPredecessorOwnSharedIdentityAttribute.id); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo).toBeDefined(); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo!.deletionStatus).toStrictEqual(DeletionStatus.DeletedByPeer); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo!.deletionDate).toStrictEqual(predecessorDeletionDate); + }); + + test("sets the deletionInfo to DeletionRequestRejected of an own shared Identity Attribute", async function () { + const sOwnSharedIdentityAttribute = await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A birth name" + }, + owner: accountController.identity.address + }), + shareInfo: { + peer: peerAddress, + requestReference: CoreId.from("reqRef") + } + }); + + const requestItem = DeleteAttributeRequestItem.from({ + mustBeAccepted: false, + attributeId: sOwnSharedIdentityAttribute.id + }); + + const requestId = await ConsumptionIds.request.generate(); + const incomingRequest = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: peerAddress, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const responseItem = RejectResponseItem.from({ + result: ResponseItemResult.Rejected + }); + + const timeBeforeUpdate = CoreDate.utc(); + await processor.applyIncomingResponseItem(responseItem, requestItem, incomingRequest); + const timeAfterUpdate = CoreDate.utc(); + + const updatedOwnSharedIdentityAttribute = await consumptionController.attributes.getLocalAttribute(sOwnSharedIdentityAttribute.id); + expect(updatedOwnSharedIdentityAttribute!.deletionInfo).toBeDefined(); + expect(updatedOwnSharedIdentityAttribute!.deletionInfo!.deletionStatus).toStrictEqual(DeletionStatus.DeletionRequestRejected); + expect(updatedOwnSharedIdentityAttribute!.deletionInfo!.deletionDate.isBetween(timeBeforeUpdate, timeAfterUpdate.add({ milliseconds: 1 }), "millisecond")).toBe(true); + }); + + test("sets the deletionInfo to DeletionRequestRejected of the predecessor of an own shared Identity Attribute", async function () { + const sOwnSharedIdentityAttributeId = await ConsumptionIds.attribute.generate(); + const sPredecessorOwnSharedIdentityAttribute = await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A birth name" + }, + owner: accountController.identity.address + }), + shareInfo: { + peer: peerAddress, + requestReference: CoreId.from("reqRef") + }, + succeededBy: sOwnSharedIdentityAttributeId + }); + + const sOwnSharedIdentityAttribute = await consumptionController.attributes.createAttributeUnsafe({ + id: sOwnSharedIdentityAttributeId, + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A succeeded birth name" + }, + owner: accountController.identity.address + }), + shareInfo: { + peer: peerAddress, + requestReference: CoreId.from("reqRef") + }, + succeeds: sPredecessorOwnSharedIdentityAttribute.id + }); + + const requestItem = DeleteAttributeRequestItem.from({ + mustBeAccepted: false, + attributeId: sOwnSharedIdentityAttribute.id + }); + + const requestId = await ConsumptionIds.request.generate(); + const incomingRequest = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: peerAddress, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const responseItem = RejectResponseItem.from({ + result: ResponseItemResult.Rejected + }); + + const timeBeforeUpdate = CoreDate.utc(); + await processor.applyIncomingResponseItem(responseItem, requestItem, incomingRequest); + const timeAfterUpdate = CoreDate.utc(); + + const updatedPredecessorOwnSharedIdentityAttribute = await consumptionController.attributes.getLocalAttribute(sPredecessorOwnSharedIdentityAttribute.id); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo).toBeDefined(); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo!.deletionStatus).toStrictEqual(DeletionStatus.DeletionRequestRejected); + expect( + updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo!.deletionDate.isBetween(timeBeforeUpdate, timeAfterUpdate.add({ milliseconds: 1 }), "millisecond") + ).toBe(true); + }); + + test("doesn't change the deletionInfo to DeletionRequestRejected of a ToBeDeletedByPeer predecessor of an own shared Identity Attribute", async function () { + const sOwnSharedIdentityAttributeId = await ConsumptionIds.attribute.generate(); + const deletionDate = CoreDate.utc().add({ days: 1 }); + + const sPredecessorOwnSharedIdentityAttribute = await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A birth name" + }, + owner: accountController.identity.address + }), + shareInfo: { + peer: peerAddress, + requestReference: CoreId.from("reqRef") + }, + succeededBy: sOwnSharedIdentityAttributeId, + deletionInfo: { + deletionStatus: DeletionStatus.ToBeDeletedByPeer, + deletionDate: deletionDate + } + }); + + const sOwnSharedIdentityAttribute = await consumptionController.attributes.createAttributeUnsafe({ + id: sOwnSharedIdentityAttributeId, + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A succeeded birth name" + }, + owner: accountController.identity.address + }), + shareInfo: { + peer: peerAddress, + requestReference: CoreId.from("reqRef") + }, + succeeds: sPredecessorOwnSharedIdentityAttribute.id + }); + + const requestItem = DeleteAttributeRequestItem.from({ + mustBeAccepted: false, + attributeId: sOwnSharedIdentityAttribute.id + }); + + const requestId = await ConsumptionIds.request.generate(); + const incomingRequest = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: peerAddress, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const responseItem = RejectResponseItem.from({ + result: ResponseItemResult.Rejected + }); + + await processor.applyIncomingResponseItem(responseItem, requestItem, incomingRequest); + + const updatedPredecessorOwnSharedIdentityAttribute = await consumptionController.attributes.getLocalAttribute(sPredecessorOwnSharedIdentityAttribute.id); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo).toBeDefined(); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo!.deletionStatus).toStrictEqual(DeletionStatus.ToBeDeletedByPeer); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo!.deletionDate).toStrictEqual(deletionDate); + }); + + test("doesn't change the deletionInfo to DeletionRequestRejected of a DeletedByPeer predecessor of an own shared Identity Attribute", async function () { + const sOwnSharedIdentityAttributeId = await ConsumptionIds.attribute.generate(); + const deletionDate = CoreDate.utc().subtract({ days: 1 }); + + const sPredecessorOwnSharedIdentityAttribute = await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A birth name" + }, + owner: accountController.identity.address + }), + shareInfo: { + peer: peerAddress, + requestReference: CoreId.from("reqRef") + }, + succeededBy: sOwnSharedIdentityAttributeId, + deletionInfo: { + deletionStatus: DeletionStatus.DeletedByPeer, + deletionDate: deletionDate + } + }); + + const sOwnSharedIdentityAttribute = await consumptionController.attributes.createAttributeUnsafe({ + id: sOwnSharedIdentityAttributeId, + content: IdentityAttribute.from({ + value: { + "@type": "BirthName", + value: "A succeeded birth name" + }, + owner: accountController.identity.address + }), + shareInfo: { + peer: peerAddress, + requestReference: CoreId.from("reqRef") + }, + succeeds: sPredecessorOwnSharedIdentityAttribute.id + }); + + const requestItem = DeleteAttributeRequestItem.from({ + mustBeAccepted: false, + attributeId: sOwnSharedIdentityAttribute.id + }); + + const requestId = await ConsumptionIds.request.generate(); + const incomingRequest = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: peerAddress, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const responseItem = RejectResponseItem.from({ + result: ResponseItemResult.Rejected + }); + + await processor.applyIncomingResponseItem(responseItem, requestItem, incomingRequest); + + const updatedPredecessorOwnSharedIdentityAttribute = await consumptionController.attributes.getLocalAttribute(sPredecessorOwnSharedIdentityAttribute.id); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo).toBeDefined(); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo!.deletionStatus).toStrictEqual(DeletionStatus.DeletedByPeer); + expect(updatedPredecessorOwnSharedIdentityAttribute!.deletionInfo!.deletionDate).toStrictEqual(deletionDate); }); }); }); diff --git a/packages/content/src/attributes/types/strings/AbstractEMailAddress.ts b/packages/content/src/attributes/types/strings/AbstractEMailAddress.ts index e374d9870..db1858576 100644 --- a/packages/content/src/attributes/types/strings/AbstractEMailAddress.ts +++ b/packages/content/src/attributes/types/strings/AbstractEMailAddress.ts @@ -4,19 +4,22 @@ import { AbstractString } from "../AbstractString"; export abstract class AbstractEMailAddress extends AbstractString { // from https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address + private static readonly regExp = new RegExp( + /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@([A-Za-z0-9ÄäÖöÜüß]([A-Za-z0-9ÄäÖöÜüß-]{0,61}[A-Za-z0-9ÄäÖöÜüß])?\.)+[A-Za-z0-9ÄäÖöÜüß][A-Za-z0-9ÄäÖöÜüß-]{0,61}[A-Za-z0-9ÄäÖöÜüß]$/ + ); @serialize() @validate({ min: 3, - max: 100, - regExp: new RegExp("^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$", "i") + max: 254, + regExp: AbstractEMailAddress.regExp }) public override value: string; public static override get valueHints(): ValueHints { return super.valueHints.copyWith({ min: 3, - max: 100, - pattern: "/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$/i" + max: 254, + pattern: String(AbstractEMailAddress.regExp) }); } diff --git a/packages/content/src/attributes/types/strings/AbstractURL.ts b/packages/content/src/attributes/types/strings/AbstractURL.ts index 65dbaf13f..5b7619fe7 100644 --- a/packages/content/src/attributes/types/strings/AbstractURL.ts +++ b/packages/content/src/attributes/types/strings/AbstractURL.ts @@ -3,14 +3,15 @@ import { RenderHints, RenderHintsDataType, RenderHintsEditType, ValueHints } fro import { AbstractString } from "../AbstractString"; export abstract class AbstractURL extends AbstractString { + private static readonly regExp = new RegExp( + /^([A-Za-z]+:\/\/)?((www\.)|(?!www\.))([A-Za-z0-9ÄäÖöÜüß]([A-Za-zÄäÖöÜüß0-9-]{0,61}[A-Za-zÄäÖöÜüß0-9])?\.)+([A-Za-z0-9ÄäÖöÜüß]([A-Za-zÄäÖöÜüß0-9-]{0,61}[A-Za-zÄäÖöÜüß0-9])?)(:[0-9]+)?(\/[A-Za-zÄäÖöÜüß0-9?#@!$&'()*+,;=%-]*)*$/ + ); + @serialize() @validate({ min: 3, max: 1024, - regExp: new RegExp( - // eslint-disable-next-line no-useless-escape - /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/ - ) + regExp: AbstractURL.regExp }) public override value: string; @@ -18,8 +19,7 @@ export abstract class AbstractURL extends AbstractString { return super.valueHints.copyWith({ min: 3, max: 1024, - pattern: - "/((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[-;:&=\\+\\$,\\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\\+\\$,\\w]+@)[A-Za-z0-9.-]+)((?:\\/[\\+~%\\/.\\w\\-_]*)?\\??(?:[-\\+=&;%@.\\w_]*)#?(?:[\\w]*))?)/" + pattern: String(AbstractURL.regExp) }); } diff --git a/packages/content/src/requests/RequestItem.ts b/packages/content/src/requests/RequestItem.ts index 3f0c5d139..dc71ca65f 100644 --- a/packages/content/src/requests/RequestItem.ts +++ b/packages/content/src/requests/RequestItem.ts @@ -7,11 +7,14 @@ import { ConsentRequestItemJSON, CreateAttributeRequestItem, CreateAttributeRequestItemJSON, + DeleteAttributeRequestItem, + DeleteAttributeRequestItemJSON, FreeTextRequestItem, FreeTextRequestItemJSON, IAuthenticationRequestItem, IConsentRequestItem, ICreateAttributeRequestItem, + IDeleteAttributeRequestItem, IFreeTextRequestItem, IProposeAttributeRequestItem, IReadAttributeRequestItem, @@ -67,6 +70,7 @@ export interface RequestItemJSON extends ContentJSON { export type RequestItemJSONDerivations = | RequestItemJSON | CreateAttributeRequestItemJSON + | DeleteAttributeRequestItemJSON | ShareAttributeRequestItemJSON | ProposeAttributeRequestItemJSON | ReadAttributeRequestItemJSON @@ -115,6 +119,7 @@ export interface IRequestItem extends ISerializable { export type IRequestItemDerivations = | IRequestItem | ICreateAttributeRequestItem + | IDeleteAttributeRequestItem | IShareAttributeRequestItem | IProposeAttributeRequestItem | IReadAttributeRequestItem @@ -152,6 +157,7 @@ export abstract class RequestItem extends Serializable { export type RequestItemDerivations = | RequestItem | CreateAttributeRequestItem + | DeleteAttributeRequestItem | ShareAttributeRequestItem | ProposeAttributeRequestItem | ReadAttributeRequestItem diff --git a/packages/content/test/attributes/EMailAddress.test.ts b/packages/content/test/attributes/EMailAddress.test.ts new file mode 100644 index 000000000..ddf22e0bf --- /dev/null +++ b/packages/content/test/attributes/EMailAddress.test.ts @@ -0,0 +1,38 @@ +import { ParsingError } from "@js-soft/ts-serval"; +import { EMailAddress } from "../../src"; + +describe("Test valid EMailAddresses", () => { + const validEMailAddresses = ["peter123@inwind.it", "peter123@inwänd.it"]; + + test.each(validEMailAddresses)("EMail %s is recognized as valid", (email) => { + const validEMailAddress = EMailAddress.from({ value: email }); + expect(validEMailAddress.value.toString()).toBe(email); + }); +}); + +describe("Test invalid EMailAddresses", () => { + const invalidEMailAddresses = ["Hugo Becker@gmx.de", "Becker@gmx", "Becker@gmx-.de", "Becker@gmx-.de", ".Becker@gmx.de", "test@.address", "test@test..address"]; + + test.each(invalidEMailAddresses)("EMail %s is recognized as invalid", (email) => { + const invalidEMailAddressCall = () => { + EMailAddress.from({ + value: email + }); + }; + expect(invalidEMailAddressCall).toThrow( + new ParsingError( + "EMailAddress", + "value", + "Value does not match regular expression /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@([A-Za-z0-9ÄäÖöÜüß]([A-Za-z0-9ÄäÖöÜüß-]{0,61}[A-Za-z0-9ÄäÖöÜüß])?\\.)+[A-Za-z0-9ÄäÖöÜüß][A-Za-z0-9ÄäÖöÜüß-]{0,61}[A-Za-z0-9ÄäÖöÜüß]$/" + ) + ); + }); + test("returns an error when trying to create an Attribute Value Type EMailAddress wich is empty", function () { + const invalidEMailAddressCall = () => { + EMailAddress.from({ + value: "" + }); + }; + expect(invalidEMailAddressCall).toThrow(new ParsingError("EMailAddress", "value", "Value is shorter than 3 characters")); + }); +}); diff --git a/packages/content/test/attributes/Website.test.ts b/packages/content/test/attributes/Website.test.ts new file mode 100644 index 000000000..53fba0c46 --- /dev/null +++ b/packages/content/test/attributes/Website.test.ts @@ -0,0 +1,45 @@ +import { ParsingError } from "@js-soft/ts-serval"; +import { Website } from "../../src"; + +describe("Test valid URLs", () => { + const validUrls = [ + "www.google.com", + "https://inwänd.it", + "http://inwind.it", + "https://enmeshed.de/blog/meilenstein-enmeshed-als-komponente-ablage-in-mein-bildungsraum-geht-in-die-testphase-der-beta-version/", + "www.foo.www.www.enmeshed.eu", + "https://example.org:8080/mein/ordner/bericht" + ]; + + test.each(validUrls)("URL %s is recognized as valid", (url) => { + const validWebsite = Website.from({ value: url }); + expect(validWebsite.value.toString()).toBe(url); + }); +}); + +describe("Test invalid URLs", () => { + const invalidUrls = ["google-.de", "www.google", "www.-google", "https://inwind.test it"]; + + test.each(invalidUrls)("URL %s is recognized as invalid", (url) => { + const invalidWebsiteCall = () => { + Website.from({ + value: url + }); + }; + expect(invalidWebsiteCall).toThrow( + new ParsingError( + "Website", + "value", + "Value does not match regular expression /^([A-Za-z]+:\\/\\/)?((www\\.)|(?!www\\.))([A-Za-z0-9ÄäÖöÜüß]([A-Za-zÄäÖöÜüß0-9-]{0,61}[A-Za-zÄäÖöÜüß0-9])?\\.)+([A-Za-z0-9ÄäÖöÜüß]([A-Za-zÄäÖöÜüß0-9-]{0,61}[A-Za-zÄäÖöÜüß0-9])?)(:[0-9]+)?(\\/[A-Za-zÄäÖöÜüß0-9?#@!$&'()*+,;=%-]*)*$/" + ) + ); + }); + test("returns an error when trying to create an Attribute Value Type Website wich is empty", function () { + const invalidWebsiteCall = () => { + Website.from({ + value: "" + }); + }; + expect(invalidWebsiteCall).toThrow(new ParsingError("Website", "value", "Value is shorter than 3 characters")); + }); +}); diff --git a/packages/runtime/package.json b/packages/runtime/package.json index a52276771..914b29db3 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -76,7 +76,7 @@ "@js-soft/docdb-access-mongo": "1.1.8", "@js-soft/node-logger": "1.1.1", "@types/json-stringify-safe": "^5.0.3", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.5", "@types/luxon": "^3.4.2", "@types/qrcode": "^1.5.5", "ts-json-schema-generator": "2.3.0" diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index b6fac8f9d..9690d92b1 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -17222,45 +17222,6 @@ export const GetRepositoryAttributesRequest: any = { } } ] - }, - "deletionInfo": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "deletionInfo.deletionStatus": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "deletionInfo.deletionDate": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] } }, "additionalProperties": false diff --git a/packages/runtime/src/useCases/consumption/attributes/GetRepositoryAttributes.ts b/packages/runtime/src/useCases/consumption/attributes/GetRepositoryAttributes.ts index 245387f5a..0395fba62 100644 --- a/packages/runtime/src/useCases/consumption/attributes/GetRepositoryAttributes.ts +++ b/packages/runtime/src/useCases/consumption/attributes/GetRepositoryAttributes.ts @@ -19,9 +19,6 @@ export interface GetRepositoryAttributesRequestQuery { "content.validFrom"?: string | string[]; "content.validTo"?: string | string[]; "content.value.@type"?: string | string[]; - deletionInfo?: string | string[]; - "deletionInfo.deletionStatus"?: string | string[]; - "deletionInfo.deletionDate"?: string | string[]; } export interface GetRepositoryAttributesResponse extends Array {} diff --git a/packages/transport/package.json b/packages/transport/package.json index 45488321b..f1c53b7b2 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -72,7 +72,7 @@ "qs": "^6.12.1", "reflect-metadata": "^0.2.2", "ts-simple-nameof": "^1.3.1", - "uuid": "^9.0.1" + "uuid": "^10.0.0" }, "devDependencies": { "@js-soft/docdb-access-loki": "1.1.0", @@ -82,7 +82,7 @@ "@js-soft/web-logger": "^1.0.4", "@nmshd/crypto": "2.0.6", "@types/json-stringify-safe": "^5.0.3", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.5", "@types/luxon": "^3.4.2", "@types/qs": "^6.9.15", "@types/uuid": "^9.0.8", diff --git a/packages/transport/src/core/backbone/RESTClient.ts b/packages/transport/src/core/backbone/RESTClient.ts index 458aa89b8..4da2861a2 100644 --- a/packages/transport/src/core/backbone/RESTClient.ts +++ b/packages/transport/src/core/backbone/RESTClient.ts @@ -4,7 +4,6 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; import formDataLib from "form-data"; import { AgentOptions } from "http"; import { AgentOptions as HTTPSAgentOptions } from "https"; -import { HttpsProxyAgent } from "https-proxy-agent"; import _ from "lodash"; import { TransportLoggerFactory } from "../TransportLoggerFactory"; import { CoreId } from "../types"; @@ -84,8 +83,14 @@ export class RESTClient { const resultingRequestConfig = _.defaultsDeep(defaults, requestConfig); if (typeof window === "undefined" && (process.env.https_proxy ?? process.env.HTTPS_PROXY)) { - const httpsProxy = (process.env.https_proxy ?? process.env.HTTPS_PROXY)!; - resultingRequestConfig.httpsAgent = new HttpsProxyAgent(httpsProxy, this.config.httpsAgentOptions); + try { + const httpsProxy = (process.env.https_proxy ?? process.env.HTTPS_PROXY)!; + // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/naming-convention + const HttpsProxyAgent = require("https-proxy-agent").HttpsProxyAgent; + resultingRequestConfig.httpsAgent = new HttpsProxyAgent(httpsProxy, this.config.httpsAgentOptions); + } catch (e) { + // ignore + } } else { try { // eslint-disable-next-line @typescript-eslint/no-require-imports From b3dcedf9ee86fd4c58e1361b5368eefa5be2ad66 Mon Sep 17 00:00:00 2001 From: Sebastian Mahr Date: Tue, 18 Jun 2024 11:59:54 +0200 Subject: [PATCH 10/40] Chore/ new alpha (#162) * chore: release new alpha * Chore/ Only use productive dependencies for audit (#163) --- .ci/runChecks.sh | 2 +- package-lock.json | 18 +- packages/app-runtime/package.json | 4 +- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- packages/runtime/package.json | 8 +- .../runtime/src/useCases/common/Schemas.ts | 601 +++++++++++++----- packages/transport/package.json | 2 +- 8 files changed, 467 insertions(+), 172 deletions(-) diff --git a/.ci/runChecks.sh b/.ci/runChecks.sh index 52e156c96..56c1e9be2 100755 --- a/.ci/runChecks.sh +++ b/.ci/runChecks.sh @@ -5,4 +5,4 @@ npm run build:node npm run lint:eslint npm run lint:prettier npx -ws license-check -npx better-npm-audit audit --exclude 1096302 +npx better-npm-audit audit -p --exclude 1096302 diff --git a/package-lock.json b/package-lock.json index a37e206b3..3c4058b86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12375,7 +12375,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.1", + "version": "5.0.0-alpha.2", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12389,12 +12389,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.1" + "@nmshd/runtime": "^5.0.0-alpha.2" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.1", + "version": "5.0.0-alpha.2", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12416,7 +12416,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.1", + "version": "5.0.0-alpha.2", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12446,17 +12446,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.1", + "version": "5.0.0-alpha.2", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.1", - "@nmshd/content": "5.0.0-alpha.1", + "@nmshd/consumption": "5.0.0-alpha.2", + "@nmshd/content": "5.0.0-alpha.2", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.1", + "@nmshd/transport": "5.0.0-alpha.2", "ajv": "^8.16.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12488,7 +12488,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.1", + "version": "5.0.0-alpha.2", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index 72d4a480f..8f69571e5 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.1", + "version": "5.0.0-alpha.2", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.1" + "@nmshd/runtime": "^5.0.0-alpha.2" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index d152e0d8b..ddfa40830 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.1", + "version": "5.0.0-alpha.2", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index d72fa2d4f..cccb2c2b6 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.1", + "version": "5.0.0-alpha.2", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 914b29db3..b9bd21b51 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.1", + "version": "5.0.0-alpha.2", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.1", - "@nmshd/content": "5.0.0-alpha.1", + "@nmshd/consumption": "5.0.0-alpha.2", + "@nmshd/content": "5.0.0-alpha.2", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.1", + "@nmshd/transport": "5.0.0-alpha.2", "ajv": "^8.16.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index 9690d92b1..5a3c99a88 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -273,6 +273,9 @@ export const CanCreateOutgoingRequestRequest: any = { { "$ref": "#/definitions/CreateAttributeRequestItemJSON" }, + { + "$ref": "#/definitions/DeleteAttributeRequestItemJSON" + }, { "$ref": "#/definitions/ShareAttributeRequestItemJSON" }, @@ -2296,6 +2299,50 @@ export const CanCreateOutgoingRequestRequest: any = { ], "additionalProperties": false }, + "DeleteAttributeRequestItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "DeleteAttributeRequestItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "title": { + "type": "string", + "description": "The human-readable title of this item." + }, + "description": { + "type": "string", + "description": "The human-readable description of this item." + }, + "metadata": { + "type": "object", + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + }, + "mustBeAccepted": { + "type": "boolean", + "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + }, + "requireManualDecision": { + "type": "boolean", + "description": "If set to `true`, it advices the recipient of this RequestItem to carefully consider their decision and especially do not decide based on some automation rules." + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "mustBeAccepted" + ], + "additionalProperties": false + }, "ShareAttributeRequestItemJSON": { "type": "object", "properties": { @@ -3191,6 +3238,12 @@ export const CompleteOutgoingRequestRequest: any = { { "$ref": "#/definitions/AcceptResponseItemJSON" }, + { + "$ref": "#/definitions/AttributeAlreadySharedAcceptResponseItemJSON" + }, + { + "$ref": "#/definitions/AttributeSuccessionAcceptResponseItemJSON" + }, { "$ref": "#/definitions/CreateAttributeAcceptResponseItemJSON" }, @@ -3234,12 +3287,12 @@ export const CompleteOutgoingRequestRequest: any = { ], "additionalProperties": false }, - "CreateAttributeAcceptResponseItemJSON": { + "AttributeAlreadySharedAcceptResponseItemJSON": { "type": "object", "properties": { "@type": { "type": "string", - "const": "CreateAttributeAcceptResponseItem" + "const": "AttributeAlreadySharedAcceptResponseItem" }, "@context": { "type": "string" @@ -3262,12 +3315,12 @@ export const CompleteOutgoingRequestRequest: any = { ], "additionalProperties": false }, - "ShareAttributeAcceptResponseItemJSON": { + "AttributeSuccessionAcceptResponseItemJSON": { "type": "object", "properties": { "@type": { "type": "string", - "const": "ShareAttributeAcceptResponseItem" + "const": "AttributeSuccessionAcceptResponseItem" }, "@context": { "type": "string" @@ -3279,38 +3332,13 @@ export const CompleteOutgoingRequestRequest: any = { "type": "string", "const": "Accepted" }, - "attributeId": { - "type": "string" - } - }, - "required": [ - "@type", - "attributeId", - "result" - ], - "additionalProperties": false - }, - "ProposeAttributeAcceptResponseItemJSON": { - "type": "object", - "properties": { - "@type": { - "type": "string", - "const": "ProposeAttributeAcceptResponseItem" - }, - "@context": { - "type": "string" - }, - "@version": { + "predecessorId": { "type": "string" }, - "result": { - "type": "string", - "const": "Accepted" - }, - "attributeId": { + "successorId": { "type": "string" }, - "attribute": { + "successorContent": { "anyOf": [ { "$ref": "#/definitions/IdentityAttributeJSON" @@ -3323,9 +3351,10 @@ export const CompleteOutgoingRequestRequest: any = { }, "required": [ "@type", - "attribute", - "attributeId", - "result" + "predecessorId", + "result", + "successorContent", + "successorId" ], "additionalProperties": false }, @@ -5239,6 +5268,101 @@ export const CompleteOutgoingRequestRequest: any = { "protected" ] }, + "CreateAttributeAcceptResponseItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "CreateAttributeAcceptResponseItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "result": { + "type": "string", + "const": "Accepted" + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "result" + ], + "additionalProperties": false + }, + "ShareAttributeAcceptResponseItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "ShareAttributeAcceptResponseItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "result": { + "type": "string", + "const": "Accepted" + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "result" + ], + "additionalProperties": false + }, + "ProposeAttributeAcceptResponseItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "ProposeAttributeAcceptResponseItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "result": { + "type": "string", + "const": "Accepted" + }, + "attributeId": { + "type": "string" + }, + "attribute": { + "anyOf": [ + { + "$ref": "#/definitions/IdentityAttributeJSON" + }, + { + "$ref": "#/definitions/RelationshipAttributeJSON" + } + ] + } + }, + "required": [ + "@type", + "attribute", + "attributeId", + "result" + ], + "additionalProperties": false + }, "ReadAttributeAcceptResponseItemJSON": { "type": "object", "properties": { @@ -5544,6 +5668,12 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq { "$ref": "#/definitions/AcceptResponseItemJSON" }, + { + "$ref": "#/definitions/AttributeAlreadySharedAcceptResponseItemJSON" + }, + { + "$ref": "#/definitions/AttributeSuccessionAcceptResponseItemJSON" + }, { "$ref": "#/definitions/CreateAttributeAcceptResponseItemJSON" }, @@ -5587,12 +5717,12 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq ], "additionalProperties": false }, - "CreateAttributeAcceptResponseItemJSON": { + "AttributeAlreadySharedAcceptResponseItemJSON": { "type": "object", "properties": { "@type": { "type": "string", - "const": "CreateAttributeAcceptResponseItem" + "const": "AttributeAlreadySharedAcceptResponseItem" }, "@context": { "type": "string" @@ -5615,12 +5745,12 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq ], "additionalProperties": false }, - "ShareAttributeAcceptResponseItemJSON": { + "AttributeSuccessionAcceptResponseItemJSON": { "type": "object", "properties": { "@type": { "type": "string", - "const": "ShareAttributeAcceptResponseItem" + "const": "AttributeSuccessionAcceptResponseItem" }, "@context": { "type": "string" @@ -5632,38 +5762,13 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq "type": "string", "const": "Accepted" }, - "attributeId": { - "type": "string" - } - }, - "required": [ - "@type", - "attributeId", - "result" - ], - "additionalProperties": false - }, - "ProposeAttributeAcceptResponseItemJSON": { - "type": "object", - "properties": { - "@type": { - "type": "string", - "const": "ProposeAttributeAcceptResponseItem" - }, - "@context": { - "type": "string" - }, - "@version": { + "predecessorId": { "type": "string" }, - "result": { - "type": "string", - "const": "Accepted" - }, - "attributeId": { + "successorId": { "type": "string" }, - "attribute": { + "successorContent": { "anyOf": [ { "$ref": "#/definitions/IdentityAttributeJSON" @@ -5676,9 +5781,10 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq }, "required": [ "@type", - "attribute", - "attributeId", - "result" + "predecessorId", + "result", + "successorContent", + "successorId" ], "additionalProperties": false }, @@ -7580,18 +7686,113 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq }, "required": [ "@type", - "consent" + "consent" + ], + "additionalProperties": false + }, + "RelationshipAttributeConfidentiality": { + "type": "string", + "enum": [ + "public", + "private", + "protected" + ] + }, + "CreateAttributeAcceptResponseItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "CreateAttributeAcceptResponseItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "result": { + "type": "string", + "const": "Accepted" + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "result" + ], + "additionalProperties": false + }, + "ShareAttributeAcceptResponseItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "ShareAttributeAcceptResponseItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "result": { + "type": "string", + "const": "Accepted" + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "result" + ], + "additionalProperties": false + }, + "ProposeAttributeAcceptResponseItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "ProposeAttributeAcceptResponseItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "result": { + "type": "string", + "const": "Accepted" + }, + "attributeId": { + "type": "string" + }, + "attribute": { + "anyOf": [ + { + "$ref": "#/definitions/IdentityAttributeJSON" + }, + { + "$ref": "#/definitions/RelationshipAttributeJSON" + } + ] + } + }, + "required": [ + "@type", + "attribute", + "attributeId", + "result" ], "additionalProperties": false }, - "RelationshipAttributeConfidentiality": { - "type": "string", - "enum": [ - "public", - "private", - "protected" - ] - }, "ReadAttributeAcceptResponseItemJSON": { "type": "object", "properties": { @@ -7871,6 +8072,9 @@ export const CreateOutgoingRequestRequest: any = { { "$ref": "#/definitions/CreateAttributeRequestItemJSON" }, + { + "$ref": "#/definitions/DeleteAttributeRequestItemJSON" + }, { "$ref": "#/definitions/ShareAttributeRequestItemJSON" }, @@ -9894,6 +10098,50 @@ export const CreateOutgoingRequestRequest: any = { ], "additionalProperties": false }, + "DeleteAttributeRequestItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "DeleteAttributeRequestItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "title": { + "type": "string", + "description": "The human-readable title of this item." + }, + "description": { + "type": "string", + "description": "The human-readable description of this item." + }, + "metadata": { + "type": "object", + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + }, + "mustBeAccepted": { + "type": "boolean", + "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + }, + "requireManualDecision": { + "type": "boolean", + "description": "If set to `true`, it advices the recipient of this RequestItem to carefully consider their decision and especially do not decide based on some automation rules." + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "mustBeAccepted" + ], + "additionalProperties": false + }, "ShareAttributeRequestItemJSON": { "type": "object", "properties": { @@ -11134,6 +11382,9 @@ export const ReceivedIncomingRequestRequest: any = { { "$ref": "#/definitions/CreateAttributeRequestItemJSON" }, + { + "$ref": "#/definitions/DeleteAttributeRequestItemJSON" + }, { "$ref": "#/definitions/ShareAttributeRequestItemJSON" }, @@ -13157,6 +13408,50 @@ export const ReceivedIncomingRequestRequest: any = { ], "additionalProperties": false }, + "DeleteAttributeRequestItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "DeleteAttributeRequestItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "title": { + "type": "string", + "description": "The human-readable title of this item." + }, + "description": { + "type": "string", + "description": "The human-readable description of this item." + }, + "metadata": { + "type": "object", + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + }, + "mustBeAccepted": { + "type": "boolean", + "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + }, + "requireManualDecision": { + "type": "boolean", + "description": "If set to `true`, it advices the recipient of this RequestItem to carefully consider their decision and especially do not decide based on some automation rules." + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "mustBeAccepted" + ], + "additionalProperties": false + }, "ShareAttributeRequestItemJSON": { "type": "object", "properties": { @@ -21715,16 +22010,16 @@ export const LoadPeerRelationshipTemplateRequest: any = { } } -export const RequestRelationshipReactivationRequest: any = { +export const AcceptRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/RequestRelationshipReactivationRequest", + "$ref": "#/definitions/AcceptRelationshipRequest", "definitions": { - "RequestRelationshipReactivationRequest": { + "AcceptRelationshipRequest": { "type": "object", "properties": { "relationshipId": { "$ref": "#/definitions/RelationshipIdString" - }, + } }, "required": [ "relationshipId" @@ -21734,7 +22029,7 @@ export const RequestRelationshipReactivationRequest: any = { "RelationshipIdString": { "type": "string", "pattern": "REL[A-Za-z0-9]{17}" - }, + } } } @@ -21743,75 +22038,6 @@ export const AcceptRelationshipReactivationRequest: any = { "$ref": "#/definitions/AcceptRelationshipReactivationRequest", "definitions": { "AcceptRelationshipReactivationRequest": { - "type": "object", - "properties": { - "relationshipId": { - "$ref": "#/definitions/RelationshipIdString" - }, - }, - "required": [ - "relationshipId" - ], - "additionalProperties": false - }, - "RelationshipIdString": { - "type": "string", - "pattern": "REL[A-Za-z0-9]{17}" - }, - } -} - -export const RejectRelationshipReactivationRequest: any = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/RejectRelationshipReactivationRequest", - "definitions": { - "RejectRelationshipReactivationRequest": { - "type": "object", - "properties": { - "relationshipId": { - "$ref": "#/definitions/RelationshipIdString" - }, - }, - "required": [ - "relationshipId" - ], - "additionalProperties": false - }, - "RelationshipIdString": { - "type": "string", - "pattern": "REL[A-Za-z0-9]{17}" - }, - } -} - -export const RevokeRelationshipReactivationRequest: any = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/RevokeRelationshipReactivationRequest", - "definitions": { - "RevokeRelationshipReactivationRequest": { - "type": "object", - "properties": { - "relationshipId": { - "$ref": "#/definitions/RelationshipIdString" - }, - }, - "required": [ - "relationshipId" - ], - "additionalProperties": false - }, - "RelationshipIdString": { - "type": "string", - "pattern": "REL[A-Za-z0-9]{17}" - }, - } -} - -export const AcceptRelationshipRequest: any = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/AcceptRelationshipRequest", - "definitions": { - "AcceptRelationshipRequest": { "type": "object", "properties": { "relationshipId": { @@ -22015,6 +22241,52 @@ export const RejectRelationshipRequest: any = { } } +export const RejectRelationshipReactivationRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/RejectRelationshipReactivationRequest", + "definitions": { + "RejectRelationshipReactivationRequest": { + "type": "object", + "properties": { + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" + } + }, + "required": [ + "relationshipId" + ], + "additionalProperties": false + }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" + } + } +} + +export const RequestRelationshipReactivationRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/RequestRelationshipReactivationRequest", + "definitions": { + "RequestRelationshipReactivationRequest": { + "type": "object", + "properties": { + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" + } + }, + "required": [ + "relationshipId" + ], + "additionalProperties": false + }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" + } + } +} + export const RevokeRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/RevokeRelationshipRequest", @@ -22038,6 +22310,29 @@ export const RevokeRelationshipRequest: any = { } } +export const RevokeRelationshipReactivationRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/RevokeRelationshipReactivationRequest", + "definitions": { + "RevokeRelationshipReactivationRequest": { + "type": "object", + "properties": { + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" + } + }, + "required": [ + "relationshipId" + ], + "additionalProperties": false + }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" + } + } +} + export const TerminateRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/TerminateRelationshipRequest", diff --git a/packages/transport/package.json b/packages/transport/package.json index f1c53b7b2..b97a53aa4 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.1", + "version": "5.0.0-alpha.2", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { From 6b06f2449601df08a07ad8870a1b60f15bf6794e Mon Sep 17 00:00:00 2001 From: Milena Czierlinski <146972016+Milena-Czierlinski@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:43:27 +0200 Subject: [PATCH 11/40] Feature/ShareAttributeRequestItem with deletionInfo (#174) * feat: allow to share an already shared attribute that is deleted by peer * fix: expect result * feat: also allow status ToBeDeletedByPeer * feat: adjust ShareRepositoryAttribute use case * chore: version bump * chore: remove unused error * chore: bump peer dependency --- package-lock.json | 24 ++--- packages/app-runtime/package.json | 4 +- packages/consumption/package.json | 2 +- .../ShareAttributeRequestItemProcessor.ts | 7 +- ...ShareAttributeRequestItemProcessor.test.ts | 90 ++++++++++++++++++- packages/content/package.json | 2 +- packages/runtime/package.json | 8 +- .../src/useCases/common/RuntimeErrors.ts | 11 --- .../attributes/ShareRepositoryAttribute.ts | 28 +----- .../test/consumption/attributes.test.ts | 10 ++- packages/transport/package.json | 2 +- 11 files changed, 126 insertions(+), 62 deletions(-) diff --git a/package-lock.json b/package-lock.json index 41f398a5c..d7ae05cbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2210,9 +2210,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.14.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.8.tgz", - "integrity": "sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==", + "version": "20.14.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.5.tgz", + "integrity": "sha512-aoRR+fJkZT2l0aGOJhuA8frnCSoNX6W7U2mpNq63+BxBIj5BQFt8rHy627kijCmm63ijdSdwvGgpUsU6MBsZZA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -12538,7 +12538,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.2", + "version": "5.0.0-alpha.3", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12552,12 +12552,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.2" + "@nmshd/runtime": "^5.0.0-alpha.3" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.2", + "version": "5.0.0-alpha.3", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12579,7 +12579,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.2", + "version": "5.0.0-alpha.3", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12609,17 +12609,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.2", + "version": "5.0.0-alpha.3", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.2", - "@nmshd/content": "5.0.0-alpha.2", + "@nmshd/consumption": "5.0.0-alpha.3", + "@nmshd/content": "5.0.0-alpha.3", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.2", + "@nmshd/transport": "5.0.0-alpha.3", "ajv": "^8.16.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12651,7 +12651,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.2", + "version": "5.0.0-alpha.3", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index 8f69571e5..ab245d7bf 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.2", + "version": "5.0.0-alpha.3", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.2" + "@nmshd/runtime": "^5.0.0-alpha.3" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index ddfa40830..9458abb94 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.2", + "version": "5.0.0-alpha.3", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts index 14d6706c6..8bd4a4fd1 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts @@ -11,6 +11,7 @@ import { import { CoreAddress } from "@nmshd/transport"; import _ from "lodash"; import { CoreErrors } from "../../../../consumption/CoreErrors"; +import { DeletionStatus } from "../../../attributes"; import { ValidationResult } from "../../../common/ValidationResult"; import { AcceptRequestItemParametersJSON } from "../../incoming/decide/AcceptRequestItemParameters"; import { GenericRequestItemProcessor } from "../GenericRequestItemProcessor"; @@ -47,7 +48,11 @@ export class ShareAttributeRequestItemProcessor extends GenericRequestItemProces } if (typeof recipient !== "undefined") { - const query: any = { "shareInfo.sourceAttribute": requestItem.sourceAttributeId.toString(), "shareInfo.peer": recipient.toString() }; + const query: any = { + "shareInfo.sourceAttribute": requestItem.sourceAttributeId.toString(), + "shareInfo.peer": recipient.toString(), + "deletionInfo.deletionStatus": { $nin: [DeletionStatus.DeletedByPeer, DeletionStatus.ToBeDeletedByPeer] } + }; if ((await this.consumptionController.attributes.getLocalAttributes(query)).length > 0) { return ValidationResult.error( diff --git a/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts index 49079c989..18e04580c 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts @@ -14,7 +14,7 @@ import { Surname } from "@nmshd/content"; import { AccountController, CoreAddress, CoreDate, CoreId, Transport } from "@nmshd/transport"; -import { ConsumptionController, ConsumptionIds, LocalAttribute, LocalRequest, LocalRequestStatus, ShareAttributeRequestItemProcessor } from "../../../../../src"; +import { ConsumptionController, ConsumptionIds, DeletionStatus, LocalAttribute, LocalRequest, LocalRequestStatus, ShareAttributeRequestItemProcessor } from "../../../../../src"; import { TestUtil } from "../../../../core/TestUtil"; import { TestObjectFactory } from "../../testHelpers/TestObjectFactory"; @@ -314,6 +314,94 @@ describe("ShareAttributeRequestItemProcessor", function () { }); }); + test("returns success when the IdentityAttribute is already shared with the peer but DeletedByPeer", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: IdentityAttribute.from({ + owner: sender, + value: GivenName.from({ + value: "AGivenName" + }) + }) + }); + + const localAttributeCopy = await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + owner: sender, + value: GivenName.from({ + value: "AGivenName" + }) + }), + shareInfo: { + peer: recipient, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: localAttribute.id + }, + deletionInfo: { + deletionStatus: DeletionStatus.DeletedByPeer, + deletionDate: CoreDate.utc().subtract({ days: 1 }) + } + }); + expect(localAttributeCopy.isShared()).toBe(true); + + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: false, + attribute: localAttribute.content, + sourceAttributeId: localAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).successfulValidationResult(); + }); + + test("returns success when the IdentityAttribute is already shared with the peer but ToBeDeletedByPeer", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + + const localAttribute = await consumptionController.attributes.createLocalAttribute({ + content: IdentityAttribute.from({ + owner: sender, + value: GivenName.from({ + value: "AGivenName" + }) + }) + }); + + const localAttributeCopy = await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + owner: sender, + value: GivenName.from({ + value: "AGivenName" + }) + }), + shareInfo: { + peer: recipient, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: localAttribute.id + }, + deletionInfo: { + deletionStatus: DeletionStatus.ToBeDeletedByPeer, + deletionDate: CoreDate.utc().subtract({ days: 1 }) + } + }); + expect(localAttributeCopy.isShared()).toBe(true); + + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: false, + attribute: localAttribute.content, + sourceAttributeId: localAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).successfulValidationResult(); + }); + test("returns an error when a successor of the existing IdentityAttribute is already shared", async function () { const sender = testAccount.identity.address; const recipient = CoreAddress.from("Recipient"); diff --git a/packages/content/package.json b/packages/content/package.json index cccb2c2b6..0a67ee2ac 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.2", + "version": "5.0.0-alpha.3", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index b9bd21b51..bb5b2b10a 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.2", + "version": "5.0.0-alpha.3", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.2", - "@nmshd/content": "5.0.0-alpha.2", + "@nmshd/consumption": "5.0.0-alpha.3", + "@nmshd/content": "5.0.0-alpha.3", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.2", + "@nmshd/transport": "5.0.0-alpha.3", "ajv": "^8.16.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/useCases/common/RuntimeErrors.ts b/packages/runtime/src/useCases/common/RuntimeErrors.ts index a6e45dee4..130674395 100644 --- a/packages/runtime/src/useCases/common/RuntimeErrors.ts +++ b/packages/runtime/src/useCases/common/RuntimeErrors.ts @@ -164,17 +164,6 @@ class Attributes { ); } - public anotherVersionOfRepositoryAttributeHasAlreadyBeenSharedWithPeer( - repositoryAttributeId: CoreId | string, - peer: CoreAddress | string, - ownSharedIdentityAttributeId: CoreId | string - ): ApplicationError { - return new ApplicationError( - "error.runtime.attributes.anotherVersionOfRepositoryAttributeHasAlreadyBeenSharedWithPeer", - `Another version of repository attribute '${repositoryAttributeId.toString()}' has already been shared with peer '${peer.toString()}'. ID of previous own shared identity attribute: ${ownSharedIdentityAttributeId.toString()}.` - ); - } - public noPreviousVersionOfRepositoryAttributeHasBeenSharedWithPeerBefore(repositoryAttributeId: CoreId | string, peer: CoreAddress | string): ApplicationError { return new ApplicationError( "error.runtime.attributes.noPreviousVersionOfRepositoryAttributeHasBeenSharedWithPeerBefore", diff --git a/packages/runtime/src/useCases/consumption/attributes/ShareRepositoryAttribute.ts b/packages/runtime/src/useCases/consumption/attributes/ShareRepositoryAttribute.ts index bcf1fe233..db2383600 100644 --- a/packages/runtime/src/useCases/consumption/attributes/ShareRepositoryAttribute.ts +++ b/packages/runtime/src/useCases/consumption/attributes/ShareRepositoryAttribute.ts @@ -1,5 +1,5 @@ import { Result } from "@js-soft/ts-utils"; -import { AttributesController, CreateOutgoingRequestParameters, DeletionStatus, LocalAttribute, OutgoingRequestsController } from "@nmshd/consumption"; +import { AttributesController, CreateOutgoingRequestParameters, ErrorValidationResult, LocalAttribute, OutgoingRequestsController } from "@nmshd/consumption"; import { Request, ShareAttributeRequestItem } from "@nmshd/content"; import { AccountController, CoreAddress, CoreId, MessageController } from "@nmshd/transport"; import { Inject } from "typescript-ioc"; @@ -50,30 +50,6 @@ export class ShareRepositoryAttributeUseCase extends UseCase 0) { - return Result.fail( - RuntimeErrors.attributes.repositoryAttributeHasAlreadyBeenSharedWithPeer(request.attributeId, request.peer, ownSharedIdentityAttributesOfRepositoryAttribute[0].id) - ); - } - - const sharedVersionsOfRepositoryAttribute = await this.attributeController.getSharedVersionsOfAttribute(repositoryAttributeId, [CoreAddress.from(request.peer)], false); - const activeSharedVersions = sharedVersionsOfRepositoryAttribute.filter( - (attr) => attr.deletionInfo?.deletionStatus !== DeletionStatus.DeletedByPeer && attr.deletionInfo?.deletionStatus !== DeletionStatus.ToBeDeletedByPeer - ); - if (activeSharedVersions.length > 0) { - return Result.fail( - RuntimeErrors.attributes.anotherVersionOfRepositoryAttributeHasAlreadyBeenSharedWithPeer(request.attributeId, request.peer, activeSharedVersions[0].id) - ); - } - const requestParams = CreateOutgoingRequestParameters.from({ peer: request.peer, content: Request.from({ @@ -90,7 +66,7 @@ export class ShareRepositoryAttributeUseCase extends UseCase { peer: services3.address }); - expect(repeatedShareRequestResult).toBeAnError(/.*/, "error.runtime.attributes.repositoryAttributeHasAlreadyBeenSharedWithPeer"); + expect(repeatedShareRequestResult).toBeAnError( + `The IdentityAttribute with the given sourceAttributeId '${sRepositoryAttribute.id}' has already been shared with the peer.`, + "error.consumption.requests.invalidRequestItem" + ); }); test("should reject sharing an attribute, of which a previous version has been shared", async () => { @@ -781,7 +784,10 @@ describe(ShareRepositoryAttributeUseCase.name, () => { attributeId: successorRepositoryAttribute.id, peer: services2.address }); - expect(response).toBeAnError(/.*/, "error.runtime.attributes.anotherVersionOfRepositoryAttributeHasAlreadyBeenSharedWithPeer"); + expect(response).toBeAnError( + `You have already shared the predecessor '${predecesssorOwnSharedIdentityAttribute.shareInfo!.sourceAttribute}' of the IdentityAttribute. Instead of sharing it, you should notify the peer about the Attribute succession.`, + "error.consumption.requests.invalidRequestItem" + ); }); test("should reject sharing an own shared identity attribute", async () => { diff --git a/packages/transport/package.json b/packages/transport/package.json index b97a53aa4..cc69cce8f 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.2", + "version": "5.0.0-alpha.3", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { From 9a005a255f210ebafeabc907ba7cf646c617c1a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:13:39 +0200 Subject: [PATCH 12/40] Chore/bump backbone again (#177) * chore: bump bkb version * chore: update config --- .dev/appsettings.override.json | 6 +++--- .dev/compose.backbone.env | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.dev/appsettings.override.json b/.dev/appsettings.override.json index ea2b61c7e..e655dfaec 100644 --- a/.dev/appsettings.override.json +++ b/.dev/appsettings.override.json @@ -51,7 +51,7 @@ } }, "Application": { - "InstanceUrl": "localhost" + "didDomainName": "localhost" } }, "Files": { @@ -74,7 +74,7 @@ } }, "Application": { - "InstanceUrl": "localhost" + "didDomainName": "localhost" } }, "Relationships": { @@ -85,7 +85,7 @@ } }, "Application": { - "InstanceUrl": "localhost" + "didDomainName": "localhost" } }, "Synchronization": { diff --git a/.dev/compose.backbone.env b/.dev/compose.backbone.env index 146f4c083..88c9fe832 100644 --- a/.dev/compose.backbone.env +++ b/.dev/compose.backbone.env @@ -1 +1 @@ -BACKBONE_VERSION=6.0.0 +BACKBONE_VERSION=6.1.0 From ec62b1150d2c5df39478f3d337d76242190445b0 Mon Sep 17 00:00:00 2001 From: Britta Stallknecht <146106656+britsta@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:30:56 +0200 Subject: [PATCH 13/40] Fix/Adjust attribute deletion feature for rejected relationships (#178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: use empty owner for CreateAttributeRequestItem * chore: add pull:backbone script * fix: accept more statuses for creating an incoming request * fix: always return the newest relationship out database layers do not do any sorting so we have to do that * chore: remove wrong type * chore: add version bumps --------- Co-authored-by: Julian König --- package-lock.json | 18 ++++++++--------- package.json | 1 + packages/app-runtime/package.json | 4 ++-- packages/consumption/package.json | 2 +- .../incoming/IncomingRequestsController.ts | 7 +++++-- .../outgoing/OutgoingRequestsController.ts | 3 +++ packages/content/package.json | 2 +- packages/runtime/package.json | 8 ++++---- packages/runtime/test/lib/testUtils.ts | 3 ++- .../test/modules/RequestModule.test.ts | 4 ++-- packages/transport/package.json | 2 +- .../relationships/RelationshipsController.ts | 20 +++++++------------ 12 files changed, 38 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index d7ae05cbb..5a3e5ce57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12538,7 +12538,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.3", + "version": "5.0.0-alpha.4", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12552,12 +12552,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.3" + "@nmshd/runtime": "^5.0.0-alpha.4" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.3", + "version": "5.0.0-alpha.4", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12579,7 +12579,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.3", + "version": "5.0.0-alpha.4", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12609,17 +12609,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.3", + "version": "5.0.0-alpha.4", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.3", - "@nmshd/content": "5.0.0-alpha.3", + "@nmshd/consumption": "5.0.0-alpha.4", + "@nmshd/content": "5.0.0-alpha.4", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.3", + "@nmshd/transport": "5.0.0-alpha.4", "ajv": "^8.16.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12651,7 +12651,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.3", + "version": "5.0.0-alpha.4", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/package.json b/package.json index f4708d5e4..03ad8fbea 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "lint:prettier": "prettier --check .", "lint:tsc": "npm run -ws lint:tsc", "outdated": "ncu -i && ncu -i -ws", + "pull:backbone": "docker compose -p runtime-test-backbone --env-file .dev/compose.backbone.env -f .dev/compose.backbone.yml pull", "start:backbone": "docker compose -p runtime-test-backbone --env-file .dev/compose.backbone.env -f .dev/compose.backbone.yml up -d", "teardown:backbone": "docker compose -p runtime-test-backbone --env-file .dev/compose.backbone.env -f .dev/compose.backbone.yml down -v", "test:teardown": "docker compose -f .dev/compose.yml down -fsv" diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index ab245d7bf..787bf1a7c 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.3", + "version": "5.0.0-alpha.4", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.3" + "@nmshd/runtime": "^5.0.0-alpha.4" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 9458abb94..610625c1e 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.3", + "version": "5.0.0-alpha.4", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts b/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts index f4f119d62..6983edb28 100644 --- a/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts +++ b/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts @@ -26,13 +26,13 @@ import { RequestItemProcessorRegistry } from "../itemProcessors/RequestItemProce import { ILocalRequestSource, LocalRequest } from "../local/LocalRequest"; import { LocalRequestStatus } from "../local/LocalRequestStatus"; import { LocalResponse, LocalResponseSource } from "../local/LocalResponse"; +import { DecideRequestParametersValidator } from "./DecideRequestParametersValidator"; import { CheckPrerequisitesOfIncomingRequestParameters, ICheckPrerequisitesOfIncomingRequestParameters } from "./checkPrerequisites/CheckPrerequisitesOfIncomingRequestParameters"; import { CompleteIncomingRequestParameters, ICompleteIncomingRequestParameters } from "./complete/CompleteIncomingRequestParameters"; import { DecideRequestItemGroupParametersJSON } from "./decide/DecideRequestItemGroupParameters"; import { DecideRequestItemParametersJSON } from "./decide/DecideRequestItemParameters"; import { DecideRequestParametersJSON } from "./decide/DecideRequestParameters"; import { InternalDecideRequestParameters, InternalDecideRequestParametersJSON } from "./decide/InternalDecideRequestParameters"; -import { DecideRequestParametersValidator } from "./DecideRequestParametersValidator"; import { IReceivedIncomingRequestParameters, ReceivedIncomingRequestParameters } from "./received/ReceivedIncomingRequestParameters"; import { IRequireManualDecisionOfIncomingRequestParameters, @@ -184,7 +184,10 @@ export class IncomingRequestsController extends ConsumptionBaseController { const relationship = await this.relationshipResolver.getRelationshipToIdentity(request.peer); // It is safe to decide an incoming Request when no Relationship is found as this is the case when the Request origins from onNewRelationship of the RelationshipTemplateContent - if (relationship && relationship.status !== RelationshipStatus.Active) { + const possibleStatuses = + request.source?.type === "RelationshipTemplate" ? [RelationshipStatus.Active, RelationshipStatus.Rejected, RelationshipStatus.Revoked] : [RelationshipStatus.Active]; + + if (relationship && !possibleStatuses.includes(relationship.status)) { return ValidationResult.error( CoreErrors.requests.wrongRelationshipStatus( `You cannot decide a request from '${request.peer.toString()}' since the relationship is in status '${relationship.status}'.` diff --git a/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts b/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts index 4a0bc3661..9a2c7f1a3 100644 --- a/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts +++ b/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts @@ -52,11 +52,14 @@ export class OutgoingRequestsController extends ConsumptionBaseController { const parsedParams = CanCreateOutgoingRequestParameters.from(params); if (parsedParams.peer) { const relationship = await this.relationshipResolver.getRelationshipToIdentity(parsedParams.peer); + + // there should at minimum be a Pending relationship to the peer if (!relationship) { return ValidationResult.error( CoreErrors.requests.missingRelationship(`You cannot create a request to '${parsedParams.peer.toString()}' since you are not in a relationship.`) ); } + if (!(relationship.status === RelationshipStatus.Pending || relationship.status === RelationshipStatus.Active)) { return ValidationResult.error( CoreErrors.requests.wrongRelationshipStatus( diff --git a/packages/content/package.json b/packages/content/package.json index 0a67ee2ac..b838cb6a7 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.3", + "version": "5.0.0-alpha.4", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index bb5b2b10a..db8f7acfc 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.3", + "version": "5.0.0-alpha.4", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.3", - "@nmshd/content": "5.0.0-alpha.3", + "@nmshd/consumption": "5.0.0-alpha.4", + "@nmshd/content": "5.0.0-alpha.4", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.3", + "@nmshd/transport": "5.0.0-alpha.4", "ajv": "^8.16.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/test/lib/testUtils.ts b/packages/runtime/test/lib/testUtils.ts index 4c452974a..1abfb9082 100644 --- a/packages/runtime/test/lib/testUtils.ts +++ b/packages/runtime/test/lib/testUtils.ts @@ -364,7 +364,8 @@ export async function establishPendingRelationshipWithRequestFlow( await rRuntimeServices.eventBus.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.request.source!.reference === template.id); const requestId = (await rRuntimeServices.consumption.incomingRequests.getRequests({ query: { "source.reference": template.id } })).value[0].id; - await rRuntimeServices.consumption.incomingRequests.accept({ requestId, items: acceptParams }); + const result = await rRuntimeServices.consumption.incomingRequests.accept({ requestId, items: acceptParams }); + expect(result).toBeSuccessful(); await rRuntimeServices.eventBus.waitForEvent(RelationshipChangedEvent); diff --git a/packages/runtime/test/modules/RequestModule.test.ts b/packages/runtime/test/modules/RequestModule.test.ts index 7d0e461f3..bf9f03394 100644 --- a/packages/runtime/test/modules/RequestModule.test.ts +++ b/packages/runtime/test/modules/RequestModule.test.ts @@ -523,7 +523,7 @@ describe("Handling the rejection and the revocation of a Relationship by the Req "@type": "GivenName", value: "AGivenName" }, - owner: (await rRuntimeServices.transport.account.getIdentityInfo()).value.address + owner: "" }).toJSON() }, { @@ -536,7 +536,7 @@ describe("Handling the rejection and the revocation of a Relationship by the Req value: "AStringValue", title: "ATitle" }, - owner: CoreAddress.from((await rRuntimeServices.transport.account.getIdentityInfo()).value.address), + owner: CoreAddress.from(""), confidentiality: RelationshipAttributeConfidentiality.Public }).toJSON() }, diff --git a/packages/transport/package.json b/packages/transport/package.json index cc69cce8f..bdd191602 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.3", + "version": "5.0.0-alpha.4", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/transport/src/modules/relationships/RelationshipsController.ts b/packages/transport/src/modules/relationships/RelationshipsController.ts index 3de816e03..04cac1992 100644 --- a/packages/transport/src/modules/relationships/RelationshipsController.ts +++ b/packages/transport/src/modules/relationships/RelationshipsController.ts @@ -9,7 +9,6 @@ import { DbCollectionName } from "../../core/DbCollectionName"; import { TransportIds } from "../../core/TransportIds"; import { RelationshipChangedEvent, RelationshipReactivationCompletedEvent, RelationshipReactivationRequestedEvent } from "../../events"; import { AccountController } from "../accounts/AccountController"; -import { Identity } from "../accounts/data/Identity"; import { RelationshipTemplate } from "../relationshipTemplates/local/RelationshipTemplate"; import { SynchronizedCollection } from "../sync/SynchronizedCollection"; import { RelationshipSecretController } from "./RelationshipSecretController"; @@ -102,21 +101,16 @@ export class RelationshipsController extends TransportController { public async getRelationshipToIdentity(address: CoreAddress, status?: RelationshipStatus): Promise { const query: any = { peerAddress: address.toString() }; if (status) query[`${nameof((r) => r.status)}`] = status; - let relationshipDoc = await this.relationships.findOne(query); + const relationships = await this.relationships.find(query); - if (!relationshipDoc) { - // If we don't find the relationship by peerAddress, we have to check again by peer.address - // as the Relationship could have been created before the peerAddress was introduced - const query = { [`${nameof((r) => r.peer)}.${nameof((r) => r.address)}`]: address.toString() }; - if (status) query[`${nameof((r) => r.status)}`] = status; - relationshipDoc = await this.relationships.findOne(query); - } + if (relationships.length === 0) return undefined; + if (relationships.length === 1) return Relationship.from(relationships[0]); - if (!relationshipDoc) { - return; - } + const newestRelationship = relationships.reduce((prev, current) => { + return prev.createdAt > current.createdAt ? prev : current; + }); - return Relationship.from(relationshipDoc); + return Relationship.from(newestRelationship); } public async getActiveRelationshipToIdentity(address: CoreAddress): Promise { From 5c01590799f38612afe7e4130bc71f3c9e3e537f Mon Sep 17 00:00:00 2001 From: Milena Czierlinski <146972016+Milena-Czierlinski@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:33:10 +0200 Subject: [PATCH 14/40] Refactor/Clean up create Attribute functions (#181) * refactor: rename functions more descriptive * fix: reset event bus --- .../consumption/src/consumption/CoreErrors.ts | 7 +- .../attributes/AttributesController.ts | 25 +- .../src/modules/attributes/index.ts | 4 +- .../local/CreateLocalAttributeParams.ts | 46 -- .../local/CreateRepositoryAttributeParams.ts | 33 + ...ts => CreateSharedLocalAttributeParams.ts} | 14 +- .../CreateAttributeRequestItemProcessor.ts | 6 +- .../ProposeAttributeRequestItemProcessor.ts | 8 +- .../ReadAttributeRequestItemProcessor.ts | 8 +- .../ShareAttributeRequestItemProcessor.ts | 2 +- .../attributes/AttributesController.test.ts | 739 +++++++++--------- .../LocalAttributeDeletionInfo.test.ts | 8 +- ...oposeAttributeRequestItemProcessor.test.ts | 54 +- .../ReadAttributeRequestItemProcessor.test.ts | 112 ++- ...ShareAttributeRequestItemProcessor.test.ts | 52 +- .../validateAttributeMatchesWithQuery.test.ts | 36 +- .../attributes/CreateRepositoryAttribute.ts | 9 +- 17 files changed, 537 insertions(+), 626 deletions(-) delete mode 100644 packages/consumption/src/modules/attributes/local/CreateLocalAttributeParams.ts create mode 100644 packages/consumption/src/modules/attributes/local/CreateRepositoryAttributeParams.ts rename packages/consumption/src/modules/attributes/local/{CreatePeerLocalAttributeParams.ts => CreateSharedLocalAttributeParams.ts} (69%) diff --git a/packages/consumption/src/consumption/CoreErrors.ts b/packages/consumption/src/consumption/CoreErrors.ts index 3d9d7bda9..d6e4792ca 100644 --- a/packages/consumption/src/consumption/CoreErrors.ts +++ b/packages/consumption/src/consumption/CoreErrors.ts @@ -216,8 +216,11 @@ class Attributes { ); } - public invalidPropertyValue(message: string) { - return new CoreError("error.consumption.attributes.invalidPropertyValue", message); + public wrongOwnerOfRepositoryAttribute() { + return new CoreError( + "error.consumption.attributes.wrongOwnerOfRepositoryAttribute", + "A wrong owner was provided wanting to create a RepositoryAttribute. You can only create RepositoryAttributes for yourself." + ); } public isNotSharedAttribute(attributeId: string | CoreId) { diff --git a/packages/consumption/src/modules/attributes/AttributesController.ts b/packages/consumption/src/modules/attributes/AttributesController.ts index 2fce409d6..a780954d9 100644 --- a/packages/consumption/src/modules/attributes/AttributesController.ts +++ b/packages/consumption/src/modules/attributes/AttributesController.ts @@ -34,9 +34,9 @@ import { ThirdPartyOwnedRelationshipAttributeSucceededEvent } from "./events"; import { AttributeSuccessorParams, AttributeSuccessorParamsJSON, IAttributeSuccessorParams } from "./local/AttributeSuccessorParams"; -import { CreateLocalAttributeParams, ICreateLocalAttributeParams } from "./local/CreateLocalAttributeParams"; -import { ICreatePeerLocalAttributeParams } from "./local/CreatePeerLocalAttributeParams"; +import { CreateRepositoryAttributeParams, ICreateRepositoryAttributeParams } from "./local/CreateRepositoryAttributeParams"; import { CreateSharedLocalAttributeCopyParams, ICreateSharedLocalAttributeCopyParams } from "./local/CreateSharedLocalAttributeCopyParams"; +import { ICreateSharedLocalAttributeParams } from "./local/CreateSharedLocalAttributeParams"; import { ILocalAttribute, LocalAttribute, LocalAttributeJSON } from "./local/LocalAttribute"; import { LocalAttributeShareInfo } from "./local/LocalAttributeShareInfo"; import { IdentityAttributeQueryTranslator, RelationshipAttributeQueryTranslator, ThirdPartyRelationshipAttributeQueryTranslator } from "./local/QueryTranslator"; @@ -232,23 +232,22 @@ export class AttributesController extends ConsumptionBaseController { return this.parseArray(attributes, LocalAttribute); } - public async createLocalAttribute(params: ICreateLocalAttributeParams): Promise { - const parsedParams = CreateLocalAttributeParams.from(params); + public async createRepositoryAttribute(params: ICreateRepositoryAttributeParams): Promise { + if (params.content.owner.toString() !== this.identity.address.toString()) { + throw CoreErrors.attributes.wrongOwnerOfRepositoryAttribute(); + } + + const parsedParams = CreateRepositoryAttributeParams.from(params); const localAttribute = LocalAttribute.from({ id: parsedParams.id ?? (await ConsumptionIds.attribute.generate()), createdAt: CoreDate.utc(), content: parsedParams.content, - parentId: parsedParams.parentId, - succeeds: parsedParams.succeeds, - shareInfo: parsedParams.shareInfo + parentId: parsedParams.parentId }); await this.attributes.create(localAttribute); - if ( - localAttribute.content instanceof IdentityAttribute && // Local Attributes for children should only be created for Identity Attributes - localAttribute.content.value instanceof AbstractComplexValue - ) { + if (localAttribute.content.value instanceof AbstractComplexValue) { await this.createLocalAttributesForChildrenOfComplexAttribute(localAttribute); } @@ -270,7 +269,7 @@ export class AttributesController extends ConsumptionBaseController { value: propertyValue.toJSON() as AttributeValues.Identity.Json }); - await this.createLocalAttribute({ + await this.createRepositoryAttribute({ content: childAttribute, parentId: localAttribute.id }); @@ -297,7 +296,7 @@ export class AttributesController extends ConsumptionBaseController { return sharedLocalAttributeCopy; } - public async createPeerLocalAttribute(params: ICreatePeerLocalAttributeParams): Promise { + public async createSharedLocalAttribute(params: ICreateSharedLocalAttributeParams): Promise { const shareInfo = LocalAttributeShareInfo.from({ peer: params.peer, requestReference: params.requestReference diff --git a/packages/consumption/src/modules/attributes/index.ts b/packages/consumption/src/modules/attributes/index.ts index e0ebc8a63..dc83fa843 100644 --- a/packages/consumption/src/modules/attributes/index.ts +++ b/packages/consumption/src/modules/attributes/index.ts @@ -1,9 +1,9 @@ export * from "./AttributesController"; export * from "./events"; export * from "./local/AttributeSuccessorParams"; -export * from "./local/CreateLocalAttributeParams"; -export * from "./local/CreatePeerLocalAttributeParams"; +export * from "./local/CreateRepositoryAttributeParams"; export * from "./local/CreateSharedLocalAttributeCopyParams"; +export * from "./local/CreateSharedLocalAttributeParams"; export * from "./local/LocalAttribute"; export * from "./local/LocalAttributeDeletionInfo"; export * from "./local/LocalAttributeShareInfo"; diff --git a/packages/consumption/src/modules/attributes/local/CreateLocalAttributeParams.ts b/packages/consumption/src/modules/attributes/local/CreateLocalAttributeParams.ts deleted file mode 100644 index 0c3486847..000000000 --- a/packages/consumption/src/modules/attributes/local/CreateLocalAttributeParams.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ISerializable, Serializable, serialize, validate } from "@js-soft/ts-serval"; -import { IIdentityAttribute, IRelationshipAttribute, IdentityAttribute, IdentityAttributeJSON, RelationshipAttribute, RelationshipAttributeJSON } from "@nmshd/content"; -import { CoreId, ICoreId } from "@nmshd/transport"; -import { ILocalAttributeShareInfo, LocalAttributeShareInfo, LocalAttributeShareInfoJSON } from "./LocalAttributeShareInfo"; - -export interface CreateLocalAttributeParamsJSON { - id?: string; - content: IdentityAttributeJSON | RelationshipAttributeJSON; - parentId?: string; - succeeds?: string; - shareInfo?: LocalAttributeShareInfoJSON; -} - -export interface ICreateLocalAttributeParams extends ISerializable { - id?: ICoreId; - content: IIdentityAttribute | IRelationshipAttribute; - parentId?: ICoreId; - succeeds?: ICoreId; - shareInfo?: ILocalAttributeShareInfo; -} - -export class CreateLocalAttributeParams extends Serializable implements ICreateLocalAttributeParams { - @serialize() - @validate({ nullable: true }) - public id?: CoreId; - - @serialize({ unionTypes: [IdentityAttribute, RelationshipAttribute] }) - @validate() - public content: IdentityAttribute | RelationshipAttribute; - - @serialize() - @validate({ nullable: true }) - public parentId?: CoreId; - - @serialize() - @validate({ nullable: true }) - public succeeds?: CoreId; - - @serialize() - @validate({ nullable: true }) - public shareInfo?: LocalAttributeShareInfo; - - public static from(value: ICreateLocalAttributeParams | CreateLocalAttributeParamsJSON): CreateLocalAttributeParams { - return this.fromAny(value); - } -} diff --git a/packages/consumption/src/modules/attributes/local/CreateRepositoryAttributeParams.ts b/packages/consumption/src/modules/attributes/local/CreateRepositoryAttributeParams.ts new file mode 100644 index 000000000..0e081dcc4 --- /dev/null +++ b/packages/consumption/src/modules/attributes/local/CreateRepositoryAttributeParams.ts @@ -0,0 +1,33 @@ +import { ISerializable, Serializable, serialize, validate } from "@js-soft/ts-serval"; +import { IIdentityAttribute, IdentityAttribute, IdentityAttributeJSON, RelationshipAttribute } from "@nmshd/content"; +import { CoreId, ICoreId } from "@nmshd/transport"; + +export interface CreateRepositoryAttributeParamsJSON { + id?: string; + content: IdentityAttributeJSON; + parentId?: string; +} + +export interface ICreateRepositoryAttributeParams extends ISerializable { + id?: ICoreId; + content: IIdentityAttribute; + parentId?: ICoreId; +} + +export class CreateRepositoryAttributeParams extends Serializable implements ICreateRepositoryAttributeParams { + @serialize() + @validate({ nullable: true }) + public id?: CoreId; + + @serialize({ unionTypes: [IdentityAttribute, RelationshipAttribute] }) + @validate() + public content: IdentityAttribute; + + @serialize() + @validate({ nullable: true }) + public parentId?: CoreId; + + public static from(value: ICreateRepositoryAttributeParams | CreateRepositoryAttributeParamsJSON): CreateRepositoryAttributeParams { + return this.fromAny(value); + } +} diff --git a/packages/consumption/src/modules/attributes/local/CreatePeerLocalAttributeParams.ts b/packages/consumption/src/modules/attributes/local/CreateSharedLocalAttributeParams.ts similarity index 69% rename from packages/consumption/src/modules/attributes/local/CreatePeerLocalAttributeParams.ts rename to packages/consumption/src/modules/attributes/local/CreateSharedLocalAttributeParams.ts index 83696cc4e..2f1cc5e81 100644 --- a/packages/consumption/src/modules/attributes/local/CreatePeerLocalAttributeParams.ts +++ b/packages/consumption/src/modules/attributes/local/CreateSharedLocalAttributeParams.ts @@ -2,24 +2,24 @@ import { ISerializable, Serializable, serialize, validate } from "@js-soft/ts-se import { IIdentityAttribute, IRelationshipAttribute, IdentityAttribute, IdentityAttributeJSON, RelationshipAttribute, RelationshipAttributeJSON } from "@nmshd/content"; import { CoreAddress, CoreId, ICoreAddress, ICoreId } from "@nmshd/transport"; -export interface CreatePeerLocalAttributeParamsJSON { - id: string; +export interface CreateSharedLocalAttributeParamsJSON { + id?: string; content: IdentityAttributeJSON | RelationshipAttributeJSON; requestReferece: string; peer: string; } -export interface ICreatePeerLocalAttributeParams extends ISerializable { +export interface ICreateSharedLocalAttributeParams extends ISerializable { id?: ICoreId; // needs to be optional because sometimes (e.g. when accepting a CreateAttributeRequestItem) the id is not known yet content: IIdentityAttribute | IRelationshipAttribute; requestReference: ICoreId; peer: ICoreAddress; } -export class CreatePeerLocalAttributeParams extends Serializable implements ICreatePeerLocalAttributeParams { +export class CreateSharedLocalAttributeParams extends Serializable implements ICreateSharedLocalAttributeParams { @serialize() - @validate() - public id: CoreId; + @validate({ nullable: true }) + public id?: CoreId; @serialize({ unionTypes: [IdentityAttribute, RelationshipAttribute] }) @validate() @@ -33,7 +33,7 @@ export class CreatePeerLocalAttributeParams extends Serializable implements ICre @validate() public peer: CoreAddress; - public static from(value: ICreatePeerLocalAttributeParams | CreatePeerLocalAttributeParamsJSON): CreatePeerLocalAttributeParams { + public static from(value: ICreateSharedLocalAttributeParams | CreateSharedLocalAttributeParamsJSON): CreateSharedLocalAttributeParams { return this.fromAny(value); } } diff --git a/packages/consumption/src/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.ts index 09a5096ad..87b225c8c 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.ts @@ -74,7 +74,7 @@ export class CreateAttributeRequestItemProcessor extends GenericRequestItemProce let sharedAttribute: LocalAttribute; if (requestItem.attribute instanceof IdentityAttribute) { - const repositoryAttribute = await this.consumptionController.attributes.createLocalAttribute({ + const repositoryAttribute = await this.consumptionController.attributes.createRepositoryAttribute({ content: requestItem.attribute }); @@ -84,7 +84,7 @@ export class CreateAttributeRequestItemProcessor extends GenericRequestItemProce sourceAttributeId: repositoryAttribute.id }); } else { - sharedAttribute = await this.consumptionController.attributes.createPeerLocalAttribute({ + sharedAttribute = await this.consumptionController.attributes.createSharedLocalAttribute({ content: requestItem.attribute, peer: requestInfo.peer, requestReference: requestInfo.id @@ -110,7 +110,7 @@ export class CreateAttributeRequestItemProcessor extends GenericRequestItemProce requestItem.attribute.owner = requestInfo.peer; } - await this.consumptionController.attributes.createPeerLocalAttribute({ + await this.consumptionController.attributes.createSharedLocalAttribute({ id: responseItem.attributeId, content: requestItem.attribute, peer: requestInfo.peer, diff --git a/packages/consumption/src/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.ts index d4379b556..1029843ff 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.ts @@ -260,18 +260,18 @@ export class ProposeAttributeRequestItemProcessor extends GenericRequestItemProc private async createNewAttribute(attribute: IdentityAttribute | RelationshipAttribute, requestInfo: LocalRequestInfo) { if (attribute instanceof IdentityAttribute) { - const repositoryLocalAttribute = await this.consumptionController.attributes.createLocalAttribute({ + const repositoryAttribute = await this.consumptionController.attributes.createRepositoryAttribute({ content: attribute }); return await this.consumptionController.attributes.createSharedLocalAttributeCopy({ - sourceAttributeId: CoreId.from(repositoryLocalAttribute.id), + sourceAttributeId: CoreId.from(repositoryAttribute.id), peer: CoreAddress.from(requestInfo.peer), requestReference: CoreId.from(requestInfo.id) }); } - return await this.consumptionController.attributes.createPeerLocalAttribute({ + return await this.consumptionController.attributes.createSharedLocalAttribute({ content: attribute, peer: requestInfo.peer, requestReference: CoreId.from(requestInfo.id) @@ -284,7 +284,7 @@ export class ProposeAttributeRequestItemProcessor extends GenericRequestItemProc requestInfo: LocalRequestInfo ): Promise { if (responseItem instanceof ProposeAttributeAcceptResponseItem) { - await this.consumptionController.attributes.createPeerLocalAttribute({ + await this.consumptionController.attributes.createSharedLocalAttribute({ id: responseItem.attributeId, content: responseItem.attribute, peer: requestInfo.peer, diff --git a/packages/consumption/src/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.ts index 605faff7a..3d6208e8c 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.ts @@ -317,18 +317,18 @@ export class ReadAttributeRequestItemProcessor extends GenericRequestItemProcess private async createNewAttribute(attribute: IdentityAttribute | RelationshipAttribute, requestInfo: LocalRequestInfo) { if (attribute instanceof IdentityAttribute) { - const repositoryLocalAttribute = await this.consumptionController.attributes.createLocalAttribute({ + const repositoryAttribute = await this.consumptionController.attributes.createRepositoryAttribute({ content: attribute }); return await this.consumptionController.attributes.createSharedLocalAttributeCopy({ - sourceAttributeId: CoreId.from(repositoryLocalAttribute.id), + sourceAttributeId: CoreId.from(repositoryAttribute.id), peer: CoreAddress.from(requestInfo.peer), requestReference: CoreId.from(requestInfo.id) }); } - return await this.consumptionController.attributes.createPeerLocalAttribute({ + return await this.consumptionController.attributes.createSharedLocalAttribute({ content: attribute, peer: requestInfo.peer, requestReference: CoreId.from(requestInfo.id) @@ -341,7 +341,7 @@ export class ReadAttributeRequestItemProcessor extends GenericRequestItemProcess requestInfo: LocalRequestInfo ): Promise { if (responseItem instanceof ReadAttributeAcceptResponseItem) { - await this.consumptionController.attributes.createPeerLocalAttribute({ + await this.consumptionController.attributes.createSharedLocalAttribute({ id: responseItem.attributeId, content: responseItem.attribute, peer: requestInfo.peer, diff --git a/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts index 8bd4a4fd1..f678d7841 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts @@ -147,7 +147,7 @@ export class ShareAttributeRequestItemProcessor extends GenericRequestItemProces requestItem.attribute.owner = requestInfo.peer; } - const localAttribute = await this.consumptionController.attributes.createPeerLocalAttribute({ + const localAttribute = await this.consumptionController.attributes.createSharedLocalAttribute({ content: requestItem.attribute, peer: requestInfo.peer, requestReference: requestInfo.id diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index 8f92c0ed6..f59238479 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -10,7 +10,6 @@ import { IIQLQuery, IRelationshipAttributeQuery, Nationality, - PhoneNumber, RelationshipAttribute, RelationshipAttributeConfidentiality, Street, @@ -25,9 +24,9 @@ import { ConsumptionController, DeletionStatus, IAttributeSuccessorParams, - ICreateLocalAttributeParams, - ICreatePeerLocalAttributeParams, + ICreateRepositoryAttributeParams, ICreateSharedLocalAttributeCopyParams, + ICreateSharedLocalAttributeParams, LocalAttribute, LocalAttributeDeletionInfo, LocalAttributeShareInfo, @@ -60,27 +59,7 @@ describe("AttributesController", function () { await connection.close(); }); - beforeEach(async function () { - const emailParams: ICreateLocalAttributeParams = { - content: IdentityAttribute.from({ - value: EMailAddress.from({ - value: "my@email.address" - }), - owner: CoreAddress.from("address") - }) - }; - - const phoneParams: ICreateLocalAttributeParams = { - content: IdentityAttribute.from({ - value: PhoneNumber.from({ - value: "000000000" - }), - owner: CoreAddress.from("address") - }) - }; - await consumptionController.attributes.createLocalAttribute(emailParams); - await consumptionController.attributes.createLocalAttribute(phoneParams); - + beforeEach(function () { mockEventBus.clearPublishedEvents(); }); @@ -92,134 +71,167 @@ describe("AttributesController", function () { } }); - test("should list all attributes", async function () { - const attributes = await consumptionController.attributes.getLocalAttributes(); - expect(attributes).toHaveLength(2); - }); + describe("create Attributes", function () { + test("should create new attributes", async function () { + const birthDateParams: ICreateRepositoryAttributeParams = { + content: IdentityAttribute.from({ + value: { + "@type": "DisplayName", + value: "ADisplayName" + }, + owner: consumptionController.accountController.identity.address + }) + }; + + const birthDate = await consumptionController.attributes.createRepositoryAttribute(birthDateParams); + expect(birthDate).toBeInstanceOf(LocalAttribute); + expect(birthDate.content).toBeInstanceOf(IdentityAttribute); - test("should create new attributes", async function () { - const attributesBeforeCreate = await consumptionController.attributes.getLocalAttributes(); - const nrAttributesBeforeCreate = attributesBeforeCreate.length; + const attributesAfterCreate = await consumptionController.attributes.getLocalAttributes(); + expect(attributesAfterCreate).toHaveLength(1); - const birthDateParams: ICreateLocalAttributeParams = { - content: IdentityAttribute.from({ + mockEventBus.expectPublishedEvents(AttributeCreatedEvent); + }); + + test("should create LocalAttributes for each property of a complex Identity Attribute", async function () { + const identityAttribute = IdentityAttribute.from({ value: { - "@type": "DisplayName", - value: "ADisplayName" + "@type": "StreetAddress", + recipient: "ARecipient", + street: "AStreet", + houseNo: "AHouseNo", + zipCode: "AZipCode", + city: "ACity", + country: "DE" }, - owner: CoreAddress.from("address") - }) - }; + validTo: CoreDate.utc(), + owner: consumptionController.accountController.identity.address + }); - const birthDate = await consumptionController.attributes.createLocalAttribute(birthDateParams); - expect(birthDate).toBeInstanceOf(LocalAttribute); - expect(birthDate.content).toBeInstanceOf(IdentityAttribute); + const address = await consumptionController.attributes.createRepositoryAttribute({ + content: identityAttribute + }); - const attributesAfterCreate = await consumptionController.attributes.getLocalAttributes(); - const nrAttributesAfterCreate = attributesAfterCreate.length; - expect(nrAttributesAfterCreate).toStrictEqual(nrAttributesBeforeCreate + 1); + expect(address).toBeInstanceOf(LocalAttribute); + expect(address.content).toBeInstanceOf(IdentityAttribute); - mockEventBus.expectPublishedEvents(AttributeCreatedEvent); - }); + const childAttributes = await consumptionController.attributes.getLocalAttributes({ + parentId: address.id.toString() + }); + expect(childAttributes).toHaveLength(5); + expect(childAttributes[0].content.value).toBeInstanceOf(Street); + expect(childAttributes[1].content.value).toBeInstanceOf(HouseNumber); + expect(childAttributes[2].content.value).toBeInstanceOf(ZipCode); + expect(childAttributes[3].content.value).toBeInstanceOf(City); + expect(childAttributes[4].content.value).toBeInstanceOf(Country); - test("should create LocalAttributes for each property of a complex Identity Attribute", async function () { - const attributesBeforeCreate = await consumptionController.attributes.getLocalAttributes(); - const nrAttributesBeforeCreate = attributesBeforeCreate.length; - - const identityAttribute = IdentityAttribute.from({ - value: { - "@type": "StreetAddress", - recipient: "ARecipient", - street: "AStreet", - houseNo: "AHouseNo", - zipCode: "AZipCode", - city: "ACity", - country: "DE" - }, - validTo: CoreDate.utc(), - owner: CoreAddress.from("address") + const attributesAfterCreate = await consumptionController.attributes.getLocalAttributes(); + expect(attributesAfterCreate).toHaveLength(6); }); - const address = await consumptionController.attributes.createLocalAttribute({ - content: identityAttribute - }); + test("should trigger an AttributeCreatedEvent for each created child Attribute of a complex Attribute", async function () { + await consumptionController.attributes.getLocalAttributes(); - expect(address).toBeInstanceOf(LocalAttribute); - expect(address.content).toBeInstanceOf(IdentityAttribute); + const identityAttribute = IdentityAttribute.from({ + value: { + "@type": "StreetAddress", + recipient: "ARecipient", + street: "AStreet", + houseNo: "AHouseNo", + zipCode: "AZipCode", + city: "ACity", + country: "DE" + }, + validTo: CoreDate.utc(), + owner: consumptionController.accountController.identity.address + }); + + await consumptionController.attributes.createRepositoryAttribute({ content: identityAttribute }); - const childAttributes = await consumptionController.attributes.getLocalAttributes({ - parentId: address.id.toString() + mockEventBus.expectPublishedEvents( + AttributeCreatedEvent, + AttributeCreatedEvent, + AttributeCreatedEvent, + AttributeCreatedEvent, + AttributeCreatedEvent, + AttributeCreatedEvent + ); }); - expect(childAttributes).toHaveLength(5); - expect(childAttributes[0].content.value).toBeInstanceOf(Street); - expect(childAttributes[1].content.value).toBeInstanceOf(HouseNumber); - expect(childAttributes[2].content.value).toBeInstanceOf(ZipCode); - expect(childAttributes[3].content.value).toBeInstanceOf(City); - expect(childAttributes[4].content.value).toBeInstanceOf(Country); - - const attributesAfterCreate = await consumptionController.attributes.getLocalAttributes(); - const nrAttributesAfterCreate = attributesAfterCreate.length; - expect(nrAttributesAfterCreate).toStrictEqual(nrAttributesBeforeCreate + 6); - }); - test("should trigger an AttributeCreatedEvent for each created child Attribute of a complex Attribute", async function () { - await consumptionController.attributes.getLocalAttributes(); - - const identityAttribute = IdentityAttribute.from({ - value: { - "@type": "StreetAddress", - recipient: "ARecipient", - street: "AStreet", - houseNo: "AHouseNo", - zipCode: "AZipCode", - city: "ACity", - country: "DE" - }, - validTo: CoreDate.utc(), - owner: CoreAddress.from("address") + test("should allow to create a shared attribute copy", async function () { + const nationalityParams: ICreateRepositoryAttributeParams = { + content: IdentityAttribute.from({ + value: { + "@type": "Nationality", + value: "DE" + }, + owner: testAccount.identity.address + }) + }; + const nationalityAttribute = await consumptionController.attributes.createRepositoryAttribute(nationalityParams); + + const peer = CoreAddress.from("address"); + const createSharedAttributesParams: ICreateSharedLocalAttributeCopyParams = { + sourceAttributeId: nationalityAttribute.id, + peer: peer, + requestReference: CoreId.from("requestId") + }; + + const sharedNationalityAttribute = await consumptionController.attributes.createSharedLocalAttributeCopy(createSharedAttributesParams); + expect(sharedNationalityAttribute).toBeInstanceOf(LocalAttribute); + expect(sharedNationalityAttribute.shareInfo?.peer).toStrictEqual(peer); + + mockEventBus.expectLastPublishedEvent(SharedAttributeCopyCreatedEvent); }); - await consumptionController.attributes.createLocalAttribute({ content: identityAttribute }); + test("should allow to create a shared attribute", async function () { + const content = IdentityAttribute.from({ + value: { + "@type": "Nationality", + value: "DE" + }, + owner: CoreAddress.from("address") + }); + const createPeerAttributeParams: ICreateSharedLocalAttributeParams = { + content: content, + requestReference: CoreId.from("requestId"), + peer: CoreAddress.from("address") + }; + const peerLocalAttribute = await consumptionController.attributes.createSharedLocalAttribute(createPeerAttributeParams); + expect(peerLocalAttribute.content.toJSON()).toStrictEqual(content.toJSON()); + expect(peerLocalAttribute.content.value).toBeInstanceOf(Nationality); + expect(createPeerAttributeParams.peer.address).toStrictEqual(CoreAddress.from("address").toString()); + expect(createPeerAttributeParams.requestReference.toString()).toStrictEqual(CoreId.from("requestId").toString()); - mockEventBus.expectPublishedEvents( - AttributeCreatedEvent, - AttributeCreatedEvent, - AttributeCreatedEvent, - AttributeCreatedEvent, - AttributeCreatedEvent, - AttributeCreatedEvent - ); - }); + mockEventBus.expectLastPublishedEvent(AttributeCreatedEvent); + }); - test("should allow to create a share attribute copy", async function () { - const nationalityParams: ICreateLocalAttributeParams = { - content: IdentityAttribute.from({ + test("should allow to create an attribute shared by a peer", async function () { + const content = IdentityAttribute.from({ value: { "@type": "Nationality", value: "DE" }, - owner: testAccount.identity.address - }) - }; - const nationalityAttribute = await consumptionController.attributes.createLocalAttribute(nationalityParams); - - const peer = CoreAddress.from("address"); - const createSharedAttributesParams: ICreateSharedLocalAttributeCopyParams = { - sourceAttributeId: nationalityAttribute.id, - peer: peer, - requestReference: CoreId.from("requestId") - }; - - const sharedNationalityAttribute = await consumptionController.attributes.createSharedLocalAttributeCopy(createSharedAttributesParams); - expect(sharedNationalityAttribute).toBeInstanceOf(LocalAttribute); - expect(sharedNationalityAttribute.shareInfo?.peer).toStrictEqual(peer); - - mockEventBus.expectLastPublishedEvent(SharedAttributeCopyCreatedEvent); + owner: CoreAddress.from("address") + }); + const createSharedAttributeParams: ICreateSharedLocalAttributeParams = { + content: content, + requestReference: CoreId.from("requestId"), + peer: CoreAddress.from("address") + }; + const peerLocalAttribute = await consumptionController.attributes.createSharedLocalAttribute(createSharedAttributeParams); + expect(peerLocalAttribute.content.toJSON()).toStrictEqual(content.toJSON()); + expect(peerLocalAttribute.content.value).toBeInstanceOf(Nationality); + expect(createSharedAttributeParams.peer.address).toStrictEqual(CoreAddress.from("address").toString()); + expect(createSharedAttributeParams.requestReference.toString()).toStrictEqual(CoreId.from("requestId").toString()); + + mockEventBus.expectLastPublishedEvent(AttributeCreatedEvent); + }); }); - describe("Attribute queries", function () { + describe("query Attributes", function () { test("should allow to query relationship attributes with empty owner", async function () { - const relationshipAttributeParams: ICreateLocalAttributeParams = { + const relationshipAttributeParams: ICreateSharedLocalAttributeParams = { content: RelationshipAttribute.from({ key: "AKey", value: { @@ -229,9 +241,11 @@ describe("AttributesController", function () { }, owner: testAccount.identity.address, confidentiality: RelationshipAttributeConfidentiality.Public - }) + }), + requestReference: CoreId.from("reqRef"), + peer: CoreAddress.from("peer") }; - await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); + await consumptionController.attributes.createSharedLocalAttribute(relationshipAttributeParams); const query: IRelationshipAttributeQuery = { key: "AKey", @@ -239,7 +253,7 @@ describe("AttributesController", function () { attributeCreationHints: { valueType: "ProprietaryString", title: "ATitle", - confidentiality: "public" as RelationshipAttributeConfidentiality + confidentiality: RelationshipAttributeConfidentiality.Public } }; @@ -248,7 +262,7 @@ describe("AttributesController", function () { }); test("should allow to query public relationship attributes", async function () { - const relationshipAttributeParams: ICreateLocalAttributeParams = { + const relationshipAttributeParams: ICreateSharedLocalAttributeParams = { content: RelationshipAttribute.from({ key: "customerId", value: { @@ -258,9 +272,11 @@ describe("AttributesController", function () { }, owner: testAccount.identity.address, confidentiality: RelationshipAttributeConfidentiality.Public - }) + }), + requestReference: CoreId.from("reqRef"), + peer: CoreAddress.from("peer") }; - await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); + await consumptionController.attributes.createSharedLocalAttribute(relationshipAttributeParams); const query: IRelationshipAttributeQuery = { key: "customerId", @@ -268,7 +284,7 @@ describe("AttributesController", function () { attributeCreationHints: { valueType: "ProprietaryString", title: "someHintTitle", - confidentiality: "public" as RelationshipAttributeConfidentiality + confidentiality: RelationshipAttributeConfidentiality.Public } }; @@ -279,7 +295,7 @@ describe("AttributesController", function () { }); test("should allow to query protected relationship attributes", async function () { - const relationshipAttributeParams: ICreateLocalAttributeParams = { + const relationshipAttributeParams: ICreateSharedLocalAttributeParams = { content: RelationshipAttribute.from({ key: "customerId", value: { @@ -289,9 +305,11 @@ describe("AttributesController", function () { }, owner: testAccount.identity.address, confidentiality: RelationshipAttributeConfidentiality.Protected - }) + }), + requestReference: CoreId.from("reqRef"), + peer: CoreAddress.from("peer") }; - await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); + await consumptionController.attributes.createSharedLocalAttribute(relationshipAttributeParams); const query: IRelationshipAttributeQuery = { key: "customerId", @@ -310,7 +328,7 @@ describe("AttributesController", function () { }); test("should not allow to query private relationship attributes", async function () { - const relationshipAttributeParams: ICreateLocalAttributeParams = { + const relationshipAttributeParams: ICreateSharedLocalAttributeParams = { content: RelationshipAttribute.from({ key: "customerId", value: { @@ -320,9 +338,11 @@ describe("AttributesController", function () { }, owner: testAccount.identity.address, confidentiality: RelationshipAttributeConfidentiality.Private - }) + }), + requestReference: CoreId.from("reqRef"), + peer: CoreAddress.from("peer") }; - await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); + await consumptionController.attributes.createSharedLocalAttribute(relationshipAttributeParams); const query: IRelationshipAttributeQuery = { key: "customerId", @@ -339,7 +359,7 @@ describe("AttributesController", function () { }); test("should query relationship attributes using the ThirdPartyRelationshipAttributeQuery", async function () { - const relationshipAttribute = await consumptionController.attributes.createPeerLocalAttribute({ + const relationshipAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "customerId", value: { @@ -364,7 +384,7 @@ describe("AttributesController", function () { }); test("should not query relationship attributes with confidentiality set to `Private` using the ThirdPartyRelationshipAttributeQuery", async function () { - await consumptionController.attributes.createPeerLocalAttribute({ + await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "customerId", value: { @@ -388,7 +408,7 @@ describe("AttributesController", function () { }); test("should not query relationship attributes with not matching key using the ThirdPartyRelationshipAttributeQuery", async function () { - await consumptionController.attributes.createPeerLocalAttribute({ + await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "customerId", value: { @@ -413,7 +433,7 @@ describe("AttributesController", function () { test("can call executeThirdPartyRelationshipAttributeQuery with ThirdPartyRelationshipAttributeQueryOwner.Recipient", async function () { const recipient = testAccount.identity.address; - await consumptionController.attributes.createPeerLocalAttribute({ + await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", value: { @@ -437,7 +457,7 @@ describe("AttributesController", function () { }); test("can call executeThirdPartyRelationshipAttributeQuery with ThirdPartyRelationshipAttributeQueryOwner.ThirdParty", async function () { - await consumptionController.attributes.createPeerLocalAttribute({ + await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", value: { @@ -462,7 +482,7 @@ describe("AttributesController", function () { test("can call executeThirdPartyRelationshipAttributeQuery with ThirdPartyRelationshipAttributeQueryOwner.Empty", async function () { const recipient = testAccount.identity.address; - await consumptionController.attributes.createPeerLocalAttribute({ + await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", value: { @@ -477,7 +497,7 @@ describe("AttributesController", function () { requestReference: CoreId.from("requestId") }); - await consumptionController.attributes.createPeerLocalAttribute({ + await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", value: { @@ -492,7 +512,7 @@ describe("AttributesController", function () { requestReference: CoreId.from("requestId") }); - await consumptionController.attributes.createPeerLocalAttribute({ + await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", value: { @@ -516,7 +536,7 @@ describe("AttributesController", function () { }); test("should allow to query identity attributes", async function () { - const identityAttributeParams: ICreateLocalAttributeParams = { + const repositoryAttributeParams: ICreateRepositoryAttributeParams = { content: IdentityAttribute.from({ value: { "@type": "Nationality", @@ -525,9 +545,9 @@ describe("AttributesController", function () { owner: testAccount.identity.address }) }; - const identityAttribute = await consumptionController.attributes.createLocalAttribute(identityAttributeParams); + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute(repositoryAttributeParams); - const relationshipAttributeParams: ICreateLocalAttributeParams = { + const relationshipAttributeParams: ICreateSharedLocalAttributeParams = { content: RelationshipAttribute.from({ key: "customerId", value: { @@ -536,10 +556,12 @@ describe("AttributesController", function () { title: "Customer Id" }, owner: testAccount.identity.address, - confidentiality: "public" as RelationshipAttributeConfidentiality - }) + confidentiality: RelationshipAttributeConfidentiality.Public + }), + requestReference: CoreId.from("reqRef"), + peer: CoreAddress.from("peer") }; - const relationshipAttribute = await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); + const relationshipAttribute = await consumptionController.attributes.createSharedLocalAttribute(relationshipAttributeParams); const query: IIdentityAttributeQuery = { valueType: "Nationality" @@ -548,11 +570,11 @@ describe("AttributesController", function () { const attributes = await consumptionController.attributes.executeIdentityAttributeQuery(query); const attributesId = attributes.map((v) => v.id.toString()); expect(attributesId).not.toContain(relationshipAttribute.id.toString()); - expect(attributesId).toContain(identityAttribute.id.toString()); + expect(attributesId).toContain(repositoryAttribute.id.toString()); }); test("should successfully execute IQL queries only with repository attributes", async function () { - const identityAttributeParams: ICreateLocalAttributeParams = { + const repositoryAttributeParams: ICreateRepositoryAttributeParams = { content: IdentityAttribute.from({ value: { "@type": "Nationality", @@ -561,11 +583,11 @@ describe("AttributesController", function () { owner: testAccount.identity.address }) }; - const identityAttribute = await consumptionController.attributes.createLocalAttribute(identityAttributeParams); + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute(repositoryAttributeParams); await consumptionController.attributes.createSharedLocalAttributeCopy({ peer: CoreAddress.from("a-fake-peer"), requestReference: CoreId.from("a-fake-reference"), - sourceAttributeId: identityAttribute.id, + sourceAttributeId: repositoryAttribute.id, attributeId: CoreId.from("fake-attribute-id") }); @@ -573,11 +595,11 @@ describe("AttributesController", function () { const matchedAttributes = await consumptionController.attributes.executeIQLQuery(iqlQuery); expect(matchedAttributes).toHaveLength(1); const matchedAttributeIds = matchedAttributes.map((v) => v.id.toString()); - expect(matchedAttributeIds).toContain(identityAttribute.id.toString()); + expect(matchedAttributeIds).toContain(repositoryAttribute.id.toString()); }); test("should only return repository attributes on IdentityAttributeQuery", async function () { - const identityAttributeParams: ICreateLocalAttributeParams = { + const repositoryAttributeParams: ICreateRepositoryAttributeParams = { content: IdentityAttribute.from({ value: { "@type": "DisplayName", @@ -586,9 +608,9 @@ describe("AttributesController", function () { owner: testAccount.identity.address }) }; - const identityAttribute = await consumptionController.attributes.createLocalAttribute(identityAttributeParams); + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute(repositoryAttributeParams); - const relationshipAttributeParams: ICreateLocalAttributeParams = { + const relationshipAttributeParams: ICreateSharedLocalAttributeParams = { content: RelationshipAttribute.from({ key: "displayName", value: { @@ -597,34 +619,25 @@ describe("AttributesController", function () { value: "DE" }, owner: testAccount.identity.address, - confidentiality: "public" as RelationshipAttributeConfidentiality - }) + confidentiality: RelationshipAttributeConfidentiality.Public + }), + requestReference: CoreId.from("reqRef"), + peer: CoreAddress.from("peer") }; - const relationshipAttribute = await consumptionController.attributes.createLocalAttribute(relationshipAttributeParams); + const relationshipAttribute = await consumptionController.attributes.createSharedLocalAttribute(relationshipAttributeParams); - const peerAttributeParams: ICreateLocalAttributeParams = { + const peerSharedIdentityAttributeParams: ICreateSharedLocalAttributeParams = { content: IdentityAttribute.from({ value: { "@type": "DisplayName", value: "DE" }, owner: CoreAddress.from("peer") - }) + }), + requestReference: CoreId.from("reqRef"), + peer: CoreAddress.from("peer") }; - const peerAttribute = await consumptionController.attributes.createLocalAttribute(peerAttributeParams); - - const sentAttribute = await consumptionController.attributes.createSharedLocalAttributeCopy({ - peer: CoreAddress.from("peer"), - requestReference: CoreId.from("ref"), - sourceAttributeId: identityAttribute.id - }); - - const receivedAttribute = await consumptionController.attributes.createSharedLocalAttributeCopy({ - peer: CoreAddress.from("peer"), - requestReference: CoreId.from("ref2"), - sourceAttributeId: peerAttribute.id, - attributeId: CoreId.from("attr1") - }); + const peerAttribute = await consumptionController.attributes.createSharedLocalAttribute(peerSharedIdentityAttributeParams); const query: IIdentityAttributeQuery = { valueType: "DisplayName" @@ -635,42 +648,13 @@ describe("AttributesController", function () { expect(attributes).toHaveLength(1); expect(attributesId).not.toContain(relationshipAttribute.id.toString()); expect(attributesId).not.toContain(peerAttribute.id.toString()); - expect(attributesId).not.toContain(sentAttribute.id.toString()); - expect(attributesId).not.toContain(receivedAttribute.id.toString()); - expect(attributesId).toContain(identityAttribute.id.toString()); + expect(attributesId).toContain(repositoryAttribute.id.toString()); }); }); - test("should allow to create an attribute shared by a peer", async function () { - const attribute: ICreateLocalAttributeParams = { - content: IdentityAttribute.from({ - value: { - "@type": "Nationality", - value: "DE" - }, - owner: CoreAddress.from("address") - }) - }; - const localAttribute = await consumptionController.attributes.createLocalAttribute(attribute); - const createPeerAttributeParams: ICreatePeerLocalAttributeParams = { - id: localAttribute.id, - content: attribute.content, - requestReference: CoreId.from("requestId"), - peer: CoreAddress.from("address") - }; - const peerLocalAttribute = await consumptionController.attributes.createPeerLocalAttribute(createPeerAttributeParams); - expect(peerLocalAttribute.content.toJSON()).toStrictEqual(localAttribute.content.toJSON()); - expect(peerLocalAttribute.content.value).toBeInstanceOf(Nationality); - expect(createPeerAttributeParams.id).toStrictEqual(localAttribute.id); - expect(createPeerAttributeParams.peer.address).toStrictEqual(CoreAddress.from("address").toString()); - expect(createPeerAttributeParams.requestReference.toString()).toStrictEqual(CoreId.from("requestId").toString()); - - mockEventBus.expectLastPublishedEvent(AttributeCreatedEvent); - }); - - describe("Attribute deletion", function () { + describe("delete Attributes", function () { test("should delete a simple attribute", async function () { - const attributeParams: ICreateLocalAttributeParams = { + const attributeParams: ICreateRepositoryAttributeParams = { content: IdentityAttribute.from({ value: EMailAddress.from({ value: "my@email.address" @@ -678,7 +662,7 @@ describe("AttributesController", function () { owner: consumptionController.accountController.identity.address }) }; - const simpleAttribute = await consumptionController.attributes.createLocalAttribute(attributeParams); + const simpleAttribute = await consumptionController.attributes.createRepositoryAttribute(attributeParams); const createdAttribute = await consumptionController.attributes.getLocalAttribute(simpleAttribute.id); expect(createdAttribute).toBeDefined(); @@ -693,17 +677,17 @@ describe("AttributesController", function () { }); test("should delete a complex attribute", async function () { - const attributeParams: ICreateLocalAttributeParams = { + const attributeParams: ICreateRepositoryAttributeParams = { content: IdentityAttribute.from({ value: BirthDate.from({ day: 24, month: 12, year: 2000 }), - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }; - const complexAttribute = await consumptionController.attributes.createLocalAttribute(attributeParams); + const complexAttribute = await consumptionController.attributes.createRepositoryAttribute(attributeParams); mockEventBus.clearPublishedEvents(); const createdAttribute = await consumptionController.attributes.getLocalAttribute(complexAttribute.id); @@ -729,36 +713,29 @@ describe("AttributesController", function () { }); }); - describe("Attribute successions", function () { + describe("succeed Attributes", function () { describe("Common validator", function () { - afterEach(async function () { - const attributes = await consumptionController.attributes.getLocalAttributes(); - for (const attribute of attributes) { - await consumptionController.attributes.deleteAttributeUnsafe(attribute.id); - } - }); - test("should catch if the successor attribute already exist, if an explicit id is provided", async function () { - const predecessor = await consumptionController.attributes.createLocalAttribute({ + const predecessor = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", value: "DE" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }); - const successorData: IAttributeSuccessorParams = { + const successorData = { id: CoreId.from("successorId"), content: IdentityAttribute.from({ value: { "@type": "Nationality", value: "DE" }, - owner: CoreAddress.from("differentAddress") + owner: consumptionController.accountController.identity.address }) }; - await consumptionController.attributes.createLocalAttribute(successorData); + await consumptionController.attributes.createRepositoryAttribute(successorData); const validationResult = await consumptionController.attributes.validateAttributeSuccessionCommon(predecessor.id, successorData); expect(validationResult).errorValidationResult({ @@ -767,24 +744,24 @@ describe("AttributesController", function () { }); test("should catch if the successor is not linked to predecessor, if succeeds is explicitly set", async function () { - const predecessor = await consumptionController.attributes.createLocalAttribute({ + const predecessor = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", value: "DE" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }); const successorData: IAttributeSuccessorParams = { content: IdentityAttribute.from({ value: { "@type": "Nationality", - value: "DE" + value: "US" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }), - succeeds: CoreId.from("differentAddress") + succeeds: CoreId.from("differentAttributeId") }; const validationResult = await consumptionController.attributes.validateAttributeSuccessionCommon(predecessor.id, successorData); @@ -794,24 +771,24 @@ describe("AttributesController", function () { }); test("should catch if the successor already has a successor itself", async function () { - const predecessor = await consumptionController.attributes.createLocalAttribute({ + const predecessor = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", value: "DE" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }); const successorData: IAttributeSuccessorParams = { content: IdentityAttribute.from({ value: { "@type": "Nationality", - value: "DE" + value: "US" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }), - succeededBy: CoreId.from("differentAddress") + succeededBy: CoreId.from("differentAttributeId") }; const validationResult = await consumptionController.attributes.validateAttributeSuccessionCommon(predecessor.id, successorData); @@ -821,22 +798,22 @@ describe("AttributesController", function () { }); test("should catch if the successor has parent", async function () { - const predecessor = await consumptionController.attributes.createLocalAttribute({ + const predecessor = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", value: "DE" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }); const successorData: IAttributeSuccessorParams = { content: IdentityAttribute.from({ value: { "@type": "Nationality", - value: "DE" + value: "US" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }), parentId: CoreId.from("parentId") }; @@ -852,9 +829,9 @@ describe("AttributesController", function () { content: IdentityAttribute.from({ value: { "@type": "Nationality", - value: "DE" + value: "US" }, - owner: CoreAddress.from("differentAddress") + owner: consumptionController.accountController.identity.address }) }; @@ -872,16 +849,16 @@ describe("AttributesController", function () { "@type": "Nationality", value: "DE" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }); const successorData: IAttributeSuccessorParams = { content: IdentityAttribute.from({ value: { "@type": "Nationality", - value: "DE" + value: "US" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }; @@ -892,23 +869,23 @@ describe("AttributesController", function () { }); test("should catch if the predecessor has parent", async function () { - const predecessor = await consumptionController.attributes.createAttributeUnsafe({ + const predecessor = await consumptionController.attributes.createRepositoryAttribute({ parentId: CoreId.from("parentId"), content: IdentityAttribute.from({ value: { "@type": "Nationality", value: "DE" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }); const successorData: IAttributeSuccessorParams = { content: IdentityAttribute.from({ value: { "@type": "Nationality", - value: "DE" + value: "US" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }; @@ -919,20 +896,20 @@ describe("AttributesController", function () { }); test("should catch attempted change of owner", async function () { - const predecessor = await consumptionController.attributes.createLocalAttribute({ + const predecessor = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", value: "DE" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }); const successorData: IAttributeSuccessorParams = { content: IdentityAttribute.from({ value: { "@type": "Nationality", - value: "DE" + value: "US" }, owner: CoreAddress.from("differentAddress") }) @@ -945,13 +922,13 @@ describe("AttributesController", function () { }); test("should catch attempted change of content type", async function () { - const predecessor = await consumptionController.attributes.createLocalAttribute({ + const predecessor = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { - "@type": "Citizenship", + "@type": "Nationality", value: "DE" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }); const successorData: IAttributeSuccessorParams = { @@ -962,7 +939,7 @@ describe("AttributesController", function () { value: "ADisplayName", title: "Display Name" }, - owner: CoreAddress.from("address"), + owner: consumptionController.accountController.identity.address, confidentiality: RelationshipAttributeConfidentiality.Public }) }; @@ -974,13 +951,13 @@ describe("AttributesController", function () { }); test("should catch attempted change of value type", async function () { - const predecessor = await consumptionController.attributes.createLocalAttribute({ + const predecessor = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "BirthName", value: "Müller" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }); const successorData: IAttributeSuccessorParams = { @@ -989,7 +966,7 @@ describe("AttributesController", function () { "@type": "BirthCountry", value: "DE" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }; @@ -1006,7 +983,7 @@ describe("AttributesController", function () { "@type": "Nationality", value: "DE" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }), deletionInfo: LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.ToBeDeleted, @@ -1017,9 +994,9 @@ describe("AttributesController", function () { content: IdentityAttribute.from({ value: { "@type": "Nationality", - value: "DE" + value: "US" }, - owner: CoreAddress.from("address") + owner: consumptionController.accountController.identity.address }) }; @@ -1031,12 +1008,12 @@ describe("AttributesController", function () { }); describe("Validator for own shared identity attribute successions", function () { - let predecessorRelationshipAttribute: LocalAttribute; - let successorRelationshipAttribute: LocalAttribute; + let predecessorRepositoryAttribute: LocalAttribute; + let successorRepositoryAttribute: LocalAttribute; let predecessorOwnSharedIdentityAttribute: LocalAttribute; let successorOwnSharedIdentityAttributeParams: IAttributeSuccessorParams; beforeEach(async function () { - predecessorRelationshipAttribute = await consumptionController.attributes.createLocalAttribute({ + predecessorRepositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Citizenship", @@ -1046,7 +1023,7 @@ describe("AttributesController", function () { }) }); - ({ successor: successorRelationshipAttribute } = await consumptionController.attributes.succeedRepositoryAttribute(predecessorRelationshipAttribute.id, { + ({ successor: successorRepositoryAttribute } = await consumptionController.attributes.succeedRepositoryAttribute(predecessorRepositoryAttribute.id, { content: IdentityAttribute.from({ value: { "@type": "Citizenship", @@ -1057,7 +1034,7 @@ describe("AttributesController", function () { })); predecessorOwnSharedIdentityAttribute = await consumptionController.attributes.createSharedLocalAttributeCopy({ - sourceAttributeId: predecessorRelationshipAttribute.id, + sourceAttributeId: predecessorRepositoryAttribute.id, peer: CoreAddress.from("peer"), requestReference: CoreId.from("reqRef") }); @@ -1073,23 +1050,17 @@ describe("AttributesController", function () { shareInfo: { peer: CoreAddress.from("peer"), notificationReference: CoreId.from("notRef"), - sourceAttribute: successorRelationshipAttribute.id + sourceAttribute: successorRepositoryAttribute.id } }; }); - afterEach(async function () { - const attributes = await consumptionController.attributes.getLocalAttributes(); - for (const attribute of attributes) { - await consumptionController.attributes.deleteAttributeUnsafe(attribute.id); - } - }); test("should catch if the source attributes do not succeed one another", async function () { - predecessorRelationshipAttribute.succeededBy = undefined; - await consumptionController.attributes.updateAttributeUnsafe(predecessorRelationshipAttribute); + predecessorRepositoryAttribute.succeededBy = undefined; + await consumptionController.attributes.updateAttributeUnsafe(predecessorRepositoryAttribute); - successorRelationshipAttribute.succeeds = undefined; - await consumptionController.attributes.updateAttributeUnsafe(successorRelationshipAttribute); + successorRepositoryAttribute.succeeds = undefined; + await consumptionController.attributes.updateAttributeUnsafe(successorRepositoryAttribute); const validationResult = await consumptionController.attributes.validateOwnSharedIdentityAttributeSuccession( predecessorOwnSharedIdentityAttribute.id, @@ -1151,13 +1122,13 @@ describe("AttributesController", function () { }); test("should catch if the successor source attribute is not a repository attribute", async function () { - successorRelationshipAttribute.shareInfo = LocalAttributeShareInfo.from({ + successorRepositoryAttribute.shareInfo = LocalAttributeShareInfo.from({ peer: CoreAddress.from("peer"), requestReference: CoreId.from("reqRef") }); - await consumptionController.attributes.updateAttributeUnsafe(successorRelationshipAttribute); + await consumptionController.attributes.updateAttributeUnsafe(successorRepositoryAttribute); - successorOwnSharedIdentityAttributeParams.shareInfo!.sourceAttribute = successorRelationshipAttribute.id; + successorOwnSharedIdentityAttributeParams.shareInfo!.sourceAttribute = successorRepositoryAttribute.id; const validationResult = await consumptionController.attributes.validateOwnSharedIdentityAttributeSuccession( predecessorOwnSharedIdentityAttribute.id, @@ -1217,7 +1188,7 @@ describe("AttributesController", function () { }); test("should catch if successor source attribute doesn't exist", async function () { - await consumptionController.attributes.deleteAttributeUnsafe(successorRelationshipAttribute.id); + await consumptionController.attributes.deleteAttributeUnsafe(successorRepositoryAttribute.id); const validationResult = await consumptionController.attributes.validateOwnSharedIdentityAttributeSuccession( predecessorOwnSharedIdentityAttribute.id, @@ -1229,13 +1200,13 @@ describe("AttributesController", function () { }); test("should allow to succeed an own shared identity attribute whose predecessor source attribute was deleted", async function () { - await consumptionController.attributes.deleteAttributeUnsafe(predecessorRelationshipAttribute.id); + await consumptionController.attributes.deleteAttributeUnsafe(predecessorRepositoryAttribute.id); predecessorOwnSharedIdentityAttribute.shareInfo!.sourceAttribute = undefined; await consumptionController.attributes.updateAttributeUnsafe(predecessorOwnSharedIdentityAttribute); - successorRelationshipAttribute.succeeds = undefined; - await consumptionController.attributes.updateAttributeUnsafe(successorRelationshipAttribute); + successorRepositoryAttribute.succeeds = undefined; + await consumptionController.attributes.updateAttributeUnsafe(successorRepositoryAttribute); const validationResult = await consumptionController.attributes.validateOwnSharedIdentityAttributeSuccession( predecessorOwnSharedIdentityAttribute.id, @@ -1246,15 +1217,8 @@ describe("AttributesController", function () { }); describe("Happy paths for attribute successions", function () { - afterEach(async function () { - const attributes = await consumptionController.attributes.getLocalAttributes(); - for (const attribute of attributes) { - await consumptionController.attributes.deleteAttributeUnsafe(attribute.id); - } - }); - test("should succeed a repository attribute", async function () { - const predecessor = await consumptionController.attributes.createLocalAttribute({ + const predecessor = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", @@ -1284,7 +1248,7 @@ describe("AttributesController", function () { }); test("should succeed an own shared identity attribute", async function () { - const predecessorRepo = await consumptionController.attributes.createLocalAttribute({ + const predecessorRepo = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", @@ -1335,7 +1299,7 @@ describe("AttributesController", function () { }); test("should succeed an own shared identity attribute skipping one version", async function () { - const predecessorRepo = await consumptionController.attributes.createLocalAttribute({ + const predecessorRepo = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", @@ -1416,7 +1380,7 @@ describe("AttributesController", function () { owner: consumptionController.accountController.identity.address }); - repoVersion0 = await consumptionController.attributes.createLocalAttribute({ + repoVersion0 = await consumptionController.attributes.createRepositoryAttribute({ content: identityAttribute }); @@ -1534,7 +1498,7 @@ describe("AttributesController", function () { owner: consumptionController.accountController.identity.address }); - repoVersion0 = await consumptionController.attributes.createLocalAttribute({ + repoVersion0 = await consumptionController.attributes.createRepositoryAttribute({ content: identityAttribute }); @@ -1578,7 +1542,7 @@ describe("AttributesController", function () { }, owner: consumptionController.accountController.identity.address }); - repoVersion0 = await consumptionController.attributes.createLocalAttribute({ + repoVersion0 = await consumptionController.attributes.createRepositoryAttribute({ content: identityAttribute }); @@ -1733,12 +1697,10 @@ describe("AttributesController", function () { owner: CoreAddress.from("peer") }); - const peerSharedVersion0 = await consumptionController.attributes.createLocalAttribute({ + const peerSharedVersion0 = await consumptionController.attributes.createSharedLocalAttribute({ content: identityAttribute, - shareInfo: { - peer: CoreAddress.from("peer"), - requestReference: CoreId.from("reqRef2") - } + peer: CoreAddress.from("peer"), + requestReference: CoreId.from("reqRef2") }); const peerSharedVersion1Params: IAttributeSuccessorParams = { @@ -1781,7 +1743,7 @@ describe("AttributesController", function () { }); test("should succeed a peer shared identity attribute", async function () { - const predecessor = await consumptionController.attributes.createLocalAttribute({ + const predecessor = await consumptionController.attributes.createAttributeUnsafe({ content: IdentityAttribute.from({ value: { "@type": "Nationality", @@ -1819,7 +1781,7 @@ describe("AttributesController", function () { }); test("should succeed an own shared relationship attribute", async function () { - const predecessor = await consumptionController.attributes.createLocalAttribute({ + const predecessor = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "customerId", value: { @@ -1830,10 +1792,8 @@ describe("AttributesController", function () { owner: consumptionController.accountController.identity.address, confidentiality: RelationshipAttributeConfidentiality.Public }), - shareInfo: { - peer: CoreAddress.from("peerAddress"), - requestReference: CoreId.from("reqRefA") - } + peer: CoreAddress.from("peerAddress"), + requestReference: CoreId.from("reqRefA") }); const successorParams: IAttributeSuccessorParams = { content: RelationshipAttribute.from({ @@ -1866,7 +1826,7 @@ describe("AttributesController", function () { }); test("should succeed a peer shared relationship attribute", async function () { - const predecessor = await consumptionController.attributes.createLocalAttribute({ + const predecessor = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "customerId", value: { @@ -1877,10 +1837,8 @@ describe("AttributesController", function () { owner: CoreAddress.from("peerAddress"), confidentiality: RelationshipAttributeConfidentiality.Public }), - shareInfo: { - peer: CoreAddress.from("peerAddress"), - requestReference: CoreId.from("reqRefA") - } + peer: CoreAddress.from("peerAddress"), + requestReference: CoreId.from("reqRefA") }); const successorParams: IAttributeSuccessorParams = { content: RelationshipAttribute.from({ @@ -1913,7 +1871,7 @@ describe("AttributesController", function () { }); test("should succeed a third party owned relationship attribute", async function () { - const predecessor = await consumptionController.attributes.createLocalAttribute({ + const predecessor = await consumptionController.attributes.createAttributeUnsafe({ content: RelationshipAttribute.from({ key: "customerId", value: { @@ -1963,9 +1921,9 @@ describe("AttributesController", function () { }); }); - describe("hideTechnical", function () { + describe("get Attributes", function () { beforeEach(async function () { - await consumptionController.attributes.createLocalAttribute({ + await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "notTechnical", value: { @@ -1976,10 +1934,12 @@ describe("AttributesController", function () { isTechnical: false, owner: testAccount.identity.address, confidentiality: RelationshipAttributeConfidentiality.Public - }) + }), + requestReference: CoreId.from("reqRef"), + peer: CoreAddress.from("peer") }); - await consumptionController.attributes.createLocalAttribute({ + await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "isTechnicalNotDefined", value: { @@ -1989,10 +1949,12 @@ describe("AttributesController", function () { }, owner: testAccount.identity.address, confidentiality: RelationshipAttributeConfidentiality.Public - }) + }), + requestReference: CoreId.from("reqRef"), + peer: CoreAddress.from("peer") }); - await consumptionController.attributes.createLocalAttribute({ + await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "technical", value: { @@ -2003,27 +1965,34 @@ describe("AttributesController", function () { isTechnical: true, owner: testAccount.identity.address, confidentiality: RelationshipAttributeConfidentiality.Public - }) + }), + requestReference: CoreId.from("reqRef"), + peer: CoreAddress.from("peer") }); }); + test("should list all attributes", async function () { + const attributes = await consumptionController.attributes.getLocalAttributes(); + expect(attributes).toHaveLength(3); + }); + test("should hide technical attributes when no query is given", async function () { const attributes = await consumptionController.attributes.getLocalAttributes(undefined, true); - expect(attributes).toHaveLength(4); + expect(attributes).toHaveLength(2); }); test("should hide technical attributes when empty query is given", async function () { const attributes = await consumptionController.attributes.getLocalAttributes({}, true); - expect(attributes).toHaveLength(4); + expect(attributes).toHaveLength(2); }); }); - describe("get versions of attribute", function () { - let rAVersion0: LocalAttribute; - let rAVersion1: LocalAttribute; - let rAVersion2: LocalAttribute; + describe("get versions of an Attribute", function () { + let repositoryAttributeVersion0: LocalAttribute; + let repositoryAttributeVersion1: LocalAttribute; + let repositoryAttributeVersion2: LocalAttribute; beforeEach(async function () { - rAVersion0 = await consumptionController.attributes.createLocalAttribute({ + repositoryAttributeVersion0 = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", @@ -2051,41 +2020,47 @@ describe("AttributesController", function () { }) }; - ({ predecessor: rAVersion0, successor: rAVersion1 } = await consumptionController.attributes.succeedRepositoryAttribute(rAVersion0.id, successorParams1)); - ({ predecessor: rAVersion1, successor: rAVersion2 } = await consumptionController.attributes.succeedRepositoryAttribute(rAVersion1.id, successorParams2)); + ({ predecessor: repositoryAttributeVersion0, successor: repositoryAttributeVersion1 } = await consumptionController.attributes.succeedRepositoryAttribute( + repositoryAttributeVersion0.id, + successorParams1 + )); + ({ predecessor: repositoryAttributeVersion1, successor: repositoryAttributeVersion2 } = await consumptionController.attributes.succeedRepositoryAttribute( + repositoryAttributeVersion1.id, + successorParams2 + )); }); test("should return all predecessors of a succeeded repository attribute", async function () { - const result0 = await consumptionController.attributes.getPredecessorsOfAttribute(rAVersion0.id); + const result0 = await consumptionController.attributes.getPredecessorsOfAttribute(repositoryAttributeVersion0.id); expect(result0).toStrictEqual([]); - const result1 = await consumptionController.attributes.getPredecessorsOfAttribute(rAVersion1.id); - expect(result1).toStrictEqual([rAVersion0]); + const result1 = await consumptionController.attributes.getPredecessorsOfAttribute(repositoryAttributeVersion1.id); + expect(result1).toStrictEqual([repositoryAttributeVersion0]); - const result2 = await consumptionController.attributes.getPredecessorsOfAttribute(rAVersion2.id); - expect(result2).toStrictEqual([rAVersion1, rAVersion0]); + const result2 = await consumptionController.attributes.getPredecessorsOfAttribute(repositoryAttributeVersion2.id); + expect(result2).toStrictEqual([repositoryAttributeVersion1, repositoryAttributeVersion0]); }); test("should return all successors of a succeeded repository attribute", async function () { - const result0 = await consumptionController.attributes.getSuccessorsOfAttribute(rAVersion0.id); - expect(result0).toStrictEqual([rAVersion1, rAVersion2]); + const result0 = await consumptionController.attributes.getSuccessorsOfAttribute(repositoryAttributeVersion0.id); + expect(result0).toStrictEqual([repositoryAttributeVersion1, repositoryAttributeVersion2]); - const result1 = await consumptionController.attributes.getSuccessorsOfAttribute(rAVersion1.id); - expect(result1).toStrictEqual([rAVersion2]); + const result1 = await consumptionController.attributes.getSuccessorsOfAttribute(repositoryAttributeVersion1.id); + expect(result1).toStrictEqual([repositoryAttributeVersion2]); - const result2 = await consumptionController.attributes.getSuccessorsOfAttribute(rAVersion2.id); + const result2 = await consumptionController.attributes.getSuccessorsOfAttribute(repositoryAttributeVersion2.id); expect(result2).toStrictEqual([]); }); test("should return all versions of a succeeded repository attribute", async function () { - const allVersions = [rAVersion2, rAVersion1, rAVersion0]; + const allVersions = [repositoryAttributeVersion2, repositoryAttributeVersion1, repositoryAttributeVersion0]; for (const version of allVersions) { const result = await consumptionController.attributes.getVersionsOfAttribute(version.id); - expect(result).toStrictEqual([rAVersion2, rAVersion1, rAVersion0]); + expect(result).toStrictEqual([repositoryAttributeVersion2, repositoryAttributeVersion1, repositoryAttributeVersion0]); } }); test("should return only input attribute if no successions were performed", async function () { - const version0 = await consumptionController.attributes.createLocalAttribute({ + const version0 = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", @@ -2100,17 +2075,17 @@ describe("AttributesController", function () { }); test("should return all versions of a possibly succeeded own shared identity attribute", async function () { - const oSIAVersion0 = await consumptionController.attributes.createSharedLocalAttributeCopy({ - sourceAttributeId: rAVersion0.id, + const ownSharedIdentityAttributeVersion0 = await consumptionController.attributes.createSharedLocalAttributeCopy({ + sourceAttributeId: repositoryAttributeVersion0.id, peer: CoreAddress.from("peerA"), requestReference: CoreId.from("reqRef") }); - const oSIAVersionsBeforeSuccession = await consumptionController.attributes.getVersionsOfAttribute(oSIAVersion0.id); - expect(oSIAVersionsBeforeSuccession).toStrictEqual([oSIAVersion0]); + const ownSharedIdentityAttributeVersionsBeforeSuccession = await consumptionController.attributes.getVersionsOfAttribute(ownSharedIdentityAttributeVersion0.id); + expect(ownSharedIdentityAttributeVersionsBeforeSuccession).toStrictEqual([ownSharedIdentityAttributeVersion0]); - const oSIAVersion1 = await consumptionController.attributes.createSharedLocalAttributeCopy({ - sourceAttributeId: rAVersion1.id, + const ownSharedIdentityAttributeVersion1 = await consumptionController.attributes.createSharedLocalAttributeCopy({ + sourceAttributeId: repositoryAttributeVersion1.id, peer: CoreAddress.from("peerB"), requestReference: CoreId.from("reqRef1") }); @@ -2126,16 +2101,14 @@ describe("AttributesController", function () { shareInfo: { peer: CoreAddress.from("peerB"), requestReference: CoreId.from("reqRef2"), - sourceAttribute: rAVersion2.id + sourceAttribute: repositoryAttributeVersion2.id } }; - const { predecessor: updatedOSIAVersion1, successor: oSIAVersion2 } = await consumptionController.attributes.succeedOwnSharedIdentityAttribute( - oSIAVersion1.id, - successorParams - ); + const { predecessor: updatedOwnSharedIdentityAttributeVersion1, successor: ownSharedIdentityAttributeVersion2 } = + await consumptionController.attributes.succeedOwnSharedIdentityAttribute(ownSharedIdentityAttributeVersion1.id, successorParams); - const allVersions = [oSIAVersion2, updatedOSIAVersion1]; + const allVersions = [ownSharedIdentityAttributeVersion2, updatedOwnSharedIdentityAttributeVersion1]; for (const version of allVersions) { const result = await consumptionController.attributes.getVersionsOfAttribute(version.id); expect(result).toStrictEqual(allVersions); @@ -2143,7 +2116,7 @@ describe("AttributesController", function () { }); test("should return all versions of a possibly succeeded peer shared identity attribute", async function () { - const version0 = await consumptionController.attributes.createLocalAttribute({ + const version0 = await consumptionController.attributes.createSharedLocalAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", @@ -2151,10 +2124,8 @@ describe("AttributesController", function () { }, owner: CoreAddress.from("peer") }), - shareInfo: { - peer: CoreAddress.from("peer"), - requestReference: CoreId.from("reqRefA") - } + peer: CoreAddress.from("peer"), + requestReference: CoreId.from("reqRefA") }); const successorParams1: IAttributeSuccessorParams = { content: IdentityAttribute.from({ @@ -2198,7 +2169,7 @@ describe("AttributesController", function () { }); test("should return all versions of a possibly succeeded own shared relationship attribute", async function () { - const version0 = await consumptionController.attributes.createLocalAttribute({ + const version0 = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "customerId", value: { @@ -2209,10 +2180,8 @@ describe("AttributesController", function () { owner: consumptionController.accountController.identity.address, confidentiality: RelationshipAttributeConfidentiality.Public }), - shareInfo: { - peer: CoreAddress.from("peerAddress"), - requestReference: CoreId.from("reqRefA") - } + peer: CoreAddress.from("peerAddress"), + requestReference: CoreId.from("reqRefA") }); const successorParams1: IAttributeSuccessorParams = { content: RelationshipAttribute.from({ @@ -2267,7 +2236,7 @@ describe("AttributesController", function () { }); test("should return all versions of a possibly succeeded peer shared relationship attribute", async function () { - const version0 = await consumptionController.attributes.createLocalAttribute({ + const version0 = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "customerId", value: { @@ -2278,10 +2247,8 @@ describe("AttributesController", function () { owner: CoreAddress.from("peerAddress"), confidentiality: RelationshipAttributeConfidentiality.Public }), - shareInfo: { - peer: CoreAddress.from("peerAddress"), - requestReference: CoreId.from("reqRefA") - } + peer: CoreAddress.from("peerAddress"), + requestReference: CoreId.from("reqRefA") }); const successorParams1: IAttributeSuccessorParams = { content: RelationshipAttribute.from({ @@ -2340,7 +2307,7 @@ describe("AttributesController", function () { }); test("should check if two attributes are subsequent in succession", async function () { - const version0 = await consumptionController.attributes.createLocalAttribute({ + const version0 = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", @@ -2380,7 +2347,7 @@ describe("AttributesController", function () { }); }); - describe("get shared versions of an attribute", function () { + describe("get shared versions of an Attribute", function () { let repositoryAttributeV0: LocalAttribute; let repositoryAttributeV1: LocalAttribute; let repositoryAttributeV2: LocalAttribute; @@ -2388,7 +2355,7 @@ describe("AttributesController", function () { let ownSharedIdentityAttributeV1PeerB: LocalAttribute; let ownSharedIdentityAttributeV2PeerB: LocalAttribute; beforeEach(async function () { - repositoryAttributeV0 = await consumptionController.attributes.createLocalAttribute({ + repositoryAttributeV0 = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { "@type": "Nationality", @@ -2528,7 +2495,7 @@ describe("AttributesController", function () { }); test("should return an empty list if a shared identity attribute is queried", async function () { - const sharedIdentityAttribute = await consumptionController.attributes.createLocalAttribute({ + const sharedIdentityAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: IdentityAttribute.from({ value: { "@type": "DisplayName", @@ -2536,10 +2503,8 @@ describe("AttributesController", function () { }, owner: consumptionController.accountController.identity.address }), - shareInfo: { - peer: CoreAddress.from("peer"), - requestReference: CoreId.from("reqRefX") - } + peer: CoreAddress.from("peer"), + requestReference: CoreId.from("reqRefX") }); const result = await consumptionController.attributes.getSharedVersionsOfAttribute(sharedIdentityAttribute.id); @@ -2547,7 +2512,7 @@ describe("AttributesController", function () { }); test("should return an empty list if a relationship attribute without associated third party relationship attributes is queried", async function () { - const relationshipAttribute = await consumptionController.attributes.createLocalAttribute({ + const relationshipAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", value: { @@ -2558,10 +2523,8 @@ describe("AttributesController", function () { owner: consumptionController.accountController.identity.address, confidentiality: RelationshipAttributeConfidentiality.Public }), - shareInfo: { - peer: CoreAddress.from("peerAddress"), - requestReference: CoreId.from("reqRef123") - } + peer: CoreAddress.from("peerAddress"), + requestReference: CoreId.from("reqRef123") }); const result = await consumptionController.attributes.getSharedVersionsOfAttribute(relationshipAttribute.id); diff --git a/packages/consumption/test/modules/attributes/LocalAttributeDeletionInfo.test.ts b/packages/consumption/test/modules/attributes/LocalAttributeDeletionInfo.test.ts index caa93d432..1c2a81172 100644 --- a/packages/consumption/test/modules/attributes/LocalAttributeDeletionInfo.test.ts +++ b/packages/consumption/test/modules/attributes/LocalAttributeDeletionInfo.test.ts @@ -33,12 +33,12 @@ describe("LocalAttributeDeletionInfo", function () { }); beforeEach(async function () { - repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: EMailAddress.from({ value: "my@email.com" }), - owner: CoreAddress.from(testAccount.identity.address) + owner: testAccount.identity.address }) }); @@ -48,7 +48,7 @@ describe("LocalAttributeDeletionInfo", function () { requestReference: CoreId.from("request") }); - peerSharedIdentityAttribute = await consumptionController.attributes.createPeerLocalAttribute({ + peerSharedIdentityAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: IdentityAttribute.from({ value: EMailAddress.from({ value: "peer@email.com" @@ -59,7 +59,7 @@ describe("LocalAttributeDeletionInfo", function () { requestReference: CoreId.from("request") }); - thirdPartyOwnedRelationshipAttribute = await consumptionController.attributes.createPeerLocalAttribute({ + thirdPartyOwnedRelationshipAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ value: ProprietaryEMailAddress.from({ value: "thirdParty@email.com", diff --git a/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts index e03a376b4..2481fcc95 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts @@ -212,7 +212,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { const sender = CoreAddress.from("Sender"); const recipient = accountController.identity.address; - const existingLocalAttribute = await consumptionController.attributes.createLocalAttribute({ + const existingLocalAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: recipient }) @@ -366,7 +366,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { const sender = CoreAddress.from("Sender"); const recipient = accountController.identity.address; - const attribute = await consumptionController.attributes.createPeerLocalAttribute({ + const attribute = await consumptionController.attributes.createSharedLocalAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: recipient }), @@ -412,7 +412,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { const sender = CoreAddress.from("Sender"); const recipient = accountController.identity.address; - const repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: recipient }) @@ -508,7 +508,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { statusLog: [] }); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, @@ -518,10 +518,8 @@ describe("ProposeAttributeRequestItemProcessor", function () { value: "AnotherStringValue" }) }), - shareInfo: { - peer: sender, - requestReference: await ConsumptionIds.request.generate() - } + peer: sender, + requestReference: await ConsumptionIds.request.generate() }); const acceptParams: AcceptProposeAttributeRequestItemParametersWithExistingAttributeJSON = { @@ -540,9 +538,9 @@ describe("ProposeAttributeRequestItemProcessor", function () { test("returns an error trying to share the predecessor of an already shared Attribute", async function () { const sender = CoreAddress.from("Sender"); - const predecessorRepositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const predecessorRepositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); @@ -552,7 +550,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { "@type": "GivenName", value: "A new given name" }, - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); @@ -600,7 +598,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { const sender = CoreAddress.from("Sender"); const recipient = accountController.identity.address; - const attribute = await consumptionController.attributes.createLocalAttribute({ + const attribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: recipient }) @@ -785,9 +783,9 @@ describe("ProposeAttributeRequestItemProcessor", function () { test("accept with existing IdentityAttribute whose predecessor was already shared", async function () { const sender = CoreAddress.from("Sender"); - const predecessorRepositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const predecessorRepositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); @@ -803,7 +801,7 @@ describe("ProposeAttributeRequestItemProcessor", function () { "@type": "GivenName", value: "A new given name" }, - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); @@ -852,9 +850,9 @@ describe("ProposeAttributeRequestItemProcessor", function () { test("accept with existing IdentityAttribute whose predecessor was already shared but deleted by peer", async function () { const sender = CoreAddress.from("Sender"); - const predecessorRepositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const predecessorRepositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); @@ -864,13 +862,13 @@ describe("ProposeAttributeRequestItemProcessor", function () { "@type": "GivenName", value: "A succeeded given name" }, - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); await consumptionController.attributes.createAttributeUnsafe({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }), shareInfo: LocalAttributeShareInfo.from({ sourceAttribute: predecessorRepositoryAttribute.id, peer: sender, requestReference: await CoreId.generate() }), deletionInfo: LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletedByPeer, deletionDate: CoreDate.utc().subtract({ days: 1 }) }) @@ -916,9 +914,9 @@ describe("ProposeAttributeRequestItemProcessor", function () { test("accept with existing IdentityAttribute that is already shared and the latest shared version", async function () { const sender = CoreAddress.from("Sender"); - const repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); @@ -964,15 +962,15 @@ describe("ProposeAttributeRequestItemProcessor", function () { test("accept with existing IdentityAttribute that is already shared and the latest shared version but deleted by peer", async function () { const sender = CoreAddress.from("Sender"); - const repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); const alreadySharedAttribute = await consumptionController.attributes.createAttributeUnsafe({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }), shareInfo: LocalAttributeShareInfo.from({ sourceAttribute: repositoryAttribute.id, peer: sender, requestReference: await CoreId.generate() }), deletionInfo: LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletedByPeer, deletionDate: CoreDate.utc().subtract({ days: 1 }) }) @@ -1059,14 +1057,12 @@ describe("ProposeAttributeRequestItemProcessor", function () { test("succeeds an existing peer shared IdentityAttribute with the Attribute received in the ResponseItem", async function () { const sender = CoreAddress.from("Sender"); - const predecessorPeerSharedIdentityAttribute = await consumptionController.attributes.createLocalAttribute({ + const predecessorPeerSharedIdentityAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: sender }), - shareInfo: LocalAttributeShareInfo.from({ - peer: sender, - requestReference: CoreId.from("oldReqRef") - }) + peer: sender, + requestReference: CoreId.from("oldReqRef") }); const requestItem = ProposeAttributeRequestItem.from({ diff --git a/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts index db9456b15..e6a9694f2 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts @@ -249,7 +249,7 @@ describe("ReadAttributeRequestItemProcessor", function () { const sender = CoreAddress.from("Sender"); const recipient = accountController.identity.address; - const attribute = await consumptionController.attributes.createLocalAttribute({ + const attribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: recipient }) @@ -327,7 +327,7 @@ describe("ReadAttributeRequestItemProcessor", function () { const sender = CoreAddress.from("Sender"); const aThirdParty = CoreAddress.from("AThirdParty"); - const attribute = await consumptionController.attributes.createLocalAttribute({ + const attribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, @@ -337,10 +337,8 @@ describe("ReadAttributeRequestItemProcessor", function () { value: "AStringValue" }) }), - shareInfo: { - peer: aThirdParty, - requestReference: await ConsumptionIds.request.generate() - } + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() }); const requestItem = ReadAttributeRequestItem.from({ @@ -411,9 +409,9 @@ describe("ReadAttributeRequestItemProcessor", function () { test("returns an error trying to share the predecessor of an already shared Attribute", async function () { const sender = CoreAddress.from("Sender"); - const predecessorRepositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const predecessorRepositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); @@ -423,7 +421,7 @@ describe("ReadAttributeRequestItemProcessor", function () { "@type": "GivenName", value: "A new given name" }, - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); @@ -469,7 +467,7 @@ describe("ReadAttributeRequestItemProcessor", function () { const sender = CoreAddress.from("Sender"); const recipient = accountController.identity.address; - const attribute = await consumptionController.attributes.createPeerLocalAttribute({ + const attribute = await consumptionController.attributes.createSharedLocalAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: recipient }), @@ -512,7 +510,7 @@ describe("ReadAttributeRequestItemProcessor", function () { const sender = CoreAddress.from("Sender"); const recipient = accountController.identity.address; - const repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: recipient }) @@ -680,7 +678,7 @@ describe("ReadAttributeRequestItemProcessor", function () { statusLog: [] }); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, @@ -690,10 +688,8 @@ describe("ReadAttributeRequestItemProcessor", function () { value: "AStringValue" }) }), - shareInfo: { - peer: sender, - requestReference: await ConsumptionIds.request.generate() - } + peer: sender, + requestReference: await ConsumptionIds.request.generate() }); const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { @@ -841,7 +837,7 @@ describe("ReadAttributeRequestItemProcessor", function () { statusLog: [] }); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createAttributeUnsafe({ content: RelationshipAttribute.from({ key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, @@ -899,7 +895,7 @@ describe("ReadAttributeRequestItemProcessor", function () { statusLog: [] }); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, @@ -909,10 +905,8 @@ describe("ReadAttributeRequestItemProcessor", function () { value: "AStringValue" }) }), - shareInfo: { - peer: anUninvolvedThirdParty, - requestReference: await ConsumptionIds.request.generate() - } + peer: anUninvolvedThirdParty, + requestReference: await ConsumptionIds.request.generate() }); const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { @@ -1005,7 +999,7 @@ describe("ReadAttributeRequestItemProcessor", function () { statusLog: [] }); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, @@ -1015,10 +1009,8 @@ describe("ReadAttributeRequestItemProcessor", function () { value: "AStringValue" }) }), - shareInfo: { - peer: anUninvolvedThirdParty, - requestReference: await ConsumptionIds.request.generate() - } + peer: anUninvolvedThirdParty, + requestReference: await ConsumptionIds.request.generate() }); const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { @@ -1058,7 +1050,7 @@ describe("ReadAttributeRequestItemProcessor", function () { statusLog: [] }); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Private, @@ -1068,10 +1060,8 @@ describe("ReadAttributeRequestItemProcessor", function () { value: "AStringValue" }) }), - shareInfo: { - peer: aThirdParty, - requestReference: await ConsumptionIds.request.generate() - } + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() }); const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { @@ -1093,7 +1083,7 @@ describe("ReadAttributeRequestItemProcessor", function () { test("accept with existing RepositoryAttribute", async function () { const sender = CoreAddress.from("Sender"); const recipient = accountController.identity.address; - const attribute = await consumptionController.attributes.createLocalAttribute({ + const attribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: recipient }) @@ -1234,7 +1224,7 @@ describe("ReadAttributeRequestItemProcessor", function () { test("accept with existing IdentityAttribute whose predecessor was already shared", async function () { const sender = CoreAddress.from("Sender"); - const predecessorRepositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const predecessorRepositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: CoreAddress.from(accountController.identity.address) }) @@ -1297,9 +1287,9 @@ describe("ReadAttributeRequestItemProcessor", function () { test("accept with existing IdentityAttribute whose predecessor was already shared but deleted by peer", async function () { const sender = CoreAddress.from("Sender"); - const predecessorRepositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const predecessorRepositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); @@ -1309,13 +1299,13 @@ describe("ReadAttributeRequestItemProcessor", function () { "@type": "GivenName", value: "A succeeded given name" }, - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); await consumptionController.attributes.createAttributeUnsafe({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }), shareInfo: LocalAttributeShareInfo.from({ sourceAttribute: predecessorRepositoryAttribute.id, peer: sender, requestReference: await CoreId.generate() }), deletionInfo: LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletedByPeer, deletionDate: CoreDate.utc().subtract({ days: 1 }) }) @@ -1357,9 +1347,9 @@ describe("ReadAttributeRequestItemProcessor", function () { test("accept with existing IdentityAttribute that is already shared and the latest shared version", async function () { const sender = CoreAddress.from("Sender"); - const repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); @@ -1401,15 +1391,15 @@ describe("ReadAttributeRequestItemProcessor", function () { test("accept with existing IdentityAttribute that is already shared and the latest shared version but deleted by peer", async function () { const sender = CoreAddress.from("Sender"); - const repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }) }); const alreadySharedAttribute = await consumptionController.attributes.createAttributeUnsafe({ content: TestObjectFactory.createIdentityAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }), shareInfo: LocalAttributeShareInfo.from({ sourceAttribute: repositoryAttribute.id, peer: sender, requestReference: await CoreId.generate() }), deletionInfo: LocalAttributeDeletionInfo.from({ deletionStatus: DeletionStatus.DeletedByPeer, deletionDate: CoreDate.utc().subtract({ days: 1 }) }) @@ -1451,14 +1441,12 @@ describe("ReadAttributeRequestItemProcessor", function () { const thirdPartyAddress = CoreAddress.from("thirdPartyAddress"); const sender = CoreAddress.from("Sender"); - const predecessorSourceAttribute = await consumptionController.attributes.createLocalAttribute({ + const predecessorSourceAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: TestObjectFactory.createRelationshipAttribute({ - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }), - shareInfo: LocalAttributeShareInfo.from({ - peer: thirdPartyAddress, - requestReference: CoreId.from("reqRef") - }) + peer: thirdPartyAddress, + requestReference: CoreId.from("reqRef") }); const predecessorOwnSharedRelationshipAttribute = await consumptionController.attributes.createSharedLocalAttributeCopy({ @@ -1476,7 +1464,7 @@ describe("ReadAttributeRequestItemProcessor", function () { }, confidentiality: RelationshipAttributeConfidentiality.Public, key: "AKey", - owner: CoreAddress.from(accountController.identity.address) + owner: accountController.identity.address }), shareInfo: LocalAttributeShareInfo.from({ peer: thirdPartyAddress, @@ -1530,14 +1518,12 @@ describe("ReadAttributeRequestItemProcessor", function () { const thirdPartyAddress = CoreAddress.from("thirdPartyAddress"); const sender = CoreAddress.from("Sender"); - const predecessorSourceAttribute = await consumptionController.attributes.createLocalAttribute({ + const predecessorSourceAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: TestObjectFactory.createRelationshipAttribute({ owner: thirdPartyAddress }), - shareInfo: LocalAttributeShareInfo.from({ - peer: thirdPartyAddress, - requestReference: CoreId.from("reqRef") - }) + peer: thirdPartyAddress, + requestReference: CoreId.from("reqRef") }); const predecessorOwnSharedRelationshipAttribute = await consumptionController.attributes.createSharedLocalAttributeCopy({ @@ -1649,14 +1635,12 @@ describe("ReadAttributeRequestItemProcessor", function () { test("succeeds an existing peer shared IdentityAttribute with the Attribute received in the ResponseItem", async function () { const recipient = CoreAddress.from("Recipient"); - const predecessorPeerSharedIdentityAttribute = await consumptionController.attributes.createLocalAttribute({ + const predecessorPeerSharedIdentityAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: recipient }), - shareInfo: LocalAttributeShareInfo.from({ - peer: recipient, - requestReference: CoreId.from("oldReqRef") - }) + peer: recipient, + requestReference: CoreId.from("oldReqRef") }); const requestItem = ReadAttributeRequestItem.from({ @@ -1706,14 +1690,12 @@ describe("ReadAttributeRequestItemProcessor", function () { const thirdPartyAddress = CoreAddress.from("thirdPartyAddress"); const recipient = CoreAddress.from("Recipient"); - const predecessorPeerSharedRelationshipAttribute = await consumptionController.attributes.createLocalAttribute({ + const predecessorPeerSharedRelationshipAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: TestObjectFactory.createRelationshipAttribute({ owner: thirdPartyAddress }), - shareInfo: LocalAttributeShareInfo.from({ - peer: recipient, - requestReference: CoreId.from("oldReqRef") - }) + peer: recipient, + requestReference: CoreId.from("oldReqRef") }); const requestItem = ReadAttributeRequestItem.from({ diff --git a/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts index 18e04580c..959823744 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts @@ -149,22 +149,20 @@ describe("ShareAttributeRequestItemProcessor", function () { let sourceAttribute; if (testParams.attribute instanceof IdentityAttribute) { - sourceAttribute = await consumptionController.attributes.createLocalAttribute({ + sourceAttribute = await consumptionController.attributes.createAttributeUnsafe({ content: { ...testParams.attribute.toJSON(), owner: testParams.attribute.owner.equals("") ? sender : testParams.attribute.owner } as IIdentityAttribute }); } else { - sourceAttribute = await consumptionController.attributes.createLocalAttribute({ + sourceAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: { ...testParams.attribute.toJSON(), owner: testParams.attribute.owner.equals("") ? sender : testParams.attribute.owner } as IRelationshipAttribute, - shareInfo: { - peer: aThirdParty, - requestReference: await ConsumptionIds.request.generate() - } + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() }); } @@ -217,7 +215,7 @@ describe("ShareAttributeRequestItemProcessor", function () { owner: sender }); - const sourceAttribute = await consumptionController.attributes.createLocalAttribute({ + const sourceAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: attribute }); const requestItem = ShareAttributeRequestItem.from({ @@ -243,7 +241,7 @@ describe("ShareAttributeRequestItemProcessor", function () { const recipient = CoreAddress.from("Recipient"); const aThirdParty = CoreAddress.from("AThirdParty"); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createAttributeUnsafe({ content: IdentityAttribute.from({ owner: sender, value: GivenName.from({ @@ -275,7 +273,7 @@ describe("ShareAttributeRequestItemProcessor", function () { const sender = testAccount.identity.address; const recipient = CoreAddress.from("Recipient"); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ owner: sender, value: GivenName.from({ @@ -284,18 +282,10 @@ describe("ShareAttributeRequestItemProcessor", function () { }) }); - const localAttributeCopy = await consumptionController.attributes.createLocalAttribute({ - content: IdentityAttribute.from({ - owner: sender, - value: GivenName.from({ - value: "AGivenName" - }) - }), - shareInfo: { - peer: recipient, - requestReference: await ConsumptionIds.request.generate(), - sourceAttribute: localAttribute.id - } + const localAttributeCopy = await consumptionController.attributes.createSharedLocalAttributeCopy({ + peer: recipient, + requestReference: await ConsumptionIds.request.generate(), + sourceAttributeId: localAttribute.id }); expect(localAttributeCopy.isShared()).toBe(true); @@ -318,7 +308,7 @@ describe("ShareAttributeRequestItemProcessor", function () { const sender = testAccount.identity.address; const recipient = CoreAddress.from("Recipient"); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ owner: sender, value: GivenName.from({ @@ -362,7 +352,7 @@ describe("ShareAttributeRequestItemProcessor", function () { const sender = testAccount.identity.address; const recipient = CoreAddress.from("Recipient"); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ owner: sender, value: GivenName.from({ @@ -406,7 +396,7 @@ describe("ShareAttributeRequestItemProcessor", function () { const sender = testAccount.identity.address; const recipient = CoreAddress.from("Recipient"); - const repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: sender }) @@ -448,7 +438,7 @@ describe("ShareAttributeRequestItemProcessor", function () { const sender = testAccount.identity.address; const recipient = CoreAddress.from("Recipient"); - const repositoryAttribute = await consumptionController.attributes.createLocalAttribute({ + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: sender }) @@ -495,7 +485,7 @@ describe("ShareAttributeRequestItemProcessor", function () { const recipient = CoreAddress.from("Recipient"); const aThirdParty = CoreAddress.from("AThirdParty"); - const relationshipAttribute = await consumptionController.attributes.createLocalAttribute({ + const relationshipAttribute = await consumptionController.attributes.createAttributeUnsafe({ content: RelationshipAttribute.from({ owner: sender, value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), @@ -527,17 +517,15 @@ describe("ShareAttributeRequestItemProcessor", function () { const sender = testAccount.identity.address; const recipient = CoreAddress.from("Recipient"); - const relationshipAttribute = await consumptionController.attributes.createLocalAttribute({ + const relationshipAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ owner: sender, value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), confidentiality: RelationshipAttributeConfidentiality.Public, key: "AKey" }), - shareInfo: { - peer: recipient, - requestReference: await ConsumptionIds.request.generate() - } + peer: recipient, + requestReference: await ConsumptionIds.request.generate() }); const requestItem = ShareAttributeRequestItem.from({ mustBeAccepted: false, @@ -656,7 +644,7 @@ describe("ShareAttributeRequestItemProcessor", function () { ? TestObjectFactory.createIdentityAttribute({ owner: testAccount.identity.address }) : TestObjectFactory.createRelationshipAttribute({ owner: testAccount.identity.address }); - const sourceAttribute = await consumptionController.attributes.createLocalAttribute({ + const sourceAttribute = await consumptionController.attributes.createAttributeUnsafe({ content: sourceAttributeContent }); diff --git a/packages/consumption/test/modules/requests/itemProcessors/utility/validateAttributeMatchesWithQuery.test.ts b/packages/consumption/test/modules/requests/itemProcessors/utility/validateAttributeMatchesWithQuery.test.ts index 74346575c..85b390ce3 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/utility/validateAttributeMatchesWithQuery.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/utility/validateAttributeMatchesWithQuery.test.ts @@ -101,7 +101,7 @@ describe("validateAttributeMatchesWithQuery", function () { test("returns an error when the given Attribute id belongs to a peer Attribute", async function () { const thirdPartyAttributeId = await ConsumptionIds.attribute.generate(); - await consumptionController.attributes.createPeerLocalAttribute({ + await consumptionController.attributes.createSharedLocalAttribute({ id: thirdPartyAttributeId, content: TestObjectFactory.createIdentityAttribute({ owner: aThirdParty @@ -917,7 +917,7 @@ describe("validateAttributeMatchesWithQuery", function () { statusLog: [] }); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createRepositoryAttribute({ content: TestObjectFactory.createIdentityAttribute({ owner: recipient }) @@ -960,7 +960,7 @@ describe("validateAttributeMatchesWithQuery", function () { statusLog: [] }); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, @@ -970,10 +970,8 @@ describe("validateAttributeMatchesWithQuery", function () { value: "AStringValue" }) }), - shareInfo: { - peer: aThirdParty, - requestReference: await ConsumptionIds.request.generate() - } + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() }); const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { @@ -1013,7 +1011,7 @@ describe("validateAttributeMatchesWithQuery", function () { statusLog: [] }); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, @@ -1023,10 +1021,8 @@ describe("validateAttributeMatchesWithQuery", function () { value: "AStringValue" }) }), - shareInfo: { - peer: anUninvolvedThirdParty, - requestReference: await ConsumptionIds.request.generate() - } + peer: anUninvolvedThirdParty, + requestReference: await ConsumptionIds.request.generate() }); const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { @@ -1066,7 +1062,7 @@ describe("validateAttributeMatchesWithQuery", function () { statusLog: [] }); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AnotherKey", confidentiality: RelationshipAttributeConfidentiality.Public, @@ -1076,10 +1072,8 @@ describe("validateAttributeMatchesWithQuery", function () { value: "AStringValue" }) }), - shareInfo: { - peer: aThirdParty, - requestReference: await ConsumptionIds.request.generate() - } + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() }); const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { @@ -1121,7 +1115,7 @@ describe("validateAttributeMatchesWithQuery", function () { statusLog: [] }); - const localAttribute = await consumptionController.attributes.createLocalAttribute({ + const localAttribute = await consumptionController.attributes.createSharedLocalAttribute({ content: RelationshipAttribute.from({ key: "AKey", confidentiality: RelationshipAttributeConfidentiality.Public, @@ -1133,10 +1127,8 @@ describe("validateAttributeMatchesWithQuery", function () { value: "AStringValue" }) }), - shareInfo: { - peer: aThirdParty, - requestReference: await ConsumptionIds.request.generate() - } + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() }); const acceptParams: AcceptReadAttributeRequestItemParametersWithExistingAttributeJSON = { diff --git a/packages/runtime/src/useCases/consumption/attributes/CreateRepositoryAttribute.ts b/packages/runtime/src/useCases/consumption/attributes/CreateRepositoryAttribute.ts index b7cc32a63..7395cb353 100644 --- a/packages/runtime/src/useCases/consumption/attributes/CreateRepositoryAttribute.ts +++ b/packages/runtime/src/useCases/consumption/attributes/CreateRepositoryAttribute.ts @@ -1,5 +1,5 @@ import { Result } from "@js-soft/ts-utils"; -import { AttributesController, CreateLocalAttributeParams } from "@nmshd/consumption"; +import { AttributesController, CreateRepositoryAttributeParams } from "@nmshd/consumption"; import { AttributeValues } from "@nmshd/content"; import { AccountController } from "@nmshd/transport"; import { Inject } from "typescript-ioc"; @@ -32,16 +32,17 @@ export class CreateRepositoryAttributeUseCase extends UseCase> { - const params = CreateLocalAttributeParams.from({ + const params = CreateRepositoryAttributeParams.from({ content: { "@type": "IdentityAttribute", owner: this.accountController.identity.address.toString(), ...request.content } }); - const createdAttribute = await this.attributeController.createLocalAttribute(params); + const createdLocalAttribute = await this.attributeController.createRepositoryAttribute(params); + await this.accountController.syncDatawallet(); - return Result.ok(AttributeMapper.toAttributeDTO(createdAttribute)); + return Result.ok(AttributeMapper.toAttributeDTO(createdLocalAttribute)); } } From f800b02dc783f8f8040897695d5fc64e13d49526 Mon Sep 17 00:00:00 2001 From: Britta Stallknecht <146106656+britsta@users.noreply.github.com> Date: Thu, 4 Jul 2024 15:55:47 +0200 Subject: [PATCH 15/40] Refactor/Remove mustBeAccepted of RequestItemGroups (#183) * refactor: remove mustBeAccepted of RequestItemGroup * fix: make runtime tests work * refactor: rename error message from parent to Request * chore: remove outdated comments * chore: build schemas * feat: add tests again that were removed firstly * refactor: define reusable auxiliary function for RequestItemDerivations * chore: version bumps of consumption, content and runtime * fix: correct indexPath typo * fix: undo unneccessary "fix" * feat: incorporate review comments --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- package-lock.json | 10 +- packages/app-runtime/package.json | 4 +- packages/consumption/package.json | 2 +- .../consumption/src/consumption/CoreErrors.ts | 4 +- .../DecideRequestParametersValidator.ts | 38 ++-- ... DecideRequestParametersValidator.test.ts} | 104 +++------- .../IncomingRequestsController.test.ts | 7 +- .../OutgoingRequestsController.test.ts | 4 - .../requests/RequestsIntegrationTest.ts | 2 - .../requests/testHelpers/TestObjectFactory.ts | 3 +- .../requests/testHelpers/TestRequestItem.ts | 11 +- packages/content/package.json | 2 +- packages/content/src/requests/RequestItem.ts | 37 ++-- .../content/src/requests/RequestItemGroup.ts | 51 +---- .../content/test/requests/Request.test.ts | 37 ---- packages/runtime/package.json | 8 +- .../runtime/src/dataViews/DataViewExpander.ts | 13 +- .../src/dataViews/content/RequestItemDVOs.ts | 1 - .../runtime/src/useCases/common/Schemas.ts | 193 ++++++++---------- .../runtime/test/consumption/iqlQuery.test.ts | 4 +- .../runtime/test/consumption/requests.test.ts | 9 +- .../dataViews/RelationshipTemplateDVO.test.ts | 2 - packages/transport/package.json | 2 +- 23 files changed, 202 insertions(+), 346 deletions(-) rename packages/consumption/test/modules/requests/{DecideRequestParamsValidator.test.ts => DecideRequestParametersValidator.test.ts} (80%) diff --git a/package-lock.json b/package-lock.json index 5a3e5ce57..2f29513e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12557,7 +12557,7 @@ }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.4", + "version": "5.0.0-alpha.5", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12579,7 +12579,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.4", + "version": "5.0.0-alpha.5", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12609,15 +12609,15 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.4", + "version": "5.0.0-alpha.5", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.4", - "@nmshd/content": "5.0.0-alpha.4", + "@nmshd/consumption": "5.0.0-alpha.5", + "@nmshd/content": "5.0.0-alpha.5", "@nmshd/crypto": "2.0.6", "@nmshd/transport": "5.0.0-alpha.4", "ajv": "^8.16.0", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index 787bf1a7c..b2a894c70 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.4", + "version": "5.0.0-alpha.5", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.4" + "@nmshd/runtime": "^5.0.0-alpha.5" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 610625c1e..4f1b04c9f 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.4", + "version": "5.0.0-alpha.5", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/consumption/src/consumption/CoreErrors.ts b/packages/consumption/src/consumption/CoreErrors.ts index d6e4792ca..b117a6bb5 100644 --- a/packages/consumption/src/consumption/CoreErrors.ts +++ b/packages/consumption/src/consumption/CoreErrors.ts @@ -296,8 +296,8 @@ class Requests { return new ApplicationError("error.consumption.requests.decide.validation.invalidNumberOfItems", message); } - public itemAcceptedButParentNotAccepted(message: string): ApplicationError { - return new ApplicationError("error.consumption.requests.decide.validation.itemAcceptedButParentNotAccepted", message); + public itemAcceptedButRequestNotAccepted(message: string): ApplicationError { + return new ApplicationError("error.consumption.requests.decide.validation.itemAcceptedButRequestNotAccepted", message); } public mustBeAcceptedItemNotAccepted(message: string): ApplicationError { diff --git a/packages/consumption/src/modules/requests/incoming/DecideRequestParametersValidator.ts b/packages/consumption/src/modules/requests/incoming/DecideRequestParametersValidator.ts index d7248fae9..cbbebdcd9 100644 --- a/packages/consumption/src/modules/requests/incoming/DecideRequestParametersValidator.ts +++ b/packages/consumption/src/modules/requests/incoming/DecideRequestParametersValidator.ts @@ -14,7 +14,7 @@ export class DecideRequestParametersValidator { } if (params.items.length !== request.content.items.length) { - return ValidationResult.error(CoreErrors.requests.decideValidation.invalidNumberOfItems("Number of items in Request and Response do not match")); + return ValidationResult.error(CoreErrors.requests.decideValidation.invalidNumberOfItems("The number of items in the Request and the Response do not match.")); } const validationResults = request.content.items.map((requestItem, index) => this.checkItemOrGroup(requestItem, params.items[index], params.accept)); @@ -24,25 +24,27 @@ export class DecideRequestParametersValidator { private checkItemOrGroup( requestItem: RequestItem | RequestItemGroup, responseItem: DecideRequestItemParametersJSON | DecideRequestItemGroupParametersJSON, - isParentAccepted: boolean + isRequestAccepted: boolean ): ValidationResult { if (requestItem instanceof RequestItem) { - return this.checkItem(requestItem, responseItem, isParentAccepted); + return this.checkItem(requestItem, responseItem, isRequestAccepted); } - return this.checkItemGroup(requestItem, responseItem, isParentAccepted); + return this.checkItemGroup(requestItem, responseItem, isRequestAccepted); } - private checkItem(requestItem: RequestItem, response: DecideRequestItemParametersJSON | DecideRequestItemGroupParametersJSON, isParentAccepted: boolean): ValidationResult { + private checkItem(requestItem: RequestItem, response: DecideRequestItemParametersJSON | DecideRequestItemGroupParametersJSON, isRequestAccepted: boolean): ValidationResult { if (isDecideRequestItemGroupParametersJSON(response)) { return ValidationResult.error(CoreErrors.requests.decideValidation.requestItemAnsweredAsRequestItemGroup()); } - if (!isParentAccepted && response.accept) { - return ValidationResult.error(CoreErrors.requests.decideValidation.itemAcceptedButParentNotAccepted("The RequestItem was accepted, but the parent was not accepted.")); + if (!isRequestAccepted && response.accept) { + return ValidationResult.error( + CoreErrors.requests.decideValidation.itemAcceptedButRequestNotAccepted("The RequestItem was accepted, but the Request was not accepted.") + ); } - if (isParentAccepted && requestItem.mustBeAccepted && !response.accept) { + if (isRequestAccepted && requestItem.mustBeAccepted && !response.accept) { return ValidationResult.error( CoreErrors.requests.decideValidation.mustBeAcceptedItemNotAccepted("The RequestItem is flagged as 'mustBeAccepted', but it was not accepted.") ); @@ -54,33 +56,19 @@ export class DecideRequestParametersValidator { private checkItemGroup( requestItemGroup: RequestItemGroup, responseItemGroup: DecideRequestItemParametersJSON | DecideRequestItemGroupParametersJSON, - isParentAccepted: boolean + isRequestAccepted: boolean ): ValidationResult { if (isDecideRequestItemParametersJSON(responseItemGroup)) { return ValidationResult.error(CoreErrors.requests.decideValidation.requestItemGroupAnsweredAsRequestItem()); } if (responseItemGroup.items.length !== requestItemGroup.items.length) { - return ValidationResult.error(CoreErrors.requests.decideValidation.invalidNumberOfItems("Number of items in RequestItemGroup and ResponseItemGroup do not match")); - } - - const isGroupAccepted = responseItemGroup.items.some((value) => value.accept); - - if (!isParentAccepted && isGroupAccepted) { - return ValidationResult.error( - CoreErrors.requests.decideValidation.itemAcceptedButParentNotAccepted("The RequestItemGroup was accepted, but the parent was not accepted.") - ); - } - - if (isParentAccepted && requestItemGroup.mustBeAccepted && !isGroupAccepted) { return ValidationResult.error( - CoreErrors.requests.decideValidation.mustBeAcceptedItemNotAccepted( - "The RequestItemGroup is flagged as 'mustBeAccepted', but it was not accepted. Please accept all 'mustBeAccepted' items in this group." - ) + CoreErrors.requests.decideValidation.invalidNumberOfItems("The number of items in the RequestItemGroup and the ResponseItemGroup do not match.") ); } - const validationResults = requestItemGroup.items.map((requestItem, index) => this.checkItem(requestItem, responseItemGroup.items[index], isGroupAccepted)); + const validationResults = requestItemGroup.items.map((requestItem, index) => this.checkItem(requestItem, responseItemGroup.items[index], isRequestAccepted)); return ValidationResult.fromItems(validationResults); } } diff --git a/packages/consumption/test/modules/requests/DecideRequestParamsValidator.test.ts b/packages/consumption/test/modules/requests/DecideRequestParametersValidator.test.ts similarity index 80% rename from packages/consumption/test/modules/requests/DecideRequestParamsValidator.test.ts rename to packages/consumption/test/modules/requests/DecideRequestParametersValidator.test.ts index d6f333413..d8c0e039a 100644 --- a/packages/consumption/test/modules/requests/DecideRequestParamsValidator.test.ts +++ b/packages/consumption/test/modules/requests/DecideRequestParametersValidator.test.ts @@ -39,7 +39,7 @@ describe("DecideRequestParametersValidator", function () { const successParams: TestParam[] = [ { - description: "(1) success: accept Request with one RequestItem and accept the item", + description: "(1) success: accept Request with one RequestItem and accept the RequestItem", input: { request: TestObjectFactory.createRequestWithOneItem(), response: { @@ -50,7 +50,7 @@ describe("DecideRequestParametersValidator", function () { } }, { - description: "(2) success: accept Request with RequestItemGroup and accept the item", + description: "(2) success: accept Request with RequestItemGroup and accept the contained RequestItem", input: { request: TestObjectFactory.createRequestWithOneItemGroup(), response: { @@ -61,7 +61,7 @@ describe("DecideRequestParametersValidator", function () { } }, { - description: "(3) success: accept Request with one RequestItem and reject the item", + description: "(3) success: accept Request with one RequestItem and reject the RequestItem", input: { request: TestObjectFactory.createRequestWithOneItem(), response: { @@ -72,7 +72,7 @@ describe("DecideRequestParametersValidator", function () { } }, { - description: "(4) success: accept Request with RequestItemGroup and reject the item", + description: "(4) success: accept Request with RequestItemGroup and reject the contained RequestItem", input: { request: TestObjectFactory.createRequestWithOneItemGroup(), response: { @@ -83,29 +83,7 @@ describe("DecideRequestParametersValidator", function () { } }, { - description: "(5) success: group must not be accepted, item must be accepted; reject item", - input: { - request: Request.from({ - items: [ - RequestItemGroup.from({ - mustBeAccepted: false, - items: [TestRequestItem.from({ mustBeAccepted: true })] - }) - ] - }), - response: { - accept: true, - items: [ - { - items: [{ accept: false }] - } - ], - requestId - } - } - }, - { - description: "(6) success: accept a Request without accepting any item (no items mustBeAccepted)", + description: "(5) success: accept a Request without accepting any RequestItem (no RequestItems mustBeAccepted)", input: { request: Request.from({ items: [TestRequestItem.from({ mustBeAccepted: false })] @@ -118,13 +96,12 @@ describe("DecideRequestParametersValidator", function () { } }, { - description: "(7) success: items that must not be accepted in a group are rejected", + description: "(6) success: RequestItems that must not be accepted in a RequestItemGroup are rejected", input: { request: Request.from({ items: [ RequestItemGroup.from({ - items: [TestRequestItem.from({ mustBeAccepted: false }), TestRequestItem.from({ mustBeAccepted: true })], - mustBeAccepted: false + items: [TestRequestItem.from({ mustBeAccepted: false }), TestRequestItem.from({ mustBeAccepted: true })] }) ] }), @@ -143,7 +120,7 @@ describe("DecideRequestParametersValidator", function () { const errorParams: TestParam[] = [ { - description: "(1) error: Request with two items is answered with one item", + description: "(1) error: Request with two RequestItems is answered with one item", input: { request: TestObjectFactory.createRequestWithTwoItems(), response: { @@ -154,11 +131,11 @@ describe("DecideRequestParametersValidator", function () { }, expectedError: { code: "error.consumption.requests.decide.validation.invalidNumberOfItems", - message: "Number of items in Request and Response do not match" + message: "The number of items in the Request and the Response do not match." } }, { - description: "(2) error: Request with one item is answered with two items", + description: "(2) error: Request with one RequestItem is answered with two items", input: { request: TestObjectFactory.createRequestWithOneItem(), response: { @@ -169,7 +146,7 @@ describe("DecideRequestParametersValidator", function () { }, expectedError: { code: "error.consumption.requests.decide.validation.invalidNumberOfItems", - message: "Number of items in Request and Response do not match" + message: "The number of items in the Request and the Response do not match." } }, { @@ -221,7 +198,7 @@ describe("DecideRequestParametersValidator", function () { expectedError: { indexPath: [0], code: "error.consumption.requests.decide.validation.invalidNumberOfItems", - message: "Number of items in RequestItemGroup and ResponseItemGroup do not match" + message: "The number of items in the RequestItemGroup and the ResponseItemGroup do not match." } }, { @@ -241,19 +218,29 @@ describe("DecideRequestParametersValidator", function () { } }, { - description: "(7) error: item in a RequestItemGroup that must be accepted was rejected", + description: "(7) error: RequestItem contained within a RequestItemGroup that must be accepted was rejected", input: { - request: TestObjectFactory.createRequestWithOneItemGroup(undefined, true), + request: Request.from({ + items: [ + RequestItemGroup.from({ + items: [TestRequestItem.from({ mustBeAccepted: true }), TestRequestItem.from({ mustBeAccepted: true })] + }) + ] + }), response: { accept: true, - items: [{ items: [{ accept: false }] }], + items: [ + { + items: [{ accept: true }, { accept: false }] + } + ], requestId } }, expectedError: { - indexPath: [0], + indexPath: [0, 1], code: "error.consumption.requests.decide.validation.mustBeAcceptedItemNotAccepted", - message: "The RequestItemGroup is flagged as 'mustBeAccepted', but it was not accepted. Please accept all 'mustBeAccepted' items in this group." + message: "The RequestItem is flagged as 'mustBeAccepted', but it was not accepted." } }, { @@ -268,12 +255,12 @@ describe("DecideRequestParametersValidator", function () { }, expectedError: { indexPath: [0], - code: "error.consumption.requests.decide.validation.itemAcceptedButParentNotAccepted", - message: "The RequestItem was accepted, but the parent was not accepted." + code: "error.consumption.requests.decide.validation.itemAcceptedButRequestNotAccepted", + message: "The RequestItem was accepted, but the Request was not accepted." } }, { - description: "(9) error: when the Request is rejected no RequestItemGroup may be accepted", + description: "(9) error: when the Request is rejected no RequestItem contained within a RequestItemGroup may be accepted", input: { request: TestObjectFactory.createRequestWithOneItemGroup(), response: { @@ -284,35 +271,8 @@ describe("DecideRequestParametersValidator", function () { }, expectedError: { indexPath: [0], - code: "error.consumption.requests.decide.validation.itemAcceptedButParentNotAccepted", - message: "The RequestItemGroup was accepted, but the parent was not accepted." - } - }, - { - description: "(10) error: accepting a group but not accepting all 'mustBeAccepted' items in the group", - input: { - request: Request.from({ - items: [ - RequestItemGroup.from({ - items: [TestRequestItem.from({ mustBeAccepted: true }), TestRequestItem.from({ mustBeAccepted: true })], - mustBeAccepted: false - }) - ] - }), - response: { - accept: true, - items: [ - { - items: [{ accept: true }, { accept: false }] - } - ], - requestId - } - }, - expectedError: { - indexPath: [0, 1], - code: "error.consumption.requests.decide.validation.mustBeAcceptedItemNotAccepted", - message: "The RequestItem is flagged as 'mustBeAccepted', but it was not accepted." + code: "inheritedFromItem", + message: "Some child items have errors." } } ]; diff --git a/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts b/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts index b190d89fa..88e384068 100644 --- a/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts +++ b/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts @@ -10,7 +10,7 @@ import { IncomingRequestStatusChangedEvent, LocalRequestStatus } from "../../../src"; -import { loggerFactory, TestUtil } from "../../core/TestUtil"; +import { TestUtil, loggerFactory } from "../../core/TestUtil"; import { RequestsGiven, RequestsTestsContext, RequestsThen, RequestsWhen } from "./RequestsIntegrationTest"; import { TestObjectFactory } from "./testHelpers/TestObjectFactory"; import { ITestRequestItem, TestRequestItem } from "./testHelpers/TestRequestItem"; @@ -133,7 +133,6 @@ describe("IncomingRequestsController", function () { items: [ { "@type": "RequestItemGroup", - mustBeAccepted: false, items: [ { "@type": "TestRequestItem", @@ -261,7 +260,6 @@ describe("IncomingRequestsController", function () { items: [ { "@type": "RequestItemGroup", - mustBeAccepted: false, items: [ { "@type": "TestRequestItem", @@ -318,7 +316,6 @@ describe("IncomingRequestsController", function () { mustBeAccepted: false }), RequestItemGroup.from({ - mustBeAccepted: false, items: [ TestRequestItem.from({ mustBeAccepted: false, @@ -452,7 +449,6 @@ describe("IncomingRequestsController", function () { items: [ { "@type": "RequestItemGroup", - mustBeAccepted: false, items: [ { "@type": "TestRequestItem", @@ -507,7 +503,6 @@ describe("IncomingRequestsController", function () { mustBeAccepted: false }), RequestItemGroup.from({ - mustBeAccepted: false, items: [ TestRequestItem.from({ mustBeAccepted: false, diff --git a/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts b/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts index 7adc93db0..82e853704 100644 --- a/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts +++ b/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts @@ -117,7 +117,6 @@ describe("OutgoingRequestsController", function () { items: [ { "@type": "RequestItemGroup", - mustBeAccepted: false, items: [ { "@type": "TestRequestItem", @@ -173,7 +172,6 @@ describe("OutgoingRequestsController", function () { mustBeAccepted: false }), RequestItemGroup.from({ - mustBeAccepted: false, items: [ TestRequestItem.from({ mustBeAccepted: false, @@ -473,7 +471,6 @@ describe("OutgoingRequestsController", function () { } as ITestRequestItem, { "@type": "RequestItemGroup", - mustBeAccepted: false, items: [ { "@type": "TestRequestItem", @@ -543,7 +540,6 @@ describe("OutgoingRequestsController", function () { items: [ { "@type": "RequestItemGroup", - mustBeAccepted: false, items: [ { "@type": "TestRequestItem", diff --git a/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts b/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts index dbfae9130..80e208251 100644 --- a/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts +++ b/packages/consumption/test/modules/requests/RequestsIntegrationTest.ts @@ -193,7 +193,6 @@ export class RequestsGiven { } }), RequestItemGroup.from({ - mustBeAccepted: false, metadata: { groupMetaKey: "groupMetaValue" }, @@ -721,7 +720,6 @@ export class RequestsWhen { content: { items: [ RequestItemGroup.from({ - mustBeAccepted: false, items: [ DeleteAttributeRequestItem.from({ attributeId: sOwnSharedIdentityAttribute1.id.toString(), diff --git a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts index a5c936bd9..728a934be 100644 --- a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts +++ b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts @@ -223,8 +223,7 @@ export class TestObjectFactory { return Request.from({ items: [ RequestItemGroup.from({ - items: [TestRequestItem.from({ mustBeAccepted })], - mustBeAccepted: mustBeAccepted + items: [TestRequestItem.from({ mustBeAccepted })] }) ], ...properties diff --git a/packages/consumption/test/modules/requests/testHelpers/TestRequestItem.ts b/packages/consumption/test/modules/requests/testHelpers/TestRequestItem.ts index 5b83ab0d5..b67837188 100644 --- a/packages/consumption/test/modules/requests/testHelpers/TestRequestItem.ts +++ b/packages/consumption/test/modules/requests/testHelpers/TestRequestItem.ts @@ -1,5 +1,14 @@ import { serialize, type, validate } from "@js-soft/ts-serval"; -import { IRequestItem, RequestItem } from "@nmshd/content"; +import { IRequestItem, RequestItem, RequestItemJSON } from "@nmshd/content"; + +export interface TestRequestItemJSON extends RequestItemJSON { + shouldFailAtCanAccept?: true; + shouldFailAtCanReject?: true; + shouldFailAtCanCreateOutgoingRequestItem?: true; + shouldFailAtCanApplyIncomingResponseItem?: true; + shouldThrowOnAccept?: true; + shouldThrowOnReject?: true; +} export interface ITestRequestItem extends IRequestItem { shouldFailAtCanAccept?: true; diff --git a/packages/content/package.json b/packages/content/package.json index b838cb6a7..35dbbae5b 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.4", + "version": "5.0.0-alpha.5", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/src/requests/RequestItem.ts b/packages/content/src/requests/RequestItem.ts index dc71ca65f..1f04ea89b 100644 --- a/packages/content/src/requests/RequestItem.ts +++ b/packages/content/src/requests/RequestItem.ts @@ -44,19 +44,14 @@ export interface RequestItemJSON extends ContentJSON { /** * This property can be used to add some arbitrary metadata to this item. The content * of this property will be copied into the response on the side of the recipient, so - * the sender can use it to identify the group content as they receive the response. + * the sender can use it to identify the item as they receive the response. */ metadata?: object; /** - * If set to `true`, the recipient has to accept this group if he wants to accept the + * If set to `true`, the recipient has to accept this item if they want to accept the * Request. - * If set to `false`, the recipient can decide whether he wants to accept it or not. - * - * Caution: this setting does not take effect in case it is inside of a - * {@link RequestItemGroupJSON RequestItemGroup}, which is not accepted by the recipient, - * since a {@link RequestItemJSON RequestItem} can only be accepted if the parent group - * is accepted as well. + * If set to `false`, the recipient can decide whether they want to accept it or not. */ mustBeAccepted: boolean; @@ -93,19 +88,14 @@ export interface IRequestItem extends ISerializable { /** * This property can be used to add some arbitrary metadata to this item. The content * of this property will be copied into the response on the side of the recipient, so - * the sender can use it to identify the group content as they receive the response. + * the sender can use it to identify the item as they receive the response. */ metadata?: object; /** - * If set to `true`, the recipient has to accept this group if he wants to accept the + * If set to `true`, the recipient has to accept this item if they want to accept the * Request. - * If set to `false`, the recipient can decide whether he wants to accept it or not. - * - * Caution: this setting does not take effect in case it is inside of a - * {@link RequestItemGroup RequestItemGroup}, which is not accepted by the recipient, - * since a {@link RequestItem RequestItem} can only be accepted if the parent group - * is accepted as well. + * If set to `false`, the recipient can decide whether they want to accept it or not. */ mustBeAccepted: boolean; @@ -165,3 +155,18 @@ export type RequestItemDerivations = | AuthenticationRequestItem | FreeTextRequestItem | RegisterAttributeListenerRequestItem; + +export function isRequestItemDerivation(input: any): input is RequestItemDerivations { + return ( + input["@type"] === "RequestItem" || + input["@type"] === "CreateAttributeRequestItem" || + input["@type"] === "DeleteAttributeRequestItem" || + input["@type"] === "ShareAttributeRequestItem" || + input["@type"] === "ProposeAttributeRequestItem" || + input["@type"] === "ReadAttributeRequestItem" || + input["@type"] === "ConsentRequestItem" || + input["@type"] === "AuthenticationRequestItem" || + input["@type"] === "FreeTextRequestItem" || + input["@type"] === "RegisterAttributeListenerRequestItem" + ); +} diff --git a/packages/content/src/requests/RequestItemGroup.ts b/packages/content/src/requests/RequestItemGroup.ts index 09863f90d..f1aee395b 100644 --- a/packages/content/src/requests/RequestItemGroup.ts +++ b/packages/content/src/requests/RequestItemGroup.ts @@ -1,17 +1,10 @@ -import { ISerializable, Serializable, serialize, type, validate, ValidationError } from "@js-soft/ts-serval"; -import { nameof } from "ts-simple-nameof"; +import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; import { ContentJSON } from "../ContentJSON"; import { IRequestItemDerivations, RequestItemDerivations, RequestItemJSONDerivations } from "./RequestItem"; /** * A RequestItemGroup can be used to group one or more RequestItems. This is useful - * if you want to - * * make sure that the items in the group can only be accepted together - * - * Example: when sending a `CreateRelationshipAttributeRequestItem` **and** a `ShareAttributeRequestItem` in a single - * Request where the latter one targets an attribute created by the first one, it it should be impossible to - * reject the first item, while accepting the second one. - * * visually group items on the UI and give the a common title/description + * if you want to visually group RequestItems on the UI and give them a common `title` or `description`. */ export interface RequestItemGroupJSON extends ContentJSON { "@type": "RequestItemGroup"; @@ -26,13 +19,6 @@ export interface RequestItemGroupJSON extends ContentJSON { */ description?: string; - /** - * If set to `true`, the recipient has to accept this group if he wants to accept the - * Request. - * If set to `false`, the recipient can decide whether he wants to accept it or not. - */ - mustBeAccepted: boolean; - /** * This property can be used to add some arbitrary metadata to this group. The content * of this property will be copied into the response on the side of the recipient, so @@ -48,13 +34,7 @@ export interface RequestItemGroupJSON extends ContentJSON { /** * A RequestItemGroup can be used to group one or more RequestItems. This is useful - * if you want to - * * make sure that the items in the group can only be accepted together - * - * Example: when sending a `CreateRelationshipAttributeRequestItem` **and** a `ShareAttributeRequestItem` in a single - * Request where the latter one targets an attribute created by the first one, it it should be impossible to - * reject the first item, while accepting the second one. - * * visually group items on the UI and give the a common title/description + * if you want to visually group RequestItems on the UI and give them a common `title` or `description`. */ export interface IRequestItemGroup extends ISerializable { /** @@ -67,13 +47,6 @@ export interface IRequestItemGroup extends ISerializable { */ description?: string; - /** - * If set to `true`, the recipient has to accept this group if he wants to accept the - * Request. - * If set to `false`, the recipient can decide whether he wants to accept it or not. - */ - mustBeAccepted: boolean; - /** * This property can be used to add some arbitrary metadata to this group. The content * of this property will be copied into the response on the side of the recipient, so @@ -97,10 +70,6 @@ export class RequestItemGroup extends Serializable { @validate({ nullable: true, max: 500 }) public description?: string; - @serialize() - @validate() - public mustBeAccepted: boolean; - @serialize() @validate({ customValidator: (v) => (v.length < 1 ? "may not be empty" : undefined) }) public items: RequestItemDerivations[]; @@ -113,20 +82,6 @@ export class RequestItemGroup extends Serializable { return this.fromAny(value); } - protected static override postFrom(value: T): T { - if (!(value instanceof RequestItemGroup)) throw new Error("this should never happen"); - - if (value.mustBeAccepted && value.items.every((item) => !item.mustBeAccepted)) { - throw new ValidationError( - RequestItemGroup.name, - nameof((x) => x.mustBeAccepted), - `${nameof((x) => x.mustBeAccepted)} can only be true if at least one item is flagged as ${nameof((x) => x.mustBeAccepted)}` - ); - } - - return value; - } - public override toJSON(verbose?: boolean | undefined, serializeAsString?: boolean | undefined): RequestItemGroupJSON { return super.toJSON(verbose, serializeAsString) as RequestItemGroupJSON; } diff --git a/packages/content/test/requests/Request.test.ts b/packages/content/test/requests/Request.test.ts index 88f967c72..1934d5798 100644 --- a/packages/content/test/requests/Request.test.ts +++ b/packages/content/test/requests/Request.test.ts @@ -24,7 +24,6 @@ describe("Request", function () { } as TestRequestItemJSON, { "@type": "RequestItemGroup", - mustBeAccepted: true, items: [ { "@type": "TestRequestItem", @@ -60,7 +59,6 @@ describe("Request", function () { } as ITestRequestItem, { "@type": "RequestItemGroup", - mustBeAccepted: true, items: [ { "@type": "TestRequestItem", @@ -102,7 +100,6 @@ describe("Request", function () { } as TestRequestItemJSON, { "@type": "RequestItemGroup", - mustBeAccepted: true, title: "item group - title", description: "item group - description", metadata: { @@ -147,7 +144,6 @@ describe("Request", function () { items: [ { "@type": "RequestItemGroup", - mustBeAccepted: true, items: [] } as RequestItemGroupJSON ] @@ -168,37 +164,4 @@ describe("Request", function () { await expectThrowsAsync(() => Serializable.fromUnknown(requestJSON), "TestRequestItem.mustBeAccepted*Value is not defined"); }); - - test("should validate the RequestItemGroups mustBeAccepted flag inside a Request", async function () { - const requestJSON = { - "@type": "Request", - items: [ - { - "@type": "RequestItemGroup", - mustBeAccepted: true, - items: [ - { - "@type": "TestRequestItem", - mustBeAccepted: false - } - ] - } - ] - } as RequestJSON; - - await expectThrowsAsync(() => Serializable.fromUnknown(requestJSON), "mustBeAccepted can only be true if at least one item is flagged as mustBeAccepted"); - }); -}); - -describe("RequestItemGroup", function () { - test("should validate the RequestItemGroups mustBeAccepted flag", async function () { - await expectThrowsAsync( - () => - RequestItemGroup.from({ - mustBeAccepted: true, - items: [TestRequestItem.fromAny({ mustBeAccepted: false })] - }), - "mustBeAccepted can only be true if at least one item is flagged as mustBeAccepted" - ); - }); }); diff --git a/packages/runtime/package.json b/packages/runtime/package.json index db8f7acfc..76834674f 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.4", + "version": "5.0.0-alpha.5", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.4", - "@nmshd/content": "5.0.0-alpha.4", + "@nmshd/consumption": "5.0.0-alpha.5", + "@nmshd/content": "5.0.0-alpha.5", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.4", + "@nmshd/transport": "5.0.0-alpha.5", "ajv": "^8.16.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/dataViews/DataViewExpander.ts b/packages/runtime/src/dataViews/DataViewExpander.ts index ea4e1f500..7656c9461 100644 --- a/packages/runtime/src/dataViews/DataViewExpander.ts +++ b/packages/runtime/src/dataViews/DataViewExpander.ts @@ -48,7 +48,8 @@ import { SurnameJSON, ThirdPartyRelationshipAttributeQueryJSON, ValueHints, - ValueHintsJSON + ValueHintsJSON, + isRequestItemDerivation } from "@nmshd/content"; import { CoreAddress, CoreId, IdentityController, Relationship, RelationshipStatus } from "@nmshd/transport"; import _ from "lodash"; @@ -285,7 +286,7 @@ export class DataViewExpander { wasReadAt: message.wasReadAt }; - if (message.content["@type"] === "Mail" || message.content["@type"] === "RequestMail") { + if (message.content["@type"] === "Mail") { const mailContent = message.content as MailJSON; const to: RecipientDVO[] = mailContent.to.map((value) => addressMap[value]); @@ -344,7 +345,7 @@ export class DataViewExpander { return requestMessageDVO; } - if (message.content["@type"] === "Response") { + if (message.content["@type"] === "ResponseWrapper") { let localRequest: LocalRequestDTO; if (isOwn) { const localRequestsResult = await this.consumption.incomingRequests.getRequests({ @@ -802,10 +803,14 @@ export class DataViewExpander { isDecidable, title: requestGroupOrItem.title, description: requestGroupOrItem.description, - mustBeAccepted: requestGroupOrItem.mustBeAccepted, response: responseGroup }; } + + if (!isRequestItemDerivation(requestGroupOrItem)) { + throw new Error("A derivation of a RequestItem was expected."); + } + return await this.expandRequestItem(requestGroupOrItem, localRequestDTO, responseGroupOrItemDVO as ResponseItemDVO); } diff --git a/packages/runtime/src/dataViews/content/RequestItemDVOs.ts b/packages/runtime/src/dataViews/content/RequestItemDVOs.ts index 868a394ee..c4bf4fed1 100644 --- a/packages/runtime/src/dataViews/content/RequestItemDVOs.ts +++ b/packages/runtime/src/dataViews/content/RequestItemDVOs.ts @@ -9,7 +9,6 @@ export interface RequestItemGroupDVO { title?: string; description?: string; isDecidable: boolean; - mustBeAccepted: boolean; response?: ResponseItemGroupDVO; } diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index 0a826853c..665a3de86 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -241,10 +241,6 @@ export const CanCreateOutgoingRequestRequest: any = { "type": "string", "description": "The human-readable description of this group." }, - "mustBeAccepted": { - "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not." - }, "metadata": { "type": "object", "description": "This property can be used to add some arbitrary metadata to this group. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." @@ -259,11 +255,10 @@ export const CanCreateOutgoingRequestRequest: any = { }, "required": [ "@type", - "items", - "mustBeAccepted" + "items" ], "additionalProperties": false, - "description": "A RequestItemGroup can be used to group one or more RequestItems. This is useful if you want to\n* make sure that the items in the group can only be accepted together\n\n Example: when sending a `CreateRelationshipAttributeRequestItem` **and** a `ShareAttributeRequestItem` in a single Request where the latter one targets an attribute created by the first one, it it should be impossible to reject the first item, while accepting the second one.\n* visually group items on the UI and give the a common title/description" + "description": "A RequestItemGroup can be used to group one or more RequestItems. This is useful if you want to visually group RequestItems on the UI and give them a common `title` or `description`." }, "RequestItemJSONDerivations": { "anyOf": [ @@ -321,11 +316,11 @@ export const CanCreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -361,11 +356,11 @@ export const CanCreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -2322,11 +2317,11 @@ export const CanCreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -2366,11 +2361,11 @@ export const CanCreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -2421,11 +2416,11 @@ export const CanCreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -2709,11 +2704,11 @@ export const CanCreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -2806,11 +2801,11 @@ export const CanCreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -2853,11 +2848,11 @@ export const CanCreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -2893,11 +2888,11 @@ export const CanCreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -2937,11 +2932,11 @@ export const CanCreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -8040,10 +8035,6 @@ export const CreateOutgoingRequestRequest: any = { "type": "string", "description": "The human-readable description of this group." }, - "mustBeAccepted": { - "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not." - }, "metadata": { "type": "object", "description": "This property can be used to add some arbitrary metadata to this group. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." @@ -8058,11 +8049,10 @@ export const CreateOutgoingRequestRequest: any = { }, "required": [ "@type", - "items", - "mustBeAccepted" + "items" ], "additionalProperties": false, - "description": "A RequestItemGroup can be used to group one or more RequestItems. This is useful if you want to\n* make sure that the items in the group can only be accepted together\n\n Example: when sending a `CreateRelationshipAttributeRequestItem` **and** a `ShareAttributeRequestItem` in a single Request where the latter one targets an attribute created by the first one, it it should be impossible to reject the first item, while accepting the second one.\n* visually group items on the UI and give the a common title/description" + "description": "A RequestItemGroup can be used to group one or more RequestItems. This is useful if you want to visually group RequestItems on the UI and give them a common `title` or `description`." }, "RequestItemJSONDerivations": { "anyOf": [ @@ -8120,11 +8110,11 @@ export const CreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -8160,11 +8150,11 @@ export const CreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -10121,11 +10111,11 @@ export const CreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -10165,11 +10155,11 @@ export const CreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -10220,11 +10210,11 @@ export const CreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -10508,11 +10498,11 @@ export const CreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -10605,11 +10595,11 @@ export const CreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -10652,11 +10642,11 @@ export const CreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -10692,11 +10682,11 @@ export const CreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -10736,11 +10726,11 @@ export const CreateOutgoingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -11350,10 +11340,6 @@ export const ReceivedIncomingRequestRequest: any = { "type": "string", "description": "The human-readable description of this group." }, - "mustBeAccepted": { - "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not." - }, "metadata": { "type": "object", "description": "This property can be used to add some arbitrary metadata to this group. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." @@ -11368,11 +11354,10 @@ export const ReceivedIncomingRequestRequest: any = { }, "required": [ "@type", - "items", - "mustBeAccepted" + "items" ], "additionalProperties": false, - "description": "A RequestItemGroup can be used to group one or more RequestItems. This is useful if you want to\n* make sure that the items in the group can only be accepted together\n\n Example: when sending a `CreateRelationshipAttributeRequestItem` **and** a `ShareAttributeRequestItem` in a single Request where the latter one targets an attribute created by the first one, it it should be impossible to reject the first item, while accepting the second one.\n* visually group items on the UI and give the a common title/description" + "description": "A RequestItemGroup can be used to group one or more RequestItems. This is useful if you want to visually group RequestItems on the UI and give them a common `title` or `description`." }, "RequestItemJSONDerivations": { "anyOf": [ @@ -11430,11 +11415,11 @@ export const ReceivedIncomingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -11470,11 +11455,11 @@ export const ReceivedIncomingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -13431,11 +13416,11 @@ export const ReceivedIncomingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -13475,11 +13460,11 @@ export const ReceivedIncomingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -13530,11 +13515,11 @@ export const ReceivedIncomingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -13818,11 +13803,11 @@ export const ReceivedIncomingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -13915,11 +13900,11 @@ export const ReceivedIncomingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -13962,11 +13947,11 @@ export const ReceivedIncomingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -14002,11 +13987,11 @@ export const ReceivedIncomingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -14046,11 +14031,11 @@ export const ReceivedIncomingRequestRequest: any = { }, "metadata": { "type": "object", - "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the item as they receive the response." }, "mustBeAccepted": { "type": "boolean", - "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + "description": "If set to `true`, the recipient has to accept this item if they want to accept the Request. If set to `false`, the recipient can decide whether they want to accept it or not." }, "requireManualDecision": { "type": "boolean", @@ -16174,6 +16159,29 @@ export const DeleteRepositoryAttributeRequest: any = { } } +export const DeleteSharedAttributesForRejectedOrRevokedRelationshipRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/DeleteSharedAttributesForRejectedOrRevokedRelationshipRequest", + "definitions": { + "DeleteSharedAttributesForRejectedOrRevokedRelationshipRequest": { + "type": "object", + "properties": { + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" + } + }, + "required": [ + "relationshipId" + ], + "additionalProperties": false + }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" + } + } +} + export const DeleteThirdPartyOwnedRelationshipAttributeAndNotifyPeerRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/DeleteThirdPartyOwnedRelationshipAttributeAndNotifyPeerRequest", @@ -22635,27 +22643,4 @@ export const LoadPeerTokenRequest: any = { "pattern": "TOK[A-Za-z0-9]{17}" } } -} - -export const DeleteSharedAttributesForRejectedOrRevokedRelationshipRequest: any = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/DeleteSharedAttributesForRejectedOrRevokedRelationshipRequest", - "definitions": { - "DeleteSharedAttributesForRejectedOrRevokedRelationshipRequest": { - "type": "object", - "properties": { - "relationshipId": { - "$ref": "#/definitions/RelationshipIdString" - } - }, - "required": [ - "relationshipId" - ], - "additionalProperties": false - }, - "RelationshipIdString": { - "type": "string", - "pattern": "REL[A-Za-z0-9]{17}" - } - } } \ No newline at end of file diff --git a/packages/runtime/test/consumption/iqlQuery.test.ts b/packages/runtime/test/consumption/iqlQuery.test.ts index 579006877..6fdc27e84 100644 --- a/packages/runtime/test/consumption/iqlQuery.test.ts +++ b/packages/runtime/test/consumption/iqlQuery.test.ts @@ -4,7 +4,7 @@ import { IQLQueryJSON, ReadAttributeRequestItemJSON } from "@nmshd/content"; import { DateTime } from "luxon"; import { ConsumptionServices, CreateOutgoingRequestRequest, LocalAttributeDTO, OutgoingRequestCreatedEvent, OutgoingRequestStatusChangedEvent, TransportServices } from "../../src"; import { IncomingRequestReceivedEvent, IncomingRequestStatusChangedEvent } from "../../src/events"; -import { establishRelationship, exchangeMessageWithRequest, RuntimeServiceProvider, sendMessageWithRequest, TestRuntimeServices } from "../lib"; +import { RuntimeServiceProvider, TestRuntimeServices, establishRelationship, exchangeMessageWithRequest, sendMessageWithRequest } from "../lib"; import { exchangeMessageWithRequestAndRequireManualDecision, exchangeMessageWithRequestAndSendResponse } from "../lib/testUtilsWithInactiveModules"; describe("IQL Query", () => { @@ -110,7 +110,7 @@ describe("IQL Query", () => { expect(sLocalRequest.status).toBe(LocalRequestStatus.Draft); expect(sLocalRequest.content.items).toHaveLength(1); expect(sLocalRequest.content.items[0]["@type"]).toBe("ReadAttributeRequestItem"); - expect(sLocalRequest.content.items[0].mustBeAccepted).toBe(false); + expect((sLocalRequest.content.items[0] as ReadAttributeRequestItemJSON).mustBeAccepted).toBe(false); }); test("sender: send the outgoing IQL Request via Message", async () => { diff --git a/packages/runtime/test/consumption/requests.test.ts b/packages/runtime/test/consumption/requests.test.ts index 5a0476cee..4661dbe27 100644 --- a/packages/runtime/test/consumption/requests.test.ts +++ b/packages/runtime/test/consumption/requests.test.ts @@ -1,5 +1,6 @@ import { EventBus } from "@js-soft/ts-utils"; import { LocalRequestStatus } from "@nmshd/consumption"; +import { TestRequestItemJSON } from "@nmshd/consumption/test/modules/requests/testHelpers/TestRequestItem"; import { CoreDate } from "@nmshd/transport"; import { ConsumptionServices, @@ -11,13 +12,13 @@ import { } from "../../src"; import { IncomingRequestReceivedEvent, IncomingRequestStatusChangedEvent } from "../../src/events"; import { + RuntimeServiceProvider, + TestRuntimeServices, establishRelationship, exchangeMessageWithRequest, exchangeTemplate, - RuntimeServiceProvider, sendMessageWithRequest, - syncUntilHasRelationships, - TestRuntimeServices + syncUntilHasRelationships } from "../lib"; import { exchangeMessageWithRequestAndRequireManualDecision, @@ -96,7 +97,7 @@ describe("Requests", () => { expect(sLocalRequest.status).toBe(LocalRequestStatus.Draft); expect(sLocalRequest.content.items).toHaveLength(1); expect(sLocalRequest.content.items[0]["@type"]).toBe("TestRequestItem"); - expect(sLocalRequest.content.items[0].mustBeAccepted).toBe(false); + expect((sLocalRequest.content.items[0] as TestRequestItemJSON).mustBeAccepted).toBe(false); }); // eslint-disable-next-line jest/expect-expect diff --git a/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts b/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts index 7dc8aaaed..1953a039e 100644 --- a/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts +++ b/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts @@ -70,7 +70,6 @@ describe("RelationshipTemplateDVO", () => { items: [ { "@type": "RequestItemGroup", - mustBeAccepted: true, title: "Templator Attributes", items: [ { @@ -87,7 +86,6 @@ describe("RelationshipTemplateDVO", () => { }, { "@type": "RequestItemGroup", - mustBeAccepted: true, title: "Proposed Attributes", items: [ ProposeAttributeRequestItem.from({ diff --git a/packages/transport/package.json b/packages/transport/package.json index bdd191602..797952375 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.4", + "version": "5.0.0-alpha.5", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { From bac26ed4c6fb575c28cff5043efea08fc878d911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Mon, 8 Jul 2024 08:19:46 +0200 Subject: [PATCH 16/40] chore: properly update lockfile (#186) --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2f29513e8..850684e3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12538,7 +12538,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.4", + "version": "5.0.0-alpha.5", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12552,7 +12552,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.4" + "@nmshd/runtime": "^5.0.0-alpha.5" } }, "packages/consumption": { @@ -12619,7 +12619,7 @@ "@nmshd/consumption": "5.0.0-alpha.5", "@nmshd/content": "5.0.0-alpha.5", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.4", + "@nmshd/transport": "5.0.0-alpha.5", "ajv": "^8.16.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12651,7 +12651,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.4", + "version": "5.0.0-alpha.5", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", From d8f6c3878e63287280d67156244fda05004dd181 Mon Sep 17 00:00:00 2001 From: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:48:40 +0200 Subject: [PATCH 17/40] Feature/Relationship decomposition (#125) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: add decompose to facade * test: add decompose runtime tests (WIP) * test: add decompose runtime tests * refactor: fewer deletion events/name deleteRecipientInMessage * refactor: variable name anonymizedMessage * test: add a non-decomposed control to test * fix: correct reason name to ReactivationRequest * fix: sync relationship in test * chore: update backbone * fix: relationships runtime test * chore: remove reactivation * Revert "chore: remove reactivation" This reverts commit 67d0133bfc2c27ebca137916ec3583a6fab9626c. * fix: remove some more * fix: relationshipsFacade * chore: remove reactivation schemas * test: more demanding address tests * fix: remove enmeshed prefix * refactor: rollback relationships controller changes * feat: re-add relationship termination in relationships controller * chore: remove reactivation core error * test: create pending/terminated relationship in test object factory * test: methods for creating pending/terminated relationships in integration tests * test: add terminated relationship message controller test * refactor: create relationship centrally in outgoing request controller describe blocks * test: add terminated relationship outgoing requests controller test * test: add terminated relationship incoming requests controller tests plus small fixes * refactor: incoming requests controller relationship check returns validation instead of throwing * test: canAccept / canReject return validation result * refactor: test util terminate relationship does not require relationship id * test: terminated relationship challenge test * fix: terminated relationship runtime test checks can decide validation * Revert "chore: remove reactivation schemas" This reverts commit 20c42d091fa8fc3cc28708ee6ef9784beaa47269. * Revert "chore: remove reactivation core error" This reverts commit 421eb79eac1c85c9c7d10c42cd981916ee0a4c8d. * Revert "fix: remove some more" This reverts commit 143c6486d3585482bf8abb9405d4b5caf1b471a6. * Revert "fix: relationshipsFacade" This reverts commit ac6f8fdc426aef5e9438b8ba72ef0c574a48a5ea. * refactor: remove second pending relationship from factory correct the existing relationships * refactor: add/terminate relationship test utils return type * test: revert last relationships test change * refactor: replace noMatchingRelationship requests error * refactor: rename noMatchingRelationship messages error * refactor: error messages * fix: message controller test * fix: error message in message controller test * fix: error message in relationships test * refactor: inline relationship status check * chore: backbone version bump * fix: end2end test relationship adding * test: add reactivation runtime tests * feat: add reactivation/validation to relationships controller * refactor: update not only pending relationship * test: extend End2End test * refactor: remove comments * chore: add new backbone event processors * refactor: remove comments * fix: relationship client method names * refactor: method name completeOperationWithBackboneCall * fix: remove optional return types * chore: sort imports * fix: bump backbone * test: add tests for added relationship validation * chore: upgrade backbone (again) * chore: add decompose to relationship client * fix: remove realm * refactor: remove test skips * fix: End2End relationship tests * chore: remove decompose * fix: wrong ! in relationships controller * test: add another event validation to runtime test * refactor: add ) * test: add toBeSuccessful checks, some fixes * test: add transport test for relationship reactivation request * refactor: name requestRelationshipReactivation * chore: add status checks to relationship use cases * test: check audit log reason instead of new status * refactor: codeshare * chore: upgrade backbone again * refactor: add relationshipId to error messages * refactor: remove duplicate relationship validations * test: remove additional relationship status checks * test: add noRecordFound relationship tests * Revert "refactor: remove duplicate relationship validations" This reverts commit 3d01a429170d59fd573935d7538b710d761ed412. * refactor: remove status checks * test: add relationship tests * fix: errors in tests * fix: End2End test syntax errors * chore: add decomposition to relationship status/audit log types * fix: adapt relationships controller * test: add some decomposition tests * fix: End2End test * refactor: move relationship termination sync in test * fix: delete attributes during decomposition * fix: queries for to be deleted objects * refactor: shorten message controller * fix: relationships test * refactor: separate negative tests * refactor: new events for relationship reactivation * fix: follow-up to new events * refactor: rename relationship tests * fix/refactor: complete relationship decomposed event * refactor: newline * fix: re-add relationship changed event * test: re-add relationship changed events to tests * fix: recipientDTO has optional relationship * chore: delete relationship template during decompose * fix: module import in relationships test * Revert "fix: module import in relationships test" This reverts commit 185d471ba7444f0c416c608590e8a156f90c18b1. * fix: module import in relationships test * feat: add pseudonymization * fix: throw on error * fix: throw on more errors * fix: relationships test for templates * refactor: cleaner pseudonymization * fix: delete relationshipId * chore: backbone version bump * refactor: separate runtime and backbone relationship status * test: skip message tests * fix: relationshipstatus to backbonerelationshipstatus * fix: relattionshipstatus DVE import * fix: relationshipstatus test utils * fix: relationshipstatus tests * refactor: remove messageRecipientDeleted event * chore: backbone bump * fix: renamings, asyncs, no skipped tests * Revert "fix: relationshipstatus tests" This reverts commit 9cdf5ed0ce376edeea5b6b190a9669a0ebc8ecb7. * Revert "fix: relationshipstatus test utils" This reverts commit 30c735434e8e9f5854b53502859d6a3d49f60f54. * Revert "fix: relattionshipstatus DVE import" This reverts commit 8784cb76f6ad9f545b08cc26fca6810f30cfaa8f. * Revert "fix: relationshipstatus to backbonerelationshipstatus" This reverts commit aa0be624edb819ec1e6f9de01139eeb222065de9. * Revert "refactor: separate runtime and backbone relationship status" This reverts commit 1344b0fb1d3a58a7eec70b5d43dbd75c1c0fd1dc. * refactor: remove message content pseudonymization * refactor: namings, imports * test: remove failing pseudonymization tests * fix: apply changed naming * fix: apply changed naming * refactor: move pseudonym to process.env * chore: log instead of error in decompose * refactor: remove awaits * refactor: move decompose into controllers * refactor: notificationsWith -> notificationsExchangedWith * fix: remove await * feat: add CachedMessageRecipient, remove relationshipIds * fix: remove pseudonym * refactor: udpate mapper * refactor: update controller * refactor: naming * chore: remove unused vars declaration * refactor: delete data from all collections * chore: remove comment that is now invalid * chore: simplify query * fix: resolve the peer instead of the recipient * fix: update test with anonymizations * chore: uncomment test * refactor: split decompose and cleanup * chore: delete message with 1 recipient * chore: resolve comments * chore: naming & easier reading * fix: decompose in status deletionProposed possible * fix: better syntax * test: add message controller tests * refactor: cleanup way more stuff / align naming * chore: formatting * chore: formatting imports and stuff * chore: re-add comment * fix: e2e * fix: re-apply message cache updates after deletion * test: move decomposition to extra file * refactor: make behaviour more failure-proof * chore: formatting * fix: async jest * chore: version bumps * chore: build schemas * fix: typo * refactor: un-parallelize deletions * fix: missing await * refactor: use for * chore: naming * fix: update doomed message tests * refactor: update message decrypt logic * fix: remove cleanupping files * chore: update error message --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Julian König --- .dev/compose.backbone.env | 2 +- package-lock.json | 18 +- packages/app-runtime/package.json | 4 +- packages/consumption/package.json | 2 +- .../src/consumption/ConsumptionController.ts | 11 +- .../AttributeListenersController.ts | 9 + .../attributes/AttributesController.ts | 7 + .../notifications/NotificationsController.ts | 9 +- .../incoming/IncomingRequestsController.ts | 7 + .../outgoing/OutgoingRequestsController.ts | 7 + .../modules/settings/SettingsController.ts | 7 + .../attributes/AttributesController.test.ts | 1 + packages/content/package.json | 2 +- .../test/attributes/EMailAddress.test.ts | 1 + .../content/test/attributes/Website.test.ts | 1 + packages/runtime/package.json | 8 +- packages/runtime/src/events/EventProxy.ts | 5 + .../RelationshipDecomposedBySelfEvent.ts | 10 + .../runtime/src/events/transport/index.ts | 1 + .../facades/transport/RelationshipsFacade.ts | 7 + .../src/types/transport/RecipientDTO.ts | 2 +- .../src/types/transport/RelationshipDTO.ts | 6 +- .../runtime/src/useCases/common/Schemas.ts | 23 ++ .../transport/messages/GetMessages.ts | 8 +- .../transport/messages/MessageMapper.ts | 26 +-- .../relationships/DecomposeRelationship.ts | 47 ++++ .../useCases/transport/relationships/index.ts | 1 + .../test/consumption/attributes.test.ts | 1 + .../runtime/test/consumption/requests.test.ts | 17 +- .../test/dataViews/RelationshipDVO.test.ts | 1 + packages/runtime/test/lib/testUtils.ts | 27 ++- packages/runtime/test/transport/files.test.ts | 2 + .../transport/identityDeletionProcess.test.ts | 1 + .../runtime/test/transport/messages.test.ts | 1 + .../test/transport/relationships.test.ts | 212 ++++++++++++++++-- packages/transport/package.json | 2 +- .../RelationshipDecomposedBySelfEvent.ts | 10 + packages/transport/src/events/index.ts | 1 + .../src/modules/accounts/AccountController.ts | 21 +- .../src/modules/files/FileController.ts | 10 +- packages/transport/src/modules/index.ts | 59 ++--- .../src/modules/messages/MessageController.ts | 120 ++++++++-- .../modules/messages/local/CachedMessage.ts | 8 +- .../messages/local/CachedMessageRecipient.ts | 39 ++++ .../src/modules/messages/local/Message.ts | 15 +- .../RelationshipTemplateController.ts | 31 ++- .../RelationshipSecretController.ts | 5 +- .../relationships/RelationshipsController.ts | 32 ++- .../backbone/RelationshipClient.ts | 4 + .../transmission/RelationshipAuditLog.ts | 3 +- .../transmission/RelationshipStatus.ts | 3 +- .../sync/DatawalletModificationsProcessor.ts | 5 +- .../src/modules/sync/SyncController.ts | 3 +- .../src/modules/tokens/TokenController.ts | 18 +- .../test/core/backbone/Authentication.test.ts | 1 + .../transport/test/end2end/End2End.test.ts | 58 ++++- .../transport/test/modules/PublicAPI.test.ts | 2 +- .../modules/messages/MessageContent.test.ts | 2 + .../messages/MessageController.test.ts | 44 +++- .../RelationshipsCustomContent.test.ts | 1 + .../relationships/decomposition.test.ts | 88 ++++++++ .../transport/test/testHelpers/TestUtil.ts | 21 +- .../test/utils/PasswordGenerator.test.ts | 3 + 63 files changed, 925 insertions(+), 178 deletions(-) create mode 100644 packages/runtime/src/events/transport/RelationshipDecomposedBySelfEvent.ts create mode 100644 packages/runtime/src/useCases/transport/relationships/DecomposeRelationship.ts create mode 100644 packages/transport/src/events/RelationshipDecomposedBySelfEvent.ts create mode 100644 packages/transport/src/modules/messages/local/CachedMessageRecipient.ts create mode 100644 packages/transport/test/modules/relationships/decomposition.test.ts diff --git a/.dev/compose.backbone.env b/.dev/compose.backbone.env index 88c9fe832..aa09837c7 100644 --- a/.dev/compose.backbone.env +++ b/.dev/compose.backbone.env @@ -1 +1 @@ -BACKBONE_VERSION=6.1.0 +BACKBONE_VERSION=6.2.0 diff --git a/package-lock.json b/package-lock.json index c2ea7bfb6..c5d5310d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12426,7 +12426,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.5", + "version": "5.0.0-alpha.6", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12440,12 +12440,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.5" + "@nmshd/runtime": "^5.0.0-alpha.6" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.5", + "version": "5.0.0-alpha.6", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12467,7 +12467,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.5", + "version": "5.0.0-alpha.6", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12497,17 +12497,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.5", + "version": "5.0.0-alpha.6", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.5", - "@nmshd/content": "5.0.0-alpha.5", + "@nmshd/consumption": "5.0.0-alpha.6", + "@nmshd/content": "5.0.0-alpha.6", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.5", + "@nmshd/transport": "5.0.0-alpha.6", "ajv": "^8.16.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12539,7 +12539,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.5", + "version": "5.0.0-alpha.6", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index b2a894c70..d43f0d800 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.5", + "version": "5.0.0-alpha.6", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.5" + "@nmshd/runtime": "^5.0.0-alpha.6" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 4f1b04c9f..f99fc2b2a 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.5", + "version": "5.0.0-alpha.6", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/consumption/src/consumption/ConsumptionController.ts b/packages/consumption/src/consumption/ConsumptionController.ts index 42594bfb7..e4683208f 100644 --- a/packages/consumption/src/consumption/ConsumptionController.ts +++ b/packages/consumption/src/consumption/ConsumptionController.ts @@ -13,7 +13,7 @@ import { ShareAttributeRequestItem, ThirdPartyOwnedRelationshipAttributeDeletedByPeerNotificationItem } from "@nmshd/content"; -import { AccountController, Transport } from "@nmshd/transport"; +import { AccountController, CoreAddress, CoreId, Transport } from "@nmshd/transport"; import { AttributeListenersController, AttributesController, @@ -154,4 +154,13 @@ export class ConsumptionController { [ThirdPartyOwnedRelationshipAttributeDeletedByPeerNotificationItem, ThirdPartyOwnedRelationshipAttributeDeletedByPeerNotificationItemProcessor] ]); } + + public async cleanupDataOfDecomposedRelationship(peer: CoreAddress, relationshipId: CoreId): Promise { + await this.attributes.deleteAttributesExchangedWithPeer(peer); + await this.outgoingRequests.deleteRequestsToPeer(peer); + await this.incomingRequests.deleteRequestsFromPeer(peer); + await this.settings.deleteSettingsForRelationship(relationshipId); + await this.attributeListeners.deletePeerAttributeListeners(peer); + await this.notifications.deleteNotificationsExchangedWithPeer(peer); + } } diff --git a/packages/consumption/src/modules/attributeListeners/AttributeListenersController.ts b/packages/consumption/src/modules/attributeListeners/AttributeListenersController.ts index 7636d5e71..7ef0b4740 100644 --- a/packages/consumption/src/modules/attributeListeners/AttributeListenersController.ts +++ b/packages/consumption/src/modules/attributeListeners/AttributeListenersController.ts @@ -53,4 +53,13 @@ export class AttributeListenersController extends ConsumptionBaseController { return listener; } + + public async deletePeerAttributeListeners(peerAddress: CoreAddress): Promise { + const listenerDocs = await this.attributeListeners.find({ peer: peerAddress.toString() }); + const listeners = this.parseArray(listenerDocs, LocalAttributeListener); + + for (const listener of listeners) { + await this.attributeListeners.delete(listener); + } + } } diff --git a/packages/consumption/src/modules/attributes/AttributesController.ts b/packages/consumption/src/modules/attributes/AttributesController.ts index a780954d9..a5c1d4d49 100644 --- a/packages/consumption/src/modules/attributes/AttributesController.ts +++ b/packages/consumption/src/modules/attributes/AttributesController.ts @@ -322,6 +322,13 @@ export class AttributesController extends ConsumptionBaseController { this.eventBus.publish(new AttributeDeletedEvent(this.identity.address.toString(), attribute)); } + public async deleteAttributesExchangedWithPeer(peer: CoreAddress): Promise { + const attributes = await this.getLocalAttributes({ "shareInfo.peer": peer.toString() }); + for (const attribute of attributes) { + await this.deleteAttributeUnsafe(attribute.id); + } + } + private async deleteChildAttributesOfComplexAttribute(complexAttribute: LocalAttribute): Promise { if (!(complexAttribute.content instanceof IdentityAttribute)) { throw new ConsumptionError("Only IdentityAttributes may have child Attributes."); diff --git a/packages/consumption/src/modules/notifications/NotificationsController.ts b/packages/consumption/src/modules/notifications/NotificationsController.ts index c9ed215d2..e303734dd 100644 --- a/packages/consumption/src/modules/notifications/NotificationsController.ts +++ b/packages/consumption/src/modules/notifications/NotificationsController.ts @@ -1,6 +1,6 @@ import { Event, EventBus } from "@js-soft/ts-utils"; import { Notification, NotificationItem } from "@nmshd/content"; -import { CoreId, Message, SynchronizedCollection, CoreErrors as TransportCoreErrors } from "@nmshd/transport"; +import { CoreAddress, CoreId, Message, SynchronizedCollection, CoreErrors as TransportCoreErrors } from "@nmshd/transport"; import { ConsumptionBaseController } from "../../consumption/ConsumptionBaseController"; import { ConsumptionController } from "../../consumption/ConsumptionController"; import { ConsumptionControllerName } from "../../consumption/ConsumptionControllerName"; @@ -156,4 +156,11 @@ export class NotificationsController extends ConsumptionBaseController { return notification; } + + public async deleteNotificationsExchangedWithPeer(peer: CoreAddress): Promise { + const notifications = await this.getNotifications({ peer: peer.toString() }); + for (const notification of notifications) { + await this.localNotifications.delete(notification); + } + } } diff --git a/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts b/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts index 6983edb28..a34a5b7c8 100644 --- a/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts +++ b/packages/consumption/src/modules/requests/incoming/IncomingRequestsController.ts @@ -422,6 +422,13 @@ export class IncomingRequestsController extends ConsumptionBaseController { await this.localRequests.update(requestDoc, request); } + public async deleteRequestsFromPeer(peer: CoreAddress): Promise { + const requests = await this.getIncomingRequests({ peer: peer.toString() }); + for (const request of requests) { + await this.localRequests.delete(request); + } + } + private assertRequestStatus(request: LocalRequest, ...status: LocalRequestStatus[]) { if (!status.includes(request.status)) { throw new ConsumptionError(`Local Request has to be in status '${status.join("/")}'.`); diff --git a/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts b/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts index 71478f68a..253e3cb9e 100644 --- a/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts +++ b/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts @@ -190,6 +190,13 @@ export class OutgoingRequestsController extends ConsumptionBaseController { return request; } + public async deleteRequestsToPeer(peer: CoreAddress): Promise { + const requests = await this.getOutgoingRequests({ peer: peer.toString() }); + for (const request of requests) { + await this.localRequests.delete(request); + } + } + private async _sent(requestId: CoreId, requestSourceObject: Message | RelationshipTemplate): Promise { const request = await this.getOrThrow(requestId); diff --git a/packages/consumption/src/modules/settings/SettingsController.ts b/packages/consumption/src/modules/settings/SettingsController.ts index ae6f0b607..9f3424b17 100644 --- a/packages/consumption/src/modules/settings/SettingsController.ts +++ b/packages/consumption/src/modules/settings/SettingsController.ts @@ -56,4 +56,11 @@ export class SettingsController extends ConsumptionBaseController { public async deleteSetting(setting: Setting): Promise { await this.settings.delete(setting); } + + public async deleteSettingsForRelationship(relationshipId: CoreId): Promise { + const settings = await this.getSettings({ reference: relationshipId.toString(), scope: SettingScope.Relationship }); + for (const setting of settings) { + await this.deleteSetting(setting); + } + } } diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index f59238479..91ec15be8 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -2029,6 +2029,7 @@ describe("AttributesController", function () { successorParams2 )); }); + test("should return all predecessors of a succeeded repository attribute", async function () { const result0 = await consumptionController.attributes.getPredecessorsOfAttribute(repositoryAttributeVersion0.id); expect(result0).toStrictEqual([]); diff --git a/packages/content/package.json b/packages/content/package.json index 35dbbae5b..94485543a 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.5", + "version": "5.0.0-alpha.6", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/test/attributes/EMailAddress.test.ts b/packages/content/test/attributes/EMailAddress.test.ts index ddf22e0bf..659ef24c9 100644 --- a/packages/content/test/attributes/EMailAddress.test.ts +++ b/packages/content/test/attributes/EMailAddress.test.ts @@ -27,6 +27,7 @@ describe("Test invalid EMailAddresses", () => { ) ); }); + test("returns an error when trying to create an Attribute Value Type EMailAddress wich is empty", function () { const invalidEMailAddressCall = () => { EMailAddress.from({ diff --git a/packages/content/test/attributes/Website.test.ts b/packages/content/test/attributes/Website.test.ts index 53fba0c46..a629b836e 100644 --- a/packages/content/test/attributes/Website.test.ts +++ b/packages/content/test/attributes/Website.test.ts @@ -34,6 +34,7 @@ describe("Test invalid URLs", () => { ) ); }); + test("returns an error when trying to create an Attribute Value Type Website wich is empty", function () { const invalidWebsiteCall = () => { Website.from({ diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 76834674f..65818fe21 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.5", + "version": "5.0.0-alpha.6", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.5", - "@nmshd/content": "5.0.0-alpha.5", + "@nmshd/consumption": "5.0.0-alpha.6", + "@nmshd/content": "5.0.0-alpha.6", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.5", + "@nmshd/transport": "5.0.0-alpha.6", "ajv": "^8.16.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/events/EventProxy.ts b/packages/runtime/src/events/EventProxy.ts index 811d866c3..882c4190e 100644 --- a/packages/runtime/src/events/EventProxy.ts +++ b/packages/runtime/src/events/EventProxy.ts @@ -28,6 +28,7 @@ import { MessageWasReadAtChangedEvent, PeerRelationshipTemplateLoadedEvent, RelationshipChangedEvent, + RelationshipDecomposedBySelfEvent, RelationshipReactivationCompletedEvent, RelationshipReactivationRequestedEvent } from "./transport"; @@ -81,6 +82,10 @@ export class EventProxy { this.targetEventBus.publish(new RelationshipReactivationCompletedEvent(event.eventTargetAddress, RelationshipMapper.toRelationshipDTO(event.data))); }); + this.subscribeToSourceEvent(transport.RelationshipDecomposedBySelfEvent, (event) => { + this.targetEventBus.publish(new RelationshipDecomposedBySelfEvent(event.eventTargetAddress, event.data.toString())); + }); + this.subscribeToSourceEvent(transport.IdentityDeletionProcessStatusChangedEvent, (event) => { this.targetEventBus.publish( new IdentityDeletionProcessStatusChangedEvent(event.eventTargetAddress, IdentityDeletionProcessMapper.toIdentityDeletionProcessDTO(event.data)) diff --git a/packages/runtime/src/events/transport/RelationshipDecomposedBySelfEvent.ts b/packages/runtime/src/events/transport/RelationshipDecomposedBySelfEvent.ts new file mode 100644 index 000000000..af2bd4d40 --- /dev/null +++ b/packages/runtime/src/events/transport/RelationshipDecomposedBySelfEvent.ts @@ -0,0 +1,10 @@ +import { RelationshipIdString } from "../../useCases/common"; +import { DataEvent } from "../DataEvent"; + +export class RelationshipDecomposedBySelfEvent extends DataEvent { + public static readonly namespace = "transport.relationshipDecomposedBySelf"; + + public constructor(eventTargetAddress: string, data: RelationshipIdString) { + super(RelationshipDecomposedBySelfEvent.namespace, eventTargetAddress, data); + } +} diff --git a/packages/runtime/src/events/transport/index.ts b/packages/runtime/src/events/transport/index.ts index eaa670f04..553041b4c 100644 --- a/packages/runtime/src/events/transport/index.ts +++ b/packages/runtime/src/events/transport/index.ts @@ -5,5 +5,6 @@ export * from "./MessageSentEvent"; export * from "./MessageWasReadAtChangedEvent"; export * from "./PeerRelationshipTemplateLoadedEvent"; export * from "./RelationshipChangedEvent"; +export * from "./RelationshipDecomposedBySelfEvent"; export * from "./RelationshipReactivationCompletedEvent"; export * from "./RelationshipReactivationRequestedEvent"; diff --git a/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts b/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts index 1b56000bf..f8c45d5c6 100644 --- a/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts +++ b/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts @@ -8,6 +8,8 @@ import { AcceptRelationshipUseCase, CreateRelationshipRequest, CreateRelationshipUseCase, + DecomposeRelationshipRequest, + DecomposeRelationshipUseCase, GetAttributesForRelationshipRequest, GetAttributesForRelationshipResponse, GetAttributesForRelationshipUseCase, @@ -45,6 +47,7 @@ export class RelationshipsFacade { @Inject private readonly acceptRelationshipReactivationUseCase: AcceptRelationshipReactivationUseCase, @Inject private readonly rejectRelationshipReactivationUseCase: RejectRelationshipReactivationUseCase, @Inject private readonly revokeRelationshipReactivationUseCase: RevokeRelationshipReactivationUseCase, + @Inject private readonly decomposeRelationshipUseCase: DecomposeRelationshipUseCase, @Inject private readonly getAttributesForRelationshipUseCase: GetAttributesForRelationshipUseCase ) {} @@ -96,6 +99,10 @@ export class RelationshipsFacade { return await this.revokeRelationshipReactivationUseCase.execute(request); } + public async decomposeRelationship(request: DecomposeRelationshipRequest): Promise> { + return await this.decomposeRelationshipUseCase.execute(request); + } + public async getAttributesForRelationship(request: GetAttributesForRelationshipRequest): Promise> { return await this.getAttributesForRelationshipUseCase.execute(request); } diff --git a/packages/runtime/src/types/transport/RecipientDTO.ts b/packages/runtime/src/types/transport/RecipientDTO.ts index ffbcaa360..abf751580 100644 --- a/packages/runtime/src/types/transport/RecipientDTO.ts +++ b/packages/runtime/src/types/transport/RecipientDTO.ts @@ -2,5 +2,5 @@ export interface RecipientDTO { address: string; receivedAt?: string; receivedByDevice?: string; - relationshipId: string; + relationshipId?: string; } diff --git a/packages/runtime/src/types/transport/RelationshipDTO.ts b/packages/runtime/src/types/transport/RelationshipDTO.ts index 7a05ba159..0bd589a75 100644 --- a/packages/runtime/src/types/transport/RelationshipDTO.ts +++ b/packages/runtime/src/types/transport/RelationshipDTO.ts @@ -6,7 +6,8 @@ export enum RelationshipStatus { Active = "Active", Rejected = "Rejected", Revoked = "Revoked", - Terminated = "Terminated" + Terminated = "Terminated", + DeletionProposed = "DeletionProposed" } export enum RelationshipAuditLogEntryReason { @@ -18,7 +19,8 @@ export enum RelationshipAuditLogEntryReason { ReactivationRequested = "ReactivationRequested", AcceptanceOfReactivation = "AcceptanceOfReactivation", RejectionOfReactivation = "RejectionOfReactivation", - RevocationOfReactivation = "RevocationOfReactivation" + RevocationOfReactivation = "RevocationOfReactivation", + Decomposition = "Decomposition" } export interface RelationshipAuditLogEntryDTO { diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index 665a3de86..975230fcd 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -22087,6 +22087,29 @@ export const CreateRelationshipRequest: any = { } } +export const DecomposeRelationshipRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/DecomposeRelationshipRequest", + "definitions": { + "DecomposeRelationshipRequest": { + "type": "object", + "properties": { + "relationshipId": { + "$ref": "#/definitions/RelationshipIdString" + } + }, + "required": [ + "relationshipId" + ], + "additionalProperties": false + }, + "RelationshipIdString": { + "type": "string", + "pattern": "REL[A-Za-z0-9]{17}" + } + } +} + export const GetAttributesForRelationshipRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/GetAttributesForRelationshipRequest", diff --git a/packages/runtime/src/useCases/transport/messages/GetMessages.ts b/packages/runtime/src/useCases/transport/messages/GetMessages.ts index 51ea228d9..6f1be4b11 100644 --- a/packages/runtime/src/useCases/transport/messages/GetMessages.ts +++ b/packages/runtime/src/useCases/transport/messages/GetMessages.ts @@ -1,6 +1,6 @@ import { QueryTranslator } from "@js-soft/docdb-querytranslator"; import { Result } from "@js-soft/ts-utils"; -import { CachedMessage, Message, MessageController, MessageEnvelopeRecipient } from "@nmshd/transport"; +import { CachedMessage, CachedMessageRecipient, Message, MessageController, MessageEnvelopeRecipient } from "@nmshd/transport"; import { nameof } from "ts-simple-nameof"; import { Inject } from "typescript-ioc"; import { MessageDTO, RecipientDTO } from "../../../types"; @@ -54,6 +54,7 @@ export class GetMessagesUseCase extends UseCase((m) => m.recipients)}.${nameof((r) => r.address)}`]: `${nameof((m) => m.cache)}.${nameof( (m) => m.recipients )}.${nameof((r) => r.address)}`, + [`${nameof((m) => m.recipients)}.${nameof((r) => r.relationshipId)}`]: `${nameof((m) => m.cache)}.${nameof((m) => m.recipients)}.${nameof((m) => m.relationshipId)}`, [`${nameof((m) => m.content)}.@type`]: `${nameof((m) => m.cache)}.${nameof((m) => m.content)}.@type`, [`${nameof((m) => m.content)}.body`]: `${nameof((m) => m.cache)}.${nameof((m) => m.content)}.body`, [`${nameof((m) => m.content)}.subject`]: `${nameof((m) => m.cache)}.${nameof((m) => m.content)}.subject`, @@ -61,11 +62,6 @@ export class GetMessagesUseCase extends UseCase((m) => m.recipients)}.${nameof((r) => r.relationshipId)}`]: (query: any, input: any) => { - query[nameof((m) => m.relationshipIds)] = { - $containsAny: Array.isArray(input) ? input : [input] - }; - }, [nameof((m) => m.attachments)]: (query: any, input: any) => { if (input === "+") { query[`${nameof((m) => m.cache)}.${nameof((m) => m.attachments)}`] = { $not: { $size: 0 } }; diff --git a/packages/runtime/src/useCases/transport/messages/MessageMapper.ts b/packages/runtime/src/useCases/transport/messages/MessageMapper.ts index f749984e5..1395fa9a7 100644 --- a/packages/runtime/src/useCases/transport/messages/MessageMapper.ts +++ b/packages/runtime/src/useCases/transport/messages/MessageMapper.ts @@ -1,6 +1,6 @@ import { CoreBuffer } from "@nmshd/crypto"; -import { CoreId, File, Message, MessageEnvelopeRecipient } from "@nmshd/transport"; -import { MessageDTO, MessageWithAttachmentsDTO, RecipientDTO } from "../../../types"; +import { CachedMessageRecipient, File, Message } from "@nmshd/transport"; +import { MessageDTO, MessageWithAttachmentsDTO } from "../../../types"; import { RuntimeErrors } from "../../common"; import { FileMapper } from "../files/FileMapper"; import { DownloadAttachmentResponse } from "./DownloadAttachment"; @@ -22,13 +22,12 @@ export class MessageMapper { if (!message.cache) { throw RuntimeErrors.general.cacheEmpty(Message, message.id.toString()); } - return { id: message.id.toString(), content: message.cache.content.toJSON(), createdBy: message.cache.createdBy.toString(), createdByDevice: message.cache.createdByDevice.toString(), - recipients: message.cache.recipients.map((r, i) => this.toRecipient(r, message.relationshipIds[i])), + recipients: this.toRecipients(message.cache.recipients), createdAt: message.cache.createdAt.toString(), attachments: attachments.map((f) => FileMapper.toFileDTO(f)), isOwn: message.isOwn, @@ -40,13 +39,12 @@ export class MessageMapper { if (!message.cache) { throw RuntimeErrors.general.cacheEmpty(Message, message.id.toString()); } - return { id: message.id.toString(), content: message.cache.content.toJSON(), createdBy: message.cache.createdBy.toString(), createdByDevice: message.cache.createdByDevice.toString(), - recipients: message.cache.recipients.map((r, i) => this.toRecipient(r, message.relationshipIds[i])), + recipients: this.toRecipients(message.cache.recipients), createdAt: message.cache.createdAt.toString(), attachments: message.cache.attachments.map((a) => a.toString()), isOwn: message.isOwn, @@ -58,12 +56,14 @@ export class MessageMapper { return messages.map((message) => this.toMessageDTO(message)); } - private static toRecipient(recipient: MessageEnvelopeRecipient, relationshipId: CoreId): RecipientDTO { - return { - address: recipient.address.toString(), - receivedAt: recipient.receivedAt?.toString(), - receivedByDevice: recipient.receivedByDevice?.toString(), - relationshipId: relationshipId.toString() - }; + private static toRecipients(recipients: CachedMessageRecipient[]) { + return recipients.map((r) => { + return { + address: r.address.toString(), + receivedAt: r.receivedAt?.toString(), + receivedByDevice: r.receivedByDevice?.toString(), + relationshipId: r.relationshipId?.toString() + }; + }); } } diff --git a/packages/runtime/src/useCases/transport/relationships/DecomposeRelationship.ts b/packages/runtime/src/useCases/transport/relationships/DecomposeRelationship.ts new file mode 100644 index 000000000..8aedf925a --- /dev/null +++ b/packages/runtime/src/useCases/transport/relationships/DecomposeRelationship.ts @@ -0,0 +1,47 @@ +import { ApplicationError, Result } from "@js-soft/ts-utils"; +import { ConsumptionController } from "@nmshd/consumption"; +import { AccountController, CoreId, Relationship, RelationshipsController } from "@nmshd/transport"; +import { Inject } from "typescript-ioc"; +import { RelationshipIdString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common"; + +export interface DecomposeRelationshipRequest { + relationshipId: RelationshipIdString; +} + +class Validator extends SchemaValidator { + public constructor(@Inject schemaRepository: SchemaRepository) { + super(schemaRepository.getSchema("DecomposeRelationshipRequest")); + } +} + +export class DecomposeRelationshipUseCase extends UseCase { + public constructor( + @Inject private readonly accountController: AccountController, + @Inject private readonly consumptionController: ConsumptionController, + @Inject private readonly relationshipsController: RelationshipsController, + @Inject validator: Validator + ) { + super(validator); + } + + protected async executeInternal(request: DecomposeRelationshipRequest): Promise> { + const relationship = await this.relationshipsController.getRelationship(CoreId.from(request.relationshipId)); + if (!relationship) { + return Result.fail(RuntimeErrors.general.recordNotFound(Relationship)); + } + + if (!relationship.cache) { + return Result.fail(RuntimeErrors.general.cacheEmpty(Relationship, relationship.id.toString())); + } + + // backbone call first so nothing is deleted in case it goes wrong + await this.relationshipsController.decompose(relationship.id); + + await this.accountController.cleanupDataOfDecomposedRelationship(relationship); + await this.consumptionController.cleanupDataOfDecomposedRelationship(relationship.peer.address, relationship.id); + + await this.accountController.syncDatawallet(); + + return Result.ok(null); + } +} diff --git a/packages/runtime/src/useCases/transport/relationships/index.ts b/packages/runtime/src/useCases/transport/relationships/index.ts index 0350a03bd..65db2183d 100644 --- a/packages/runtime/src/useCases/transport/relationships/index.ts +++ b/packages/runtime/src/useCases/transport/relationships/index.ts @@ -1,6 +1,7 @@ export * from "./AcceptRelationship"; export * from "./AcceptRelationshipReactivation"; export * from "./CreateRelationship"; +export * from "./DecomposeRelationship"; export * from "./GetAttributesForRelationship"; export * from "./GetRelationship"; export * from "./GetRelationshipByAddress"; diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index 0062b7e38..8220aa098 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -1500,6 +1500,7 @@ describe("Get (shared) versions of attribute", () => { beforeAll(async () => { await setUpIdentityAttributeVersions(); }); + test("should get only latest shared version per peer of a repository attribute", async () => { for (const version of sRepositoryAttributeVersions) { const result1 = await services1.consumption.attributes.getSharedVersionsOfAttribute({ attributeId: version.id }); diff --git a/packages/runtime/test/consumption/requests.test.ts b/packages/runtime/test/consumption/requests.test.ts index 4661dbe27..f07db20b4 100644 --- a/packages/runtime/test/consumption/requests.test.ts +++ b/packages/runtime/test/consumption/requests.test.ts @@ -2,14 +2,7 @@ import { EventBus } from "@js-soft/ts-utils"; import { LocalRequestStatus } from "@nmshd/consumption"; import { TestRequestItemJSON } from "@nmshd/consumption/test/modules/requests/testHelpers/TestRequestItem"; import { CoreDate } from "@nmshd/transport"; -import { - ConsumptionServices, - CreateOutgoingRequestRequest, - OutgoingRequestCreatedEvent, - OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent, - OutgoingRequestStatusChangedEvent, - TransportServices -} from "../../src"; +import { ConsumptionServices, CreateOutgoingRequestRequest, OutgoingRequestCreatedEvent, OutgoingRequestStatusChangedEvent, TransportServices } from "../../src"; import { IncomingRequestReceivedEvent, IncomingRequestStatusChangedEvent } from "../../src/events"; import { RuntimeServiceProvider, @@ -355,7 +348,6 @@ describe("Requests", () => { let sTransportServices: TransportServices; let rTransportServices: TransportServices; let rEventBus: EventBus; - let sEventBus: EventBus; const templateContent = { "@type": "RelationshipTemplateContent", @@ -379,7 +371,6 @@ describe("Requests", () => { rTransportServices = rRuntimeServices.transport; sConsumptionServices = sRuntimeServices.consumption; rConsumptionServices = rRuntimeServices.consumption; - sEventBus = sRuntimeServices.eventBus; rEventBus = rRuntimeServices.eventBus; }, 30000); afterAll(async () => await runtimeServiceProvider.stop()); @@ -579,12 +570,6 @@ describe("Requests", () => { const sRelationship = syncResult[0]; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - let triggeredCompletionEvent: OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent | undefined; - sEventBus.subscribeOnce(OutgoingRequestFromRelationshipCreationCreatedAndCompletedEvent, (event) => { - triggeredCompletionEvent = event; - }); - const completionResult = await sConsumptionServices.outgoingRequests.createAndCompleteFromRelationshipTemplateResponse({ responseSourceId: sRelationship.id, response: sRelationship.creationContent.response, diff --git a/packages/runtime/test/dataViews/RelationshipDVO.test.ts b/packages/runtime/test/dataViews/RelationshipDVO.test.ts index 5a641d429..1c00e4095 100644 --- a/packages/runtime/test/dataViews/RelationshipDVO.test.ts +++ b/packages/runtime/test/dataViews/RelationshipDVO.test.ts @@ -74,6 +74,7 @@ describe("RelationshipDVO", () => { expect(dvo.relationship!.templateId).toBe(dto.template.id); }); + test("check the relationship dvo for the requestor", async () => { const dtos = (await transportServices2.relationships.getRelationships({})).value; const dvos = await expander2.expandRelationshipDTOs(dtos); diff --git a/packages/runtime/test/lib/testUtils.ts b/packages/runtime/test/lib/testUtils.ts index 1abfb9082..1bb6ed0df 100644 --- a/packages/runtime/test/lib/testUtils.ts +++ b/packages/runtime/test/lib/testUtils.ts @@ -18,7 +18,8 @@ import { RequestItemGroupJSON, RequestItemJSONDerivations } from "@nmshd/content"; -import { CoreId } from "@nmshd/transport"; +import { CoreBuffer } from "@nmshd/crypto"; +import { CoreAddress, CoreId, IdentityUtil } from "@nmshd/transport"; import fs from "fs"; import { DateTime } from "luxon"; import { @@ -243,6 +244,23 @@ export async function sendMessage(transportServices: TransportServices, recipien return response.value; } +export async function sendMessageToMultipleRecipients(transportServices: TransportServices, recipients: string[], content?: any, attachments?: string[]): Promise { + const response = await transportServices.messages.sendMessage({ + recipients, + content: content ?? { + "@type": "Mail", + subject: "This is the mail subject", + body: "This is the mail body", + cc: [], + to: recipients + }, + attachments + }); + expect(response).toBeSuccessful(); + + return response.value; +} + export async function sendMessageWithRequest(sender: TestRuntimeServices, recipient: TestRuntimeServices, request: CreateOutgoingRequestRequest): Promise { const createRequestResult = await sender.consumption.outgoingRequests.create(request); expect(createRequestResult).toBeSuccessful(); @@ -680,3 +698,10 @@ export async function waitForEvent( clearTimeout(timeoutId); }); } + +export async function generateAddressPseudonym(backboneBaseUrl: string): Promise { + const pseudoPublicKey = CoreBuffer.fromUtf8("deleted identity"); + const pseudonym = await IdentityUtil.createAddress({ algorithm: 1, publicKey: pseudoPublicKey }, new URL(backboneBaseUrl).hostname); + + return pseudonym; +} diff --git a/packages/runtime/test/transport/files.test.ts b/packages/runtime/test/transport/files.test.ts index aa4742a8e..c306600b1 100644 --- a/packages/runtime/test/transport/files.test.ts +++ b/packages/runtime/test/transport/files.test.ts @@ -82,6 +82,7 @@ describe("File upload", () => { expect(response).toBeAnError("content must be object", "error.runtime.validation.invalidPropertyValue"); }); + test("can upload same file twice", async () => { const request = await makeUploadRequest({ content: await fs.promises.readFile(`${__dirname}/../__assets__/test.txt`) }); @@ -121,6 +122,7 @@ describe("Get file", () => { beforeAll(async () => { file = await uploadFile(transportServices1); }); + test("can get file by id", async () => { const response = await transportServices1.files.getFile({ id: file.id }); diff --git a/packages/runtime/test/transport/identityDeletionProcess.test.ts b/packages/runtime/test/transport/identityDeletionProcess.test.ts index cd5cb5249..446bdf1b1 100644 --- a/packages/runtime/test/transport/identityDeletionProcess.test.ts +++ b/packages/runtime/test/transport/identityDeletionProcess.test.ts @@ -88,6 +88,7 @@ describe("IdentityDeletionProcess", () => { "error.runtime.identityDeletionProcess.activeIdentityDeletionProcessAlreadyExists" ); }); + test("should return an error trying to initiate an IdentityDeletionProcess if there already is one waiting for approval", async function () { await startIdentityDeletionProcessFromBackboneAdminApi(transportService, accountAddress); await transportService.identityDeletionProcesses.initiateIdentityDeletionProcess(); diff --git a/packages/runtime/test/transport/messages.test.ts b/packages/runtime/test/transport/messages.test.ts index 12a99399b..d0234a36c 100644 --- a/packages/runtime/test/transport/messages.test.ts +++ b/packages/runtime/test/transport/messages.test.ts @@ -139,6 +139,7 @@ describe("Message errors", () => { }); requestId = createRequestResult.value.id; }); + test("should throw correct error for empty 'to' in the Message", async () => { const result = await client1.transport.messages.sendMessage({ recipients: [client2.address], diff --git a/packages/runtime/test/transport/relationships.test.ts b/packages/runtime/test/transport/relationships.test.ts index 96ca836f3..6b8c36ffe 100644 --- a/packages/runtime/test/transport/relationships.test.ts +++ b/packages/runtime/test/transport/relationships.test.ts @@ -9,6 +9,7 @@ import { RelationshipAuditLogEntryReason, RelationshipChangedEvent, RelationshipDTO, + RelationshipDecomposedBySelfEvent, RelationshipReactivationCompletedEvent, RelationshipReactivationRequestedEvent, RelationshipStatus @@ -19,12 +20,16 @@ import { TestRuntimeServices, createTemplate, ensureActiveRelationship, + establishRelationship, exchangeMessageWithRequest, exchangeTemplate, executeFullCreateAndShareRelationshipAttributeFlow, executeFullCreateAndShareRepositoryAttributeFlow, executeFullSucceedRepositoryAttributeAndNotifyPeerFlow, + generateAddressPseudonym, getRelationship, + sendAndReceiveNotification, + sendMessageToMultipleRecipients, syncUntilHasMessageWithNotification, syncUntilHasRelationships } from "../lib"; @@ -69,9 +74,11 @@ describe("Create Relationship", () => { test("should not accept relationship sent by yourself", async () => { expect(await services2.transport.relationships.acceptRelationship({ relationshipId })).toBeAnError(/.*/, "error.transport.relationships.operationOnlyAllowedForPeer"); }); + test("should not reject relationship sent by yourself", async () => { expect(await services2.transport.relationships.rejectRelationship({ relationshipId })).toBeAnError(/.*/, "error.transport.relationships.operationOnlyAllowedForPeer"); }); + test("should not revoke relationship sent by yourself", async () => { expect(await services1.transport.relationships.revokeRelationship({ relationshipId })).toBeAnError(/.*/, "error.transport.relationships.operationOnlyAllowedForPeer"); }); @@ -152,32 +159,36 @@ describe("Relationship status validations on active relationship", () => { expect(await services1.transport.relationships.rejectRelationship({ relationshipId })).toBeAnError(/.*/, "error.transport.relationships.wrongRelationshipStatus"); }); - describe("Templator with active IdentityDeletionProcess", () => { - const serviceProvider = new RuntimeServiceProvider(); - let services1: TestRuntimeServices; - let services2: TestRuntimeServices; + test("should not decompose a relationship", async () => { + expect(await services1.transport.relationships.decomposeRelationship({ relationshipId })).toBeAnError(/.*/, "error.transport.relationships.wrongRelationshipStatus"); + }); +}); - beforeAll(async () => { - const runtimeServices = await serviceProvider.launch(2, { enableRequestModule: true, enableDeciderModule: true, enableNotificationModule: true }); - services1 = runtimeServices[0]; - services2 = runtimeServices[1]; - }, 30000); +describe("Templator with active IdentityDeletionProcess", () => { + const serviceProvider = new RuntimeServiceProvider(); + let services1: TestRuntimeServices; + let services2: TestRuntimeServices; - afterAll(() => serviceProvider.stop()); + beforeAll(async () => { + const runtimeServices = await serviceProvider.launch(2, { enableRequestModule: true, enableDeciderModule: true, enableNotificationModule: true }); + services1 = runtimeServices[0]; + services2 = runtimeServices[1]; + }, 30000); - test("returns error if templator has active IdentityDeletionProcess", async () => { - const templateId = (await exchangeTemplate(services1.transport, services2.transport)).id; - await services1.transport.identityDeletionProcesses.initiateIdentityDeletionProcess(); + afterAll(() => serviceProvider.stop()); - const createRelationshipResponse = await services2.transport.relationships.createRelationship({ - templateId: templateId, - creationContent: { a: "b" } - }); - expect(createRelationshipResponse).toBeAnError( - "The Identity who created the RelationshipTemplate is currently in the process of deleting itself. Thus, it is not possible to establish a Relationship to it.", - "error.transport.relationships.activeIdentityDeletionProcessOfOwnerOfRelationshipTemplate" - ); + test("returns error if templator has active IdentityDeletionProcess", async () => { + const templateId = (await exchangeTemplate(services1.transport, services2.transport)).id; + await services1.transport.identityDeletionProcesses.initiateIdentityDeletionProcess(); + + const createRelationshipResponse = await services2.transport.relationships.createRelationship({ + templateId: templateId, + creationContent: { a: "b" } }); + expect(createRelationshipResponse).toBeAnError( + "The Identity who created the RelationshipTemplate is currently in the process of deleting itself. Thus, it is not possible to establish a Relationship to it.", + "error.transport.relationships.activeIdentityDeletionProcessOfOwnerOfRelationshipTemplate" + ); }); }); @@ -658,6 +669,161 @@ describe("RelationshipTermination", () => { }); }); +describe("RelationshipDecomposition", () => { + let services3: TestRuntimeServices; + let relationshipId: string; + let templateId: string; + let relationshipId2: string; + let templateId2: string; + let multipleRecipientsMessageId: string; + + let decompositionResult: Result; + beforeAll(async () => { + const relationship = await ensureActiveRelationship(services1.transport, services2.transport); + relationshipId = relationship.id; + templateId = relationship.template.id; + + await createRelationshipData(services1, services2); + + const runtimeServices = await serviceProvider.launch(1, { enableRequestModule: true, enableDeciderModule: true, enableNotificationModule: true }); + services3 = runtimeServices[0]; + const relationship2 = await establishRelationship(services1.transport, services3.transport); + relationshipId2 = relationship2.id; + templateId2 = relationship2.template.id; + + await createRelationshipData(services1, services3); + multipleRecipientsMessageId = (await sendMessageToMultipleRecipients(services1.transport, [services2.address, services3.address])).id; + + await services1.transport.relationships.terminateRelationship({ relationshipId }); + decompositionResult = await services1.transport.relationships.decomposeRelationship({ relationshipId }); + await services2.eventBus.waitForEvent(RelationshipChangedEvent); + + await services1.transport.relationships.terminateRelationship({ relationshipId: relationshipId2 }); + }); + + test("relationship should be decomposed", async () => { + expect(decompositionResult).toBeSuccessful(); + await expect(services1.eventBus).toHavePublished(RelationshipDecomposedBySelfEvent, (e) => e.data === relationshipId); + + const ownRelationship = await services1.transport.relationships.getRelationship({ id: relationshipId }); + expect(ownRelationship).toBeAnError(/.*/, "error.runtime.recordNotFound"); + + const peerRelationship = (await syncUntilHasRelationships(services2.transport))[0]; + expect(peerRelationship.status).toBe(RelationshipStatus.DeletionProposed); + await expect(services2.eventBus).toHavePublished( + RelationshipChangedEvent, + (e) => e.data.id === relationshipId && e.data.auditLog[e.data.auditLog.length - 1].reason === RelationshipAuditLogEntryReason.Decomposition + ); + }); + + test("relationship template should be deleted", async () => { + const result = await services1.transport.relationshipTemplates.getRelationshipTemplate({ id: templateId }); + expect(result).toBeAnError(/.*/, "error.runtime.recordNotFound"); + + const resultControl = await services1.transport.relationshipTemplates.getRelationshipTemplate({ id: templateId2 }); + expect(resultControl).toBeSuccessful(); + }); + + test("requests should be deleted", async () => { + const outgoingRequests = (await services1.consumption.outgoingRequests.getRequests({ query: { peer: services2.address } })).value; + expect(outgoingRequests).toHaveLength(0); + + const incomingRequests = (await services1.consumption.incomingRequests.getRequests({ query: { peer: services2.address } })).value; + expect(incomingRequests).toHaveLength(0); + + const outgoingRequestsControl = (await services1.consumption.outgoingRequests.getRequests({ query: { peer: services3.address } })).value; + expect(outgoingRequestsControl).not.toHaveLength(0); + + const incomingRequestsControl = (await services1.consumption.incomingRequests.getRequests({ query: { peer: services3.address } })).value; + expect(incomingRequestsControl).not.toHaveLength(0); + }); + + test("attributes should be deleted", async () => { + const ownSharedAttributes = (await services1.consumption.attributes.getOwnSharedAttributes({ peer: services2.address })).value; + expect(ownSharedAttributes).toHaveLength(0); + + const peerSharedAttributes = (await services1.consumption.attributes.getPeerSharedAttributes({ peer: services2.address })).value; + expect(peerSharedAttributes).toHaveLength(0); + + const ownSharedAttributesControl = (await services1.consumption.attributes.getOwnSharedAttributes({ peer: services3.address })).value; + expect(ownSharedAttributesControl).not.toHaveLength(0); + + const peerSharedAttributesControl = (await services1.consumption.attributes.getPeerSharedAttributes({ peer: services3.address })).value; + expect(peerSharedAttributesControl).not.toHaveLength(0); + }); + + test("notifications should be deleted", async () => { + const notifications = (await services1.consumption.notifications.getNotifications({ query: { peer: services2.address } })).value; + expect(notifications).toHaveLength(0); + + const notificationsControl = (await services1.consumption.notifications.getNotifications({ query: { peer: services3.address } })).value; + expect(notificationsControl).not.toHaveLength(0); + }); + + test("messages should be deleted/anonymized", async () => { + const messagesToPeer = (await services1.transport.messages.getMessages({ query: { "recipients.address": services2.address } })).value; + expect(messagesToPeer).toHaveLength(0); + + const messagesFromPeer = (await services1.transport.messages.getMessages({ query: { createdBy: services2.address } })).value; + expect(messagesFromPeer).toHaveLength(0); + + const messagesToControlPeer = (await services1.transport.messages.getMessages({ query: { "recipients.address": services3.address } })).value; + expect(messagesToControlPeer).not.toHaveLength(0); + + const messagesFromControlPeer = (await services1.transport.messages.getMessages({ query: { createdBy: services3.address } })).value; + expect(messagesFromControlPeer).not.toHaveLength(0); + + const addressPseudonym = (await generateAddressPseudonym(process.env.NMSHD_TEST_BASEURL!)).toString(); + const anonymizedMessages = (await services1.transport.messages.getMessages({ query: { "recipients.address": addressPseudonym } })).value; + expect(anonymizedMessages).toHaveLength(1); + + const anonymizedMessage = anonymizedMessages[0]; + expect(anonymizedMessage.id).toBe(multipleRecipientsMessageId); + expect(anonymizedMessage.recipients.map((r) => r.address)).toStrictEqual([addressPseudonym, services3.address]); + }); + + test("messages with multiple recipients should be deleted if all its relationships are decomposed", async () => { + await services1.transport.relationships.decomposeRelationship({ relationshipId: relationshipId2 }); + const messages = (await services1.transport.messages.getMessages({})).value; + expect(messages).toHaveLength(0); + }); + + async function createRelationshipData(services1: TestRuntimeServices, services2: TestRuntimeServices) { + const requestContent = { + content: { + items: [ + { + "@type": "TestRequestItem", + mustBeAccepted: false + } + ] + } + }; + await exchangeMessageWithRequest(services1, services2, { ...requestContent, peer: services2.address }); + await exchangeMessageWithRequest(services2, services1, { ...requestContent, peer: services1.address }); + + await sendAndReceiveNotification(services1.transport, services2.transport, services2.consumption); + + await executeFullCreateAndShareRepositoryAttributeFlow(services1, services2, { + content: { + value: { + "@type": "GivenName", + value: "Own name" + } + } + }); + + await executeFullCreateAndShareRepositoryAttributeFlow(services2, services1, { + content: { + value: { + "@type": "GivenName", + value: "Own name" + } + } + }); + } +}); + describe("Relationship existence check", () => { const fakeRelationshipId = "REL00000000000000000"; @@ -692,4 +858,8 @@ describe("Relationship existence check", () => { test("should not revoke a relationship reactivation", async function () { expect(await services1.transport.relationships.revokeRelationshipReactivation({ relationshipId: fakeRelationshipId })).toBeAnError(/.*/, "error.runtime.recordNotFound"); }); + + test("should not decompose a relationship", async function () { + expect(await services1.transport.relationships.decomposeRelationship({ relationshipId: fakeRelationshipId })).toBeAnError(/.*/, "error.runtime.recordNotFound"); + }); }); diff --git a/packages/transport/package.json b/packages/transport/package.json index 797952375..680bb4107 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.5", + "version": "5.0.0-alpha.6", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/transport/src/events/RelationshipDecomposedBySelfEvent.ts b/packages/transport/src/events/RelationshipDecomposedBySelfEvent.ts new file mode 100644 index 000000000..7321284e1 --- /dev/null +++ b/packages/transport/src/events/RelationshipDecomposedBySelfEvent.ts @@ -0,0 +1,10 @@ +import { CoreId } from "../core"; +import { TransportDataEvent } from "./TransportDataEvent"; + +export class RelationshipDecomposedBySelfEvent extends TransportDataEvent { + public static readonly namespace = "transport.relationshipDecomposedBySelf"; + + public constructor(eventTargetAddress: string, relationshipId: CoreId) { + super(RelationshipDecomposedBySelfEvent.namespace, eventTargetAddress, relationshipId); + } +} diff --git a/packages/transport/src/events/index.ts b/packages/transport/src/events/index.ts index 893681fd9..2a7df2149 100644 --- a/packages/transport/src/events/index.ts +++ b/packages/transport/src/events/index.ts @@ -5,6 +5,7 @@ export * from "./MessageSentEvent"; export * from "./MessageWasReadAtChangedEvent"; export * from "./PeerRelationshipTemplateLoadedEvent"; export * from "./RelationshipChangedEvent"; +export * from "./RelationshipDecomposedBySelfEvent"; export * from "./RelationshipReactivationCompletedEvent"; export * from "./RelationshipReactivationRequestedEvent"; export * from "./TransportDataEvent"; diff --git a/packages/transport/src/modules/accounts/AccountController.ts b/packages/transport/src/modules/accounts/AccountController.ts index 2f2a5cc6c..e8ed8a85d 100644 --- a/packages/transport/src/modules/accounts/AccountController.ts +++ b/packages/transport/src/modules/accounts/AccountController.ts @@ -12,30 +12,31 @@ import { CertificateController } from "../certificates/CertificateController"; import { CertificateIssuer } from "../certificates/CertificateIssuer"; import { CertificateValidator } from "../certificates/CertificateValidator"; import { ChallengeController } from "../challenges/ChallengeController"; -import { BackbonePutDevicesPushNotificationRequest, DeviceAuthClient } from "../devices/backbone/DeviceAuthClient"; -import { DeviceClient } from "../devices/backbone/DeviceClient"; import { DeviceController } from "../devices/DeviceController"; -import { DevicesController } from "../devices/DevicesController"; import { DeviceSecretType } from "../devices/DeviceSecretController"; +import { DevicesController } from "../devices/DevicesController"; +import { BackbonePutDevicesPushNotificationRequest, DeviceAuthClient } from "../devices/backbone/DeviceAuthClient"; +import { DeviceClient } from "../devices/backbone/DeviceClient"; import { Device, DeviceInfo, DeviceType } from "../devices/local/Device"; import { DeviceSecretCredentials } from "../devices/local/DeviceSecretCredentials"; import { DeviceSharedSecret } from "../devices/transmission/DeviceSharedSecret"; import { FileController } from "../files/FileController"; import { MessageController } from "../messages/MessageController"; -import { RelationshipsController } from "../relationships/RelationshipsController"; -import { RelationshipSecretController } from "../relationships/RelationshipSecretController"; import { RelationshipTemplateController } from "../relationshipTemplates/RelationshipTemplateController"; +import { RelationshipSecretController } from "../relationships/RelationshipSecretController"; +import { RelationshipsController } from "../relationships/RelationshipsController"; +import { Relationship } from "../relationships/local/Relationship"; import { SecretController } from "../secrets/SecretController"; import { ChangedItems } from "../sync/ChangedItems"; import { SyncProgressCallback, SyncProgressReporter } from "../sync/SyncCallback"; import { SyncController } from "../sync/SyncController"; import { SynchronizedCollection } from "../sync/SynchronizedCollection"; import { TokenController } from "../tokens/TokenController"; -import { IdentityClient } from "./backbone/IdentityClient"; -import { Identity } from "./data/Identity"; import { IdentityController } from "./IdentityController"; import { IdentityDeletionProcessController } from "./IdentityDeletionProcessController"; import { IdentityUtil } from "./IdentityUtil"; +import { IdentityClient } from "./backbone/IdentityClient"; +import { Identity } from "./data/Identity"; export class AccountController { private readonly _authenticator: AbstractAuthenticator; @@ -430,4 +431,10 @@ export class AccountController { return new SynchronizedCollection(collection, this.config.supportedDatawalletVersion, this.unpushedDatawalletModifications); } + + public async cleanupDataOfDecomposedRelationship(relationship: Relationship): Promise { + await this.messages.cleanupMessagesOfDecomposedRelationship(relationship); + await this.relationshipTemplates.cleanupTemplatesOfDecomposedRelationship(relationship); + await this.tokens.cleanupTokensOfDecomposedRelationship(relationship.peer.address); + } } diff --git a/packages/transport/src/modules/files/FileController.ts b/packages/transport/src/modules/files/FileController.ts index 720cbbc8a..090b9f5dc 100644 --- a/packages/transport/src/modules/files/FileController.ts +++ b/packages/transport/src/modules/files/FileController.ts @@ -48,12 +48,20 @@ export class FileController extends TransportController { const decryptionPromises = backboneFiles.map(async (f) => { const fileDoc = await this.files.read(f.id); + if (!fileDoc) { + this._log.error( + `File '${f.id}' not found in local database and the cache fetching was therefore skipped. This should not happen and might be a bug in the application logic.` + ); + return; + } + const file = File.from(fileDoc); return { id: CoreId.from(f.id), cache: await this.decryptFile(f, file.secretKey) }; }); - return await Promise.all(decryptionPromises); + const caches = await Promise.all(decryptionPromises); + return caches.filter((c) => c !== undefined); } public async updateCache(ids: string[]): Promise { diff --git a/packages/transport/src/modules/index.ts b/packages/transport/src/modules/index.ts index 24952d583..b85ffc311 100644 --- a/packages/transport/src/modules/index.ts +++ b/packages/transport/src/modules/index.ts @@ -1,12 +1,12 @@ export * from "./accounts/AccountController"; +export * from "./accounts/IdentityController"; +export * from "./accounts/IdentityDeletionProcessController"; +export * from "./accounts/IdentityUtil"; export * from "./accounts/backbone/IdentityClient"; export * from "./accounts/backbone/IdentityDeletionProcessClient"; export * from "./accounts/data/Identity"; export * from "./accounts/data/IdentityDeletionProcess"; export * from "./accounts/data/IdentityDeletionProcessStatus"; -export * from "./accounts/IdentityController"; -export * from "./accounts/IdentityDeletionProcessController"; -export * from "./accounts/IdentityUtil"; export * from "./certificates/CertificateController"; export * from "./certificates/CertificateIssuer"; export * from "./certificates/CertificateValidator"; @@ -24,42 +24,56 @@ export * from "./certificates/data/items/CertificatePrivateAttributeItem"; export * from "./certificates/data/items/CertificatePrivateAttributeItemSource"; export * from "./certificates/data/items/CertificatePublicAttributeItem"; export * from "./certificates/data/items/CertificateRoleItem"; +export * from "./challenges/ChallengeController"; export * from "./challenges/backbone/ChallengeAuthClient"; export * from "./challenges/backbone/ChallengeClient"; -export * from "./challenges/ChallengeController"; export * from "./challenges/data/Challenge"; export * from "./challenges/data/ChallengeSigned"; +export * from "./devices/DeviceController"; +export * from "./devices/DeviceSecretController"; +export * from "./devices/DevicesController"; export * from "./devices/backbone/BackbonePostDevices"; export * from "./devices/backbone/DeviceAuthClient"; export * from "./devices/backbone/DeviceClient"; -export * from "./devices/DeviceController"; -export * from "./devices/DevicesController"; -export * from "./devices/DeviceSecretController"; export * from "./devices/local/Device"; export * from "./devices/local/DeviceSecretCredentials"; export * from "./devices/local/SendDeviceParameters"; export * from "./devices/transmission/DeviceSharedSecret"; +export * from "./files/FileController"; export * from "./files/backbone/BackboneGetFiles"; export * from "./files/backbone/BackbonePostFiles"; export * from "./files/backbone/FileClient"; -export * from "./files/FileController"; export * from "./files/local/CachedFile"; export * from "./files/local/File"; export * from "./files/local/SendFileParameters"; export * from "./files/transmission/FileMetadata"; export * from "./files/transmission/FileReference"; +export * from "./messages/MessageController"; export * from "./messages/backbone/BackboneGetMessages"; export * from "./messages/backbone/BackbonePostMessages"; export * from "./messages/backbone/MessageClient"; export * from "./messages/local/CachedMessage"; +export * from "./messages/local/CachedMessageRecipient"; export * from "./messages/local/Message"; export * from "./messages/local/SendMessageParameters"; -export * from "./messages/MessageController"; export * from "./messages/transmission/MessageContentWrapper"; export * from "./messages/transmission/MessageEnvelope"; export * from "./messages/transmission/MessageEnvelopeRecipient"; export * from "./messages/transmission/MessageSignature"; export * from "./messages/transmission/MessageSigned"; +export * from "./relationshipTemplates/RelationshipTemplateController"; +export * from "./relationshipTemplates/backbone/BackboneGetRelationshipTemplates"; +export * from "./relationshipTemplates/backbone/BackbonePostRelationshipTemplates"; +export * from "./relationshipTemplates/backbone/RelationshipTemplateClient"; +export * from "./relationshipTemplates/local/CachedRelationshipTemplate"; +export * from "./relationshipTemplates/local/RelationshipTemplate"; +export * from "./relationshipTemplates/local/SendRelationshipTemplateParameters"; +export * from "./relationshipTemplates/transmission/RelationshipTemplateContentWrapper"; +export * from "./relationshipTemplates/transmission/RelationshipTemplatePublicKey"; +export * from "./relationshipTemplates/transmission/RelationshipTemplateReference"; +export * from "./relationshipTemplates/transmission/RelationshipTemplateSigned"; +export * from "./relationships/RelationshipSecretController"; +export * from "./relationships/RelationshipsController"; export * from "./relationships/backbone/BackboneGetRelationships"; export * from "./relationships/backbone/BackbonePostRelationship"; export * from "./relationships/backbone/RelationshipClient"; @@ -68,8 +82,6 @@ export * from "./relationships/local/Relationship"; export * from "./relationships/local/RelationshipAuditLog"; export * from "./relationships/local/RelationshipAuditLogEntry"; export * from "./relationships/local/SendRelationshipParameters"; -export * from "./relationships/RelationshipsController"; -export * from "./relationships/RelationshipSecretController"; export * from "./relationships/transmission/RelationshipAuditLog"; export * from "./relationships/transmission/RelationshipStatus"; export * from "./relationships/transmission/requests/RelationshipCreationContentCipher"; @@ -78,20 +90,14 @@ export * from "./relationships/transmission/requests/RelationshipCreationContent export * from "./relationships/transmission/responses/RelationshipCreationResponseContentCipher"; export * from "./relationships/transmission/responses/RelationshipCreationResponseContentSigned"; export * from "./relationships/transmission/responses/RelationshipCreationResponseContentWrapper"; -export * from "./relationshipTemplates/backbone/BackboneGetRelationshipTemplates"; -export * from "./relationshipTemplates/backbone/BackbonePostRelationshipTemplates"; -export * from "./relationshipTemplates/backbone/RelationshipTemplateClient"; -export * from "./relationshipTemplates/local/CachedRelationshipTemplate"; -export * from "./relationshipTemplates/local/RelationshipTemplate"; -export * from "./relationshipTemplates/local/SendRelationshipTemplateParameters"; -export * from "./relationshipTemplates/RelationshipTemplateController"; -export * from "./relationshipTemplates/transmission/RelationshipTemplateContentWrapper"; -export * from "./relationshipTemplates/transmission/RelationshipTemplatePublicKey"; -export * from "./relationshipTemplates/transmission/RelationshipTemplateReference"; -export * from "./relationshipTemplates/transmission/RelationshipTemplateSigned"; +export * from "./secrets/SecretController"; export * from "./secrets/data/SecretContainerCipher"; export * from "./secrets/data/SecretContainerPlain"; -export * from "./secrets/SecretController"; +export * from "./sync/ChangedItems"; +export * from "./sync/DatawalletModificationsProcessor"; +export { SyncProgressCallback as SyncPercentageCallback, SyncStep } from "./sync/SyncCallback"; +export * from "./sync/SyncController"; +export * from "./sync/SynchronizedCollection"; export * from "./sync/backbone/BackboneDatawalletModification"; export * from "./sync/backbone/BackboneExternalEvent"; export * from "./sync/backbone/CreateDatawalletModifications"; @@ -100,21 +106,16 @@ export * from "./sync/backbone/GetDatawallet"; export * from "./sync/backbone/GetDatawalletModifications"; export * from "./sync/backbone/StartSyncRun"; export * from "./sync/backbone/SyncClient"; -export * from "./sync/ChangedItems"; export * from "./sync/data/ExternalEvent"; -export * from "./sync/DatawalletModificationsProcessor"; export * from "./sync/local/DatawalletModification"; -export { SyncProgressCallback as SyncPercentageCallback, SyncStep } from "./sync/SyncCallback"; -export * from "./sync/SyncController"; -export * from "./sync/SynchronizedCollection"; export * from "./tokens/AnonymousTokenController"; +export * from "./tokens/TokenController"; export * from "./tokens/backbone/BackboneGetTokens"; export * from "./tokens/backbone/BackbonePostTokens"; export * from "./tokens/backbone/TokenClient"; export * from "./tokens/local/CachedToken"; export * from "./tokens/local/SendTokenParameters"; export * from "./tokens/local/Token"; -export * from "./tokens/TokenController"; export * from "./tokens/transmission/TokenContentDeviceSharedSecret"; export * from "./tokens/transmission/TokenContentFile"; export * from "./tokens/transmission/TokenContentRelationshipTemplate"; diff --git a/packages/transport/src/modules/messages/MessageController.ts b/packages/transport/src/modules/messages/MessageController.ts index 3f06df771..afed3bb42 100644 --- a/packages/transport/src/modules/messages/MessageController.ts +++ b/packages/transport/src/modules/messages/MessageController.ts @@ -2,21 +2,24 @@ import { ISerializable } from "@js-soft/ts-serval"; import { log } from "@js-soft/ts-utils"; import { CoreBuffer, CryptoCipher, CryptoSecretKey } from "@nmshd/crypto"; import { nameof } from "ts-simple-nameof"; -import { CoreAddress, CoreCrypto, CoreDate, CoreErrors, CoreId, ICoreAddress, TransportError } from "../../core"; +import { CoreAddress, CoreCrypto, CoreDate, CoreErrors, CoreId, ICoreAddress, ICoreId, TransportError } from "../../core"; import { DbCollectionName } from "../../core/DbCollectionName"; import { ControllerName, TransportController } from "../../core/TransportController"; import { MessageSentEvent, MessageWasReadAtChangedEvent } from "../../events"; import { AccountController } from "../accounts/AccountController"; +import { IdentityUtil } from "../accounts/IdentityUtil"; import { File } from "../files/local/File"; import { FileReference } from "../files/transmission/FileReference"; import { RelationshipSecretController } from "../relationships/RelationshipSecretController"; import { RelationshipsController } from "../relationships/RelationshipsController"; import { Relationship } from "../relationships/local/Relationship"; +import { RelationshipStatus } from "../relationships/transmission/RelationshipStatus"; import { SynchronizedCollection } from "../sync/SynchronizedCollection"; import { BackboneGetMessagesResponse } from "./backbone/BackboneGetMessages"; import { BackbonePostMessagesRecipientRequest } from "./backbone/BackbonePostMessages"; import { MessageClient } from "./backbone/MessageClient"; import { CachedMessage } from "./local/CachedMessage"; +import { CachedMessageRecipient } from "./local/CachedMessageRecipient"; import { Message } from "./local/Message"; import { ISendMessageParameters, SendMessageParameters } from "./local/SendMessageParameters"; import { MessageContentWrapper } from "./transmission/MessageContentWrapper"; @@ -56,12 +59,47 @@ export class MessageController extends TransportController { public async getMessagesByRelationshipId(id: CoreId): Promise { return await this.getMessages({ - [nameof((m) => m.relationshipIds)]: { - $contains: id.toString() - } + [`${nameof((m) => m.cache)}.${nameof((m) => m.recipients)}.${nameof((m) => m.relationshipId)}`]: id.toString() }); } + public async cleanupMessagesOfDecomposedRelationship(relationship: Relationship): Promise { + const messages = await this.getMessagesByRelationshipId(relationship.id); + for (const message of messages) { + await this.cleanupMessageOfDecomposedRelationship(message.id, relationship); + } + } + + private async cleanupMessageOfDecomposedRelationship(messageId: CoreId, relationship: Relationship): Promise { + const messageDoc = await this.messages.read(messageId.toString()); + const message = Message.from(messageDoc); + + // a received message only has one recipient (yourself) so it can be deleted without further looking into the recipients + // also if the message is not own, it can be deleted when there is only one recipient + if (!message.isOwn || message.cache!.recipients.length === 1) { + await this.messages.delete(message); + return; + } + + const recipient = message.cache!.recipients.find((r) => r.relationshipId?.equals(relationship.id)); + if (!recipient) { + this.log.warn(`Recipient not found in message ${message.id.toString()}`); + return; + } + + const pseudonym = await IdentityUtil.createAddress({ algorithm: 1, publicKey: CoreBuffer.fromUtf8("deleted identity") }, new URL(this.parent.config.baseUrl).hostname); + + recipient.address = pseudonym; + recipient.relationshipId = undefined; + + if (message.cache!.recipients.every((r) => r.address.equals(pseudonym))) { + await this.messages.delete(message); + return; + } + + await this.messages.update(messageDoc, message); + } + @log() public async getMessagesByAddress(address: CoreAddress): Promise { const relationship = await this.parent.relationships.getActiveRelationshipToIdentity(address); @@ -102,6 +140,13 @@ export class MessageController extends TransportController { const decryptionPromises = backboneMessages.map(async (m) => { const messageDoc = await this.messages.read(m.id); + if (!messageDoc) { + this._log.error( + `Message '${m.id}' not found in local database and the cache fetching was therefore skipped. This should not happen and might be a bug in the application logic.` + ); + return; + } + const message = Message.from(messageDoc); const envelope = this.getEnvelopeFromBackboneGetMessagesResponse(m); @@ -109,7 +154,8 @@ export class MessageController extends TransportController { return { id: CoreId.from(m.id), cache: cachedMessage }; }); - return await Promise.all(decryptionPromises); + const caches = await Promise.all(decryptionPromises); + return caches.filter((c) => c !== undefined); } @log() @@ -154,8 +200,7 @@ export class MessageController extends TransportController { const message = Message.from({ id: envelope.id, isOwn: false, - secretKey: messageKey, - relationshipIds: [relationship.id] + secretKey: messageKey }); message.setCache(cachedMessage); await this.messages.create(message); @@ -282,7 +327,10 @@ export class MessageController extends TransportController { const plaintextBuffer = CoreBuffer.fromUtf8(serializedPlaintext); const messageSignatures: MessageSignature[] = []; - const relationshipIds = []; + + // a object of address to relationshipId + const addressToRelationshipId: Record = {}; + for (const recipient of parsedParams.recipients) { const relationship = await this.relationships.getActiveRelationshipToIdentity(CoreAddress.from(recipient)); if (!relationship) { @@ -295,7 +343,8 @@ export class MessageController extends TransportController { signature: signature }); messageSignatures.push(messageSignature); - relationshipIds.push(relationship.id); + + addressToRelationshipId[recipient.toString()] = relationship.id; } const signed = MessageSigned.from({ @@ -326,12 +375,22 @@ export class MessageController extends TransportController { }) ).value; + const recipients = envelopeRecipients.map((r) => + CachedMessageRecipient.from({ + address: r.address, + encryptedKey: r.encryptedKey, + receivedAt: r.receivedAt, + receivedByDevice: r.receivedByDevice, + relationshipId: addressToRelationshipId[r.address.toString()] + }) + ); + const cachedMessage = CachedMessage.from({ content: parsedParams.content, createdAt: CoreDate.from(response.createdAt), createdBy: this.parent.identity.identity.address, createdByDevice: this.parent.activeDevice.id, - recipients: envelopeRecipients, + recipients, attachments: publicAttachmentArray, receivedByEveryone: false }); @@ -341,8 +400,7 @@ export class MessageController extends TransportController { secretKey: secret, cache: cachedMessage, cachedAt: CoreDate.utc(), - isOwn: true, - relationshipIds: relationshipIds + isOwn: true }); await this.messages.create(message); @@ -416,6 +474,8 @@ export class MessageController extends TransportController { let plainMessage: MessageContentWrapper; let messageKey: CryptoSecretKey; + const recipients: CachedMessageRecipient[] = []; + let relationship; if (this.parent.identity.isMe(envelope.createdBy)) { if (!secretKey) { @@ -423,6 +483,28 @@ export class MessageController extends TransportController { } messageKey = secretKey; plainMessage = await this.decryptOwnEnvelope(envelope, secretKey); + + const pseudonym = await IdentityUtil.createAddress({ algorithm: 1, publicKey: CoreBuffer.fromUtf8("deleted identity") }, new URL(this.parent.config.baseUrl).hostname); + + for (const recipient of envelope.recipients) { + let relationship = await this.relationships.getRelationshipToIdentity(recipient.address); + if (relationship?.status === RelationshipStatus.Rejected || relationship?.status === RelationshipStatus.Revoked) { + // if the relationship is rejected or revoked, it should be handled like there is no relationship + relationship = undefined; + } + + recipients.push( + CachedMessageRecipient.from({ + // make sure to save the pseudonym instead of the real address if the relationship was removed + // in cases the backbone did not already process the relationship termination + address: relationship ? recipient.address : pseudonym, + encryptedKey: recipient.encryptedKey, + receivedAt: recipient.receivedAt, + receivedByDevice: recipient.receivedByDevice, + relationshipId: relationship?.id + }) + ); + } } else { relationship = await this.relationships.getActiveRelationshipToIdentity(envelope.createdBy); @@ -433,6 +515,18 @@ export class MessageController extends TransportController { const [peerMessage, peerKey] = await this.decryptPeerEnvelope(envelope, relationship); plainMessage = peerMessage; messageKey = peerKey; + + // we don't care about other recipients in the envelope, because we do not need them + const currentIdentityAsRecipient = envelope.recipients.find((r) => this.parent.identity.isMe(r.address))!; + recipients.push( + CachedMessageRecipient.from({ + address: currentIdentityAsRecipient.address, + encryptedKey: currentIdentityAsRecipient.encryptedKey, + receivedAt: currentIdentityAsRecipient.receivedAt, + receivedByDevice: currentIdentityAsRecipient.receivedByDevice, + relationshipId: relationship.id + }) + ); } this.log.trace("Message is valid. Fetching attachments for message..."); @@ -449,7 +543,7 @@ export class MessageController extends TransportController { const cachedMessage = CachedMessage.from({ createdBy: envelope.createdBy, createdByDevice: envelope.createdByDevice, - recipients: envelope.recipients, + recipients, attachments: fileArray, content: plainMessage.content, createdAt: envelope.createdAt, diff --git a/packages/transport/src/modules/messages/local/CachedMessage.ts b/packages/transport/src/modules/messages/local/CachedMessage.ts index b9a94be0d..5c2408a4b 100644 --- a/packages/transport/src/modules/messages/local/CachedMessage.ts +++ b/packages/transport/src/modules/messages/local/CachedMessage.ts @@ -2,13 +2,13 @@ import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; import { CoreAddress, CoreId, CoreSerializable, ICoreAddress, ICoreId, ICoreSerializable } from "../../../core"; import { CoreDate, ICoreDate } from "../../../core/types/CoreDate"; -import { IMessageEnvelopeRecipient, MessageEnvelopeRecipient } from "../transmission/MessageEnvelopeRecipient"; +import { CachedMessageRecipient, ICachedMessageRecipient } from "./CachedMessageRecipient"; export interface ICachedMessage extends ICoreSerializable { createdBy: ICoreAddress; createdByDevice: ICoreId; - recipients: IMessageEnvelopeRecipient[]; + recipients: ICachedMessageRecipient[]; createdAt: ICoreDate; @@ -29,8 +29,8 @@ export class CachedMessage extends CoreSerializable implements ICachedMessage { public createdByDevice: CoreId; @validate() - @serialize({ type: MessageEnvelopeRecipient }) - public recipients: MessageEnvelopeRecipient[]; + @serialize({ type: CachedMessageRecipient }) + public recipients: CachedMessageRecipient[]; @validate() @serialize() diff --git a/packages/transport/src/modules/messages/local/CachedMessageRecipient.ts b/packages/transport/src/modules/messages/local/CachedMessageRecipient.ts new file mode 100644 index 000000000..3acb4f09e --- /dev/null +++ b/packages/transport/src/modules/messages/local/CachedMessageRecipient.ts @@ -0,0 +1,39 @@ +import { serialize, type, validate } from "@js-soft/ts-serval"; +import { CryptoCipher, ICryptoCipher } from "@nmshd/crypto"; +import { CoreDate, CoreId, CoreSerializable, ICoreDate, ICoreId, ICoreSerializable } from "../../../core"; +import { CoreAddress, ICoreAddress } from "../../../core/types/CoreAddress"; + +export interface ICachedMessageRecipient extends ICoreSerializable { + address: ICoreAddress; + encryptedKey: ICryptoCipher; + receivedAt?: ICoreDate; + receivedByDevice?: ICoreId; + relationshipId?: ICoreId; +} + +@type("CachedMessageRecipient") +export class CachedMessageRecipient extends CoreSerializable implements ICachedMessageRecipient { + @validate() + @serialize() + public address: CoreAddress; + + @validate() + @serialize() + public encryptedKey: CryptoCipher; + + @validate({ nullable: true }) + @serialize() + public receivedAt?: CoreDate; + + @validate({ nullable: true }) + @serialize() + public receivedByDevice?: CoreId; + + @validate({ nullable: true }) + @serialize() + public relationshipId?: CoreId; + + public static from(value: ICachedMessageRecipient): CachedMessageRecipient { + return this.fromAny(value); + } +} diff --git a/packages/transport/src/modules/messages/local/Message.ts b/packages/transport/src/modules/messages/local/Message.ts index b51f8992b..797129f7b 100644 --- a/packages/transport/src/modules/messages/local/Message.ts +++ b/packages/transport/src/modules/messages/local/Message.ts @@ -1,7 +1,7 @@ import { serialize, type, validate } from "@js-soft/ts-serval"; import { CryptoSecretKey, ICryptoSecretKey } from "@nmshd/crypto"; import { nameof } from "ts-simple-nameof"; -import { CoreDate, CoreId, CoreSynchronizable, ICoreDate, ICoreId, ICoreSynchronizable } from "../../../core"; +import { CoreDate, CoreSynchronizable, ICoreDate, ICoreSynchronizable } from "../../../core"; import { CachedMessage, ICachedMessage } from "./CachedMessage"; export interface IMessage extends ICoreSynchronizable { @@ -11,19 +11,12 @@ export interface IMessage extends ICoreSynchronizable { cachedAt?: ICoreDate; metadata?: any; metadataModifiedAt?: ICoreDate; - relationshipIds: ICoreId[]; wasReadAt?: ICoreDate; } @type("Message") export class Message extends CoreSynchronizable implements IMessage { - public override readonly technicalProperties = [ - "@type", - "@context", - nameof((r) => r.secretKey), - nameof((r) => r.isOwn), - nameof((r) => r.relationshipIds) - ]; + public override readonly technicalProperties = ["@type", "@context", nameof((r) => r.secretKey), nameof((r) => r.isOwn)]; public override readonly metadataProperties = [nameof((r) => r.metadata), nameof((r) => r.metadataModifiedAt)]; @@ -53,10 +46,6 @@ export class Message extends CoreSynchronizable implements IMessage { @serialize() public metadataModifiedAt?: CoreDate; - @validate() - @serialize({ type: CoreId }) - public relationshipIds: CoreId[]; - @validate({ nullable: true }) @serialize() public wasReadAt?: CoreDate; diff --git a/packages/transport/src/modules/relationshipTemplates/RelationshipTemplateController.ts b/packages/transport/src/modules/relationshipTemplates/RelationshipTemplateController.ts index cf7e660ec..f5d36e5b8 100644 --- a/packages/transport/src/modules/relationshipTemplates/RelationshipTemplateController.ts +++ b/packages/transport/src/modules/relationshipTemplates/RelationshipTemplateController.ts @@ -6,6 +6,7 @@ import { DbCollectionName } from "../../core/DbCollectionName"; import { ControllerName, TransportController } from "../../core/TransportController"; import { PeerRelationshipTemplateLoadedEvent } from "../../events"; import { AccountController } from "../accounts/AccountController"; +import { Relationship } from "../relationships/local/Relationship"; import { RelationshipSecretController } from "../relationships/RelationshipSecretController"; import { SynchronizedCollection } from "../sync/SynchronizedCollection"; import { BackboneGetRelationshipTemplatesResponse } from "./backbone/BackboneGetRelationshipTemplates"; @@ -93,7 +94,9 @@ export class RelationshipTemplateController extends TransportController { } public async deleteRelationshipTemplate(template: RelationshipTemplate): Promise { - await this.client.deleteRelationshipTemplate(template.id.toString()); + const response = await this.client.deleteRelationshipTemplate(template.id.toString()); + if (response.isError) throw response.error; + await this.templates.delete(template); } @@ -123,12 +126,20 @@ export class RelationshipTemplateController extends TransportController { const decryptionPromises = backboneRelationships.map(async (t) => { const templateDoc = await this.templates.read(t.id); + if (!templateDoc) { + this._log.error( + `Template '${t.id}' not found in local database and the cache fetching was therefore skipped. This should not happen and might be a bug in the application logic.` + ); + return; + } + const template = RelationshipTemplate.from(templateDoc); return { id: CoreId.from(t.id), cache: await this.decryptRelationshipTemplate(t, template.secretKey) }; }); - return await Promise.all(decryptionPromises); + const caches = await Promise.all(decryptionPromises); + return caches.filter((c) => c !== undefined); } @log() @@ -246,4 +257,20 @@ export class RelationshipTemplateController extends TransportController { return relationshipTemplate; } + + public async cleanupTemplatesOfDecomposedRelationship(relationship: Relationship): Promise { + const templateOfRelationship = relationship.cache!.template; + + if (!templateOfRelationship.isOwn || templateOfRelationship.cache!.maxNumberOfAllocations === 1) { + await this.templates.delete(templateOfRelationship); + } + + const otherTemplatesOfPeer = await this.getRelationshipTemplates({ + "cache.createdBy": relationship.peer.address.toString() + }); + + for (const template of otherTemplatesOfPeer) { + await this.templates.delete(template); + } + } } diff --git a/packages/transport/src/modules/relationships/RelationshipSecretController.ts b/packages/transport/src/modules/relationships/RelationshipSecretController.ts index 8db400c60..094940416 100644 --- a/packages/transport/src/modules/relationships/RelationshipSecretController.ts +++ b/packages/transport/src/modules/relationships/RelationshipSecretController.ts @@ -17,7 +17,6 @@ import { CoreErrors } from "../../core/CoreErrors"; import { CoreUtil } from "../../core/CoreUtil"; import { TransportIds } from "../../core/TransportIds"; import { AccountController } from "../accounts/AccountController"; -import { Identity } from "../accounts/data/Identity"; import { CachedRelationshipTemplate } from "../relationshipTemplates/local/CachedRelationshipTemplate"; import { RelationshipTemplatePublicKey } from "../relationshipTemplates/transmission/RelationshipTemplatePublicKey"; import { SecretContainerCipher } from "../secrets/data/SecretContainerCipher"; @@ -116,8 +115,8 @@ export class RelationshipSecretController extends SecretController { return container; } - public async deleteSecretForRequest(peerIdentity: Identity): Promise { - const secret = await this.loadActiveSecretByName(`request_to_${peerIdentity.address}`); + public async deleteSecretForRelationship(relationshipSecretId: CoreId): Promise { + const secret = await this.loadActiveSecretByName(relationshipSecretId.toString()); if (!secret) { return false; } diff --git a/packages/transport/src/modules/relationships/RelationshipsController.ts b/packages/transport/src/modules/relationships/RelationshipsController.ts index 04cac1992..d52bee67b 100644 --- a/packages/transport/src/modules/relationships/RelationshipsController.ts +++ b/packages/transport/src/modules/relationships/RelationshipsController.ts @@ -7,7 +7,7 @@ import { CoreErrors } from "../../core/CoreErrors"; import { CoreUtil } from "../../core/CoreUtil"; import { DbCollectionName } from "../../core/DbCollectionName"; import { TransportIds } from "../../core/TransportIds"; -import { RelationshipChangedEvent, RelationshipReactivationCompletedEvent, RelationshipReactivationRequestedEvent } from "../../events"; +import { RelationshipChangedEvent, RelationshipDecomposedBySelfEvent, RelationshipReactivationCompletedEvent, RelationshipReactivationRequestedEvent } from "../../events"; import { AccountController } from "../accounts/AccountController"; import { RelationshipTemplate } from "../relationshipTemplates/local/RelationshipTemplate"; import { SynchronizedCollection } from "../sync/SynchronizedCollection"; @@ -74,6 +74,13 @@ export class RelationshipsController extends TransportController { const decryptionPromises = backboneRelationships.map(async (r) => { const relationshipDoc = await this.relationships.read(r.id); + if (!relationshipDoc) { + this._log.error( + `Relationship '${r.id}' not found in local database and the cache fetching was therefore skipped. This should not happen and might be a bug in the application logic.` + ); + return; + } + const relationship = Relationship.from(relationshipDoc); return { @@ -82,7 +89,8 @@ export class RelationshipsController extends TransportController { }; }); - return await Promise.all(decryptionPromises); + const caches = await Promise.all(decryptionPromises); + return caches.filter((c) => c !== undefined); } @log() @@ -293,6 +301,22 @@ export class RelationshipsController extends TransportController { return await this.completeOperationWithBackboneCall(RelationshipAuditLogEntryReason.AcceptanceOfReactivation, relationshipId); } + public async decompose(relationshipId: CoreId): Promise { + const relationship = await this.getRelationshipWithCache(relationshipId); + this.assertRelationshipStatus(relationship, RelationshipStatus.Terminated, RelationshipStatus.DeletionProposed); + + const result = await this.client.decomposeRelationship(relationshipId.toString()); + if (result.isError) throw result.error; + + const isSecretDeletionSuccessful = await this.secrets.deleteSecretForRelationship(relationship.relationshipSecretId); + if (!isSecretDeletionSuccessful) { + this._log.error("Decomposition failed to delete secrets"); + } + await this.relationships.delete({ id: relationshipId }); + + this.eventBus.publish(new RelationshipDecomposedBySelfEvent(this.parent.identity.address.toString(), relationshipId)); + } + private async getRelationshipWithCache(id: CoreId): Promise { const relationship = await this.getRelationship(id); if (!relationship) throw CoreErrors.general.recordNotFound(Relationship, id.toString()); @@ -302,8 +326,8 @@ export class RelationshipsController extends TransportController { return relationship as Relationship & { cache: CachedRelationship }; } - private assertRelationshipStatus(relationship: Relationship, status: RelationshipStatus) { - if (relationship.status === status) return; + private assertRelationshipStatus(relationship: Relationship, ...status: RelationshipStatus[]) { + if (status.includes(relationship.status)) return; throw CoreErrors.relationships.wrongRelationshipStatus(relationship.id.toString(), relationship.status); } diff --git a/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts b/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts index a05ddda60..5fcfdf3b2 100644 --- a/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts +++ b/packages/transport/src/modules/relationships/backbone/RelationshipClient.ts @@ -42,6 +42,10 @@ export class RelationshipClient extends RESTClientAuthenticate { return await this.put(`/api/v1/Relationships/${relationshipId}/Reactivate/Revoke`, {}); } + public async decomposeRelationship(relationshipId: string): Promise> { + return await this.put(`/api/v1/Relationships/${relationshipId}/Decompose`, {}); + } + public async getRelationships(request?: BackboneGetRelationshipsRequest): Promise>> { return await this.getPaged("/api/v1/Relationships", request); } diff --git a/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts b/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts index 92297e817..b91d8477c 100644 --- a/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts +++ b/packages/transport/src/modules/relationships/transmission/RelationshipAuditLog.ts @@ -20,5 +20,6 @@ export enum RelationshipAuditLogEntryReason { ReactivationRequested = "ReactivationRequested", AcceptanceOfReactivation = "AcceptanceOfReactivation", RejectionOfReactivation = "RejectionOfReactivation", - RevocationOfReactivation = "RevocationOfReactivation" + RevocationOfReactivation = "RevocationOfReactivation", + Decomposition = "Decomposition" } diff --git a/packages/transport/src/modules/relationships/transmission/RelationshipStatus.ts b/packages/transport/src/modules/relationships/transmission/RelationshipStatus.ts index a9a13c363..afc69707d 100644 --- a/packages/transport/src/modules/relationships/transmission/RelationshipStatus.ts +++ b/packages/transport/src/modules/relationships/transmission/RelationshipStatus.ts @@ -3,5 +3,6 @@ export enum RelationshipStatus { Active = "Active", Rejected = "Rejected", Revoked = "Revoked", - Terminated = "Terminated" + Terminated = "Terminated", + DeletionProposed = "DeletionProposed" } diff --git a/packages/transport/src/modules/sync/DatawalletModificationsProcessor.ts b/packages/transport/src/modules/sync/DatawalletModificationsProcessor.ts index 5462a0a7d..64bb0a582 100644 --- a/packages/transport/src/modules/sync/DatawalletModificationsProcessor.ts +++ b/packages/transport/src/modules/sync/DatawalletModificationsProcessor.ts @@ -67,8 +67,8 @@ export class DatawalletModificationsProcessor { public async execute(): Promise { await this.applyCreates(); await this.applyUpdates(); - await this.applyCacheChanges(); await this.applyDeletes(); + await this.applyCacheChanges(); // cache-fills are optimized by the backbone, so it is possible that the processedItemCount is // lower than the total number of items - in this case the 100% callback is triggered here @@ -156,7 +156,8 @@ export class DatawalletModificationsProcessor { this.ensureAllItemsAreCacheable(); - const cacheChangesGroupedByCollection = this.groupCacheChangesByCollection(this.cacheChanges); + const cacheChangesWithoutDeletes = this.cacheChanges.filter((c) => !this.deletes.some((d) => d.objectIdentifier.equals(c.objectIdentifier))); + const cacheChangesGroupedByCollection = this.groupCacheChangesByCollection(cacheChangesWithoutDeletes); const caches = await this.cacheFetcher.fetchCacheFor({ files: cacheChangesGroupedByCollection.fileIds, diff --git a/packages/transport/src/modules/sync/SyncController.ts b/packages/transport/src/modules/sync/SyncController.ts index 33263fbbe..3b6453f25 100644 --- a/packages/transport/src/modules/sync/SyncController.ts +++ b/packages/transport/src/modules/sync/SyncController.ts @@ -461,10 +461,11 @@ export class SyncController extends TransportController { const { backboneModifications, localModificationIds } = await this.prepareLocalDatawalletModificationsForPush(); - await this.client.finalizeExternalEventSync(this.currentSyncRun.id.toString(), { + const response = await this.client.finalizeExternalEventSync(this.currentSyncRun.id.toString(), { datawalletModifications: backboneModifications, externalEventResults: externalEventResults }); + if (response.isError) throw response.error; await this.deleteUnpushedDatawalletModifications(localModificationIds); diff --git a/packages/transport/src/modules/tokens/TokenController.ts b/packages/transport/src/modules/tokens/TokenController.ts index 500b03f9f..84b19d0f1 100644 --- a/packages/transport/src/modules/tokens/TokenController.ts +++ b/packages/transport/src/modules/tokens/TokenController.ts @@ -115,12 +115,20 @@ export class TokenController extends TransportController { const decryptionPromises = backboneTokens.map(async (t) => { const tokenDoc = await this.tokens.read(t.id); + if (!tokenDoc) { + this._log.error( + `Token '${t.id}' not found in local database and the cache fetching was therefore skipped. This should not happen and might be a bug in the application logic.` + ); + return; + } + const token = Token.from(tokenDoc); return { id: CoreId.from(t), cache: await this.decryptToken(t, token.secretKey) }; }); - return await Promise.all(decryptionPromises); + const caches = await Promise.all(decryptionPromises); + return caches.filter((c) => c !== undefined); } @log() @@ -212,4 +220,12 @@ export class TokenController extends TransportController { return token; } + + public async cleanupTokensOfDecomposedRelationship(peer: CoreAddress): Promise { + const tokenDocs = await this.getTokens({ "cache.createdBy": peer.toString() }); + const tokens = this.parseArray(tokenDocs, Token); + for await (const token of tokens) { + await this.tokens.delete(token); + } + } } diff --git a/packages/transport/test/core/backbone/Authentication.test.ts b/packages/transport/test/core/backbone/Authentication.test.ts index 82607f06e..5a2d55df1 100644 --- a/packages/transport/test/core/backbone/Authentication.test.ts +++ b/packages/transport/test/core/backbone/Authentication.test.ts @@ -96,6 +96,7 @@ describe("AuthenticationTest", function () { expect(requests[0].method).toBe("post"); expect(requests[0].url).toMatch(/^\/connect\/token/); }); + test("should throw correct error on authentication issues", async function () { setAuthTokenToExpired(testAccount); diff --git a/packages/transport/test/end2end/End2End.test.ts b/packages/transport/test/end2end/End2End.test.ts index 605ccfcc4..31c153ab5 100644 --- a/packages/transport/test/end2end/End2End.test.ts +++ b/packages/transport/test/end2end/End2End.test.ts @@ -646,12 +646,12 @@ describe("RelationshipTest: Revoke Reactivation", function () { }); }); -describe("RelationshipTest: validations for non-existent record", function () { +describe("RelationshipTest: Decompose", function () { let connection: IDatabaseConnection; let transport: Transport; let from: AccountController; - const fakeRelationshipId = CoreId.from("REL00000000000000000"); + let to: AccountController; beforeAll(async function () { connection = await TestUtil.createDatabaseConnection(); @@ -661,6 +661,52 @@ describe("RelationshipTest: validations for non-existent record", function () { const accounts = await TestUtil.provideAccounts(transport, 2); from = accounts[0]; + to = accounts[1]; + }); + + afterAll(async function () { + await from.close(); + await to.close(); + await connection.close(); + }); + + test("should request decomposing a relationship", async function () { + const relationshipId = (await TestUtil.addRelationship(from, to)).acceptedRelationshipFromSelf.id; + await from.relationships.terminate(relationshipId); + await TestUtil.syncUntilHasRelationships(to); + + await from.relationships.decompose(relationshipId); + const decomposedRelationship = await from.relationships.getRelationship(relationshipId); + expect(decomposedRelationship).toBeUndefined(); + + const syncedRelationshipsPeer = await TestUtil.syncUntilHasRelationships(to); + expect(syncedRelationshipsPeer).toHaveLength(1); + const decomposedRelationshipPeer = syncedRelationshipsPeer[0]; + + expect(decomposedRelationshipPeer.id.toString()).toStrictEqual(relationshipId.toString()); + expect(decomposedRelationshipPeer.status).toStrictEqual(RelationshipStatus.DeletionProposed); + expect(decomposedRelationshipPeer.cache?.auditLog).toHaveLength(4); + expect(decomposedRelationshipPeer.cache!.auditLog[3].reason).toBe(RelationshipAuditLogEntryReason.Decomposition); + + await expect(to.relationships.decompose(relationshipId)).resolves.not.toThrow(); + }); +}); + +describe("RelationshipTest: validations for non-existent record", function () { + let connection: IDatabaseConnection; + + let transport: Transport; + let from: AccountController; + const fakeRelationshipId = CoreId.from("REL00000000000000000"); + + beforeAll(async function () { + connection = await TestUtil.createDatabaseConnection(); + transport = TestUtil.createTransport(connection); + + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 1); + from = accounts[0]; }); afterAll(async function () { @@ -699,6 +745,10 @@ describe("RelationshipTest: validations for non-existent record", function () { test("should not revoke a relationship reactivation", async function () { await expect(from.relationships.revokeReactivation(fakeRelationshipId)).rejects.toThrow("error.transport.recordNotFound"); }); + + test("should not decompose a relationship", async function () { + await expect(from.relationships.decompose(fakeRelationshipId)).rejects.toThrow("error.transport.recordNotFound"); + }); }); describe("RelationshipTest: validations (on terminated relationship)", function () { @@ -892,6 +942,10 @@ describe("RelationshipTest: relationship status validation (on active relationsh await expect(from.relationships.revokeReactivation(relationshipId)).rejects.toThrow("error.transport.relationships.wrongRelationshipStatus"); }); + test("should not decompose a relationship", async function () { + await expect(from.relationships.decompose(relationshipId)).rejects.toThrow("error.transport.relationships.wrongRelationshipStatus"); + }); + test("should not accept a relationship", async function () { await expect(from.relationships.accept(relationshipId)).rejects.toThrow("error.transport.relationships.wrongRelationshipStatus"); }); diff --git a/packages/transport/test/modules/PublicAPI.test.ts b/packages/transport/test/modules/PublicAPI.test.ts index a4c572f41..f05c8ba47 100644 --- a/packages/transport/test/modules/PublicAPI.test.ts +++ b/packages/transport/test/modules/PublicAPI.test.ts @@ -119,7 +119,7 @@ publicFunctions[RelationshipSecretController.name] = [ nameof((r) => r.createTemplatorSecrets), nameof((r) => r.getPublicCreationResponseContentCrypto), nameof((r) => r.convertSecrets), - nameof((r) => r.deleteSecretForRequest), + nameof((r) => r.deleteSecretForRelationship), nameof((r) => r.decryptTemplate), nameof((r) => r.verifyTemplate), nameof((r) => r.encryptCreationContent), diff --git a/packages/transport/test/modules/messages/MessageContent.test.ts b/packages/transport/test/modules/messages/MessageContent.test.ts index 35fe1cc6b..4ac8219f3 100644 --- a/packages/transport/test/modules/messages/MessageContent.test.ts +++ b/packages/transport/test/modules/messages/MessageContent.test.ts @@ -40,6 +40,7 @@ describe("MessageContent", function () { expect(value).toBeInstanceOf(JSONWrapper); await TestUtil.sendMessage(sender, recipient1, value); }); + test("should correctly store the message (sender)", async function () { const messages = await sender.messages.getMessagesByAddress(recipient1.identity.address); expect(messages).toHaveLength(1); @@ -94,6 +95,7 @@ describe("MessageContent", function () { expect(message).toBeDefined(); }); + test("should correctly store the me4ssage (sender)", async function () { const messages = await sender.messages.getMessagesByAddress(recipient1.identity.address); expect(messages).toHaveLength(2); diff --git a/packages/transport/test/modules/messages/MessageController.test.ts b/packages/transport/test/modules/messages/MessageController.test.ts index da0e3958a..ef98544d0 100644 --- a/packages/transport/test/modules/messages/MessageController.test.ts +++ b/packages/transport/test/modules/messages/MessageController.test.ts @@ -9,6 +9,7 @@ describe("MessageController", function () { let sender: AccountController; let recipient: AccountController; + let recipient2: AccountController; let tempId1: CoreId; let tempId2: CoreId; let tempDate: CoreDate; @@ -16,9 +17,9 @@ describe("MessageController", function () { function expectValidMessages(sentMessage: Message, receivedMessage: Message, nowMinusSeconds: CoreDate) { expect(sentMessage.id.toString()).toBe(receivedMessage.id.toString()); - const sentRelIds = sentMessage.relationshipIds.map((id) => id.toString()); - const receivedRelIds = receivedMessage.relationshipIds.map((id) => id.toString()); - expect(sentRelIds.join()).toBe(receivedRelIds.join()); + const sentRelIds = sentMessage.cache!.recipients.map((recipient) => recipient.relationshipId!.toString()); + const receivedRelIds = receivedMessage.cache!.recipients.map((recipient) => recipient.relationshipId?.toString()); + expect(sentRelIds).toContainEqual(receivedRelIds[0]); expect(sentMessage.cache).toBeDefined(); expect(sentMessage.cachedAt?.isSameOrAfter(nowMinusSeconds)).toBe(true); expect(sentMessage.cache?.createdBy.toString()).toBe(sender.identity.address.toString()); @@ -29,7 +30,7 @@ describe("MessageController", function () { expect(receivedMessage.cache?.createdBy.toString()).toBe(sender.identity.address.toString()); expect(receivedMessage.cache?.createdByDevice.toString()).toBe(sender.activeDevice.id.toString()); expect(receivedMessage.cache?.createdAt.isSameOrAfter(nowMinusSeconds)).toBe(true); - expect(sentMessage.cache!.recipients.map((r) => r.toString())).toStrictEqual(receivedMessage.cache!.recipients.map((r) => r.toString())); + expect(sentMessage.cache!.recipients.map((r) => r.toString())).toContainEqual(receivedMessage.cache!.recipients[0].toString()); expect(sentMessage.cache!.attachments.map((r) => r.toString())).toStrictEqual(receivedMessage.cache!.attachments.map((r) => r.toString())); expect(sentMessage.cache!.receivedByEveryone).toBe(receivedMessage.cache!.receivedByEveryone); expect(sentMessage.cache!.content.serialize()).toBe(receivedMessage.cache!.content.serialize()); @@ -41,10 +42,12 @@ describe("MessageController", function () { await transport.init(); - const accounts = await TestUtil.provideAccounts(transport, 2); + const accounts = await TestUtil.provideAccounts(transport, 3); sender = accounts[0]; recipient = accounts[1]; + recipient2 = accounts[2]; const rels = await TestUtil.addRelationship(sender, recipient); + await TestUtil.addRelationship(sender, recipient2); relationshipId = rels.acceptedRelationshipFromSelf.id; }); @@ -94,8 +97,8 @@ describe("MessageController", function () { const relationship = await recipient.relationships.getRelationshipToIdentity(receivedMessage.cache!.createdBy); expectValidMessages(sentMessage, receivedMessage, tempDate); - expect(receivedMessage.relationshipIds[0].toString()).toStrictEqual(relationship!.id.toString()); - expect(sentMessage.relationshipIds[0].toString()).toStrictEqual(relationship!.id.toString()); + expect(receivedMessage.cache!.recipients[0].relationshipId!.toString()).toStrictEqual(relationship!.id.toString()); + expect(sentMessage.cache!.recipients[0].relationshipId!.toString()).toStrictEqual(relationship!.id.toString()); expect(receivedMessage.cache!.recipients[0].receivedByDevice?.toString()).toBe(recipient.activeDevice.id.toString()); }); @@ -180,8 +183,29 @@ describe("MessageController", function () { expect(unreadMessage.wasReadAt).toBeUndefined(); }); - test("should not send a message on a terminated relationship", async function () { - await TestUtil.terminateRelationship(sender, recipient); - await expect(TestUtil.sendMessage(sender, recipient)).rejects.toThrow("error.transport.messages.missingOrInactiveRelationship"); + test("should send and receive a Message (multiple recipients)", async function () { + tempDate = CoreDate.utc().subtract(TestUtil.tempDateThreshold); + const sentMessage = await TestUtil.sendMessage(sender, [recipient, recipient2]); + + const messages = await TestUtil.syncUntilHasMessages(recipient, 1); + const receivedMessage = messages[0]; + + const messages2 = await TestUtil.syncUntilHasMessages(recipient2, 1); + const receivedMessage2 = messages2[0]; + + tempId1 = sentMessage.id; + + expectValidMessages(sentMessage, receivedMessage, tempDate); + expectValidMessages(sentMessage, receivedMessage2, tempDate); + }); + + describe("Relationship Termination", function () { + beforeAll(async function () { + await TestUtil.terminateRelationship(sender, recipient); + }); + + test("should not send a message on a terminated relationship", async function () { + await expect(TestUtil.sendMessage(sender, recipient)).rejects.toThrow("error.transport.messages.missingOrInactiveRelationship"); + }); }); }); diff --git a/packages/transport/test/modules/relationships/RelationshipsCustomContent.test.ts b/packages/transport/test/modules/relationships/RelationshipsCustomContent.test.ts index 356647ade..f88fc574e 100644 --- a/packages/transport/test/modules/relationships/RelationshipsCustomContent.test.ts +++ b/packages/transport/test/modules/relationships/RelationshipsCustomContent.test.ts @@ -28,6 +28,7 @@ describe("Relationships Custom Content", function () { await connection.close(); }); + test("should create a relationship with custom content", async function () { const tokenReference = await TestUtil.sendRelationshipTemplateAndToken(sender); const template = await TestUtil.fetchRelationshipTemplateFromTokenReference(recipient, tokenReference); diff --git a/packages/transport/test/modules/relationships/decomposition.test.ts b/packages/transport/test/modules/relationships/decomposition.test.ts new file mode 100644 index 000000000..e2ffc8883 --- /dev/null +++ b/packages/transport/test/modules/relationships/decomposition.test.ts @@ -0,0 +1,88 @@ +import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; +import { AccountController, CoreId, Transport } from "../../../src"; +import { TestUtil } from "../../testHelpers/TestUtil"; + +describe("Relationship Decomposition", function () { + let connection: IDatabaseConnection; + + let transport: Transport; + + let sender: AccountController; + let recipient1: AccountController; + + let relationship2Id: CoreId; + let recipient2: AccountController; + + beforeAll(async function () { + connection = await TestUtil.createDatabaseConnection(); + transport = TestUtil.createTransport(connection); + + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 3); + sender = accounts[0]; + recipient1 = accounts[1]; + recipient2 = accounts[2]; + + await TestUtil.addRelationship(sender, recipient1); + relationship2Id = (await TestUtil.addRelationship(sender, recipient2)).acceptedRelationshipFromSelf.id; + + await TestUtil.sendMessage(sender, [recipient1, recipient2]); + + await TestUtil.syncUntilHasMessages(recipient1); + await TestUtil.terminateRelationship(sender, recipient1); + await TestUtil.decomposeRelationship(sender, recipient1); + }); + + afterAll(async function () { + await sender.close(); + await recipient1.close(); + await recipient2.close(); + await connection.close(); + }); + + test("messages should be deleted/pseudonymized", async function () { + const messages = await sender.messages.getMessages(); + expect(messages).toHaveLength(1); + + expect(messages[0].cache!.recipients.map((r) => [r.address, r.relationshipId])).toStrictEqual( + expect.arrayContaining([ + [await TestUtil.generateAddressPseudonym(process.env.NMSHD_TEST_BASEURL!), undefined], + [recipient2.identity.address, relationship2Id] + ]) + ); + }); + + test("should sync the pseudonymization", async function () { + const { device1: sender1, device2: sender2 } = await TestUtil.createIdentityWithTwoDevices(connection, { + datawalletEnabled: true + }); + + const accounts = await TestUtil.provideAccounts(transport, 2); + const recipient1 = accounts[0]; + const recipient2 = accounts[1]; + + await TestUtil.addRelationship(sender1, recipient1); + const relationship2Id = (await TestUtil.addRelationship(sender1, recipient2)).acceptedRelationshipPeer.id; + + await sender2.syncDatawallet(); + await TestUtil.sendMessage(sender1, [recipient1, recipient2]); + + await sender1.syncDatawallet(); + await sender2.syncDatawallet(); + + await TestUtil.terminateRelationship(sender1, recipient1); + await TestUtil.decomposeRelationship(sender1, recipient1); + + await sender1.syncDatawallet(); + await sender2.syncDatawallet(); + + const sender2Messages = await sender2.messages.getMessages(); + expect(sender2Messages[0].cache?.recipients.map((r) => [r.address, r.relationshipId])).toStrictEqual( + expect.arrayContaining([ + [await TestUtil.generateAddressPseudonym(process.env.NMSHD_TEST_BASEURL!), undefined], + [recipient2.identity.address, relationship2Id] + ]) + ); + }); +}); diff --git a/packages/transport/test/testHelpers/TestUtil.ts b/packages/transport/test/testHelpers/TestUtil.ts index a1571f0c7..686591405 100644 --- a/packages/transport/test/testHelpers/TestUtil.ts +++ b/packages/transport/test/testHelpers/TestUtil.ts @@ -22,6 +22,7 @@ import { IChangedItems, IConfigOverwrite, IdentityDeletionProcess, + IdentityUtil, ISendFileParameters, Message, Relationship, @@ -395,6 +396,22 @@ export class TestUtil { return { terminatedRelationshipFromSelf, terminatedRelationshipPeer }; } + public static async decomposeRelationship(from: AccountController, to: AccountController): Promise { + const relationship = (await from.relationships.getRelationshipToIdentity(to.identity.address))!; + await from.relationships.decompose(relationship.id); + await from.cleanupDataOfDecomposedRelationship(relationship); + const decomposedRelationshipPeer = (await TestUtil.syncUntil(to, (syncResult) => syncResult.relationships.length > 0)).relationships[0]; + + return decomposedRelationshipPeer; + } + + public static async generateAddressPseudonym(backboneBaseUrl: string): Promise { + const pseudoPublicKey = CoreBuffer.fromUtf8("deleted identity"); + const pseudonym = await IdentityUtil.createAddress({ algorithm: 1, publicKey: pseudoPublicKey }, new URL(backboneBaseUrl).hostname); + + return pseudonym; + } + /** * SyncEvents in the backbone are only eventually consistent. This means that if you send a message now and * get all SyncEvents right after, you cannot rely on getting a NewMessage SyncEvent right away. So instead @@ -528,8 +545,8 @@ export class TestUtil { return template; } - public static async sendMessage(from: AccountController, to: AccountController, content?: Serializable): Promise { - return await this.sendMessagesWithFiles(from, [to], [], content); + public static async sendMessage(from: AccountController, to: AccountController | AccountController[], content?: Serializable): Promise { + return await this.sendMessagesWithFiles(from, Array.isArray(to) ? to : [to], [], content); } public static async sendMessageWithFile(from: AccountController, to: AccountController, file: File, content?: Serializable): Promise { diff --git a/packages/transport/test/utils/PasswordGenerator.test.ts b/packages/transport/test/utils/PasswordGenerator.test.ts index 72c8b3ed7..604917049 100644 --- a/packages/transport/test/utils/PasswordGenerator.test.ts +++ b/packages/transport/test/utils/PasswordGenerator.test.ts @@ -29,12 +29,14 @@ describe("PasswordGeneratorTest", function () { expect(password.length).toBeLessThanOrEqual(12); } }); + test("should return a random password with the correct given fix length", async function () { for (let i = 1; i < 20; i++) { const pass = await PasswordGenerator.createStrongPassword(50, 50); expect(pass).toHaveLength(50); } }); + test("should return a random password with the correct given length interval", async function () { for (let i = 1; i < 20; i++) { const pass = await PasswordGenerator.createStrongPassword(20, 50); @@ -43,6 +45,7 @@ describe("PasswordGeneratorTest", function () { expect(pass.length).toBeLessThanOrEqual(51); } }); + test("should throw an error if minLength is too low", async function () { for (let i = 1; i < 20; i++) { await TestUtil.expectThrowsAsync(PasswordGenerator.createStrongPassword(2, 20), "Minimum password length for a strong password should be 8 characters."); From fe4d27faaa19a4305a1bf9946dede0e6637588ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:07:17 +0200 Subject: [PATCH 18/40] Refactor/remove sync callbacks (#191) * refactor: remove sync callbacks from transport * refactor: remove sync callbacks from runtime * chore: remove export * chore: update schemas * chore: audit fix * chore: audit exclude * chore: remove doomed test --- .ci/runChecks.sh | 2 +- package-lock.json | 5 -- .../facades/transport/AccountFacade.ts | 10 +-- .../runtime/src/useCases/common/Schemas.ts | 22 ----- .../transport/account/SyncDatawallet.ts | 10 +-- .../transport/account/SyncEverything.ts | 14 ++-- .../transport/src/core/ProgressReporter.ts | 42 ---------- packages/transport/src/core/index.ts | 1 - .../src/modules/accounts/AccountController.ts | 11 +-- packages/transport/src/modules/index.ts | 57 +++++++------ .../sync/DatawalletModificationsProcessor.ts | 18 +--- .../src/modules/sync/SyncCallback.ts | 21 ----- .../src/modules/sync/SyncController.ts | 82 ++++++------------- .../sync/SyncController.callback.test.ts | 57 ------------- 14 files changed, 73 insertions(+), 279 deletions(-) delete mode 100644 packages/transport/src/core/ProgressReporter.ts delete mode 100644 packages/transport/src/modules/sync/SyncCallback.ts delete mode 100644 packages/transport/test/modules/sync/SyncController.callback.test.ts diff --git a/.ci/runChecks.sh b/.ci/runChecks.sh index 52e156c96..8fb75944d 100755 --- a/.ci/runChecks.sh +++ b/.ci/runChecks.sh @@ -5,4 +5,4 @@ npm run build:node npm run lint:eslint npm run lint:prettier npx -ws license-check -npx better-npm-audit audit --exclude 1096302 +npx better-npm-audit audit --exclude 1096302,1098122 diff --git a/package-lock.json b/package-lock.json index c5d5310d3..38b19d943 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12480,11 +12480,6 @@ "@types/luxon": "^3.4.2" } }, - "packages/content/node_modules/@nmshd/iql": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@nmshd/iql/-/iql-1.0.2.tgz", - "integrity": "sha512-fRUIDoZeAKDJ99/yjbjlKryMv1poNaiRDTC8eNltZJSPSkQgchlt0yrWHBDl+CZEPF2Ae0hDj7vpo2n0c6R6JA==" - }, "packages/iql": { "name": "@nmshd/iql", "version": "1.0.2", diff --git a/packages/runtime/src/extensibility/facades/transport/AccountFacade.ts b/packages/runtime/src/extensibility/facades/transport/AccountFacade.ts index a04b90473..520e2ca14 100644 --- a/packages/runtime/src/extensibility/facades/transport/AccountFacade.ts +++ b/packages/runtime/src/extensibility/facades/transport/AccountFacade.ts @@ -14,9 +14,7 @@ import { RegisterPushNotificationTokenRequest, RegisterPushNotificationTokenResponse, RegisterPushNotificationTokenUseCase, - SyncDatawalletRequest, SyncDatawalletUseCase, - SyncEverythingRequest, SyncEverythingResponse, SyncEverythingUseCase, SyncInfo, @@ -53,12 +51,12 @@ export class AccountFacade { return await this.unregisterPushNotificationTokenUseCase.execute(); } - public async syncDatawallet(request: SyncDatawalletRequest = {}): Promise> { - return await this.syncDatawalletUseCase.execute(request); + public async syncDatawallet(): Promise> { + return await this.syncDatawalletUseCase.execute(); } - public async syncEverything(request: SyncEverythingRequest = {}): Promise> { - return await this.syncEverythingUseCase.execute(request); + public async syncEverything(): Promise> { + return await this.syncEverythingUseCase.execute(); } public async getSyncInfo(): Promise> { diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index 975230fcd..c9e422521 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -20350,17 +20350,6 @@ export const RegisterPushNotificationTokenRequest: any = { } } -export const SyncDatawalletRequest: any = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/SyncDatawalletRequest", - "definitions": { - "SyncDatawalletRequest": { - "type": "object", - "additionalProperties": false - } - } -} - export const GetIdentityDeletionProcessRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/GetIdentityDeletionProcessRequest", @@ -20415,17 +20404,6 @@ export const DownloadAttachmentRequest: any = { } } -export const SyncEverythingRequest: any = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/SyncEverythingRequest", - "definitions": { - "SyncEverythingRequest": { - "type": "object", - "additionalProperties": false - } - } -} - export const CreateRelationshipChallengeRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/CreateRelationshipChallengeRequest", diff --git a/packages/runtime/src/useCases/transport/account/SyncDatawallet.ts b/packages/runtime/src/useCases/transport/account/SyncDatawallet.ts index 83dde5602..064345858 100644 --- a/packages/runtime/src/useCases/transport/account/SyncDatawallet.ts +++ b/packages/runtime/src/useCases/transport/account/SyncDatawallet.ts @@ -3,17 +3,13 @@ import { AccountController } from "@nmshd/transport"; import { Inject } from "typescript-ioc"; import { UseCase } from "../../common"; -export interface SyncDatawalletRequest { - callback?(percentage: number, syncStep: string): void; -} - -export class SyncDatawalletUseCase extends UseCase { +export class SyncDatawalletUseCase extends UseCase { public constructor(@Inject private readonly accountController: AccountController) { super(); } - protected async executeInternal(request: SyncDatawalletRequest): Promise> { - await this.accountController.syncDatawallet(true, request.callback); + protected async executeInternal(): Promise> { + await this.accountController.syncDatawallet(true); return Result.ok(undefined); } } diff --git a/packages/runtime/src/useCases/transport/account/SyncEverything.ts b/packages/runtime/src/useCases/transport/account/SyncEverything.ts index 17f8b67ca..b8ffdf705 100644 --- a/packages/runtime/src/useCases/transport/account/SyncEverything.ts +++ b/packages/runtime/src/useCases/transport/account/SyncEverything.ts @@ -15,11 +15,7 @@ export interface SyncEverythingResponse { identityDeletionProcesses: IdentityDeletionProcessDTO[]; } -export interface SyncEverythingRequest { - callback?(percentage: number, syncStep: string): void; -} - -export class SyncEverythingUseCase extends UseCase { +export class SyncEverythingUseCase extends UseCase { private readonly logger: ILogger; public constructor( @Inject private readonly accountController: AccountController, @@ -32,12 +28,12 @@ export class SyncEverythingUseCase extends UseCase>; - protected async executeInternal(request: SyncEverythingRequest): Promise> { + protected async executeInternal(): Promise> { if (this.currentSync) { return await this.currentSync; } - this.currentSync = this._executeInternal(request); + this.currentSync = this._executeInternal(); try { return await this.currentSync; @@ -46,8 +42,8 @@ export class SyncEverythingUseCase extends UseCase { - public constructor(private readonly callback?: (currentPercentage: number, currentStep: T) => void) {} - - public createStep(stepName: T, totalNumberOfItemsInStep = -1): ProgressReporterStep { - return new ProgressReporterStep(stepName, totalNumberOfItemsInStep, this.callback); - } -} -export class ProgressReporterStep { - private currentItem: number; - public constructor( - public readonly name: T, - private totalNumberOfItems: number, - private readonly callback?: (currentPercentage: number, currentStep: T) => void - ) { - if (totalNumberOfItems > 0) this.progressTo(0); - } - - public progress(): void { - this.progressTo(this.currentItem + 1); - } - - public progressTo(itemIndex: number): void { - this.currentItem = itemIndex; - this.callback?.(Math.round((itemIndex / this.totalNumberOfItems) * 100), this.name); - } - - public incrementTotalNumberOfItems(): void { - this.updateTotalNumberOfItems(this.totalNumberOfItems + 1); - } - - public updateTotalNumberOfItems(newValue: number): void { - this.totalNumberOfItems = newValue; - } - - public finish(): void { - if (this.currentItem < this.totalNumberOfItems) this.progressTo(this.totalNumberOfItems); - } - - public manualReport(percentage: number): void { - this.callback?.(percentage, this.name); - } -} diff --git a/packages/transport/src/core/index.ts b/packages/transport/src/core/index.ts index 149c8fa27..649d09a3a 100644 --- a/packages/transport/src/core/index.ts +++ b/packages/transport/src/core/index.ts @@ -9,7 +9,6 @@ export * from "./CoreSynchronizable"; export * from "./CoreUtil"; export * from "./DbCollectionName"; export * from "./DependencyOverrides"; -export * from "./ProgressReporter"; export * from "./Reference"; export * from "./Transport"; export * from "./TransportController"; diff --git a/packages/transport/src/modules/accounts/AccountController.ts b/packages/transport/src/modules/accounts/AccountController.ts index e8ed8a85d..bf58b51fd 100644 --- a/packages/transport/src/modules/accounts/AccountController.ts +++ b/packages/transport/src/modules/accounts/AccountController.ts @@ -28,7 +28,6 @@ import { RelationshipsController } from "../relationships/RelationshipsControlle import { Relationship } from "../relationships/local/Relationship"; import { SecretController } from "../secrets/SecretController"; import { ChangedItems } from "../sync/ChangedItems"; -import { SyncProgressCallback, SyncProgressReporter } from "../sync/SyncCallback"; import { SyncController } from "../sync/SyncController"; import { SynchronizedCollection } from "../sync/SynchronizedCollection"; import { TokenController } from "../tokens/TokenController"; @@ -226,18 +225,16 @@ export class AccountController { await this.syncDatawallet(); } - public async syncDatawallet(force = false, syncProgressCallback?: SyncProgressCallback): Promise { + public async syncDatawallet(force = false): Promise { if (!force && !this.autoSync) { return; } - const reporter = SyncProgressReporter.fromCallback(syncProgressCallback); - return await this.synchronization.sync("OnlyDatawallet", reporter); + return await this.synchronization.sync("OnlyDatawallet"); } - public async syncEverything(syncProgressCallback?: SyncProgressCallback): Promise { - const reporter = SyncProgressReporter.fromCallback(syncProgressCallback); - return await this.synchronization.sync("Everything", reporter); + public async syncEverything(): Promise { + return await this.synchronization.sync("Everything"); } public async getLastCompletedSyncTime(): Promise { diff --git a/packages/transport/src/modules/index.ts b/packages/transport/src/modules/index.ts index b85ffc311..ffd4aa48a 100644 --- a/packages/transport/src/modules/index.ts +++ b/packages/transport/src/modules/index.ts @@ -1,12 +1,12 @@ export * from "./accounts/AccountController"; -export * from "./accounts/IdentityController"; -export * from "./accounts/IdentityDeletionProcessController"; -export * from "./accounts/IdentityUtil"; export * from "./accounts/backbone/IdentityClient"; export * from "./accounts/backbone/IdentityDeletionProcessClient"; export * from "./accounts/data/Identity"; export * from "./accounts/data/IdentityDeletionProcess"; export * from "./accounts/data/IdentityDeletionProcessStatus"; +export * from "./accounts/IdentityController"; +export * from "./accounts/IdentityDeletionProcessController"; +export * from "./accounts/IdentityUtil"; export * from "./certificates/CertificateController"; export * from "./certificates/CertificateIssuer"; export * from "./certificates/CertificateValidator"; @@ -24,31 +24,30 @@ export * from "./certificates/data/items/CertificatePrivateAttributeItem"; export * from "./certificates/data/items/CertificatePrivateAttributeItemSource"; export * from "./certificates/data/items/CertificatePublicAttributeItem"; export * from "./certificates/data/items/CertificateRoleItem"; -export * from "./challenges/ChallengeController"; export * from "./challenges/backbone/ChallengeAuthClient"; export * from "./challenges/backbone/ChallengeClient"; +export * from "./challenges/ChallengeController"; export * from "./challenges/data/Challenge"; export * from "./challenges/data/ChallengeSigned"; -export * from "./devices/DeviceController"; -export * from "./devices/DeviceSecretController"; -export * from "./devices/DevicesController"; export * from "./devices/backbone/BackbonePostDevices"; export * from "./devices/backbone/DeviceAuthClient"; export * from "./devices/backbone/DeviceClient"; +export * from "./devices/DeviceController"; +export * from "./devices/DevicesController"; +export * from "./devices/DeviceSecretController"; export * from "./devices/local/Device"; export * from "./devices/local/DeviceSecretCredentials"; export * from "./devices/local/SendDeviceParameters"; export * from "./devices/transmission/DeviceSharedSecret"; -export * from "./files/FileController"; export * from "./files/backbone/BackboneGetFiles"; export * from "./files/backbone/BackbonePostFiles"; export * from "./files/backbone/FileClient"; +export * from "./files/FileController"; export * from "./files/local/CachedFile"; export * from "./files/local/File"; export * from "./files/local/SendFileParameters"; export * from "./files/transmission/FileMetadata"; export * from "./files/transmission/FileReference"; -export * from "./messages/MessageController"; export * from "./messages/backbone/BackboneGetMessages"; export * from "./messages/backbone/BackbonePostMessages"; export * from "./messages/backbone/MessageClient"; @@ -56,24 +55,12 @@ export * from "./messages/local/CachedMessage"; export * from "./messages/local/CachedMessageRecipient"; export * from "./messages/local/Message"; export * from "./messages/local/SendMessageParameters"; +export * from "./messages/MessageController"; export * from "./messages/transmission/MessageContentWrapper"; export * from "./messages/transmission/MessageEnvelope"; export * from "./messages/transmission/MessageEnvelopeRecipient"; export * from "./messages/transmission/MessageSignature"; export * from "./messages/transmission/MessageSigned"; -export * from "./relationshipTemplates/RelationshipTemplateController"; -export * from "./relationshipTemplates/backbone/BackboneGetRelationshipTemplates"; -export * from "./relationshipTemplates/backbone/BackbonePostRelationshipTemplates"; -export * from "./relationshipTemplates/backbone/RelationshipTemplateClient"; -export * from "./relationshipTemplates/local/CachedRelationshipTemplate"; -export * from "./relationshipTemplates/local/RelationshipTemplate"; -export * from "./relationshipTemplates/local/SendRelationshipTemplateParameters"; -export * from "./relationshipTemplates/transmission/RelationshipTemplateContentWrapper"; -export * from "./relationshipTemplates/transmission/RelationshipTemplatePublicKey"; -export * from "./relationshipTemplates/transmission/RelationshipTemplateReference"; -export * from "./relationshipTemplates/transmission/RelationshipTemplateSigned"; -export * from "./relationships/RelationshipSecretController"; -export * from "./relationships/RelationshipsController"; export * from "./relationships/backbone/BackboneGetRelationships"; export * from "./relationships/backbone/BackbonePostRelationship"; export * from "./relationships/backbone/RelationshipClient"; @@ -82,6 +69,8 @@ export * from "./relationships/local/Relationship"; export * from "./relationships/local/RelationshipAuditLog"; export * from "./relationships/local/RelationshipAuditLogEntry"; export * from "./relationships/local/SendRelationshipParameters"; +export * from "./relationships/RelationshipsController"; +export * from "./relationships/RelationshipSecretController"; export * from "./relationships/transmission/RelationshipAuditLog"; export * from "./relationships/transmission/RelationshipStatus"; export * from "./relationships/transmission/requests/RelationshipCreationContentCipher"; @@ -90,14 +79,20 @@ export * from "./relationships/transmission/requests/RelationshipCreationContent export * from "./relationships/transmission/responses/RelationshipCreationResponseContentCipher"; export * from "./relationships/transmission/responses/RelationshipCreationResponseContentSigned"; export * from "./relationships/transmission/responses/RelationshipCreationResponseContentWrapper"; -export * from "./secrets/SecretController"; +export * from "./relationshipTemplates/backbone/BackboneGetRelationshipTemplates"; +export * from "./relationshipTemplates/backbone/BackbonePostRelationshipTemplates"; +export * from "./relationshipTemplates/backbone/RelationshipTemplateClient"; +export * from "./relationshipTemplates/local/CachedRelationshipTemplate"; +export * from "./relationshipTemplates/local/RelationshipTemplate"; +export * from "./relationshipTemplates/local/SendRelationshipTemplateParameters"; +export * from "./relationshipTemplates/RelationshipTemplateController"; +export * from "./relationshipTemplates/transmission/RelationshipTemplateContentWrapper"; +export * from "./relationshipTemplates/transmission/RelationshipTemplatePublicKey"; +export * from "./relationshipTemplates/transmission/RelationshipTemplateReference"; +export * from "./relationshipTemplates/transmission/RelationshipTemplateSigned"; export * from "./secrets/data/SecretContainerCipher"; export * from "./secrets/data/SecretContainerPlain"; -export * from "./sync/ChangedItems"; -export * from "./sync/DatawalletModificationsProcessor"; -export { SyncProgressCallback as SyncPercentageCallback, SyncStep } from "./sync/SyncCallback"; -export * from "./sync/SyncController"; -export * from "./sync/SynchronizedCollection"; +export * from "./secrets/SecretController"; export * from "./sync/backbone/BackboneDatawalletModification"; export * from "./sync/backbone/BackboneExternalEvent"; export * from "./sync/backbone/CreateDatawalletModifications"; @@ -106,16 +101,20 @@ export * from "./sync/backbone/GetDatawallet"; export * from "./sync/backbone/GetDatawalletModifications"; export * from "./sync/backbone/StartSyncRun"; export * from "./sync/backbone/SyncClient"; +export * from "./sync/ChangedItems"; export * from "./sync/data/ExternalEvent"; +export * from "./sync/DatawalletModificationsProcessor"; export * from "./sync/local/DatawalletModification"; +export * from "./sync/SyncController"; +export * from "./sync/SynchronizedCollection"; export * from "./tokens/AnonymousTokenController"; -export * from "./tokens/TokenController"; export * from "./tokens/backbone/BackboneGetTokens"; export * from "./tokens/backbone/BackbonePostTokens"; export * from "./tokens/backbone/TokenClient"; export * from "./tokens/local/CachedToken"; export * from "./tokens/local/SendTokenParameters"; export * from "./tokens/local/Token"; +export * from "./tokens/TokenController"; export * from "./tokens/transmission/TokenContentDeviceSharedSecret"; export * from "./tokens/transmission/TokenContentFile"; export * from "./tokens/transmission/TokenContentRelationshipTemplate"; diff --git a/packages/transport/src/modules/sync/DatawalletModificationsProcessor.ts b/packages/transport/src/modules/sync/DatawalletModificationsProcessor.ts index 64bb0a582..cb46b9a80 100644 --- a/packages/transport/src/modules/sync/DatawalletModificationsProcessor.ts +++ b/packages/transport/src/modules/sync/DatawalletModificationsProcessor.ts @@ -24,14 +24,12 @@ import { CachedToken } from "../tokens/local/CachedToken"; import { Token } from "../tokens/local/Token"; import { TokenController } from "../tokens/TokenController"; import { DatawalletModification, DatawalletModificationType } from "./local/DatawalletModification"; -import { SyncProgressReporter, SyncProgressReporterStep, SyncStep } from "./SyncCallback"; export class DatawalletModificationsProcessor { private readonly creates: DatawalletModification[]; private readonly updates: DatawalletModification[]; private readonly deletes: DatawalletModification[]; private readonly cacheChanges: DatawalletModification[]; - private readonly syncStep: SyncProgressReporterStep; public get log(): ILogger { return this.logger; @@ -41,8 +39,7 @@ export class DatawalletModificationsProcessor { modifications: DatawalletModification[], private readonly cacheFetcher: CacheFetcher, private readonly collectionProvider: IDatabaseCollectionProvider, - private readonly logger: ILogger, - reporter: SyncProgressReporter + private readonly logger: ILogger ) { const modificationsGroupedByType = _.groupBy(modifications, (m) => m.type); @@ -50,9 +47,6 @@ export class DatawalletModificationsProcessor { this.updates = modificationsGroupedByType[DatawalletModificationType.Update] ?? []; this.deletes = modificationsGroupedByType[DatawalletModificationType.Delete] ?? []; this.cacheChanges = modificationsGroupedByType[DatawalletModificationType.CacheChanged] ?? []; - - const totalItems = this.creates.length + this.updates.length + this.deletes.length + this.cacheChanges.length; - this.syncStep = reporter.createStep(SyncStep.DatawalletSyncProcessing, totalItems); } private readonly collectionsWithCacheableItems: string[] = [ @@ -69,10 +63,6 @@ export class DatawalletModificationsProcessor { await this.applyUpdates(); await this.applyDeletes(); await this.applyCacheChanges(); - - // cache-fills are optimized by the backbone, so it is possible that the processedItemCount is - // lower than the total number of items - in this case the 100% callback is triggered here - this.syncStep.finish(); } private async applyCreates() { @@ -105,8 +95,6 @@ export class DatawalletModificationsProcessor { await this.simulateCacheChangeForCreate(targetCollectionName, objectIdentifier); await targetCollection.create(newObject); } - - this.syncStep.progress(); } } @@ -125,7 +113,6 @@ export class DatawalletModificationsProcessor { }); this.cacheChanges.push(modification); - this.syncStep.incrementTotalNumberOfItems(); } private async applyUpdates() { @@ -145,7 +132,6 @@ export class DatawalletModificationsProcessor { const newObject = { ...oldObject.toJSON(), ...updateModification.payload }; await targetCollection.update(oldDoc, newObject); - this.syncStep.progress(); } } @@ -217,7 +203,6 @@ export class DatawalletModificationsProcessor { const item = (constructorOfT as any).from(itemDoc); item.setCache(c.cache); await collection.update(itemDoc, item); - this.syncStep.progress(); }) ); } @@ -230,7 +215,6 @@ export class DatawalletModificationsProcessor { for (const deleteModification of this.deletes) { const targetCollection = await this.collectionProvider.getCollection(deleteModification.collection); await targetCollection.delete({ id: deleteModification.objectIdentifier.toString() }); - this.syncStep.progress(); } } } diff --git a/packages/transport/src/modules/sync/SyncCallback.ts b/packages/transport/src/modules/sync/SyncCallback.ts deleted file mode 100644 index 4ce0c2b1e..000000000 --- a/packages/transport/src/modules/sync/SyncCallback.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ProgressReporter, ProgressReporterStep } from "../../core"; - -export type SyncProgressCallback = (percentage: number, step: SyncStep) => void; - -export class SyncProgressReporter extends ProgressReporter { - public static fromCallback(callback?: SyncProgressCallback): SyncProgressReporter { - return new SyncProgressReporter(callback); - } -} -export class SyncProgressReporterStep extends ProgressReporterStep {} - -export enum SyncStep { - Sync = "sync", - DatawalletSync = "sync:datawallet", - DatawalletSyncDownloading = "sync:datawallet:downloading", - DatawalletSyncDecryption = "sync:datawallet:decrypting", - DatawalletSyncProcessing = "sync:datawallet:processing", - ExternalEventsSync = "sync:externalEvents", - ExternalEventsSyncDownloading = "sync:externalEvents:downloading", - ExternalEventsProcessing = "sync:externalEvents:processing" -} diff --git a/packages/transport/src/modules/sync/SyncController.ts b/packages/transport/src/modules/sync/SyncController.ts index 3b6453f25..ea966443e 100644 --- a/packages/transport/src/modules/sync/SyncController.ts +++ b/packages/transport/src/modules/sync/SyncController.ts @@ -6,7 +6,6 @@ import { AccountController } from "../accounts/AccountController"; import { ChangedItems } from "./ChangedItems"; import { DatawalletModificationMapper } from "./DatawalletModificationMapper"; import { CacheFetcher, DatawalletModificationsProcessor } from "./DatawalletModificationsProcessor"; -import { SyncProgressReporter, SyncStep } from "./SyncCallback"; import { WhatToSync } from "./WhatToSync"; import { BackboneDatawalletModification } from "./backbone/BackboneDatawalletModification"; import { BackboneSyncRun } from "./backbone/BackboneSyncRun"; @@ -67,10 +66,10 @@ export class SyncController extends TransportController { private currentSync?: LocalSyncRun; private currentSyncRun?: BackboneSyncRun; - public async sync(whatToSync: "OnlyDatawallet", reporter: SyncProgressReporter): Promise; - public async sync(whatToSync: "Everything", reporter: SyncProgressReporter): Promise; - public async sync(whatToSync: WhatToSync, reporter: SyncProgressReporter): Promise; - public async sync(whatToSync: WhatToSync = "Everything", reporter: SyncProgressReporter): Promise { + public async sync(whatToSync: "OnlyDatawallet"): Promise; + public async sync(whatToSync: "Everything"): Promise; + public async sync(whatToSync: WhatToSync): Promise; + public async sync(whatToSync: WhatToSync = "Everything"): Promise { if (this.currentSync?.includes(whatToSync)) { return await this.currentSync.promise; } @@ -79,10 +78,10 @@ export class SyncController extends TransportController { await this.currentSync.promise.catch(() => { // ignore this error because we only want to wait for the current sync to finish before we are starting a new one }); - return await this.sync(whatToSync, reporter); + return await this.sync(whatToSync); } - const syncPromise = this._syncAndResyncDatawallet(whatToSync, reporter); + const syncPromise = this._syncAndResyncDatawallet(whatToSync); this.currentSync = new LocalSyncRun(syncPromise, whatToSync); try { @@ -92,27 +91,24 @@ export class SyncController extends TransportController { } } - private async _syncAndResyncDatawallet(whatToSync: WhatToSync = "Everything", reporter: SyncProgressReporter) { + private async _syncAndResyncDatawallet(whatToSync: WhatToSync = "Everything") { try { - return await this._sync(whatToSync, reporter); + return await this._sync(whatToSync); } finally { if (this.datawalletEnabled && (await this.unpushedDatawalletModifications.exists())) { - await this.syncDatawallet(reporter).catch((e) => this.log.error(e)); + await this.syncDatawallet().catch((e) => this.log.error(e)); } } } @log() - private async _sync(whatToSync: WhatToSync, reporter: SyncProgressReporter): Promise { - const syncStep = reporter.createStep(SyncStep.Sync, 1); - + private async _sync(whatToSync: WhatToSync): Promise { if (whatToSync === "OnlyDatawallet") { - const value = await this.syncDatawallet(reporter); - syncStep.finish(); + const value = await this.syncDatawallet(); return value; } - const externalEventSyncResult = await this.syncExternalEvents(reporter); + const externalEventSyncResult = await this.syncExternalEvents(); await this.setLastCompletedSyncTime(); @@ -126,36 +122,34 @@ export class SyncController extends TransportController { ); } - syncStep.finish(); - if (this.datawalletEnabled && (await this.unpushedDatawalletModifications.exists())) { - await this.syncDatawallet(reporter).catch((e) => this.log.error(e)); + await this.syncDatawallet().catch((e) => this.log.error(e)); } return externalEventSyncResult.changedItems; } - private async syncExternalEvents(reporter: SyncProgressReporter): Promise<{ + private async syncExternalEvents(): Promise<{ externalEventResults: FinalizeSyncRunRequestExternalEventResult[]; changedItems: ChangedItems; }> { const syncRunWasStarted = await this.startExternalEventsSyncRun(); if (!syncRunWasStarted) { - await this.syncDatawallet(reporter); + await this.syncDatawallet(); return { changedItems: new ChangedItems(), externalEventResults: [] }; } - await this.applyIncomingDatawalletModifications(reporter); - const result = await this.applyIncomingExternalEvents(reporter); + await this.applyIncomingDatawalletModifications(); + const result = await this.applyIncomingExternalEvents(); await this.finalizeExternalEventsSyncRun(result.externalEventResults); return result; } @log() - private async syncDatawallet(reporter: SyncProgressReporter) { + private async syncDatawallet() { if (!this.datawalletEnabled) { return; } @@ -170,7 +164,7 @@ export class SyncController extends TransportController { this.log.trace("Synchronization of Datawallet events started..."); try { - await this.applyIncomingDatawalletModifications(reporter); + await this.applyIncomingDatawalletModifications(); await this.pushLocalDatawalletModifications(); await this.setLastCompletedDatawalletSyncTime(); @@ -256,22 +250,15 @@ export class SyncController extends TransportController { } } - private async applyIncomingDatawalletModifications(reporter: SyncProgressReporter) { - const datawalletSyncStep = reporter.createStep(SyncStep.DatawalletSync, 1); - - const downloadingStep = reporter.createStep(SyncStep.DatawalletSyncDownloading); - const getDatawalletModificationsResult = await this.client.getDatawalletModifications( - { localIndex: await this.getLocalDatawalletModificationIndex() }, - (percentage: number) => downloadingStep.manualReport(percentage) - ); + private async applyIncomingDatawalletModifications() { + const getDatawalletModificationsResult = await this.client.getDatawalletModifications({ localIndex: await this.getLocalDatawalletModificationIndex() }); const encryptedIncomingModifications = await getDatawalletModificationsResult.value.collect(); if (encryptedIncomingModifications.length === 0) { - datawalletSyncStep.finish(); return; } - const incomingModifications = await this.decryptDatawalletModifications(encryptedIncomingModifications, reporter); + const incomingModifications = await this.decryptDatawalletModifications(encryptedIncomingModifications); this.log.trace(`${incomingModifications.length} incoming modifications found`); @@ -279,8 +266,7 @@ export class SyncController extends TransportController { incomingModifications, this.cacheFetcher, this.db, - TransportLoggerFactory.getLogger(DatawalletModificationsProcessor), - reporter + TransportLoggerFactory.getLogger(DatawalletModificationsProcessor) ); await datawalletModificationsProcessor.execute(); @@ -288,8 +274,6 @@ export class SyncController extends TransportController { this.log.trace(`${incomingModifications.length} incoming modifications executed`, incomingModifications); await this.updateLocalDatawalletModificationIndex(encryptedIncomingModifications.sort(descending)[0].index); - - datawalletSyncStep.finish(); } private async promiseAllWithProgess(promises: Promise[], callback: (percentage: number) => void) { @@ -309,11 +293,9 @@ export class SyncController extends TransportController { return await Promise.all(promises); } - private async decryptDatawalletModifications(encryptedModifications: BackboneDatawalletModification[], reporter: SyncProgressReporter): Promise { + private async decryptDatawalletModifications(encryptedModifications: BackboneDatawalletModification[]): Promise { const promises = encryptedModifications.map((m) => this.decryptDatawalletModification(m)); - const step = reporter.createStep(SyncStep.DatawalletSyncDecryption); - - return await this.promiseAllWithProgess(promises, (p: number) => step.manualReport(p)); + return await Promise.all(promises); } private async decryptDatawalletModification(encryptedModification: BackboneDatawalletModification) { @@ -399,19 +381,13 @@ export class SyncController extends TransportController { return this.currentSyncRun !== undefined; } - private async applyIncomingExternalEvents(reporter: SyncProgressReporter) { - const externalEventStep = reporter.createStep(SyncStep.ExternalEventsSync, 1); - - const downloadingStep = reporter.createStep(SyncStep.ExternalEventsSyncDownloading); - const getExternalEventsResult = await this.client.getExternalEventsOfSyncRun(this.currentSyncRun!.id.toString(), (percentage: number) => - downloadingStep.manualReport(percentage) - ); + private async applyIncomingExternalEvents() { + const getExternalEventsResult = await this.client.getExternalEventsOfSyncRun(this.currentSyncRun!.id.toString()); if (getExternalEventsResult.isError) throw getExternalEventsResult.error; const externalEvents = await getExternalEventsResult.value.collect(); - const syncStep = reporter.createStep(SyncStep.ExternalEventsProcessing, externalEvents.length); const results: FinalizeSyncRunRequestExternalEventResult[] = []; const changedItems = new ChangedItems(); @@ -441,13 +417,9 @@ export class SyncController extends TransportController { externalEventId: externalEvent.id, errorCode: errorCode }); - } finally { - syncStep.progress(); } } - externalEventStep.finish(); - return { externalEventResults: results, changedItems: changedItems diff --git a/packages/transport/test/modules/sync/SyncController.callback.test.ts b/packages/transport/test/modules/sync/SyncController.callback.test.ts deleted file mode 100644 index 31f9f9047..000000000 --- a/packages/transport/test/modules/sync/SyncController.callback.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; -import { sleep } from "@js-soft/ts-utils"; -import _ from "lodash"; -import { AccountController } from "../../../src"; -import { TestUtil } from "../../testHelpers/TestUtil"; - -interface CallbackObject { - percentage: number; - process: string; -} - -describe("SyncControllerCallback", function () { - let connection: IDatabaseConnection; - - let a1: AccountController; - let b1: AccountController; - let b2: AccountController; - - beforeAll(async () => { - connection = await TestUtil.createDatabaseConnection(); - a1 = await TestUtil.createIdentityWithOneDevice(connection, { datawalletEnabled: true }); - ({ device1: b1, device2: b2 } = await TestUtil.createIdentityWithTwoDevices(connection, { - datawalletEnabled: true - })); - }); - - afterAll(async function () { - await a1.close(); - await b1.close(); - await b2.close(); - - await connection.close(); - }); - - test("should execute the callback during syncEverything", async function () { - await TestUtil.addRelationship(a1, b1); - - await TestUtil.sendMessage(a1, b1); - - const events: CallbackObject[] = []; - - await sleep(1000); - - await b2.syncEverything((percentage: number, process: string) => { - events.push({ percentage, process }); - }); - - expect(events).toHaveLength(30); - - const grouped = _.groupBy(events, "process"); - for (const key in grouped) { - const percentages = grouped[key].map((e) => e.percentage); - expect(percentages).toContain(0); - expect(percentages).toContain(100); - } - }); -}); From b2bbff2c117db64d2acc2cc125702a982dc9c4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Tue, 16 Jul 2024 09:54:38 +0200 Subject: [PATCH 19/40] Chore/use did schema where id2 was used (#193) * chore: use did schema * chore: use did schema --- .../test/modules/requests/testHelpers/TestObjectFactory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts index 728a934be..d8fe366da 100644 --- a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts +++ b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts @@ -60,7 +60,7 @@ export class TestObjectFactory { auditLog: [ { createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), - createdBy: CoreAddress.from("id2"), + createdBy: CoreAddress.from("did:e:a-domain:dids:anidentity2"), createdByDevice: CoreId.from("DVC1"), reason: RelationshipAuditLogEntryReason.Creation, newStatus: RelationshipStatus.Pending @@ -92,7 +92,7 @@ export class TestObjectFactory { auditLog: [ { createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), - createdBy: CoreAddress.from("id2"), + createdBy: CoreAddress.from("did:e:a-domain:dids:anidentity2"), createdByDevice: CoreId.from("DVC1"), reason: RelationshipAuditLogEntryReason.Creation, newStatus: RelationshipStatus.Pending @@ -133,7 +133,7 @@ export class TestObjectFactory { auditLog: [ { createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), - createdBy: CoreAddress.from("id2"), + createdBy: CoreAddress.from("did:e:a-domain:dids:anidentity2"), createdByDevice: CoreId.from("DVC1"), reason: RelationshipAuditLogEntryReason.Creation, newStatus: RelationshipStatus.Pending From f74f803f72c67874561d65b3a71d60a50438bc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:09:08 +0200 Subject: [PATCH 20/40] Fix/use void instead of null (#196) * fix: use void for decompose * fix: use void in the facade * chore: version bumps --- package-lock.json | 18 +++++++++--------- packages/app-runtime/package.json | 4 ++-- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- packages/runtime/package.json | 8 ++++---- .../facades/transport/RelationshipsFacade.ts | 2 +- .../relationships/DecomposeRelationship.ts | 6 +++--- .../test/transport/relationships.test.ts | 4 +--- packages/transport/package.json | 2 +- 9 files changed, 23 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index ad4295731..67ce21571 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12546,7 +12546,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.6", + "version": "5.0.0-alpha.7", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12560,12 +12560,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.6" + "@nmshd/runtime": "^5.0.0-alpha.7" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.6", + "version": "5.0.0-alpha.7", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12587,7 +12587,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.6", + "version": "5.0.0-alpha.7", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12612,17 +12612,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.6", + "version": "5.0.0-alpha.7", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.6", - "@nmshd/content": "5.0.0-alpha.6", + "@nmshd/consumption": "5.0.0-alpha.7", + "@nmshd/content": "5.0.0-alpha.7", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.6", + "@nmshd/transport": "5.0.0-alpha.7", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12654,7 +12654,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.6", + "version": "5.0.0-alpha.7", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index ce83340f2..a96cabad8 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.6", + "version": "5.0.0-alpha.7", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.6" + "@nmshd/runtime": "^5.0.0-alpha.7" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 995987953..62148dbd9 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.6", + "version": "5.0.0-alpha.7", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index 94485543a..8ce5c1399 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.6", + "version": "5.0.0-alpha.7", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index ecce2b119..6823c0aa9 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.6", + "version": "5.0.0-alpha.7", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.6", - "@nmshd/content": "5.0.0-alpha.6", + "@nmshd/consumption": "5.0.0-alpha.7", + "@nmshd/content": "5.0.0-alpha.7", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.6", + "@nmshd/transport": "5.0.0-alpha.7", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts b/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts index f8c45d5c6..180073aa4 100644 --- a/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts +++ b/packages/runtime/src/extensibility/facades/transport/RelationshipsFacade.ts @@ -99,7 +99,7 @@ export class RelationshipsFacade { return await this.revokeRelationshipReactivationUseCase.execute(request); } - public async decomposeRelationship(request: DecomposeRelationshipRequest): Promise> { + public async decomposeRelationship(request: DecomposeRelationshipRequest): Promise> { return await this.decomposeRelationshipUseCase.execute(request); } diff --git a/packages/runtime/src/useCases/transport/relationships/DecomposeRelationship.ts b/packages/runtime/src/useCases/transport/relationships/DecomposeRelationship.ts index 8aedf925a..371586b4d 100644 --- a/packages/runtime/src/useCases/transport/relationships/DecomposeRelationship.ts +++ b/packages/runtime/src/useCases/transport/relationships/DecomposeRelationship.ts @@ -14,7 +14,7 @@ class Validator extends SchemaValidator { } } -export class DecomposeRelationshipUseCase extends UseCase { +export class DecomposeRelationshipUseCase extends UseCase { public constructor( @Inject private readonly accountController: AccountController, @Inject private readonly consumptionController: ConsumptionController, @@ -24,7 +24,7 @@ export class DecomposeRelationshipUseCase extends UseCase> { + protected async executeInternal(request: DecomposeRelationshipRequest): Promise> { const relationship = await this.relationshipsController.getRelationship(CoreId.from(request.relationshipId)); if (!relationship) { return Result.fail(RuntimeErrors.general.recordNotFound(Relationship)); @@ -42,6 +42,6 @@ export class DecomposeRelationshipUseCase extends UseCase { let templateId2: string; let multipleRecipientsMessageId: string; - let decompositionResult: Result; beforeAll(async () => { const relationship = await ensureActiveRelationship(services1.transport, services2.transport); relationshipId = relationship.id; @@ -695,14 +694,13 @@ describe("RelationshipDecomposition", () => { multipleRecipientsMessageId = (await sendMessageToMultipleRecipients(services1.transport, [services2.address, services3.address])).id; await services1.transport.relationships.terminateRelationship({ relationshipId }); - decompositionResult = await services1.transport.relationships.decomposeRelationship({ relationshipId }); + await services1.transport.relationships.decomposeRelationship({ relationshipId }); await services2.eventBus.waitForEvent(RelationshipChangedEvent); await services1.transport.relationships.terminateRelationship({ relationshipId: relationshipId2 }); }); test("relationship should be decomposed", async () => { - expect(decompositionResult).toBeSuccessful(); await expect(services1.eventBus).toHavePublished(RelationshipDecomposedBySelfEvent, (e) => e.data === relationshipId); const ownRelationship = await services1.transport.relationships.getRelationship({ id: relationshipId }); diff --git a/packages/transport/package.json b/packages/transport/package.json index 62879f6f8..ef56c1492 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.6", + "version": "5.0.0-alpha.7", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { From 7787b8157434ac22cc2e90622bacea435bb2baf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Thu, 18 Jul 2024 12:37:18 +0200 Subject: [PATCH 21/40] Fix/do not use string as event data (#197) * fix: do not use string as event data * chore: version bump * chore: change on callee side * fix: test * ci: downgrade node --- .github/workflows/publish-prereleases.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 24 +++++++++---------- package-lock.json | 18 +++++++------- packages/app-runtime/package.json | 4 ++-- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- packages/runtime/package.json | 8 +++---- packages/runtime/src/events/EventProxy.ts | 2 +- .../RelationshipDecomposedBySelfEvent.ts | 9 ++++--- .../test/transport/relationships.test.ts | 2 +- packages/transport/package.json | 2 +- .../RelationshipDecomposedBySelfEvent.ts | 10 +++++--- .../relationships/RelationshipsController.ts | 2 +- 14 files changed, 48 insertions(+), 41 deletions(-) diff --git a/.github/workflows/publish-prereleases.yml b/.github/workflows/publish-prereleases.yml index 09662f375..94621f536 100644 --- a/.github/workflows/publish-prereleases.yml +++ b/.github/workflows/publish-prereleases.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 registry-url: https://registry.npmjs.org/ - run: npm ci - run: npm run build:ci -ws diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index edf9e28b1..5fe59345f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 registry-url: https://registry.npmjs.org/ - run: npm ci - run: npm run build:ci -ws diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 242478550..c23dc7db6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: run: npm run start:backbone - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 - run: npm ci - run: npm run build:node - run: npm run test:ci --workspace=@nmshd/app-runtime @@ -40,7 +40,7 @@ jobs: run: npm run start:backbone - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 - run: npm ci - name: Start MongoDB uses: supercharge/mongodb-github-action@v1 @@ -60,7 +60,7 @@ jobs: run: npm run start:backbone - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 - run: npm ci - uses: js-soft/ferretdb-github-action@1.1.0 with: @@ -82,7 +82,7 @@ jobs: run: npm run start:backbone - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 - run: npm ci - run: npm run build:node - run: npm run test:ci:lokijs --workspace=@nmshd/consumption @@ -97,7 +97,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 - run: npm ci env: BUILD_NUMBER: ${{ github.run_number }} @@ -113,7 +113,7 @@ jobs: run: npm run start:backbone - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 - uses: supercharge/mongodb-github-action@v1 - run: npm ci - run: npm run build:node @@ -134,7 +134,7 @@ jobs: run: npm run start:backbone - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 - uses: js-soft/ferretdb-github-action@1.1.0 with: ferretdb-telemetry: "enabled" @@ -158,7 +158,7 @@ jobs: run: npm run start:backbone - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 - run: npm ci - run: npm run build:node - run: npm run build:schemas --workspace=@nmshd/runtime @@ -177,7 +177,7 @@ jobs: run: npm run start:backbone - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 - run: npm ci - run: npm run build:node - name: Start MongoDB @@ -198,7 +198,7 @@ jobs: run: npm run start:backbone - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 - run: npm ci - run: npm run build:node - uses: js-soft/ferretdb-github-action@1.1.0 @@ -221,7 +221,7 @@ jobs: run: npm run start:backbone - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 - run: npm ci - run: npm run build:node env: @@ -240,7 +240,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: current + node-version: 22.4.1 - run: npm ci - run: npm run build:node env: diff --git a/package-lock.json b/package-lock.json index 67ce21571..3d23531cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12546,7 +12546,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.7", + "version": "5.0.0-alpha.8", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12560,12 +12560,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.7" + "@nmshd/runtime": "^5.0.0-alpha.8" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.7", + "version": "5.0.0-alpha.8", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12587,7 +12587,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.7", + "version": "5.0.0-alpha.8", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12612,17 +12612,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.7", + "version": "5.0.0-alpha.8", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.7", - "@nmshd/content": "5.0.0-alpha.7", + "@nmshd/consumption": "5.0.0-alpha.8", + "@nmshd/content": "5.0.0-alpha.8", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.7", + "@nmshd/transport": "5.0.0-alpha.8", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12654,7 +12654,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.7", + "version": "5.0.0-alpha.8", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index a96cabad8..c3b385cc4 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.7", + "version": "5.0.0-alpha.8", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.7" + "@nmshd/runtime": "^5.0.0-alpha.8" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 62148dbd9..f8adadade 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.7", + "version": "5.0.0-alpha.8", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index 8ce5c1399..d856aed65 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.7", + "version": "5.0.0-alpha.8", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 6823c0aa9..6de8ef1f4 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.7", + "version": "5.0.0-alpha.8", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.7", - "@nmshd/content": "5.0.0-alpha.7", + "@nmshd/consumption": "5.0.0-alpha.8", + "@nmshd/content": "5.0.0-alpha.8", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.7", + "@nmshd/transport": "5.0.0-alpha.8", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/events/EventProxy.ts b/packages/runtime/src/events/EventProxy.ts index 882c4190e..f9304544d 100644 --- a/packages/runtime/src/events/EventProxy.ts +++ b/packages/runtime/src/events/EventProxy.ts @@ -83,7 +83,7 @@ export class EventProxy { }); this.subscribeToSourceEvent(transport.RelationshipDecomposedBySelfEvent, (event) => { - this.targetEventBus.publish(new RelationshipDecomposedBySelfEvent(event.eventTargetAddress, event.data.toString())); + this.targetEventBus.publish(new RelationshipDecomposedBySelfEvent(event.eventTargetAddress, { relationshipId: event.data.relationshipId.toString() })); }); this.subscribeToSourceEvent(transport.IdentityDeletionProcessStatusChangedEvent, (event) => { diff --git a/packages/runtime/src/events/transport/RelationshipDecomposedBySelfEvent.ts b/packages/runtime/src/events/transport/RelationshipDecomposedBySelfEvent.ts index af2bd4d40..74acf0121 100644 --- a/packages/runtime/src/events/transport/RelationshipDecomposedBySelfEvent.ts +++ b/packages/runtime/src/events/transport/RelationshipDecomposedBySelfEvent.ts @@ -1,10 +1,13 @@ -import { RelationshipIdString } from "../../useCases/common"; import { DataEvent } from "../DataEvent"; -export class RelationshipDecomposedBySelfEvent extends DataEvent { +export interface RelationshipDecomposedBySelfEventData { + relationshipId: string; +} + +export class RelationshipDecomposedBySelfEvent extends DataEvent { public static readonly namespace = "transport.relationshipDecomposedBySelf"; - public constructor(eventTargetAddress: string, data: RelationshipIdString) { + public constructor(eventTargetAddress: string, data: RelationshipDecomposedBySelfEventData) { super(RelationshipDecomposedBySelfEvent.namespace, eventTargetAddress, data); } } diff --git a/packages/runtime/test/transport/relationships.test.ts b/packages/runtime/test/transport/relationships.test.ts index bf81d21d8..f6396ac13 100644 --- a/packages/runtime/test/transport/relationships.test.ts +++ b/packages/runtime/test/transport/relationships.test.ts @@ -701,7 +701,7 @@ describe("RelationshipDecomposition", () => { }); test("relationship should be decomposed", async () => { - await expect(services1.eventBus).toHavePublished(RelationshipDecomposedBySelfEvent, (e) => e.data === relationshipId); + await expect(services1.eventBus).toHavePublished(RelationshipDecomposedBySelfEvent, (e) => e.data.relationshipId === relationshipId); const ownRelationship = await services1.transport.relationships.getRelationship({ id: relationshipId }); expect(ownRelationship).toBeAnError(/.*/, "error.runtime.recordNotFound"); diff --git a/packages/transport/package.json b/packages/transport/package.json index ef56c1492..782b38420 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.7", + "version": "5.0.0-alpha.8", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/transport/src/events/RelationshipDecomposedBySelfEvent.ts b/packages/transport/src/events/RelationshipDecomposedBySelfEvent.ts index 7321284e1..3fef9c151 100644 --- a/packages/transport/src/events/RelationshipDecomposedBySelfEvent.ts +++ b/packages/transport/src/events/RelationshipDecomposedBySelfEvent.ts @@ -1,10 +1,14 @@ import { CoreId } from "../core"; import { TransportDataEvent } from "./TransportDataEvent"; -export class RelationshipDecomposedBySelfEvent extends TransportDataEvent { +export interface RelationshipDecomposedBySelfEventData { + relationshipId: CoreId; +} + +export class RelationshipDecomposedBySelfEvent extends TransportDataEvent { public static readonly namespace = "transport.relationshipDecomposedBySelf"; - public constructor(eventTargetAddress: string, relationshipId: CoreId) { - super(RelationshipDecomposedBySelfEvent.namespace, eventTargetAddress, relationshipId); + public constructor(eventTargetAddress: string, data: RelationshipDecomposedBySelfEventData) { + super(RelationshipDecomposedBySelfEvent.namespace, eventTargetAddress, data); } } diff --git a/packages/transport/src/modules/relationships/RelationshipsController.ts b/packages/transport/src/modules/relationships/RelationshipsController.ts index d52bee67b..149fd69f4 100644 --- a/packages/transport/src/modules/relationships/RelationshipsController.ts +++ b/packages/transport/src/modules/relationships/RelationshipsController.ts @@ -314,7 +314,7 @@ export class RelationshipsController extends TransportController { } await this.relationships.delete({ id: relationshipId }); - this.eventBus.publish(new RelationshipDecomposedBySelfEvent(this.parent.identity.address.toString(), relationshipId)); + this.eventBus.publish(new RelationshipDecomposedBySelfEvent(this.parent.identity.address.toString(), { relationshipId })); } private async getRelationshipWithCache(id: CoreId): Promise { From f5b729ccefed5216165ee0238adc53187a5803a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Mon, 22 Jul 2024 16:27:37 +0200 Subject: [PATCH 22/40] Fix/local development with different ways to connect to the backbone (#200) * refactor: update error messages without logging addresses * fix: provide addressGenerationBaseUrlOverride to make sure to match the backbones identity * chore: version bumps * chore: better naming * refactor: simplify error output --- package-lock.json | 18 +++++++++--------- packages/app-runtime/package.json | 4 ++-- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- packages/runtime/package.json | 8 ++++---- packages/transport/package.json | 2 +- packages/transport/src/core/Transport.ts | 2 ++ .../src/modules/accounts/AccountController.ts | 4 ++-- 8 files changed, 22 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3d23531cb..40ab7da1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12546,7 +12546,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.8", + "version": "5.0.0-alpha.9", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12560,12 +12560,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.8" + "@nmshd/runtime": "^5.0.0-alpha.9" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.8", + "version": "5.0.0-alpha.9", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12587,7 +12587,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.8", + "version": "5.0.0-alpha.9", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12612,17 +12612,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.8", + "version": "5.0.0-alpha.9", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.8", - "@nmshd/content": "5.0.0-alpha.8", + "@nmshd/consumption": "5.0.0-alpha.9", + "@nmshd/content": "5.0.0-alpha.9", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.8", + "@nmshd/transport": "5.0.0-alpha.9", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12654,7 +12654,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.8", + "version": "5.0.0-alpha.9", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index c3b385cc4..e9481cf0e 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.8", + "version": "5.0.0-alpha.9", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.8" + "@nmshd/runtime": "^5.0.0-alpha.9" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index f8adadade..77f67f04b 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.8", + "version": "5.0.0-alpha.9", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index d856aed65..1d15e6143 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.8", + "version": "5.0.0-alpha.9", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 6de8ef1f4..1464e087b 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.8", + "version": "5.0.0-alpha.9", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.8", - "@nmshd/content": "5.0.0-alpha.8", + "@nmshd/consumption": "5.0.0-alpha.9", + "@nmshd/content": "5.0.0-alpha.9", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.8", + "@nmshd/transport": "5.0.0-alpha.9", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/transport/package.json b/packages/transport/package.json index 782b38420..3902f98df 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.8", + "version": "5.0.0-alpha.9", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/transport/src/core/Transport.ts b/packages/transport/src/core/Transport.ts index 1f6bed6e7..b9580f67a 100644 --- a/packages/transport/src/core/Transport.ts +++ b/packages/transport/src/core/Transport.ts @@ -24,6 +24,7 @@ export interface IConfig { platformMaxUnencryptedFileSize: number; platformAdditionalHeaders?: Record; baseUrl: string; + addressGenerationHostnameOverride?: string; datawalletEnabled: boolean; httpAgentOptions: AgentOptions; httpsAgentOptions: HTTPSAgentOptions; @@ -40,6 +41,7 @@ export interface IConfigOverwrite { platformMaxUnencryptedFileSize?: number; platformAdditionalHeaders?: Record; baseUrl: string; + addressGenerationHostnameOverride?: string; datawalletEnabled?: boolean; httpAgentOptions?: AgentOptions; httpsAgentOptions?: HTTPSAgentOptions; diff --git a/packages/transport/src/modules/accounts/AccountController.ts b/packages/transport/src/modules/accounts/AccountController.ts index bf58b51fd..14b55300c 100644 --- a/packages/transport/src/modules/accounts/AccountController.ts +++ b/packages/transport/src/modules/accounts/AccountController.ts @@ -279,7 +279,7 @@ export class AccountController { CoreCrypto.generateSecretKey(), // Generate address locally - IdentityUtil.createAddress(identityKeypair.publicKey, new URL(this._config.baseUrl).hostname), + IdentityUtil.createAddress(identityKeypair.publicKey, this._config.addressGenerationHostnameOverride ?? new URL(this._config.baseUrl).hostname), this.fetchDeviceInfo() ]); @@ -295,7 +295,7 @@ export class AccountController { this._log.trace(`Registered identity with address ${deviceResponse.address}, device id is ${deviceResponse.device.id}.`); if (!localAddress.equals(deviceResponse.address)) { - throw new TransportError("The backbone address does not match the local address."); + throw new TransportError(`The backbone address '${deviceResponse.address}' does not match the local address '${localAddress.toString()}'.`); } const identity = Identity.from({ From fa633b51d85d66753f5c0ef9e98d4ed15cfd74b7 Mon Sep 17 00:00:00 2001 From: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com> Date: Tue, 23 Jul 2024 17:12:45 +0200 Subject: [PATCH 23/40] Test/relationship decomposition tests (#190) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: remove enmeshed prefix * refactor: rollback relationships controller changes * feat: re-add relationship termination in relationships controller * chore: remove reactivation core error * test: create pending/terminated relationship in test object factory * test: methods for creating pending/terminated relationships in integration tests * test: add terminated relationship message controller test * refactor: create relationship centrally in outgoing request controller describe blocks * test: add terminated relationship outgoing requests controller test * test: add terminated relationship incoming requests controller tests plus small fixes * refactor: incoming requests controller relationship check returns validation instead of throwing * test: canAccept / canReject return validation result * refactor: test util terminate relationship does not require relationship id * test: terminated relationship challenge test * fix: terminated relationship runtime test checks can decide validation * Revert "chore: remove reactivation schemas" This reverts commit 20c42d091fa8fc3cc28708ee6ef9784beaa47269. * Revert "chore: remove reactivation core error" This reverts commit 421eb79eac1c85c9c7d10c42cd981916ee0a4c8d. * Revert "fix: remove some more" This reverts commit 143c6486d3585482bf8abb9405d4b5caf1b471a6. * Revert "fix: relationshipsFacade" This reverts commit ac6f8fdc426aef5e9438b8ba72ef0c574a48a5ea. * refactor: remove second pending relationship from factory correct the existing relationships * refactor: add/terminate relationship test utils return type * test: revert last relationships test change * refactor: replace noMatchingRelationship requests error * refactor: rename noMatchingRelationship messages error * refactor: error messages * fix: message controller test * fix: error message in message controller test * fix: error message in relationships test * refactor: inline relationship status check * chore: backbone version bump * fix: end2end test relationship adding * test: add reactivation runtime tests * feat: add reactivation/validation to relationships controller * refactor: update not only pending relationship * test: extend End2End test * refactor: remove comments * chore: add new backbone event processors * refactor: remove comments * fix: relationship client method names * refactor: method name completeOperationWithBackboneCall * fix: remove optional return types * chore: sort imports * fix: bump backbone * test: add tests for added relationship validation * chore: upgrade backbone (again) * chore: add decompose to relationship client * fix: remove realm * refactor: remove test skips * fix: End2End relationship tests * chore: remove decompose * fix: wrong ! in relationships controller * test: add another event validation to runtime test * refactor: add ) * test: add toBeSuccessful checks, some fixes * test: add transport test for relationship reactivation request * refactor: name requestRelationshipReactivation * chore: add status checks to relationship use cases * test: check audit log reason instead of new status * refactor: codeshare * chore: upgrade backbone again * refactor: add relationshipId to error messages * refactor: remove duplicate relationship validations * test: remove additional relationship status checks * test: add noRecordFound relationship tests * Revert "refactor: remove duplicate relationship validations" This reverts commit 3d01a429170d59fd573935d7538b710d761ed412. * refactor: remove status checks * test: add relationship tests * fix: errors in tests * fix: End2End test syntax errors * chore: add decomposition to relationship status/audit log types * fix: adapt relationships controller * test: add some decomposition tests * fix: End2End test * refactor: move relationship termination sync in test * fix: delete attributes during decomposition * fix: queries for to be deleted objects * refactor: shorten message controller * fix: relationships test * refactor: separate negative tests * refactor: new events for relationship reactivation * fix: follow-up to new events * refactor: rename relationship tests * fix/refactor: complete relationship decomposed event * refactor: newline * fix: re-add relationship changed event * test: re-add relationship changed events to tests * fix: recipientDTO has optional relationship * chore: delete relationship template during decompose * fix: module import in relationships test * Revert "fix: module import in relationships test" This reverts commit 185d471ba7444f0c416c608590e8a156f90c18b1. * fix: module import in relationships test * feat: add pseudonymization * fix: throw on error * fix: throw on more errors * fix: relationships test for templates * refactor: cleaner pseudonymization * fix: delete relationshipId * chore: backbone version bump * refactor: separate runtime and backbone relationship status * test: skip message tests * fix: relationshipstatus to backbonerelationshipstatus * fix: relattionshipstatus DVE import * fix: relationshipstatus test utils * fix: relationshipstatus tests * refactor: remove messageRecipientDeleted event * chore: backbone bump * fix: renamings, asyncs, no skipped tests * Revert "fix: relationshipstatus tests" This reverts commit 9cdf5ed0ce376edeea5b6b190a9669a0ebc8ecb7. * Revert "fix: relationshipstatus test utils" This reverts commit 30c735434e8e9f5854b53502859d6a3d49f60f54. * Revert "fix: relattionshipstatus DVE import" This reverts commit 8784cb76f6ad9f545b08cc26fca6810f30cfaa8f. * Revert "fix: relationshipstatus to backbonerelationshipstatus" This reverts commit aa0be624edb819ec1e6f9de01139eeb222065de9. * Revert "refactor: separate runtime and backbone relationship status" This reverts commit 1344b0fb1d3a58a7eec70b5d43dbd75c1c0fd1dc. * refactor: remove message content pseudonymization * refactor: namings, imports * test: remove failing pseudonymization tests * fix: apply changed naming * fix: apply changed naming * refactor: move pseudonym to process.env * chore: log instead of error in decompose * refactor: remove awaits * refactor: move decompose into controllers * refactor: notificationsWith -> notificationsExchangedWith * fix: remove await * feat: add CachedMessageRecipient, remove relationshipIds * fix: remove pseudonym * refactor: udpate mapper * refactor: update controller * refactor: naming * chore: remove unused vars declaration * refactor: delete data from all collections * chore: remove comment that is now invalid * chore: simplify query * fix: resolve the peer instead of the recipient * fix: update test with anonymizations * chore: uncomment test * refactor: split decompose and cleanup * chore: delete message with 1 recipient * chore: resolve comments * chore: naming & easier reading * fix: decompose in status deletionProposed possible * fix: better syntax * test: add message controller tests * refactor: cleanup way more stuff / align naming * chore: formatting * chore: formatting imports and stuff * chore: re-add comment * fix: e2e * fix: re-apply message cache updates after deletion * test: move decomposition to extra file * refactor: make behaviour more failure-proof * chore: formatting * fix: async jest * chore: version bumps * chore: build schemas * fix: typo * refactor: un-parallelize deletions * fix: missing await * refactor: use for * test: add consumption tests for decompose * ffix: readd requestItemProcessors * fix: clear consumption decompose in testutil * chore: naming * fix: update doomed message tests * refactor: update message decrypt logic * fix: remove cleanupping files * chore: update error message * refactor: id2 -> did:e:... * test: address test regex * fix: test name * test: add transport decompose tests * refactor: move and skip the files test * fix: allow both undefined and null in test * refactor: remove skipped test * test: add tests * fix: test syntax error --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Julian König --- packages/consumption/test/core/TestUtil.ts | 21 ++++++ .../AttributeListenersController.test.ts | 11 +++ .../attributes/AttributesController.test.ts | 38 +++++++++++ .../notifications/NotificationEnd2End.test.ts | 10 +++ .../NotificationsController.test.ts | 68 +++++++++++++++++++ .../modules/requests/DeleteRequest.test.ts | 66 ++++++++++++++++++ .../runtime/test/transport/account.test.ts | 3 +- .../messages/MessageController.test.ts | 26 +++++-- .../RelationshipTemplateController.test.ts | 14 ++++ .../relationships/decomposition.test.ts | 41 +++++++++-- .../modules/tokens/TokenController.test.ts | 16 +++++ 11 files changed, 303 insertions(+), 11 deletions(-) create mode 100644 packages/consumption/test/modules/notifications/NotificationsController.test.ts create mode 100644 packages/consumption/test/modules/requests/DeleteRequest.test.ts diff --git a/packages/consumption/test/core/TestUtil.ts b/packages/consumption/test/core/TestUtil.ts index 099a9fc23..2a28ff8ee 100644 --- a/packages/consumption/test/core/TestUtil.ts +++ b/packages/consumption/test/core/TestUtil.ts @@ -270,6 +270,27 @@ export class TestUtil { return [acceptedRelationshipFromSelf, acceptedRelationshipPeer]; } + public static async terminateRelationship( + from: AccountController, + to: AccountController + ): Promise<{ terminatedRelationshipFromSelf: Relationship; terminatedRelationshipPeer: Relationship }> { + const relationshipId = (await from.relationships.getRelationshipToIdentity(to.identity.address))!.id; + const terminatedRelationshipFromSelf = await from.relationships.terminate(relationshipId); + const terminatedRelationshipPeer = (await TestUtil.syncUntil(to, (syncResult) => syncResult.relationships.length > 0)).relationships[0]; + + return { terminatedRelationshipFromSelf, terminatedRelationshipPeer }; + } + + public static async decomposeRelationship(fromAccount: AccountController, fromConsumption: ConsumptionController, to: AccountController): Promise { + const relationship = (await fromAccount.relationships.getRelationshipToIdentity(to.identity.address))!; + await fromAccount.relationships.decompose(relationship.id); + await fromAccount.cleanupDataOfDecomposedRelationship(relationship); + await fromConsumption.cleanupDataOfDecomposedRelationship(to.identity.address, relationship.id); + const decomposedRelationshipPeer = (await TestUtil.syncUntil(to, (syncResult) => syncResult.relationships.length > 0)).relationships[0]; + + return decomposedRelationshipPeer; + } + /** * SyncEvents in the backbone are only enventually consistent. This means that if you send a message now and * get all SyncEvents right after, you cannot rely on getting a NewMessage SyncEvent right away. So instead diff --git a/packages/consumption/test/modules/attributeListeners/AttributeListenersController.test.ts b/packages/consumption/test/modules/attributeListeners/AttributeListenersController.test.ts index 3b2b04c88..9f0637235 100644 --- a/packages/consumption/test/modules/attributeListeners/AttributeListenersController.test.ts +++ b/packages/consumption/test/modules/attributeListeners/AttributeListenersController.test.ts @@ -82,4 +82,15 @@ describe("AttributeListenersController", function () { expect(listeners).toHaveLength(1); expect(listeners.map((l) => l.toJSON())).toContainEqual(expect.objectContaining({ id: listener.id.toString() })); }); + + test("should delete peer attribute listeners", async function () { + const listener = await consumptionController.attributeListeners.createAttributeListener({ + peer: dummyPeer, + query: dummyQuery + }); + + await consumptionController.attributeListeners.deletePeerAttributeListeners(dummyPeer); + const retrievedListener = await consumptionController.attributeListeners.getAttributeListener(listener.id); + expect(retrievedListener).toBeUndefined(); + }); }); diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index 91ec15be8..c020ba6d3 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -2532,4 +2532,42 @@ describe("AttributesController", function () { expect(result).toHaveLength(0); }); }); + + test("should delete attributes exchanged with peer", async function () { + const ownRelationshipAttribute = await consumptionController.attributes.createSharedLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + value: { + "@type": "ProprietaryString", + value: "Some value", + title: "Some title" + }, + owner: consumptionController.accountController.identity.address, + confidentiality: RelationshipAttributeConfidentiality.Public + }), + peer: CoreAddress.from("peerAddress"), + requestReference: CoreId.from("reqRef123") + }); + + const peerRelationshipAttribute = await consumptionController.attributes.createSharedLocalAttribute({ + content: RelationshipAttribute.from({ + key: "AKey", + value: { + "@type": "ProprietaryString", + value: "Some value", + title: "Some title" + }, + owner: consumptionController.accountController.identity.address, + confidentiality: RelationshipAttributeConfidentiality.Public + }), + peer: CoreAddress.from("peerAddress"), + requestReference: CoreId.from("reqRef123") + }); + + await consumptionController.attributes.deleteAttributesExchangedWithPeer(CoreAddress.from("peerAddress")); + const ownAttribute = await consumptionController.attributes.getLocalAttribute(ownRelationshipAttribute.id); + const peerAttribute = await consumptionController.attributes.getLocalAttribute(peerRelationshipAttribute.id); + expect(ownAttribute).toBeUndefined(); + expect(peerAttribute).toBeUndefined(); + }); }); diff --git a/packages/consumption/test/modules/notifications/NotificationEnd2End.test.ts b/packages/consumption/test/modules/notifications/NotificationEnd2End.test.ts index c8bdd4219..8f98d7993 100644 --- a/packages/consumption/test/modules/notifications/NotificationEnd2End.test.ts +++ b/packages/consumption/test/modules/notifications/NotificationEnd2End.test.ts @@ -150,4 +150,14 @@ describe("End2End Notification via Messages", function () { await rConsumptionController.notifications.processOpenNotifactionsReceivedByCurrentDevice(); expect(TestNotificationItemProcessor.processedItems).toHaveLength(0); }); + + test("recipient: delete notification while decomposing relationship", async function () { + await TestUtil.terminateRelationship(rAccountController, sAccountController); + await TestUtil.decomposeRelationship(rAccountController, rConsumptionController, sAccountController); + + const notification = await rNotificationsCollection.findOne({ + id: rLocalNotification.id.toString() + }); + expect(notification).toBeFalsy(); + }); }); diff --git a/packages/consumption/test/modules/notifications/NotificationsController.test.ts b/packages/consumption/test/modules/notifications/NotificationsController.test.ts new file mode 100644 index 000000000..c98d1aa23 --- /dev/null +++ b/packages/consumption/test/modules/notifications/NotificationsController.test.ts @@ -0,0 +1,68 @@ +import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; +import { Notification } from "@nmshd/content"; +import { AccountController, CoreAddress, CoreDate, CoreId, SynchronizedCollection, Transport } from "@nmshd/transport"; +import { ConsumptionController, ConsumptionIds, LocalNotification, LocalNotificationSource, LocalNotificationStatus } from "../../../src"; +import { TestUtil } from "../../core/TestUtil"; +import { TestNotificationItem, TestNotificationItemProcessor } from "./testHelpers/TestNotificationItem"; + +describe("End2End Notification via Messages", function () { + let connection: IDatabaseConnection; + let transport: Transport; + + let sAccountController: AccountController; + let sConsumptionController: ConsumptionController; + let rAccountController: AccountController; + let rConsumptionController: ConsumptionController; + let localNotification: LocalNotification; + + let rNotificationsCollection: SynchronizedCollection; + + beforeAll(async function () { + connection = await TestUtil.createConnection(); + transport = TestUtil.createTransport(connection); + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 2); + + ({ accountController: sAccountController, consumptionController: sConsumptionController } = accounts[0]); + sConsumptionController.notifications["processorRegistry"].registerProcessor(TestNotificationItem, TestNotificationItemProcessor); + ({ accountController: rAccountController, consumptionController: rConsumptionController } = accounts[1]); + rConsumptionController.notifications["processorRegistry"].registerProcessor(TestNotificationItem, TestNotificationItemProcessor); + rNotificationsCollection = await rConsumptionController.accountController.getSynchronizedCollection("Notifications"); + + await TestUtil.addRelationship(sAccountController, rAccountController); + + const id = await ConsumptionIds.notification.generate(); + localNotification = LocalNotification.from({ + id, + content: Notification.from({ id, items: [TestNotificationItem.from({})] }), + isOwn: true, + peer: CoreAddress.from("anAddress"), + createdAt: CoreDate.utc(), + status: LocalNotificationStatus.Open, + source: LocalNotificationSource.message(CoreId.from("anId")) + }); + + await sAccountController.messages.sendMessage({ + content: localNotification.content, + recipients: [rAccountController.identity.address] + }); + }); + + afterAll(async function () { + await connection.close(); + }); + + beforeEach(function () { + TestNotificationItemProcessor.reset(); + }); + + test("recipient: delete notification while decomposing relationship", async function () { + await rConsumptionController.notifications.deleteNotificationsExchangedWithPeer(sAccountController.identity.address); + + const notification = await rNotificationsCollection.findOne({ + id: localNotification.id.toString() + }); + expect(notification).toBeFalsy(); + }); +}); diff --git a/packages/consumption/test/modules/requests/DeleteRequest.test.ts b/packages/consumption/test/modules/requests/DeleteRequest.test.ts new file mode 100644 index 000000000..846c9956e --- /dev/null +++ b/packages/consumption/test/modules/requests/DeleteRequest.test.ts @@ -0,0 +1,66 @@ +import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; +import { Request } from "@nmshd/content"; +import { AccountController, Message, Transport } from "@nmshd/transport"; +import { ConsumptionController, LocalRequest } from "../../../src"; +import { TestUtil } from "../../core/TestUtil"; +import { TestRequestItem } from "./testHelpers/TestRequestItem"; +import { TestRequestItemProcessor } from "./testHelpers/TestRequestItemProcessor"; + +let connection: IDatabaseConnection; +let transport: Transport; +describe("Delete requests", function () { + let sAccountController: AccountController; + let sConsumptionController: ConsumptionController; + let rAccountController: AccountController; + let rConsumptionController: ConsumptionController; + + let sLocalRequest: LocalRequest; + let rMessageWithRequest: Message; + let rLocalRequest: LocalRequest; + + beforeAll(async function () { + connection = await TestUtil.createConnection(); + transport = TestUtil.createTransport(connection); + await transport.init(); + + const accounts = await TestUtil.provideAccounts(transport, 2); + + ({ accountController: sAccountController, consumptionController: sConsumptionController } = accounts[0]); + sConsumptionController.incomingRequests["processorRegistry"].registerProcessor(TestRequestItem, TestRequestItemProcessor); + ({ accountController: rAccountController, consumptionController: rConsumptionController } = accounts[1]); + rConsumptionController.incomingRequests["processorRegistry"].registerProcessor(TestRequestItem, TestRequestItemProcessor); + + await TestUtil.addRelationship(sAccountController, rAccountController); + sLocalRequest = await sConsumptionController.outgoingRequests.create({ + content: Request.from({ + items: [TestRequestItem.from({ mustBeAccepted: false })] + }), + peer: rAccountController.identity.address + }); + + await sAccountController.messages.sendMessage({ + content: sLocalRequest.content, + recipients: [rAccountController.identity.address] + }); + const messages = await TestUtil.syncUntilHasMessages(rAccountController); + rMessageWithRequest = messages[0]; + rLocalRequest = await rConsumptionController.incomingRequests.received({ + receivedRequest: rMessageWithRequest.cache!.content as Request, + requestSourceObject: rMessageWithRequest + }); + }); + + afterAll(async function () { + await connection.close(); + }); + + test("requests should be deleted after decomposing", async function () { + await TestUtil.terminateRelationship(sAccountController, rAccountController); + await TestUtil.decomposeRelationship(sAccountController, sConsumptionController, rAccountController); + await TestUtil.decomposeRelationship(rAccountController, rConsumptionController, sAccountController); + const sRequest = await sConsumptionController.outgoingRequests.getOutgoingRequest(sLocalRequest.id); + const rRequest = await sConsumptionController.incomingRequests.getIncomingRequest(rLocalRequest.id); + expect(sRequest).toBeUndefined(); + expect(rRequest).toBeUndefined(); + }); +}); diff --git a/packages/runtime/test/transport/account.test.ts b/packages/runtime/test/transport/account.test.ts index bbde45123..f179855b5 100644 --- a/packages/runtime/test/transport/account.test.ts +++ b/packages/runtime/test/transport/account.test.ts @@ -72,8 +72,7 @@ describe("IdentityInfo", () => { expect(identityInfoResult).toBeSuccessful(); const identityInfo = identityInfoResult.value; - expect(identityInfo.address.length).toBeGreaterThanOrEqual(35); - expect(identityInfo.address).toMatch(/^did:e:/); + expect(identityInfo.address).toMatch(/^did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}$/); expect(identityInfo.publicKey).toHaveLength(82); }); }); diff --git a/packages/transport/test/modules/messages/MessageController.test.ts b/packages/transport/test/modules/messages/MessageController.test.ts index ef98544d0..c0d1ad9bb 100644 --- a/packages/transport/test/modules/messages/MessageController.test.ts +++ b/packages/transport/test/modules/messages/MessageController.test.ts @@ -1,5 +1,5 @@ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; -import { AccountController, CoreDate, CoreId, Message, Transport } from "../../../src"; +import { AccountController, CoreDate, CoreId, Message, Relationship, Transport } from "../../../src"; import { TestUtil } from "../../testHelpers/TestUtil"; describe("MessageController", function () { @@ -13,7 +13,9 @@ describe("MessageController", function () { let tempId1: CoreId; let tempId2: CoreId; let tempDate: CoreDate; + let relationship: Relationship; let relationshipId: CoreId; + let relationship2: Relationship; function expectValidMessages(sentMessage: Message, receivedMessage: Message, nowMinusSeconds: CoreDate) { expect(sentMessage.id.toString()).toBe(receivedMessage.id.toString()); @@ -46,9 +48,9 @@ describe("MessageController", function () { sender = accounts[0]; recipient = accounts[1]; recipient2 = accounts[2]; - const rels = await TestUtil.addRelationship(sender, recipient); - await TestUtil.addRelationship(sender, recipient2); - relationshipId = rels.acceptedRelationshipFromSelf.id; + relationship = (await TestUtil.addRelationship(sender, recipient)).acceptedRelationshipFromSelf; + relationship2 = (await TestUtil.addRelationship(sender, recipient2)).acceptedRelationshipFromSelf; + relationshipId = relationship.id; }); afterAll(async function () { @@ -199,6 +201,22 @@ describe("MessageController", function () { expectValidMessages(sentMessage, receivedMessage2, tempDate); }); + test("should delete / pseudonymize messages", async function () { + const multipleRecipientsMessage = await TestUtil.sendMessage(sender, [recipient, recipient2]); + const message = await TestUtil.sendMessage(sender, recipient); + await sender.messages.cleanupMessagesOfDecomposedRelationship(relationship); + + expect(await sender.messages.getMessage(message.id)).toBeUndefined(); + const pseudonymizedMessage = await sender.messages.getMessage(multipleRecipientsMessage.id); + expect(pseudonymizedMessage).toBeDefined(); + expect(pseudonymizedMessage!.cache!.recipients.map((r) => [r.address, r.relationshipId])).toStrictEqual( + expect.arrayContaining([ + [await TestUtil.generateAddressPseudonym(process.env.NMSHD_TEST_BASEURL!), undefined], + [recipient2.identity.address, relationship2.id] + ]) + ); + }); + describe("Relationship Termination", function () { beforeAll(async function () { await TestUtil.terminateRelationship(sender, recipient); diff --git a/packages/transport/test/modules/relationshipTemplates/RelationshipTemplateController.test.ts b/packages/transport/test/modules/relationshipTemplates/RelationshipTemplateController.test.ts index 8788ffc2b..3aec40aa7 100644 --- a/packages/transport/test/modules/relationshipTemplates/RelationshipTemplateController.test.ts +++ b/packages/transport/test/modules/relationshipTemplates/RelationshipTemplateController.test.ts @@ -133,4 +133,18 @@ describe("RelationshipTemplateController", function () { expectValidRelationshipTemplates(sentRelationshipTemplate, receivedRelationshipTemplate, tempDate); }); + + test("should clean up relationship templates of a relationship", async function () { + const relationship = (await TestUtil.addRelationship(sender, recipient)).acceptedRelationshipFromSelf; + + const tokenReference = await TestUtil.sendRelationshipTemplateAndToken(recipient); + const templateId = (await TestUtil.fetchRelationshipTemplateFromTokenReference(sender, tokenReference)).id; + + await sender.relationshipTemplates.cleanupTemplatesOfDecomposedRelationship(relationship); + + const templateForRelationship = await sender.relationshipTemplates.getRelationshipTemplate(relationship.cache!.template.id); + const otherTemplate = await sender.relationshipTemplates.getRelationshipTemplate(templateId); + expect(templateForRelationship).toBeUndefined(); + expect(otherTemplate).toBeUndefined(); + }); }); diff --git a/packages/transport/test/modules/relationships/decomposition.test.ts b/packages/transport/test/modules/relationships/decomposition.test.ts index e2ffc8883..985aaabd0 100644 --- a/packages/transport/test/modules/relationships/decomposition.test.ts +++ b/packages/transport/test/modules/relationships/decomposition.test.ts @@ -1,17 +1,22 @@ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; -import { AccountController, CoreId, Transport } from "../../../src"; +import { Serializable } from "@js-soft/ts-serval"; +import { AccountController, CoreDate, CoreId, Relationship, Transport } from "../../../src"; import { TestUtil } from "../../testHelpers/TestUtil"; -describe("Relationship Decomposition", function () { +describe("Data cleanup after relationship decomposition", function () { let connection: IDatabaseConnection; let transport: Transport; let sender: AccountController; let recipient1: AccountController; + let recipient2: AccountController; + let relationship: Relationship; let relationship2Id: CoreId; - let recipient2: AccountController; + + let tokenId: CoreId; + let templateId: CoreId; beforeAll(async function () { connection = await TestUtil.createDatabaseConnection(); @@ -24,12 +29,26 @@ describe("Relationship Decomposition", function () { recipient1 = accounts[1]; recipient2 = accounts[2]; - await TestUtil.addRelationship(sender, recipient1); + relationship = (await TestUtil.addRelationship(sender, recipient1)).acceptedRelationshipFromSelf; relationship2Id = (await TestUtil.addRelationship(sender, recipient2)).acceptedRelationshipFromSelf.id; await TestUtil.sendMessage(sender, [recipient1, recipient2]); - await TestUtil.syncUntilHasMessages(recipient1); + + // generate another template not used for a relationship + const tokenReference = await TestUtil.sendRelationshipTemplateAndToken(recipient1); + templateId = (await TestUtil.fetchRelationshipTemplateFromTokenReference(sender, tokenReference)).id; + + const expiresAt = CoreDate.utc().add({ minutes: 5 }); + const content = Serializable.fromAny({ content: "TestToken" }); + const sentToken = await recipient1.tokens.sendToken({ + content, + expiresAt, + ephemeral: false + }); + const reference = sentToken.toTokenReference().truncate(); + tokenId = (await sender.tokens.loadPeerTokenByTruncated(reference, false)).id; + await TestUtil.terminateRelationship(sender, recipient1); await TestUtil.decomposeRelationship(sender, recipient1); }); @@ -41,6 +60,18 @@ describe("Relationship Decomposition", function () { await connection.close(); }); + test("templates should be deleted", async function () { + const templateForRelationship = await sender.relationshipTemplates.getRelationshipTemplate(relationship.cache!.template.id); + const otherTemplate = await sender.relationshipTemplates.getRelationshipTemplate(templateId); + expect(templateForRelationship).toBeUndefined(); + expect(otherTemplate).toBeUndefined(); + }); + + test("token should be deleted", async function () { + const token = await sender.tokens.getToken(tokenId); + expect(token).toBeUndefined(); + }); + test("messages should be deleted/pseudonymized", async function () { const messages = await sender.messages.getMessages(); expect(messages).toHaveLength(1); diff --git a/packages/transport/test/modules/tokens/TokenController.test.ts b/packages/transport/test/modules/tokens/TokenController.test.ts index a81dd2f63..78ea382c1 100644 --- a/packages/transport/test/modules/tokens/TokenController.test.ts +++ b/packages/transport/test/modules/tokens/TokenController.test.ts @@ -149,4 +149,20 @@ describe("TokenController", function () { testTokens(sentTokens[0], receivedTokens[0], tempDate); testTokens(sentTokens[1], receivedTokens[1], tempDate); }); + + test("should delete a token", async function () { + const expiresAt = CoreDate.utc().add({ minutes: 5 }); + const content = Serializable.fromAny({ content: "TestToken" }); + const sentToken = await recipient.tokens.sendToken({ + content, + expiresAt, + ephemeral: false + }); + const reference = sentToken.toTokenReference().truncate(); + const tokenId = (await sender.tokens.loadPeerTokenByTruncated(reference, false)).id; + + await sender.tokens.cleanupTokensOfDecomposedRelationship(recipient.identity.address); + const token = await sender.tokens.getToken(tokenId); + expect(token).toBeUndefined(); + }); }); From 6d96acfa3c9741fe7e6215a9c01f3301caac199c Mon Sep 17 00:00:00 2001 From: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com> Date: Tue, 23 Jul 2024 17:36:17 +0200 Subject: [PATCH 24/40] Feature/Set the communication language (#198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add backbone routes * fix: only set for backbone client * feat: add setting communication language * refactor: remove comment * refactor: remove comment * fix: use iso639 language * fix/test: use iso639 validation properly * chore: version bump * test: also check error message * refactor: variable renaming * refactor: enum as input * refactor: name updateDevice * test: add negative tests * chore: version bump * fix: error handling * fix: use correct client * test: fix expected error message --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Julian König Co-authored-by: Julian König <33655937+jkoenig134@users.noreply.github.com> --- package-lock.json | 18 +- packages/app-runtime/package.json | 4 +- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- packages/runtime/package.json | 8 +- .../facades/transport/DevicesFacade.ts | 11 +- .../runtime/src/useCases/common/Schemas.ts | 207 ++++++++++++++++++ .../devices/SetCommunicationLanguage.ts | 30 +++ .../src/useCases/transport/devices/index.ts | 1 + .../runtime/test/transport/devices.test.ts | 11 + packages/transport/package.json | 2 +- .../src/modules/devices/DeviceController.ts | 7 + .../devices/backbone/BackboneUpdateDevice.ts | 3 + .../devices/backbone/DeviceAuthClient.ts | 5 + packages/transport/src/modules/index.ts | 1 + .../devices/SetCommunicationLanguage.test.ts | 38 ++++ 16 files changed, 330 insertions(+), 20 deletions(-) create mode 100644 packages/runtime/src/useCases/transport/devices/SetCommunicationLanguage.ts create mode 100644 packages/transport/src/modules/devices/backbone/BackboneUpdateDevice.ts create mode 100644 packages/transport/test/modules/devices/SetCommunicationLanguage.test.ts diff --git a/package-lock.json b/package-lock.json index 7a5ebdd45..6a3fb90ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12546,7 +12546,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.9", + "version": "5.0.0-alpha.10", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12560,12 +12560,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.9" + "@nmshd/runtime": "^5.0.0-alpha.10" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.9", + "version": "5.0.0-alpha.10", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12587,7 +12587,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.9", + "version": "5.0.0-alpha.10", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12612,17 +12612,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.9", + "version": "5.0.0-alpha.10", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.9", - "@nmshd/content": "5.0.0-alpha.9", + "@nmshd/consumption": "5.0.0-alpha.10", + "@nmshd/content": "5.0.0-alpha.10", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.9", + "@nmshd/transport": "5.0.0-alpha.10", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12654,7 +12654,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.9", + "version": "5.0.0-alpha.10", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index 66c42bfa4..2f4a4dfbe 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.9", + "version": "5.0.0-alpha.10", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.9" + "@nmshd/runtime": "^5.0.0-alpha.10" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 75d7d0135..fcc69036c 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.9", + "version": "5.0.0-alpha.10", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index 1d15e6143..58ba3f15b 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.9", + "version": "5.0.0-alpha.10", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index cc3d3924c..88651b915 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.9", + "version": "5.0.0-alpha.10", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.9", - "@nmshd/content": "5.0.0-alpha.9", + "@nmshd/consumption": "5.0.0-alpha.10", + "@nmshd/content": "5.0.0-alpha.10", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.9", + "@nmshd/transport": "5.0.0-alpha.10", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/extensibility/facades/transport/DevicesFacade.ts b/packages/runtime/src/extensibility/facades/transport/DevicesFacade.ts index 41bd38bce..a24d50b28 100644 --- a/packages/runtime/src/extensibility/facades/transport/DevicesFacade.ts +++ b/packages/runtime/src/extensibility/facades/transport/DevicesFacade.ts @@ -11,8 +11,10 @@ import { GetDeviceOnboardingInfoRequest, GetDeviceOnboardingInfoUseCase, GetDeviceRequest, - GetDeviceUseCase, GetDevicesUseCase, + GetDeviceUseCase, + SetCommunicationLanguageRequest, + SetCommunicationLanguageUseCase, UpdateDeviceRequest, UpdateDeviceUseCase } from "../../../useCases"; @@ -25,7 +27,8 @@ export class DevicesFacade { @Inject private readonly updateDeviceUseCase: UpdateDeviceUseCase, @Inject private readonly deleteDeviceUseCase: DeleteDeviceUseCase, @Inject private readonly getDeviceOnboardingInfoUseCase: GetDeviceOnboardingInfoUseCase, - @Inject private readonly getDeviceOnboardingTokenUseCase: CreateDeviceOnboardingTokenUseCase + @Inject private readonly getDeviceOnboardingTokenUseCase: CreateDeviceOnboardingTokenUseCase, + @Inject private readonly setCommunicationLanguageUseCase: SetCommunicationLanguageUseCase ) {} public async getDevice(request: GetDeviceRequest): Promise> { @@ -55,4 +58,8 @@ export class DevicesFacade { public async deleteDevice(request: DeleteDeviceRequest): Promise> { return await this.deleteDeviceUseCase.execute(request); } + + public async setCommunicationLanguage(request: SetCommunicationLanguageRequest): Promise> { + return await this.setCommunicationLanguageUseCase.execute(request); + } } diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index c9e422521..2ebda13a5 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -20759,6 +20759,213 @@ export const GetDeviceOnboardingInfoRequest: any = { } } +export const SetCommunicationLanguageRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/SetCommunicationLanguageRequest", + "definitions": { + "SetCommunicationLanguageRequest": { + "type": "object", + "properties": { + "communicationLanguage": { + "$ref": "#/definitions/LanguageISO639" + } + }, + "required": [ + "communicationLanguage" + ], + "additionalProperties": false + }, + "LanguageISO639": { + "type": "string", + "enum": [ + "aa", + "ab", + "ae", + "af", + "ak", + "am", + "an", + "ar", + "as", + "av", + "ay", + "az", + "ba", + "be", + "bg", + "bi", + "bm", + "bn", + "bo", + "br", + "bs", + "ca", + "ce", + "ch", + "co", + "cr", + "cs", + "cu", + "cv", + "cy", + "da", + "de", + "dv", + "dz", + "ee", + "el", + "en", + "eo", + "es", + "et", + "eu", + "fa", + "ff", + "fi", + "fj", + "fo", + "fr", + "fy", + "ga", + "gd", + "gl", + "gn", + "gu", + "gv", + "ha", + "he", + "hi", + "ho", + "hr", + "ht", + "hu", + "hy", + "hz", + "ia", + "id", + "ie", + "ig", + "ii", + "ik", + "io", + "is", + "it", + "iu", + "ja", + "jv", + "ka", + "kg", + "ki", + "kj", + "kk", + "kl", + "km", + "kn", + "ko", + "kr", + "ks", + "ku", + "kv", + "kw", + "ky", + "la", + "lb", + "lg", + "li", + "ln", + "lo", + "lt", + "lu", + "lv", + "mg", + "mh", + "mi", + "mk", + "ml", + "mn", + "mr", + "ms", + "mt", + "my", + "na", + "nb", + "nd", + "ne", + "ng", + "nl", + "nn", + "no", + "nr", + "nv", + "ny", + "oc", + "oj", + "om", + "or", + "os", + "pa", + "pi", + "pl", + "ps", + "pt", + "qu", + "rm", + "rn", + "ro", + "ru", + "rw", + "sa", + "sc", + "sd", + "se", + "sg", + "si", + "sk", + "sl", + "sm", + "sn", + "so", + "sq", + "sr", + "ss", + "st", + "su", + "sv", + "sw", + "ta", + "te", + "tg", + "th", + "ti", + "tk", + "tl", + "tn", + "to", + "tr", + "ts", + "tt", + "tw", + "ty", + "ug", + "uk", + "ur", + "uz", + "ve", + "vi", + "vo", + "wa", + "wo", + "xh", + "yi", + "yo", + "za", + "zh", + "zu" + ] + } + } +} + export const UpdateDeviceRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/UpdateDeviceRequest", diff --git a/packages/runtime/src/useCases/transport/devices/SetCommunicationLanguage.ts b/packages/runtime/src/useCases/transport/devices/SetCommunicationLanguage.ts new file mode 100644 index 000000000..189745f92 --- /dev/null +++ b/packages/runtime/src/useCases/transport/devices/SetCommunicationLanguage.ts @@ -0,0 +1,30 @@ +import { Result } from "@js-soft/ts-utils"; +import { LanguageISO639 } from "@nmshd/content"; +import { DeviceController } from "@nmshd/transport"; +import { Inject } from "typescript-ioc"; +import { SchemaRepository, SchemaValidator, UseCase } from "../../common"; + +export interface SetCommunicationLanguageRequest { + communicationLanguage: LanguageISO639; +} + +class Validator extends SchemaValidator { + public constructor(@Inject schemaRepository: SchemaRepository) { + super(schemaRepository.getSchema("SetCommunicationLanguageRequest")); + } +} + +export class SetCommunicationLanguageUseCase extends UseCase { + public constructor( + @Inject private readonly deviceController: DeviceController, + @Inject validator: Validator + ) { + super(validator); + } + + protected async executeInternal(request: SetCommunicationLanguageRequest): Promise> { + await this.deviceController.setCommunicationLanguage(request.communicationLanguage); + + return Result.ok(undefined); + } +} diff --git a/packages/runtime/src/useCases/transport/devices/index.ts b/packages/runtime/src/useCases/transport/devices/index.ts index 25762c470..6d43c41ad 100644 --- a/packages/runtime/src/useCases/transport/devices/index.ts +++ b/packages/runtime/src/useCases/transport/devices/index.ts @@ -5,4 +5,5 @@ export * from "./DeviceMapper"; export * from "./GetDevice"; export * from "./GetDeviceOnboardingInfo"; export * from "./GetDevices"; +export * from "./SetCommunicationLanguage"; export * from "./UpdateDevice"; diff --git a/packages/runtime/test/transport/devices.test.ts b/packages/runtime/test/transport/devices.test.ts index 78027a7e8..14f7d6371 100644 --- a/packages/runtime/test/transport/devices.test.ts +++ b/packages/runtime/test/transport/devices.test.ts @@ -1,3 +1,4 @@ +import { LanguageISO639 } from "@nmshd/content"; import { DeviceMapper, TransportServices } from "../../src"; import { RuntimeServiceProvider } from "../lib"; @@ -19,4 +20,14 @@ describe("Devices", () => { const sharedSecret = DeviceMapper.toDeviceSharedSecret(onboardingInfo); expect(sharedSecret).toBeDefined(); }); + + test("should set the communication language", async () => { + const result = await transportServices1.devices.setCommunicationLanguage({ communicationLanguage: LanguageISO639.fr }); + expect(result).toBeSuccessful(); + }); + + test("should not set the communication language with an invalid language", async () => { + const result = await transportServices1.devices.setCommunicationLanguage({ communicationLanguage: "fra" as any as LanguageISO639 }); + expect(result).toBeAnError("communicationLanguage must be equal to one of the allowed values", "error.runtime.validation.invalidPropertyValue"); + }); }); diff --git a/packages/transport/package.json b/packages/transport/package.json index f4cd73c4f..475996387 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.9", + "version": "5.0.0-alpha.10", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/transport/src/modules/devices/DeviceController.ts b/packages/transport/src/modules/devices/DeviceController.ts index b506e9636..61432e9eb 100644 --- a/packages/transport/src/modules/devices/DeviceController.ts +++ b/packages/transport/src/modules/devices/DeviceController.ts @@ -143,6 +143,13 @@ export class DeviceController extends TransportController { }; } + public async setCommunicationLanguage(language: string): Promise { + const result = await this.parent.deviceAuthClient.updateCurrentDevice({ communicationLanguage: language }); + if (result.isError) { + throw result.error; + } + } + public async markAsOffboarded(): Promise { this.device.isOffboarded = true; await this.parent.devices.update(this.device); diff --git a/packages/transport/src/modules/devices/backbone/BackboneUpdateDevice.ts b/packages/transport/src/modules/devices/backbone/BackboneUpdateDevice.ts new file mode 100644 index 000000000..089b01106 --- /dev/null +++ b/packages/transport/src/modules/devices/backbone/BackboneUpdateDevice.ts @@ -0,0 +1,3 @@ +export interface BackboneUpdateDeviceRequest { + communicationLanguage: string; +} diff --git a/packages/transport/src/modules/devices/backbone/DeviceAuthClient.ts b/packages/transport/src/modules/devices/backbone/DeviceAuthClient.ts index 50ba697a1..11530a0ab 100644 --- a/packages/transport/src/modules/devices/backbone/DeviceAuthClient.ts +++ b/packages/transport/src/modules/devices/backbone/DeviceAuthClient.ts @@ -1,6 +1,7 @@ import { RESTClientAuthenticate, RESTClientLogDirective } from "../../../core"; import { ClientResult } from "../../../core/backbone/ClientResult"; import { BackbonePostDevicesRequest, BackbonePostDevicesResponse } from "./BackbonePostDevices"; +import { BackboneUpdateDeviceRequest } from "./BackboneUpdateDevice"; export interface BackbonePutDevicesPasswordRequest { oldPassword: string; @@ -40,4 +41,8 @@ export class DeviceAuthClient extends RESTClientAuthenticate { public async unregisterPushNotificationToken(): Promise> { return await this.delete("/api/v1/Devices/Self/PushNotifications"); } + + public async updateCurrentDevice(value: BackboneUpdateDeviceRequest): Promise> { + return await this.put("/api/v1/Devices/Self", value); + } } diff --git a/packages/transport/src/modules/index.ts b/packages/transport/src/modules/index.ts index ffd4aa48a..410064c57 100644 --- a/packages/transport/src/modules/index.ts +++ b/packages/transport/src/modules/index.ts @@ -30,6 +30,7 @@ export * from "./challenges/ChallengeController"; export * from "./challenges/data/Challenge"; export * from "./challenges/data/ChallengeSigned"; export * from "./devices/backbone/BackbonePostDevices"; +export * from "./devices/backbone/BackboneUpdateDevice"; export * from "./devices/backbone/DeviceAuthClient"; export * from "./devices/backbone/DeviceClient"; export * from "./devices/DeviceController"; diff --git a/packages/transport/test/modules/devices/SetCommunicationLanguage.test.ts b/packages/transport/test/modules/devices/SetCommunicationLanguage.test.ts new file mode 100644 index 000000000..8f3461044 --- /dev/null +++ b/packages/transport/test/modules/devices/SetCommunicationLanguage.test.ts @@ -0,0 +1,38 @@ +import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; +import { AccountController } from "../../../src"; +import { AppDeviceTest } from "../../testHelpers/AppDeviceTest"; +import { DeviceTestParameters } from "../../testHelpers/DeviceTestParameters"; +import { TestUtil } from "../../testHelpers/TestUtil"; + +describe("set the communication language", function () { + let connection: IDatabaseConnection; + + let deviceTest: AppDeviceTest; + + let deviceAccount: AccountController; + + beforeAll(async function () { + connection = await TestUtil.createDatabaseConnection(); + + const parameters = new DeviceTestParameters({ ...TestUtil.createConfig(), datawalletEnabled: true }, connection, TestUtil.loggerFactory); + deviceTest = new AppDeviceTest(parameters); + + await deviceTest.init(); + + deviceAccount = await deviceTest.createAccount(); + }); + + afterAll(async function () { + await deviceTest.close(); + + await connection.close(); + }); + + test("should set the communication language", async function () { + await expect(deviceAccount.activeDevice.setCommunicationLanguage("fr")).resolves.not.toThrow(); + }); + + test("should not set a false communication language", async function () { + await expect(deviceAccount.activeDevice.setCommunicationLanguage("fra")).rejects.toThrow("error.platform.validation.invalidDeviceCommunicationLanguage (400)"); + }); +}); From 56dd362bc8e02ff2a3e84fc1462d7f3443685c80 Mon Sep 17 00:00:00 2001 From: Ruth Di Giacomo <150357721+RuthDiG@users.noreply.github.com> Date: Wed, 24 Jul 2024 09:42:16 +0200 Subject: [PATCH 25/40] refactor/error codes (#179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor/delete unused errors and rename errors * refactor: change SentNotification in SaveSentNotification * feat: add a test in notifications.test.ts * fix: error-message * refactor: rephrasing of error messages * chore: version bump * refactor: more rephrasing of error messages * fix: error message in test * fix: change error message in test * refactor: rechange error message * refactor: last changes in error messages * refactor: rechange saveSentNotification and version bump * fix: DeviceDeletion.test * refactor: further changes * chore: version bump * refactor: some other changes * chore: version bump * chore: version bump --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Julian König <33655937+jkoenig134@users.noreply.github.com> --- package-lock.json | 18 ++--- packages/app-runtime/package.json | 4 +- packages/consumption/package.json | 2 +- .../consumption/src/consumption/CoreErrors.ts | 72 +++++++++---------- .../src/modules/common/ValidationResult.ts | 8 ++- .../notifications/NotificationsController.ts | 2 +- .../attributes/AttributesController.test.ts | 4 +- .../notifications/NotificationEnd2End.test.ts | 2 +- ...SucceededNotificationItemProcessor.test.ts | 2 +- .../DecideRequestParametersValidator.test.ts | 9 +-- .../IncomingRequestsController.test.ts | 15 ++-- .../OutgoingRequestsController.test.ts | 14 ++-- packages/content/package.json | 2 +- packages/runtime/package.json | 8 +-- .../src/useCases/common/RuntimeErrors.ts | 37 +++++----- .../notifications/SentNotification.ts | 2 +- .../test/consumption/notifications.test.ts | 21 ++++-- packages/transport/package.json | 2 +- packages/transport/src/core/CoreErrors.ts | 36 ++++------ .../modules/devices/DeviceDeletion.test.ts | 4 +- 20 files changed, 139 insertions(+), 125 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a3fb90ee..d903e22ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12546,7 +12546,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.10", + "version": "5.0.0-alpha.11", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12560,12 +12560,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.10" + "@nmshd/runtime": "^5.0.0-alpha.11" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.10", + "version": "5.0.0-alpha.11", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12587,7 +12587,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.10", + "version": "5.0.0-alpha.11", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12612,17 +12612,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.10", + "version": "5.0.0-alpha.11", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.10", - "@nmshd/content": "5.0.0-alpha.10", + "@nmshd/consumption": "5.0.0-alpha.11", + "@nmshd/content": "5.0.0-alpha.11", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.10", + "@nmshd/transport": "5.0.0-alpha.11", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12654,7 +12654,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.10", + "version": "5.0.0-alpha.11", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index 2f4a4dfbe..c1d3d200d 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.10", + "version": "5.0.0-alpha.11", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.10" + "@nmshd/runtime": "^5.0.0-alpha.11" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index fcc69036c..161f05ead 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.10", + "version": "5.0.0-alpha.11", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/consumption/src/consumption/CoreErrors.ts b/packages/consumption/src/consumption/CoreErrors.ts index b117a6bb5..6f41b2fea 100644 --- a/packages/consumption/src/consumption/CoreErrors.ts +++ b/packages/consumption/src/consumption/CoreErrors.ts @@ -15,54 +15,54 @@ class Attributes { public successionMustNotChangeKey() { return new CoreError( "error.consumption.attributes.successionMustNotChangeKey", - "The predecessor attribute's key does not match that of the successor. The succession of a relationship attribute must not change the key." + "The predecessor RelationshipAttribute's key does not match that of the successor. The succession of a RelationshipAttribute must not change the key." ); } public successionPeerIsNotOwner() { - return new CoreError("error.consumption.attributes.successionPeerIsNotOwner", "The peer of the succeeded attribute is not its owner. This may be an attempt of spoofing."); + return new CoreError("error.consumption.attributes.successionPeerIsNotOwner", "The peer of the succeeded Attribute is not its owner. This may be an attempt of spoofing."); } public predecessorSourceAttributeIsNotRepositoryAttribute() { - return new CoreError("error.consumption.attributes.predecessorSourceAttributeIsNotRepositoryAttribute", "Predecessor source attribute is not a repository attribute."); + return new CoreError("error.consumption.attributes.predecessorSourceAttributeIsNotRepositoryAttribute", "Predecessor sourceAttribute is not a RepositoryAttribute."); } public successorSourceAttributeIsNotRepositoryAttribute() { - return new CoreError("error.consumption.attributes.successorSourceAttributeIsNotRepositoryAttribute", "Successor source attribute is not a repository attribute."); + return new CoreError("error.consumption.attributes.successorSourceAttributeIsNotRepositoryAttribute", "Successor sourceAttribute is not a RepositoryAttribute."); } public successorSourceDoesNotSucceedPredecessorSource() { return new CoreError( "error.consumption.attributes.successorSourceDoesNotSucceedPredecessorSource", - "Predecessor source attribute is not succeeded by successor source attribute." + "Predecessor sourceAttribute is not succeeded by successor sourceAttribute." ); } public predecessorSourceContentIsNotEqualToCopyContent() { return new CoreError( "error.consumption.attributes.predecessorSourceContentIsNotEqualToCopyContent", - "Predecessor source attribute content doesn't match predecessor shared attribute copy." + "Predecessor sourceAttribute content doesn't match the content of the predecessor shared Attribute copy." ); } public successorSourceContentIsNotEqualToCopyContent() { return new CoreError( "error.consumption.attributes.successorSourceContentIsNotEqualToCopyContent", - "Successor source attribute content doesn't match successor shared attribute copy." + "Successor sourceAttribute content doesn't match the content of the successor shared Attribute copy." ); } public cannotSucceedChildOfComplexAttribute(parentId: string | CoreId) { return new CoreError( "error.consumption.attributes.cannotSucceedChildOfComplexAttribute", - `The attribute you want to succeed is child attribute of a complex attribute (id: ${parentId}), and cannot be succeeded on its own. Instead, succeed the parent which will implicitly succeed all its children.` + `The Attribute you want to succeed is a child Attribute of a complex Attribute (id: '${parentId}'), and cannot be succeeded on its own. Instead, succeed the parent which will implicitly succeed all its children.` ); } public successorMustNotYetExist() { return new CoreError( "error.consumption.attributes.successorMustNotYetExist", - "The predecessor attribute's successor must not exist. It will be created by the succession handlers and must not be created manually." + "The predecessor Attribute's successor must not exist. It will be created by the succession handlers and must not be created manually." ); } @@ -73,57 +73,57 @@ class Attributes { } public predecessorIsNotRepositoryAttribute() { - return new CoreError("error.consumption.attributes.predecessorIsNotRepositoryAttribute", "Predecessor is not a repository attribute."); + return new CoreError("error.consumption.attributes.predecessorIsNotRepositoryAttribute", "Predecessor is not a RepositoryAttribute."); } public predecessorIsNotOwnSharedIdentityAttribute() { - return new CoreError("error.consumption.attributes.predecessorIsNotOwnSharedIdentityAttribute", "Predecessor is not an own shared identity attribute."); + return new CoreError("error.consumption.attributes.predecessorIsNotOwnSharedIdentityAttribute", "Predecessor is not an own shared IdentityAttribute."); } public predecessorIsNotPeerSharedIdentityAttribute() { - return new CoreError("error.consumption.attributes.predecessorIsNotPeerSharedIdentityAttribute", "Predecessor is not a peer shared identity attribute."); + return new CoreError("error.consumption.attributes.predecessorIsNotPeerSharedIdentityAttribute", "Predecessor is not a peer shared IdentityAttribute."); } public predecessorIsNotOwnSharedRelationshipAttribute() { - return new CoreError("error.consumption.attributes.predecessorIsNotOwnSharedRelationshipAttribute", "Predecessor is not an own shared relationship attribute."); + return new CoreError("error.consumption.attributes.predecessorIsNotOwnSharedRelationshipAttribute", "Predecessor is not an own shared RelationshipAttribute."); } public predecessorIsNotPeerSharedRelationshipAttribute() { - return new CoreError("error.consumption.attributes.predecessorIsNotPeerSharedRelationshipAttribute", "Predecessor is not a peer shared relationship attribute."); + return new CoreError("error.consumption.attributes.predecessorIsNotPeerSharedRelationshipAttribute", "Predecessor is not a peer shared RelationshipAttribute."); } public predecessorIsNotThirdPartyOwnedRelationshipAttribute() { - return new CoreError("error.consumption.attributes.predecessorIsNotThirdPartyOwnedRelationshipAttribute", "Predecessor is not a third party owned relationship attribute."); + return new CoreError("error.consumption.attributes.predecessorIsNotThirdPartyOwnedRelationshipAttribute", "Predecessor is not a third party owned RelationshipAttribute."); } public successorIsNotRepositoryAttribute() { - return new CoreError("error.consumption.attributes.successorIsNotRepositoryAttribute", "Successor is not a repository attribute."); + return new CoreError("error.consumption.attributes.successorIsNotRepositoryAttribute", "Successor is not a RepositoryAttribute."); } public successorIsNotOwnSharedIdentityAttribute() { - return new CoreError("error.consumption.attributes.successorIsNotOwnSharedIdentityAttribute", "Successor is not an own shared identity attribute."); + return new CoreError("error.consumption.attributes.successorIsNotOwnSharedIdentityAttribute", "Successor is not an own shared IdentityAttribute."); } public successorIsNotPeerSharedIdentityAttribute() { - return new CoreError("error.consumption.attributes.successorIsNotPeerSharedIdentityAttribute", "Successor is not a peer shared identity attribute."); + return new CoreError("error.consumption.attributes.successorIsNotPeerSharedIdentityAttribute", "Successor is not a peer shared IdentityAttribute."); } public successorIsNotOwnSharedRelationshipAttribute() { - return new CoreError("error.consumption.attributes.successorIsNotOwnSharedRelationshipAttribute", "Successor is not an own shared relationship attribute."); + return new CoreError("error.consumption.attributes.successorIsNotOwnSharedRelationshipAttribute", "Successor is not an own shared RelationshipAttribute."); } public successorIsNotPeerSharedRelationshipAttribute() { - return new CoreError("error.consumption.attributes.successorIsNotPeerSharedRelationshipAttribute", "Successor is not a peer shared relationship attribute."); + return new CoreError("error.consumption.attributes.successorIsNotPeerSharedRelationshipAttribute", "Successor is not a peer shared RelationshipAttribute."); } public successorIsNotThirdPartyOwnedRelationshipAttribute() { - return new CoreError("error.consumption.attributes.successorIsNotThirdPartyOwnedRelationshipAttribute", "Successor is not a third party owned relationship attribute."); + return new CoreError("error.consumption.attributes.successorIsNotThirdPartyOwnedRelationshipAttribute", "Successor is not a third party owned RelationshipAttribute."); } public setPredecessorIdDoesNotMatchActualPredecessorId() { return new CoreError( "error.consumption.attributes.setPredecessorIdDoesNotMatchActualPredecessorId", - "The predecessor's id and the explicitly set value for the successor's succeeds field don't match." + "The predecessor's ID and the explicitly set value for the successor's succeeds field don't match." ); } @@ -136,36 +136,36 @@ class Attributes { } public successorSourceAttributeIsNotSpecified() { - return new CoreError("error.consumption.attributes.successorSourceAttributeIsNotSpecified", "You must specify the source attribute of the successor."); + return new CoreError("error.consumption.attributes.successorSourceAttributeIsNotSpecified", "You must specify the sourceAttribute of the successor."); } public successorSourceAttributeDoesNotExist() { - return new CoreError("error.consumption.attributes.successorSourceAttributeDoesNotExist", "The successor source Attribute does not exist."); + return new CoreError("error.consumption.attributes.successorSourceAttributeDoesNotExist", "The successor sourceAttribute does not exist."); } public successionMustNotChangeOwner() { return new CoreError( "error.consumption.attributes.successionMustNotChangeOwner", - "The successor attribute's owner does not match that of the predecessor. An attribute succession must not change the attribute's owner." + "The successor Attribute's owner does not match that of the predecessor. An Attribute succession must not change the Attribute's owner." ); } public successionMustNotChangeValueType() { return new CoreError( "error.consumption.attributes.successionMustNotChangeValueType", - "The successor attribute's value type does not match that of the predecessor. An attribute succession must not change the attribute's value type." + "The successor Attribute's value type does not match that of the predecessor. An Attribute succession must not change the Attribute's value type." ); } public successionMustNotChangeContentType() { return new CoreError( "error.consumption.attributes.successionMustNotChangeContentType", - "The successor attribute's content type does not match that of the predecessor. An attribute succession must not change the attribute's content type, i.e. an identity attribute must not be succeeded by a relationship attribute and v.v." + "The successor Attribute's content type does not match that of the predecessor. An Attribute succession must not change the Attribute's content type, i.e. an IdentityAttribute must not be succeeded by a RelationshipAttribute and v.v." ); } public successionMustNotChangePeer(comment?: string) { - let errorMessage = "The peer of the shared attribute must not change."; + let errorMessage = "The peer of the shared Attribute must not change."; if (comment) errorMessage += ` ${comment}`; return new CoreError("error.consumption.attributes.successionMustNotChangePeer", errorMessage); } @@ -173,18 +173,18 @@ class Attributes { public cannotSucceedAttributesWithASuccessor(successorId: string | CoreId) { return new CoreError( "error.consumption.attributes.cannotSucceedAttributesWithASuccessor", - `The attribute you want to succeed has a successor (id: ${successorId}). You cannot succeed attributes with a successor. Instead, succeed the successor.` + `The Attribute you want to succeed has a successor (id: ${successorId}). You cannot succeed Attributes with a successor. Instead, succeed the successor.` ); } public invalidParentSuccessor(parentSuccessorId: string | CoreId) { - return new CoreError("error.consumption.attributes.invalidParentSuccessor", `The complex parent successor (id: ${parentSuccessorId}) does not exist.`); + return new CoreError("error.consumption.attributes.invalidParentSuccessor", `The complex parent successor (id: '${parentSuccessorId}') does not exist.`); } public cannotSucceedAttributesWithDeletionInfo() { return new CoreError( "error.consumption.attributes.cannotSucceedAttributesWithDeletionInfo", - "You cannot succeed attributes with a deletionInfo, since the peer may have already deleted it or marked it for deletion." + "You cannot succeed Attributes with a deletionInfo, since the peer may have already deleted it or marked it for deletion." ); } @@ -224,28 +224,28 @@ class Attributes { } public isNotSharedAttribute(attributeId: string | CoreId) { - return new CoreError("error.consumption.attributes.isNotSharedAttribute", `The attribute (id: ${attributeId}) is not a shared attribute.`); + return new CoreError("error.consumption.attributes.isNotSharedAttribute", `The Attribute (id: '${attributeId}') is not a shared Attribute.`); } public isNotOwnSharedAttribute(attributeId: string | CoreId) { - return new CoreError("error.consumption.attributes.isNotOwnSharedAttribute", `The attribute (id: ${attributeId}) is not an own shared attribute.`); + return new CoreError("error.consumption.attributes.isNotOwnSharedAttribute", `The Attribute (id: '${attributeId}') is not an own shared Attribute.`); } public isNotPeerSharedAttribute(attributeId: string | CoreId) { - return new CoreError("error.consumption.attributes.isNotPeerSharedAttribute", `The attribute (id: ${attributeId}) is not a peer shared attribute.`); + return new CoreError("error.consumption.attributes.isNotPeerSharedAttribute", `The Attribute (id: '${attributeId}') is not a peer shared Attribute.`); } public isNotThirdPartyOwnedRelationshipAttribute(attributeId: string | CoreId) { return new CoreError( "error.consumption.attributes.isNotThirdPartyOwnedRelationshipAttribute", - `The attribute (id: ${attributeId}) is not a third party owned RelationshipAttribute.` + `The Attribute (id: '${attributeId}') is not a third party owned RelationshipAttribute.` ); } public senderIsNotPeerOfSharedAttribute(senderId: string | CoreAddress, attributeId: string | CoreId) { return new CoreError( "error.consumption.attributes.senderIsNotPeerOfSharedAttribute", - `The sender (id: ${senderId}) is not the peer you shared the attribute (id: ${attributeId}) with.` + `The sender (id: '${senderId}') of the Notification is not the peer you shared the Attribute (id: '${attributeId}') with.` ); } } diff --git a/packages/consumption/src/modules/common/ValidationResult.ts b/packages/consumption/src/modules/common/ValidationResult.ts index c6296f28c..99a817c0c 100644 --- a/packages/consumption/src/modules/common/ValidationResult.ts +++ b/packages/consumption/src/modules/common/ValidationResult.ts @@ -21,7 +21,13 @@ export abstract class ValidationResult { public static fromItems(items: ValidationResult[]): ValidationResult { return items.some((r) => r.isError()) - ? ValidationResult.error(new ApplicationError("inheritedFromItem", "Some child items have errors."), items) + ? ValidationResult.error( + new ApplicationError( + "error.consumption.validation.inheritedFromItem", + "Some child items have errors. If this error occurred during the specification of a Request, call 'canCreate' to get more information." + ), + items + ) : ValidationResult.success(items); } } diff --git a/packages/consumption/src/modules/notifications/NotificationsController.ts b/packages/consumption/src/modules/notifications/NotificationsController.ts index e303734dd..23a8293ac 100644 --- a/packages/consumption/src/modules/notifications/NotificationsController.ts +++ b/packages/consumption/src/modules/notifications/NotificationsController.ts @@ -34,7 +34,7 @@ export class NotificationsController extends ConsumptionBaseController { } public async sent(message: Message): Promise { - if (!message.isOwn) throw new Error("Cannot send a Notification from a foreign message."); + if (!message.isOwn) throw new Error("Cannot mark a LocalNotification as sent from a received Message."); const content = this.extractNotificationFromMessage(message); diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index c020ba6d3..12097fb36 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -1071,7 +1071,7 @@ describe("AttributesController", function () { }); }); - test("should catch if the predecessor is not an own shared identity attribute", async function () { + test("should catch if the predecessor is not an own shared IdentityAttribute", async function () { predecessorOwnSharedIdentityAttribute.shareInfo = undefined; await consumptionController.attributes.updateAttributeUnsafe(predecessorOwnSharedIdentityAttribute); @@ -1084,7 +1084,7 @@ describe("AttributesController", function () { }); }); - test("should catch if the successor is not an own shared identity attribute", async function () { + test("should catch if the successor is not an own shared IdentityAttribute", async function () { successorOwnSharedIdentityAttributeParams.shareInfo = undefined; const validationResult = await consumptionController.attributes.validateOwnSharedIdentityAttributeSuccession( diff --git a/packages/consumption/test/modules/notifications/NotificationEnd2End.test.ts b/packages/consumption/test/modules/notifications/NotificationEnd2End.test.ts index 8f98d7993..060dbf1ff 100644 --- a/packages/consumption/test/modules/notifications/NotificationEnd2End.test.ts +++ b/packages/consumption/test/modules/notifications/NotificationEnd2End.test.ts @@ -66,7 +66,7 @@ describe("End2End Notification via Messages", function () { TestNotificationItemProcessor.reset(); }); - test("sender: sent Notification", async function () { + test("sender: mark LocalNotification as sent", async function () { const localNotification = await sConsumptionController.notifications.sent(sMessageWithNotification); expect(localNotification.status).toStrictEqual(LocalNotificationStatus.Sent); expect(localNotification.content.items[0]).toBeInstanceOf(TestNotificationItem); diff --git a/packages/consumption/test/modules/notifications/itemProcessors/attributeSucceeded/PeerSharedAttributeSucceededNotificationItemProcessor.test.ts b/packages/consumption/test/modules/notifications/itemProcessors/attributeSucceeded/PeerSharedAttributeSucceededNotificationItemProcessor.test.ts index 2e9c75b15..15cd9c32e 100644 --- a/packages/consumption/test/modules/notifications/itemProcessors/attributeSucceeded/PeerSharedAttributeSucceededNotificationItemProcessor.test.ts +++ b/packages/consumption/test/modules/notifications/itemProcessors/attributeSucceeded/PeerSharedAttributeSucceededNotificationItemProcessor.test.ts @@ -243,7 +243,7 @@ describe("PeerSharedAttributeSucceededNotificationItemProcessor", function () { const checkResult = await processor.checkPrerequisitesOfIncomingNotificationItem(notificationItem, notification); expect(checkResult).errorValidationResult({ code: "error.consumption.attributes.successionPeerIsNotOwner", - message: "The peer of the succeeded attribute is not its owner. This may be an attempt of spoofing." + message: "The peer of the succeeded Attribute is not its owner. This may be an attempt of spoofing." }); }); }); diff --git a/packages/consumption/test/modules/requests/DecideRequestParametersValidator.test.ts b/packages/consumption/test/modules/requests/DecideRequestParametersValidator.test.ts index d8c0e039a..be794a482 100644 --- a/packages/consumption/test/modules/requests/DecideRequestParametersValidator.test.ts +++ b/packages/consumption/test/modules/requests/DecideRequestParametersValidator.test.ts @@ -271,8 +271,8 @@ describe("DecideRequestParametersValidator", function () { }, expectedError: { indexPath: [0], - code: "inheritedFromItem", - message: "Some child items have errors." + code: "error.consumption.validation.inheritedFromItem", + message: "Some child items have errors. If this error occurred during the specification of a Request, call 'canCreate' to get more information." } } ]; @@ -315,8 +315,9 @@ describe("DecideRequestParametersValidator", function () { return; } - expect(validationResult.error.code).toBe("inheritedFromItem"); - expect(validationResult.error.message).toBe("Some child items have errors."); + expect(validationResult).errorValidationResult({ + code: "error.consumption.validation.inheritedFromItem" + }); let childResult = validationResult; for (const index of errorIndexPath) childResult = childResult.items[index] as ErrorValidationResult; diff --git a/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts b/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts index 88e384068..d1c80c3b2 100644 --- a/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts +++ b/packages/consumption/test/modules/requests/IncomingRequestsController.test.ts @@ -5,7 +5,6 @@ import { ConsumptionIds, DecideRequestItemGroupParametersJSON, DecideRequestParametersJSON, - ErrorValidationResult, IncomingRequestReceivedEvent, IncomingRequestStatusChangedEvent, LocalRequestStatus @@ -364,16 +363,15 @@ describe("IncomingRequestsController", function () { }); expect(validationResult).errorValidationResult({ - code: "inheritedFromItem", - message: "Some child items have errors." + code: "error.consumption.validation.inheritedFromItem", + message: "Some child items have errors. If this error occurred during the specification of a Request, call 'canCreate' to get more information." }); expect(validationResult.items).toHaveLength(2); expect(validationResult.items[0].isError()).toBe(false); expect(validationResult.items[1].isError()).toBe(true); - expect((validationResult.items[1] as ErrorValidationResult).error.code).toBe("inheritedFromItem"); - expect((validationResult.items[1] as ErrorValidationResult).error.message).toBe("Some child items have errors."); + expect(validationResult.items[1]).errorValidationResult({ code: "error.consumption.validation.inheritedFromItem" }); expect(validationResult.items[1].items).toHaveLength(3); expect(validationResult.items[1].items[0].isError()).toBe(true); @@ -550,16 +548,15 @@ describe("IncomingRequestsController", function () { const validationResult = await When.iCallCanRejectWith(rejectParams); expect(validationResult).errorValidationResult({ - code: "inheritedFromItem", - message: "Some child items have errors." + code: "error.consumption.validation.inheritedFromItem", + message: "Some child items have errors. If this error occurred during the specification of a Request, call 'canCreate' to get more information." }); expect(validationResult.items).toHaveLength(2); expect(validationResult.items[0].isError()).toBe(false); expect(validationResult.items[1].isError()).toBe(true); - expect((validationResult.items[1] as ErrorValidationResult).error.code).toBe("inheritedFromItem"); - expect((validationResult.items[1] as ErrorValidationResult).error.message).toBe("Some child items have errors."); + expect(validationResult.items[1]).errorValidationResult({ code: "error.consumption.validation.inheritedFromItem" }); expect(validationResult.items[1].items).toHaveLength(3); expect(validationResult.items[1].items[0].isError()).toBe(true); diff --git a/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts b/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts index 82e853704..8804727c8 100644 --- a/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts +++ b/packages/consumption/test/modules/requests/OutgoingRequestsController.test.ts @@ -150,8 +150,8 @@ describe("OutgoingRequestsController", function () { } }); expect(validationResult).errorValidationResult({ - code: "inheritedFromItem", - message: "Some child items have errors." + code: "error.consumption.validation.inheritedFromItem", + message: "Some child items have errors. If this error occurred during the specification of a Request, call 'canCreate' to get more information." }); expect(validationResult.items).toHaveLength(2); @@ -183,16 +183,18 @@ describe("OutgoingRequestsController", function () { } }); expect(validationResult).errorValidationResult({ - code: "inheritedFromItem", - message: "Some child items have errors." + code: "error.consumption.validation.inheritedFromItem", + message: "Some child items have errors. If this error occurred during the specification of a Request, call 'canCreate' to get more information." }); expect(validationResult.items).toHaveLength(2); expect(validationResult.items[0].isError()).toBe(false); expect(validationResult.items[1].isError()).toBe(true); - expect((validationResult.items[1] as ErrorValidationResult).error.code).toBe("inheritedFromItem"); - expect((validationResult.items[1] as ErrorValidationResult).error.message).toBe("Some child items have errors."); + expect((validationResult.items[1] as ErrorValidationResult).error.code).toBe("error.consumption.validation.inheritedFromItem"); + expect((validationResult.items[1] as ErrorValidationResult).error.message).toBe( + "Some child items have errors. If this error occurred during the specification of a Request, call 'canCreate' to get more information." + ); expect(validationResult.items[1].items).toHaveLength(1); expect(validationResult.items[1].items[0].isError()).toBe(true); diff --git a/packages/content/package.json b/packages/content/package.json index 58ba3f15b..2a8b4b1b6 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.10", + "version": "5.0.0-alpha.11", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 88651b915..d522be7d0 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.10", + "version": "5.0.0-alpha.11", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.10", - "@nmshd/content": "5.0.0-alpha.10", + "@nmshd/consumption": "5.0.0-alpha.11", + "@nmshd/content": "5.0.0-alpha.11", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.10", + "@nmshd/transport": "5.0.0-alpha.11", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/useCases/common/RuntimeErrors.ts b/packages/runtime/src/useCases/common/RuntimeErrors.ts index 66afe43c9..b0e509beb 100644 --- a/packages/runtime/src/useCases/common/RuntimeErrors.ts +++ b/packages/runtime/src/useCases/common/RuntimeErrors.ts @@ -44,7 +44,7 @@ class General { } public invalidTokenContent() { - return new ApplicationError("error.runtime.invalidTokenContent", "The given token has an invalid content for this route."); + return new ApplicationError("error.runtime.invalidTokenContent", "The given Token has an invalid content for this route."); } public cacheEmpty(entityName: string | Function, id: string) { @@ -70,24 +70,24 @@ class Files { public invalidReference(reference: string): ApplicationError { return new ApplicationError( "error.runtime.files.invalidReference", - `The given reference '${reference}' is not valid. The reference for a file must start with '${Base64ForIdPrefix.Token}' or '${Base64ForIdPrefix.File}'.` + `The given reference '${reference}' is not valid. The reference for a File must start with '${Base64ForIdPrefix.Token}' or '${Base64ForIdPrefix.File}'.` ); } } class RelationshipTemplates { public cannotCreateTokenForPeerTemplate(): ApplicationError { - return new ApplicationError("error.runtime.relationshipTemplates.cannotCreateTokenForPeerTemplate", "You cannot create a token for a peer template."); + return new ApplicationError("error.runtime.relationshipTemplates.cannotCreateTokenForPeerTemplate", "You cannot create a Token for a peer RelationshipTemplate."); } public cannotCreateQRCodeForPeerTemplate(): ApplicationError { - return new ApplicationError("error.runtime.relationshipTemplates.cannotCreateQRCodeForPeerTemplate", "You cannot create a QRCode for a peer template."); + return new ApplicationError("error.runtime.relationshipTemplates.cannotCreateQRCodeForPeerTemplate", "You cannot create a QR code for a peer RelationshipTemplate."); } public invalidReference(reference: string): ApplicationError { return new ApplicationError( "error.runtime.relationshipTemplates.invalidReference", - `The given reference '${reference}' is not valid. The reference for a relationship template must start with '${Base64ForIdPrefix.Token}' or '${Base64ForIdPrefix.RelationshipTemplate}'.` + `The given reference '${reference}' is not valid. The reference for a RelationshipTemplate must start with '${Base64ForIdPrefix.Token}' or '${Base64ForIdPrefix.RelationshipTemplate}'.` ); } } @@ -96,18 +96,18 @@ class Relationships { public wrongRelationshipStatus(relationshipId: string, status: string): ApplicationError { return new ApplicationError( "error.runtime.relationships.wrongRelationshipStatus", - `The relationship '${relationshipId}' has the wrong status (${status}) to run this operation` + `The Relationship '${relationshipId}' has the wrong status ('${status}') to run this operation.` ); } public isNeitherRejectedNorRevoked(): ApplicationError { - return new ApplicationError("error.runtime.relationships.isNeitherRejectedNorRevoked", 'The `status` of the Relationship is neither "Rejected" nor "Revoked".'); + return new ApplicationError("error.runtime.relationships.isNeitherRejectedNorRevoked", "The status of the Relationship is neither 'Rejected' nor 'Revoked'."); } } class Messages { public fileNotFoundInMessage(attachmentId: string) { - return new ApplicationError("error.runtime.messages.fileNotFoundInMessage", `The requested file '${attachmentId}' was not found in the given message.`); + return new ApplicationError("error.runtime.messages.fileNotFoundInMessage", `The requested File '${attachmentId}' was not found in the given Message.`); } } @@ -137,11 +137,14 @@ class Challenges { class Notifications { public cannotReceiveNotificationFromOwnMessage(): ApplicationError { - return new ApplicationError("error.runtime.notifications.cannotReceiveNotificationFromOwnMessage", "Cannot receive Notification from own message."); + return new ApplicationError("error.runtime.notifications.cannotReceiveNotificationFromOwnMessage", "Cannot receive Notification from own Message."); } - public cannotSaveSentNotificationFromPeerMessage(): ApplicationError { - return new ApplicationError("error.runtime.notifications.cannotSaveSendNotificationFromPeerMessage", "Cannot send Notification from peer message."); + public cannotSaveSentNotificationFromPeerMessage(messageId: CoreId): ApplicationError { + return new ApplicationError( + "error.runtime.notifications.cannotSaveSentNotificationFromPeerMessage", + `The Message '${messageId}' was received from a peer, but an own Message is expected here to save its Notification content.` + ); } public messageDoesNotContainNotification(messageId: CoreId): ApplicationError { @@ -154,7 +157,7 @@ class Notifications { class Attributes { public isNotRepositoryAttribute(attributeId: CoreId | string): ApplicationError { - return new ApplicationError("error.runtime.attributes.isNotRepositoryAttribute", `Attribute '${attributeId.toString()}' is not a repository attribute.`); + return new ApplicationError("error.runtime.attributes.isNotRepositoryAttribute", `Attribute '${attributeId.toString()}' is not a RepositoryAttribute.`); } public repositoryAttributeHasAlreadyBeenSharedWithPeer( @@ -164,29 +167,29 @@ class Attributes { ): ApplicationError { return new ApplicationError( "error.runtime.attributes.repositoryAttributeHasAlreadyBeenSharedWithPeer", - `Repository attribute '${repositoryAttributeId.toString()}' has already been shared with peer '${peer.toString()}'. ID of own shared identity attribute: ${ownSharedIdentityAttributeId.toString()}.` + `RepositoryAttribute '${repositoryAttributeId.toString()}' has already been shared with peer '${peer.toString()}'. ID of own shared IdentityAttribute: '${ownSharedIdentityAttributeId.toString()}'.` ); } public noPreviousVersionOfRepositoryAttributeHasBeenSharedWithPeerBefore(repositoryAttributeId: CoreId | string, peer: CoreAddress | string): ApplicationError { return new ApplicationError( "error.runtime.attributes.noPreviousVersionOfRepositoryAttributeHasBeenSharedWithPeerBefore", - `No previous version of repository attribute '${repositoryAttributeId.toString()}' has been shared with peer '${peer.toString()}' before. If you wish to execute an initial sharing of this attribute, use 'ShareRepositoryAttribute'.` + `No previous version of the RepositoryAttribute '${repositoryAttributeId.toString()}' has been shared with peer '${peer.toString()}' before. If you wish to execute an initial sharing of this Attribute, use the ShareRepositoryAttributeUseCase instead.` ); } public isNotOwnSharedAttribute(attributeId: CoreId | string): ApplicationError { - return new ApplicationError("error.runtime.attributes.isNotOwnSharedAttribute", `Attribute '${attributeId.toString()}' is not an own shared attribute.`); + return new ApplicationError("error.runtime.attributes.isNotOwnSharedAttribute", `Attribute '${attributeId.toString()}' is not an own shared Attribute.`); } public isNotPeerSharedAttribute(attributeId: CoreId | string): ApplicationError { - return new ApplicationError("error.runtime.attributes.isNotPeerSharedAttribute", `Attribute '${attributeId.toString()}' is not a peer shared attribute.`); + return new ApplicationError("error.runtime.attributes.isNotPeerSharedAttribute", `Attribute '${attributeId.toString()}' is not a peer shared Attribute.`); } public isNotThirdPartyOwnedRelationshipAttribute(attributeId: CoreId | string): ApplicationError { return new ApplicationError( "error.runtime.attributes.isNotThirdPartyOwnedRelationshipAttribute", - `Attribute '${attributeId.toString()}' is not a third party owned relationship attribute.` + `Attribute '${attributeId.toString()}' is not a third party owned RelationshipAttribute.` ); } } diff --git a/packages/runtime/src/useCases/consumption/notifications/SentNotification.ts b/packages/runtime/src/useCases/consumption/notifications/SentNotification.ts index 5918d0536..8636f6c59 100644 --- a/packages/runtime/src/useCases/consumption/notifications/SentNotification.ts +++ b/packages/runtime/src/useCases/consumption/notifications/SentNotification.ts @@ -32,7 +32,7 @@ export class SentNotificationUseCase extends UseCase { expect(getNotificationResult.value.status).toStrictEqual(LocalNotificationStatus.Sent); }); + test("sent Notification with error", async () => { + const id = await ConsumptionIds.notification.generate(); + const notificationToSend = Notification.from({ id, items: [TestNotificationItem.from({})] }); + await sTransportServices.messages.sendMessage({ recipients: [rAddress], content: notificationToSend.toJSON() }); + + const message = await syncUntilHasMessageWithNotification(rTransportServices, id); + const result = await rConsumptionServices.notifications.sentNotification({ messageId: message.id }); + expect(result).toBeAnError( + RuntimeErrors.notifications.cannotSaveSentNotificationFromPeerMessage(CoreId.from(message.id)).message, + "error.runtime.notifications.cannotSaveSentNotificationFromPeerMessage" + ); + }); + test("receivedNotification", async () => { const id = await ConsumptionIds.notification.generate(); const notificationToSend = Notification.from({ id, items: [TestNotificationItem.from({})] }); @@ -122,12 +135,12 @@ describe("Notifications", () => { expect(result).toBeAnError("'LocalNotification' not found.", "error.transport.recordNotFound"); }); - test("wrong message id for sentNotification", async () => { + test("wrong Message ID for sentNotification", async () => { const result = await sConsumptionServices.notifications.sentNotification({ messageId: "wrong-id" }); expect(result).toBeAnError("messageId must match pattern MSG\\[A-Za-z0-9\\]\\{17\\}", "error.runtime.validation.invalidPropertyValue"); }); - test("not existing message id for sentNotification", async () => { + test("not existing Message ID for sentNotification", async () => { const result = await sConsumptionServices.notifications.sentNotification({ messageId: (await new CoreIdHelper("MSG").generate()).toString() }); expect(result).toBeAnError("Message not found. Make sure the ID exists and the record is not expired.", "error.runtime.recordNotFound"); }); diff --git a/packages/transport/package.json b/packages/transport/package.json index 475996387..e87ad4b16 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.10", + "version": "5.0.0-alpha.11", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/transport/src/core/CoreErrors.ts b/packages/transport/src/core/CoreErrors.ts index 6a9eeba7b..eb66c5ab3 100644 --- a/packages/transport/src/core/CoreErrors.ts +++ b/packages/transport/src/core/CoreErrors.ts @@ -10,12 +10,12 @@ class Relationships { public wrongRelationshipStatus(relationshipId: string, status: RelationshipStatus) { return new CoreError( "error.transport.relationships.wrongRelationshipStatus", - `The relationship '${relationshipId}' has the wrong status (${status}) to run this operation` + `The Relationship '${relationshipId}' has the wrong status '${status}' to run this operation.` ); } public reactivationNotRequested(relationshipId: string) { - return new CoreError("error.transport.relationships.reactivationNotRequested", `The relationship '${relationshipId}' has no reactivation request to respond to.`); + return new CoreError("error.transport.relationships.reactivationNotRequested", `The Relationship '${relationshipId}' has no reactivation Request to respond to.`); } public reactivationAlreadyRequested(message: string) { @@ -36,7 +36,7 @@ class Device { } public couldNotDeleteDevice(reason: string, rootCause?: any) { - return new CoreError("error.transport.devices.couldNotDeleteDevice", `Could not delete device: ${reason}`, rootCause); + return new CoreError("error.transport.devices.couldNotDeleteDevice", `Could not delete device: '${reason}'`, rootCause); } } @@ -44,25 +44,25 @@ class Messages { public plaintextMismatch(ownAddress: string) { return new CoreError( "error.transport.messages.plaintextMismatch", - `The own address ${ownAddress} was not named as a recipient within the signed MessagePlaintext. A replay attack might be the cause of this.` + `The own address '${ownAddress}' was not named as a recipient within the signed MessagePlaintext. A replay attack might be the cause of this.` ); } public signatureListMismatch(address: string) { - return new CoreError("error.transport.messages.signatureListMismatch", `The signature list didn't contain an entry for address ${address}.`); + return new CoreError("error.transport.messages.signatureListMismatch", `The signature list didn't contain an entry for address '${address}'.`); } public signatureNotValid() { return new CoreError( "error.transport.messages.signatureNotValid", - "The digital signature on this message for peer key is invalid. An impersonation attack might be the cause of this." + "The digital signature on this Message for peer key is invalid. An impersonation attack might be the cause of this." ); } public ownAddressNotInList(messageId: string) { return new CoreError( "error.transport.messages.ownAddressNotInList", - `The recipients list of message ${messageId} didn't contain an entry for the own address. This message should not have been received.` + `The recipients list of Message ${messageId} didn't contain an entry for the own address. This Message should not have been received.` ); } @@ -73,27 +73,23 @@ class Messages { class Secrets { public wrongSecretType(secretId?: string) { - return new CoreError("error.transport.secrets.wrongBaseKeyType", "The given secret type is not supported!", { + return new CoreError("error.transport.secrets.wrongSecretType", "The given secret type is not supported!", { secretId: secretId }); } public secretNotFound(type: string) { - return new CoreError("error.transport.secrets.secretNotFound", `secret "${type}" not found`); + return new CoreError("error.transport.secrets.secretNotFound", `Secret '${type}' not found.`); } } class Challenges { public challengeTypeRequiresActiveRelationship() { - return new CoreError("error.transport.challenges.challengeTypeRequiresActiveRelationship", "The challenge type 'Relationship' requires an active relationship."); + return new CoreError("error.transport.challenges.challengeTypeRequiresActiveRelationship", "The challenge type Relationship requires an active Relationship."); } } class Datawallet { - public encryptedPayloadIsNoCipher() { - return new CoreError("error.transport.datawallet.encryptedPayloadIsNoCipher", "The given encrypted payload is no cipher."); - } - public unsupportedModification(type: "unsupportedCacheChangedModificationCollection", data: any) { const errorCode = "error.transport.datawallet.unsupportedModification"; const formattedData = data ? stringify(data) : ""; @@ -102,7 +98,7 @@ class Datawallet { case "unsupportedCacheChangedModificationCollection": return new CoreError( errorCode, - `The following collections were received in CacheChanged datawallet modifications but are not supported by the current version of this library: ${formattedData}.` + `The following collections were received in CacheChanged datawallet modifications but are not supported by the current version of this library: '${formattedData}'.` ); default: @@ -138,24 +134,20 @@ class Files { } public invalidMetadata(id: string) { - return new CoreError("error.transport.files.invalidMetadata", `The metadata of the file with id "${id}" is invalid.`); - } - - public fileContentUndefined() { - return new CoreError("error.transport.files.fileContentUndefined", "The given file content is undefined."); + return new CoreError("error.transport.files.invalidMetadata", `The metadata of the File with id '${id}' is invalid.`); } public maxFileSizeExceeded(fileSize: number, platformMaxFileSize: number) { return new CoreError( "error.transport.files.maxFileSizeExceeded", - `The given file content size (${fileSize}) exceeds the max file size the backbone accepts (${platformMaxFileSize}).` + `The given File content size (${fileSize}) exceeds the max File size the Backbone accepts (${platformMaxFileSize}).` ); } } class Tokens { public invalidTokenContent(id: string) { - return new CoreError("error.transport.tokens.invalidTokenContent", `The content of token ${id} is not of type TokenContent`); + return new CoreError("error.transport.tokens.invalidTokenContent", `The content of Token '${id}' is not of type TokenContent.`); } } diff --git a/packages/transport/test/modules/devices/DeviceDeletion.test.ts b/packages/transport/test/modules/devices/DeviceDeletion.test.ts index 2da426a4f..405181509 100644 --- a/packages/transport/test/modules/devices/DeviceDeletion.test.ts +++ b/packages/transport/test/modules/devices/DeviceDeletion.test.ts @@ -46,7 +46,7 @@ describe("Device Deletion", function () { await accountController.syncDatawallet(); await deviceTest.onboardDevice(await accountController.devices.getSharedSecret(newDevice.id)); - await expect(accountController.devices.delete(newDevice)).rejects.toThrow("Could not delete device: Backbone did not authorize deletion."); + await expect(accountController.devices.delete(newDevice)).rejects.toThrow("Could not delete device: 'Backbone did not authorize deletion.'"); const devices = await accountController.devices.list(); const deviceIds = devices.map((d) => d.id.toString()); @@ -60,7 +60,7 @@ describe("Device Deletion", function () { await accountController.syncDatawallet(); const deviceToDelete = await accountController.devices.get(newDevice.id); - await expect(accountController.devices.delete(deviceToDelete!)).rejects.toThrow("Could not delete device: Device is already onboarded."); + await expect(accountController.devices.delete(deviceToDelete!)).rejects.toThrow("Could not delete device: 'Device is already onboarded.'"); const devices = await accountController.devices.list(); const deviceIds = devices.map((d) => d.id.toString()); From 89a09c1ef9f16aba4b775e8ecfcf9d7ac358733e Mon Sep 17 00:00:00 2001 From: Milena Czierlinski <146972016+Milena-Czierlinski@users.noreply.github.com> Date: Wed, 24 Jul 2024 15:15:19 +0200 Subject: [PATCH 26/40] Fix/Adjust GetAttributesQuery options (#203) * fix: don't allow array for isTechnical but allow array for parentId * chore: version bump --- package-lock.json | 18 ++++---- packages/app-runtime/package.json | 4 +- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- packages/runtime/package.json | 8 ++-- .../runtime/src/useCases/common/Schemas.ts | 42 +++++-------------- .../consumption/attributes/GetAttributes.ts | 4 +- .../attributes/GetOwnSharedAttributes.ts | 2 +- .../attributes/GetPeerSharedAttributes.ts | 2 +- packages/transport/package.json | 2 +- 10 files changed, 33 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index d903e22ec..53b45d6b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12546,7 +12546,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.11", + "version": "5.0.0-alpha.12", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12560,12 +12560,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.11" + "@nmshd/runtime": "^5.0.0-alpha.12" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.11", + "version": "5.0.0-alpha.12", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12587,7 +12587,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.11", + "version": "5.0.0-alpha.12", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12612,17 +12612,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.11", + "version": "5.0.0-alpha.12", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.11", - "@nmshd/content": "5.0.0-alpha.11", + "@nmshd/consumption": "5.0.0-alpha.12", + "@nmshd/content": "5.0.0-alpha.12", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.11", + "@nmshd/transport": "5.0.0-alpha.12", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12654,7 +12654,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.11", + "version": "5.0.0-alpha.12", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index c1d3d200d..cbae352e5 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.11", + "version": "5.0.0-alpha.12", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.11" + "@nmshd/runtime": "^5.0.0-alpha.12" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 161f05ead..2bc576585 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.11", + "version": "5.0.0-alpha.12", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index 2a8b4b1b6..169098238 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.11", + "version": "5.0.0-alpha.12", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index d522be7d0..b4727d093 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.11", + "version": "5.0.0-alpha.12", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.11", - "@nmshd/content": "5.0.0-alpha.11", + "@nmshd/consumption": "5.0.0-alpha.12", + "@nmshd/content": "5.0.0-alpha.12", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.11", + "@nmshd/transport": "5.0.0-alpha.12", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index 2ebda13a5..06f70d73b 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -16729,9 +16729,6 @@ export const GetAttributesRequest: any = { "type": "string" }, "parentId": { - "type": "string" - }, - "content.@type": { "anyOf": [ { "type": "string" @@ -16744,7 +16741,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.tags": { + "content.@type": { "anyOf": [ { "type": "string" @@ -16757,7 +16754,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.owner": { + "content.tags": { "anyOf": [ { "type": "string" @@ -16770,7 +16767,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.validFrom": { + "content.owner": { "anyOf": [ { "type": "string" @@ -16783,7 +16780,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.validTo": { + "content.validFrom": { "anyOf": [ { "type": "string" @@ -16796,7 +16793,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.key": { + "content.validTo": { "anyOf": [ { "type": "string" @@ -16809,7 +16806,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.isTechnical": { + "content.key": { "anyOf": [ { "type": "string" @@ -16822,6 +16819,9 @@ export const GetAttributesRequest: any = { } ] }, + "content.isTechnical": { + "type": "string" + }, "content.confidentiality": { "anyOf": [ { @@ -17089,17 +17089,7 @@ export const GetOwnSharedAttributesRequest: any = { ] }, "content.isTechnical": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] + "type": "string" }, "content.confidentiality": { "anyOf": [ @@ -17329,17 +17319,7 @@ export const GetPeerSharedAttributesRequest: any = { ] }, "content.isTechnical": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] + "type": "string" }, "content.confidentiality": { "anyOf": [ diff --git a/packages/runtime/src/useCases/consumption/attributes/GetAttributes.ts b/packages/runtime/src/useCases/consumption/attributes/GetAttributes.ts index 60031cba7..43c787500 100644 --- a/packages/runtime/src/useCases/consumption/attributes/GetAttributes.ts +++ b/packages/runtime/src/useCases/consumption/attributes/GetAttributes.ts @@ -18,14 +18,14 @@ export interface GetAttributesRequest { export interface GetAttributesRequestQuery { createdAt?: string; - parentId?: string; + parentId?: string | string[]; "content.@type"?: string | string[]; "content.tags"?: string | string[]; "content.owner"?: string | string[]; "content.validFrom"?: string | string[]; "content.validTo"?: string | string[]; "content.key"?: string | string[]; - "content.isTechnical"?: string | string[]; + "content.isTechnical"?: string; "content.confidentiality"?: string | string[]; "content.value.@type"?: string | string[]; succeeds?: string | string[]; diff --git a/packages/runtime/src/useCases/consumption/attributes/GetOwnSharedAttributes.ts b/packages/runtime/src/useCases/consumption/attributes/GetOwnSharedAttributes.ts index 0eca7b7f6..2eb864042 100644 --- a/packages/runtime/src/useCases/consumption/attributes/GetOwnSharedAttributes.ts +++ b/packages/runtime/src/useCases/consumption/attributes/GetOwnSharedAttributes.ts @@ -26,7 +26,7 @@ export interface GetOwnSharedAttributeRequestQuery { "content.validFrom"?: string | string[]; "content.validTo"?: string | string[]; "content.key"?: string | string[]; - "content.isTechnical"?: string | string[]; + "content.isTechnical"?: string; "content.confidentiality"?: string | string[]; "content.value.@type"?: string | string[]; shareInfo?: string | string[]; diff --git a/packages/runtime/src/useCases/consumption/attributes/GetPeerSharedAttributes.ts b/packages/runtime/src/useCases/consumption/attributes/GetPeerSharedAttributes.ts index 23764f3ca..517515021 100644 --- a/packages/runtime/src/useCases/consumption/attributes/GetPeerSharedAttributes.ts +++ b/packages/runtime/src/useCases/consumption/attributes/GetPeerSharedAttributes.ts @@ -25,7 +25,7 @@ export interface GetPeerSharedAttributesRequestQuery { "content.validFrom"?: string | string[]; "content.validTo"?: string | string[]; "content.key"?: string | string[]; - "content.isTechnical"?: string | string[]; + "content.isTechnical"?: string; "content.confidentiality"?: string | string[]; "content.value.@type"?: string | string[]; shareInfo?: string | string[]; diff --git a/packages/transport/package.json b/packages/transport/package.json index e87ad4b16..7c3916b75 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.11", + "version": "5.0.0-alpha.12", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { From f0f278ef6c5589c96d1cf45de437cae5354ab396 Mon Sep 17 00:00:00 2001 From: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com> Date: Wed, 31 Jul 2024 10:17:32 +0200 Subject: [PATCH 27/40] chore/remove deprecated, add missing translateables (#210) --- .../runtime/src/dataViews/DataViewExpander.ts | 33 ++++++++++++------- .../src/dataViews/DataViewTranslateable.ts | 19 ++--------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/packages/runtime/src/dataViews/DataViewExpander.ts b/packages/runtime/src/dataViews/DataViewExpander.ts index fd3d2231a..f8f2def40 100644 --- a/packages/runtime/src/dataViews/DataViewExpander.ts +++ b/packages/runtime/src/dataViews/DataViewExpander.ts @@ -51,7 +51,7 @@ import { ValueHintsJSON, isRequestItemDerivation } from "@nmshd/content"; -import { CoreAddress, CoreId, IdentityController, Relationship, RelationshipStatus } from "@nmshd/transport"; +import { CoreAddress, CoreId, IdentityController, Relationship } from "@nmshd/transport"; import _ from "lodash"; import { Inject } from "typescript-ioc"; import { @@ -85,6 +85,7 @@ import { MessageWithAttachmentsDTO, RecipientDTO, RelationshipDTO, + RelationshipStatus, RelationshipTemplateDTO } from "../types"; import { RuntimeErrors } from "../useCases"; @@ -1732,16 +1733,26 @@ export class DataViewExpander { } let statusText = ""; - if (relationship.status === RelationshipStatus.Pending && direction === RelationshipDirection.Outgoing) { - statusText = DataViewTranslateable.transport.relationshipOutgoing; - } else if (relationship.status === RelationshipStatus.Pending) { - statusText = DataViewTranslateable.transport.relationshipIncoming; - } else if (relationship.status === RelationshipStatus.Rejected) { - statusText = DataViewTranslateable.transport.relationshipRejected; - } else if (relationship.status === RelationshipStatus.Revoked) { - statusText = DataViewTranslateable.transport.relationshipRevoked; - } else if (relationship.status === RelationshipStatus.Active) { - statusText = DataViewTranslateable.transport.relationshipActive; + switch (relationship.status) { + case RelationshipStatus.Pending: + statusText = + direction === RelationshipDirection.Outgoing ? DataViewTranslateable.transport.relationshipOutgoing : DataViewTranslateable.transport.relationshipIncoming; + break; + case RelationshipStatus.Rejected: + statusText = DataViewTranslateable.transport.relationshipRejected; + break; + case RelationshipStatus.Revoked: + statusText = DataViewTranslateable.transport.relationshipRevoked; + break; + case RelationshipStatus.Active: + statusText = DataViewTranslateable.transport.relationshipActive; + break; + case RelationshipStatus.Terminated: + statusText = DataViewTranslateable.transport.relationshipTerminated; + break; + case RelationshipStatus.DeletionProposed: + statusText = DataViewTranslateable.transport.relationshipDeletionProposed; + break; } const creationDate = relationship.auditLog[0].createdAt; diff --git a/packages/runtime/src/dataViews/DataViewTranslateable.ts b/packages/runtime/src/dataViews/DataViewTranslateable.ts index e00ef661c..0afd35491 100644 --- a/packages/runtime/src/dataViews/DataViewTranslateable.ts +++ b/packages/runtime/src/dataViews/DataViewTranslateable.ts @@ -7,31 +7,18 @@ export class DataViewTranslateable { relationshipRejected: `${DataViewTranslateable.prefix}relationship.Rejected`, relationshipRevoked: `${DataViewTranslateable.prefix}relationship.Revoked`, relationshipActive: `${DataViewTranslateable.prefix}relationship.Active`, + relationshipTerminated: `${DataViewTranslateable.prefix}relationship.Terminated`, + relationshipDeletionProposed: `${DataViewTranslateable.prefix}relationship.DeletionProposed`, fileName: `${DataViewTranslateable.prefix}file.name` }; public static readonly consumption = { mails: { - mailSubjectFallback: `${DataViewTranslateable.prefix}mails.mailSubjectFallback`, - requestMailSubjectFallback: `${DataViewTranslateable.prefix}mails.requestMailSubjectFallback` + mailSubjectFallback: `${DataViewTranslateable.prefix}mails.mailSubjectFallback` }, attributes: { unknownAttributeName: `${DataViewTranslateable.prefix}attributes.UnknownAttributeName` }, - requests: { - // Request for sharing an attribute - attributesShareRequestName: `${DataViewTranslateable.prefix}requests.AttributesShareRequest.name`, - // Request for sharing multiple attributes - attributesShareRequestNamePlural: `${DataViewTranslateable.prefix}requests.AttributesShareRequest.namePlural`, - // You don't have a relationship to any of the recipients of this request - attributesShareRequestNoRelationship: `${DataViewTranslateable.prefix}requests.AttributesShareRequest.noRelationship`, - // The data is only shared with known recipients - attributesShareRequestOnlyRelationships: `${DataViewTranslateable.prefix}requests.AttributesShareRequest.onlyRelationships`, - // Request for changing an attribute - attributesChangeRequestName: `${DataViewTranslateable.prefix}requests.AttributesChangeRequest.name`, - // Request for changing multiple attributes - attributesChangeRequestNamePlural: `${DataViewTranslateable.prefix}requests.AttributesChangeRequest.namePlural` - }, identities: { self: `${DataViewTranslateable.prefix}identities.self.name` } From 1bd20cf31cb9e1472468ab0bdaafdecb099ce071 Mon Sep 17 00:00:00 2001 From: Milena Czierlinski <146972016+Milena-Czierlinski@users.noreply.github.com> Date: Wed, 31 Jul 2024 14:34:15 +0200 Subject: [PATCH 28/40] Feature/Default RepositoryAttributes (#185) * feat: add default property to LocalAttribute for RepositoryAttributes * refactor: change structure of AttributesController test * refactor: rename functions more descriptive * test: CreateRepositoryAttribute use case * feat: add default as query option * feat: move default to successor * feat: make other attribute default if possible deleting the default attribute * feat: add ChangeDefaultRepositoryAttribute use case * refactor: clean up * feat: integrate comments * feat: throw error if errorneous attributes * feat: validate new default doesn't have successor * test: delete default attribute with predecessor * refactor: descriptive names * feat: check for successor earlier transmitting the default * refactor: use easier query * chore: version bump * feat: add check for repository attribute * feat: integrate comments * fix: make get attributes test independent * fix: make chage default RepositoryAttribute tests independent * test: integrate comments * fix: adjust expected error message * refactor: make default of type true * chore: version bump * chore: version bump * feat: ensure consistent default child attributes * refactor: rename property default to isDefault --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- package-lock.json | 18 +- packages/app-runtime/package.json | 4 +- packages/consumption/package.json | 2 +- .../consumption/src/consumption/CoreErrors.ts | 4 + .../attributes/AttributesController.ts | 96 ++++++- .../attributes/local/LocalAttribute.ts | 25 +- .../attributes/AttributesController.test.ts | 249 ++++++++++++++++-- packages/content/package.json | 2 +- packages/runtime/package.json | 8 +- .../runtime/src/dataViews/DataViewExpander.ts | 3 +- .../consumption/LocalAttributeDVO.ts | 3 +- .../facades/consumption/AttributesFacade.ts | 35 ++- .../types/consumption/LocalAttributeDTO.ts | 1 + .../src/useCases/common/RuntimeErrors.ts | 8 + .../runtime/src/useCases/common/Schemas.ts | 55 +++- .../consumption/attributes/AttributeMapper.ts | 3 +- .../ChangeDefaultRepositoryAttribute.ts | 46 ++++ .../consumption/attributes/GetAttributes.ts | 7 +- .../attributes/GetRepositoryAttributes.ts | 3 +- .../useCases/consumption/attributes/index.ts | 1 + .../test/consumption/attributes.test.ts | 209 ++++++++++++++- packages/transport/package.json | 2 +- 22 files changed, 700 insertions(+), 84 deletions(-) create mode 100644 packages/runtime/src/useCases/consumption/attributes/ChangeDefaultRepositoryAttribute.ts diff --git a/package-lock.json b/package-lock.json index 6f93abc39..dcb51d850 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12548,7 +12548,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.12", + "version": "5.0.0-alpha.13", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12562,12 +12562,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.12" + "@nmshd/runtime": "^5.0.0-alpha.13" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.12", + "version": "5.0.0-alpha.13", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12589,7 +12589,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.12", + "version": "5.0.0-alpha.13", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12614,17 +12614,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.12", + "version": "5.0.0-alpha.13", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.12", - "@nmshd/content": "5.0.0-alpha.12", + "@nmshd/consumption": "5.0.0-alpha.13", + "@nmshd/content": "5.0.0-alpha.13", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.12", + "@nmshd/transport": "5.0.0-alpha.13", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12656,7 +12656,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.12", + "version": "5.0.0-alpha.13", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index cbae352e5..7ab24278a 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.12", + "version": "5.0.0-alpha.13", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.12" + "@nmshd/runtime": "^5.0.0-alpha.13" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 2bc576585..18f949315 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.12", + "version": "5.0.0-alpha.13", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/consumption/src/consumption/CoreErrors.ts b/packages/consumption/src/consumption/CoreErrors.ts index 6f41b2fea..4dcc29082 100644 --- a/packages/consumption/src/consumption/CoreErrors.ts +++ b/packages/consumption/src/consumption/CoreErrors.ts @@ -223,6 +223,10 @@ class Attributes { ); } + public isNotRepositoryAttribute(attributeId: string | CoreId) { + return new CoreError("error.consumption.attributes.isNotRepositoryAttribute", `The attribute (id: ${attributeId}) is not a RepositoryAttribute.`); + } + public isNotSharedAttribute(attributeId: string | CoreId) { return new CoreError("error.consumption.attributes.isNotSharedAttribute", `The Attribute (id: '${attributeId}') is not a shared Attribute.`); } diff --git a/packages/consumption/src/modules/attributes/AttributesController.ts b/packages/consumption/src/modules/attributes/AttributesController.ts index 1c3d3aac5..630905ae8 100644 --- a/packages/consumption/src/modules/attributes/AttributesController.ts +++ b/packages/consumption/src/modules/attributes/AttributesController.ts @@ -238,7 +238,7 @@ export class AttributesController extends ConsumptionBaseController { } const parsedParams = CreateRepositoryAttributeParams.from(params); - const localAttribute = LocalAttribute.from({ + let localAttribute = LocalAttribute.from({ id: parsedParams.id ?? (await ConsumptionIds.attribute.generate()), createdAt: CoreDate.utc(), content: parsedParams.content, @@ -247,6 +247,8 @@ export class AttributesController extends ConsumptionBaseController { await this.attributes.create(localAttribute); + localAttribute = await this.setAsDefaultRepositoryAttribute(localAttribute, true); + if (localAttribute.content.value instanceof AbstractComplexValue) { await this.createLocalAttributesForChildrenOfComplexAttribute(localAttribute); } @@ -276,6 +278,51 @@ export class AttributesController extends ConsumptionBaseController { } } + public async setAsDefaultRepositoryAttribute(newDefaultAttribute: LocalAttribute, skipOverwrite?: boolean): Promise { + if (!newDefaultAttribute.isRepositoryAttribute(this.identity.address)) { + throw CoreErrors.attributes.isNotRepositoryAttribute(newDefaultAttribute.id); + } + + if (newDefaultAttribute.isDefault) return newDefaultAttribute; + + if (newDefaultAttribute.parentId) { + const parentAttribute = await this.getLocalAttribute(newDefaultAttribute.parentId); + if (!parentAttribute) throw TransportCoreErrors.general.recordNotFound(LocalAttribute, newDefaultAttribute.parentId.toString()); + if (parentAttribute.isDefault) skipOverwrite = false; + } + + const valueType = newDefaultAttribute.content.value.constructor.name; + const query = { + $and: [ + { + [`${nameof((c) => c.content)}.value.@type`]: valueType + }, + { + [nameof((c) => c.isDefault)]: true + } + ] + }; + + const currentDefaultAttributeResult = await this.getLocalAttributes(query); + + if (currentDefaultAttributeResult.length > 1) { + throw new ConsumptionError(`There are multiple default Attributes for type ${valueType.toString()}, even though only one is expected.`); + } + + const currentDefaultAttributeExists = currentDefaultAttributeResult.length === 1; + if (skipOverwrite && currentDefaultAttributeExists) return newDefaultAttribute; + + if (!skipOverwrite && currentDefaultAttributeExists) { + const currentDefaultAttribute = currentDefaultAttributeResult[0]; + currentDefaultAttribute.isDefault = undefined; + await this.updateAttributeUnsafe(currentDefaultAttribute); + } + + newDefaultAttribute.isDefault = true; + await this.updateAttributeUnsafe(newDefaultAttribute); + return newDefaultAttribute; + } + public async createSharedLocalAttributeCopy(params: ICreateSharedLocalAttributeCopyParams): Promise { const parsedParams = CreateSharedLocalAttributeCopyParams.from(params); const sourceAttribute = await this.getLocalAttribute(parsedParams.sourceAttributeId); @@ -571,15 +618,26 @@ export class AttributesController extends ConsumptionBaseController { shareInfo: successorParams.shareInfo, parentId: successorParams.parentId, createdAt: successorParams.createdAt, - succeededBy: successorParams.succeededBy + succeededBy: successorParams.succeededBy, + isDefault: predecessor.isDefault }); + await this.removeDefault(predecessor); + predecessor.succeededBy = successor.id; await this.updateAttributeUnsafe(predecessor); return { predecessor, successor }; } + private async removeDefault(attribute: LocalAttribute): Promise { + if (!attribute.isDefault) return attribute; + + attribute.isDefault = undefined; + await this.updateAttributeUnsafe(attribute); + return attribute; + } + public async validateRepositoryAttributeSuccession( predecessorId: CoreId, successorParams: IAttributeSuccessorParams | AttributeSuccessorParamsJSON @@ -940,7 +998,8 @@ export class AttributesController extends ConsumptionBaseController { parentId: attributeData.parentId, succeededBy: attributeData.succeededBy, succeeds: attributeData.succeeds, - deletionInfo: attributeData.deletionInfo + deletionInfo: attributeData.deletionInfo, + isDefault: attributeData.isDefault }); await this.attributes.create(localAttribute); return localAttribute; @@ -962,7 +1021,8 @@ export class AttributesController extends ConsumptionBaseController { shareInfo: attributeParams.shareInfo, succeededBy: attributeParams.succeededBy, succeeds: attributeParams.succeeds, - deletionInfo: attributeParams.deletionInfo + deletionInfo: attributeParams.deletionInfo, + isDefault: attributeParams.isDefault }; const newAttribute = LocalAttribute.from(params); await this.attributes.update(doc, newAttribute); @@ -994,6 +1054,9 @@ export class AttributesController extends ConsumptionBaseController { await this.detachAttributeCopies(attributeCopiesToDetach); await this.deletePredecessorsOfAttribute(attribute.id); + + await this.transferDefault(attribute); + await this.deleteAttribute(attribute); } @@ -1055,6 +1118,31 @@ export class AttributesController extends ConsumptionBaseController { } } + private async transferDefault(attribute: LocalAttribute): Promise { + if (!attribute.isDefault) return; + + const valueType = attribute.content.value.constructor.name; + const query = { + $and: [ + { + [`${nameof((c) => c.content)}.value.@type`]: valueType + }, + { + [nameof((c) => c.succeededBy)]: undefined + }, + { + [nameof((c) => c.id)]: { $ne: attribute.id.toString() } + } + ] + }; + + const defaultCandidates = await this.getLocalAttributes(query); + if (defaultCandidates.length === 0) return; + + defaultCandidates[defaultCandidates.length - 1].isDefault = true; + await this.updateAttributeUnsafe(defaultCandidates[defaultCandidates.length - 1]); + } + public async getVersionsOfAttribute(id: CoreId): Promise { const attribute = await this.getLocalAttribute(id); if (!attribute) { diff --git a/packages/consumption/src/modules/attributes/local/LocalAttribute.ts b/packages/consumption/src/modules/attributes/local/LocalAttribute.ts index 21349a4df..45387c13b 100644 --- a/packages/consumption/src/modules/attributes/local/LocalAttribute.ts +++ b/packages/consumption/src/modules/attributes/local/LocalAttribute.ts @@ -1,10 +1,10 @@ import { serialize, type, validate } from "@js-soft/ts-serval"; import { AbstractComplexValue, - IdentityAttribute, - IdentityAttributeJSON, IIdentityAttribute, IRelationshipAttribute, + IdentityAttribute, + IdentityAttributeJSON, RelationshipAttribute, RelationshipAttributeJSON } from "@nmshd/content"; @@ -23,6 +23,7 @@ export interface LocalAttributeJSON { shareInfo?: LocalAttributeShareInfoJSON; deletionInfo?: LocalAttributeDeletionInfoJSON; parentId?: string; + isDefault?: true; } export interface ILocalAttribute extends ICoreSynchronizable { @@ -33,36 +34,43 @@ export interface ILocalAttribute extends ICoreSynchronizable { shareInfo?: ILocalAttributeShareInfo; deletionInfo?: ILocalAttributeDeletionInfo; parentId?: ICoreId; + isDefault?: true; } export type OwnSharedIdentityAttribute = LocalAttribute & { content: IdentityAttribute; shareInfo: LocalAttributeShareInfo; + isDefault: undefined; }; export type OwnSharedRelationshipAttribute = LocalAttribute & { content: RelationshipAttribute; shareInfo: LocalAttributeShareInfo; + isDefault: undefined; }; export type PeerSharedIdentityAttribute = LocalAttribute & { content: IdentityAttribute; shareInfo: LocalAttributeShareInfo & { sourceAttribute: undefined }; + isDefault: undefined; }; export type PeerSharedRelationshipAttribute = LocalAttribute & { content: RelationshipAttribute; shareInfo: LocalAttributeShareInfo & { sourceAttribute: undefined }; + isDefault: undefined; }; export type ThirdPartyOwnedRelationshipAttribute = LocalAttribute & { content: RelationshipAttribute; shareInfo: LocalAttributeShareInfo; + isDefault: undefined; }; export type RepositoryAttribute = LocalAttribute & { content: IdentityAttribute; shareInfo: undefined; + deletionInfo: undefined; }; @type("LocalAttribute") @@ -75,7 +83,8 @@ export class LocalAttribute extends CoreSynchronizable implements ILocalAttribut nameof((r) => r.succeededBy), nameof((r) => r.shareInfo), nameof((r) => r.deletionInfo), - nameof((r) => r.parentId) + nameof((r) => r.parentId), + nameof((r) => r.isDefault) ]; public override readonly userdataProperties = [nameof((r) => r.content)]; @@ -108,6 +117,10 @@ export class LocalAttribute extends CoreSynchronizable implements ILocalAttribut @serialize() public parentId?: CoreId; + @validate({ nullable: true }) + @serialize() + public isDefault?: true; + public isOwnSharedIdentityAttribute(ownAddress: CoreAddress, peerAddress?: CoreAddress): this is OwnSharedIdentityAttribute { return this.isIdentityAttribute() && this.isOwnSharedAttribute(ownAddress, peerAddress); } @@ -135,6 +148,8 @@ export class LocalAttribute extends CoreSynchronizable implements ILocalAttribut public isOwnSharedAttribute(ownAddress: CoreAddress, peerAddress?: CoreAddress): this is OwnSharedIdentityAttribute | OwnSharedRelationshipAttribute { let isOwnSharedAttribute = this.isShared() && this.isOwnedBy(ownAddress); + isOwnSharedAttribute &&= !this.isDefault; + if (peerAddress) isOwnSharedAttribute &&= this.shareInfo!.peer.equals(peerAddress); return isOwnSharedAttribute; } @@ -144,6 +159,8 @@ export class LocalAttribute extends CoreSynchronizable implements ILocalAttribut isPeerSharedAttribute &&= !this.shareInfo!.sourceAttribute; + isPeerSharedAttribute &&= !this.isDefault; + if (peerAddress) isPeerSharedAttribute &&= this.isOwnedBy(peerAddress); return isPeerSharedAttribute; } @@ -151,6 +168,8 @@ export class LocalAttribute extends CoreSynchronizable implements ILocalAttribut public isThirdPartyOwnedAttribute(ownAddress: CoreAddress, thirdPartyAddress?: CoreAddress): this is ThirdPartyOwnedRelationshipAttribute { let isThirdPartyOwnedAttribute = this.isShared() && !this.isOwnedBy(ownAddress) && !this.isOwnedBy(this.shareInfo.peer); + isThirdPartyOwnedAttribute &&= !this.isDefault; + if (thirdPartyAddress) isThirdPartyOwnedAttribute &&= this.isOwnedBy(thirdPartyAddress); return isThirdPartyOwnedAttribute; } diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index 04d3fd7ae..f769d233a 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -1,6 +1,7 @@ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; import { BirthDate, + BirthYear, City, Country, EMailAddress, @@ -158,6 +159,87 @@ describe("AttributesController", function () { ); }); + test("should set an Attribute as default if it is the only of its value type", async function () { + const attributeParams: ICreateRepositoryAttributeParams = { + content: IdentityAttribute.from({ + value: EMailAddress.from({ + value: "my@email.address" + }), + owner: consumptionController.accountController.identity.address + }) + }; + const attribute = await consumptionController.attributes.createRepositoryAttribute(attributeParams); + expect(attribute.isDefault).toBe(true); + }); + + test("should not set an Attribute as default if already another exists with that value type", async function () { + const attributeParams: ICreateRepositoryAttributeParams = { + content: IdentityAttribute.from({ + value: EMailAddress.from({ + value: "my@email.address" + }), + owner: consumptionController.accountController.identity.address + }) + }; + const firstAttribute = await consumptionController.attributes.createRepositoryAttribute(attributeParams); + expect(firstAttribute.isDefault).toBe(true); + + const secondAttribute = await consumptionController.attributes.createRepositoryAttribute(attributeParams); + expect(secondAttribute.isDefault).toBeUndefined(); + }); + + test("should set a child Attribute of a complex default attribute as default", async function () { + const complexBirthDate = await consumptionController.attributes.createRepositoryAttribute({ + content: IdentityAttribute.from({ + value: BirthDate.from({ + day: 28, + month: 2, + year: 2000 + }), + owner: consumptionController.accountController.identity.address + }) + }); + expect(complexBirthDate.isDefault).toBe(true); + + const childBirthYear = await consumptionController.attributes.getLocalAttributes({ + parentId: complexBirthDate.id.toString(), + "content.value.@type": "BirthYear" + }); + expect(childBirthYear).toHaveLength(1); + expect(childBirthYear[0].isDefault).toBe(true); + }); + + test("should set a child Attribute of a complex default attribute as default even if already another attribute with that value type exists", async function () { + const independentBirthYear = await consumptionController.attributes.createRepositoryAttribute({ + content: IdentityAttribute.from({ + value: BirthYear.from({ + value: 2000 + }), + owner: consumptionController.accountController.identity.address + }) + }); + expect(independentBirthYear.isDefault).toBe(true); + + const complexBirthDate = await consumptionController.attributes.createRepositoryAttribute({ + content: IdentityAttribute.from({ + value: BirthDate.from({ + day: 28, + month: 2, + year: 2000 + }), + owner: consumptionController.accountController.identity.address + }) + }); + expect(complexBirthDate.isDefault).toBe(true); + + const childBirthYear = await consumptionController.attributes.getLocalAttributes({ parentId: complexBirthDate.id.toString(), "content.value.@type": "BirthYear" }); + expect(childBirthYear).toHaveLength(1); + expect(childBirthYear[0].isDefault).toBe(true); + + const updatedIndependentBirthYear = await consumptionController.attributes.getLocalAttribute(independentBirthYear.id); + expect(updatedIndependentBirthYear!.isDefault).toBeUndefined(); + }); + test("should allow to create a shared attribute copy", async function () { const nationalityParams: ICreateRepositoryAttributeParams = { content: IdentityAttribute.from({ @@ -831,6 +913,42 @@ describe("AttributesController", function () { const updatedPredecessorOwnSharedIdentityAttribute = await consumptionController.attributes.getLocalAttribute(predecessorOwnSharedIdentityAttribute.id); expect(updatedPredecessorOwnSharedIdentityAttribute!.shareInfo!.sourceAttribute).toBeUndefined(); }); + + test("should change default from deleted attribute to newest of the same value type if another exists", async function () { + const otherRepositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ + content: IdentityAttribute.from({ + value: EMailAddress.from({ + value: "my2@email.address" + }), + owner: consumptionController.accountController.identity.address + }) + }); + + const otherNewerRepositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ + content: IdentityAttribute.from({ + value: EMailAddress.from({ + value: "my3@email.address" + }), + owner: consumptionController.accountController.identity.address + }) + }); + + expect(successorRepositoryAttribute.isDefault).toBe(true); + expect(otherRepositoryAttribute.isDefault).toBeUndefined(); + expect(otherNewerRepositoryAttribute.isDefault).toBeUndefined(); + + await consumptionController.attributes.executeFullAttributeDeletionProcess(successorRepositoryAttribute); + const updatedOtherRepositoryAttribute = await consumptionController.attributes.getLocalAttribute(otherNewerRepositoryAttribute.id); + expect(updatedOtherRepositoryAttribute!.isDefault).toBe(true); + }); + + test("should not set a default if the deleted default attribute had predecessors but no other candidate exists", async function () { + expect(successorRepositoryAttribute.isDefault).toBe(true); + await consumptionController.attributes.executeFullAttributeDeletionProcess(successorRepositoryAttribute); + + const defaultAttributes = await consumptionController.attributes.getLocalAttributes({ isDefault: "true" }); + expect(defaultAttributes).toHaveLength(0); + }); }); describe("should validate and execute full attribute deletion process for child attribute", function () { @@ -1089,7 +1207,7 @@ describe("AttributesController", function () { }); test("should catch if the predecessor has parent", async function () { - const predecessor = await consumptionController.attributes.createRepositoryAttribute({ + const predecessor = await consumptionController.attributes.createAttributeUnsafe({ parentId: CoreId.from("parentId"), content: IdentityAttribute.from({ value: { @@ -1467,6 +1585,33 @@ describe("AttributesController", function () { expect((successor.content.value.toJSON() as any).value).toBe("US"); }); + test("should make successor default succeeding a default repository attribute", async function () { + const predecessor = await consumptionController.attributes.createRepositoryAttribute({ + content: IdentityAttribute.from({ + value: { + "@type": "Nationality", + value: "DE" + }, + owner: consumptionController.accountController.identity.address + }) + }); + expect(predecessor.isDefault).toBe(true); + + const successorParams: IAttributeSuccessorParams = { + content: IdentityAttribute.from({ + value: { + "@type": "Nationality", + value: "US" + }, + owner: consumptionController.accountController.identity.address + }) + }; + + const { predecessor: updatedPredecessor, successor } = await consumptionController.attributes.succeedRepositoryAttribute(predecessor.id, successorParams); + expect(successor.isDefault).toBe(true); + expect(updatedPredecessor.isDefault).toBeUndefined(); + }); + test("should succeed an own shared identity attribute", async function () { const predecessorRepo = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ @@ -2141,6 +2286,73 @@ describe("AttributesController", function () { }); }); + describe("change default Attributes", function () { + test("should change default RepositoryAttribute", async function () { + const firstAttribute = await consumptionController.attributes.createRepositoryAttribute({ + content: IdentityAttribute.from({ + value: { + "@type": "GivenName", + value: "My default given name" + }, + owner: consumptionController.accountController.identity.address + }) + }); + + const secondAttribute = await consumptionController.attributes.createRepositoryAttribute({ + content: IdentityAttribute.from({ + value: { + "@type": "GivenName", + value: "My other given name" + }, + owner: consumptionController.accountController.identity.address + }) + }); + expect(secondAttribute.isDefault).toBeUndefined(); + + const updatedSecondAttribute = await consumptionController.attributes.setAsDefaultRepositoryAttribute(secondAttribute); + expect(updatedSecondAttribute.isDefault).toBe(true); + + const updatedFirstAttribute = await consumptionController.attributes.getLocalAttribute(firstAttribute.id); + expect(updatedFirstAttribute!.isDefault).toBeUndefined(); + }); + + test("should not change default RepositoryAttribute if candidate is already default", async function () { + const firstAttribute = await consumptionController.attributes.createRepositoryAttribute({ + content: IdentityAttribute.from({ + value: { + "@type": "GivenName", + value: "My default given name" + }, + owner: consumptionController.accountController.identity.address + }) + }); + expect(firstAttribute.isDefault).toBe(true); + const updatedFirstAttribute = await consumptionController.attributes.setAsDefaultRepositoryAttribute(firstAttribute); + expect(updatedFirstAttribute.isDefault).toBe(true); + }); + + test("should throw an error if the new default Attribute is not a RepositoryAttribute", async function () { + const sharedAttribute = await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + value: { + "@type": "GivenName", + value: "My shared given name" + }, + owner: consumptionController.accountController.identity.address + }), + shareInfo: LocalAttributeShareInfo.from({ + peer: CoreAddress.from("peer"), + requestReference: CoreId.from("reqRef") + }) + }); + + await TestUtil.expectThrowsAsync( + consumptionController.attributes.setAsDefaultRepositoryAttribute(sharedAttribute), + "error.consumption.attributes.isNotRepositoryAttribute" + ); + }); + }); + describe("get Attributes", function () { beforeEach(async function () { await consumptionController.attributes.createSharedLocalAttribute({ @@ -2190,7 +2402,6 @@ describe("AttributesController", function () { peer: CoreAddress.from("peer") }); }); - test("should list all attributes", async function () { const attributes = await consumptionController.attributes.getLocalAttributes(); expect(attributes).toHaveLength(3); @@ -2255,18 +2466,18 @@ describe("AttributesController", function () { expect(result0).toStrictEqual([]); const result1 = await consumptionController.attributes.getPredecessorsOfAttribute(repositoryAttributeVersion1.id); - expect(result1).toStrictEqual([repositoryAttributeVersion0]); + expect(JSON.stringify(result1)).toStrictEqual(JSON.stringify([repositoryAttributeVersion0])); const result2 = await consumptionController.attributes.getPredecessorsOfAttribute(repositoryAttributeVersion2.id); - expect(result2).toStrictEqual([repositoryAttributeVersion1, repositoryAttributeVersion0]); + expect(JSON.stringify(result2)).toStrictEqual(JSON.stringify([repositoryAttributeVersion1, repositoryAttributeVersion0])); }); test("should return all successors of a succeeded repository attribute", async function () { const result0 = await consumptionController.attributes.getSuccessorsOfAttribute(repositoryAttributeVersion0.id); - expect(result0).toStrictEqual([repositoryAttributeVersion1, repositoryAttributeVersion2]); + expect(JSON.stringify(result0)).toStrictEqual(JSON.stringify([repositoryAttributeVersion1, repositoryAttributeVersion2])); const result1 = await consumptionController.attributes.getSuccessorsOfAttribute(repositoryAttributeVersion1.id); - expect(result1).toStrictEqual([repositoryAttributeVersion2]); + expect(JSON.stringify(result1)).toStrictEqual(JSON.stringify([repositoryAttributeVersion2])); const result2 = await consumptionController.attributes.getSuccessorsOfAttribute(repositoryAttributeVersion2.id); expect(result2).toStrictEqual([]); @@ -2276,7 +2487,7 @@ describe("AttributesController", function () { const allVersions = [repositoryAttributeVersion2, repositoryAttributeVersion1, repositoryAttributeVersion0]; for (const version of allVersions) { const result = await consumptionController.attributes.getVersionsOfAttribute(version.id); - expect(result).toStrictEqual([repositoryAttributeVersion2, repositoryAttributeVersion1, repositoryAttributeVersion0]); + expect(JSON.stringify(result)).toStrictEqual(JSON.stringify([repositoryAttributeVersion2, repositoryAttributeVersion1, repositoryAttributeVersion0])); } }); @@ -2303,7 +2514,7 @@ describe("AttributesController", function () { }); const ownSharedIdentityAttributeVersionsBeforeSuccession = await consumptionController.attributes.getVersionsOfAttribute(ownSharedIdentityAttributeVersion0.id); - expect(ownSharedIdentityAttributeVersionsBeforeSuccession).toStrictEqual([ownSharedIdentityAttributeVersion0]); + expect(JSON.stringify(ownSharedIdentityAttributeVersionsBeforeSuccession)).toStrictEqual(JSON.stringify([ownSharedIdentityAttributeVersion0])); const ownSharedIdentityAttributeVersion1 = await consumptionController.attributes.createSharedLocalAttributeCopy({ sourceAttributeId: repositoryAttributeVersion1.id, @@ -2332,7 +2543,7 @@ describe("AttributesController", function () { const allVersions = [ownSharedIdentityAttributeVersion2, updatedOwnSharedIdentityAttributeVersion1]; for (const version of allVersions) { const result = await consumptionController.attributes.getVersionsOfAttribute(version.id); - expect(result).toStrictEqual(allVersions); + expect(JSON.stringify(result)).toStrictEqual(JSON.stringify(allVersions)); } }); @@ -2376,7 +2587,7 @@ describe("AttributesController", function () { }; const onlyVersion0 = await consumptionController.attributes.getVersionsOfAttribute(version0.id); - expect(onlyVersion0).toStrictEqual([version0]); + expect(JSON.stringify(onlyVersion0)).toStrictEqual(JSON.stringify([version0])); const { predecessor: updatedVersion0, successor: version1 } = await consumptionController.attributes.succeedPeerSharedIdentityAttribute(version0.id, successorParams1); const { predecessor: updatedVersion1, successor: version2 } = await consumptionController.attributes.succeedPeerSharedIdentityAttribute(version1.id, successorParams2); @@ -2385,7 +2596,7 @@ describe("AttributesController", function () { for (const version of allVersions) { const result = await consumptionController.attributes.getVersionsOfAttribute(version.id); - expect(result).toStrictEqual(allVersions); + expect(JSON.stringify(result)).toStrictEqual(JSON.stringify(allVersions)); } }); @@ -2438,7 +2649,7 @@ describe("AttributesController", function () { }; const onlyVersion0 = await consumptionController.attributes.getVersionsOfAttribute(version0.id); - expect(onlyVersion0).toStrictEqual([version0]); + expect(JSON.stringify(onlyVersion0)).toStrictEqual(JSON.stringify([version0])); const { predecessor: updatedVersion0, successor: version1 } = await consumptionController.attributes.succeedOwnSharedRelationshipAttribute( version0.id, @@ -2452,7 +2663,7 @@ describe("AttributesController", function () { const allVersions = [version2, updatedVersion1, updatedVersion0]; for (const version of allVersions) { const result = await consumptionController.attributes.getVersionsOfAttribute(version.id); - expect(result).toStrictEqual(allVersions); + expect(JSON.stringify(result)).toStrictEqual(JSON.stringify(allVersions)); } }); @@ -2505,7 +2716,7 @@ describe("AttributesController", function () { }; const onlyVersion0 = await consumptionController.attributes.getVersionsOfAttribute(version0.id); - expect(onlyVersion0).toStrictEqual([version0]); + expect(JSON.stringify(onlyVersion0)).toStrictEqual(JSON.stringify([version0])); const { predecessor: updatedVersion0, successor: version1 } = await consumptionController.attributes.succeedPeerSharedRelationshipAttribute( version0.id, @@ -2519,7 +2730,7 @@ describe("AttributesController", function () { const allVersions = [version2, updatedVersion1, updatedVersion0]; for (const version of allVersions) { const result = await consumptionController.attributes.getVersionsOfAttribute(version.id); - expect(result).toStrictEqual(allVersions); + expect(JSON.stringify(result)).toStrictEqual(JSON.stringify(allVersions)); } }); @@ -2651,7 +2862,7 @@ describe("AttributesController", function () { test("should return all shared predecessors for a single peer", async function () { const result = await consumptionController.attributes.getSharedPredecessorsOfAttribute(repositoryAttributeV2, { "shareInfo.peer": "peerB" }); - expect(result).toStrictEqual([ownSharedIdentityAttributeV1PeerB]); + expect(JSON.stringify(result)).toStrictEqual(JSON.stringify([ownSharedIdentityAttributeV1PeerB])); }); test("should return all shared successors for all peers", async function () { @@ -2661,7 +2872,7 @@ describe("AttributesController", function () { test("should return all shared successors for a single peer", async function () { const result = await consumptionController.attributes.getSharedSuccessorsOfAttribute(repositoryAttributeV0, { "shareInfo.peer": "peerB" }); - expect(result).toStrictEqual([ownSharedIdentityAttributeV1PeerB, ownSharedIdentityAttributeV2PeerB]); + expect(JSON.stringify(result)).toStrictEqual(JSON.stringify([ownSharedIdentityAttributeV1PeerB, ownSharedIdentityAttributeV2PeerB])); }); test("should return all shared versions for all peers", async function () { @@ -2685,10 +2896,10 @@ describe("AttributesController", function () { const allOwnSharedAttributeVersionsPeerB = [ownSharedIdentityAttributeV2PeerB, ownSharedIdentityAttributeV1PeerB]; for (const repositoryAttributeVersion of allRepositoryAttributeVersions) { const resultA = await consumptionController.attributes.getSharedVersionsOfAttribute(repositoryAttributeVersion.id, [CoreAddress.from("peerA")], false); - expect(resultA).toStrictEqual([ownSharedIdentityAttributeV1PeerA]); + expect(JSON.stringify(resultA)).toStrictEqual(JSON.stringify([ownSharedIdentityAttributeV1PeerA])); const resultB = await consumptionController.attributes.getSharedVersionsOfAttribute(repositoryAttributeVersion.id, [CoreAddress.from("peerB")], false); - expect(resultB).toStrictEqual(allOwnSharedAttributeVersionsPeerB); + expect(JSON.stringify(resultB)).toStrictEqual(JSON.stringify(allOwnSharedAttributeVersionsPeerB)); } }); diff --git a/packages/content/package.json b/packages/content/package.json index 169098238..b8966713b 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.12", + "version": "5.0.0-alpha.13", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index b4727d093..983eba472 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.12", + "version": "5.0.0-alpha.13", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.12", - "@nmshd/content": "5.0.0-alpha.12", + "@nmshd/consumption": "5.0.0-alpha.13", + "@nmshd/content": "5.0.0-alpha.13", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.12", + "@nmshd/transport": "5.0.0-alpha.13", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/dataViews/DataViewExpander.ts b/packages/runtime/src/dataViews/DataViewExpander.ts index f8f2def40..7842e836f 100644 --- a/packages/runtime/src/dataViews/DataViewExpander.ts +++ b/packages/runtime/src/dataViews/DataViewExpander.ts @@ -1216,7 +1216,8 @@ export class DataViewExpander { isDraft: false, sharedWith: sharedToPeerDVOs as SharedToPeerAttributeDVO[], tags: identityAttribute.tags, - valueType + valueType, + isDefault: attribute.isDefault }; } diff --git a/packages/runtime/src/dataViews/consumption/LocalAttributeDVO.ts b/packages/runtime/src/dataViews/consumption/LocalAttributeDVO.ts index 454295d97..88f10a623 100644 --- a/packages/runtime/src/dataViews/consumption/LocalAttributeDVO.ts +++ b/packages/runtime/src/dataViews/consumption/LocalAttributeDVO.ts @@ -1,6 +1,6 @@ import { IdentityAttributeJSON, RelationshipAttributeCreationHintsJSON, RelationshipAttributeJSON, RenderHintsJSON, ValueHintsJSON } from "@nmshd/content"; -import { AttributeQueryDVO } from "../content/AttributeDVOs"; import { DataViewObject } from "../DataViewObject"; +import { AttributeQueryDVO } from "../content/AttributeDVOs"; import { IdentityDVO } from "../transport"; /** @@ -34,6 +34,7 @@ export interface RepositoryAttributeDVO extends LocalAttributeDVO { sharedWith: SharedToPeerAttributeDVO[]; isOwn: true; tags?: string[]; + isDefault?: true; } /** diff --git a/packages/runtime/src/extensibility/facades/consumption/AttributesFacade.ts b/packages/runtime/src/extensibility/facades/consumption/AttributesFacade.ts index ae86520c6..181fd2c63 100644 --- a/packages/runtime/src/extensibility/facades/consumption/AttributesFacade.ts +++ b/packages/runtime/src/extensibility/facades/consumption/AttributesFacade.ts @@ -2,6 +2,8 @@ import { Result } from "@js-soft/ts-utils"; import { Inject } from "typescript-ioc"; import { LocalAttributeDTO, LocalRequestDTO } from "../../../types"; import { + ChangeDefaultRepositoryAttributeRequest, + ChangeDefaultRepositoryAttributeUseCase, CreateAndShareRelationshipAttributeRequest, CreateAndShareRelationshipAttributeUseCase, CreateRepositoryAttributeRequest, @@ -60,7 +62,6 @@ import { export class AttributesFacade { public constructor( @Inject private readonly createRepositoryAttributeUseCase: CreateRepositoryAttributeUseCase, - @Inject private readonly shareRepositoryAttributeUseCase: ShareRepositoryAttributeUseCase, @Inject private readonly getPeerSharedAttributesUseCase: GetPeerSharedAttributesUseCase, @Inject private readonly getOwnSharedAttributesUseCase: GetOwnSharedAttributesUseCase, @Inject private readonly getRepositoryAttributesUseCase: GetRepositoryAttributesUseCase, @@ -68,15 +69,17 @@ export class AttributesFacade { @Inject private readonly getAttributesUseCase: GetAttributesUseCase, @Inject private readonly getVersionsOfAttributeUseCase: GetVersionsOfAttributeUseCase, @Inject private readonly getSharedVersionsOfAttributeUseCase: GetSharedVersionsOfAttributeUseCase, - @Inject private readonly succeedRepositoryAttributeUseCase: SucceedRepositoryAttributeUseCase, @Inject private readonly executeIdentityAttributeQueryUseCase: ExecuteIdentityAttributeQueryUseCase, @Inject private readonly executeRelationshipAttributeQueryUseCase: ExecuteRelationshipAttributeQueryUseCase, - @Inject private readonly succeedRelationshipAttributeAndNotifyPeerUseCase: SucceedRelationshipAttributeAndNotifyPeerUseCase, @Inject private readonly executeThirdPartyRelationshipAttributeQueryUseCase: ExecuteThirdPartyRelationshipAttributeQueryUseCase, @Inject private readonly executeIQLQueryUseCase: ExecuteIQLQueryUseCase, @Inject private readonly validateIQLQueryUseCase: ValidateIQLQueryUseCase, - @Inject private readonly createAndShareRelationshipAttributeUseCase: CreateAndShareRelationshipAttributeUseCase, + @Inject private readonly succeedRepositoryAttributeUseCase: SucceedRepositoryAttributeUseCase, + @Inject private readonly shareRepositoryAttributeUseCase: ShareRepositoryAttributeUseCase, @Inject private readonly notifyPeerAboutRepositoryAttributeSuccessionUseCase: NotifyPeerAboutRepositoryAttributeSuccessionUseCase, + @Inject private readonly createAndShareRelationshipAttributeUseCase: CreateAndShareRelationshipAttributeUseCase, + @Inject private readonly succeedRelationshipAttributeAndNotifyPeerUseCase: SucceedRelationshipAttributeAndNotifyPeerUseCase, + @Inject private readonly changeDefaultRepositoryAttributeUseCase: ChangeDefaultRepositoryAttributeUseCase, @Inject private readonly deleteOwnSharedAttributeAndNotifyPeerUseCase: DeleteOwnSharedAttributeAndNotifyPeerUseCase, @Inject private readonly deletePeerSharedAttributeAndNotifyOwnerUseCase: DeletePeerSharedAttributeAndNotifyOwnerUseCase, @Inject private readonly deleteThirdPartyOwnedRelationshipAttributeAndNotifyPeerUseCase: DeleteThirdPartyOwnedRelationshipAttributeAndNotifyPeerUseCase, @@ -124,12 +127,6 @@ export class AttributesFacade { return await this.executeRelationshipAttributeQueryUseCase.execute(request); } - public async succeedRelationshipAttributeAndNotifyPeer( - request: SucceedRelationshipAttributeAndNotifyPeerRequest - ): Promise> { - return await this.succeedRelationshipAttributeAndNotifyPeerUseCase.execute(request); - } - public async executeThirdPartyRelationshipAttributeQuery(request: ExecuteThirdPartyRelationshipAttributeQueryRequest): Promise> { return await this.executeThirdPartyRelationshipAttributeQueryUseCase.execute(request); } @@ -150,16 +147,26 @@ export class AttributesFacade { return await this.shareRepositoryAttributeUseCase.execute(request); } - public async createAndShareRelationshipAttribute(request: CreateAndShareRelationshipAttributeRequest): Promise> { - return await this.createAndShareRelationshipAttributeUseCase.execute(request); - } - public async notifyPeerAboutRepositoryAttributeSuccession( request: NotifyPeerAboutRepositoryAttributeSuccessionRequest ): Promise> { return await this.notifyPeerAboutRepositoryAttributeSuccessionUseCase.execute(request); } + public async createAndShareRelationshipAttribute(request: CreateAndShareRelationshipAttributeRequest): Promise> { + return await this.createAndShareRelationshipAttributeUseCase.execute(request); + } + + public async succeedRelationshipAttributeAndNotifyPeer( + request: SucceedRelationshipAttributeAndNotifyPeerRequest + ): Promise> { + return await this.succeedRelationshipAttributeAndNotifyPeerUseCase.execute(request); + } + + public async changeDefaultRepositoryAttribute(request: ChangeDefaultRepositoryAttributeRequest): Promise> { + return await this.changeDefaultRepositoryAttributeUseCase.execute(request); + } + public async deleteOwnSharedAttributeAndNotifyPeer(request: DeleteOwnSharedAttributeAndNotifyPeerRequest): Promise> { return await this.deleteOwnSharedAttributeAndNotifyPeerUseCase.execute(request); } diff --git a/packages/runtime/src/types/consumption/LocalAttributeDTO.ts b/packages/runtime/src/types/consumption/LocalAttributeDTO.ts index 6e110e998..8b08c0742 100644 --- a/packages/runtime/src/types/consumption/LocalAttributeDTO.ts +++ b/packages/runtime/src/types/consumption/LocalAttributeDTO.ts @@ -10,4 +10,5 @@ export interface LocalAttributeDTO { succeededBy?: string; shareInfo?: LocalAttributeShareInfoJSON; deletionInfo?: LocalAttributeDeletionInfoJSON; + isDefault?: true; } diff --git a/packages/runtime/src/useCases/common/RuntimeErrors.ts b/packages/runtime/src/useCases/common/RuntimeErrors.ts index 7df1cd074..dda47bf4f 100644 --- a/packages/runtime/src/useCases/common/RuntimeErrors.ts +++ b/packages/runtime/src/useCases/common/RuntimeErrors.ts @@ -1,4 +1,5 @@ import { ApplicationError } from "@js-soft/ts-utils"; +import { LocalAttribute } from "@nmshd/consumption"; import { CoreAddress, CoreId } from "@nmshd/transport"; import { Base64ForIdPrefix } from "./Base64ForIdPrefix"; @@ -193,6 +194,13 @@ class Attributes { ); } + public hasSuccessor(predecessor: LocalAttribute): ApplicationError { + return new ApplicationError( + "error.runtime.attributes.hasSuccessor", + `Attribute '${predecessor.id.toString()}' already has a successor ${predecessor.succeededBy?.toString()}.` + ); + } + public cannotSeparatelyDeleteChildOfComplexAttribute(attributeId: CoreId | string): ApplicationError { return new ApplicationError( "error.runtime.attributes.cannotSeparatelyDeleteChildOfComplexAttribute", diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index 06f70d73b..110e365da 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -71,6 +71,29 @@ export const GetAttributeListenerRequest: any = { } } +export const ChangeDefaultRepositoryAttributeRequest: any = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/ChangeDefaultRepositoryAttributeRequest", + "definitions": { + "ChangeDefaultRepositoryAttributeRequest": { + "type": "object", + "properties": { + "attributeId": { + "$ref": "#/definitions/AttributeIdString" + } + }, + "required": [ + "attributeId" + ], + "additionalProperties": false + }, + "AttributeIdString": { + "type": "string", + "pattern": "ATT[A-Za-z0-9]{17}" + } + } +} + export const AcceptIncomingRequestRequest: any = { "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/definitions/AcceptIncomingRequestRequest", @@ -16741,7 +16764,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.@type": { + "succeeds": { "anyOf": [ { "type": "string" @@ -16754,7 +16777,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.tags": { + "succeededBy": { "anyOf": [ { "type": "string" @@ -16767,7 +16790,10 @@ export const GetAttributesRequest: any = { } ] }, - "content.owner": { + "isDefault": { + "type": "string" + }, + "content.@type": { "anyOf": [ { "type": "string" @@ -16780,7 +16806,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.validFrom": { + "content.tags": { "anyOf": [ { "type": "string" @@ -16793,7 +16819,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.validTo": { + "content.owner": { "anyOf": [ { "type": "string" @@ -16806,7 +16832,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.key": { + "content.validFrom": { "anyOf": [ { "type": "string" @@ -16819,10 +16845,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.isTechnical": { - "type": "string" - }, - "content.confidentiality": { + "content.validTo": { "anyOf": [ { "type": "string" @@ -16835,7 +16858,7 @@ export const GetAttributesRequest: any = { } ] }, - "content.value.@type": { + "content.key": { "anyOf": [ { "type": "string" @@ -16848,7 +16871,10 @@ export const GetAttributesRequest: any = { } ] }, - "succeeds": { + "content.isTechnical": { + "type": "string" + }, + "content.confidentiality": { "anyOf": [ { "type": "string" @@ -16861,7 +16887,7 @@ export const GetAttributesRequest: any = { } ] }, - "succeededBy": { + "content.value.@type": { "anyOf": [ { "type": "string" @@ -17454,6 +17480,9 @@ export const GetRepositoryAttributesRequest: any = { "createdAt": { "type": "string" }, + "isDefault": { + "type": "string" + }, "content.tags": { "anyOf": [ { diff --git a/packages/runtime/src/useCases/consumption/attributes/AttributeMapper.ts b/packages/runtime/src/useCases/consumption/attributes/AttributeMapper.ts index f57daac89..bef4a1c3d 100644 --- a/packages/runtime/src/useCases/consumption/attributes/AttributeMapper.ts +++ b/packages/runtime/src/useCases/consumption/attributes/AttributeMapper.ts @@ -11,7 +11,8 @@ export class AttributeMapper { succeeds: attribute.succeeds?.toString(), succeededBy: attribute.succeededBy?.toString(), shareInfo: attribute.shareInfo?.toJSON() as LocalAttributeShareInfoJSON, - deletionInfo: attribute.deletionInfo?.toJSON() as LocalAttributeDeletionInfoJSON + deletionInfo: attribute.deletionInfo?.toJSON() as LocalAttributeDeletionInfoJSON, + isDefault: attribute.isDefault }; } diff --git a/packages/runtime/src/useCases/consumption/attributes/ChangeDefaultRepositoryAttribute.ts b/packages/runtime/src/useCases/consumption/attributes/ChangeDefaultRepositoryAttribute.ts new file mode 100644 index 000000000..3773107fd --- /dev/null +++ b/packages/runtime/src/useCases/consumption/attributes/ChangeDefaultRepositoryAttribute.ts @@ -0,0 +1,46 @@ +import { Result } from "@js-soft/ts-utils"; +import { AttributesController, LocalAttribute } from "@nmshd/consumption"; +import { AccountController, CoreId } from "@nmshd/transport"; +import { Inject } from "typescript-ioc"; +import { LocalAttributeDTO } from "../../../types"; +import { AttributeIdString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common"; +import { AttributeMapper } from "./AttributeMapper"; + +export interface ChangeDefaultRepositoryAttributeRequest { + attributeId: AttributeIdString; +} + +class Validator extends SchemaValidator { + public constructor(@Inject schemaRepository: SchemaRepository) { + super(schemaRepository.getSchema("ChangeDefaultRepositoryAttributeRequest")); + } +} + +export class ChangeDefaultRepositoryAttributeUseCase extends UseCase { + public constructor( + @Inject private readonly attributesController: AttributesController, + @Inject private readonly accountController: AccountController, + @Inject validator: Validator + ) { + super(validator); + } + + protected async executeInternal(request: ChangeDefaultRepositoryAttributeRequest): Promise> { + const newDefaultAttribute = await this.attributesController.getLocalAttribute(CoreId.from(request.attributeId)); + if (!newDefaultAttribute) return Result.fail(RuntimeErrors.general.recordNotFound(LocalAttribute)); + + if (!newDefaultAttribute.isRepositoryAttribute(this.accountController.identity.address)) { + return Result.fail(RuntimeErrors.attributes.isNotRepositoryAttribute(CoreId.from(request.attributeId))); + } + + if (newDefaultAttribute.succeededBy) { + return Result.fail(RuntimeErrors.attributes.hasSuccessor(newDefaultAttribute)); + } + + const defaultRepositoryAttribute = await this.attributesController.setAsDefaultRepositoryAttribute(newDefaultAttribute, false); + + await this.accountController.syncDatawallet(); + + return Result.ok(AttributeMapper.toAttributeDTO(defaultRepositoryAttribute)); + } +} diff --git a/packages/runtime/src/useCases/consumption/attributes/GetAttributes.ts b/packages/runtime/src/useCases/consumption/attributes/GetAttributes.ts index 43c787500..abfa816de 100644 --- a/packages/runtime/src/useCases/consumption/attributes/GetAttributes.ts +++ b/packages/runtime/src/useCases/consumption/attributes/GetAttributes.ts @@ -19,6 +19,9 @@ export interface GetAttributesRequest { export interface GetAttributesRequestQuery { createdAt?: string; parentId?: string | string[]; + succeeds?: string | string[]; + succeededBy?: string | string[]; + isDefault?: string; "content.@type"?: string | string[]; "content.tags"?: string | string[]; "content.owner"?: string | string[]; @@ -28,8 +31,6 @@ export interface GetAttributesRequestQuery { "content.isTechnical"?: string; "content.confidentiality"?: string | string[]; "content.value.@type"?: string | string[]; - succeeds?: string | string[]; - succeededBy?: string | string[]; shareInfo?: string | string[]; "shareInfo.requestReference"?: string | string[]; "shareInfo.notificationReference"?: string | string[]; @@ -47,6 +48,7 @@ export class GetAttributesUseCase extends UseCase((x) => x.parentId)]: true, [nameof((x) => x.succeeds)]: true, [nameof((x) => x.succeededBy)]: true, + [nameof((x) => x.isDefault)]: true, // content.abstractAttribute [`${nameof((x) => x.content)}.${nameof((x) => x.validFrom)}`]: true, @@ -80,6 +82,7 @@ export class GetAttributesUseCase extends UseCase((x) => x.parentId)]: nameof((x) => x.parentId), [nameof((x) => x.succeeds)]: nameof((x) => x.succeeds), [nameof((x) => x.succeededBy)]: nameof((x) => x.succeededBy), + [nameof((x) => x.isDefault)]: nameof((x) => x.isDefault), // content.abstractAttribute [`${nameof((x) => x.content)}.${nameof((x) => x.validFrom)}`]: `${nameof((x) => x.content)}.${nameof((x) => x.validFrom)}`, diff --git a/packages/runtime/src/useCases/consumption/attributes/GetRepositoryAttributes.ts b/packages/runtime/src/useCases/consumption/attributes/GetRepositoryAttributes.ts index 0395fba62..ff43cd711 100644 --- a/packages/runtime/src/useCases/consumption/attributes/GetRepositoryAttributes.ts +++ b/packages/runtime/src/useCases/consumption/attributes/GetRepositoryAttributes.ts @@ -3,7 +3,7 @@ import { AttributesController } from "@nmshd/consumption"; import { Inject } from "typescript-ioc"; import { AttributeMapper, GetAttributesRequestQuery, GetAttributesUseCase } from ".."; import { LocalAttributeDTO } from "../../../types"; -import { flattenObject, SchemaRepository, SchemaValidator, UseCase } from "../../common"; +import { SchemaRepository, SchemaValidator, UseCase, flattenObject } from "../../common"; export interface GetRepositoryAttributesRequest { /** @@ -15,6 +15,7 @@ export interface GetRepositoryAttributesRequest { export interface GetRepositoryAttributesRequestQuery { createdAt?: string; + isDefault?: string; "content.tags"?: string | string[]; "content.validFrom"?: string | string[]; "content.validTo"?: string | string[]; diff --git a/packages/runtime/src/useCases/consumption/attributes/index.ts b/packages/runtime/src/useCases/consumption/attributes/index.ts index 3adbaeb42..f670d5d7f 100644 --- a/packages/runtime/src/useCases/consumption/attributes/index.ts +++ b/packages/runtime/src/useCases/consumption/attributes/index.ts @@ -1,4 +1,5 @@ export * from "./AttributeMapper"; +export * from "./ChangeDefaultRepositoryAttribute"; export * from "./CreateAndShareRelationshipAttribute"; export * from "./CreateRepositoryAttribute"; export * from "./DeleteOwnSharedAttributeAndNotifyPeer"; diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index 1f9107dbb..eb11c6a00 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -15,6 +15,7 @@ import { import { CoreAddress, CoreDate, CoreId } from "@nmshd/transport"; import { AttributeCreatedEvent, + ChangeDefaultRepositoryAttributeUseCase, CreateAndShareRelationshipAttributeRequest, CreateAndShareRelationshipAttributeUseCase, CreateRepositoryAttributeRequest, @@ -106,6 +107,14 @@ describe("get attribute(s)", () => { beforeAll(async function () { const senderRequests: CreateRepositoryAttributeRequest[] = [ + { + content: { + value: { + "@type": "GivenName", + value: "AGivenName" + } + } + }, { content: { value: { @@ -117,8 +126,8 @@ describe("get attribute(s)", () => { { content: { value: { - "@type": "GivenName", - value: "AGivenName" + "@type": "Surname", + value: "Another Surname" } } } @@ -163,7 +172,7 @@ describe("get attribute(s)", () => { const result = await services1.consumption.attributes.getAttributes({ query: {} }); expect(result.isSuccess).toBe(true); const attributes = result.value; - expect(attributes).toHaveLength(3); + expect(attributes).toHaveLength(4); const attributeIds = attributes.map((attribute) => attribute.id); expect(attributeIds).toContain(relationshipAttributeId); expect(attributeIds).toStrictEqual(expect.arrayContaining(identityAttributeIds)); @@ -171,7 +180,7 @@ describe("get attribute(s)", () => { test("should allow to get an attribute by type", async function () { const result = await services1.consumption.attributes.getAttributes({ - query: { "content.value.@type": "Surname" } + query: { "content.value.@type": "GivenName" } }); expect(result).toBeSuccessful(); @@ -189,7 +198,10 @@ describe("get attribute(s)", () => { expect(result).toBeSuccessful(); const attributes = result.value; - expect(attributes).toHaveLength(2); + expect(attributes).toHaveLength(3); + + const attributeIds = attributes.map((attribute) => attribute.id); + expect(attributeIds).toStrictEqual(expect.arrayContaining(identityAttributeIds)); }); test("should hide technical attributes when hideTechnical=true", async () => { @@ -197,7 +209,7 @@ describe("get attribute(s)", () => { expect(result.isSuccess).toBe(true); const attributes = result.value; expect(attributes.filter((a) => a.id === relationshipAttributeId)).toHaveLength(0); - expect(attributes).toHaveLength(2); + expect(attributes).toHaveLength(3); const attributeIds = attributes.map((attribute) => attribute.id); expect(attributeIds).toStrictEqual(identityAttributeIds); }); @@ -207,11 +219,41 @@ describe("get attribute(s)", () => { expect(getAttributesResponse.isSuccess).toBe(true); const attributes = getAttributesResponse.value; expect(attributes.filter((a) => a.id === relationshipAttributeId)).toHaveLength(1); - expect(attributes).toHaveLength(3); + expect(attributes).toHaveLength(4); const attributeIds = attributes.map((attribute) => attribute.id); expect(attributeIds).toContain(relationshipAttributeId); expect(attributeIds).toStrictEqual(expect.arrayContaining(identityAttributeIds)); }); + + test("should allow to get only default attributes", async function () { + const result = await services1.consumption.attributes.getAttributes({ + query: { isDefault: "true" } + }); + expect(result).toBeSuccessful(); + + const attributes = result.value; + expect(attributes).toHaveLength(2); + + const attributeIds = attributes.map((attr) => attr.id); + expect(attributeIds).toContain(identityAttributeIds[0]); + expect(attributeIds).toContain(identityAttributeIds[1]); + expect(attributeIds).not.toContain(identityAttributeIds[2]); + expect(attributeIds).not.toContain(relationshipAttributeId); + }); + + test("should allow not to get default attributes", async function () { + const result = await services1.consumption.attributes.getAttributes({ + query: { isDefault: "!true" } + }); + expect(result).toBeSuccessful(); + + const attributes = result.value; + expect(attributes).toHaveLength(2); + + const attributeIds = attributes.map((attr) => attr.id); + expect(attributeIds).toContain(relationshipAttributeId); + expect(attributeIds).toContain(identityAttributeIds[2]); + }); }); }); @@ -452,6 +494,22 @@ describe("get repository, own shared and peer shared attributes", () => { const repositoryAttributes = result.value; expect(repositoryAttributes).toStrictEqual([services1RepoSurnameV0, services1RepoSurnameV1, services1RepoGivenNameV0, services1RepoGivenNameV1]); }); + + test("should allow to get only default attributes", async function () { + const result = await services1.consumption.attributes.getRepositoryAttributes({ + query: { + isDefault: "true" + } + }); + expect(result).toBeSuccessful(); + + const attributes = result.value; + expect(attributes).toHaveLength(2); + + const attributeIds = attributes.map((attr) => attr.id); + expect(attributeIds).toContain(services1RepoSurnameV1.id); + expect(attributeIds).toContain(services1RepoGivenNameV1.id); + }); }); describe(GetOwnSharedAttributesUseCase.name, () => { @@ -611,6 +669,35 @@ describe(CreateRepositoryAttributeUseCase.name, () => { await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "City"); await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "Country"); }); + + test("should create a RepositoryAttribute that is the default if it is the first of its value type", async () => { + const request: CreateRepositoryAttributeRequest = { + content: { + value: { + "@type": "Pseudonym", + value: "A pseudonym" + } + } + }; + const result = await services1.consumption.attributes.createRepositoryAttribute(request); + const attribute = result.value; + expect(attribute.isDefault).toBe(true); + }); + + test("should create a RepositoryAttribute that is not the default if it is not the first of its value type", async () => { + const request: CreateRepositoryAttributeRequest = { + content: { + value: { + "@type": "JobTitle", + value: "A job title" + } + } + }; + await services1.consumption.attributes.createRepositoryAttribute(request); + const result = await services1.consumption.attributes.createRepositoryAttribute(request); + const attribute = result.value; + expect(attribute.isDefault).toBeUndefined(); + }); }); describe(ShareRepositoryAttributeUseCase.name, () => { @@ -1258,6 +1345,114 @@ describe(SucceedRelationshipAttributeAndNotifyPeerUseCase.name, () => { }); }); +describe(ChangeDefaultRepositoryAttributeUseCase.name, () => { + beforeAll(async () => { + await cleanupAttributes(); + }); + + test("should change default RepositoryAttribute", async () => { + const defaultAttribute = ( + await services1.consumption.attributes.createRepositoryAttribute({ + content: { + value: { + "@type": "GivenName", + value: "My default name" + } + } + }) + ).value; + expect(defaultAttribute.isDefault).toBe(true); + + const desiredDefaultAttribute = ( + await services1.consumption.attributes.createRepositoryAttribute({ + content: { + value: { + "@type": "GivenName", + value: "My new default name" + } + } + }) + ).value; + expect(desiredDefaultAttribute.isDefault).toBeUndefined(); + + const result = await services1.consumption.attributes.changeDefaultRepositoryAttribute({ attributeId: desiredDefaultAttribute.id }); + expect(result.isSuccess).toBe(true); + const newDefaultAttribute = result.value; + expect(newDefaultAttribute.isDefault).toBe(true); + + const updatedFormerDesiredDefaultAttribute = (await services1.consumption.attributes.getAttribute({ id: desiredDefaultAttribute.id })).value; + expect(updatedFormerDesiredDefaultAttribute.isDefault).toBe(true); + + const updatedFormerDefaultAttribute = (await services1.consumption.attributes.getAttribute({ id: defaultAttribute.id })).value; + expect(updatedFormerDefaultAttribute.isDefault).toBeUndefined(); + }); + + test("should return an error if the new default attribute is not a RepositoryAttribute", async () => { + await services1.consumption.attributes.createRepositoryAttribute({ + content: { + value: { + "@type": "GivenName", + value: "My default name" + } + } + }); + + const desiredSharedDefaultAttribute = await executeFullCreateAndShareRepositoryAttributeFlow(services1, services2, { + content: { + value: { + "@type": "GivenName", + value: "My new shared name" + } + } + }); + const result = await services1.consumption.attributes.changeDefaultRepositoryAttribute({ attributeId: desiredSharedDefaultAttribute.id }); + expect(result).toBeAnError(`Attribute '${desiredSharedDefaultAttribute.id.toString()}' is not a RepositoryAttribute.`, "error.runtime.attributes.isNotRepositoryAttribute"); + }); + + test("should return an error if the new default attribute has a successor", async () => { + await services1.consumption.attributes.createRepositoryAttribute({ + content: { + value: { + "@type": "GivenName", + value: "My default name" + } + } + }); + + const desiredDefaultAttribute = ( + await services1.consumption.attributes.createRepositoryAttribute({ + content: { + value: { + "@type": "GivenName", + value: "My new default name" + } + } + }) + ).value; + + const successionResult = ( + await services1.consumption.attributes.succeedRepositoryAttribute({ + predecessorId: desiredDefaultAttribute.id, + successorContent: { + value: { + "@type": "GivenName", + value: "My new successor default name" + } + } + }) + ).value; + + const updatedDesiredDefaultAttribute = successionResult.predecessor; + const desiredDefaultAttributeSuccessor = successionResult.successor; + + const result = await services1.consumption.attributes.changeDefaultRepositoryAttribute({ attributeId: updatedDesiredDefaultAttribute.id }); + expect(result).toBeAnError( + `Attribute '${updatedDesiredDefaultAttribute.id.toString()}' already has a successor ${desiredDefaultAttributeSuccessor.id.toString()}.`, + "error.runtime.attributes.hasSuccessor" + ); + }); +}); + describe("Get (shared) versions of attribute", () => { let sRepositoryAttributeVersion0: LocalAttributeDTO; let sRepositoryAttributeVersion1: LocalAttributeDTO; diff --git a/packages/transport/package.json b/packages/transport/package.json index 7c3916b75..68bfb6fc4 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.12", + "version": "5.0.0-alpha.13", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { From 974fdcf32ccb217b937fe6db6a9ad8268b08413c Mon Sep 17 00:00:00 2001 From: Milena Czierlinski <146972016+Milena-Czierlinski@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:17:34 +0200 Subject: [PATCH 29/40] Fix/ShareAttribute with deletionInfo (#213) * fix: allow to share RepositoryAttributes with shared other versions with deletionInfo again * feat: adjust error messages * feat: add validation for ThirdPartyRelationshipAttributes * fix: adjust error messages in tests --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../ShareAttributeRequestItemProcessor.ts | 29 +- ...ShareAttributeRequestItemProcessor.test.ts | 359 +++++++++++++++++- .../test/consumption/attributes.test.ts | 4 +- 3 files changed, 375 insertions(+), 17 deletions(-) diff --git a/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts index f678d7841..5fa1eda12 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.ts @@ -57,31 +57,33 @@ export class ShareAttributeRequestItemProcessor extends GenericRequestItemProces if ((await this.consumptionController.attributes.getLocalAttributes(query)).length > 0) { return ValidationResult.error( CoreErrors.requests.invalidRequestItem( - `The IdentityAttribute with the given sourceAttributeId '${requestItem.sourceAttributeId.toString()}' has already been shared with the peer.` + `The IdentityAttribute with the given sourceAttributeId '${requestItem.sourceAttributeId.toString()}' is already shared with the peer.` ) ); } const ownSharedIdentityAttributeSuccessors = await this.consumptionController.attributes.getSharedSuccessorsOfAttribute(foundAttribute, { - "shareInfo.peer": recipient.toString() + "shareInfo.peer": recipient.toString(), + "deletionInfo.deletionStatus": { $nin: [DeletionStatus.DeletedByPeer, DeletionStatus.ToBeDeletedByPeer] } }); if (ownSharedIdentityAttributeSuccessors.length > 0) { return ValidationResult.error( CoreErrors.requests.invalidRequestItem( - `The provided IdentityAttribute is outdated. You have already shared the successor '${ownSharedIdentityAttributeSuccessors[0].shareInfo?.sourceAttribute}' of it.` + `The provided IdentityAttribute is outdated. Its successor '${ownSharedIdentityAttributeSuccessors[0].shareInfo?.sourceAttribute}' is already shared with the peer.` ) ); } const ownSharedIdentityAttributePredecessors = await this.consumptionController.attributes.getSharedPredecessorsOfAttribute(foundAttribute, { - "shareInfo.peer": recipient.toString() + "shareInfo.peer": recipient.toString(), + "deletionInfo.deletionStatus": { $nin: [DeletionStatus.DeletedByPeer, DeletionStatus.ToBeDeletedByPeer] } }); if (ownSharedIdentityAttributePredecessors.length > 0) { return ValidationResult.error( CoreErrors.requests.invalidRequestItem( - `You have already shared the predecessor '${ownSharedIdentityAttributePredecessors[0].shareInfo?.sourceAttribute}' of the IdentityAttribute. Instead of sharing it, you should notify the peer about the Attribute succession.` + `The predecessor '${ownSharedIdentityAttributePredecessors[0].shareInfo?.sourceAttribute}' of the IdentityAttribute is already shared with the peer. Instead of sharing it, you should notify the peer about the Attribute succession.` ) ); } @@ -99,10 +101,19 @@ export class ShareAttributeRequestItemProcessor extends GenericRequestItemProces return ValidationResult.error(CoreErrors.requests.invalidRequestItem("You can only share RelationshipAttributes that are not a copy of a sourceAttribute.")); } - if (typeof recipient !== "undefined" && foundAttribute.shareInfo.peer.equals(recipient)) { - return ValidationResult.error( - CoreErrors.requests.invalidRequestItem("The provided RelationshipAttribute already exists in the context of the Relationship with the peer.") - ); + if (typeof recipient !== "undefined") { + const query: any = { + "shareInfo.sourceAttribute": requestItem.sourceAttributeId.toString(), + "shareInfo.peer": recipient.toString(), + "deletionInfo.deletionStatus": { $nin: [DeletionStatus.DeletedByPeer, DeletionStatus.ToBeDeletedByPeer] } + }; + const thirdPartyRelationshipAttribute = await this.consumptionController.attributes.getLocalAttributes(query); + + if (foundAttribute.shareInfo.peer.equals(recipient) || thirdPartyRelationshipAttribute.length > 0) { + return ValidationResult.error( + CoreErrors.requests.invalidRequestItem("The provided RelationshipAttribute already exists in the context of the Relationship with the peer.") + ); + } } } diff --git a/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts index 959823744..af07a7cb3 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/shareAttribute/ShareAttributeRequestItemProcessor.test.ts @@ -300,7 +300,7 @@ describe("ShareAttributeRequestItemProcessor", function () { expect(result).errorValidationResult({ code: "error.consumption.requests.invalidRequestItem", - message: `The IdentityAttribute with the given sourceAttributeId '${requestItem.sourceAttributeId.toString()}' has already been shared with the peer.` + message: `The IdentityAttribute with the given sourceAttributeId '${requestItem.sourceAttributeId.toString()}' is already shared with the peer.` }); }); @@ -375,7 +375,7 @@ describe("ShareAttributeRequestItemProcessor", function () { }, deletionInfo: { deletionStatus: DeletionStatus.ToBeDeletedByPeer, - deletionDate: CoreDate.utc().subtract({ days: 1 }) + deletionDate: CoreDate.utc().add({ days: 1 }) } }); expect(localAttributeCopy.isShared()).toBe(true); @@ -392,7 +392,7 @@ describe("ShareAttributeRequestItemProcessor", function () { expect(result).successfulValidationResult(); }); - test("returns an error when a successor of the existing IdentityAttribute is already shared", async function () { + test("returns an error when a successor of the existing IdentityAttribute is already shared with the peer", async function () { const sender = testAccount.identity.address; const recipient = CoreAddress.from("Recipient"); @@ -430,10 +430,112 @@ describe("ShareAttributeRequestItemProcessor", function () { expect(result).errorValidationResult({ code: "error.consumption.requests.invalidRequestItem", - message: `The provided IdentityAttribute is outdated. You have already shared the successor '${ownSharedCopyOfSuccessor.shareInfo?.sourceAttribute?.toString()}' of it.` + message: `The provided IdentityAttribute is outdated. Its successor '${ownSharedCopyOfSuccessor.shareInfo?.sourceAttribute?.toString()}' is already shared with the peer.` }); }); + test("returns success when a successor of the existing IdentityAttribute is already shared with the peer but DeletedByPeer", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ + content: TestObjectFactory.createIdentityAttribute({ + owner: sender + }) + }); + + const { successor: successorOfRepositoryAttribute } = await consumptionController.attributes.succeedRepositoryAttribute(repositoryAttribute.id, { + content: { + "@type": "IdentityAttribute", + owner: sender.toString(), + value: { + "@type": "GivenName", + value: "AnotherGivenName" + } + } + }); + + await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + owner: sender, + value: GivenName.from({ + value: "AGivenName" + }) + }), + shareInfo: { + peer: recipient, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: successorOfRepositoryAttribute.id + }, + deletionInfo: { + deletionStatus: DeletionStatus.DeletedByPeer, + deletionDate: CoreDate.utc().subtract({ days: 1 }) + } + }); + + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: repositoryAttribute.content, + sourceAttributeId: repositoryAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).successfulValidationResult(); + }); + + test("returns success when a successor of the existing IdentityAttribute is already shared with the peer but ToBeDeletedByPeer", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ + content: TestObjectFactory.createIdentityAttribute({ + owner: sender + }) + }); + + const { successor: successorOfRepositoryAttribute } = await consumptionController.attributes.succeedRepositoryAttribute(repositoryAttribute.id, { + content: { + "@type": "IdentityAttribute", + owner: sender.toString(), + value: { + "@type": "GivenName", + value: "AnotherGivenName" + } + } + }); + + await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + owner: sender, + value: GivenName.from({ + value: "AGivenName" + }) + }), + shareInfo: { + peer: recipient, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: successorOfRepositoryAttribute.id + }, + deletionInfo: { + deletionStatus: DeletionStatus.ToBeDeletedByPeer, + deletionDate: CoreDate.utc().add({ days: 1 }) + } + }); + + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: repositoryAttribute.content, + sourceAttributeId: repositoryAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).successfulValidationResult(); + }); + test("returns an error when a predecessor of the existing IdentityAttribute is already shared and therefore the user should notify about the Attribute succession instead of share it.", async function () { const sender = testAccount.identity.address; const recipient = CoreAddress.from("Recipient"); @@ -476,8 +578,110 @@ describe("ShareAttributeRequestItemProcessor", function () { expect(result).errorValidationResult({ code: "error.consumption.requests.invalidRequestItem", - message: `You have already shared the predecessor '${ownSharedCopyOfPredecessor.shareInfo?.sourceAttribute?.toString()}' of the IdentityAttribute. Instead of sharing it, you should notify the peer about the Attribute succession.` + message: `The predecessor '${ownSharedCopyOfPredecessor.shareInfo?.sourceAttribute?.toString()}' of the IdentityAttribute is already shared with the peer. Instead of sharing it, you should notify the peer about the Attribute succession.` + }); + }); + + test("returns success when a predecessor of the existing IdentityAttribute is already shared with the peer but DeletedByPeer", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ + content: TestObjectFactory.createIdentityAttribute({ + owner: sender + }) + }); + + const { successor: successorOfRepositoryAttribute } = await consumptionController.attributes.succeedRepositoryAttribute(repositoryAttribute.id, { + content: { + "@type": "IdentityAttribute", + owner: sender.toString(), + value: { + "@type": "GivenName", + value: "AnotherGivenName" + } + } + }); + + await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + owner: sender, + value: GivenName.from({ + value: "AGivenName" + }) + }), + shareInfo: { + peer: recipient, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: repositoryAttribute.id + }, + deletionInfo: { + deletionStatus: DeletionStatus.DeletedByPeer, + deletionDate: CoreDate.utc().subtract({ days: 1 }) + } + }); + + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: successorOfRepositoryAttribute.content, + sourceAttributeId: successorOfRepositoryAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).successfulValidationResult(); + }); + + test("returns success when a predecessor of the existing IdentityAttribute is already shared with the peer but ToBeDeletedByPeer", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ + content: TestObjectFactory.createIdentityAttribute({ + owner: sender + }) + }); + + const { successor: successorOfRepositoryAttribute } = await consumptionController.attributes.succeedRepositoryAttribute(repositoryAttribute.id, { + content: { + "@type": "IdentityAttribute", + owner: sender.toString(), + value: { + "@type": "GivenName", + value: "AnotherGivenName" + } + } + }); + + await consumptionController.attributes.createAttributeUnsafe({ + content: IdentityAttribute.from({ + owner: sender, + value: GivenName.from({ + value: "AGivenName" + }) + }), + shareInfo: { + peer: recipient, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: repositoryAttribute.id + }, + deletionInfo: { + deletionStatus: DeletionStatus.ToBeDeletedByPeer, + deletionDate: CoreDate.utc().add({ days: 1 }) + } + }); + + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: true, + attribute: successorOfRepositoryAttribute.content, + sourceAttributeId: successorOfRepositoryAttribute.id }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).successfulValidationResult(); }); test("returns error when the RelationshipAttribute is a copy of another RelationshipAttribute", async function () { @@ -513,7 +717,7 @@ describe("ShareAttributeRequestItemProcessor", function () { }); }); - test("returns error when the RelationshipAttribute exists in the context of the Relationship with the peer", async function () { + test("returns error when the initial RelationshipAttribute already exists in the context of the Relationship with the peer", async function () { const sender = testAccount.identity.address; const recipient = CoreAddress.from("Recipient"); @@ -541,6 +745,149 @@ describe("ShareAttributeRequestItemProcessor", function () { message: "The provided RelationshipAttribute already exists in the context of the Relationship with the peer." }); }); + + test("returns error when a ThirdPartyRelationshipAttribute already exists in the context of the Relationship with the peer", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + const aThirdParty = CoreAddress.from("AThirdParty"); + + const initialRelationshipAttribute = await consumptionController.attributes.createAttributeUnsafe({ + content: RelationshipAttribute.from({ + owner: sender, + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + confidentiality: RelationshipAttributeConfidentiality.Public, + key: "AKey" + }), + shareInfo: { + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() + } + }); + + await consumptionController.attributes.createAttributeUnsafe({ + content: RelationshipAttribute.from({ + owner: sender, + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + confidentiality: RelationshipAttributeConfidentiality.Public, + key: "AKey" + }), + shareInfo: { + peer: recipient, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: initialRelationshipAttribute.id + } + }); + + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: false, + attribute: initialRelationshipAttribute.content, + sourceAttributeId: initialRelationshipAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.invalidRequestItem", + message: "The provided RelationshipAttribute already exists in the context of the Relationship with the peer." + }); + }); + + test("returns success when a ThirdPartyRelationshipAttribute already exists in the context of the Relationship with the peer but is ToBeDeletedByPeer", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + const aThirdParty = CoreAddress.from("AThirdParty"); + + const initialRelationshipAttribute = await consumptionController.attributes.createAttributeUnsafe({ + content: RelationshipAttribute.from({ + owner: sender, + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + confidentiality: RelationshipAttributeConfidentiality.Public, + key: "AKey" + }), + shareInfo: { + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() + } + }); + + await consumptionController.attributes.createAttributeUnsafe({ + content: RelationshipAttribute.from({ + owner: sender, + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + confidentiality: RelationshipAttributeConfidentiality.Public, + key: "AKey" + }), + shareInfo: { + peer: recipient, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: initialRelationshipAttribute.id + }, + deletionInfo: { + deletionStatus: DeletionStatus.ToBeDeletedByPeer, + deletionDate: CoreDate.utc().add({ day: 1 }) + } + }); + + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: false, + attribute: initialRelationshipAttribute.content, + sourceAttributeId: initialRelationshipAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).successfulValidationResult(); + }); + + test("returns success when a ThirdPartyRelationshipAttribute already exists in the context of the Relationship with the peer but is DeletedByPeer", async function () { + const sender = testAccount.identity.address; + const recipient = CoreAddress.from("Recipient"); + const aThirdParty = CoreAddress.from("AThirdParty"); + + const initialRelationshipAttribute = await consumptionController.attributes.createAttributeUnsafe({ + content: RelationshipAttribute.from({ + owner: sender, + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + confidentiality: RelationshipAttributeConfidentiality.Public, + key: "AKey" + }), + shareInfo: { + peer: aThirdParty, + requestReference: await ConsumptionIds.request.generate() + } + }); + + await consumptionController.attributes.createAttributeUnsafe({ + content: RelationshipAttribute.from({ + owner: sender, + value: ProprietaryString.fromAny({ value: "AGivenName", title: "ATitle" }), + confidentiality: RelationshipAttributeConfidentiality.Public, + key: "AKey" + }), + shareInfo: { + peer: recipient, + requestReference: await ConsumptionIds.request.generate(), + sourceAttribute: initialRelationshipAttribute.id + }, + deletionInfo: { + deletionStatus: DeletionStatus.DeletedByPeer, + deletionDate: CoreDate.utc().subtract({ day: 1 }) + } + }); + + const requestItem = ShareAttributeRequestItem.from({ + mustBeAccepted: false, + attribute: initialRelationshipAttribute.content, + sourceAttributeId: initialRelationshipAttribute.id + }); + const request = Request.from({ items: [requestItem] }); + + const result = await processor.canCreateOutgoingRequestItem(requestItem, request, recipient); + + expect(result).successfulValidationResult(); + }); }); describe("accept", function () { diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index eb11c6a00..62c8c4d18 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -840,7 +840,7 @@ describe(ShareRepositoryAttributeUseCase.name, () => { }); expect(repeatedShareRequestResult).toBeAnError( - `The IdentityAttribute with the given sourceAttributeId '${sRepositoryAttribute.id}' has already been shared with the peer.`, + `The IdentityAttribute with the given sourceAttributeId '${sRepositoryAttribute.id}' is already shared with the peer.`, "error.consumption.requests.invalidRequestItem" ); }); @@ -872,7 +872,7 @@ describe(ShareRepositoryAttributeUseCase.name, () => { peer: services2.address }); expect(response).toBeAnError( - `You have already shared the predecessor '${predecesssorOwnSharedIdentityAttribute.shareInfo!.sourceAttribute}' of the IdentityAttribute. Instead of sharing it, you should notify the peer about the Attribute succession.`, + `The predecessor '${predecesssorOwnSharedIdentityAttribute.shareInfo!.sourceAttribute}' of the IdentityAttribute is already shared with the peer. Instead of sharing it, you should notify the peer about the Attribute succession.`, "error.consumption.requests.invalidRequestItem" ); }); From bcd12587faadb74eddf7a490714060ef49f15f5d Mon Sep 17 00:00:00 2001 From: Milena Czierlinski <146972016+Milena-Czierlinski@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:30:55 +0200 Subject: [PATCH 30/40] Feature/Query returns defaultAttributes first (#214) * feat: return default attributes first for query * chore: version bump * test: isDefault value * refactor: dont use number constructor for sorting --- package-lock.json | 18 +++--- packages/app-runtime/package.json | 4 +- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- packages/runtime/package.json | 8 +-- .../runtime/src/dataViews/DataViewExpander.ts | 9 ++- .../IdentityAttributeQueryExpanded.test.ts | 62 +++++++++++-------- packages/transport/package.json | 2 +- 8 files changed, 60 insertions(+), 47 deletions(-) diff --git a/package-lock.json b/package-lock.json index dcb51d850..6ff1ea70c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12548,7 +12548,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.13", + "version": "5.0.0-alpha.14", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12562,12 +12562,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.13" + "@nmshd/runtime": "^5.0.0-alpha.14" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.13", + "version": "5.0.0-alpha.14", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12589,7 +12589,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.13", + "version": "5.0.0-alpha.14", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12614,17 +12614,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.13", + "version": "5.0.0-alpha.14", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.13", - "@nmshd/content": "5.0.0-alpha.13", + "@nmshd/consumption": "5.0.0-alpha.14", + "@nmshd/content": "5.0.0-alpha.14", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.13", + "@nmshd/transport": "5.0.0-alpha.14", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12656,7 +12656,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.13", + "version": "5.0.0-alpha.14", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index 7ab24278a..30b4402b5 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.13", + "version": "5.0.0-alpha.14", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.13" + "@nmshd/runtime": "^5.0.0-alpha.14" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 18f949315..0923e8ff9 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.13", + "version": "5.0.0-alpha.14", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index b8966713b..a5db12d81 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.13", + "version": "5.0.0-alpha.14", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 983eba472..d7cca9a9f 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.13", + "version": "5.0.0-alpha.14", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.13", - "@nmshd/content": "5.0.0-alpha.13", + "@nmshd/consumption": "5.0.0-alpha.14", + "@nmshd/content": "5.0.0-alpha.14", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.13", + "@nmshd/transport": "5.0.0-alpha.14", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/dataViews/DataViewExpander.ts b/packages/runtime/src/dataViews/DataViewExpander.ts index 7842e836f..4e1c63aec 100644 --- a/packages/runtime/src/dataViews/DataViewExpander.ts +++ b/packages/runtime/src/dataViews/DataViewExpander.ts @@ -1400,12 +1400,17 @@ export class DataViewExpander { const matchedAttributeDTOs = await this.consumption.attributes.executeIdentityAttributeQuery({ query }); - const matchedAttributeDVOs = await this.expandLocalAttributeDTOs(matchedAttributeDTOs.value); + if (matchedAttributeDTOs.isError) throw matchedAttributeDTOs.error; + + const matchedAttributeDTOsSortedByDefaultFirst = matchedAttributeDTOs.value.sort((attribute1, attribute2) => + attribute1.isDefault === attribute2.isDefault ? 0 : attribute1.isDefault ? -1 : 1 + ); + const matchedAttributeDVOs = await this.expandLocalAttributeDTOs(matchedAttributeDTOsSortedByDefaultFirst); return { ...this.expandIdentityAttributeQuery(query), type: "ProcessedIdentityAttributeQueryDVO", - results: matchedAttributeDVOs as (RepositoryAttributeDVO | SharedToPeerAttributeDVO)[], + results: matchedAttributeDVOs as RepositoryAttributeDVO[], isProcessed: true }; } diff --git a/packages/runtime/test/dataViews/IdentityAttributeQueryExpanded.test.ts b/packages/runtime/test/dataViews/IdentityAttributeQueryExpanded.test.ts index d11e5c7db..9bb5dfb5e 100644 --- a/packages/runtime/test/dataViews/IdentityAttributeQueryExpanded.test.ts +++ b/packages/runtime/test/dataViews/IdentityAttributeQueryExpanded.test.ts @@ -18,33 +18,37 @@ describe("IdentityAttributeQueryExpanded", () => { const attributes: LocalAttributeDTO[] = []; beforeAll(async () => { - attributes.push( - ( - await consumptionServices1.attributes.createRepositoryAttribute({ - content: { - value: { - "@type": "GivenName", - value: "Hugo" - } - } - }) - ).value - ); - attributes.push( - ( - await consumptionServices1.attributes.createRepositoryAttribute({ - content: { - value: { - "@type": "GivenName", - value: "Egon" - } - } - }) - ).value - ); + const firstlyCreatedGivenName = ( + await consumptionServices1.attributes.createRepositoryAttribute({ + content: { + value: { + "@type": "GivenName", + value: "A first given name" + }, + tags: ["notDefault"] + } + }) + ).value; + + const secondlyCreatedGivenName = ( + await consumptionServices1.attributes.createRepositoryAttribute({ + content: { + value: { + "@type": "GivenName", + value: "A second given name" + }, + tags: ["default"] + } + }) + ).value; + + const updatedSecondlyCreatedGivenName = (await consumptionServices1.attributes.changeDefaultRepositoryAttribute({ attributeId: secondlyCreatedGivenName.id })).value; + const updatedFirstlyCreatedGivenName = (await consumptionServices1.attributes.getAttribute({ id: firstlyCreatedGivenName.id })).value; + + attributes.push(updatedSecondlyCreatedGivenName, updatedFirstlyCreatedGivenName); }); - test("check the GivenName", async () => { + test("check the order and content of the expanded LocalAttributes that match the query", async () => { const query: IdentityAttributeQueryJSON = { "@type": "IdentityAttributeQuery", valueType: "GivenName" @@ -75,7 +79,9 @@ describe("IdentityAttributeQueryExpanded", () => { expect(dvo.content).toStrictEqual(attribute.content); const givenName = dvo.value as AbstractStringJSON; expect(givenName["@type"]).toBe("GivenName"); - expect(givenName.value).toBe("Hugo"); + expect(givenName.value).toBe("A second given name"); + expect(dvo.tags).toStrictEqual(["default"]); + expect(dvo.isDefault).toBe(true); expect(dvo.createdAt).toStrictEqual(attribute.createdAt); expect(dvo.isOwn).toBe(true); expect(dvo.isValid).toBe(true); @@ -98,7 +104,9 @@ describe("IdentityAttributeQueryExpanded", () => { expect(dvo.content).toStrictEqual(attribute.content); const value = dvo.value as AbstractStringJSON; expect(value["@type"]).toBe("GivenName"); - expect(value.value).toBe("Egon"); + expect(value.value).toBe("A first given name"); + expect(dvo.tags).toStrictEqual(["notDefault"]); + expect(dvo.isDefault).toBeUndefined(); expect(dvo.createdAt).toStrictEqual(attribute.createdAt); expect(dvo.isOwn).toBe(true); expect(dvo.isValid).toBe(true); diff --git a/packages/transport/package.json b/packages/transport/package.json index 68bfb6fc4..19030710b 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.13", + "version": "5.0.0-alpha.14", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { From 11dbcf870b0a5749d12dbf8e169fb91c6bd38dcd Mon Sep 17 00:00:00 2001 From: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:32:07 +0200 Subject: [PATCH 31/40] chore: upgrade backbone (#217) --- .dev/compose.backbone.env | 2 +- .dev/compose.backbone.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.dev/compose.backbone.env b/.dev/compose.backbone.env index aa09837c7..358d4582c 100644 --- a/.dev/compose.backbone.env +++ b/.dev/compose.backbone.env @@ -1 +1 @@ -BACKBONE_VERSION=6.2.0 +BACKBONE_VERSION=6.4.0 diff --git a/.dev/compose.backbone.yml b/.dev/compose.backbone.yml index 26e3b8d3a..8428deec3 100644 --- a/.dev/compose.backbone.yml +++ b/.dev/compose.backbone.yml @@ -108,7 +108,7 @@ services: image: postgres environment: - PGPASSWORD=Passw0rd - command: /bin/bash -c 'env && apt update -y && apt install -y wget && wget https://raw.githubusercontent.com/nmshd/backbone/${BACKBONE_VERSION}/setup-db/setup-postgres.sql -O /setup-postgres.sql && psql -h postgres -U postgres -d enmeshed -f /setup-postgres.sql' + command: /bin/bash -c 'env && apt update -y && apt install -y wget && wget https://raw.githubusercontent.com/nmshd/backbone/${BACKBONE_VERSION}/scripts/sql/postgres/setup.sql -O /setup-postgres.sql && psql -h postgres -U postgres -d enmeshed -f /setup-postgres.sql' depends_on: database: condition: service_healthy From 85065c46629ee5769206f3bc36a1e48f705b1270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Tue, 6 Aug 2024 13:54:41 +0200 Subject: [PATCH 32/40] Fix/display unknown identities as unknown instead of a random string (#221) * fix: display unknown identities as unknown instead of a random string * fix: update tests * chore: version bump * chore: remove another comment --- package-lock.json | 18 ++++++++--------- packages/app-runtime/package.json | 4 ++-- .../test/modules/MessageEventing.test.ts | 2 +- .../RelationshipEventingAccept.test.ts | 4 ++-- .../RelationshipEventingReject.test.ts | 4 ++-- .../RelationshipEventingRevoke.test.ts | 4 ++-- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- packages/runtime/package.json | 8 ++++---- .../runtime/src/dataViews/DataViewExpander.ts | 20 +++++++------------ .../runtime/test/dataViews/MessageDVO.test.ts | 10 +++++----- .../test/dataViews/RelationshipDVO.test.ts | 4 ++-- ...ateIdentityAttributeRequestItemDVO.test.ts | 2 +- ...elationshipAttributeRequestItemDVO.test.ts | 2 +- .../DeleteAttributeRequestItemDVO.test.ts | 2 +- .../ShareAttributeRequestItemDVO.test.ts | 2 +- packages/transport/package.json | 2 +- 17 files changed, 43 insertions(+), 49 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ff1ea70c..69da84f96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12548,7 +12548,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.14", + "version": "5.0.0-alpha.15", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -12562,12 +12562,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.14" + "@nmshd/runtime": "^5.0.0-alpha.15" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.14", + "version": "5.0.0-alpha.15", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -12589,7 +12589,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.14", + "version": "5.0.0-alpha.15", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -12614,17 +12614,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.14", + "version": "5.0.0-alpha.15", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.14", - "@nmshd/content": "5.0.0-alpha.14", + "@nmshd/consumption": "5.0.0-alpha.15", + "@nmshd/content": "5.0.0-alpha.15", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.14", + "@nmshd/transport": "5.0.0-alpha.15", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -12656,7 +12656,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.14", + "version": "5.0.0-alpha.15", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index 30b4402b5..fa894b5e6 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.14", + "version": "5.0.0-alpha.15", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -54,7 +54,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.14" + "@nmshd/runtime": "^5.0.0-alpha.15" }, "publishConfig": { "access": "public", diff --git a/packages/app-runtime/test/modules/MessageEventing.test.ts b/packages/app-runtime/test/modules/MessageEventing.test.ts index 09dbb4552..0b181c9d7 100644 --- a/packages/app-runtime/test/modules/MessageEventing.test.ts +++ b/packages/app-runtime/test/modules/MessageEventing.test.ts @@ -54,6 +54,6 @@ describe("MessageEventingTest", function () { expect(mailReceivedEvent.data.date).toBe(message.createdAt); expect(mailReceivedEvent.data.type).toBe("MailDVO"); expect(mailReceivedEvent.data.createdBy.type).toBe("IdentityDVO"); - expect(mailReceivedEvent.data.createdBy.name).toBe(createdBy.substring(3, 9)); + expect(mailReceivedEvent.data.createdBy.name).toBe("i18n://dvo.identity.unknown"); }); }); diff --git a/packages/app-runtime/test/modules/RelationshipEventingAccept.test.ts b/packages/app-runtime/test/modules/RelationshipEventingAccept.test.ts index 2435794e0..cec74ef2d 100644 --- a/packages/app-runtime/test/modules/RelationshipEventingAccept.test.ts +++ b/packages/app-runtime/test/modules/RelationshipEventingAccept.test.ts @@ -45,7 +45,7 @@ describe("RelationshipEventingAcceptTest", function () { expect(onboardingChangeReceivedEvent.data.auditLogEntry.newStatus).toBe(RelationshipStatus.Pending); expect(onboardingChangeReceivedEvent.data.identity).toBeDefined(); expect(onboardingChangeReceivedEvent.data.identity.id).toBe(sessionB.address.toString()); - expect(onboardingChangeReceivedEvent.data.identity.name).toBe(sessionB.address.toString().substring(3, 9)); + expect(onboardingChangeReceivedEvent.data.identity.name).toBe("i18n://dvo.identity.unknown"); expect(onboardingChangeReceivedEvent.data.identity.hasRelationship).toBe(true); expect(onboardingChangeReceivedEvent.data.identity.relationship?.id).toBe(relationshipChangedEvent.data.id); expect(onboardingChangeReceivedEvent.data.relationship).toBe(relationshipChangedEvent.data); @@ -86,7 +86,7 @@ describe("RelationshipEventingAcceptTest", function () { expect(onboardingChangeReceivedEvent.data).toBeDefined(); expect(onboardingChangeReceivedEvent.data.auditLogEntry.newStatus).toBe(RelationshipStatus.Active); expect(onboardingChangeReceivedEvent.data.identity).toBeDefined(); - expect(onboardingChangeReceivedEvent.data.identity.name).toBe(sessionA.accountController.identity.address.toString().substring(3, 9)); + expect(onboardingChangeReceivedEvent.data.identity.name).toBe("i18n://dvo.identity.unknown"); expect(onboardingChangeReceivedEvent.data.identity.id).toBe(sessionA.accountController.identity.address.toString()); expect(onboardingChangeReceivedEvent.data.identity.hasRelationship).toBe(true); expect(onboardingChangeReceivedEvent.data.identity.relationship?.id).toBe(relationshipChangedEvent.data.id); diff --git a/packages/app-runtime/test/modules/RelationshipEventingReject.test.ts b/packages/app-runtime/test/modules/RelationshipEventingReject.test.ts index d3b7ecc22..6f39cfe42 100644 --- a/packages/app-runtime/test/modules/RelationshipEventingReject.test.ts +++ b/packages/app-runtime/test/modules/RelationshipEventingReject.test.ts @@ -44,7 +44,7 @@ describe("RelationshipEventingRejectTest", function () { expect(onboardingChangeReceivedEvent.data).toBeDefined(); expect(onboardingChangeReceivedEvent.data.auditLogEntry.newStatus).toBe(RelationshipStatus.Pending); expect(onboardingChangeReceivedEvent.data.identity).toBeDefined(); - expect(onboardingChangeReceivedEvent.data.identity.name).toBe(sessionB.accountController.identity.address.toString().substring(3, 9)); + expect(onboardingChangeReceivedEvent.data.identity.name).toBe("i18n://dvo.identity.unknown"); expect(onboardingChangeReceivedEvent.data.identity.id).toBe(sessionB.accountController.identity.address.toString()); expect(onboardingChangeReceivedEvent.data.relationship).toBe(relationshipChangedEvent.data); @@ -86,7 +86,7 @@ describe("RelationshipEventingRejectTest", function () { expect(onboardingChangeReceivedEvent.data.auditLogEntry.newStatus).toBe(RelationshipStatus.Rejected); expect(onboardingChangeReceivedEvent.data.identity).toBeDefined(); - expect(onboardingChangeReceivedEvent.data.identity.name).toBe(sessionA.accountController.identity.address.toString().substring(3, 9)); + expect(onboardingChangeReceivedEvent.data.identity.name).toBe("i18n://dvo.identity.unknown"); expect(onboardingChangeReceivedEvent.data.identity.id).toBe(sessionA.accountController.identity.address.toString()); expect(onboardingChangeReceivedEvent.data.identity.hasRelationship).toBe(true); expect(onboardingChangeReceivedEvent.data.identity.relationship?.id).toBe(relationshipChangedEvent.data.id); diff --git a/packages/app-runtime/test/modules/RelationshipEventingRevoke.test.ts b/packages/app-runtime/test/modules/RelationshipEventingRevoke.test.ts index 92801ecb1..b2f6d3bfc 100644 --- a/packages/app-runtime/test/modules/RelationshipEventingRevoke.test.ts +++ b/packages/app-runtime/test/modules/RelationshipEventingRevoke.test.ts @@ -46,7 +46,7 @@ describe("RelationshipEventingRevokeTest", function () { expect(onboardingChangeReceivedEvent.data.auditLogEntry.newStatus).toBe(RelationshipStatus.Pending); expect(onboardingChangeReceivedEvent.data.identity).toBeDefined(); - expect(onboardingChangeReceivedEvent.data.identity.name).toBe(sessionB.accountController.identity.address.toString().substring(3, 9)); + expect(onboardingChangeReceivedEvent.data.identity.name).toBe("i18n://dvo.identity.unknown"); expect(onboardingChangeReceivedEvent.data.identity.id).toBe(sessionB.accountController.identity.address.toString()); expect(onboardingChangeReceivedEvent.data.identity.hasRelationship).toBe(true); @@ -88,7 +88,7 @@ describe("RelationshipEventingRevokeTest", function () { expect(onboardingChangeReceivedEvent.data.auditLogEntry.newStatus).toBe(RelationshipStatus.Revoked); expect(onboardingChangeReceivedEvent.data.identity).toBeDefined(); - expect(onboardingChangeReceivedEvent.data.identity.name).toBe(sessionB.accountController.identity.address.toString().substring(3, 9)); + expect(onboardingChangeReceivedEvent.data.identity.name).toBe("i18n://dvo.identity.unknown"); expect(onboardingChangeReceivedEvent.data.identity.id).toBe(sessionB.accountController.identity.address.toString()); expect(onboardingChangeReceivedEvent.data.identity.hasRelationship).toBe(true); expect(onboardingChangeReceivedEvent.data.identity.relationship?.id).toBe(relationshipChangedEvent.data.id); diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 0923e8ff9..699c2edd1 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.14", + "version": "5.0.0-alpha.15", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index a5db12d81..21471d49a 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.14", + "version": "5.0.0-alpha.15", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index d7cca9a9f..89992b954 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.14", + "version": "5.0.0-alpha.15", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -56,10 +56,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.14", - "@nmshd/content": "5.0.0-alpha.14", + "@nmshd/consumption": "5.0.0-alpha.15", + "@nmshd/content": "5.0.0-alpha.15", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.14", + "@nmshd/transport": "5.0.0-alpha.15", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/dataViews/DataViewExpander.ts b/packages/runtime/src/dataViews/DataViewExpander.ts index 4e1c63aec..b44fe62fe 100644 --- a/packages/runtime/src/dataViews/DataViewExpander.ts +++ b/packages/runtime/src/dataViews/DataViewExpander.ts @@ -1577,14 +1577,11 @@ export class DataViewExpander { } public expandUnknown(address: string): IdentityDVO { - const name = address.substring(3, 9); - const initials = (name.match(/\b\w/g) ?? []).join(""); - return { id: address, type: "IdentityDVO", - name: name, - initials: initials, + name: "i18n://dvo.identity.unknown", + initials: "", description: "i18n://dvo.identity.unknown.description", isSelf: false, hasRelationship: false @@ -1639,7 +1636,7 @@ export class DataViewExpander { private expandAddressFromRequest(request: LocalRequestDTO): IdentityDVO { const sharedAttributesOnNewRelationship = this.getSharedAttributesFromRequest(request); const address = request.peer; - const name = this.getNameFromAttributeContents(sharedAttributesOnNewRelationship) ?? address.substring(3, 9); + const name = this.getNameFromAttributeContents(sharedAttributesOnNewRelationship) ?? "i18n://dvo.identity.unknown"; const initials = (name.match(/\b\w/g) ?? []).join(""); return { @@ -1775,7 +1772,7 @@ export class DataViewExpander { } else if (stringByType["Surname"]) { name = `${stringByType["Surname"]}`; } else { - name = relationship.peer.substring(3, 9); + name = "i18n://dvo.identity.unknown"; } return { @@ -1831,16 +1828,13 @@ export class DataViewExpander { if (relationshipResult.error.code !== RuntimeErrors.general.recordNotFound(Relationship).code) throw relationshipResult.error; - const name = address.substring(3, 9); - const initials = (name.match(/\b\w/g) ?? []).join(""); - return { id: address, type: "IdentityDVO", - name: name, - initials: initials, + name: "i18n://dvo.identity.unknown", + initials: "", publicKey: "i18n://dvo.identity.publicKey.unknown", - description: "i18n://dvo.identity.unknown", + description: "i18n://dvo.identity.unknown.description", isSelf: false, hasRelationship: false }; diff --git a/packages/runtime/test/dataViews/MessageDVO.test.ts b/packages/runtime/test/dataViews/MessageDVO.test.ts index 69d7d6b3b..cf35ef710 100644 --- a/packages/runtime/test/dataViews/MessageDVO.test.ts +++ b/packages/runtime/test/dataViews/MessageDVO.test.ts @@ -107,7 +107,7 @@ describe("MessageDVO", () => { const recipient = dvo.recipients[0]; expect(recipient.type).toBe("RecipientDVO"); expect(recipient.id).toStrictEqual(dto.recipients[0].address); - expect(recipient.name).toBe(recipient.id.substring(3, 9)); // "Barbara" + expect(recipient.name).toBe("i18n://dvo.identity.unknown"); expect(recipient.isSelf).toBe(false); expect(dvo.status).toBe("Delivering"); }); @@ -129,7 +129,7 @@ describe("MessageDVO", () => { expect(dvo.isOwn).toBe(false); expect(dvo.createdBy.type).toBe("IdentityDVO"); expect(dvo.createdBy.id).toStrictEqual(dto.createdBy); - expect(dvo.createdBy.name).toBe(dvo.createdBy.id.substring(3, 9)); // "Jürgen" + expect(dvo.createdBy.name).toBe("i18n://dvo.identity.unknown"); expect(dvo.createdBy.isSelf).toBe(false); const recipient = dvo.recipients[0]; expect(recipient.type).toBe("RecipientDVO"); @@ -164,14 +164,14 @@ describe("MessageDVO", () => { const recipient = dvo.recipients[0]; expect(recipient.type).toBe("RecipientDVO"); expect(recipient.id).toStrictEqual(dto.recipients[0].address); - expect(recipient.name).toBe(recipient.id.substring(3, 9)); // "Barbara" + expect(recipient.name).toBe("i18n://dvo.identity.unknown"); expect(recipient.isSelf).toBe(false); expect(dvo.to).toHaveLength(1); const to = dvo.to[0]; expect(to.type).toBe("RecipientDVO"); expect(to.id).toStrictEqual(mail.to[0]); - expect(to.name).toBe(to.id.substring(3, 9)); // "Barbara" + expect(to.name).toBe("i18n://dvo.identity.unknown"); expect(to.isSelf).toBe(false); expect(dvo.toCount).toStrictEqual(mail.to.length); expect(dvo.ccCount).toStrictEqual(mail.cc!.length); @@ -198,7 +198,7 @@ describe("MessageDVO", () => { expect(dvo.isOwn).toBe(false); expect(dvo.createdBy.type).toBe("IdentityDVO"); expect(dvo.createdBy.id).toStrictEqual(dto.createdBy); - expect(dvo.createdBy.name).toBe(dvo.createdBy.id.substring(3, 9)); // "Jürgen" + expect(dvo.createdBy.name).toBe("i18n://dvo.identity.unknown"); expect(dvo.createdBy.isSelf).toBe(false); const recipient = dvo.recipients[0]; expect(recipient.type).toBe("RecipientDVO"); diff --git a/packages/runtime/test/dataViews/RelationshipDVO.test.ts b/packages/runtime/test/dataViews/RelationshipDVO.test.ts index 1c00e4095..2cdd09e03 100644 --- a/packages/runtime/test/dataViews/RelationshipDVO.test.ts +++ b/packages/runtime/test/dataViews/RelationshipDVO.test.ts @@ -62,7 +62,7 @@ describe("RelationshipDVO", () => { const dvo = dvos[0]; expect(dvo).toBeDefined(); expect(dvo.id).toBe(dto.peer); - expect(dvo.name).toBe(dto.peer.substring(3, 9)); + expect(dvo.name).toBe("i18n://dvo.identity.unknown"); expect(dvo.description).toBe("i18n://dvo.relationship.Active"); expect(dvo.type).toBe("IdentityDVO"); @@ -82,7 +82,7 @@ describe("RelationshipDVO", () => { const dvo = dvos[0]; expect(dvo).toBeDefined(); expect(dvo.id).toBe(dto.peer); - expect(dvo.name).toBe(dto.peer.substring(3, 9)); + expect(dvo.name).toBe("i18n://dvo.identity.unknown"); expect(dvo.description).toBe("i18n://dvo.relationship.Active"); expect(dvo.type).toBe("IdentityDVO"); diff --git a/packages/runtime/test/dataViews/requestItems/CreateIdentityAttributeRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/CreateIdentityAttributeRequestItemDVO.test.ts index 08646b1be..91bbb54b0 100644 --- a/packages/runtime/test/dataViews/requestItems/CreateIdentityAttributeRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/CreateIdentityAttributeRequestItemDVO.test.ts @@ -216,7 +216,7 @@ describe("CreateIdentityAttributeRequestItemDVO", () => { test("check the sender's dvo for the recipient", async () => { const senderMessage = await exchangeAndAcceptRequestByMessage(sRuntimeServices, rRuntimeServices, requestContent, responseItems); const dvo = await rExpander.expandAddress(senderMessage.createdBy); - expect(dvo.name).toStrictEqual(senderMessage.createdBy.substring(3, 9)); + expect(dvo.name).toBe("i18n://dvo.identity.unknown"); expect(dvo.items).toHaveLength(0); }); diff --git a/packages/runtime/test/dataViews/requestItems/CreateRelationshipAttributeRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/CreateRelationshipAttributeRequestItemDVO.test.ts index 49557c6f5..af485eabf 100644 --- a/packages/runtime/test/dataViews/requestItems/CreateRelationshipAttributeRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/CreateRelationshipAttributeRequestItemDVO.test.ts @@ -331,7 +331,7 @@ describe("CreateRelationshipAttributeRequestItemDVO", () => { const senderMessage = await exchangeAndAcceptRequestByMessage(sRuntimeServices, rRuntimeServices, requestContent, responseItems); const dvo = await sExpander.expandAddress(senderMessage.recipients[0].address); - expect(dvo.name).toStrictEqual(senderMessage.recipients[0].address.substring(3, 9)); + expect(dvo.name).toBe("i18n://dvo.identity.unknown"); expect(dvo.items).toHaveLength(0); }); }); diff --git a/packages/runtime/test/dataViews/requestItems/DeleteAttributeRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/DeleteAttributeRequestItemDVO.test.ts index fe4edbe0d..2fe1023d9 100644 --- a/packages/runtime/test/dataViews/requestItems/DeleteAttributeRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/DeleteAttributeRequestItemDVO.test.ts @@ -252,7 +252,7 @@ describe("DeleteAttributeRequestItemDVO", () => { const senderMessage = await exchangeAndAcceptRequestByMessage(sRuntimeServices, rRuntimeServices, requestContent, responseItems); const dvo = await sExpander.expandAddress(senderMessage.recipients[0].address); - expect(dvo.name).toStrictEqual(senderMessage.recipients[0].address.substring(3, 9)); + expect(dvo.name).toBe("i18n://dvo.identity.unknown"); expect(dvo.items).toHaveLength(0); }); }); diff --git a/packages/runtime/test/dataViews/requestItems/ShareAttributeRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/ShareAttributeRequestItemDVO.test.ts index ee021eef9..8bf79897a 100644 --- a/packages/runtime/test/dataViews/requestItems/ShareAttributeRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/ShareAttributeRequestItemDVO.test.ts @@ -320,7 +320,7 @@ describe("ShareAttributeRequestItemDVO", () => { const senderMessage = await exchangeAndAcceptRequestByMessage(sRuntimeServices, rRuntimeServices, requestContent, responseItems); const dvo = await sExpander.expandAddress(senderMessage.recipients[0].address); - expect(dvo.name).toStrictEqual(senderMessage.recipients[0].address.substring(3, 9)); + expect(dvo.name).toBe("i18n://dvo.identity.unknown"); expect(dvo.items).toHaveLength(0); }); }); diff --git a/packages/transport/package.json b/packages/transport/package.json index 19030710b..b4337565c 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.14", + "version": "5.0.0-alpha.15", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { From 3696198ba3132501a49eb0c6eeb21ad2e5f142d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:23:47 +0200 Subject: [PATCH 33/40] Refactor/remove duplicated expander code (#222) * refactor: remove duplicated expander code * chore: correct initials --- .../runtime/src/dataViews/DataViewExpander.ts | 40 ++++--------------- 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/packages/runtime/src/dataViews/DataViewExpander.ts b/packages/runtime/src/dataViews/DataViewExpander.ts index b44fe62fe..0ed224b51 100644 --- a/packages/runtime/src/dataViews/DataViewExpander.ts +++ b/packages/runtime/src/dataViews/DataViewExpander.ts @@ -51,7 +51,7 @@ import { ValueHintsJSON, isRequestItemDerivation } from "@nmshd/content"; -import { CoreAddress, CoreId, IdentityController, Relationship } from "@nmshd/transport"; +import { CoreAddress, CoreId, IdentityController } from "@nmshd/transport"; import _ from "lodash"; import { Inject } from "typescript-ioc"; import { @@ -963,7 +963,7 @@ export class DataViewExpander { public async expandLocalAttributeListenerDTO(attributeListener: LocalAttributeListenerDTO): Promise { const query = (await this.expandAttributeQuery(attributeListener.query)) as IdentityAttributeQueryDVO | ThirdPartyRelationshipAttributeQueryDVO; - const peer = await this.expandIdentityForAddress(attributeListener.peer); + const peer = await this.expandAddress(attributeListener.peer); return { type: "LocalAttributeListenerDVO", name: "dvo.localAttributeListener.name", @@ -1583,6 +1583,7 @@ export class DataViewExpander { name: "i18n://dvo.identity.unknown", initials: "", description: "i18n://dvo.identity.unknown.description", + publicKey: "i18n://dvo.identity.publicKey.unknown", isSelf: false, hasRelationship: false }; @@ -1636,14 +1637,13 @@ export class DataViewExpander { private expandAddressFromRequest(request: LocalRequestDTO): IdentityDVO { const sharedAttributesOnNewRelationship = this.getSharedAttributesFromRequest(request); const address = request.peer; - const name = this.getNameFromAttributeContents(sharedAttributesOnNewRelationship) ?? "i18n://dvo.identity.unknown"; - const initials = (name.match(/\b\w/g) ?? []).join(""); + const name = this.getNameFromAttributeContents(sharedAttributesOnNewRelationship); return { type: "IdentityDVO", id: address, - name: name, - initials, + name: name ?? "i18n://dvo.identity.unknown", + initials: name ? (name.match(/\b\w/g) ?? []).join("") : "", description: "i18n://dvo.identity.unknown.description", isSelf: false, hasRelationship: false @@ -1814,34 +1814,8 @@ export class DataViewExpander { }; } - public async expandIdentityForAddress(address: string): Promise { - if (address === this.identityController.address.toString()) { - return this.expandSelf(); - } - - const relationshipResult = await this.transport.relationships.getRelationshipByAddress({ - address: address - }); - if (relationshipResult.isSuccess) { - return await this.expandRelationshipDTO(relationshipResult.value); - } - - if (relationshipResult.error.code !== RuntimeErrors.general.recordNotFound(Relationship).code) throw relationshipResult.error; - - return { - id: address, - type: "IdentityDVO", - name: "i18n://dvo.identity.unknown", - initials: "", - publicKey: "i18n://dvo.identity.publicKey.unknown", - description: "i18n://dvo.identity.unknown.description", - isSelf: false, - hasRelationship: false - }; - } - public async expandIdentityDTO(identity: IdentityDTO): Promise { - return await this.expandIdentityForAddress(identity.address); + return await this.expandAddress(identity.address); } public async expandRelationshipDTOs(relationships: RelationshipDTO[]): Promise { From 85dd86b81a8e22f1394a659c297fd00e8571597a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Thu, 8 Aug 2024 12:26:25 +0200 Subject: [PATCH 34/40] Chore/use identityPart for being closer to the pseudocode (#224) --- packages/transport/src/modules/accounts/IdentityUtil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/transport/src/modules/accounts/IdentityUtil.ts b/packages/transport/src/modules/accounts/IdentityUtil.ts index a3afc945f..f7b681281 100644 --- a/packages/transport/src/modules/accounts/IdentityUtil.ts +++ b/packages/transport/src/modules/accounts/IdentityUtil.ts @@ -10,7 +10,7 @@ export class IdentityUtil { const hashedPublicKey = new CoreBuffer(hash.buffer.slice(0, 10)); const identityPart = hashedPublicKey.toString(Encoding.Hex); - const checksumSource = CoreBuffer.fromUtf8(`${enmeshedAddressDIDPrefix}${backboneHostname}:dids:${hashedPublicKey.toString(Encoding.Hex)}`); + const checksumSource = CoreBuffer.fromUtf8(`${enmeshedAddressDIDPrefix}${backboneHostname}:dids:${identityPart}`); const checksumHash = await CryptoHash.hash(checksumSource, CryptoHashAlgorithm.SHA256); const checksum = new CoreBuffer(checksumHash.buffer.slice(0, 1)); From 21db57c0b1dc9425f5160b3d46e4e5965ea43227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Thu, 8 Aug 2024 14:33:35 +0200 Subject: [PATCH 35/40] Fix/expose correct regex representations (#225) * chore: use toString instead of String * chore: use toString instead of String * fix: do some regex replacements * chore: version bumps --- package-lock.json | 18 +++++++++--------- packages/app-runtime/package.json | 4 ++-- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- .../types/strings/AbstractEMailAddress.ts | 2 +- .../attributes/types/strings/AbstractURL.ts | 2 +- packages/runtime/package.json | 8 ++++---- packages/transport/package.json | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6961c9e7a..bc46cf8a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10734,7 +10734,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.15", + "version": "5.0.0-alpha.16", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -10748,12 +10748,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.15" + "@nmshd/runtime": "^5.0.0-alpha.16" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.15", + "version": "5.0.0-alpha.16", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -10775,7 +10775,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.15", + "version": "5.0.0-alpha.16", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -10800,17 +10800,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.15", + "version": "5.0.0-alpha.16", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.15", - "@nmshd/content": "5.0.0-alpha.15", + "@nmshd/consumption": "5.0.0-alpha.16", + "@nmshd/content": "5.0.0-alpha.16", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.15", + "@nmshd/transport": "5.0.0-alpha.16", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -10842,7 +10842,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.15", + "version": "5.0.0-alpha.16", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index c4af92f85..7e9533103 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.15", + "version": "5.0.0-alpha.16", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -63,7 +63,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.15" + "@nmshd/runtime": "^5.0.0-alpha.16" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 31fd4f746..b3c095bcf 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.15", + "version": "5.0.0-alpha.16", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index 8df0c2c34..7104d5899 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.15", + "version": "5.0.0-alpha.16", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/src/attributes/types/strings/AbstractEMailAddress.ts b/packages/content/src/attributes/types/strings/AbstractEMailAddress.ts index db1858576..5781e0a87 100644 --- a/packages/content/src/attributes/types/strings/AbstractEMailAddress.ts +++ b/packages/content/src/attributes/types/strings/AbstractEMailAddress.ts @@ -19,7 +19,7 @@ export abstract class AbstractEMailAddress extends AbstractString { return super.valueHints.copyWith({ min: 3, max: 254, - pattern: String(AbstractEMailAddress.regExp) + pattern: AbstractEMailAddress.regExp.toString().slice(1, -1).replaceAll("/", "\\/") }); } diff --git a/packages/content/src/attributes/types/strings/AbstractURL.ts b/packages/content/src/attributes/types/strings/AbstractURL.ts index 5b7619fe7..668309092 100644 --- a/packages/content/src/attributes/types/strings/AbstractURL.ts +++ b/packages/content/src/attributes/types/strings/AbstractURL.ts @@ -19,7 +19,7 @@ export abstract class AbstractURL extends AbstractString { return super.valueHints.copyWith({ min: 3, max: 1024, - pattern: String(AbstractURL.regExp) + pattern: AbstractURL.regExp.toString().slice(1, -1).replaceAll("/", "\\/") }); } diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 27edc673f..bb95669f5 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.15", + "version": "5.0.0-alpha.16", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -65,10 +65,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.15", - "@nmshd/content": "5.0.0-alpha.15", + "@nmshd/consumption": "5.0.0-alpha.16", + "@nmshd/content": "5.0.0-alpha.16", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.15", + "@nmshd/transport": "5.0.0-alpha.16", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/transport/package.json b/packages/transport/package.json index d8a236a8b..416296b62 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.15", + "version": "5.0.0-alpha.16", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { From c0214589d396cbc92236c813a43825bee05cf799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:12:09 +0200 Subject: [PATCH 36/40] Fix/undo escape for urls (#226) * fix: undo escape for URLs * chore: version bump --- package-lock.json | 18 +++++++++--------- packages/app-runtime/package.json | 4 ++-- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- .../attributes/types/strings/AbstractURL.ts | 2 +- packages/runtime/package.json | 8 ++++---- packages/transport/package.json | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index bc46cf8a9..59d59c79f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10734,7 +10734,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.16", + "version": "5.0.0-alpha.17", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -10748,12 +10748,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.16" + "@nmshd/runtime": "^5.0.0-alpha.17" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.16", + "version": "5.0.0-alpha.17", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -10775,7 +10775,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.16", + "version": "5.0.0-alpha.17", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -10800,17 +10800,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.16", + "version": "5.0.0-alpha.17", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.16", - "@nmshd/content": "5.0.0-alpha.16", + "@nmshd/consumption": "5.0.0-alpha.17", + "@nmshd/content": "5.0.0-alpha.17", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.16", + "@nmshd/transport": "5.0.0-alpha.17", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -10842,7 +10842,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.16", + "version": "5.0.0-alpha.17", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index 7e9533103..11d6e1114 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.16", + "version": "5.0.0-alpha.17", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -63,7 +63,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.16" + "@nmshd/runtime": "^5.0.0-alpha.17" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index b3c095bcf..8f9414b6a 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.16", + "version": "5.0.0-alpha.17", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index 7104d5899..45a460b21 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.16", + "version": "5.0.0-alpha.17", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/src/attributes/types/strings/AbstractURL.ts b/packages/content/src/attributes/types/strings/AbstractURL.ts index 668309092..5d37e5ea3 100644 --- a/packages/content/src/attributes/types/strings/AbstractURL.ts +++ b/packages/content/src/attributes/types/strings/AbstractURL.ts @@ -19,7 +19,7 @@ export abstract class AbstractURL extends AbstractString { return super.valueHints.copyWith({ min: 3, max: 1024, - pattern: AbstractURL.regExp.toString().slice(1, -1).replaceAll("/", "\\/") + pattern: AbstractURL.regExp.toString().slice(1, -1) }); } diff --git a/packages/runtime/package.json b/packages/runtime/package.json index bb95669f5..28470174d 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.16", + "version": "5.0.0-alpha.17", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -65,10 +65,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.16", - "@nmshd/content": "5.0.0-alpha.16", + "@nmshd/consumption": "5.0.0-alpha.17", + "@nmshd/content": "5.0.0-alpha.17", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.16", + "@nmshd/transport": "5.0.0-alpha.17", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/transport/package.json b/packages/transport/package.json index 416296b62..2fa837eb4 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.16", + "version": "5.0.0-alpha.17", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { From 2c816b11c5b36ec3956ed0e79fc3d55f7548ba14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:17:22 +0200 Subject: [PATCH 37/40] Fix/ignore rejected and revoked relationships while expanding (#228) * fix: ignore rejected and revoked relationships while expanding * chore: version bump --- package-lock.json | 18 +++++++++--------- packages/app-runtime/package.json | 4 ++-- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- packages/runtime/package.json | 8 ++++---- .../runtime/src/dataViews/DataViewExpander.ts | 6 ++++-- packages/transport/package.json | 2 +- 7 files changed, 22 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 59d59c79f..a26098c15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10734,7 +10734,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.17", + "version": "5.0.0-alpha.18", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -10748,12 +10748,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.17" + "@nmshd/runtime": "^5.0.0-alpha.18" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.17", + "version": "5.0.0-alpha.18", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -10775,7 +10775,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.17", + "version": "5.0.0-alpha.18", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -10800,17 +10800,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.17", + "version": "5.0.0-alpha.18", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.17", - "@nmshd/content": "5.0.0-alpha.17", + "@nmshd/consumption": "5.0.0-alpha.18", + "@nmshd/content": "5.0.0-alpha.18", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.17", + "@nmshd/transport": "5.0.0-alpha.18", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -10842,7 +10842,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.17", + "version": "5.0.0-alpha.18", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index 11d6e1114..2570bf656 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.17", + "version": "5.0.0-alpha.18", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -63,7 +63,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.17" + "@nmshd/runtime": "^5.0.0-alpha.18" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 8f9414b6a..7d8e50771 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.17", + "version": "5.0.0-alpha.18", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index 45a460b21..50e196592 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.17", + "version": "5.0.0-alpha.18", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 28470174d..8515f2adf 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.17", + "version": "5.0.0-alpha.18", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -65,10 +65,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.17", - "@nmshd/content": "5.0.0-alpha.17", + "@nmshd/consumption": "5.0.0-alpha.18", + "@nmshd/content": "5.0.0-alpha.18", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.17", + "@nmshd/transport": "5.0.0-alpha.18", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/dataViews/DataViewExpander.ts b/packages/runtime/src/dataViews/DataViewExpander.ts index 0ed224b51..77fc5cc73 100644 --- a/packages/runtime/src/dataViews/DataViewExpander.ts +++ b/packages/runtime/src/dataViews/DataViewExpander.ts @@ -1595,7 +1595,8 @@ export class DataViewExpander { } const result = await this.transport.relationships.getRelationshipByAddress({ address }); - if (result.isSuccess) { + // revoked relationships should be handled like not existant as the will never have attributes attached + if (result.isSuccess && result.value.status !== RelationshipStatus.Rejected && result.value.status !== RelationshipStatus.Revoked) { return await this.expandRelationshipDTO(result.value); } @@ -1608,7 +1609,8 @@ export class DataViewExpander { }) ).value; if (requestResult.length > 0) { - return this.expandAddressFromRequest(requestResult[0]); // with no relationship max 1 request available + // with no relationship max 1 request available + return this.expandAddressFromRequest(requestResult[0]); } return this.expandUnknown(address); diff --git a/packages/transport/package.json b/packages/transport/package.json index 2fa837eb4..f9e36e64a 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.17", + "version": "5.0.0-alpha.18", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { From 78ad7cd7f0f41236abe0c4dcd7c2cd50331a0c0f Mon Sep 17 00:00:00 2001 From: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:23:56 +0200 Subject: [PATCH 38/40] test/add identityDVO with a revoked relationship test (#229) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: add identityDVO test * fix: typos * chore: wording --------- Co-authored-by: Julian König <33655937+jkoenig134@users.noreply.github.com> --- .../runtime/src/dataViews/DataViewExpander.ts | 2 +- .../test/dataViews/IdentityDVO.test.ts | 28 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/dataViews/DataViewExpander.ts b/packages/runtime/src/dataViews/DataViewExpander.ts index 77fc5cc73..b9b8f3443 100644 --- a/packages/runtime/src/dataViews/DataViewExpander.ts +++ b/packages/runtime/src/dataViews/DataViewExpander.ts @@ -1595,7 +1595,7 @@ export class DataViewExpander { } const result = await this.transport.relationships.getRelationshipByAddress({ address }); - // revoked relationships should be handled like not existant as the will never have attributes attached + // revoked relationships should be treated as non-existent as they will never have attributes attached if (result.isSuccess && result.value.status !== RelationshipStatus.Rejected && result.value.status !== RelationshipStatus.Revoked) { return await this.expandRelationshipDTO(result.value); } diff --git a/packages/runtime/test/dataViews/IdentityDVO.test.ts b/packages/runtime/test/dataViews/IdentityDVO.test.ts index aec1e3a11..f3d410d3c 100644 --- a/packages/runtime/test/dataViews/IdentityDVO.test.ts +++ b/packages/runtime/test/dataViews/IdentityDVO.test.ts @@ -1,6 +1,13 @@ import { LocalRequestStatus } from "@nmshd/consumption"; import { ShareAttributeRequestItemJSON } from "@nmshd/content"; -import { IncomingRequestStatusChangedEvent, LocalRequestDTO, PeerRelationshipTemplateDVO, RelationshipTemplateDTO } from "../../src"; +import { + AttributeDeletedEvent, + IncomingRequestStatusChangedEvent, + LocalRequestDTO, + PeerRelationshipTemplateDVO, + RelationshipChangedEvent, + RelationshipTemplateDTO +} from "../../src"; import { createTemplate, RuntimeServiceProvider, TestRuntimeServices } from "../lib"; const serviceProvider = new RuntimeServiceProvider(); @@ -65,4 +72,23 @@ describe("IdentityDVO after loading a relationship template sharing a DisplayNam expect(dvo.peer.name).toBe("A Display Name"); expect(dvo.peer.initials).toBe("ADN"); }); + + test("IdentityDVO should use the DisplayName if a revoked relationship exists", async () => { + await requestor.consumption.incomingRequests.accept({ requestId: request.id, items: [{ accept: true }] }); + const relationshipId = (await requestor.eventBus.waitForEvent(RelationshipChangedEvent)).data.id; + await requestor.transport.relationships.revokeRelationship({ relationshipId }); + await requestor.eventBus.waitForEvent(AttributeDeletedEvent); + + requestor.eventBus.reset(); + requestorTemplate = (await requestor.transport.relationshipTemplates.loadPeerRelationshipTemplate({ reference: templatorTemplate.truncatedReference })).value; + await requestor.eventBus.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); + + const dvo = await requestor.expander.expandLocalRequestDTO(request); + expect(dvo).toBeDefined(); + expect(dvo.createdBy.name).toBe("A Display Name"); + expect(dvo.createdBy.initials).toBe("ADN"); + + expect(dvo.peer.name).toBe("A Display Name"); + expect(dvo.peer.initials).toBe("ADN"); + }); }); From 5db38da090cae40138e9db5467e3b1fe7a9b217e Mon Sep 17 00:00:00 2001 From: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:50:43 +0200 Subject: [PATCH 39/40] Feature/restrict "any" content types (#170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: restrict message content type * feat: restrict relationship template content type * fix: only JSONs as parameters, linter, template content optional * chore: adapt message schema * feat: restrict relationship creation content type * refactor: optional template content * chore: adapt changes to relationship in schema * feat: add arbitrary types to content * refactor: rename non-arbitrary creation contents * chore: adapt use cases to non-arbitrary content * Revert "chore: adapt changes to relationship in schema" This reverts commit 470a0c38892bab94e3ba0b2c05d2620115d77555. * Revert "chore: adapt message schema" This reverts commit 9f38be6371711c855dbad72b8d1e96b1a582823b. * chore: other adaptations to content renaming * chore: re-add serializing validation to template creation * chore: add ts-serval check to relationship creation * test: re-add arbitrary content message dvo test * test: use new content types in tests * fix: use new content type in test object factory * fix: add "ContainingRequest" * test: use emptyRelationshipContent * fix: use/import new content type in test utils * fix: use/import new content type in test utils * fix: bad types in relationship template dvo test * fix: type errors in tests * chore: adapt dto/dvo * refactor: adapt modules, remove response as message content type * test/fix: add ! to access request ids * test/fix: add !s and typecasts * fix: messagemapper imports * fix: requestModule, add type-safety to testutil * fix: linter checks * refactor: add abstraction content types in content * refactor: use new abstraction type * fix: linter check for unnecessary ! * fix: remove getPseudonym * refactor: use content constructors * fix: use new types in app-runtime tests * Revert "fix: linter check for unnecessary !" This reverts commit cd88720fd86f5c4f5199d20d8229e026685b7efa. * chore: version bump * debugging on * debugging continued * debugging off * fix: any messageContent type * fix: remove debugging changes * fix: relationship content type any, missing imports * refactor: remove ...withresponse / ...withrequest * refactor: MessageContentJSON to MessageContentDTO * refactor: RelationshipTemplate/creationContentDTO * fix: add missing imports * fix: adapt share attribute after deletion validation * test: add validation tests * fix: update app-runtime imports * test: re-add messages test * refactor: cosmetic changes * chore: version bump * chore: version bump * refactor/chore: error messages, if ordering * fix: test error message * chore: update package-lock * refactor: code ordering * fix: merge cleanup * test: re-add test * refactor: code ordering * refactor: use Derivations * refactor: content -> value * chore: adapt app-runtime * refactor: DerivationDTO -> Derivation * chore: remove requestMail * fix: use correct type in test * chore: format * refactor: ContentDTO -> ContentDerivation * fix: compiler error * chore: formatting * chore: review comments * chore: review comments, linter, update fix * fix: mandatory creationContent * fix: adapt testObjectFactory * refactor: import ordering * refactor: requestModule * refactor: some review comments * refactor: unknown -> any * refactor: unknown -> any * feat: improve error messages * test: fix error messages * chore: version bump --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Julian König --- package-lock.json | 18 ++--- packages/app-runtime/package.json | 4 +- packages/app-runtime/test/lib/TestUtil.ts | 32 ++++++--- .../test/modules/MessageEventing.test.ts | 3 +- packages/consumption/package.json | 2 +- .../outgoing/OutgoingRequestsController.ts | 2 +- .../modules/requests/RequestEnd2End.test.ts | 2 +- .../requests/testHelpers/TestObjectFactory.ts | 5 +- packages/content/package.json | 2 +- packages/content/src/index.ts | 2 +- .../src/messages/ArbitraryMessageContent.ts | 26 +++++++ packages/content/src/messages/Mail.ts | 1 + packages/content/src/messages/index.ts | 2 + .../content/src/notifications/Notification.ts | 4 ++ .../ArbitraryRelationshipCreationContent.ts | 26 +++++++ .../ArbitraryRelationshipTemplateContent.ts | 26 +++++++ packages/content/src/relationships/index.ts | 2 + packages/runtime/package.json | 8 +-- .../src/dataViews/transport/MessageDVO.ts | 3 +- .../dataViews/transport/RelationshipDVO.ts | 6 +- .../transport/RelationshipTemplateDVO.ts | 5 +- packages/runtime/src/modules/MessageModule.ts | 4 +- packages/runtime/src/modules/RequestModule.ts | 24 +++---- .../runtime/src/types/transport/MessageDTO.ts | 5 +- .../src/types/transport/RelationshipDTO.ts | 5 +- .../transport/RelationshipTemplateDTO.ts | 6 +- .../notifications/NotificationMapper.ts | 3 +- .../transport/messages/MessageMapper.ts | 24 ++++++- .../transport/messages/SendMessage.ts | 17 ++++- .../CreateOwnRelationshipTemplate.ts | 9 ++- .../RelationshipTemplateMapper.ts | 11 ++- .../relationships/CreateRelationship.ts | 18 +++-- .../relationships/RelationshipMapper.ts | 11 ++- .../runtime/test/consumption/iqlQuery.test.ts | 8 +-- .../runtime/test/consumption/requests.test.ts | 21 +++--- .../test/dataViews/IdentityDVO.test.ts | 4 +- .../runtime/test/dataViews/MessageDVO.test.ts | 33 ++++++--- .../test/dataViews/RelationshipDVO.test.ts | 24 +++++-- .../dataViews/RelationshipTemplateDVO.test.ts | 31 ++++---- ...ComplexReadAttributeRequestItemDVO.test.ts | 12 ++-- ...ateIdentityAttributeRequestItemDVO.test.ts | 6 +- ...elationshipAttributeRequestItemDVO.test.ts | 6 +- .../DeleteAttributeRequestItemDVO.test.ts | 6 +- .../FreeTextRequestItemDVO.test.ts | 6 +- .../ProposeAttributeRequestItemDVO.test.ts | 14 ++-- .../ReadAttributeRequestItemDVO.test.ts | 26 +++---- .../ShareAttributeRequestItemDVO.test.ts | 6 +- packages/runtime/test/lib/testUtils.ts | 72 ++++++++++++------- .../test/lib/testUtilsWithInactiveModules.ts | 18 ++--- .../test/modules/DeciderModule.test.ts | 12 ++-- .../test/modules/RequestModule.test.ts | 37 +++++----- .../runtime/test/transport/account.test.ts | 4 +- .../runtime/test/transport/messages.test.ts | 22 +++--- .../transport/relationshipTemplates.test.ts | 32 ++++++--- .../test/transport/relationships.test.ts | 15 +++- packages/transport/package.json | 2 +- .../transport/src/core/CoreSynchronizable.ts | 1 + .../RelationshipTemplatePublicKey.ts | 1 + .../relationships/local/CachedRelationship.ts | 6 +- packages/transport/src/util/Random.ts | 1 + 60 files changed, 498 insertions(+), 246 deletions(-) create mode 100644 packages/content/src/messages/ArbitraryMessageContent.ts create mode 100644 packages/content/src/messages/index.ts create mode 100644 packages/content/src/relationships/ArbitraryRelationshipCreationContent.ts create mode 100644 packages/content/src/relationships/ArbitraryRelationshipTemplateContent.ts diff --git a/package-lock.json b/package-lock.json index a26098c15..5e7634f50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10734,7 +10734,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.18", + "version": "5.0.0-alpha.19", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -10748,12 +10748,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.18" + "@nmshd/runtime": "^5.0.0-alpha.19" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.18", + "version": "5.0.0-alpha.19", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -10775,7 +10775,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.18", + "version": "5.0.0-alpha.19", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -10800,17 +10800,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.18", + "version": "5.0.0-alpha.19", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.18", - "@nmshd/content": "5.0.0-alpha.18", + "@nmshd/consumption": "5.0.0-alpha.19", + "@nmshd/content": "5.0.0-alpha.19", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.18", + "@nmshd/transport": "5.0.0-alpha.19", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -10842,7 +10842,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.18", + "version": "5.0.0-alpha.19", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index 2570bf656..e35582463 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.18", + "version": "5.0.0-alpha.19", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -63,7 +63,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.18" + "@nmshd/runtime": "^5.0.0-alpha.19" }, "publishConfig": { "access": "public", diff --git a/packages/app-runtime/test/lib/TestUtil.ts b/packages/app-runtime/test/lib/TestUtil.ts index 60e499ee1..c28ba00b0 100644 --- a/packages/app-runtime/test/lib/TestUtil.ts +++ b/packages/app-runtime/test/lib/TestUtil.ts @@ -1,9 +1,18 @@ /* eslint-disable jest/no-standalone-expect */ import { ILoggerFactory } from "@js-soft/logging-abstractions"; import { SimpleLoggerFactory } from "@js-soft/simple-logger"; -import { Serializable } from "@js-soft/ts-serval"; import { Result, sleep, SubscriptionTarget } from "@js-soft/ts-utils"; -import { FileDTO, MessageDTO, RelationshipDTO, RelationshipTemplateDTO, SyncEverythingResponse } from "@nmshd/runtime"; +import { ArbitraryMessageContent, ArbitraryRelationshipCreationContent, ArbitraryRelationshipTemplateContent } from "@nmshd/content"; +import { + FileDTO, + MessageContentDerivation, + MessageDTO, + RelationshipCreationContentDerivation, + RelationshipDTO, + RelationshipTemplateContentDerivation, + RelationshipTemplateDTO, + SyncEverythingResponse +} from "@nmshd/runtime"; import { CoreDate, IConfigOverwrite, TransportLoggerFactory } from "@nmshd/transport"; import { LogLevel } from "typescript-logging"; import { AppConfig, AppRuntime, LocalAccountDTO, LocalAccountSession, createAppConfig as runtime_createAppConfig } from "../../src"; @@ -166,9 +175,7 @@ export class TestUtil { public static async createAndLoadPeerTemplate( from: LocalAccountSession, to: LocalAccountSession, - content: any = { - mycontent: "template" - } + content: RelationshipTemplateContentDerivation = ArbitraryRelationshipTemplateContent.from({ value: {} }).toJSON() ): Promise { const templateFrom = ( await from.transportServices.relationshipTemplates.createOwnRelationshipTemplate({ @@ -195,9 +202,7 @@ export class TestUtil { public static async requestRelationshipForTemplate( from: LocalAccountSession, templateId: string, - content: any = { - mycontent: "request" - } + content: RelationshipCreationContentDerivation = ArbitraryRelationshipCreationContent.from({ value: {} }).toJSON() ): Promise { const relRequest = await from.transportServices.relationships.createRelationship({ templateId, creationContent: content }); return relRequest.value; @@ -285,13 +290,18 @@ export class TestUtil { return syncResult.messages[0]; } - public static async sendMessage(from: LocalAccountSession, to: LocalAccountSession, content?: any): Promise { + public static async sendMessage(from: LocalAccountSession, to: LocalAccountSession, content?: MessageContentDerivation): Promise { return await this.sendMessagesWithAttachments(from, [to], [], content); } - public static async sendMessagesWithAttachments(from: LocalAccountSession, recipients: LocalAccountSession[], attachments: string[], content?: any): Promise { + public static async sendMessagesWithAttachments( + from: LocalAccountSession, + recipients: LocalAccountSession[], + attachments: string[], + content?: MessageContentDerivation + ): Promise { if (!content) { - content = Serializable.fromUnknown({ content: "TestContent" }); + content = ArbitraryMessageContent.from({ value: "TestContent" }).toJSON(); } const result = await from.transportServices.messages.sendMessage({ diff --git a/packages/app-runtime/test/modules/MessageEventing.test.ts b/packages/app-runtime/test/modules/MessageEventing.test.ts index 0b181c9d7..bf8bb8302 100644 --- a/packages/app-runtime/test/modules/MessageEventing.test.ts +++ b/packages/app-runtime/test/modules/MessageEventing.test.ts @@ -1,3 +1,4 @@ +import { MailJSON } from "@nmshd/content"; import { MessageReceivedEvent } from "@nmshd/runtime"; import { AppRuntime, LocalAccountSession, MailReceivedEvent } from "../../src"; import { EventListener, TestUtil } from "../lib"; @@ -26,7 +27,7 @@ describe("MessageEventingTest", function () { test("should fire events when mail is received", async function () { const createdBy = (await sessionA.transportServices.account.getIdentityInfo()).value.address; const recipient = (await sessionB.transportServices.account.getIdentityInfo()).value.address; - const mail = { + const mail: MailJSON = { "@type": "Mail", to: [recipient], subject: "Hallo Horst", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index 7d8e50771..c8a3761dc 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.18", + "version": "5.0.0-alpha.19", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts b/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts index 253e3cb9e..4152f1616 100644 --- a/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts +++ b/packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts @@ -150,7 +150,7 @@ export class OutgoingRequestsController extends ConsumptionBaseController { const templateContent = parsedParams.template.cache!.content; if (!(templateContent instanceof RelationshipTemplateContent)) { - throw new ConsumptionError("The content of the template is not supported as it is not type of RelationshipTemplateContent."); + throw new ConsumptionError("The content of the template is not of type RelationshipTemplateContent hence it's not possible to create a request from it."); } // checking for an active relationship is not secure as in the meantime the relationship could have been accepted diff --git a/packages/consumption/test/modules/requests/RequestEnd2End.test.ts b/packages/consumption/test/modules/requests/RequestEnd2End.test.ts index 9b9cb89ad..1f2b3ca72 100644 --- a/packages/consumption/test/modules/requests/RequestEnd2End.test.ts +++ b/packages/consumption/test/modules/requests/RequestEnd2End.test.ts @@ -109,7 +109,7 @@ describe("End2End Request/Response via Relationship Template", function () { }); test("sender: create Local Request and Response from Relationship Change", async function () { - sCreationContent = sRelationship.cache!.creationContent! as RelationshipCreationContent; + sCreationContent = sRelationship.cache!.creationContent as RelationshipCreationContent; const response = sCreationContent.response; sLocalRequest = await sConsumptionController.outgoingRequests.createAndCompleteFromRelationshipTemplateResponse({ diff --git a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts index d8fe366da..ce8983a68 100644 --- a/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts +++ b/packages/consumption/test/modules/requests/testHelpers/TestObjectFactory.ts @@ -57,6 +57,7 @@ export class TestObjectFactory { cache: properties?.cache ?? CachedRelationship.from({ + creationContent: {}, auditLog: [ { createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), @@ -89,6 +90,7 @@ export class TestObjectFactory { cache: properties?.cache ?? CachedRelationship.from({ + creationContent: {}, auditLog: [ { createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), @@ -130,6 +132,7 @@ export class TestObjectFactory { cache: properties?.cache ?? CachedRelationship.from({ + creationContent: {}, auditLog: [ { createdAt: CoreDate.from("2020-01-01T00:00:00.000Z"), @@ -395,7 +398,7 @@ export class TestObjectFactory { return RelationshipTemplate.from(this.createOutgoingIRelationshipTemplate(creator)); } - public static createOutgoingIRelationshipTemplate(creator: CoreAddress, content?: IRequest | IRelationshipTemplateContent): IRelationshipTemplate { + public static createOutgoingIRelationshipTemplate(creator: CoreAddress, content?: IRelationshipTemplateContent): IRelationshipTemplate { return { // @ts-expect-error "@type": "RelationshipTemplate", diff --git a/packages/content/package.json b/packages/content/package.json index 50e196592..9346eaa9e 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.18", + "version": "5.0.0-alpha.19", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/src/index.ts b/packages/content/src/index.ts index 3b72a10de..d77129ba9 100644 --- a/packages/content/src/index.ts +++ b/packages/content/src/index.ts @@ -1,7 +1,7 @@ export * from "./attributes"; export * from "./buildInformation"; export * from "./ContentJSON"; -export * from "./messages/Mail"; +export * from "./messages"; export * from "./notifications"; export * from "./relationships"; export * from "./requests"; diff --git a/packages/content/src/messages/ArbitraryMessageContent.ts b/packages/content/src/messages/ArbitraryMessageContent.ts new file mode 100644 index 000000000..3cc458eec --- /dev/null +++ b/packages/content/src/messages/ArbitraryMessageContent.ts @@ -0,0 +1,26 @@ +import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; +import { ContentJSON } from "../ContentJSON"; + +export interface ArbitraryMessageContentJSON extends ContentJSON { + "@type": "ArbitraryMessageContent"; + value: unknown; +} + +export interface IArbitraryMessageContent extends ISerializable { + value: unknown; +} + +@type("ArbitraryMessageContent") +export class ArbitraryMessageContent extends Serializable implements IArbitraryMessageContent { + @serialize({ any: true }) + @validate() + public value: unknown; + + public static from(value: IArbitraryMessageContent | Omit): ArbitraryMessageContent { + return this.fromAny(value); + } + + public override toJSON(verbose?: boolean | undefined, serializeAsString?: boolean | undefined): ArbitraryMessageContentJSON { + return super.toJSON(verbose, serializeAsString) as ArbitraryMessageContentJSON; + } +} diff --git a/packages/content/src/messages/Mail.ts b/packages/content/src/messages/Mail.ts index 6f5ee37bd..f8337432a 100644 --- a/packages/content/src/messages/Mail.ts +++ b/packages/content/src/messages/Mail.ts @@ -9,6 +9,7 @@ export interface MailJSON extends ContentJSON { subject: string; body: string; } + export interface IMail extends ISerializable { to: ICoreAddress[]; cc?: ICoreAddress[]; diff --git a/packages/content/src/messages/index.ts b/packages/content/src/messages/index.ts new file mode 100644 index 000000000..c82ac67fa --- /dev/null +++ b/packages/content/src/messages/index.ts @@ -0,0 +1,2 @@ +export * from "./ArbitraryMessageContent"; +export * from "./Mail"; diff --git a/packages/content/src/notifications/Notification.ts b/packages/content/src/notifications/Notification.ts index be0e2a845..e08885411 100644 --- a/packages/content/src/notifications/Notification.ts +++ b/packages/content/src/notifications/Notification.ts @@ -35,4 +35,8 @@ export class Notification extends Serializable implements INotification { public static from(value: INotification): Notification { return this.fromAny(value); } + + public override toJSON(verbose?: boolean | undefined, serializeAsString?: boolean | undefined): NotificationJSON { + return super.toJSON(verbose, serializeAsString) as NotificationJSON; + } } diff --git a/packages/content/src/relationships/ArbitraryRelationshipCreationContent.ts b/packages/content/src/relationships/ArbitraryRelationshipCreationContent.ts new file mode 100644 index 000000000..ac85f154f --- /dev/null +++ b/packages/content/src/relationships/ArbitraryRelationshipCreationContent.ts @@ -0,0 +1,26 @@ +import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; +import { ContentJSON } from "../ContentJSON"; + +export interface ArbitraryRelationshipCreationContentJSON extends ContentJSON { + "@type": "ArbitraryRelationshipCreationContent"; + value: unknown; +} + +export interface IArbitraryRelationshipCreationContent extends ISerializable { + value: unknown; +} + +@type("ArbitraryRelationshipCreationContent") +export class ArbitraryRelationshipCreationContent extends Serializable implements IArbitraryRelationshipCreationContent { + @serialize({ any: true }) + @validate() + public value: unknown; + + public static from(value: IArbitraryRelationshipCreationContent | Omit): ArbitraryRelationshipCreationContent { + return this.fromAny(value); + } + + public override toJSON(verbose?: boolean | undefined, serializeAsString?: boolean | undefined): ArbitraryRelationshipCreationContentJSON { + return super.toJSON(verbose, serializeAsString) as ArbitraryRelationshipCreationContentJSON; + } +} diff --git a/packages/content/src/relationships/ArbitraryRelationshipTemplateContent.ts b/packages/content/src/relationships/ArbitraryRelationshipTemplateContent.ts new file mode 100644 index 000000000..45b17e4c4 --- /dev/null +++ b/packages/content/src/relationships/ArbitraryRelationshipTemplateContent.ts @@ -0,0 +1,26 @@ +import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; +import { ContentJSON } from "../ContentJSON"; + +export interface ArbitraryRelationshipTemplateContentJSON extends ContentJSON { + "@type": "ArbitraryRelationshipTemplateContent"; + value: unknown; +} + +export interface IArbitraryRelationshipTemplateContent extends ISerializable { + value: unknown; +} + +@type("ArbitraryRelationshipTemplateContent") +export class ArbitraryRelationshipTemplateContent extends Serializable implements IArbitraryRelationshipTemplateContent { + @serialize({ any: true }) + @validate() + public value: unknown; + + public static from(value: IArbitraryRelationshipTemplateContent | Omit): ArbitraryRelationshipTemplateContent { + return this.fromAny(value); + } + + public override toJSON(verbose?: boolean | undefined, serializeAsString?: boolean | undefined): ArbitraryRelationshipTemplateContentJSON { + return super.toJSON(verbose, serializeAsString) as ArbitraryRelationshipTemplateContentJSON; + } +} diff --git a/packages/content/src/relationships/index.ts b/packages/content/src/relationships/index.ts index 57d92728d..9820bd3b3 100644 --- a/packages/content/src/relationships/index.ts +++ b/packages/content/src/relationships/index.ts @@ -1,2 +1,4 @@ +export * from "./ArbitraryRelationshipCreationContent"; +export * from "./ArbitraryRelationshipTemplateContent"; export * from "./RelationshipCreationContent"; export * from "./RelationshipTemplateContent"; diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 8515f2adf..6a3d6b19a 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.18", + "version": "5.0.0-alpha.19", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -65,10 +65,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.18", - "@nmshd/content": "5.0.0-alpha.18", + "@nmshd/consumption": "5.0.0-alpha.19", + "@nmshd/content": "5.0.0-alpha.19", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.18", + "@nmshd/transport": "5.0.0-alpha.19", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/runtime/src/dataViews/transport/MessageDVO.ts b/packages/runtime/src/dataViews/transport/MessageDVO.ts index 5d1bcd03c..ce720db02 100644 --- a/packages/runtime/src/dataViews/transport/MessageDVO.ts +++ b/packages/runtime/src/dataViews/transport/MessageDVO.ts @@ -1,3 +1,4 @@ +import { MessageContentDerivation } from "../../types/transport/MessageDTO"; import { DataViewObject } from "../DataViewObject"; import { FileDVO } from "./FileDVO"; import { IdentityDVO } from "./IdentityDVO"; @@ -82,7 +83,7 @@ export interface MessageDVO extends DataViewObject { /** * The content of the message. */ - content: unknown; + content: MessageContentDerivation; /** * The read indicator of the message diff --git a/packages/runtime/src/dataViews/transport/RelationshipDVO.ts b/packages/runtime/src/dataViews/transport/RelationshipDVO.ts index 012c82679..11ada7bfd 100644 --- a/packages/runtime/src/dataViews/transport/RelationshipDVO.ts +++ b/packages/runtime/src/dataViews/transport/RelationshipDVO.ts @@ -1,6 +1,6 @@ -import { RelationshipAuditLogDTO } from "../../types/transport/RelationshipDTO"; -import { LocalAttributeDVO } from "../consumption"; +import { RelationshipAuditLogDTO, RelationshipCreationContentDerivation } from "../../types/transport/RelationshipDTO"; import { DataViewObject } from "../DataViewObject"; +import { LocalAttributeDVO } from "../consumption"; export enum RelationshipDirection { Outgoing = "Outgoing", @@ -14,7 +14,7 @@ export interface RelationshipDVO extends DataViewObject { statusText: string; isPinned: boolean; theme?: RelationshipTheme; - creationContent: any; + creationContent: RelationshipCreationContentDerivation; auditLog: RelationshipAuditLogDTO; items: LocalAttributeDVO[]; attributeMap: Record; diff --git a/packages/runtime/src/dataViews/transport/RelationshipTemplateDVO.ts b/packages/runtime/src/dataViews/transport/RelationshipTemplateDVO.ts index 3bc29f06a..7618753b6 100644 --- a/packages/runtime/src/dataViews/transport/RelationshipTemplateDVO.ts +++ b/packages/runtime/src/dataViews/transport/RelationshipTemplateDVO.ts @@ -1,6 +1,7 @@ +import { RelationshipTemplateContentDerivation } from "../../types/transport/RelationshipTemplateDTO"; +import { DataViewObject } from "../DataViewObject"; import { LocalRequestDVO } from "../consumption/LocalRequestDVO"; import { RequestDVO } from "../content"; -import { DataViewObject } from "../DataViewObject"; import { IdentityDVO } from "./IdentityDVO"; export interface RelationshipTemplateDVO extends DataViewObject { @@ -20,5 +21,5 @@ export interface RelationshipTemplateDVO extends DataViewObject { request?: LocalRequestDVO; - content: unknown; + content: RelationshipTemplateContentDerivation; } diff --git a/packages/runtime/src/modules/MessageModule.ts b/packages/runtime/src/modules/MessageModule.ts index 47a97b374..d457fe0f6 100644 --- a/packages/runtime/src/modules/MessageModule.ts +++ b/packages/runtime/src/modules/MessageModule.ts @@ -1,5 +1,5 @@ import { Event } from "@js-soft/ts-utils"; -import { Mail } from "@nmshd/content"; +import { Mail, MailJSON } from "@nmshd/content"; import { MailReceivedEvent, MessageReceivedEvent, RelationshipEvent } from "../events"; import { ModuleConfiguration, RuntimeModule } from "../extensibility/modules/RuntimeModule"; @@ -23,7 +23,7 @@ export class MessageModule extends RuntimeModule { let event: Event | undefined; switch (type) { case "Mail": - const mail = Mail.from(message.content); + const mail = Mail.from(message.content as MailJSON); event = new MailReceivedEvent(messageReceivedEvent.eventTargetAddress, mail, message); this.runtime.eventBus.publish(event); this.logger.trace(`Published MailReceivedEvent for ${message.id}`); diff --git a/packages/runtime/src/modules/RequestModule.ts b/packages/runtime/src/modules/RequestModule.ts index d724d97d1..7a68774e3 100644 --- a/packages/runtime/src/modules/RequestModule.ts +++ b/packages/runtime/src/modules/RequestModule.ts @@ -1,5 +1,5 @@ import { LocalRequestStatus } from "@nmshd/consumption"; -import { RelationshipCreationContent, RelationshipTemplateContentJSON, RequestJSON, ResponseJSON, ResponseResult, ResponseWrapper, ResponseWrapperJSON } from "@nmshd/content"; +import { RelationshipCreationContent, RequestJSON, ResponseJSON, ResponseResult, ResponseWrapper } from "@nmshd/content"; import { IncomingRequestStatusChangedEvent, MessageProcessedEvent, @@ -38,7 +38,7 @@ export class RequestModule extends RuntimeModule { return; } - const body = template.content as RelationshipTemplateContentJSON; + const body = template.content; const services = await this.runtime.getServices(event.eventTargetAddress); @@ -111,17 +111,11 @@ export class RequestModule extends RuntimeModule { const messageContentType = message.content["@type"]; switch (messageContentType) { case "Request": - await this.createIncomingRequest(services, message.content as RequestJSON, message.id); - break; - - // Handle responses directly sent via messages. This is only for backwards compatibility and - // not viable for responding to Requests from RelationshipTemplates' onExistingRelationship. - case "Response": - await this.completeExistingRequestWithResponseReceivedByMessage(services, message.id, message.content as ResponseJSON); + await this.createIncomingRequest(services, message.content, message.id); break; case "ResponseWrapper": - const responseWrapper = message.content as ResponseWrapperJSON; + const responseWrapper = message.content; if (responseWrapper.requestSourceType === "Message") { await this.completeExistingRequestWithResponseReceivedByMessage(services, message.id, responseWrapper.response); @@ -134,6 +128,8 @@ export class RequestModule extends RuntimeModule { response: responseWrapper.response }); break; + default: + break; } if (messageContentType !== "Request") { @@ -153,7 +149,7 @@ export class RequestModule extends RuntimeModule { if (message.content["@type"] !== "Request") return; const services = await this.runtime.getServices(event.eventTargetAddress); - const request = message.content as RequestJSON; + const request = message.content; const requestResult = await services.consumptionServices.outgoingRequests.sent({ requestId: request.id!, messageId: message.id }); if (requestResult.isError) { @@ -217,7 +213,7 @@ export class RequestModule extends RuntimeModule { return; } - const creationContent = RelationshipCreationContent.from({ response: request.response!.content }); + const creationContent = RelationshipCreationContent.from({ response: request.response!.content }).toJSON(); const createRelationshipResult = await services.transportServices.relationships.createRelationship({ templateId, creationContent }); if (createRelationshipResult.isError) { this.logger.error(`Could not create relationship for templateId '${templateId}'. Root error:`, createRelationshipResult.error); @@ -286,6 +282,10 @@ export class RequestModule extends RuntimeModule { const templateId = template.id; // do not trigger for templates without the correct content type if (template.content["@type"] !== "RelationshipTemplateContent") return; + if (createdRelationship.creationContent["@type"] !== "RelationshipCreationContent") { + this.logger.error(`The creation content of relationshipId ${createdRelationship.id} is not of type RelationshipCreationContent.`); + return; + } const result = await services.consumptionServices.outgoingRequests.createAndCompleteFromRelationshipTemplateResponse({ templateId, diff --git a/packages/runtime/src/types/transport/MessageDTO.ts b/packages/runtime/src/types/transport/MessageDTO.ts index d541d8797..80daafd80 100644 --- a/packages/runtime/src/types/transport/MessageDTO.ts +++ b/packages/runtime/src/types/transport/MessageDTO.ts @@ -1,8 +1,11 @@ +import { ArbitraryMessageContentJSON, MailJSON, NotificationJSON, RequestJSON, ResponseWrapperJSON } from "@nmshd/content"; import { RecipientDTO } from "./RecipientDTO"; +export type MessageContentDerivation = MailJSON | ResponseWrapperJSON | RequestJSON | NotificationJSON | ArbitraryMessageContentJSON; + export interface MessageDTO { id: string; - content: any; + content: MessageContentDerivation; createdBy: string; createdByDevice: string; recipients: RecipientDTO[]; diff --git a/packages/runtime/src/types/transport/RelationshipDTO.ts b/packages/runtime/src/types/transport/RelationshipDTO.ts index 0bd589a75..b3fa23c79 100644 --- a/packages/runtime/src/types/transport/RelationshipDTO.ts +++ b/packages/runtime/src/types/transport/RelationshipDTO.ts @@ -1,3 +1,4 @@ +import { ArbitraryRelationshipCreationContentJSON, RelationshipCreationContentJSON } from "@nmshd/content"; import { IdentityDTO } from "./IdentityDTO"; import { RelationshipTemplateDTO } from "./RelationshipTemplateDTO"; @@ -34,12 +35,14 @@ export interface RelationshipAuditLogEntryDTO { export interface RelationshipAuditLogDTO extends Array {} +export type RelationshipCreationContentDerivation = RelationshipCreationContentJSON | ArbitraryRelationshipCreationContentJSON; + export interface RelationshipDTO { id: string; template: RelationshipTemplateDTO; status: RelationshipStatus; peer: string; peerIdentity: IdentityDTO; - creationContent: any; + creationContent: RelationshipCreationContentDerivation; auditLog: RelationshipAuditLogDTO; } diff --git a/packages/runtime/src/types/transport/RelationshipTemplateDTO.ts b/packages/runtime/src/types/transport/RelationshipTemplateDTO.ts index 2e38bb234..3b5f1a6a2 100644 --- a/packages/runtime/src/types/transport/RelationshipTemplateDTO.ts +++ b/packages/runtime/src/types/transport/RelationshipTemplateDTO.ts @@ -1,10 +1,14 @@ +import { ArbitraryRelationshipTemplateContentJSON, RelationshipTemplateContentJSON } from "@nmshd/content"; + +export type RelationshipTemplateContentDerivation = RelationshipTemplateContentJSON | ArbitraryRelationshipTemplateContentJSON; + export interface RelationshipTemplateDTO { id: string; isOwn: boolean; createdBy: string; createdByDevice: string; createdAt: string; - content: any; + content: RelationshipTemplateContentDerivation; expiresAt?: string; maxNumberOfAllocations?: number; secretKey: string; diff --git a/packages/runtime/src/useCases/consumption/notifications/NotificationMapper.ts b/packages/runtime/src/useCases/consumption/notifications/NotificationMapper.ts index 3bc3fb207..889655c02 100644 --- a/packages/runtime/src/useCases/consumption/notifications/NotificationMapper.ts +++ b/packages/runtime/src/useCases/consumption/notifications/NotificationMapper.ts @@ -1,5 +1,4 @@ import { LocalNotification } from "@nmshd/consumption"; -import { NotificationJSON } from "@nmshd/content"; import { LocalNotificationDTO } from "../../../types"; export class NotificationMapper { @@ -10,7 +9,7 @@ export class NotificationMapper { peer: notification.peer.toString(), createdAt: notification.createdAt.toISOString(), receivedByDevice: notification.receivedByDevice?.toString(), - content: notification.content.toJSON() as NotificationJSON, + content: notification.content.toJSON(), status: notification.status, source: { type: "Message", diff --git a/packages/runtime/src/useCases/transport/messages/MessageMapper.ts b/packages/runtime/src/useCases/transport/messages/MessageMapper.ts index 1395fa9a7..78d27d379 100644 --- a/packages/runtime/src/useCases/transport/messages/MessageMapper.ts +++ b/packages/runtime/src/useCases/transport/messages/MessageMapper.ts @@ -1,3 +1,5 @@ +import { Serializable } from "@js-soft/ts-serval"; +import { ArbitraryMessageContent, Mail, Notification, Request, ResponseWrapper } from "@nmshd/content"; import { CoreBuffer } from "@nmshd/crypto"; import { CachedMessageRecipient, File, Message } from "@nmshd/transport"; import { MessageDTO, MessageWithAttachmentsDTO } from "../../../types"; @@ -22,9 +24,10 @@ export class MessageMapper { if (!message.cache) { throw RuntimeErrors.general.cacheEmpty(Message, message.id.toString()); } + return { id: message.id.toString(), - content: message.cache.content.toJSON(), + content: this.toMessageContent(message.cache.content), createdBy: message.cache.createdBy.toString(), createdByDevice: message.cache.createdByDevice.toString(), recipients: this.toRecipients(message.cache.recipients), @@ -39,9 +42,10 @@ export class MessageMapper { if (!message.cache) { throw RuntimeErrors.general.cacheEmpty(Message, message.id.toString()); } + return { id: message.id.toString(), - content: message.cache.content.toJSON(), + content: this.toMessageContent(message.cache.content), createdBy: message.cache.createdBy.toString(), createdByDevice: message.cache.createdByDevice.toString(), recipients: this.toRecipients(message.cache.recipients), @@ -66,4 +70,20 @@ export class MessageMapper { }; }); } + + private static toMessageContent(content: Serializable) { + if ( + !( + content instanceof Mail || + content instanceof Request || + content instanceof ResponseWrapper || + content instanceof Notification || + content instanceof ArbitraryMessageContent + ) + ) { + return ArbitraryMessageContent.from({ value: content }).toJSON(); + } + + return content.toJSON(); + } } diff --git a/packages/runtime/src/useCases/transport/messages/SendMessage.ts b/packages/runtime/src/useCases/transport/messages/SendMessage.ts index 389fdef8d..1faca9d92 100644 --- a/packages/runtime/src/useCases/transport/messages/SendMessage.ts +++ b/packages/runtime/src/useCases/transport/messages/SendMessage.ts @@ -1,7 +1,7 @@ import { Serializable } from "@js-soft/ts-serval"; import { Result } from "@js-soft/ts-utils"; import { OutgoingRequestsController } from "@nmshd/consumption"; -import { Request } from "@nmshd/content"; +import { ArbitraryMessageContent, Mail, Notification, Request, ResponseWrapper } from "@nmshd/content"; import { AccountController, CoreAddress, CoreId, File, FileController, MessageController } from "@nmshd/transport"; import _ from "lodash"; import { Inject } from "typescript-ioc"; @@ -58,6 +58,20 @@ export class SendMessageUseCase extends UseCase private async validateMessageContent(content: any, recipients: string[]) { const transformedContent = Serializable.fromUnknown(content); + if ( + !( + transformedContent instanceof Mail || + transformedContent instanceof ResponseWrapper || + transformedContent instanceof Notification || + transformedContent instanceof ArbitraryMessageContent || + transformedContent instanceof Request + ) + ) { + return RuntimeErrors.general.invalidPropertyValue( + "The content of a Message must either be a Mail, Request, ResponseWrapper, Notification or an ArbitraryMessageContent." + ); + } + if (!(transformedContent instanceof Request)) return; if (!transformedContent.id) return RuntimeErrors.general.invalidPropertyValue("The Request must have an id."); @@ -73,7 +87,6 @@ export class SendMessageUseCase extends UseCase const recipient = CoreAddress.from(recipients[0]); if (!recipient.equals(localRequest.peer)) return RuntimeErrors.general.invalidPropertyValue("The recipient does not match the Request's peer."); - return; } diff --git a/packages/runtime/src/useCases/transport/relationshipTemplates/CreateOwnRelationshipTemplate.ts b/packages/runtime/src/useCases/transport/relationshipTemplates/CreateOwnRelationshipTemplate.ts index 64c06a899..84f34e449 100644 --- a/packages/runtime/src/useCases/transport/relationshipTemplates/CreateOwnRelationshipTemplate.ts +++ b/packages/runtime/src/useCases/transport/relationshipTemplates/CreateOwnRelationshipTemplate.ts @@ -1,7 +1,7 @@ import { Serializable } from "@js-soft/ts-serval"; import { Result } from "@js-soft/ts-utils"; import { OutgoingRequestsController } from "@nmshd/consumption"; -import { RelationshipTemplateContent } from "@nmshd/content"; +import { ArbitraryRelationshipTemplateContent, RelationshipTemplateContent } from "@nmshd/content"; import { AccountController, CoreDate, RelationshipTemplateController } from "@nmshd/transport"; import { DateTime } from "luxon"; import { nameof } from "ts-simple-nameof"; @@ -68,6 +68,13 @@ export class CreateOwnRelationshipTemplateUseCase extends UseCase this.toRelationshipTemplateDTO(i)); } + + private static toTemplateContent(content: Serializable) { + if (!(content instanceof RelationshipTemplateContent || content instanceof ArbitraryRelationshipTemplateContent)) { + return ArbitraryRelationshipTemplateContent.from({ value: content }).toJSON(); + } + return content.toJSON(); + } } diff --git a/packages/runtime/src/useCases/transport/relationships/CreateRelationship.ts b/packages/runtime/src/useCases/transport/relationships/CreateRelationship.ts index 18e60b954..fdf5bee8f 100644 --- a/packages/runtime/src/useCases/transport/relationships/CreateRelationship.ts +++ b/packages/runtime/src/useCases/transport/relationships/CreateRelationship.ts @@ -1,5 +1,7 @@ +import { Serializable } from "@js-soft/ts-serval"; import { Result } from "@js-soft/ts-utils"; -import { AccountController, CoreId, RelationshipsController, RelationshipTemplate, RelationshipTemplateController } from "@nmshd/transport"; +import { ArbitraryRelationshipCreationContent, RelationshipCreationContent } from "@nmshd/content"; +import { AccountController, CoreId, RelationshipTemplate, RelationshipTemplateController, RelationshipsController } from "@nmshd/transport"; import { Inject } from "typescript-ioc"; import { RelationshipDTO } from "../../../types"; import { RelationshipTemplateIdString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common"; @@ -32,10 +34,16 @@ export class CreateRelationshipUseCase extends UseCase this.toAuditLogEntryDTO(entry)), - creationContent: relationship.cache.creationContent?.toJSON() + creationContent: this.toCreationContent(relationship.cache.creationContent) }; } @@ -37,4 +39,11 @@ export class RelationshipMapper { public static toRelationshipDTOList(relationships: Relationship[]): RelationshipDTO[] { return relationships.map((r) => this.toRelationshipDTO(r)); } + + private static toCreationContent(content: Serializable) { + if (!(content instanceof RelationshipCreationContent || content instanceof ArbitraryRelationshipCreationContent)) { + return ArbitraryRelationshipCreationContent.from({ value: content }).toJSON(); + } + return content.toJSON(); + } } diff --git a/packages/runtime/test/consumption/iqlQuery.test.ts b/packages/runtime/test/consumption/iqlQuery.test.ts index 6fdc27e84..fa565ccb5 100644 --- a/packages/runtime/test/consumption/iqlQuery.test.ts +++ b/packages/runtime/test/consumption/iqlQuery.test.ts @@ -123,7 +123,7 @@ describe("IQL Query", () => { triggeredEvent = event; }); const sRequestMessage = await sendMessageWithRequest(sRuntimeServices, rRuntimeServices, requestContent); - const result = await sConsumptionServices.outgoingRequests.sent({ requestId: sRequestMessage.content.id, messageId: sRequestMessage.id }); + const result = await sConsumptionServices.outgoingRequests.sent({ requestId: sRequestMessage.content.id!, messageId: sRequestMessage.id }); expect(result).toBeSuccessful(); @@ -170,7 +170,7 @@ describe("IQL Query", () => { }); const result = await rConsumptionServices.incomingRequests.checkPrerequisites({ - requestId: message.content.id + requestId: message.content.id! }); expect(result).toBeSuccessful(); @@ -193,7 +193,7 @@ describe("IQL Query", () => { requestSourceId: message.id }); await rConsumptionServices.incomingRequests.checkPrerequisites({ - requestId: message.content.id + requestId: message.content.id! }); let triggeredEvent: IncomingRequestStatusChangedEvent | undefined; rEventBus.subscribeOnce(IncomingRequestStatusChangedEvent, (event) => { @@ -201,7 +201,7 @@ describe("IQL Query", () => { }); const result = await rConsumptionServices.incomingRequests.requireManualDecision({ - requestId: message.content.id + requestId: message.content.id! }); expect(result).toBeSuccessful(); diff --git a/packages/runtime/test/consumption/requests.test.ts b/packages/runtime/test/consumption/requests.test.ts index f07db20b4..4a06974e2 100644 --- a/packages/runtime/test/consumption/requests.test.ts +++ b/packages/runtime/test/consumption/requests.test.ts @@ -1,6 +1,7 @@ import { EventBus } from "@js-soft/ts-utils"; import { LocalRequestStatus } from "@nmshd/consumption"; import { TestRequestItemJSON } from "@nmshd/consumption/test/modules/requests/testHelpers/TestRequestItem"; +import { RelationshipCreationContentJSON, RelationshipTemplateContentJSON } from "@nmshd/content"; import { CoreDate } from "@nmshd/transport"; import { ConsumptionServices, CreateOutgoingRequestRequest, OutgoingRequestCreatedEvent, OutgoingRequestStatusChangedEvent, TransportServices } from "../../src"; import { IncomingRequestReceivedEvent, IncomingRequestStatusChangedEvent } from "../../src/events"; @@ -104,7 +105,7 @@ describe("Requests", () => { triggeredEvent = event; }); const sRequestMessage = await sendMessageWithRequest(sRuntimeServices, rRuntimeServices, requestContent); - const result = await sConsumptionServices.outgoingRequests.sent({ requestId: sRequestMessage.content.id, messageId: sRequestMessage.id }); + const result = await sConsumptionServices.outgoingRequests.sent({ requestId: sRequestMessage.content.id!, messageId: sRequestMessage.id }); expect(result).toBeSuccessful(); expect(result.value.status).toBe(LocalRequestStatus.Open); @@ -131,7 +132,7 @@ describe("Requests", () => { expect(rLocalRequest).toBeDefined(); expect(rLocalRequest.status).toBe(LocalRequestStatus.Open); - expect(rLocalRequest.id).toBe(rRequestMessage.content.id); + expect(rLocalRequest.id).toBe(rRequestMessage.content.id!); expect(triggeredEvent).toBeDefined(); expect(triggeredEvent!.data).toBeDefined(); @@ -150,7 +151,7 @@ describe("Requests", () => { }); const result = await rConsumptionServices.incomingRequests.checkPrerequisites({ - requestId: message.content.id + requestId: message.content.id! }); expect(result).toBeSuccessful(); @@ -173,7 +174,7 @@ describe("Requests", () => { requestSourceId: message.id }); await rConsumptionServices.incomingRequests.checkPrerequisites({ - requestId: message.content.id + requestId: message.content.id! }); let triggeredEvent: IncomingRequestStatusChangedEvent | undefined; rEventBus.subscribeOnce(IncomingRequestStatusChangedEvent, (event) => { @@ -181,7 +182,7 @@ describe("Requests", () => { }); const result = await rConsumptionServices.incomingRequests.requireManualDecision({ - requestId: message.content.id + requestId: message.content.id! }); expect(result).toBeSuccessful(); @@ -349,7 +350,7 @@ describe("Requests", () => { let rTransportServices: TransportServices; let rEventBus: EventBus; - const templateContent = { + const templateContent: RelationshipTemplateContentJSON = { "@type": "RelationshipTemplateContent", onNewRelationship: { "@type": "Request", @@ -396,7 +397,7 @@ describe("Requests", () => { }); const result = await rConsumptionServices.incomingRequests.received({ - receivedRequest: rRelationshipTemplate.content.onNewRelationship, + receivedRequest: (rRelationshipTemplate.content as RelationshipTemplateContentJSON).onNewRelationship, requestSourceId: rRelationshipTemplate.id }); @@ -417,7 +418,7 @@ describe("Requests", () => { const rRelationshipTemplate = await exchangeTemplate(sTransportServices, rTransportServices, templateContent); const incomingRequest = ( await rConsumptionServices.incomingRequests.received({ - receivedRequest: rRelationshipTemplate.content.onNewRelationship, + receivedRequest: (rRelationshipTemplate.content as RelationshipTemplateContentJSON).onNewRelationship, requestSourceId: rRelationshipTemplate.id }) ).value; @@ -447,7 +448,7 @@ describe("Requests", () => { const rRelationshipTemplate = await exchangeTemplate(sTransportServices, rTransportServices, templateContent); const incomingRequest = ( await rConsumptionServices.incomingRequests.received({ - receivedRequest: rRelationshipTemplate.content.onNewRelationship, + receivedRequest: (rRelationshipTemplate.content as RelationshipTemplateContentJSON).onNewRelationship, requestSourceId: rRelationshipTemplate.id }) ).value; @@ -572,7 +573,7 @@ describe("Requests", () => { const completionResult = await sConsumptionServices.outgoingRequests.createAndCompleteFromRelationshipTemplateResponse({ responseSourceId: sRelationship.id, - response: sRelationship.creationContent.response, + response: (sRelationship.creationContent as RelationshipCreationContentJSON).response, templateId: relationship!.template.id }); diff --git a/packages/runtime/test/dataViews/IdentityDVO.test.ts b/packages/runtime/test/dataViews/IdentityDVO.test.ts index f3d410d3c..b0901ce71 100644 --- a/packages/runtime/test/dataViews/IdentityDVO.test.ts +++ b/packages/runtime/test/dataViews/IdentityDVO.test.ts @@ -1,5 +1,5 @@ import { LocalRequestStatus } from "@nmshd/consumption"; -import { ShareAttributeRequestItemJSON } from "@nmshd/content"; +import { RelationshipTemplateContentJSON, ShareAttributeRequestItemJSON } from "@nmshd/content"; import { AttributeDeletedEvent, IncomingRequestStatusChangedEvent, @@ -36,7 +36,7 @@ describe("IdentityDVO after loading a relationship template sharing a DisplayNam } }); - const templateContent = { + const templateContent: RelationshipTemplateContentJSON = { "@type": "RelationshipTemplateContent", onNewRelationship: { "@type": "Request", diff --git a/packages/runtime/test/dataViews/MessageDVO.test.ts b/packages/runtime/test/dataViews/MessageDVO.test.ts index cf35ef710..e9ce67a8f 100644 --- a/packages/runtime/test/dataViews/MessageDVO.test.ts +++ b/packages/runtime/test/dataViews/MessageDVO.test.ts @@ -1,4 +1,15 @@ -import { GivenName, IdentityAttribute, MailJSON, ReadAttributeAcceptResponseItem, ReadAttributeRequestItem, ResponseItemResult, ResponseResult } from "@nmshd/content"; +import { + ArbitraryMessageContent, + GivenName, + IdentityAttribute, + MailJSON, + ReadAttributeAcceptResponseItem, + ReadAttributeRequestItem, + RelationshipCreationContent, + RelationshipTemplateContent, + ResponseItemResult, + ResponseResult +} from "@nmshd/content"; import { CoreAddress, CoreId } from "@nmshd/transport"; import { DataViewExpander, MailDVO, SendMessageRequest, TransportServices } from "../../src"; import { establishRelationshipWithContents, getRelationship, RuntimeServiceProvider, syncUntilHasMessage, uploadFile } from "../lib"; @@ -18,7 +29,7 @@ beforeAll(async () => { await establishRelationshipWithContents( transportServices1, transportServices2, - { + RelationshipTemplateContent.from({ onNewRelationship: { "@type": "Request", items: [ @@ -31,12 +42,12 @@ beforeAll(async () => { }) ] } - }, - { + }).toJSON(), + RelationshipCreationContent.from({ response: { "@type": "Response", result: ResponseResult.Accepted, - requestId: await CoreId.generate(), + requestId: (await CoreId.generate()).toString(), items: [ ReadAttributeAcceptResponseItem.from({ result: ResponseItemResult.Accepted, @@ -45,10 +56,10 @@ beforeAll(async () => { owner: CoreAddress.from((await transportServices1.account.getIdentityInfo()).value.address), value: GivenName.from("AGivenName") }) - }) + }).toJSON() ] } - } + }).toJSON() ); }, 30000); @@ -70,9 +81,11 @@ describe("MessageDVO", () => { messageRequest = { recipients: [transportService2Address], - content: { - arbitraryValue: true - }, + content: ArbitraryMessageContent.from({ + value: { + arbitraryValue: true + } + }).toJSON(), attachments: [fileId] }; mailRequest = { diff --git a/packages/runtime/test/dataViews/RelationshipDVO.test.ts b/packages/runtime/test/dataViews/RelationshipDVO.test.ts index 2cdd09e03..19cdc26f8 100644 --- a/packages/runtime/test/dataViews/RelationshipDVO.test.ts +++ b/packages/runtime/test/dataViews/RelationshipDVO.test.ts @@ -1,4 +1,13 @@ -import { GivenName, IdentityAttribute, ReadAttributeAcceptResponseItem, ReadAttributeRequestItem, ResponseItemResult, ResponseResult } from "@nmshd/content"; +import { + GivenName, + IdentityAttribute, + ReadAttributeAcceptResponseItem, + ReadAttributeRequestItem, + RelationshipCreationContent, + RelationshipTemplateContent, + ResponseItemResult, + ResponseResult +} from "@nmshd/content"; import { CoreAddress, CoreId } from "@nmshd/transport"; import { DataViewExpander, TransportServices } from "../../src"; import { establishRelationshipWithContents, RuntimeServiceProvider } from "../lib"; @@ -18,7 +27,8 @@ beforeAll(async () => { await establishRelationshipWithContents( transportServices1, transportServices2, - { + + RelationshipTemplateContent.from({ onNewRelationship: { "@type": "Request", items: [ @@ -31,12 +41,12 @@ beforeAll(async () => { }) ] } - }, - { + }).toJSON(), + RelationshipCreationContent.from({ response: { "@type": "Response", result: ResponseResult.Accepted, - requestId: await CoreId.generate(), + requestId: (await CoreId.generate()).toString(), items: [ ReadAttributeAcceptResponseItem.from({ result: ResponseItemResult.Accepted, @@ -45,10 +55,10 @@ beforeAll(async () => { owner: CoreAddress.from((await transportServices1.account.getIdentityInfo()).value.address), value: GivenName.from("AGivenName") }) - }) + }).toJSON() ] } - } + }).toJSON() ); }, 30000); diff --git a/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts b/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts index 1953a039e..a5835ed28 100644 --- a/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts +++ b/packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts @@ -7,6 +7,10 @@ import { ProposeAttributeRequestItem, ProposeAttributeRequestItemJSON, RelationshipAttributeConfidentiality, + RelationshipAttributeJSON, + RelationshipTemplateContent, + RelationshipTemplateContentJSON, + RequestItemGroupJSON, Surname } from "@nmshd/content"; import { CoreAddress } from "@nmshd/transport"; @@ -22,7 +26,7 @@ import { RuntimeServiceProvider, TestRuntimeServices, createTemplate, syncUntilH const serviceProvider = new RuntimeServiceProvider(); let templator: TestRuntimeServices; let requestor: TestRuntimeServices; -let templatorTemplate: RelationshipTemplateDTO; +let templatorTemplate: RelationshipTemplateDTO & { content: RelationshipTemplateContentJSON }; let templateId: string; let responseItems: DecideRequestItemGroupParametersJSON[]; @@ -41,7 +45,7 @@ beforeEach(function () { describe("RelationshipTemplateDVO", () => { beforeAll(async () => { - const relationshipAttributeContent1 = { + const relationshipAttributeContent1: RelationshipAttributeJSON = { "@type": "RelationshipAttribute", owner: templator.address, value: { @@ -52,7 +56,7 @@ describe("RelationshipTemplateDVO", () => { key: "givenName", confidentiality: "protected" as RelationshipAttributeConfidentiality }; - const relationshipAttributeContent2 = { + const relationshipAttributeContent2: RelationshipAttributeJSON = { "@type": "RelationshipAttribute", owner: templator.address, value: { @@ -63,8 +67,7 @@ describe("RelationshipTemplateDVO", () => { key: "surname", confidentiality: "protected" as RelationshipAttributeConfidentiality }; - const templateContent = { - "@type": "RelationshipTemplateContent", + const templateContent = RelationshipTemplateContent.from({ onNewRelationship: { "@type": "Request", items: [ @@ -97,7 +100,7 @@ describe("RelationshipTemplateDVO", () => { owner: CoreAddress.from(""), value: GivenName.from("Theo") }) - }), + }).toJSON(), ProposeAttributeRequestItem.from({ mustBeAccepted: true, query: IdentityAttributeQuery.from({ @@ -107,17 +110,17 @@ describe("RelationshipTemplateDVO", () => { owner: CoreAddress.from(""), value: Surname.from("Templator") }) - }) + }).toJSON() ] } ] } - }; + }).toJSON(); const newIdentityAttribute1 = IdentityAttribute.from( - (templateContent.onNewRelationship.items[1].items[0] as ProposeAttributeRequestItemJSON).attribute as IdentityAttributeJSON + ((templateContent.onNewRelationship.items[1] as RequestItemGroupJSON).items[0] as ProposeAttributeRequestItemJSON).attribute as IdentityAttributeJSON ).toJSON(); const newIdentityAttribute2 = IdentityAttribute.from( - (templateContent.onNewRelationship.items[1].items[1] as ProposeAttributeRequestItemJSON).attribute as IdentityAttributeJSON + ((templateContent.onNewRelationship.items[1] as RequestItemGroupJSON).items[1] as ProposeAttributeRequestItemJSON).attribute as IdentityAttributeJSON ).toJSON(); responseItems = [ { items: [{ accept: true }, { accept: true }] }, @@ -138,7 +141,7 @@ describe("RelationshipTemplateDVO", () => { ] } ]; - templatorTemplate = await createTemplate(templator.transport, templateContent); + templatorTemplate = (await createTemplate(templator.transport, templateContent)) as RelationshipTemplateDTO & { content: RelationshipTemplateContentJSON }; templateId = templatorTemplate.id; }); @@ -173,7 +176,8 @@ describe("RelationshipTemplateDVO", () => { }); test("TemplateDVO for requestor", async () => { - const requestorTemplate = (await requestor.transport.relationshipTemplates.loadPeerRelationshipTemplate({ reference: templatorTemplate.truncatedReference })).value; + const requestorTemplate = (await requestor.transport.relationshipTemplates.loadPeerRelationshipTemplate({ reference: templatorTemplate.truncatedReference })) + .value as RelationshipTemplateDTO & { content: RelationshipTemplateContentJSON }; await requestor.eventBus.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const dto = requestorTemplate; @@ -245,7 +249,8 @@ describe("RelationshipTemplateDVO", () => { "source.reference": templateId } }); - const requestorTemplate = (await requestor.transport.relationshipTemplates.loadPeerRelationshipTemplate({ reference: templatorTemplate.truncatedReference })).value; + const requestorTemplate = (await requestor.transport.relationshipTemplates.loadPeerRelationshipTemplate({ reference: templatorTemplate.truncatedReference })) + .value as RelationshipTemplateDTO & { content: RelationshipTemplateContentJSON }; if (requestResult.value.length === 0) { await requestor.eventBus.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); requestResult = await requestor.consumption.incomingRequests.getRequests({ diff --git a/packages/runtime/test/dataViews/requestItems/ComplexReadAttributeRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/ComplexReadAttributeRequestItemDVO.test.ts index 958d4c565..3aa52c0bc 100644 --- a/packages/runtime/test/dataViews/requestItems/ComplexReadAttributeRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/ComplexReadAttributeRequestItemDVO.test.ts @@ -100,7 +100,7 @@ describe("ComplexReadAttributeRequestItemDVO with IdentityAttributeQuery", () => test("check the MessageDVO for the sender", async () => { const senderMessage = await sendMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); - await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id); + await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id!); const dto = senderMessage; const dvo = (await expander1.expandMessageDTO(senderMessage)) as RequestMessageDVO; expect(dvo).toBeDefined(); @@ -181,7 +181,7 @@ describe("ComplexReadAttributeRequestItemDVO with IdentityAttributeQuery", () => const recipientMessage = await exchangeMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); await eventBus2.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await consumptionServices2.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -257,7 +257,7 @@ describe("ComplexReadAttributeRequestItemDVO with IdentityAttributeQuery", () => expect(responseItem.attributeId).toStrictEqual(attributeResult.value[numberOfAttributes - 1].id); expect(returnedValue).toStrictEqual(attributeValue); - await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id!); await eventBus1.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.Completed); }); @@ -375,7 +375,7 @@ describe("ComplexReadAttributeRequestItemDVO with IQL", () => { test("check the MessageDVO for the sender", async () => { const senderMessage = await sendMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); - await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id); + await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id!); const dto = senderMessage; const dvo = (await expander1.expandMessageDTO(senderMessage)) as RequestMessageDVO; expect(dvo).toBeDefined(); @@ -442,7 +442,7 @@ describe("ComplexReadAttributeRequestItemDVO with IQL", () => { const recipientMessage = await exchangeMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); await eventBus2.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await consumptionServices2.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -511,7 +511,7 @@ describe("ComplexReadAttributeRequestItemDVO with IQL", () => { expect(responseItem.attributeId).toStrictEqual(attributeResult.value[0].id); expect(returnedValue).toStrictEqual(attributeValue); - await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id!); await eventBus1.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.Completed); }); diff --git a/packages/runtime/test/dataViews/requestItems/CreateIdentityAttributeRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/CreateIdentityAttributeRequestItemDVO.test.ts index 91bbb54b0..af190868d 100644 --- a/packages/runtime/test/dataViews/requestItems/CreateIdentityAttributeRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/CreateIdentityAttributeRequestItemDVO.test.ts @@ -87,7 +87,7 @@ beforeEach(function () { describe("CreateIdentityAttributeRequestItemDVO", () => { test("check the MessageDVO for the sender", async () => { const senderMessage = await sendMessageWithRequest(sRuntimeServices, rRuntimeServices, requestContent); - await syncUntilHasMessageWithRequest(rTransportServices, senderMessage.content.id); + await syncUntilHasMessageWithRequest(rTransportServices, senderMessage.content.id!); const dto = senderMessage; const dvo = (await sExpander.expandMessageDTO(senderMessage)) as RequestMessageDVO; expect(dvo).toBeDefined(); @@ -154,7 +154,7 @@ describe("CreateIdentityAttributeRequestItemDVO", () => { const recipientMessage = await exchangeMessageWithRequest(sRuntimeServices, rRuntimeServices, requestContent); await rEventBus.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await rConsumptionServices.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -209,7 +209,7 @@ describe("CreateIdentityAttributeRequestItemDVO", () => { expect(responseItem.attribute.valueType).toBe("DisplayName"); expect((attributeResult.value[0].content.value as DisplayNameJSON).value).toStrictEqual((responseItem.attribute.content.value as DisplayNameJSON).value); - await syncUntilHasMessageWithResponse(sTransportServices, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(sTransportServices, recipientMessage.content.id!); await sEventBus.waitForEvent(OutgoingRequestStatusChangedEvent); }); diff --git a/packages/runtime/test/dataViews/requestItems/CreateRelationshipAttributeRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/CreateRelationshipAttributeRequestItemDVO.test.ts index af485eabf..393368ff6 100644 --- a/packages/runtime/test/dataViews/requestItems/CreateRelationshipAttributeRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/CreateRelationshipAttributeRequestItemDVO.test.ts @@ -92,7 +92,7 @@ beforeEach(function () { describe("CreateRelationshipAttributeRequestItemDVO", () => { test("check the MessageDVO for the sender", async () => { const senderMessage = await sendMessageWithRequest(sRuntimeServices, rRuntimeServices, requestContent); - await syncUntilHasMessageWithRequest(rTransportServices, senderMessage.content.id); + await syncUntilHasMessageWithRequest(rTransportServices, senderMessage.content.id!); const dto = senderMessage; const dvo = (await sExpander.expandMessageDTO(senderMessage)) as RequestMessageDVO; expect(dvo).toBeDefined(); @@ -159,7 +159,7 @@ describe("CreateRelationshipAttributeRequestItemDVO", () => { const recipientMessage = await exchangeMessageWithRequest(sRuntimeServices, rRuntimeServices, requestContent); await rEventBus.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await rConsumptionServices.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -216,7 +216,7 @@ describe("CreateRelationshipAttributeRequestItemDVO", () => { expect(responseItem.attribute.valueType).toBe("ProprietaryString"); expect(proprietaryString.value).toStrictEqual((responseItem.attribute.content.value as ProprietaryStringJSON).value); - await syncUntilHasMessageWithResponse(sTransportServices, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(sTransportServices, recipientMessage.content.id!); await sEventBus.waitForEvent(OutgoingRequestStatusChangedEvent); }); diff --git a/packages/runtime/test/dataViews/requestItems/DeleteAttributeRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/DeleteAttributeRequestItemDVO.test.ts index 2fe1023d9..fbb85cdb3 100644 --- a/packages/runtime/test/dataViews/requestItems/DeleteAttributeRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/DeleteAttributeRequestItemDVO.test.ts @@ -95,7 +95,7 @@ afterAll(() => serviceProvider.stop()); describe("DeleteAttributeRequestItemDVO", () => { test("check the MessageDVO for the sender", async () => { const senderMessage = await sendMessageWithRequest(sRuntimeServices, rRuntimeServices, requestContent); - await syncUntilHasMessageWithRequest(rTransportServices, senderMessage.content.id); + await syncUntilHasMessageWithRequest(rTransportServices, senderMessage.content.id!); const dto = senderMessage; const dvo = (await sExpander.expandMessageDTO(senderMessage)) as RequestMessageDVO; expect(dvo).toBeDefined(); @@ -163,7 +163,7 @@ describe("DeleteAttributeRequestItemDVO", () => { const recipientMessage = await exchangeMessageWithRequest(sRuntimeServices, rRuntimeServices, requestContent); await rEventBus.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await rConsumptionServices.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -198,7 +198,7 @@ describe("DeleteAttributeRequestItemDVO", () => { expect((responseItem as any).deletionDate).toBe(deletionDate); expect(requestItemDVO.response).toStrictEqual(responseItem); - await syncUntilHasMessageWithResponse(sTransportServices, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(sTransportServices, recipientMessage.content.id!); await sEventBus.waitForEvent(OutgoingRequestStatusChangedEvent); }); diff --git a/packages/runtime/test/dataViews/requestItems/FreeTextRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/FreeTextRequestItemDVO.test.ts index 6fbe30d86..f4c07ff41 100644 --- a/packages/runtime/test/dataViews/requestItems/FreeTextRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/FreeTextRequestItemDVO.test.ts @@ -78,7 +78,7 @@ describe("FreeTextRequestItemDVO", () => { test("check the MessageDVO for the sender", async () => { const senderMessage = await sendMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); - await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id); + await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id!); const dto = senderMessage; const dvo = (await expander1.expandMessageDTO(senderMessage)) as RequestMessageDVO; expect(dvo).toBeDefined(); @@ -130,7 +130,7 @@ describe("FreeTextRequestItemDVO", () => { const recipientMessage = await exchangeMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); await eventBus2.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await consumptionServices2.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -169,7 +169,7 @@ describe("FreeTextRequestItemDVO", () => { expect(responseItem.freeText).toBe("I accept the free text."); expect(requestItemDVO.response).toStrictEqual(responseItem); - await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id!); await eventBus1.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.Completed); }); diff --git a/packages/runtime/test/dataViews/requestItems/ProposeAttributeRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/ProposeAttributeRequestItemDVO.test.ts index 9e69ee6e7..745220682 100644 --- a/packages/runtime/test/dataViews/requestItems/ProposeAttributeRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/ProposeAttributeRequestItemDVO.test.ts @@ -156,7 +156,7 @@ describe("ProposeAttributeRequestItemDVO with IdentityAttributeQuery", () => { test("check the MessageDVO for the sender", async () => { const senderMessage = await sendMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); - await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id); + await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id!); const dto = senderMessage; const dvo = (await expander1.expandMessageDTO(senderMessage)) as RequestMessageDVO; expect(dvo).toBeDefined(); @@ -280,7 +280,7 @@ describe("ProposeAttributeRequestItemDVO with IdentityAttributeQuery", () => { const recipientMessage = await exchangeMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); await eventBus2.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await consumptionServices2.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -385,7 +385,7 @@ describe("ProposeAttributeRequestItemDVO with IdentityAttributeQuery", () => { expect(responseItem2.attributeId).toStrictEqual(surnameShareResult.value[0].id); expect(surname.value).toStrictEqual((responseItem2.attribute.content.value as SurnameJSON).value); - await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id!); await eventBus1.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.Completed); }); @@ -535,7 +535,7 @@ describe("AttributeSuccessionAcceptResponseItemDVO with IdentityAttributeQuery", const recipientMessage = await exchangeMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); await eventBus2.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await consumptionServices2.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -613,7 +613,7 @@ describe("AttributeSuccessionAcceptResponseItemDVO with IdentityAttributeQuery", expect(responseItem.successorId).toStrictEqual(successorResult.value[0].id); expect(successorName.value).toStrictEqual((responseItem.successor.content.value as GivenNameJSON).value); - await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id!); await eventBus1.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.Completed); }); @@ -745,7 +745,7 @@ describe("AttributeAlreadySharedAcceptResponseItemDVO with IdentityAttributeQuer const recipientMessage = await exchangeMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); await eventBus2.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await consumptionServices2.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -795,7 +795,7 @@ describe("AttributeAlreadySharedAcceptResponseItemDVO with IdentityAttributeQuer expect((responseItem.attribute.content.value as GivenNameJSON).value).toBe("Theodor"); expect(requestItemDVO.response).toStrictEqual(responseItem); - await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id!); await eventBus1.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.Completed); }); diff --git a/packages/runtime/test/dataViews/requestItems/ReadAttributeRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/ReadAttributeRequestItemDVO.test.ts index 204d6bcb0..0c2ce9172 100644 --- a/packages/runtime/test/dataViews/requestItems/ReadAttributeRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/ReadAttributeRequestItemDVO.test.ts @@ -106,7 +106,7 @@ describe("ReadAttributeRequestItemDVO with IdentityAttributeQuery", () => { test("check the MessageDVO for the sender", async () => { const senderMessage = await sendMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); - await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id); + await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id!); const dto = senderMessage; const dvo = (await expander1.expandMessageDTO(senderMessage)) as RequestMessageDVO; expect(dvo).toBeDefined(); @@ -174,7 +174,7 @@ describe("ReadAttributeRequestItemDVO with IdentityAttributeQuery", () => { const recipientMessage = await exchangeMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); await eventBus2.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await consumptionServices2.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -236,7 +236,7 @@ describe("ReadAttributeRequestItemDVO with IdentityAttributeQuery", () => { expect(responseItem.attributeId).toStrictEqual(attributeResult.value[0].id); expect(displayName.value).toStrictEqual((responseItem.attribute.content.value as GivenNameJSON).value); - await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id!); await eventBus1.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.Completed); }); @@ -342,7 +342,7 @@ describe("ReadAttributeRequestItemDVO with IQL and results", () => { test("check the MessageDVO for the sender", async () => { const senderMessage = await sendMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); - await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id); + await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id!); const dto = senderMessage; const dvo = (await expander1.expandMessageDTO(senderMessage)) as RequestMessageDVO; expect(dvo).toBeDefined(); @@ -405,7 +405,7 @@ describe("ReadAttributeRequestItemDVO with IQL and results", () => { const recipientMessage = await exchangeMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); await eventBus2.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await consumptionServices2.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -462,7 +462,7 @@ describe("ReadAttributeRequestItemDVO with IQL and results", () => { expect(responseItem.attributeId).toStrictEqual(attributeResult.value[0].id); expect(displayName.value).toStrictEqual((responseItem.attribute.content.value as GivenNameJSON).value); - await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id!); await eventBus1.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.Completed); }); @@ -577,7 +577,7 @@ describe("ReadAttributeRequestItemDVO with IQL and fallback", () => { test("check the MessageDVO for the sender", async () => { const senderMessage = await sendMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); - await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id); + await syncUntilHasMessageWithRequest(transportServices2, senderMessage.content.id!); const dto = senderMessage; const dvo = (await expander1.expandMessageDTO(senderMessage)) as RequestMessageDVO; expect(dvo).toBeDefined(); @@ -636,7 +636,7 @@ describe("ReadAttributeRequestItemDVO with IQL and fallback", () => { const recipientMessage = await exchangeMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); await eventBus2.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await consumptionServices2.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -693,7 +693,7 @@ describe("ReadAttributeRequestItemDVO with IQL and fallback", () => { expect(responseItem.attributeId).toStrictEqual(attributeResult.value[0].id); expect(displayName.value).toStrictEqual((responseItem.attribute.content.value as SurnameJSON).value); - await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id!); await eventBus1.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.Completed); }); @@ -829,7 +829,7 @@ describe("AttributeSuccessionAcceptResponseItemDVO with IdentityAttributeQuery", const recipientMessage = await exchangeMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); await eventBus2.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await consumptionServices2.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -907,7 +907,7 @@ describe("AttributeSuccessionAcceptResponseItemDVO with IdentityAttributeQuery", expect(responseItem.successorId).toStrictEqual(successorResult.value[0].id); expect(successorName.value).toStrictEqual((responseItem.successor.content.value as GivenNameJSON).value); - await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id!); await eventBus1.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.Completed); }); @@ -1050,7 +1050,7 @@ describe("AttributeAlreadySharedAcceptResponseItemDVO with IdentityAttributeQuer const recipientMessage = await exchangeMessageWithRequest(runtimeServices1, runtimeServices2, requestContent); await eventBus2.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await consumptionServices2.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -1100,7 +1100,7 @@ describe("AttributeAlreadySharedAcceptResponseItemDVO with IdentityAttributeQuer expect((responseItem.attribute.content.value as GivenNameJSON).value).toBe("Theodor"); expect(requestItemDVO.response).toStrictEqual(responseItem); - await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(transportServices1, recipientMessage.content.id!); await eventBus1.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.Completed); }); diff --git a/packages/runtime/test/dataViews/requestItems/ShareAttributeRequestItemDVO.test.ts b/packages/runtime/test/dataViews/requestItems/ShareAttributeRequestItemDVO.test.ts index 8bf79897a..b02f69f66 100644 --- a/packages/runtime/test/dataViews/requestItems/ShareAttributeRequestItemDVO.test.ts +++ b/packages/runtime/test/dataViews/requestItems/ShareAttributeRequestItemDVO.test.ts @@ -110,7 +110,7 @@ async function cleanupAttributes() { describe("ShareAttributeRequestItemDVO", () => { test("check the MessageDVO for the sender", async () => { const senderMessage = await sendMessageWithRequest(sRuntimeServices, rRuntimeServices, requestContent); - await syncUntilHasMessageWithRequest(rTransportServices, senderMessage.content.id); + await syncUntilHasMessageWithRequest(rTransportServices, senderMessage.content.id!); const dto = senderMessage; const dvo = (await sExpander.expandMessageDTO(senderMessage)) as RequestMessageDVO; expect(dvo).toBeDefined(); @@ -177,7 +177,7 @@ describe("ShareAttributeRequestItemDVO", () => { const recipientMessage = await exchangeMessageWithRequest(sRuntimeServices, rRuntimeServices, requestContent); await rEventBus.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); const acceptResult = await rConsumptionServices.incomingRequests.accept({ - requestId: recipientMessage.content.id, + requestId: recipientMessage.content.id!, items: responseItems }); expect(acceptResult).toBeSuccessful(); @@ -229,7 +229,7 @@ describe("ShareAttributeRequestItemDVO", () => { expect(attributeResult.value[0].id).toBeDefined(); expect((attributeResult.value[0].content.value as DisplayNameJSON).value).toBe("Dr. Theodor Munchkin von Reichenhardt"); - await syncUntilHasMessageWithResponse(sTransportServices, recipientMessage.content.id); + await syncUntilHasMessageWithResponse(sTransportServices, recipientMessage.content.id!); await sEventBus.waitForEvent(OutgoingRequestStatusChangedEvent); }); diff --git a/packages/runtime/test/lib/testUtils.ts b/packages/runtime/test/lib/testUtils.ts index 1bb6ed0df..12bdb22a5 100644 --- a/packages/runtime/test/lib/testUtils.ts +++ b/packages/runtime/test/lib/testUtils.ts @@ -7,16 +7,19 @@ import { LocalRequestStatus } from "@nmshd/consumption"; import { + ArbitraryRelationshipCreationContent, + ArbitraryRelationshipCreationContentJSON, + ArbitraryRelationshipTemplateContent, + ArbitraryRelationshipTemplateContentJSON, INotificationItem, - IRelationshipCreationContent, - IRelationshipTemplateContent, Notification, - RelationshipCreationContent, RelationshipCreationContentJSON, - RelationshipTemplateContent, RelationshipTemplateContentJSON, RequestItemGroupJSON, - RequestItemJSONDerivations + RequestItemJSONDerivations, + RequestJSON, + ResponseWrapperJSON, + ShareAttributeAcceptResponseItemJSON } from "@nmshd/content"; import { CoreBuffer } from "@nmshd/crypto"; import { CoreAddress, CoreId, IdentityUtil } from "@nmshd/transport"; @@ -34,6 +37,7 @@ import { IncomingRequestStatusChangedEvent, LocalAttributeDTO, LocalNotificationDTO, + MessageContentDerivation, MessageDTO, MessageSentEvent, NotifyPeerAboutRepositoryAttributeSuccessionRequest, @@ -108,12 +112,16 @@ export async function syncUntilHasMessage(transportServices: TransportServices, return await syncUntilHas(transportServices, "messages", (m) => m.id === messageId.toString()); } -export async function syncUntilHasMessageWithRequest(transportServices: TransportServices, requestId: string | CoreId): Promise { - return await syncUntilHas(transportServices, "messages", (m) => m.content["@type"] === "Request" && m.content.id === requestId.toString()); +export async function syncUntilHasMessageWithRequest(transportServices: TransportServices, requestId: string | CoreId): Promise { + return (await syncUntilHas(transportServices, "messages", (m) => m.content["@type"] === "Request" && m.content.id === requestId.toString())) as MessageDTO & { + content: RequestJSON; + }; } -export async function syncUntilHasMessageWithResponse(transportServices: TransportServices, requestId: string | CoreId): Promise { - return await syncUntilHas(transportServices, "messages", (m) => m.content["@type"] === "ResponseWrapper" && m.content.requestId === requestId.toString()); +export async function syncUntilHasMessageWithResponse(transportServices: TransportServices, requestId: string | CoreId): Promise { + return (await syncUntilHas(transportServices, "messages", (m) => m.content["@type"] === "ResponseWrapper" && m.content.requestId === requestId.toString())) as MessageDTO & { + content: ResponseWrapperJSON; + }; } export async function syncUntilHasMessageWithNotification(transportServices: TransportServices, notificationId: string | CoreId): Promise { @@ -172,11 +180,15 @@ export async function makeUploadRequest(values: object = {}): Promise { +export const emptyRelationshipTemplateContent: ArbitraryRelationshipTemplateContentJSON = ArbitraryRelationshipTemplateContent.from({ value: {} }).toJSON(); + +export const emptyRelationshipCreationContent: ArbitraryRelationshipCreationContentJSON = ArbitraryRelationshipCreationContent.from({ value: {} }).toJSON(); + +export async function createTemplate(transportServices: TransportServices, body?: RelationshipTemplateContentJSON): Promise { const response = await transportServices.relationshipTemplates.createOwnRelationshipTemplate({ maxNumberOfAllocations: 1, expiresAt: DateTime.utc().plus({ minutes: 10 }).toString(), - content: body + content: body ?? emptyRelationshipTemplateContent }); expect(response).toBeSuccessful(); @@ -196,7 +208,7 @@ export async function getFileToken(transportServices: TransportServices): Promis export async function exchangeTemplate( transportServicesCreator: TransportServices, transportServicesRecipient: TransportServices, - content: RelationshipTemplateContent | {} = {} + content?: RelationshipTemplateContentJSON ): Promise { const template = await createTemplate(transportServicesCreator, content); @@ -227,7 +239,7 @@ export async function exchangeToken(transportServicesCreator: TransportServices, return response.value; } -export async function sendMessage(transportServices: TransportServices, recipient: string, content?: any, attachments?: string[]): Promise { +export async function sendMessage(transportServices: TransportServices, recipient: string, content?: MessageContentDerivation, attachments?: string[]): Promise { const response = await transportServices.messages.sendMessage({ recipients: [recipient], content: content ?? { @@ -261,7 +273,11 @@ export async function sendMessageToMultipleRecipients(transportServices: Transpo return response.value; } -export async function sendMessageWithRequest(sender: TestRuntimeServices, recipient: TestRuntimeServices, request: CreateOutgoingRequestRequest): Promise { +export async function sendMessageWithRequest( + sender: TestRuntimeServices, + recipient: TestRuntimeServices, + request: CreateOutgoingRequestRequest +): Promise { const createRequestResult = await sender.consumption.outgoingRequests.create(request); expect(createRequestResult).toBeSuccessful(); const sendMessageResult = await sender.transport.messages.sendMessage({ @@ -270,7 +286,7 @@ export async function sendMessageWithRequest(sender: TestRuntimeServices, recipi }); expect(sendMessageResult).toBeSuccessful(); - return sendMessageResult.value; + return sendMessageResult.value as MessageDTO & { content: RequestJSON }; } export async function exchangeMessage(transportServicesCreator: TransportServices, transportServicesRecipient: TransportServices, attachments?: string[]): Promise { @@ -285,9 +301,13 @@ export async function exchangeMessage(transportServicesCreator: TransportService return message; } -export async function exchangeMessageWithRequest(sender: TestRuntimeServices, recipient: TestRuntimeServices, request: CreateOutgoingRequestRequest): Promise { +export async function exchangeMessageWithRequest( + sender: TestRuntimeServices, + recipient: TestRuntimeServices, + request: CreateOutgoingRequestRequest +): Promise { const sentMessage = await sendMessageWithRequest(sender, recipient, request); - return await syncUntilHasMessageWithRequest(recipient.transport, sentMessage.content.id); + return await syncUntilHasMessageWithRequest(recipient.transport, sentMessage.content.id!); } export async function exchangeMessageWithAttachment(transportServicesCreator: TransportServices, transportServicesRecipient: TransportServices): Promise { @@ -306,11 +326,11 @@ export async function getRelationship(transportServices: TransportServices): Pro } export async function establishRelationship(transportServices1: TransportServices, transportServices2: TransportServices): Promise { - const template = await exchangeTemplate(transportServices1, transportServices2, {}); + const template = await exchangeTemplate(transportServices1, transportServices2); const createRelationshipResponse = await transportServices2.relationships.createRelationship({ templateId: template.id, - creationContent: { a: "b" } + creationContent: emptyRelationshipCreationContent }); expect(createRelationshipResponse).toBeSuccessful(); @@ -330,14 +350,14 @@ export async function establishRelationship(transportServices1: TransportService export async function establishRelationshipWithContents( transportServices1: TransportServices, transportServices2: TransportServices, - templateContent: RelationshipTemplateContentJSON | RelationshipTemplateContent | IRelationshipTemplateContent, - creationContent: RelationshipCreationContentJSON | RelationshipCreationContent | IRelationshipCreationContent + templateContent?: RelationshipTemplateContentJSON, + creationContent?: RelationshipCreationContentJSON ): Promise { const template = await exchangeTemplate(transportServices1, transportServices2, templateContent); const createRelationshipResponse = await transportServices2.relationships.createRelationship({ templateId: template.id, - creationContent: creationContent + creationContent: creationContent ?? emptyRelationshipCreationContent }); expect(createRelationshipResponse).toBeSuccessful(); @@ -413,11 +433,11 @@ export async function ensurePendingRelationship(sTransportServices: TransportSer const rTransportServicesAddress = (await rTransportServices.account.getIdentityInfo()).value.address; const relationships = (await sTransportServices.relationships.getRelationships({ query: { peer: rTransportServicesAddress } })).value; if (relationships.length === 0) { - const template = await exchangeTemplate(sTransportServices, rTransportServices, {}); + const template = await exchangeTemplate(sTransportServices, rTransportServices); const createRelationshipResponse = await rTransportServices.relationships.createRelationship({ templateId: template.id, - creationContent: { a: "b" } + creationContent: emptyRelationshipCreationContent }); expect(createRelationshipResponse).toBeSuccessful(); @@ -513,7 +533,7 @@ export async function acceptIncomingShareAttributeRequest(sender: TestRuntimeSer await recipient.consumption.incomingRequests.accept({ requestId: requestId, items: [{ accept: true }] }); const responseMessage = await syncUntilHasMessageWithResponse(sender.transport, requestId); - const sharedAttributeId = responseMessage.content.response.items[0].attributeId; + const sharedAttributeId = (responseMessage.content.response.items[0] as ShareAttributeAcceptResponseItemJSON).attributeId; await sender.eventBus.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => { return e.data.request.id === requestId && e.data.newStatus === LocalRequestStatus.Completed; }); @@ -621,7 +641,7 @@ export async function executeFullRequestAndShareThirdPartyRelationshipAttributeF }); const responseMessage = await syncUntilHasMessageWithResponse(peer.transport, localRequest.id); - const sharedAttributeId = responseMessage.content.response.items[0].attributeId; + const sharedAttributeId = (responseMessage.content.response.items[0] as ShareAttributeAcceptResponseItemJSON).attributeId; await peer.eventBus.waitForEvent(OutgoingRequestStatusChangedEvent, (e) => { return e.data.request.id === localRequest.id && e.data.newStatus === LocalRequestStatus.Completed; }); diff --git a/packages/runtime/test/lib/testUtilsWithInactiveModules.ts b/packages/runtime/test/lib/testUtilsWithInactiveModules.ts index a0a009695..73415903a 100644 --- a/packages/runtime/test/lib/testUtilsWithInactiveModules.ts +++ b/packages/runtime/test/lib/testUtilsWithInactiveModules.ts @@ -1,4 +1,4 @@ -import { IResponse, RelationshipCreationContent } from "@nmshd/content"; +import { IResponse, RelationshipCreationContent, RelationshipTemplateContentJSON, ResponseWrapperJSON } from "@nmshd/content"; import { CreateOutgoingRequestRequest, LocalRequestDTO, MessageDTO, RelationshipDTO } from "src"; import { TestRuntimeServices } from "./RuntimeServiceProvider"; import { exchangeMessageWithRequest, exchangeTemplate, syncUntilHasMessageWithResponse } from "./testUtils"; @@ -9,8 +9,8 @@ export interface LocalRequestWithSource { } export interface ResponseMessagesForSenderAndRecipient { - rResponseMessage: MessageDTO; - sResponseMessage: MessageDTO; + rResponseMessage: MessageDTO & { content: ResponseWrapperJSON }; + sResponseMessage: MessageDTO & { content: ResponseWrapperJSON }; } export interface RelationshipRequestWithRelationshipIfAccepted { @@ -24,19 +24,19 @@ export async function exchangeMessageWithRequestAndRequireManualDecision( requestForCreate: CreateOutgoingRequestRequest ): Promise { const message = await exchangeMessageWithRequest(sRuntimeServices, rRuntimeServices, requestForCreate); - await sRuntimeServices.consumption.outgoingRequests.sent({ requestId: message.content.id, messageId: message.id }); + await sRuntimeServices.consumption.outgoingRequests.sent({ requestId: message.content.id!, messageId: message.id }); await rRuntimeServices.consumption.incomingRequests.received({ receivedRequest: message.content, requestSourceId: message.id }); await rRuntimeServices.consumption.incomingRequests.checkPrerequisites({ - requestId: message.content.id + requestId: message.content.id! }); return { request: ( await rRuntimeServices.consumption.incomingRequests.requireManualDecision({ - requestId: message.content.id + requestId: message.content.id! }) ).value, source: message.id @@ -70,7 +70,7 @@ export async function exchangeMessageWithRequestAndSendResponse( }, recipients: [(await sRuntimeServices.transport.account.getIdentityInfo()).value.address] }) - ).value; + ).value as MessageDTO & { content: ResponseWrapperJSON }; const sResponseMessage = await syncUntilHasMessageWithResponse(sRuntimeServices.transport, request.id); return { rResponseMessage, sResponseMessage }; } @@ -84,7 +84,7 @@ export async function exchangeTemplateAndReceiverRequiresManualDecision( const request = ( await rRuntimeServices.consumption.incomingRequests.received({ - receivedRequest: template.content.onNewRelationship, + receivedRequest: (template.content as RelationshipTemplateContentJSON).onNewRelationship, requestSourceId: template.id }) ).value; @@ -121,7 +121,7 @@ export async function exchangeTemplateAndReceiverSendsResponse( if (actionLowerCase !== "accept") return { request, relationship: undefined }; - const creationContent = RelationshipCreationContent.from({ response: decidedRequest.response!.content as unknown as IResponse }); + const creationContent = RelationshipCreationContent.from({ response: decidedRequest.response!.content as unknown as IResponse }).toJSON(); const result = await rRuntimeServices.transport.relationships.createRelationship({ creationContent, templateId }); expect(result).toBeSuccessful(); diff --git a/packages/runtime/test/modules/DeciderModule.test.ts b/packages/runtime/test/modules/DeciderModule.test.ts index 3fcdc29fb..c4a67a547 100644 --- a/packages/runtime/test/modules/DeciderModule.test.ts +++ b/packages/runtime/test/modules/DeciderModule.test.ts @@ -1,5 +1,5 @@ import { LocalRequestStatus } from "@nmshd/consumption"; -import { Request } from "@nmshd/content"; +import { RelationshipTemplateContent, Request } from "@nmshd/content"; import { CoreDate } from "@nmshd/transport"; import { ConsumptionServices, @@ -10,7 +10,7 @@ import { RelationshipTemplateProcessedResult, TransportServices } from "../../src"; -import { establishRelationship, exchangeMessage, MockEventBus, RuntimeServiceProvider, TestRequestItem } from "../lib"; +import { MockEventBus, RuntimeServiceProvider, TestRequestItem, establishRelationship, exchangeMessage } from "../lib"; const runtimeServiceProvider = new RuntimeServiceProvider(); let sTransportServices: TransportServices; @@ -72,7 +72,9 @@ describe("DeciderModule", () => { const request = Request.from({ items: [TestRequestItem.from({ mustBeAccepted: false })] }); const template = ( await sTransportServices.relationshipTemplates.createOwnRelationshipTemplate({ - content: request, + content: RelationshipTemplateContent.from({ + onNewRelationship: request + }).toJSON(), expiresAt: CoreDate.utc().add({ minutes: 5 }).toISOString() }) ).value; @@ -100,7 +102,9 @@ describe("DeciderModule", () => { const request = Request.from({ items: [TestRequestItem.from({ mustBeAccepted: false })] }); const template = ( await sTransportServices.relationshipTemplates.createOwnRelationshipTemplate({ - content: request, + content: RelationshipTemplateContent.from({ + onNewRelationship: request + }).toJSON(), expiresAt: CoreDate.utc().add({ minutes: 5 }).toISOString() }) ).value; diff --git a/packages/runtime/test/modules/RequestModule.test.ts b/packages/runtime/test/modules/RequestModule.test.ts index bf9f03394..c6ba1324e 100644 --- a/packages/runtime/test/modules/RequestModule.test.ts +++ b/packages/runtime/test/modules/RequestModule.test.ts @@ -5,10 +5,11 @@ import { RelationshipAttribute, RelationshipAttributeConfidentiality, RelationshipCreationContentJSON, - RelationshipTemplateContentJSON, + RelationshipTemplateContent, ResponseItemJSON, ResponseItemResult, - ResponseResult + ResponseResult, + ResponseWrapperJSON } from "@nmshd/content"; import { CoreAddress } from "@nmshd/transport"; import { @@ -68,11 +69,10 @@ describe("RequestModule", () => { let template: RelationshipTemplateDTO; const metadata = { aMetadataKey: "aMetadataValue" }; - const templateContent: RelationshipTemplateContentJSON = { - "@type": "RelationshipTemplateContent", + const templateContent = RelationshipTemplateContent.from({ onNewRelationship: { "@type": "Request", items: [{ "@type": "TestRequestItem", mustBeAccepted: false }] }, metadata - }; + }).toJSON(); async function getRequestIdOfTemplate(eventBus: MockEventBus, templateId: string) { let requests: LocalRequestDTO[]; @@ -137,11 +137,10 @@ describe("RequestModule", () => { test("triggers RelationshipTemplateProcessedEvent when another Template is loaded and a pending Relationship exists", async () => { const requestId = await getRequestIdOfTemplate(rRuntimeServices.eventBus, template.id); await rRuntimeServices.consumption.incomingRequests.accept({ requestId, items: [{ accept: true }] }); - const templateContent: RelationshipTemplateContentJSON = { - "@type": "RelationshipTemplateContent", + const templateContent = RelationshipTemplateContent.from({ onNewRelationship: { "@type": "Request", items: [{ "@type": "TestRequestItem", mustBeAccepted: false }] }, metadata - }; + }).toJSON(); await exchangeTemplate(sRuntimeServices.transport, rRuntimeServices.transport, templateContent); @@ -152,7 +151,7 @@ describe("RequestModule", () => { }); test("triggers RelationshipTemplateProcessedEvent if there is no request in the template", async () => { - await exchangeTemplate(sRuntimeServices.transport, rRuntimeServices.transport, {}); + await exchangeTemplate(sRuntimeServices.transport, rRuntimeServices.transport); await expect(rRuntimeServices.eventBus).toHavePublished(RelationshipTemplateProcessedEvent, (e) => e.data.result === RelationshipTemplateProcessedResult.NoRequest); }); @@ -184,9 +183,6 @@ describe("RequestModule", () => { const creationContent = relationship.creationContent as RelationshipCreationContentJSON; expect(creationContent["@type"]).toBe("RelationshipCreationContent"); - const creationChangeRequestContent = relationship.creationContent as RelationshipCreationContentJSON; - expect(creationChangeRequestContent["@type"]).toBe("RelationshipCreationContent"); - const response = creationContent.response; const responseItems = response.items; expect(responseItems).toHaveLength(1); @@ -276,7 +272,7 @@ describe("RequestModule", () => { await expect(rRuntimeServices.eventBus).toHavePublished( MessageSentEvent, - (e) => e.data.content?.response?.requestId === requestAfterReject.response!.content.requestId + (e) => (e.data.content as ResponseWrapperJSON).response.requestId === requestAfterReject.response!.content.requestId ); }); @@ -309,7 +305,7 @@ describe("RequestModule", () => { await expect(rRuntimeServices.eventBus).toHavePublished( MessageSentEvent, - (e) => e.data.content?.response?.requestId === requestAfterReject.response!.content.requestId + (e) => (e.data.content as ResponseWrapperJSON).response.requestId === requestAfterReject.response!.content.requestId ); }); @@ -330,8 +326,7 @@ describe("RequestModule", () => { }); async function exchangeRelationshipTemplate() { - const templateContent: RelationshipTemplateContentJSON = { - "@type": "RelationshipTemplateContent", + const templateContent = RelationshipTemplateContent.from({ onNewRelationship: { "@type": "Request", items: [{ "@type": "TestRequestItem", mustBeAccepted: false }] @@ -349,7 +344,7 @@ describe("RequestModule", () => { } ] } - }; + }).toJSON(); const template = await exchangeTemplate(sRuntimeServices.transport, rRuntimeServices.transport, templateContent); @@ -376,7 +371,7 @@ describe("RequestModule", () => { await sRuntimeServices.eventBus.waitForEvent(OutgoingRequestStatusChangedEvent, (event) => event.data.newStatus === LocalRequestStatus.Open); - const requestAfterAction = (await sRuntimeServices.consumption.outgoingRequests.getRequest({ id: message.content.id })).value; + const requestAfterAction = (await sRuntimeServices.consumption.outgoingRequests.getRequest({ id: message.content.id! })).value; expect(requestAfterAction.status).toBe(LocalRequestStatus.Open); }); @@ -403,12 +398,12 @@ describe("RequestModule", () => { expect(incomingRequestStatusChangedEvent.data.newStatus).toBe(LocalRequestStatus.DecisionRequired); - const requestsResult = await rRuntimeServices.consumption.incomingRequests.getRequest({ id: message.content.id }); + const requestsResult = await rRuntimeServices.consumption.incomingRequests.getRequest({ id: message.content.id! }); expect(requestsResult).toBeSuccessful(); }); test("triggers a MessageProcessedEvent when the incoming Message does not contain a Request", async () => { - await sendMessage(sRuntimeServices.transport, recipientAddress, {}); + await sendMessage(sRuntimeServices.transport, recipientAddress); await syncUntilHasMessages(rRuntimeServices.transport, 1); @@ -418,7 +413,7 @@ describe("RequestModule", () => { test("sends a message when the request is accepted", async () => { const message = await exchangeMessageWithRequest(sRuntimeServices, rRuntimeServices, requestContent); await rRuntimeServices.eventBus.waitForEvent(IncomingRequestStatusChangedEvent, (e) => e.data.newStatus === LocalRequestStatus.DecisionRequired); - const acceptRequestResult = await rRuntimeServices.consumption.incomingRequests.accept({ requestId: message.content.id, items: [{ accept: true }] }); + const acceptRequestResult = await rRuntimeServices.consumption.incomingRequests.accept({ requestId: message.content.id!, items: [{ accept: true }] }); expect(acceptRequestResult).toBeSuccessful(); const incomingRequestStatusChangedEvent = await rRuntimeServices.eventBus.waitForEvent( diff --git a/packages/runtime/test/transport/account.test.ts b/packages/runtime/test/transport/account.test.ts index f179855b5..49fe99aff 100644 --- a/packages/runtime/test/transport/account.test.ts +++ b/packages/runtime/test/transport/account.test.ts @@ -1,7 +1,7 @@ import { CoreDate } from "@nmshd/transport"; import { DateTime } from "luxon"; import { DeviceDTO, DeviceOnboardingInfoDTO, TransportServices } from "../../src"; -import { RuntimeServiceProvider, uploadFile } from "../lib"; +import { emptyRelationshipTemplateContent, RuntimeServiceProvider, uploadFile } from "../lib"; const serviceProvider = new RuntimeServiceProvider(); let sTransportServices: TransportServices; @@ -108,7 +108,7 @@ describe("LoadItemFromTruncatedReference", () => { beforeAll(async () => { const relationshipTemplate = ( await sTransportServices.relationshipTemplates.createOwnRelationshipTemplate({ - content: {}, + content: emptyRelationshipTemplateContent, expiresAt: CoreDate.utc().add({ days: 1 }).toISOString() }) ).value; diff --git a/packages/runtime/test/transport/messages.test.ts b/packages/runtime/test/transport/messages.test.ts index d0234a36c..17a77d5b8 100644 --- a/packages/runtime/test/transport/messages.test.ts +++ b/packages/runtime/test/transport/messages.test.ts @@ -1,5 +1,5 @@ import { ConsentRequestItemJSON } from "@nmshd/content"; -import { CoreDate, CoreId } from "@nmshd/transport"; +import { CoreDate } from "@nmshd/transport"; import { GetMessagesQuery, MessageReceivedEvent, MessageSentEvent, MessageWasReadAtChangedEvent } from "../../src"; import { QueryParamConditions, @@ -165,6 +165,14 @@ describe("Message errors", () => { expect(result).toBeAnError("Mail.to :: Value is not defined", "error.runtime.requestDeserialization"); }); + test("should throw correct error for false content type", async () => { + const result = await client1.transport.messages.sendMessage({ + recipients: [client2.address], + content: {} + }); + expect(result).toBeAnError("The content of a Message", "error.runtime.validation.invalidPropertyValue"); + }); + test("should throw correct error for missing Request ID in a Message with Request content", async () => { const result = await client1.transport.messages.sendMessage({ recipients: [client2.address], @@ -181,7 +189,7 @@ describe("Message errors", () => { recipients: [client2.address], content: { "@type": "Request", - id: CoreId.from("REQxxxxxxxxxxxxxxxxx"), + id: "REQxxxxxxxxxxxxxxxxx", items: [requestItem] } }); @@ -320,14 +328,8 @@ describe("Message query", () => { const relationshipToRecipient1 = await client1.transport.relationships.getRelationshipByAddress({ address: addressRecipient1 }); const relationshipToRecipient2 = await client1.transport.relationships.getRelationshipByAddress({ address: addressRecipient2 }); - await client1.transport.messages.sendMessage({ - content: {}, - recipients: [addressRecipient1] - }); - await client1.transport.messages.sendMessage({ - content: {}, - recipients: [addressRecipient2] - }); + await sendMessage(client1.transport, addressRecipient1); + await sendMessage(client1.transport, addressRecipient2); const messagesToRecipient1 = await client1.transport.messages.getMessages({ query: { "recipients.relationshipId": relationshipToRecipient1.value.id } }); const messagesToRecipient2 = await client1.transport.messages.getMessages({ query: { "recipients.relationshipId": relationshipToRecipient2.value.id } }); diff --git a/packages/runtime/test/transport/relationshipTemplates.test.ts b/packages/runtime/test/transport/relationshipTemplates.test.ts index d3a33baae..845181d98 100644 --- a/packages/runtime/test/transport/relationshipTemplates.test.ts +++ b/packages/runtime/test/transport/relationshipTemplates.test.ts @@ -1,6 +1,6 @@ import { DateTime } from "luxon"; import { GetRelationshipTemplatesQuery, OwnerRestriction, TransportServices } from "../../src"; -import { QueryParamConditions, RuntimeServiceProvider } from "../lib"; +import { emptyRelationshipTemplateContent, QueryParamConditions, RuntimeServiceProvider } from "../lib"; const serviceProvider = new RuntimeServiceProvider(); let transportServices1: TransportServices; @@ -18,7 +18,7 @@ describe("Template Tests", () => { const response = await transportServices1.relationshipTemplates.createOwnRelationshipTemplate({ maxNumberOfAllocations: 1, expiresAt: DateTime.utc().plus({ minutes: 10 }).toString(), - content: { a: "b" } + content: emptyRelationshipTemplateContent }); expect(response).toBeSuccessful(); @@ -26,7 +26,7 @@ describe("Template Tests", () => { test("create a template with undefined expiresAt", async () => { const response = await transportServices1.relationshipTemplates.createOwnRelationshipTemplate({ - content: { a: "A" }, + content: emptyRelationshipTemplateContent, expiresAt: undefined as unknown as string }); @@ -35,7 +35,7 @@ describe("Template Tests", () => { test("create a template with undefined maxNumberOfAllocations", async () => { const response = await transportServices1.relationshipTemplates.createOwnRelationshipTemplate({ - content: { a: "A" }, + content: emptyRelationshipTemplateContent, expiresAt: DateTime.utc().plus({ minutes: 1 }).toString() }); @@ -48,7 +48,7 @@ describe("Template Tests", () => { test("read a template with undefined maxNumberOfAllocations", async () => { const templateWithUndefinedMaxNumberOfAllocations = ( await transportServices1.relationshipTemplates.createOwnRelationshipTemplate({ - content: { a: "A" }, + content: emptyRelationshipTemplateContent, expiresAt: DateTime.utc().plus({ minutes: 1 }).toString() }) ).value; @@ -66,7 +66,7 @@ describe("Template Tests", () => { await transportServices1.relationshipTemplates.createOwnRelationshipTemplate({ maxNumberOfAllocations: 1, expiresAt: DateTime.utc().plus({ minutes: 10 }).toString(), - content: { a: "b" } + content: emptyRelationshipTemplateContent }) ).value; @@ -82,7 +82,7 @@ describe("Template Tests", () => { await transportServices1.relationshipTemplates.createOwnRelationshipTemplate({ maxNumberOfAllocations: 1, expiresAt: DateTime.utc().plus({ minutes: 10 }).toString(), - content: { a: "b" } + content: emptyRelationshipTemplateContent }) ).value; @@ -92,7 +92,7 @@ describe("Template Tests", () => { test("expect a validation error for sending maxNumberOfAllocations 0", async () => { const response = await transportServices1.relationshipTemplates.createOwnRelationshipTemplate({ - content: { a: "A" }, + content: emptyRelationshipTemplateContent, expiresAt: DateTime.utc().plus({ minutes: 1 }).toString(), maxNumberOfAllocations: 0 }); @@ -100,6 +100,16 @@ describe("Template Tests", () => { expect(response.isError).toBeTruthy(); expect(response.error.code).toBe("error.runtime.validation.invalidPropertyValue"); }); + + test("expect a validation error for sending a false template content type", async () => { + const response = await transportServices1.relationshipTemplates.createOwnRelationshipTemplate({ + content: {}, + expiresAt: DateTime.utc().plus({ minutes: 1 }).toString(), + maxNumberOfAllocations: 1 + }); + + expect(response).toBeAnError("The content of a RelationshipTemplate", "error.runtime.validation.invalidPropertyValue"); + }); }); describe("Serialization Errors", () => { @@ -129,7 +139,7 @@ describe("RelationshipTemplates query", () => { await transportServices1.relationshipTemplates.createOwnRelationshipTemplate({ maxNumberOfAllocations: 1, expiresAt: DateTime.utc().plus({ minutes: 10 }).toString(), - content: {} + content: emptyRelationshipTemplateContent }) ).value; const conditions = new QueryParamConditions(template, transportServices1) @@ -148,7 +158,7 @@ describe("RelationshipTemplates query", () => { await transportServices1.relationshipTemplates.createOwnRelationshipTemplate({ maxNumberOfAllocations: 1, expiresAt: DateTime.utc().plus({ minutes: 10 }).toString(), - content: {} + content: emptyRelationshipTemplateContent }) ).value; const conditions = new QueryParamConditions(template, transportServices1) @@ -165,7 +175,7 @@ describe("RelationshipTemplates query", () => { await transportServices1.relationshipTemplates.createOwnRelationshipTemplate({ maxNumberOfAllocations: 1, expiresAt: DateTime.utc().plus({ minutes: 10 }).toString(), - content: {} + content: emptyRelationshipTemplateContent }) ).value; const peerTemplate = (await transportServices2.relationshipTemplates.loadPeerRelationshipTemplate({ reference: createdTemplate.truncatedReference })).value; diff --git a/packages/runtime/test/transport/relationships.test.ts b/packages/runtime/test/transport/relationships.test.ts index f6396ac13..4780d397c 100644 --- a/packages/runtime/test/transport/relationships.test.ts +++ b/packages/runtime/test/transport/relationships.test.ts @@ -19,6 +19,7 @@ import { RuntimeServiceProvider, TestRuntimeServices, createTemplate, + emptyRelationshipCreationContent, ensureActiveRelationship, establishRelationship, exchangeMessageWithRequest, @@ -56,12 +57,22 @@ describe("Create Relationship", () => { expect(response).toBeSuccessful(); }); + test("should not create a relationship with a false creation content type", async () => { + const templateId = (await exchangeTemplate(services1.transport, services2.transport)).id; + + const createRelationshipResponse = await services2.transport.relationships.createRelationship({ + templateId: templateId, + creationContent: {} + }); + expect(createRelationshipResponse).toBeAnError("The creation content of a Relationship", "error.runtime.validation.invalidPropertyValue"); + }); + test("create pending relationship", async () => { const templateId = (await exchangeTemplate(services1.transport, services2.transport)).id; const createRelationshipResponse = await services2.transport.relationships.createRelationship({ templateId: templateId, - creationContent: { a: "b" } + creationContent: emptyRelationshipCreationContent }); expect(createRelationshipResponse).toBeSuccessful(); @@ -183,7 +194,7 @@ describe("Templator with active IdentityDeletionProcess", () => { const createRelationshipResponse = await services2.transport.relationships.createRelationship({ templateId: templateId, - creationContent: { a: "b" } + creationContent: emptyRelationshipCreationContent }); expect(createRelationshipResponse).toBeAnError( "The Identity who created the RelationshipTemplate is currently in the process of deleting itself. Thus, it is not possible to establish a Relationship to it.", diff --git a/packages/transport/package.json b/packages/transport/package.json index f9e36e64a..b33928e29 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.18", + "version": "5.0.0-alpha.19", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/transport/src/core/CoreSynchronizable.ts b/packages/transport/src/core/CoreSynchronizable.ts index faa3cda33..dca14a1a9 100644 --- a/packages/transport/src/core/CoreSynchronizable.ts +++ b/packages/transport/src/core/CoreSynchronizable.ts @@ -5,6 +5,7 @@ import { CoreId, ICoreId } from "./types/CoreId"; export interface ICoreSynchronizable extends ICoreSerializable { id: ICoreId; } + export abstract class CoreSynchronizable extends CoreSerializable implements ICoreSynchronizable { public readonly technicalProperties: string[] = []; public readonly userdataProperties: string[] = []; diff --git a/packages/transport/src/modules/relationshipTemplates/transmission/RelationshipTemplatePublicKey.ts b/packages/transport/src/modules/relationshipTemplates/transmission/RelationshipTemplatePublicKey.ts index 8072e519d..0674d5b90 100644 --- a/packages/transport/src/modules/relationshipTemplates/transmission/RelationshipTemplatePublicKey.ts +++ b/packages/transport/src/modules/relationshipTemplates/transmission/RelationshipTemplatePublicKey.ts @@ -5,6 +5,7 @@ import { CoreId, ICoreId } from "../../../core"; export interface IRelationshipTemplatePublicKey extends ICryptoExchangePublicKey { id: ICoreId; } + export interface IRelationshipTemplatePublicKeySerialized extends ICryptoExchangePublicKeySerialized, ISerialized { id: string; } diff --git a/packages/transport/src/modules/relationships/local/CachedRelationship.ts b/packages/transport/src/modules/relationships/local/CachedRelationship.ts index 142c5c808..f277b1b45 100644 --- a/packages/transport/src/modules/relationships/local/CachedRelationship.ts +++ b/packages/transport/src/modules/relationships/local/CachedRelationship.ts @@ -5,7 +5,7 @@ import { IRelationshipAuditLogEntry, RelationshipAuditLogEntry } from "./Relatio export interface ICachedRelationship extends ICoreSerializable { template: IRelationshipTemplate; - creationContent?: ISerializable; + creationContent: ISerializable; lastMessageSentAt?: ICoreDate; lastMessageReceivedAt?: ICoreDate; @@ -18,9 +18,9 @@ export class CachedRelationship extends CoreSerializable implements ICachedRelat @serialize() public template: RelationshipTemplate; - @validate({ nullable: true }) + @validate() @serialize() - public creationContent?: Serializable; + public creationContent: Serializable; @validate({ nullable: true }) @serialize() diff --git a/packages/transport/src/util/Random.ts b/packages/transport/src/util/Random.ts index 2fdf92b46..9290ac270 100644 --- a/packages/transport/src/util/Random.ts +++ b/packages/transport/src/util/Random.ts @@ -26,6 +26,7 @@ export interface RandomCharacterBucket { } export interface IRandom {} + export interface IRandomStatic { new (): IRandom; bytes(length: number): Promise; From bd2a968d02369d766c2ee62540186fb8810ae2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= Date: Mon, 12 Aug 2024 16:58:51 +0200 Subject: [PATCH 40/40] chore: remove alpha tag --- package-lock.json | 18 +++++++++--------- packages/app-runtime/package.json | 4 ++-- packages/consumption/package.json | 2 +- packages/content/package.json | 2 +- packages/runtime/package.json | 8 ++++---- packages/transport/package.json | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5e7634f50..1b67ccb53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10734,7 +10734,7 @@ }, "packages/app-runtime": { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.19", + "version": "5.0.0", "license": "MIT", "dependencies": { "@js-soft/docdb-access-loki": "^1.1.0", @@ -10748,12 +10748,12 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.19" + "@nmshd/runtime": "^5.0.0" } }, "packages/consumption": { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.19", + "version": "5.0.0", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", @@ -10775,7 +10775,7 @@ }, "packages/content": { "name": "@nmshd/content", - "version": "5.0.0-alpha.19", + "version": "5.0.0", "license": "MIT", "dependencies": { "@js-soft/logging-abstractions": "^1.0.1", @@ -10800,17 +10800,17 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.19", + "version": "5.0.0", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.19", - "@nmshd/content": "5.0.0-alpha.19", + "@nmshd/consumption": "5.0.0", + "@nmshd/content": "5.0.0", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.19", + "@nmshd/transport": "5.0.0", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -10842,7 +10842,7 @@ }, "packages/transport": { "name": "@nmshd/transport", - "version": "5.0.0-alpha.19", + "version": "5.0.0", "license": "MIT", "dependencies": { "@js-soft/docdb-access-abstractions": "1.0.4", diff --git a/packages/app-runtime/package.json b/packages/app-runtime/package.json index e35582463..7b41a8075 100644 --- a/packages/app-runtime/package.json +++ b/packages/app-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/app-runtime", - "version": "5.0.0-alpha.19", + "version": "5.0.0", "description": "The App Runtime", "homepage": "https://enmeshed.eu", "repository": { @@ -63,7 +63,7 @@ "@types/luxon": "^3.4.2" }, "peerDependencies": { - "@nmshd/runtime": "^5.0.0-alpha.19" + "@nmshd/runtime": "^5.0.0" }, "publishConfig": { "access": "public", diff --git a/packages/consumption/package.json b/packages/consumption/package.json index c8a3761dc..58eb0ccb6 100644 --- a/packages/consumption/package.json +++ b/packages/consumption/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/consumption", - "version": "5.0.0-alpha.19", + "version": "5.0.0", "description": "The consumption library extends the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/content/package.json b/packages/content/package.json index 9346eaa9e..a0735523d 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/content", - "version": "5.0.0-alpha.19", + "version": "5.0.0", "description": "The content library defines data structures that can be transmitted using the transport library.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 6a3d6b19a..f4bfdf8c9 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "5.0.0-alpha.19", + "version": "5.0.0", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { @@ -65,10 +65,10 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.10", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "5.0.0-alpha.19", - "@nmshd/content": "5.0.0-alpha.19", + "@nmshd/consumption": "5.0.0", + "@nmshd/content": "5.0.0", "@nmshd/crypto": "2.0.6", - "@nmshd/transport": "5.0.0-alpha.19", + "@nmshd/transport": "5.0.0", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", diff --git a/packages/transport/package.json b/packages/transport/package.json index b33928e29..82deef5ff 100644 --- a/packages/transport/package.json +++ b/packages/transport/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/transport", - "version": "5.0.0-alpha.19", + "version": "5.0.0", "description": "The transport library handles backbone communication and content encryption.", "homepage": "https://enmeshed.eu", "repository": {