Skip to content

Commit

Permalink
Accepting a CreateAttributeRequestItem sends an AttributeAlreadyShare…
Browse files Browse the repository at this point in the history
…dAcceptResponseItem if already shared Attribute was deleted by peer (#441)

* feat: add query to getSharedVersionsOfAttribute

* refactor: use query in ReadAttributeRequestItemProcessor

* feat: mind deletionInfo in CreateAttributeRequestItemProcessor

* test: CreateAttributeRequestItem with existing Attributes with deletionInfo

* test: change test value

* test: adjust name

* fix: handle case that no query is passed

* feat: pr comments

* feat: pr comments

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
Milena-Czierlinski and mergify[bot] authored Mar 6, 2025
1 parent 1b27368 commit 7f9649d
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1250,11 +1250,11 @@ export class AttributesController extends ConsumptionBaseController {
return false;
}

public async getSharedVersionsOfAttribute(id: CoreId, peers?: CoreAddress[], onlyLatestVersions = true): Promise<LocalAttribute[]> {
public async getSharedVersionsOfAttribute(id: CoreId, peers?: CoreAddress[], onlyLatestVersions = true, query: any = {}): Promise<LocalAttribute[]> {
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 };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -187,6 +188,11 @@ export class GivenSteps {
});
return createdPeerSharedIdentityAttribute;
}

public async anAttribute(attributeData: Omit<ILocalAttribute, "id" | "createdAt"> & { id?: ICoreId; createdAt?: ICoreDate }): Promise<LocalAttribute> {
const createdAttribute = await this.context.consumptionController.attributes.createAttributeUnsafe(attributeData);
return createdAttribute;
}
}

export class ThenSteps {
Expand Down Expand Up @@ -232,7 +238,7 @@ export class ThenSteps {
expect(createdRepositoryAttribute!.shareInfo).toBeUndefined();
}

public async anOwnSharedIdentityAttributeIsCreated(): Promise<void> {
public async anOwnSharedIdentityAttributeIsCreated(sourceAttribute?: CoreId): Promise<void> {
expect((this.context.responseItemAfterAction as CreateAttributeAcceptResponseItem).attributeId).toBeDefined();

const createdAttribute = await this.context.consumptionController.attributes.getLocalAttribute(
Expand All @@ -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<void> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 7f9649d

Please sign in to comment.