diff --git a/packages/consumption/src/modules/attributes/AttributesController.ts b/packages/consumption/src/modules/attributes/AttributesController.ts index 21cf31fde..a481a4384 100644 --- a/packages/consumption/src/modules/attributes/AttributesController.ts +++ b/packages/consumption/src/modules/attributes/AttributesController.ts @@ -1250,11 +1250,11 @@ export class AttributesController extends ConsumptionBaseController { return false; } - public async getSharedVersionsOfAttribute(id: CoreId, peers?: CoreAddress[], onlyLatestVersions = true): Promise { + public async getSharedVersionsOfAttribute(id: CoreId, peers?: CoreAddress[], onlyLatestVersions = true, query: any = {}): Promise { const sourceAttribute = await this.getLocalAttribute(id); if (!sourceAttribute) throw TransportCoreErrors.general.recordNotFound(LocalAttribute, id.toString()); - const query: any = { "shareInfo.sourceAttribute": sourceAttribute.id.toString() }; + query["shareInfo.sourceAttribute"] = sourceAttribute.id.toString(); if (peers) query["shareInfo.peer"] = { $in: peers.map((address) => address.toString()) }; if (onlyLatestVersions) query["succeededBy"] = { $exists: false }; diff --git a/packages/consumption/src/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.ts index c2d48393c..395ddf2df 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.ts @@ -11,7 +11,7 @@ import { } from "@nmshd/content"; import { CoreAddress } from "@nmshd/core-types"; import { ConsumptionCoreErrors } from "../../../../consumption/ConsumptionCoreErrors"; -import { AttributeSuccessorParams, LocalAttribute, LocalAttributeShareInfo, PeerSharedAttributeSucceededEvent } from "../../../attributes"; +import { AttributeSuccessorParams, LocalAttribute, LocalAttributeDeletionStatus, LocalAttributeShareInfo, PeerSharedAttributeSucceededEvent } from "../../../attributes"; import { ValidationResult } from "../../../common/ValidationResult"; import { AcceptRequestItemParametersJSON } from "../../incoming/decide/AcceptRequestItemParameters"; import { GenericRequestItemProcessor } from "../GenericRequestItemProcessor"; @@ -124,7 +124,12 @@ export class CreateAttributeRequestItemProcessor extends GenericRequestItemProce const repositoryAttribute = await this.getSourceRepositoryAttribute(requestItem.attribute); - const latestSharedVersions = await this.consumptionController.attributes.getSharedVersionsOfAttribute(repositoryAttribute.id, [requestInfo.peer]); + const query = { + "deletionInfo.deletionStatus": { + $nin: [LocalAttributeDeletionStatus.DeletedByPeer, LocalAttributeDeletionStatus.ToBeDeletedByPeer] + } + }; + const latestSharedVersions = await this.consumptionController.attributes.getSharedVersionsOfAttribute(repositoryAttribute.id, [requestInfo.peer], true, query); const latestSharedVersion = latestSharedVersions.length > 0 ? latestSharedVersions[0] : undefined; if (!latestSharedVersion) { diff --git a/packages/consumption/src/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.ts index 9341b23be..b6dda1cb6 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.ts @@ -212,18 +212,23 @@ export class ProposeAttributeRequestItemProcessor extends GenericRequestItemProc const existingSourceAttribute = await this.consumptionController.attributes.getLocalAttribute(parsedParams.attributeId); if (!existingSourceAttribute) throw TransportCoreErrors.general.recordNotFound(LocalAttribute, parsedParams.attributeId.toString()); - const latestSharedVersion = await this.consumptionController.attributes.getSharedVersionsOfAttribute(parsedParams.attributeId, [requestInfo.peer], true); + const query = { + "deletionInfo.deletionStatus": { + $nin: [ + LocalAttributeDeletionStatus.DeletedByPeer, + LocalAttributeDeletionStatus.DeletedByOwner, + LocalAttributeDeletionStatus.ToBeDeletedByPeer, + LocalAttributeDeletionStatus.ToBeDeleted + ] + } + }; + const latestSharedVersion = await this.consumptionController.attributes.getSharedVersionsOfAttribute(parsedParams.attributeId, [requestInfo.peer], true, query); const wasSharedBefore = latestSharedVersion.length > 0; - const wasDeletedByPeerOrOwner = - latestSharedVersion[0]?.deletionInfo?.deletionStatus === LocalAttributeDeletionStatus.DeletedByPeer || - latestSharedVersion[0]?.deletionInfo?.deletionStatus === LocalAttributeDeletionStatus.DeletedByOwner || - latestSharedVersion[0]?.deletionInfo?.deletionStatus === LocalAttributeDeletionStatus.ToBeDeletedByPeer || - latestSharedVersion[0]?.deletionInfo?.deletionStatus === LocalAttributeDeletionStatus.ToBeDeleted; const isLatestSharedVersion = latestSharedVersion[0]?.shareInfo?.sourceAttribute?.toString() === existingSourceAttribute.id.toString(); const predecessorWasSharedBefore = wasSharedBefore && !isLatestSharedVersion; - if (!wasSharedBefore || wasDeletedByPeerOrOwner) { + if (!wasSharedBefore) { sharedLocalAttribute = await this.consumptionController.attributes.createSharedLocalAttributeCopy({ sourceAttributeId: CoreId.from(existingSourceAttribute.id), peer: CoreAddress.from(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 d19673ae0..b02785b8f 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.ts @@ -298,18 +298,23 @@ export class ReadAttributeRequestItemProcessor extends GenericRequestItemProcess existingSourceAttribute = attributesAfterSuccession.successor; } - const latestSharedVersion = await this.consumptionController.attributes.getSharedVersionsOfAttribute(parsedParams.existingAttributeId, [requestInfo.peer], true); + const query = { + "deletionInfo.deletionStatus": { + $nin: [ + LocalAttributeDeletionStatus.DeletedByPeer, + LocalAttributeDeletionStatus.DeletedByOwner, + LocalAttributeDeletionStatus.ToBeDeletedByPeer, + LocalAttributeDeletionStatus.ToBeDeleted + ] + } + }; + const latestSharedVersion = await this.consumptionController.attributes.getSharedVersionsOfAttribute(parsedParams.existingAttributeId, [requestInfo.peer], true, query); const wasSharedBefore = latestSharedVersion.length > 0; - const wasDeletedByPeerOrOwner = - latestSharedVersion[0]?.deletionInfo?.deletionStatus === LocalAttributeDeletionStatus.DeletedByPeer || - latestSharedVersion[0]?.deletionInfo?.deletionStatus === LocalAttributeDeletionStatus.DeletedByOwner || - latestSharedVersion[0]?.deletionInfo?.deletionStatus === LocalAttributeDeletionStatus.ToBeDeletedByPeer || - latestSharedVersion[0]?.deletionInfo?.deletionStatus === LocalAttributeDeletionStatus.ToBeDeleted; const isLatestSharedVersion = latestSharedVersion[0]?.shareInfo?.sourceAttribute?.toString() === existingSourceAttribute.id.toString(); const predecessorWasSharedBefore = wasSharedBefore && !isLatestSharedVersion; - if (!wasSharedBefore || wasDeletedByPeerOrOwner) { + if (!wasSharedBefore) { sharedLocalAttribute = await this.consumptionController.attributes.createSharedLocalAttributeCopy({ sourceAttributeId: CoreId.from(existingSourceAttribute.id), peer: CoreAddress.from(requestInfo.peer), diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index be5f90689..bd67e5dff 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -3136,6 +3136,15 @@ describe("AttributesController", function () { } }); + test("should return all shared versions that match query", async function () { + const query = { "content.value.value": "US" }; + const ownSharedAttributeVersionsWithValueMatchingQuery = [ownSharedIdentityAttributeV1PeerA, ownSharedIdentityAttributeV1PeerB]; + + const result = await consumptionController.attributes.getSharedVersionsOfAttribute(repositoryAttributeV2.id, undefined, false, query); + expect(result).toHaveLength(2); + expect(result).toStrictEqual(expect.arrayContaining(ownSharedAttributeVersionsWithValueMatchingQuery)); + }); + test("should return all shared versions for a single peer", async function () { const allRepositoryAttributeVersions = [repositoryAttributeV0, repositoryAttributeV1, repositoryAttributeV2]; const allOwnSharedAttributeVersionsPeerB = [ownSharedIdentityAttributeV2PeerB, ownSharedIdentityAttributeV1PeerB]; diff --git a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts index cc621b166..24e9cbe22 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts @@ -9,13 +9,14 @@ import { RelationshipAttribute, ResponseItemResult } from "@nmshd/content"; -import { CoreAddress, CoreDate, CoreId } from "@nmshd/core-types"; +import { CoreAddress, CoreDate, CoreId, ICoreDate, ICoreId } from "@nmshd/core-types"; import { AccountController, Transport } from "@nmshd/transport"; import { ConsumptionController, ConsumptionIds, CreateAttributeRequestItemProcessor, IAttributeSuccessorParams, + ILocalAttribute, LocalAttribute, LocalAttributeDeletionInfo, LocalAttributeDeletionStatus, @@ -187,6 +188,11 @@ export class GivenSteps { }); return createdPeerSharedIdentityAttribute; } + + public async anAttribute(attributeData: Omit & { id?: ICoreId; createdAt?: ICoreDate }): Promise { + const createdAttribute = await this.context.consumptionController.attributes.createAttributeUnsafe(attributeData); + return createdAttribute; + } } export class ThenSteps { @@ -232,7 +238,7 @@ export class ThenSteps { expect(createdRepositoryAttribute!.shareInfo).toBeUndefined(); } - public async anOwnSharedIdentityAttributeIsCreated(): Promise { + public async anOwnSharedIdentityAttributeIsCreated(sourceAttribute?: CoreId): Promise { expect((this.context.responseItemAfterAction as CreateAttributeAcceptResponseItem).attributeId).toBeDefined(); const createdAttribute = await this.context.consumptionController.attributes.getLocalAttribute( @@ -243,6 +249,10 @@ export class ThenSteps { expect(createdAttribute!.shareInfo).toBeDefined(); expect(createdAttribute!.shareInfo!.peer.toString()).toStrictEqual(this.context.peerAddress.toString()); expect(createdAttribute!.shareInfo!.sourceAttribute).toBeDefined(); + + if (sourceAttribute) { + expect(createdAttribute!.shareInfo!.sourceAttribute!.toString()).toStrictEqual(sourceAttribute.toString()); + } } public async anOwnSharedRelationshipAttributeIsCreated(): Promise { 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 7add8a89a..061e5da31 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts @@ -1,7 +1,8 @@ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; import { GivenName, ProprietaryInteger, ProprietaryString } from "@nmshd/content"; -import { CoreAddress } from "@nmshd/core-types"; +import { CoreAddress, CoreDate, CoreId } from "@nmshd/core-types"; import { Transport } from "@nmshd/transport"; +import { LocalAttributeDeletionStatus } from "../../../../../src"; import { TestUtil } from "../../../../core/TestUtil"; import { TestObjectFactory } from "../../testHelpers/TestObjectFactory"; import { Context, GivenSteps, ThenSteps, WhenSteps } from "./Context"; @@ -360,6 +361,32 @@ describe("CreateAttributeRequestItemProcessor", function () { await Then.theIdOfTheAlreadySharedAttributeMatches(ownSharedIdentityAttribute.id); }); + test("in case of an IdentityAttribute that already exists as own shared IdentityAttribute but is deleted by peer: creates a new own shared IdentityAttribute", async function () { + const repositoryAttribute = await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aGivenName") }); + await Given.anAttribute({ + content: repositoryAttribute.content, + shareInfo: { sourceAttribute: repositoryAttribute.id, peer: TestIdentity.PEER, requestReference: CoreId.from("reqRef") }, + deletionInfo: { deletionStatus: LocalAttributeDeletionStatus.DeletedByPeer, deletionDate: CoreDate.utc().subtract({ days: 1 }) } + }); + await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aGivenName") }); + await When.iCallAccept(); + await Then.theResponseItemShouldBeOfType("CreateAttributeAcceptResponseItem"); + await Then.anOwnSharedIdentityAttributeIsCreated(repositoryAttribute.id); + }); + + test("in case of an IdentityAttribute that already exists as own shared IdentityAttribute but is to be deleted by peer: creates a new own shared IdentityAttribute", async function () { + const repositoryAttribute = await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aGivenName") }); + await Given.anAttribute({ + content: repositoryAttribute.content, + shareInfo: { sourceAttribute: repositoryAttribute.id, peer: TestIdentity.PEER, requestReference: CoreId.from("reqRef") }, + deletionInfo: { deletionStatus: LocalAttributeDeletionStatus.ToBeDeletedByPeer, deletionDate: CoreDate.utc().add({ days: 1 }) } + }); + await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aGivenName") }); + await When.iCallAccept(); + await Then.theResponseItemShouldBeOfType("CreateAttributeAcceptResponseItem"); + await Then.anOwnSharedIdentityAttributeIsCreated(repositoryAttribute.id); + }); + test("in case of an IdentityAttribute that already exists as own shared IdentityAttribute with different tags: returns an AttributeSuccessionAcceptResponseItem", async function () { const repositoryAttribute = await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY,