Skip to content

Commit

Permalink
Notifying about RepositoryAttribute succession isn't possible if addi…
Browse files Browse the repository at this point in the history
…tional own shared IdentityAttribute with deletionInfo exsists (#439)

* feat: check for Attribute without deletionInfo trying to succeed own shared IdentityAttribute

* feat: filter for sent deletion Requests

* test: happy path

* feat: adjust error

* feat: allow to succeed Attributes with deletionStatus DeletionRequestSent

* refactor: use find()

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
Milena-Czierlinski and mergify[bot] authored Feb 28, 2025
1 parent ebded0a commit 23b0ae3
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,13 @@ export class AttributesController extends ConsumptionBaseController {
return ValidationResult.error(ConsumptionCoreErrors.attributes.cannotSucceedChildOfComplexAttribute(predecessorId.toString()));
}

if (predecessor.hasDeletionInfo() && predecessor.deletionInfo.deletionStatus !== LocalAttributeDeletionStatus.DeletionRequestRejected) {
if (
predecessor.hasDeletionInfo() &&
!(
predecessor.deletionInfo.deletionStatus === LocalAttributeDeletionStatus.DeletionRequestRejected ||
predecessor.deletionInfo.deletionStatus === LocalAttributeDeletionStatus.DeletionRequestSent
)
) {
return ValidationResult.error(ConsumptionCoreErrors.attributes.cannotSucceedAttributesWithDeletionInfo());
}

Expand Down
7 changes: 7 additions & 0 deletions packages/runtime/src/useCases/common/RuntimeErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,13 @@ class Attributes {
);
}

public cannotSucceedAttributesWithDeletionInfo(ownSharedIdentityAttributeIds: CoreId[] | string[]): ApplicationError {
return new ApplicationError(
"error.runtime.attributes.cannotSucceedAttributesWithDeletionInfo",
`The own shared IdentityAttribute predecessor(s) ${ownSharedIdentityAttributeIds.map((ownSharedIdentityAttributeId) => `'${ownSharedIdentityAttributeId.toString()}'`).join(", ")} can't be succeeded due to set deletionInfo.`
);
}

public cannotSeparatelyDeleteChildOfComplexAttribute(attributeId: CoreId | string): ApplicationError {
return new ApplicationError(
"error.runtime.attributes.cannotSeparatelyDeleteChildOfComplexAttribute",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Result } from "@js-soft/ts-utils";
import { AttributesController, ConsumptionIds, IAttributeSuccessorParams, LocalAttribute } from "@nmshd/consumption";
import { AttributesController, ConsumptionIds, IAttributeSuccessorParams, LocalAttribute, LocalAttributeDeletionStatus } from "@nmshd/consumption";
import { Notification, PeerSharedAttributeSucceededNotificationItem } from "@nmshd/content";
import { CoreAddress, CoreId } from "@nmshd/core-types";
import { AccountController, MessageController } from "@nmshd/transport";
Expand Down Expand Up @@ -54,11 +54,21 @@ export class NotifyPeerAboutRepositoryAttributeSuccessionUseCase extends UseCase
return Result.fail(RuntimeErrors.attributes.noPreviousVersionOfRepositoryAttributeHasBeenSharedWithPeerBefore(repositoryAttributeSuccessorId, request.peer));
}

if (candidatePredecessors[0].shareInfo?.sourceAttribute?.toString() === request.attributeId) {
return Result.fail(RuntimeErrors.attributes.repositoryAttributeHasAlreadyBeenSharedWithPeer(request.attributeId, request.peer, candidatePredecessors[0].id));
const ownSharedIdentityAttributePredecessor = candidatePredecessors.find(
(attribute) =>
attribute.deletionInfo?.deletionStatus !== LocalAttributeDeletionStatus.DeletedByPeer &&
attribute.deletionInfo?.deletionStatus !== LocalAttributeDeletionStatus.ToBeDeletedByPeer
);

if (!ownSharedIdentityAttributePredecessor) {
return Result.fail(RuntimeErrors.attributes.cannotSucceedAttributesWithDeletionInfo(candidatePredecessors.map((attribute) => attribute.id)));
}

const ownSharedIdentityAttributePredecessor = candidatePredecessors[0];
if (ownSharedIdentityAttributePredecessor.shareInfo?.sourceAttribute?.toString() === request.attributeId) {
return Result.fail(
RuntimeErrors.attributes.repositoryAttributeHasAlreadyBeenSharedWithPeer(request.attributeId, request.peer, ownSharedIdentityAttributePredecessor.id)
);
}

const notificationId = await ConsumptionIds.notification.generate();
const successorParams: IAttributeSuccessorParams = {
Expand Down
27 changes: 26 additions & 1 deletion packages/runtime/test/consumption/attributes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1696,6 +1696,31 @@ describe(NotifyPeerAboutRepositoryAttributeSuccessionUseCase.name, () => {
expect(ownSharedIdentityAttributeVersion2.succeeds).toStrictEqual(ownSharedIdentityAttributeVersion0.id);
});

test("should allow to notify about successor if the predecessor was deleted by peer but additional predecessor exists", async () => {
const deleteResult = await services2.consumption.attributes.deletePeerSharedAttributeAndNotifyOwner({ attributeId: ownSharedIdentityAttributeVersion0.id });
const notificationId = deleteResult.value.notificationId!;

await syncUntilHasMessageWithNotification(services1.transport, notificationId);
await services1.eventBus.waitForEvent(PeerSharedAttributeDeletedByPeerEvent, (e) => {
return e.data.id === ownSharedIdentityAttributeVersion0.id;
});
const updatedOwnSharedIdentityAttribute = (await services1.consumption.attributes.getAttribute({ id: ownSharedIdentityAttributeVersion0.id })).value;
expect(updatedOwnSharedIdentityAttribute.deletionInfo?.deletionStatus).toStrictEqual(LocalAttributeDeletionStatus.DeletedByPeer);

const ownSharedIdentityAttributeVersion0WithoutDeletionInfo = await executeFullShareRepositoryAttributeFlow(
services1,
services2,
ownSharedIdentityAttributeVersion0.shareInfo!.sourceAttribute!
);

const result = await services1.consumption.attributes.notifyPeerAboutRepositoryAttributeSuccession({
attributeId: repositoryAttributeVersion2.id,
peer: services2.address
});
expect(result).toBeSuccessful();
expect(result.value.predecessor.id).toBe(ownSharedIdentityAttributeVersion0WithoutDeletionInfo.id);
});

test("should throw if the predecessor repository attribute was deleted", async () => {
const repositoryAttributeVersion0 = (await services1.consumption.attributes.getAttribute({ id: ownSharedIdentityAttributeVersion0.shareInfo!.sourceAttribute! })).value;
await services1.consumption.attributes.deleteRepositoryAttribute({ attributeId: repositoryAttributeVersion0.id });
Expand Down Expand Up @@ -1735,7 +1760,7 @@ describe(NotifyPeerAboutRepositoryAttributeSuccessionUseCase.name, () => {
attributeId: repositoryAttributeVersion2.id,
peer: services2.address
});
expect(notificationResult).toBeAnError(/.*/, "error.consumption.attributes.cannotSucceedAttributesWithDeletionInfo");
expect(notificationResult).toBeAnError(/.*/, "error.runtime.attributes.cannotSucceedAttributesWithDeletionInfo");
});

test("should throw if the same version of the attribute has been notified about already", async () => {
Expand Down

0 comments on commit 23b0ae3

Please sign in to comment.