Skip to content

Commit 2d129ee

Browse files
Consumer Api: Decompose Relationships during Identity Deletion (#940)
* feat: Add relationship audit log reason for decomposition due to identity deletion * feat: Make relationship participants writable internally and write skeleton decomposition method * chore: Rename DeleteRelationshipsOfIdentityCommand to DecomposeRelationshipsOfIdentityCommand * feat: Make CreatedBy property in Relationship Audit Log nullable * chore: Clear cache when deleting Relationships and Relationship Templates * fix: Include every properties when querying for relationships * feat: Add method to decompose relationship due to identity deletion * feat: Add methods and command for anonymizing a relationship * feat: Use the decomposition and anonymization commands * chore: Rename tests, methods and variables, resolve pr comments * chore: Merge Decomposition and Anonymization into one command * chore: Remove cache clearing from repositories * chore: Write unit tests for Relationship Decomposition due to Identity Deletion and Anonymization * chore: Include Anonymization method in the Decomposition, remove unnecessary tests * chore: Add unit test for anonymization# * fix: Fix formatting --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 3e6d810 commit 2d129ee

22 files changed

+869
-95
lines changed

Modules/Relationships/src/Relationships.Application/Identities/IdentityDeleter.Tests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using Backbone.BuildingBlocks.Application.Identities;
2-
using Backbone.Modules.Relationships.Application.Relationships.Commands.DeleteRelationshipsOfIdentity;
2+
using Backbone.Modules.Relationships.Application.Relationships.Commands.DecomposeAndAnonymizeRelationshipsOfIdentity;
33
using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.AnonymizeRelationshipTemplateAllocationsAllocatedByIdentity;
44
using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.DeleteRelationshipTemplatesOfIdentity;
55
using FakeItEasy;
@@ -23,7 +23,7 @@ public async Task Deleter_calls_correct_command()
2323

2424
// Assert
2525
A.CallTo(() => mockMediator.Send(
26-
A<DeleteRelationshipsOfIdentityCommand>.That.Matches(i => i.IdentityAddress == identityAddress),
26+
A<DecomposeAndAnonymizeRelationshipsOfIdentityCommand>.That.Matches(i => i.IdentityAddress == identityAddress),
2727
A<CancellationToken>._)).MustHaveHappenedOnceExactly();
2828
A.CallTo(() => mockMediator.Send(
2929
A<DeleteRelationshipTemplatesOfIdentityCommand>.That.Matches(i => i.IdentityAddress == identityAddress),

Modules/Relationships/src/Relationships.Application/Identities/IdentityDeleter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using Backbone.BuildingBlocks.Application.Identities;
22
using Backbone.DevelopmentKit.Identity.ValueObjects;
3-
using Backbone.Modules.Relationships.Application.Relationships.Commands.DeleteRelationshipsOfIdentity;
3+
using Backbone.Modules.Relationships.Application.Relationships.Commands.DecomposeAndAnonymizeRelationshipsOfIdentity;
44
using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.AnonymizeRelationshipTemplateAllocationsAllocatedByIdentity;
55
using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.DeleteRelationshipTemplatesOfIdentity;
66
using MediatR;
@@ -20,7 +20,7 @@ public IdentityDeleter(IMediator mediator, IDeletionProcessLogger deletionProces
2020

2121
public async Task Delete(IdentityAddress identityAddress)
2222
{
23-
await _mediator.Send(new DeleteRelationshipsOfIdentityCommand(identityAddress));
23+
await _mediator.Send(new DecomposeAndAnonymizeRelationshipsOfIdentityCommand(identityAddress));
2424
await _deletionProcessLogger.LogDeletion(identityAddress, "Relationships");
2525
await _mediator.Send(new DeleteRelationshipTemplatesOfIdentityCommand(identityAddress));
2626
await _deletionProcessLogger.LogDeletion(identityAddress, "RelationshipTemplates");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using MediatR;
2+
3+
namespace Backbone.Modules.Relationships.Application.Relationships.Commands.DecomposeAndAnonymizeRelationshipsOfIdentity;
4+
5+
public class DecomposeAndAnonymizeRelationshipsOfIdentityCommand : IRequest
6+
{
7+
public DecomposeAndAnonymizeRelationshipsOfIdentityCommand(string identityAddress)
8+
{
9+
IdentityAddress = identityAddress;
10+
}
11+
12+
public string IdentityAddress { get; set; }
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Backbone.Modules.Relationships.Application.Infrastructure.Persistence.Repository;
2+
using Backbone.Modules.Relationships.Domain.Aggregates.Relationships;
3+
using FakeItEasy;
4+
using Microsoft.Extensions.Options;
5+
6+
namespace Backbone.Modules.Relationships.Application.Relationships.Commands.DecomposeAndAnonymizeRelationshipsOfIdentity;
7+
8+
public class HandlerTests : AbstractTestsBase
9+
{
10+
[Fact]
11+
public async Task Command_calls_update_on_repository()
12+
{
13+
// Arrange
14+
var mockRelationshipTemplatesRepository = A.Fake<IRelationshipsRepository>();
15+
var mockOptions = A.Dummy<IOptions<ApplicationOptions>>();
16+
17+
var handler = new Handler(mockRelationshipTemplatesRepository, mockOptions);
18+
var request = new DecomposeAndAnonymizeRelationshipsOfIdentityCommand(CreateRandomIdentityAddress());
19+
20+
// Act
21+
await handler.Handle(request, CancellationToken.None);
22+
23+
// Assert
24+
A.CallTo(() => mockRelationshipTemplatesRepository.Update(A<IEnumerable<Relationship>>._)).MustHaveHappened();
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Backbone.Modules.Relationships.Application.Infrastructure.Persistence.Repository;
2+
using Backbone.Modules.Relationships.Domain.Aggregates.Relationships;
3+
using MediatR;
4+
using Microsoft.Extensions.Options;
5+
6+
namespace Backbone.Modules.Relationships.Application.Relationships.Commands.DecomposeAndAnonymizeRelationshipsOfIdentity;
7+
8+
public class Handler : IRequestHandler<DecomposeAndAnonymizeRelationshipsOfIdentityCommand>
9+
{
10+
private readonly IRelationshipsRepository _relationshipsRepository;
11+
private readonly ApplicationOptions _applicationOptions;
12+
13+
public Handler(IRelationshipsRepository relationshipsRepository, IOptions<ApplicationOptions> applicationOptions)
14+
{
15+
_relationshipsRepository = relationshipsRepository;
16+
_applicationOptions = applicationOptions.Value;
17+
}
18+
19+
public async Task Handle(DecomposeAndAnonymizeRelationshipsOfIdentityCommand request, CancellationToken cancellationToken)
20+
{
21+
var relationships = (await _relationshipsRepository.FindRelationships(Relationship.HasParticipant(request.IdentityAddress), cancellationToken)).ToList();
22+
23+
foreach (var relationship in relationships)
24+
relationship.DecomposeDueToIdentityDeletion(request.IdentityAddress, _applicationOptions.DidDomainName);
25+
26+
await _relationshipsRepository.Update(relationships);
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Backbone.BuildingBlocks.Application.Extensions;
2+
using Backbone.DevelopmentKit.Identity.ValueObjects;
3+
using FluentValidation;
4+
5+
namespace Backbone.Modules.Relationships.Application.Relationships.Commands.DecomposeAndAnonymizeRelationshipsOfIdentity;
6+
7+
public class Validator : AbstractValidator<DecomposeAndAnonymizeRelationshipsOfIdentityCommand>
8+
{
9+
public Validator()
10+
{
11+
RuleFor(x => x.IdentityAddress).ValidId<DecomposeAndAnonymizeRelationshipsOfIdentityCommand, IdentityAddress>();
12+
}
13+
}

Modules/Relationships/src/Relationships.Application/Relationships/Commands/DeleteRelationshipsOfIdentity/DeleteRelationshipsOfIdentityCommand.cs

Lines changed: 0 additions & 13 deletions
This file was deleted.

Modules/Relationships/src/Relationships.Application/Relationships/Commands/DeleteRelationshipsOfIdentity/Handler.Tests.cs

Lines changed: 0 additions & 25 deletions
This file was deleted.

Modules/Relationships/src/Relationships.Application/Relationships/Commands/DeleteRelationshipsOfIdentity/Handler.cs

Lines changed: 0 additions & 20 deletions
This file was deleted.

Modules/Relationships/src/Relationships.Application/Relationships/Commands/DeleteRelationshipsOfIdentity/Validator.cs

Lines changed: 0 additions & 13 deletions
This file was deleted.

Modules/Relationships/src/Relationships.Application/Relationships/DTOs/RelationshipAuditLogEntryDTO.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ public RelationshipAuditLogEntryDTO(RelationshipAuditLogEntry entry)
88
{
99
CreatedAt = entry.CreatedAt;
1010
CreatedBy = entry.CreatedBy;
11-
CreatedByDevice = entry.CreatedByDevice;
11+
CreatedByDevice = entry.CreatedByDevice?.Value;
1212
Reason = entry.Reason.ToString();
1313
OldStatus = entry.OldStatus.ToDtoString();
1414
NewStatus = entry.NewStatus.ToDtoString();
1515
}
1616

1717
public DateTime CreatedAt { get; set; }
1818
public string CreatedBy { get; set; }
19-
public string CreatedByDevice { get; set; }
19+
public string? CreatedByDevice { get; set; }
2020
public string Reason { get; set; }
2121

2222
public string? OldStatus { get; set; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using Backbone.BuildingBlocks.Domain.Exceptions;
2+
using Backbone.DevelopmentKit.Identity.ValueObjects;
3+
using Backbone.UnitTestTools.Extensions;
4+
using static Backbone.Modules.Relationships.Domain.TestHelpers.TestData;
5+
6+
namespace Backbone.Modules.Relationships.Domain.Aggregates.Relationships;
7+
8+
public class RelationshipDecomposeDueToIdentityDeletionTests : AbstractTestsBase
9+
{
10+
private const string DID_DOMAIN_NAME = "localhost";
11+
12+
[Theory]
13+
[InlineData(RelationshipStatus.Pending)]
14+
[InlineData(RelationshipStatus.Active)]
15+
[InlineData(RelationshipStatus.Rejected)]
16+
[InlineData(RelationshipStatus.Revoked)]
17+
[InlineData(RelationshipStatus.Terminated)]
18+
public void Decomposition_can_be_performed_from_multiple_statuses(RelationshipStatus status)
19+
{
20+
// Arrange
21+
var relationship = CreateRelationshipInStatus(status, IDENTITY_1, IDENTITY_2);
22+
23+
// Act
24+
var acting = () => relationship.DecomposeDueToIdentityDeletion(IDENTITY_1, DID_DOMAIN_NAME);
25+
26+
// Assert
27+
acting.Should().NotThrow();
28+
relationship.Status.Should().Be(RelationshipStatus.DeletionProposed);
29+
relationship.FromHasDecomposed.Should().BeTrue();
30+
relationship.ToHasDecomposed.Should().BeFalse();
31+
}
32+
33+
[Fact]
34+
public void Decomposition_can_not_be_called_by_the_same_identity_twice()
35+
{
36+
// Arrange
37+
var relationship = CreateRelationshipDecomposedByFrom(IDENTITY_1, IDENTITY_2);
38+
39+
// Act
40+
var acting = () => relationship.DecomposeDueToIdentityDeletion(IDENTITY_1, DID_DOMAIN_NAME);
41+
42+
// Assert
43+
acting.Should().Throw<DomainException>().WithError("error.platform.validation.relationship.relationshipAlreadyDecomposed");
44+
}
45+
46+
[Fact]
47+
public void Decomposition_can_not_be_performed_by_other_identities()
48+
{
49+
// Arrange
50+
var relationship = CreateActiveRelationship(IDENTITY_1, IDENTITY_2);
51+
52+
// Act
53+
var acting = () => relationship.DecomposeDueToIdentityDeletion(CreateRandomIdentityAddress(), DID_DOMAIN_NAME);
54+
55+
// Assert
56+
acting.Should().Throw<DomainException>().WithError("error.platform.validation.relationship.requestingIdentityDoesNotBelongToRelationship");
57+
}
58+
59+
[Fact]
60+
public void Decomposition_by_both_identities_transitions_relationship_to_status_ReadyForDeletion()
61+
{
62+
// Arrange
63+
var relationship = CreateActiveRelationship(IDENTITY_1, IDENTITY_2);
64+
relationship.DecomposeDueToIdentityDeletion(IDENTITY_1, DID_DOMAIN_NAME);
65+
66+
// Act
67+
var acting = () => relationship.DecomposeDueToIdentityDeletion(IDENTITY_2, DID_DOMAIN_NAME);
68+
69+
// Assert
70+
acting.Should().NotThrow();
71+
relationship.Status.Should().Be(RelationshipStatus.ReadyForDeletion);
72+
}
73+
74+
[Fact]
75+
public void Anonymizes_the_deleted_identity()
76+
{
77+
// Arrange
78+
var relationship = CreateActiveRelationship(IDENTITY_1, IDENTITY_2);
79+
var anonymousIdentity = IdentityAddress.GetAnonymized(DID_DOMAIN_NAME);
80+
81+
// Act
82+
relationship.DecomposeDueToIdentityDeletion(IDENTITY_1, DID_DOMAIN_NAME);
83+
84+
// Assert
85+
relationship.From.Should().Be(anonymousIdentity);
86+
relationship.FromHasDecomposed.Should().BeTrue();
87+
}
88+
89+
[Fact]
90+
public void Anonymizes_the_audit_logs()
91+
{
92+
// Arrange
93+
var relationship = CreateActiveRelationship(IDENTITY_1, IDENTITY_2);
94+
var anonymousIdentity = IdentityAddress.GetAnonymized(DID_DOMAIN_NAME);
95+
96+
// Act
97+
relationship.DecomposeDueToIdentityDeletion(IDENTITY_1, DID_DOMAIN_NAME);
98+
99+
// Assert
100+
relationship.AuditLog.Should().HaveCount(3);
101+
relationship.AuditLog[0].CreatedBy.Should().Be(anonymousIdentity);
102+
relationship.AuditLog[2].CreatedBy.Should().Be(anonymousIdentity);
103+
}
104+
}

0 commit comments

Comments
 (0)