diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 04abe6f477..e0f041c288 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -234,6 +234,9 @@ jobs: - publish-admin-ui - publish-consumer-api - publish-event-handler + - publish-database-migrator + - publish-identity-deletion-jobs + - publish-sse-server steps: - name: Checkout uses: actions/checkout@v4 diff --git a/Backbone.Tests.ArchUnit/DomainDrivenDesign.cs b/Backbone.Tests.ArchUnit/DomainDrivenDesign.cs index 6799f2fb5a..466a3bc4b3 100644 --- a/Backbone.Tests.ArchUnit/DomainDrivenDesign.cs +++ b/Backbone.Tests.ArchUnit/DomainDrivenDesign.cs @@ -17,7 +17,7 @@ public void EntitiesShouldHaveEmptyDefaultConstructors() { var constructors = type.GetConstructors(); - if (constructors.All(c => c.Parameters.Count() != 0)) + if (constructors.All(c => c.Parameters.Any())) return new ConditionResult(type, false, "Entity should have a parameterless constructor"); return new ConditionResult(type, true); diff --git a/BuildingBlocks/src/UnitTestTools/FluentAssertions/Assertions/EntityAssertions.cs b/BuildingBlocks/src/UnitTestTools/FluentAssertions/Assertions/EntityAssertions.cs index 0dbc2352e5..d965c2c78f 100644 --- a/BuildingBlocks/src/UnitTestTools/FluentAssertions/Assertions/EntityAssertions.cs +++ b/BuildingBlocks/src/UnitTestTools/FluentAssertions/Assertions/EntityAssertions.cs @@ -19,13 +19,35 @@ public TEvent HaveASingleDomainEvent(string because = "", params object[ .BecauseOf(because, becauseArgs) .Given(() => Subject.DomainEvents) .ForCondition(events => events.Count == 1) - .FailWith("Expected {context:entity} to have 1 domain event, but found {0}.", - Subject.DomainEvents.Count) + .FailWith("Expected {context:entity} to have 1 domain event, but found {0}.", Subject.DomainEvents.Count) .Then .ForCondition(events => events[0].GetType() == typeof(TEvent)) - .FailWith("Expected the domain event to be of type {0}, but found {1}.", - typeof(TEvent), Subject.DomainEvents[0].GetType()); + .FailWith("Expected the domain event to be of type {0}, but found {1}.", typeof(TEvent).Name, Subject.DomainEvents[0].GetType().Name); return (TEvent)Subject.DomainEvents[0]; } + + public (TEvent1 event1, TEvent2 event2) HaveDomainEvents(string because = "", params object[] becauseArgs) + where TEvent1 : DomainEvent + where TEvent2 : DomainEvent + { + var joinedEvents = string.Join(", ", Subject.DomainEvents.Select(e => e.GetType().Name)); + + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => Subject.DomainEvents) + .ForCondition(events => events.Count == 2) + .FailWith("Expected {context:entity} to have 2 domain events, but found {0}.", Subject.DomainEvents.Count) + .Then + .ForCondition(events => events.Any(e => e.GetType() == typeof(TEvent1))) + .FailWith("Expected to find a domain event of type {0}, but only found {1}.", typeof(TEvent1).Name, joinedEvents) + .Then + .ForCondition(events => events.Any(e => e.GetType() == typeof(TEvent2))) + .FailWith("Expected to find a domain event of type {0}, but only found {1}.", typeof(TEvent2).Name, joinedEvents); + + return ( + (TEvent1)Subject.DomainEvents.Single(e => e.GetType() == typeof(TEvent1)), + (TEvent2)Subject.DomainEvents.Single(e => e.GetType() == typeof(TEvent2)) + ); + } } diff --git a/Jobs/src/Job.IdentityDeletion/Workers/ActualDeletionWorker.cs b/Jobs/src/Job.IdentityDeletion/Workers/ActualDeletionWorker.cs index a07f47c37d..f90f46d496 100644 --- a/Jobs/src/Job.IdentityDeletion/Workers/ActualDeletionWorker.cs +++ b/Jobs/src/Job.IdentityDeletion/Workers/ActualDeletionWorker.cs @@ -1,12 +1,9 @@ -using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; -using Backbone.BuildingBlocks.Application.Identities; +using Backbone.BuildingBlocks.Application.Identities; using Backbone.BuildingBlocks.Application.PushNotifications; using Backbone.BuildingBlocks.Domain.Errors; using Backbone.DevelopmentKit.Identity.ValueObjects; using Backbone.Modules.Devices.Application.Identities.Commands.TriggerRipeDeletionProcesses; using Backbone.Modules.Devices.Application.Infrastructure.PushNotifications.DeletionProcess; -using Backbone.Modules.Devices.Domain.DomainEvents.Outgoing; -using Backbone.Modules.Relationships.Application.Relationships.Commands.FindRelationshipsOfIdentity; using CSharpFunctionalExtensions; using MediatR; @@ -14,7 +11,6 @@ namespace Backbone.Job.IdentityDeletion.Workers; public class ActualDeletionWorker : IHostedService { - private readonly IEventBus _eventBus; private readonly IHostApplicationLifetime _host; private readonly IEnumerable _identityDeleters; private readonly IMediator _mediator; @@ -25,14 +21,12 @@ public ActualDeletionWorker(IHostApplicationLifetime host, IEnumerable identityDeleters, IMediator mediator, IPushNotificationSender pushNotificationSender, - IEventBus eventBus, ILogger logger) { _host = host; _identityDeleters = identityDeleters; _mediator = mediator; _pushNotificationSender = pushNotificationSender; - _eventBus = eventBus; _logger = logger; } @@ -70,7 +64,6 @@ private async Task ExecuteDeletion(IEnumerable addresses, Cance private async Task ExecuteDeletion(CancellationToken cancellationToken, IdentityAddress identityAddress) { await NotifyIdentityAboutStartingDeletion(cancellationToken, identityAddress); - await NotifyRelationshipsAboutStartingDeletion(identityAddress, cancellationToken); await Delete(identityAddress); } @@ -79,16 +72,6 @@ private async Task NotifyIdentityAboutStartingDeletion(CancellationToken cancell await _pushNotificationSender.SendNotification(identityAddress, new DeletionStartsPushNotification(), cancellationToken); } - private async Task NotifyRelationshipsAboutStartingDeletion(IdentityAddress identityAddress, CancellationToken cancellationToken) - { - var relationships = await _mediator.Send(new FindRelationshipsOfIdentityQuery(identityAddress), cancellationToken); - - foreach (var relationship in relationships) - { - _eventBus.Publish(new PeerIdentityDeletedDomainEvent(relationship.Id, identityAddress)); - } - } - private async Task Delete(IdentityAddress identityAddress) { foreach (var identityDeleter in _identityDeleters) diff --git a/Jobs/src/Job.IdentityDeletion/Workers/CancelStaleDeletionProcessesWorker.cs b/Jobs/src/Job.IdentityDeletion/Workers/CancelStaleDeletionProcessesWorker.cs index ba4c7fc2e9..1464cdbcca 100644 --- a/Jobs/src/Job.IdentityDeletion/Workers/CancelStaleDeletionProcessesWorker.cs +++ b/Jobs/src/Job.IdentityDeletion/Workers/CancelStaleDeletionProcessesWorker.cs @@ -51,7 +51,7 @@ internal static partial class CancelIdentityDeletionProcessWorkerLogs EventId = 440986, EventName = "Job.CancelIdentityDeletionProcessWorker.CompletedWithResults", Level = LogLevel.Information, - Message = "Automatically canceled identity deletion processes: {concatenatedProcessIds}")] + Message = "Automatically cancelled identity deletion processes: {concatenatedProcessIds}")] public static partial void WorkerCompletedWithResults(this ILogger logger, string concatenatedProcessIds); [LoggerMessage( diff --git a/Jobs/test/Job.IdentityDeletion.Tests/Tests/ActualDeletionWorkerTests.cs b/Jobs/test/Job.IdentityDeletion.Tests/Tests/ActualDeletionWorkerTests.cs index 675bcf71cd..3acdec42d1 100644 --- a/Jobs/test/Job.IdentityDeletion.Tests/Tests/ActualDeletionWorkerTests.cs +++ b/Jobs/test/Job.IdentityDeletion.Tests/Tests/ActualDeletionWorkerTests.cs @@ -1,5 +1,4 @@ -using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; -using Backbone.BuildingBlocks.Application.Identities; +using Backbone.BuildingBlocks.Application.Identities; using Backbone.BuildingBlocks.Application.PushNotifications; using Backbone.BuildingBlocks.Domain.Errors; using Backbone.DevelopmentKit.Identity.ValueObjects; @@ -94,19 +93,17 @@ private void SetupRipeDeletionProcessesCommand(IMediator mediator, params Identi private static ActualDeletionWorker CreateWorker(IMediator mediator, IPushNotificationSender pushNotificationSender) { - return CreateWorker(mediator, null, null, pushNotificationSender); + return CreateWorker(mediator, null, pushNotificationSender); } private static ActualDeletionWorker CreateWorker(IMediator mediator, List? identityDeleters = null, - IEventBus? eventBus = null, IPushNotificationSender? pushNotificationSender = null) { var hostApplicationLifetime = A.Dummy(); identityDeleters ??= [A.Dummy()]; - eventBus ??= A.Dummy(); pushNotificationSender ??= A.Dummy(); var logger = A.Dummy>(); - return new ActualDeletionWorker(hostApplicationLifetime, identityDeleters, mediator, pushNotificationSender, eventBus, logger); + return new ActualDeletionWorker(hostApplicationLifetime, identityDeleters, mediator, pushNotificationSender, logger); } } diff --git a/Modules/Devices/src/Devices.Domain/DomainEvents/Outgoing/IdentityDeletedDomainEvent.cs b/Modules/Devices/src/Devices.Domain/DomainEvents/Outgoing/IdentityDeletedDomainEvent.cs new file mode 100644 index 0000000000..864c62b123 --- /dev/null +++ b/Modules/Devices/src/Devices.Domain/DomainEvents/Outgoing/IdentityDeletedDomainEvent.cs @@ -0,0 +1,12 @@ +using Backbone.BuildingBlocks.Domain.Events; + +namespace Backbone.Modules.Devices.Domain.DomainEvents.Outgoing; +public class IdentityDeletedDomainEvent : DomainEvent +{ + public IdentityDeletedDomainEvent(string identityAddress) : base($"{identityAddress}/IdentityDeleted") + { + IdentityAddress = identityAddress; + } + + public string IdentityAddress { get; } +} diff --git a/Modules/Devices/src/Devices.Domain/DomainEvents/Outgoing/IdentityDeletionCancelledDomainEvent.cs b/Modules/Devices/src/Devices.Domain/DomainEvents/Outgoing/IdentityDeletionCancelledDomainEvent.cs new file mode 100644 index 0000000000..e1123aaf55 --- /dev/null +++ b/Modules/Devices/src/Devices.Domain/DomainEvents/Outgoing/IdentityDeletionCancelledDomainEvent.cs @@ -0,0 +1,13 @@ +using Backbone.BuildingBlocks.Domain.Events; + +namespace Backbone.Modules.Devices.Domain.DomainEvents.Outgoing; + +public class IdentityDeletionCancelledDomainEvent : DomainEvent +{ + public IdentityDeletionCancelledDomainEvent(string identityAddress) : base($"{identityAddress}/IdentityDeletionCancelled", randomizeId: true) + { + IdentityAddress = identityAddress; + } + + public string IdentityAddress { get; } +} diff --git a/Modules/Devices/src/Devices.Domain/DomainEvents/Outgoing/IdentityToBeDeletedDomainEvent.cs b/Modules/Devices/src/Devices.Domain/DomainEvents/Outgoing/IdentityToBeDeletedDomainEvent.cs new file mode 100644 index 0000000000..e718c84f81 --- /dev/null +++ b/Modules/Devices/src/Devices.Domain/DomainEvents/Outgoing/IdentityToBeDeletedDomainEvent.cs @@ -0,0 +1,12 @@ +using Backbone.BuildingBlocks.Domain.Events; + +namespace Backbone.Modules.Devices.Domain.DomainEvents.Outgoing; +public class IdentityToBeDeletedDomainEvent : DomainEvent +{ + public IdentityToBeDeletedDomainEvent(string identityAddress) : base($"{identityAddress}/IdentityToBeDeleted", randomizeId: true) + { + IdentityAddress = identityAddress; + } + + public string IdentityAddress { get; } +} diff --git a/Modules/Devices/src/Devices.Domain/DomainEvents/Outgoing/PeerIdentityDeletedDomainEvent.cs b/Modules/Devices/src/Devices.Domain/DomainEvents/Outgoing/PeerIdentityDeletedDomainEvent.cs deleted file mode 100644 index a414dbd319..0000000000 --- a/Modules/Devices/src/Devices.Domain/DomainEvents/Outgoing/PeerIdentityDeletedDomainEvent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Backbone.BuildingBlocks.Domain.Events; -using Backbone.DevelopmentKit.Identity.ValueObjects; - -namespace Backbone.Modules.Devices.Domain.DomainEvents.Outgoing; - -public class PeerIdentityDeletedDomainEvent : DomainEvent -{ - public PeerIdentityDeletedDomainEvent(string relationshipId, IdentityAddress identityAddress) : base($"{relationshipId}/PeerIdentityDeleted/${identityAddress}") - { - IdentityAddress = identityAddress; - RelationshipId = relationshipId; - } - - public string IdentityAddress { get; } - public string RelationshipId { get; } -} diff --git a/Modules/Devices/src/Devices.Domain/Entities/Identities/Identity.cs b/Modules/Devices/src/Devices.Domain/Entities/Identities/Identity.cs index a1bad03035..393cc5a9b5 100644 --- a/Modules/Devices/src/Devices.Domain/Entities/Identities/Identity.cs +++ b/Modules/Devices/src/Devices.Domain/Entities/Identities/Identity.cs @@ -115,6 +115,7 @@ public IdentityDeletionProcess StartDeletionProcessAsOwner(DeviceId asDevice) DeletionGracePeriodEndsAt = deletionProcess.GracePeriodEndsAt; TierId = Tier.QUEUED_FOR_DELETION.Id; Status = IdentityStatus.ToBeDeleted; + RaiseDomainEvent(new IdentityToBeDeletedDomainEvent(Address)); return deletionProcess; } @@ -167,6 +168,7 @@ public IdentityDeletionProcess ApproveDeletionProcess(IdentityDeletionProcessId deletionProcess.Approve(Address, deviceId); Status = IdentityStatus.ToBeDeleted; + RaiseDomainEvent(new IdentityToBeDeletedDomainEvent(Address)); DeletionGracePeriodEndsAt = deletionProcess.GracePeriodEndsAt; TierId = Tier.QUEUED_FOR_DELETION.Id; @@ -180,22 +182,7 @@ public void DeletionStarted() deletionProcess.DeletionStarted(Address); Status = IdentityStatus.Deleting; - } - - public IdentityDeletionProcess CancelDeletionProcess(IdentityDeletionProcessId deletionProcessId, DeviceId canceledByDeviceId) - { - EnsureIdentityOwnsDevice(canceledByDeviceId); - - var deletionProcess = DeletionProcesses.FirstOrDefault(x => x.Id == deletionProcessId) ?? - throw new DomainException(GenericDomainErrors.NotFound(nameof(IdentityDeletionProcess))); - - deletionProcess.CancelAsOwner(Address, canceledByDeviceId); - - TierId = TierIdBeforeDeletion ?? throw new Exception($"Error when trying to cancel deletion process: '{nameof(TierIdBeforeDeletion)}' is null."); - TierIdBeforeDeletion = null; - Status = IdentityStatus.Active; - - return deletionProcess; + RaiseDomainEvent(new IdentityDeletedDomainEvent(Address)); } private IdentityDeletionProcess GetDeletionProcess(IdentityDeletionProcessId deletionProcessId) @@ -275,47 +262,41 @@ public static Expression> IsReadyForDeletion() return i => i.Status == IdentityStatus.ToBeDeleted && i.DeletionGracePeriodEndsAt != null && i.DeletionGracePeriodEndsAt < SystemTime.UtcNow; } - public IdentityDeletionProcess CancelDeletionProcessAsOwner(IdentityDeletionProcessId deletionProcessId, DeviceId canceledByDeviceId) + public IdentityDeletionProcess CancelDeletionProcessAsOwner(IdentityDeletionProcessId deletionProcessId, DeviceId cancelledByDeviceId) { + EnsureIdentityOwnsDevice(cancelledByDeviceId); + var deletionProcess = GetDeletionProcessWithId(deletionProcessId); deletionProcess.EnsureStatus(DeletionProcessStatus.Approved); - EnsureIdentityOwnsDevice(canceledByDeviceId); - - deletionProcess.CancelAsOwner(Address, canceledByDeviceId); - TierId = TierIdBeforeDeletion!; + deletionProcess.CancelAsOwner(Address, cancelledByDeviceId); + TierId = TierIdBeforeDeletion ?? throw new Exception($"Error when trying to cancel deletion process: '{nameof(TierIdBeforeDeletion)}' is null."); TierIdBeforeDeletion = null; Status = IdentityStatus.Active; - return deletionProcess; - } + RaiseDomainEvent(new IdentityDeletionCancelledDomainEvent(Address)); - private IdentityDeletionProcess GetDeletionProcessWithId(IdentityDeletionProcessId deletionProcessId) - { - return DeletionProcesses.FirstOrDefault(x => x.Id == deletionProcessId) ?? throw new DomainException(GenericDomainErrors.NotFound(nameof(IdentityDeletionProcess))); + return deletionProcess; } public IdentityDeletionProcess CancelDeletionProcessAsSupport(IdentityDeletionProcessId deletionProcessId) { - EnsureDeletionProcessExists(deletionProcessId); - EnsureDeletionProcessInStatusExists(DeletionProcessStatus.Approved); - - var deletionProcess = DeletionProcesses.First(d => d.Id == deletionProcessId); + var deletionProcess = GetDeletionProcessWithId(deletionProcessId); + deletionProcess.EnsureStatus(DeletionProcessStatus.Approved); deletionProcess.CancelAsSupport(Address); - TierId = TierIdBeforeDeletion!; + TierId = TierIdBeforeDeletion ?? throw new Exception($"Error when trying to cancel deletion process: '{nameof(TierIdBeforeDeletion)}' is null."); TierIdBeforeDeletion = null; Status = IdentityStatus.Active; + RaiseDomainEvent(new IdentityDeletionCancelledDomainEvent(Address)); + return deletionProcess; } - private void EnsureDeletionProcessExists(IdentityDeletionProcessId deletionProcessId) + private IdentityDeletionProcess GetDeletionProcessWithId(IdentityDeletionProcessId deletionProcessId) { - var isDeletionProcessOwnedByDevice = DeletionProcesses.Any(d => d.Id == deletionProcessId); - - if (!isDeletionProcessOwnedByDevice) - throw new DomainException(GenericDomainErrors.NotFound(nameof(IdentityDeletionProcess))); + return DeletionProcesses.FirstOrDefault(x => x.Id == deletionProcessId) ?? throw new DomainException(GenericDomainErrors.NotFound(nameof(IdentityDeletionProcess))); } } diff --git a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Commands/CancelDeletionProcessAsOwner/HandlerTests.cs b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Commands/CancelDeletionProcessAsOwner/HandlerTests.cs index 0438c328a9..1c2b3884ce 100644 --- a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Commands/CancelDeletionProcessAsOwner/HandlerTests.cs +++ b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Commands/CancelDeletionProcessAsOwner/HandlerTests.cs @@ -1,11 +1,9 @@ using Backbone.BuildingBlocks.Application.Abstractions.Exceptions; -using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.UserContext; using Backbone.BuildingBlocks.Application.PushNotifications; using Backbone.Modules.Devices.Application.Identities.Commands.CancelDeletionProcessAsOwner; using Backbone.Modules.Devices.Application.Infrastructure.Persistence.Repository; using Backbone.Modules.Devices.Application.Infrastructure.PushNotifications.DeletionProcess; -using Backbone.Modules.Devices.Domain.DomainEvents.Outgoing; using Backbone.Modules.Devices.Domain.Entities.Identities; using Backbone.UnitTestTools.BaseClasses; using FakeItEasy; @@ -35,10 +33,9 @@ public async Task Happy_path() A.CallTo(() => fakeUserContext.GetDeviceId()).Returns(activeDevice.Id); var handler = CreateHandler(mockIdentitiesRepository, fakeUserContext, mockPushNotificationSender); - var command = new CancelDeletionProcessAsOwnerCommand(deletionProcess.Id); // Act - var response = await handler.Handle(command, CancellationToken.None); + var response = await handler.Handle(new CancelDeletionProcessAsOwnerCommand(deletionProcess.Id), CancellationToken.None); // Assert A.CallTo(() => mockIdentitiesRepository.Update(A.That.Matches(i => diff --git a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Commands/StartDeletionProcessAsOwner/HandlerTests.cs b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Commands/StartDeletionProcessAsOwner/HandlerTests.cs index f5f8ba7a00..df45d386bf 100644 --- a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Commands/StartDeletionProcessAsOwner/HandlerTests.cs +++ b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Commands/StartDeletionProcessAsOwner/HandlerTests.cs @@ -36,8 +36,7 @@ public async Task Happy_path() var handler = CreateHandler(mockIdentitiesRepository, fakeUserContext, mockPushNotificationSender); // Act - var command = new StartDeletionProcessAsOwnerCommand(); - var response = await handler.Handle(command, CancellationToken.None); + var response = await handler.Handle(new StartDeletionProcessAsOwnerCommand(), CancellationToken.None); // Assert response.Should().NotBeNull(); @@ -74,8 +73,7 @@ public void Cannot_start_when_given_identity_does_not_exist() var handler = CreateHandler(fakeIdentitiesRepository, fakeUserContext); // Act - var command = new StartDeletionProcessAsOwnerCommand(); - var acting = async () => await handler.Handle(command, CancellationToken.None); + var acting = async () => await handler.Handle(new StartDeletionProcessAsOwnerCommand(), CancellationToken.None); // Assert acting.Should().AwaitThrowAsync().Which.Message.Should().Contain("Identity"); @@ -83,6 +81,7 @@ public void Cannot_start_when_given_identity_does_not_exist() private static Handler CreateHandler(IIdentitiesRepository identitiesRepository, IUserContext userContext, IPushNotificationSender? pushNotificationSender = null) { - return new Handler(identitiesRepository, userContext, pushNotificationSender ?? A.Dummy()); + pushNotificationSender ??= A.Dummy(); + return new Handler(identitiesRepository, userContext, pushNotificationSender); } } diff --git a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Commands/UpdateDeletionProcesses/HandlerTests.cs b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Commands/UpdateDeletionProcesses/HandlerTests.cs index b2b64bbb40..fddfcd4dcb 100644 --- a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Commands/UpdateDeletionProcesses/HandlerTests.cs +++ b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Commands/UpdateDeletionProcesses/HandlerTests.cs @@ -19,10 +19,9 @@ public async Task Empty_response_if_no_identities_are_past_DeletionGracePeriodEn var mockIdentitiesRepository = CreateFakeIdentitiesRepository(0, out _); var handler = CreateHandler(mockIdentitiesRepository); - var command = new TriggerRipeDeletionProcessesCommand(); // Act - var response = await handler.Handle(command, CancellationToken.None); + var response = await handler.Handle(new TriggerRipeDeletionProcessesCommand(), CancellationToken.None); // Assert response.Results.Should().BeEmpty(); @@ -38,10 +37,9 @@ public async Task Response_contains_expected_identities() anIdentity.StartDeletionProcessAsOwner(anIdentity.Devices.First().Id); var handler = CreateHandler(identitiesRepository); - var command = new TriggerRipeDeletionProcessesCommand(); // Act - var response = await handler.Handle(command, CancellationToken.None); + var response = await handler.Handle(new TriggerRipeDeletionProcessesCommand(), CancellationToken.None); // Assert response.Results.Should().HaveCount(1); @@ -60,10 +58,9 @@ public async Task Deletion_process_started_successfully() SystemTime.Set(SystemTime.UtcNow.AddDays(IdentityDeletionConfiguration.LengthOfGracePeriod + 1)); var handler = CreateHandler(identitiesRepository); - var command = new TriggerRipeDeletionProcessesCommand(); // Act - var response = await handler.Handle(command, CancellationToken.None); + var response = await handler.Handle(new TriggerRipeDeletionProcessesCommand(), CancellationToken.None); // Assert response.Results.Should().HaveCount(1); diff --git a/Modules/Devices/test/Devices.Domain.Tests/Identities/ApproveDeletionProcessTests.cs b/Modules/Devices/test/Devices.Domain.Tests/Identities/ApproveDeletionProcessTests.cs index 3f61f7e2e1..02f9b69fd0 100644 --- a/Modules/Devices/test/Devices.Domain.Tests/Identities/ApproveDeletionProcessTests.cs +++ b/Modules/Devices/test/Devices.Domain.Tests/Identities/ApproveDeletionProcessTests.cs @@ -1,10 +1,12 @@ using Backbone.BuildingBlocks.Domain; using Backbone.DevelopmentKit.Identity.ValueObjects; using Backbone.Modules.Devices.Domain.Aggregates.Tier; +using Backbone.Modules.Devices.Domain.DomainEvents.Outgoing; using Backbone.Modules.Devices.Domain.Entities.Identities; using Backbone.Modules.Devices.Domain.Tests.Identities.TestDoubles; using Backbone.Tooling; using Backbone.UnitTestTools.BaseClasses; +using Backbone.UnitTestTools.FluentAssertions.Extensions; using FluentAssertions; using Xunit; @@ -83,6 +85,28 @@ public void Throws_when_deletion_process_is_not_in_status_waiting_for_approval() exception.Message.Should().Contain("WaitingForApproval"); } + [Fact] + public void Raises_domain_events() + { + //Arrange + var activeIdentity = TestDataGenerator.CreateIdentity(); + var activeDevice = activeIdentity.Devices[0]; + var tierBeforeDeletion = activeIdentity.TierId; + var deletionProcess = activeIdentity.StartDeletionProcessAsSupport(); + + //Act + activeIdentity.ApproveDeletionProcess(deletionProcess.Id, activeDevice.Id); + + //Assert + var (tierOfIdentityChangedDomainEvent, identityToBeDeletedDomainEvent) = activeIdentity.Should().HaveDomainEvents(); + + tierOfIdentityChangedDomainEvent.IdentityAddress.Should().Be(activeIdentity.Address); + tierOfIdentityChangedDomainEvent.OldTierId.Should().Be(tierBeforeDeletion); + tierOfIdentityChangedDomainEvent.NewTierId.Should().Be(Tier.QUEUED_FOR_DELETION.Id); + + identityToBeDeletedDomainEvent.IdentityAddress.Should().Be(activeIdentity.Address); + } + private static void AssertAuditLogEntryWasCreated(IdentityDeletionProcess deletionProcess) { deletionProcess.AuditLog.Should().HaveCount(2); diff --git a/Modules/Devices/test/Devices.Domain.Tests/Identities/CancelDeletionProcessAsOwnerTests.cs b/Modules/Devices/test/Devices.Domain.Tests/Identities/CancelDeletionProcessAsOwnerTests.cs index d9f793b6d8..02911ec884 100644 --- a/Modules/Devices/test/Devices.Domain.Tests/Identities/CancelDeletionProcessAsOwnerTests.cs +++ b/Modules/Devices/test/Devices.Domain.Tests/Identities/CancelDeletionProcessAsOwnerTests.cs @@ -1,4 +1,5 @@ using Backbone.BuildingBlocks.Domain; +using Backbone.Modules.Devices.Domain.Aggregates.Tier; using Backbone.Modules.Devices.Domain.DomainEvents.Outgoing; using Backbone.Modules.Devices.Domain.Entities.Identities; using Backbone.Tooling; @@ -62,7 +63,7 @@ public void Throws_when_deletion_process_is_in_wrong_status() } [Fact] - public void Raises_IdentityDeletionProcessStatusChangedDomainEvent_when_Cancelling() + public void Raises_domain_events() { // Arrange var identity = TestDataGenerator.CreateIdentityWithApprovedDeletionProcess(); @@ -71,10 +72,19 @@ public void Raises_IdentityDeletionProcessStatusChangedDomainEvent_when_Cancelli // Act var deletionProcess = identity.CancelDeletionProcessAsOwner(identity.DeletionProcesses[0].Id, identity.Devices[0].Id); - var domainEvent = deletionProcess.Should().HaveASingleDomainEvent(); - domainEvent.DeletionProcessId.Should().Be(deletionProcess.Id); - domainEvent.Address.Should().Be(identity.Address); - domainEvent.Initiator.Should().Be(identity.Address); + // Assert + var deletionProcessDomainEvent = deletionProcess.Should().HaveASingleDomainEvent(); + deletionProcessDomainEvent.DeletionProcessId.Should().Be(deletionProcess.Id); + deletionProcessDomainEvent.Address.Should().Be(identity.Address); + deletionProcessDomainEvent.Initiator.Should().Be(identity.Address); + + var (tierOfIdentityChangedDomainEvent, identityDeletionCancelledDomainEvent) = identity.Should().HaveDomainEvents(); + + tierOfIdentityChangedDomainEvent.IdentityAddress.Should().Be(identity.Address); + tierOfIdentityChangedDomainEvent.OldTierId.Should().Be(Tier.QUEUED_FOR_DELETION.Id); + tierOfIdentityChangedDomainEvent.NewTierId.Should().Be(identity.TierId); + + identityDeletionCancelledDomainEvent.IdentityAddress.Should().Be(identity.Address); } private static void AssertAuditLogEntryWasCreated(IdentityDeletionProcess deletionProcess) diff --git a/Modules/Devices/test/Devices.Domain.Tests/Identities/CancelDeletionProcessAsSupportTests.cs b/Modules/Devices/test/Devices.Domain.Tests/Identities/CancelDeletionProcessAsSupportTests.cs index 18a59ab5c7..01c42c44f3 100644 --- a/Modules/Devices/test/Devices.Domain.Tests/Identities/CancelDeletionProcessAsSupportTests.cs +++ b/Modules/Devices/test/Devices.Domain.Tests/Identities/CancelDeletionProcessAsSupportTests.cs @@ -1,4 +1,5 @@ using Backbone.BuildingBlocks.Domain; +using Backbone.Modules.Devices.Domain.Aggregates.Tier; using Backbone.Modules.Devices.Domain.DomainEvents.Outgoing; using Backbone.Modules.Devices.Domain.Entities.Identities; using Backbone.Tooling; @@ -61,7 +62,7 @@ public void Throws_when_deletion_process_is_in_wrong_status() } [Fact] - public void Raises_IdentityDeletionProcessStatusChangedDomainEvent_when_Cancelling() + public void Raises_domain_events() { // Arrange var identity = TestDataGenerator.CreateIdentityWithApprovedDeletionProcess(); @@ -69,10 +70,19 @@ public void Raises_IdentityDeletionProcessStatusChangedDomainEvent_when_Cancelli // Act var deletionProcess = identity.CancelDeletionProcessAsSupport(identity.DeletionProcesses[0].Id); - var domainEvent = deletionProcess.Should().HaveASingleDomainEvent(); - domainEvent.DeletionProcessId.Should().Be(deletionProcess.Id); - domainEvent.Address.Should().Be(identity.Address); - domainEvent.Initiator.Should().Be(null); + // Assert + var deletionProcessDomainEvent = deletionProcess.Should().HaveASingleDomainEvent(); + deletionProcessDomainEvent.DeletionProcessId.Should().Be(deletionProcess.Id); + deletionProcessDomainEvent.Address.Should().Be(identity.Address); + deletionProcessDomainEvent.Initiator.Should().Be(null); + + var (tierOfIdentityChangedDomainEvent, identityDeletionCancelledDomainEvent) = identity.Should().HaveDomainEvents(); + + tierOfIdentityChangedDomainEvent.IdentityAddress.Should().Be(identity.Address); + tierOfIdentityChangedDomainEvent.OldTierId.Should().Be(Tier.QUEUED_FOR_DELETION.Id); + tierOfIdentityChangedDomainEvent.NewTierId.Should().Be(identity.TierId); + + identityDeletionCancelledDomainEvent.IdentityAddress.Should().Be(identity.Address); } private static void AssertAuditLogEntryWasCreated(IdentityDeletionProcess deletionProcess) diff --git a/Modules/Devices/test/Devices.Domain.Tests/Identities/DeletionStartedTests.cs b/Modules/Devices/test/Devices.Domain.Tests/Identities/DeletionStartedTests.cs index 0e4f52058f..c1e90de0da 100644 --- a/Modules/Devices/test/Devices.Domain.Tests/Identities/DeletionStartedTests.cs +++ b/Modules/Devices/test/Devices.Domain.Tests/Identities/DeletionStartedTests.cs @@ -1,7 +1,9 @@ using Backbone.BuildingBlocks.Domain; +using Backbone.Modules.Devices.Domain.DomainEvents.Outgoing; using Backbone.Modules.Devices.Domain.Entities.Identities; using Backbone.Tooling; using Backbone.UnitTestTools.BaseClasses; +using Backbone.UnitTestTools.FluentAssertions.Extensions; using FluentAssertions; using Xunit; @@ -65,10 +67,27 @@ public void Fails_to_start_if_no_approved_deletion_process_exists() acting.Should().Throw().Which.Code.Should().Be("error.platform.validation.device.deletionProcessIsNotInRequiredStatus"); } + [Fact] + public void Raises_domain_events() + { + //Arrange + var activeIdentity = TestDataGenerator.CreateIdentityWithApprovedDeletionProcess(); + + SystemTime.Set(SystemTime.UtcNow.AddDays(IdentityDeletionConfiguration.LengthOfGracePeriod).AddDays(1)); + + //Act + activeIdentity.DeletionStarted(); + + //Assert + var domainEvent = activeIdentity.Should().HaveASingleDomainEvent(); + domainEvent.IdentityAddress.Should().Be(activeIdentity.Address); + } + private static Identity CreateIdentityWithApprovedDeletionProcess() { var identity = TestDataGenerator.CreateIdentity(); identity.StartDeletionProcessAsOwner(identity.Devices.First().Id); + return identity; } } diff --git a/Modules/Devices/test/Devices.Domain.Tests/Identities/StartDeletionProcessAsOwnerTests.cs b/Modules/Devices/test/Devices.Domain.Tests/Identities/StartDeletionProcessAsOwnerTests.cs index fd9a77cf44..c87d5bc1e5 100644 --- a/Modules/Devices/test/Devices.Domain.Tests/Identities/StartDeletionProcessAsOwnerTests.cs +++ b/Modules/Devices/test/Devices.Domain.Tests/Identities/StartDeletionProcessAsOwnerTests.cs @@ -1,10 +1,12 @@ using Backbone.BuildingBlocks.Domain; using Backbone.DevelopmentKit.Identity.ValueObjects; using Backbone.Modules.Devices.Domain.Aggregates.Tier; +using Backbone.Modules.Devices.Domain.DomainEvents.Outgoing; using Backbone.Modules.Devices.Domain.Entities.Identities; using Backbone.Modules.Devices.Domain.Tests.Identities.TestDoubles; using Backbone.Tooling; using Backbone.UnitTestTools.BaseClasses; +using Backbone.UnitTestTools.FluentAssertions.Extensions; using FluentAssertions; using Xunit; @@ -86,6 +88,27 @@ public void Only_one_active_deletion_process_is_allowed_when_started() acting.Should().Throw().Which.Code.Should().Be("error.platform.validation.device.onlyOneActiveDeletionProcessAllowed"); } + [Fact] + public void Raises_domain_events() + { + //Arrange + var activeIdentity = TestDataGenerator.CreateIdentity(); + var tierBeforeDeletion = activeIdentity.TierId; + var activeDevice = activeIdentity.Devices[0]; + + //Act + activeIdentity.StartDeletionProcessAsOwner(activeDevice.Id); + + //Assert + var (tierOfIdentityChangedDomainEvent, identityToBeDeletedDomainEvent) = activeIdentity.Should().HaveDomainEvents(); + + tierOfIdentityChangedDomainEvent.IdentityAddress.Should().Be(activeIdentity.Address); + tierOfIdentityChangedDomainEvent.OldTierId.Should().Be(tierBeforeDeletion); + tierOfIdentityChangedDomainEvent.NewTierId.Should().Be(Tier.QUEUED_FOR_DELETION.Id); + + identityToBeDeletedDomainEvent.IdentityAddress.Should().Be(activeIdentity.Address); + } + private static void AssertDeletionProcessWasStarted(Identity activeIdentity) { activeIdentity.DeletionProcesses.Should().HaveCount(1); diff --git a/Modules/Devices/test/Devices.Domain.Tests/TestDataGenerator.cs b/Modules/Devices/test/Devices.Domain.Tests/TestDataGenerator.cs index d9dc46faad..68198ca033 100644 --- a/Modules/Devices/test/Devices.Domain.Tests/TestDataGenerator.cs +++ b/Modules/Devices/test/Devices.Domain.Tests/TestDataGenerator.cs @@ -49,6 +49,7 @@ public static Identity CreateIdentityWithApprovedDeletionProcess() { var identity = CreateIdentity(); identity.StartDeletionProcessAsOwner(identity.Devices.First().Id); + identity.ClearDomainEvents(); foreach (var deletionProcess in identity.DeletionProcesses) { diff --git a/Modules/Relationships/src/Relationships.Application/DomainEvents/Incoming/IdentityDeleted/IdentityDeletedDomainEventHandler.cs b/Modules/Relationships/src/Relationships.Application/DomainEvents/Incoming/IdentityDeleted/IdentityDeletedDomainEventHandler.cs new file mode 100644 index 0000000000..e66aeb7393 --- /dev/null +++ b/Modules/Relationships/src/Relationships.Application/DomainEvents/Incoming/IdentityDeleted/IdentityDeletedDomainEventHandler.cs @@ -0,0 +1,41 @@ +using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; +using Backbone.Modules.Relationships.Application.Infrastructure.Persistence.Repository; +using Backbone.Modules.Relationships.Domain.DomainEvents.Incoming; +using Backbone.Modules.Relationships.Domain.Entities; + +namespace Backbone.Modules.Relationships.Application.DomainEvents.Incoming.IdentityDeleted; + +public class IdentityDeletedDomainEventHandler : IDomainEventHandler +{ + private readonly IRelationshipsRepository _relationshipsRepository; + + public IdentityDeletedDomainEventHandler(IRelationshipsRepository relationshipsRepository) + { + _relationshipsRepository = relationshipsRepository; + } + + public async Task Handle(IdentityDeletedDomainEvent @event) + { + var relationships = await GetRelationshipsOf(@event.IdentityAddress); + + NotifyRelationshipsAboutDeletedPeer(@event.IdentityAddress, relationships); + + await _relationshipsRepository.Update(relationships); + } + + private async Task> GetRelationshipsOf(string identityAddress) + { + var relationships = (await _relationshipsRepository + .FindRelationships(r => (r.From == identityAddress || r.To == identityAddress) && r.Status == RelationshipStatus.Active, + CancellationToken.None)).ToList(); + return relationships; + } + + private static void NotifyRelationshipsAboutDeletedPeer(string identityToBeDeleted, IEnumerable relationships) + { + foreach (var relationship in relationships) + { + relationship.DeletionOfParticipantStarted(identityToBeDeleted); + } + } +} diff --git a/Modules/Relationships/src/Relationships.Application/DomainEvents/Incoming/IdentityDeletionCancelled/IdentityDeletionCancelledDomainEventHandler.cs b/Modules/Relationships/src/Relationships.Application/DomainEvents/Incoming/IdentityDeletionCancelled/IdentityDeletionCancelledDomainEventHandler.cs new file mode 100644 index 0000000000..b9a0fac7e2 --- /dev/null +++ b/Modules/Relationships/src/Relationships.Application/DomainEvents/Incoming/IdentityDeletionCancelled/IdentityDeletionCancelledDomainEventHandler.cs @@ -0,0 +1,41 @@ +using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; +using Backbone.Modules.Relationships.Application.Infrastructure.Persistence.Repository; +using Backbone.Modules.Relationships.Domain.DomainEvents.Incoming; +using Backbone.Modules.Relationships.Domain.Entities; + +namespace Backbone.Modules.Relationships.Application.DomainEvents.Incoming.IdentityDeletionCancelled; + +public class IdentityDeletionCancelledDomainEventHandler : IDomainEventHandler +{ + private readonly IRelationshipsRepository _relationshipsRepository; + + public IdentityDeletionCancelledDomainEventHandler(IRelationshipsRepository relationshipsRepository) + { + _relationshipsRepository = relationshipsRepository; + } + + public async Task Handle(IdentityDeletionCancelledDomainEvent @event) + { + var relationships = await GetRelationshipsOf(@event.IdentityAddress); + + NotifyRelationshipsAboutCancelledDeletion(@event.IdentityAddress, relationships); + + await _relationshipsRepository.Update(relationships); + } + + private async Task> GetRelationshipsOf(string identityAddress) + { + var relationships = (await _relationshipsRepository + .FindRelationships(r => (r.From == identityAddress || r.To == identityAddress) && r.Status == RelationshipStatus.Active, + CancellationToken.None)).ToList(); + return relationships; + } + + private static void NotifyRelationshipsAboutCancelledDeletion(string identityToBeDeleted, IEnumerable relationships) + { + foreach (var relationship in relationships) + { + relationship.DeletionOfParticipantCancelled(identityToBeDeleted); + } + } +} diff --git a/Modules/Relationships/src/Relationships.Application/DomainEvents/Incoming/IdentityToBeDeleted/IdentityToBeDeletedDomainEventHandler.cs b/Modules/Relationships/src/Relationships.Application/DomainEvents/Incoming/IdentityToBeDeleted/IdentityToBeDeletedDomainEventHandler.cs new file mode 100644 index 0000000000..ecf5dafae7 --- /dev/null +++ b/Modules/Relationships/src/Relationships.Application/DomainEvents/Incoming/IdentityToBeDeleted/IdentityToBeDeletedDomainEventHandler.cs @@ -0,0 +1,42 @@ +using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; +using Backbone.Modules.Relationships.Application.Infrastructure.Persistence.Repository; +using Backbone.Modules.Relationships.Domain.DomainEvents.Incoming; +using Backbone.Modules.Relationships.Domain.Entities; + +namespace Backbone.Modules.Relationships.Application.DomainEvents.Incoming.IdentityToBeDeleted; + +public class IdentityToBeDeletedDomainEventHandler : IDomainEventHandler +{ + private readonly IRelationshipsRepository _relationshipsRepository; + + public IdentityToBeDeletedDomainEventHandler(IRelationshipsRepository relationshipsRepository) + { + _relationshipsRepository = relationshipsRepository; + } + + public async Task Handle(IdentityToBeDeletedDomainEvent @event) + { + var relationships = await GetRelationshipsOf(@event.IdentityAddress); + + NotifyRelationshipsOfPeerToBeDeleted(@event.IdentityAddress, relationships); + + await _relationshipsRepository.Update(relationships); + } + + private async Task> GetRelationshipsOf(string identityAddress) + { + var relationships = (await _relationshipsRepository + .FindRelationships(r => (r.From == identityAddress || r.To == identityAddress) && r.Status == RelationshipStatus.Active, + CancellationToken.None)).ToList(); + + return relationships; + } + + private static void NotifyRelationshipsOfPeerToBeDeleted(string identityToBeDeleted, IEnumerable relationships) + { + foreach (var relationship in relationships) + { + relationship.ParticipantIsToBeDeleted(identityToBeDeleted); + } + } +} diff --git a/Modules/Relationships/src/Relationships.Application/Extensions/IEventBusExtensions.cs b/Modules/Relationships/src/Relationships.Application/Extensions/IEventBusExtensions.cs index bc2d33623e..8c66b94c0a 100644 --- a/Modules/Relationships/src/Relationships.Application/Extensions/IEventBusExtensions.cs +++ b/Modules/Relationships/src/Relationships.Application/Extensions/IEventBusExtensions.cs @@ -1,4 +1,8 @@ using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; +using Backbone.Modules.Relationships.Application.DomainEvents.Incoming.IdentityDeleted; +using Backbone.Modules.Relationships.Application.DomainEvents.Incoming.IdentityDeletionCancelled; +using Backbone.Modules.Relationships.Application.DomainEvents.Incoming.IdentityToBeDeleted; +using Backbone.Modules.Relationships.Domain.DomainEvents.Incoming; namespace Backbone.Modules.Relationships.Application.Extensions; @@ -6,6 +10,14 @@ public static class IEventBusExtensions { public static IEventBus AddRelationshipsDomainEventSubscriptions(this IEventBus eventBus) { + eventBus.SubscribeToIdentitiesEvents(); return eventBus; } + + private static void SubscribeToIdentitiesEvents(this IEventBus eventBus) + { + eventBus.Subscribe(); + eventBus.Subscribe(); + eventBus.Subscribe(); + } } diff --git a/Modules/Relationships/src/Relationships.Application/Extensions/IServiceCollectionExtensions.cs b/Modules/Relationships/src/Relationships.Application/Extensions/IServiceCollectionExtensions.cs index 14f66d67a4..fef7aff125 100644 --- a/Modules/Relationships/src/Relationships.Application/Extensions/IServiceCollectionExtensions.cs +++ b/Modules/Relationships/src/Relationships.Application/Extensions/IServiceCollectionExtensions.cs @@ -1,3 +1,5 @@ +using System.Reflection; +using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; using Backbone.BuildingBlocks.Application.MediatR; using Backbone.Modules.Relationships.Application.AutoMapper; using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.CreateRelationshipTemplate; @@ -19,5 +21,26 @@ public static void AddApplication(this IServiceCollection services) services.AddAutoMapper(typeof(AutoMapperProfile).Assembly); services.AddValidatorsFromAssembly(typeof(CreateRelationshipTemplateCommandValidator).Assembly); + + services.AddEventHandlers(); + } + + private static void AddEventHandlers(this IServiceCollection services) + { + foreach (var eventHandler in GetAllDomainEventHandlers()) + { + services.AddTransient(eventHandler); + } + } + + private static IEnumerable GetAllDomainEventHandlers() + { + var domainEventHandlerTypes = + from t in Assembly.GetExecutingAssembly().GetTypes() + from i in t.GetInterfaces() + where t.IsClass && !t.IsAbstract && i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDomainEventHandler<>) + select t; + + return domainEventHandlerTypes; } } diff --git a/Modules/Relationships/src/Relationships.Application/Infrastructure/Persistence/Repository/IRelationshipsRepository.cs b/Modules/Relationships/src/Relationships.Application/Infrastructure/Persistence/Repository/IRelationshipsRepository.cs index bc3af0c907..e688334574 100644 --- a/Modules/Relationships/src/Relationships.Application/Infrastructure/Persistence/Repository/IRelationshipsRepository.cs +++ b/Modules/Relationships/src/Relationships.Application/Infrastructure/Persistence/Repository/IRelationshipsRepository.cs @@ -10,15 +10,19 @@ namespace Backbone.Modules.Relationships.Application.Infrastructure.Persistence. public interface IRelationshipsRepository { - Task> FindRelationshipsWithIds(IEnumerable ids, IdentityAddress identityAddress, PaginationFilter paginationFilter, CancellationToken cancellationToken, bool track = false); + Task> FindRelationshipsWithIds(IEnumerable ids, IdentityAddress identityAddress, PaginationFilter paginationFilter, + CancellationToken cancellationToken, bool track = false); + Task> FindChangesWithIds(IEnumerable ids, RelationshipChangeType? relationshipChangeType, RelationshipChangeStatus? relationshipChangeStatus, OptionalDateRange? modifiedAt, OptionalDateRange? createdAt, OptionalDateRange? completedAt, IdentityAddress? createdBy, IdentityAddress? completedBy, IdentityAddress activeIdentity, PaginationFilter paginationFilter, CancellationToken cancellationToken, bool onlyPeerChanges = false, bool track = false); + Task FindRelationship(RelationshipId id, IdentityAddress identityAddress, CancellationToken cancellationToken, bool track = false); Task FindRelationshipPeer(RelationshipId id, IdentityAddress identityAddress, CancellationToken cancellationToken); Task FindRelationshipChange(RelationshipChangeId id, IdentityAddress identityAddress, CancellationToken cancellationToken, bool track = false); Task Add(Relationship relationship, CancellationToken cancellationToken); Task Update(Relationship relationship); + Task Update(IEnumerable relationships); Task> FindRelationships(Expression> filter, CancellationToken cancellationToken); Task RelationshipBetweenTwoIdentitiesExists(IdentityAddress identityAddressA, IdentityAddress identityAddressB, CancellationToken cancellationToken); Task DeleteRelationships(Expression> filter, CancellationToken cancellationToken); diff --git a/Modules/Relationships/src/Relationships.Application/Relationships.Application.csproj b/Modules/Relationships/src/Relationships.Application/Relationships.Application.csproj index bf13bf8eb0..d9aec71c4c 100644 --- a/Modules/Relationships/src/Relationships.Application/Relationships.Application.csproj +++ b/Modules/Relationships/src/Relationships.Application/Relationships.Application.csproj @@ -7,6 +7,7 @@ + diff --git a/Modules/Relationships/src/Relationships.Domain/DomainEvents/Incoming/IdentityDeletedDomainEvent.cs b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Incoming/IdentityDeletedDomainEvent.cs new file mode 100644 index 0000000000..3c7f27d6f9 --- /dev/null +++ b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Incoming/IdentityDeletedDomainEvent.cs @@ -0,0 +1,12 @@ +using Backbone.BuildingBlocks.Domain.Events; + +namespace Backbone.Modules.Relationships.Domain.DomainEvents.Incoming; +public class IdentityDeletedDomainEvent : DomainEvent +{ + public IdentityDeletedDomainEvent(string identityAddress) + { + IdentityAddress = identityAddress; + } + + public string IdentityAddress { get; } +} diff --git a/Modules/Relationships/src/Relationships.Domain/DomainEvents/Incoming/IdentityDeletionCancelledDomainEvent.cs b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Incoming/IdentityDeletionCancelledDomainEvent.cs new file mode 100644 index 0000000000..98160f9161 --- /dev/null +++ b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Incoming/IdentityDeletionCancelledDomainEvent.cs @@ -0,0 +1,13 @@ +using Backbone.BuildingBlocks.Domain.Events; + +namespace Backbone.Modules.Relationships.Domain.DomainEvents.Incoming; + +public class IdentityDeletionCancelledDomainEvent : DomainEvent +{ + public IdentityDeletionCancelledDomainEvent(string identityAddress) + { + IdentityAddress = identityAddress; + } + + public string IdentityAddress { get; } +} diff --git a/Modules/Relationships/src/Relationships.Domain/DomainEvents/Incoming/IdentityToBeDeletedDomainEvent.cs b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Incoming/IdentityToBeDeletedDomainEvent.cs new file mode 100644 index 0000000000..d7c470d84f --- /dev/null +++ b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Incoming/IdentityToBeDeletedDomainEvent.cs @@ -0,0 +1,12 @@ +using Backbone.BuildingBlocks.Domain.Events; + +namespace Backbone.Modules.Relationships.Domain.DomainEvents.Incoming; +public class IdentityToBeDeletedDomainEvent : DomainEvent +{ + public IdentityToBeDeletedDomainEvent(string identityAddress) + { + IdentityAddress = identityAddress; + } + + public string IdentityAddress { get; } +} diff --git a/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/PeerDeletedDomainEvent.cs b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/PeerDeletedDomainEvent.cs new file mode 100644 index 0000000000..24b9636edf --- /dev/null +++ b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/PeerDeletedDomainEvent.cs @@ -0,0 +1,18 @@ +using Backbone.BuildingBlocks.Domain.Events; + +namespace Backbone.Modules.Relationships.Domain.DomainEvents.Outgoing; + +public class PeerDeletedDomainEvent : DomainEvent +{ + public PeerDeletedDomainEvent(string peerOfDeletedIdentity, string relationshipId, string deletedIdentity) + : base($"{relationshipId}/peerDeletionCancelled/{deletedIdentity}") + { + PeerOfDeletedIdentity = peerOfDeletedIdentity; + RelationshipId = relationshipId; + DeletedIdentity = deletedIdentity; + } + + public string PeerOfDeletedIdentity { get; } + public string RelationshipId { get; } + public string DeletedIdentity { get; } +} diff --git a/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/PeerDeletionCancelledDomainEvent.cs b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/PeerDeletionCancelledDomainEvent.cs new file mode 100644 index 0000000000..3687466f40 --- /dev/null +++ b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/PeerDeletionCancelledDomainEvent.cs @@ -0,0 +1,18 @@ +using Backbone.BuildingBlocks.Domain.Events; + +namespace Backbone.Modules.Relationships.Domain.DomainEvents.Outgoing; + +public class PeerDeletionCancelledDomainEvent : DomainEvent +{ + public PeerDeletionCancelledDomainEvent(string peerOfIdentityWithDeletionCancelled, string relationshipId, string identityWithDeletionCancelled) + : base($"{relationshipId}/peerDeletionCancelled/{identityWithDeletionCancelled}", randomizeId: true) + { + PeerOfIdentityWithDeletionCancelled = peerOfIdentityWithDeletionCancelled; + RelationshipId = relationshipId; + IdentityWithDeletionCancelled = identityWithDeletionCancelled; + } + + public string PeerOfIdentityWithDeletionCancelled { get; } + public string RelationshipId { get; } + public string IdentityWithDeletionCancelled { get; } +} diff --git a/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/PeerToBeDeletedDomainEvent.cs b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/PeerToBeDeletedDomainEvent.cs new file mode 100644 index 0000000000..c40ef94c4a --- /dev/null +++ b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/PeerToBeDeletedDomainEvent.cs @@ -0,0 +1,17 @@ +using Backbone.BuildingBlocks.Domain.Events; + +namespace Backbone.Modules.Relationships.Domain.DomainEvents.Outgoing; +public class PeerToBeDeletedDomainEvent : DomainEvent +{ + public PeerToBeDeletedDomainEvent(string peerOfIdentityToBeDeleted, string relationshipId, string identityToBeDeleted) + : base($"{relationshipId}/peerToBeDeleted/{identityToBeDeleted}", randomizeId: true) + { + PeerOfIdentityToBeDeleted = peerOfIdentityToBeDeleted; + RelationshipId = relationshipId; + IdentityToBeDeleted = identityToBeDeleted; + } + + public string PeerOfIdentityToBeDeleted { get; } + public string RelationshipId { get; } + public string IdentityToBeDeleted { get; } +} diff --git a/Modules/Relationships/src/Relationships.Domain/Entities/Relationship.cs b/Modules/Relationships/src/Relationships.Domain/Entities/Relationship.cs index 61d8bf5be5..d5d2eedacc 100644 --- a/Modules/Relationships/src/Relationships.Domain/Entities/Relationship.cs +++ b/Modules/Relationships/src/Relationships.Domain/Entities/Relationship.cs @@ -1,6 +1,7 @@ using System.Linq.Expressions; using Backbone.BuildingBlocks.Domain; using Backbone.DevelopmentKit.Identity.ValueObjects; +using Backbone.Modules.Relationships.Domain.DomainEvents.Outgoing; using Backbone.Modules.Relationships.Domain.Errors; using Backbone.Modules.Relationships.Domain.Ids; using Backbone.Tooling; @@ -121,11 +122,6 @@ private static RelationshipStatus GetStatusAfterChange(RelationshipChange change } } - private RelationshipChange? GetPendingChangeOrNull() - { - return _changes.FirstOrDefault(c => c.Status == RelationshipChangeStatus.Pending); - } - public RelationshipChange RequestTermination(IdentityAddress requestedBy, DeviceId requestedByDevice) { EnsureCanBeTerminated(); @@ -147,6 +143,37 @@ private void EnsureCanBeTerminated() throw new DomainException(DomainErrors.PendingChangeAlreadyExists(existingChange.Id)); } + private RelationshipChange? GetPendingChangeOrNull() + { + return _changes.FirstOrDefault(c => c.Status == RelationshipChangeStatus.Pending); + } + + public void ParticipantIsToBeDeleted(string identityToBeDeleted) + { + var peer = GetPeerOf(identityToBeDeleted); + + RaiseDomainEvent(new PeerToBeDeletedDomainEvent(peer, Id, identityToBeDeleted)); + } + + public void DeletionOfParticipantCancelled(string identityWithDeletionCancelled) + { + var peer = GetPeerOf(identityWithDeletionCancelled); + + RaiseDomainEvent(new PeerDeletionCancelledDomainEvent(peer, Id, identityWithDeletionCancelled)); + } + + public void DeletionOfParticipantStarted(string deletedIdentity) + { + var peer = GetPeerOf(deletedIdentity); + + RaiseDomainEvent(new PeerDeletedDomainEvent(peer, Id, deletedIdentity)); + } + + private IdentityAddress GetPeerOf(string identity) + { + return To == identity ? From : To; + } + #region Selectors public static Expression> HasParticipant(string identity) diff --git a/Modules/Relationships/src/Relationships.Infrastructure/Persistence/Database/Repository/RelationshipsRepository.cs b/Modules/Relationships/src/Relationships.Infrastructure/Persistence/Database/Repository/RelationshipsRepository.cs index 12c33443ab..924b323454 100644 --- a/Modules/Relationships/src/Relationships.Infrastructure/Persistence/Database/Repository/RelationshipsRepository.cs +++ b/Modules/Relationships/src/Relationships.Infrastructure/Persistence/Database/Repository/RelationshipsRepository.cs @@ -119,6 +119,11 @@ public async Task Update(Relationship relationship) await _dbContext.SaveChangesAsync(); } + public async Task Update(IEnumerable relationships) + { + _relationships.UpdateRange(relationships); + await _dbContext.SaveChangesAsync(); + } public async Task Add(Relationship relationship, CancellationToken cancellationToken) { diff --git a/Modules/Relationships/test/Relationships.Application.Tests/Relationships.Application.Tests.csproj b/Modules/Relationships/test/Relationships.Application.Tests/Relationships.Application.Tests.csproj index 473f23a428..cc0d3b26a1 100644 --- a/Modules/Relationships/test/Relationships.Application.Tests/Relationships.Application.Tests.csproj +++ b/Modules/Relationships/test/Relationships.Application.Tests/Relationships.Application.Tests.csproj @@ -6,13 +6,13 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Modules/Relationships/test/Relationships.Application.Tests/TestHelpers/TestData.cs b/Modules/Relationships/test/Relationships.Application.Tests/TestHelpers/TestData.cs new file mode 100644 index 0000000000..f4c27e9391 --- /dev/null +++ b/Modules/Relationships/test/Relationships.Application.Tests/TestHelpers/TestData.cs @@ -0,0 +1,26 @@ +using Backbone.DevelopmentKit.Identity.ValueObjects; +using Backbone.Modules.Relationships.Domain.Entities; +using Backbone.UnitTestTools.Data; + +namespace Backbone.Modules.Relationships.Application.Tests.TestHelpers; + +public static class TestData +{ + public static RelationshipTemplate CreateRelationshipTemplate(IdentityAddress? createdBy = null) + { + createdBy ??= TestDataGenerator.CreateRandomIdentityAddress(); + return new RelationshipTemplate(createdBy, TestDataGenerator.CreateRandomDeviceId(), 1, null, []); + } + + public static Relationship CreateActiveRelationship(IdentityAddress? from = null, IdentityAddress? to = null) + { + from ??= TestDataGenerator.CreateRandomIdentityAddress(); + to ??= TestDataGenerator.CreateRandomIdentityAddress(); + + var relationship = new Relationship(CreateRelationshipTemplate(createdBy: to), from, TestDataGenerator.CreateRandomDeviceId(), []); + var change = relationship.Changes.First(); + relationship.AcceptChange(change.Id, to, TestDataGenerator.CreateRandomDeviceId(), []); + + return relationship; + } +} diff --git a/Modules/Relationships/test/Relationships.Application.Tests/Tests/DomainEvents/Incoming/IdentityDeletedDomainEventHandlerTests.cs b/Modules/Relationships/test/Relationships.Application.Tests/Tests/DomainEvents/Incoming/IdentityDeletedDomainEventHandlerTests.cs new file mode 100644 index 0000000000..0f653f7c57 --- /dev/null +++ b/Modules/Relationships/test/Relationships.Application.Tests/Tests/DomainEvents/Incoming/IdentityDeletedDomainEventHandlerTests.cs @@ -0,0 +1,57 @@ +using System.Linq.Expressions; +using Backbone.Modules.Relationships.Application.DomainEvents.Incoming.IdentityDeleted; +using Backbone.Modules.Relationships.Application.Infrastructure.Persistence.Repository; +using Backbone.Modules.Relationships.Application.Tests.TestHelpers; +using Backbone.Modules.Relationships.Domain.DomainEvents.Incoming; +using Backbone.Modules.Relationships.Domain.DomainEvents.Outgoing; +using Backbone.Modules.Relationships.Domain.Entities; +using Backbone.UnitTestTools.BaseClasses; +using Backbone.UnitTestTools.Data; +using Backbone.UnitTestTools.FluentAssertions.Extensions; +using FakeItEasy; +using FluentAssertions; +using Xunit; + +namespace Backbone.Modules.Relationships.Application.Tests.Tests.DomainEvents.Incoming; + +public class IdentityDeletedDomainEventHandlerTests : AbstractTestsBase +{ + [Fact] + public static async Task Publishes_PeerToBeDeletedDomainEvent() + { + //Arrange + var identityToBeDeleted = TestDataGenerator.CreateRandomIdentityAddress(); + + var peer1 = TestDataGenerator.CreateRandomIdentityAddress(); + var peer2 = TestDataGenerator.CreateRandomIdentityAddress(); + + var relationshipToPeer1 = TestData.CreateActiveRelationship(peer1, identityToBeDeleted); + var relationshipToPeer2 = TestData.CreateActiveRelationship(peer2, identityToBeDeleted); + + var fakeRelationshipsRepository = A.Fake(); + + A.CallTo(() => fakeRelationshipsRepository.FindRelationships(A>>._, A._)) + .Returns(new List { relationshipToPeer1, relationshipToPeer2 }); + + var handler = CreateHandler(fakeRelationshipsRepository); + + //Act + await handler.Handle(new IdentityDeletedDomainEvent(identityToBeDeleted)); + + //Assert + var event1 = relationshipToPeer1.Should().HaveASingleDomainEvent(); + event1.PeerOfDeletedIdentity.Should().Be(peer1); + event1.RelationshipId.Should().Be(relationshipToPeer1.Id); + event1.DeletedIdentity.Should().Be(identityToBeDeleted); + + var event2 = relationshipToPeer2.Should().HaveASingleDomainEvent(); + event2.PeerOfDeletedIdentity.Should().Be(peer2); + event2.RelationshipId.Should().Be(relationshipToPeer2.Id); + event2.DeletedIdentity.Should().Be(identityToBeDeleted); + } + + private static IdentityDeletedDomainEventHandler CreateHandler(IRelationshipsRepository relationshipsRepository) + { + return new IdentityDeletedDomainEventHandler(relationshipsRepository); + } +} diff --git a/Modules/Relationships/test/Relationships.Application.Tests/Tests/DomainEvents/Incoming/IdentityDeletionCancelledDomainEventHandlerTests.cs b/Modules/Relationships/test/Relationships.Application.Tests/Tests/DomainEvents/Incoming/IdentityDeletionCancelledDomainEventHandlerTests.cs new file mode 100644 index 0000000000..9adf7dafbb --- /dev/null +++ b/Modules/Relationships/test/Relationships.Application.Tests/Tests/DomainEvents/Incoming/IdentityDeletionCancelledDomainEventHandlerTests.cs @@ -0,0 +1,57 @@ +using System.Linq.Expressions; +using Backbone.Modules.Relationships.Application.DomainEvents.Incoming.IdentityDeletionCancelled; +using Backbone.Modules.Relationships.Application.Infrastructure.Persistence.Repository; +using Backbone.Modules.Relationships.Application.Tests.TestHelpers; +using Backbone.Modules.Relationships.Domain.DomainEvents.Incoming; +using Backbone.Modules.Relationships.Domain.DomainEvents.Outgoing; +using Backbone.Modules.Relationships.Domain.Entities; +using Backbone.UnitTestTools.BaseClasses; +using Backbone.UnitTestTools.Data; +using Backbone.UnitTestTools.FluentAssertions.Extensions; +using FakeItEasy; +using FluentAssertions; +using Xunit; + +namespace Backbone.Modules.Relationships.Application.Tests.Tests.DomainEvents.Incoming; + +public class IdentityDeletionCancelledDomainEventHandlerTests : AbstractTestsBase +{ + [Fact] + public static async Task Publishes_PeerToBeDeletedDomainEvent() + { + //Arrange + var identityWithDeletionCancelled = TestDataGenerator.CreateRandomIdentityAddress(); + + var peer1 = TestDataGenerator.CreateRandomIdentityAddress(); + var peer2 = TestDataGenerator.CreateRandomIdentityAddress(); + + var relationshipToPeer1 = TestData.CreateActiveRelationship(peer1, identityWithDeletionCancelled); + var relationshipToPeer2 = TestData.CreateActiveRelationship(peer2, identityWithDeletionCancelled); + + var fakeRelationshipsRepository = A.Dummy(); + + A.CallTo(() => fakeRelationshipsRepository.FindRelationships(A>>._, A._)) + .Returns(new List { relationshipToPeer1, relationshipToPeer2 }); + + var handler = CreateHandler(fakeRelationshipsRepository); + + //Act + await handler.Handle(new IdentityDeletionCancelledDomainEvent(identityWithDeletionCancelled)); + + //Assert + var event1 = relationshipToPeer1.Should().HaveASingleDomainEvent(); + event1.PeerOfIdentityWithDeletionCancelled.Should().Be(peer1); + event1.RelationshipId.Should().Be(relationshipToPeer1.Id); + event1.IdentityWithDeletionCancelled.Should().Be(identityWithDeletionCancelled); + + var event2 = relationshipToPeer2.Should().HaveASingleDomainEvent(); + event2.PeerOfIdentityWithDeletionCancelled.Should().Be(peer2); + event2.RelationshipId.Should().Be(relationshipToPeer2.Id); + event2.IdentityWithDeletionCancelled.Should().Be(identityWithDeletionCancelled); + } + + private static IdentityDeletionCancelledDomainEventHandler CreateHandler(IRelationshipsRepository relationshipsRepository) + { + return new IdentityDeletionCancelledDomainEventHandler(relationshipsRepository); + } +} diff --git a/Modules/Relationships/test/Relationships.Application.Tests/Tests/DomainEvents/Incoming/IdentityToBeDeletedDomainEventHandlerTests.cs b/Modules/Relationships/test/Relationships.Application.Tests/Tests/DomainEvents/Incoming/IdentityToBeDeletedDomainEventHandlerTests.cs new file mode 100644 index 0000000000..2ca5a9e270 --- /dev/null +++ b/Modules/Relationships/test/Relationships.Application.Tests/Tests/DomainEvents/Incoming/IdentityToBeDeletedDomainEventHandlerTests.cs @@ -0,0 +1,57 @@ +using System.Linq.Expressions; +using Backbone.Modules.Relationships.Application.DomainEvents.Incoming.IdentityToBeDeleted; +using Backbone.Modules.Relationships.Application.Infrastructure.Persistence.Repository; +using Backbone.Modules.Relationships.Application.Tests.TestHelpers; +using Backbone.Modules.Relationships.Domain.DomainEvents.Incoming; +using Backbone.Modules.Relationships.Domain.DomainEvents.Outgoing; +using Backbone.Modules.Relationships.Domain.Entities; +using Backbone.UnitTestTools.BaseClasses; +using Backbone.UnitTestTools.Data; +using Backbone.UnitTestTools.FluentAssertions.Extensions; +using FakeItEasy; +using FluentAssertions; +using Xunit; + +namespace Backbone.Modules.Relationships.Application.Tests.Tests.DomainEvents.Incoming; + +public class IdentityToBeDeletedDomainEventHandlerTests : AbstractTestsBase +{ + [Fact] + public static async Task Publishes_PeerToBeDeletedDomainEvent() + { + //Arrange + var identityToBeDeleted = TestDataGenerator.CreateRandomIdentityAddress(); + + var peer1 = TestDataGenerator.CreateRandomIdentityAddress(); + var peer2 = TestDataGenerator.CreateRandomIdentityAddress(); + + var relationshipToPeer1 = TestData.CreateActiveRelationship(peer1, identityToBeDeleted); + var relationshipToPeer2 = TestData.CreateActiveRelationship(peer2, identityToBeDeleted); + + var fakeRelationshipsRepository = A.Dummy(); + + A.CallTo(() => fakeRelationshipsRepository.FindRelationships(A>>._, A._)) + .Returns(new List() { relationshipToPeer1, relationshipToPeer2 }); + + var handler = CreateHandler(fakeRelationshipsRepository); + + //Act + await handler.Handle(new IdentityToBeDeletedDomainEvent(identityToBeDeleted)); + + //Assert + var event1 = relationshipToPeer1.Should().HaveASingleDomainEvent(); + event1.PeerOfIdentityToBeDeleted.Should().Be(peer1); + event1.RelationshipId.Should().Be(relationshipToPeer1.Id); + event1.IdentityToBeDeleted.Should().Be(identityToBeDeleted); + + var event2 = relationshipToPeer2.Should().HaveASingleDomainEvent(); + event2.PeerOfIdentityToBeDeleted.Should().Be(peer2); + event2.RelationshipId.Should().Be(relationshipToPeer2.Id); + event2.IdentityToBeDeleted.Should().Be(identityToBeDeleted); + } + + private static IdentityToBeDeletedDomainEventHandler CreateHandler(IRelationshipsRepository relationshipsRepository) + { + return new IdentityToBeDeletedDomainEventHandler(relationshipsRepository); + } +} diff --git a/Modules/Relationships/test/Relationships.Domain.Tests/Tests/RelationshipTests.cs b/Modules/Relationships/test/Relationships.Domain.Tests/Tests/RelationshipTests.cs index 7d46cc2e8f..89e8f91683 100644 --- a/Modules/Relationships/test/Relationships.Domain.Tests/Tests/RelationshipTests.cs +++ b/Modules/Relationships/test/Relationships.Domain.Tests/Tests/RelationshipTests.cs @@ -670,6 +670,64 @@ private static Relationship CreateRelationshipWithOpenTermination() } #endregion + + #region DomainEvents + + [Fact] + public void Raises_PeerToBeDeletedDeletedDomainEvent() + { + // Arrange + var identityToBeDeleted = TestDataGenerator.CreateRandomIdentityAddress(); + var peer = TestDataGenerator.CreateRandomIdentityAddress(); + var relationship = CreateActiveRelationship((peer, identityToBeDeleted)); + + // Act + relationship.ParticipantIsToBeDeleted(identityToBeDeleted); + + // Assert + var domainEvent = relationship.Should().HaveASingleDomainEvent(); + domainEvent.PeerOfIdentityToBeDeleted.Should().Be(peer); + domainEvent.RelationshipId.Should().Be(relationship.Id); + domainEvent.IdentityToBeDeleted.Should().Be(identityToBeDeleted); + } + + [Fact] + public void Raises_PeerDeletionCancelledDomainEvent() + { + // Arrange + var identityToBeDeleted = TestDataGenerator.CreateRandomIdentityAddress(); + var peer = TestDataGenerator.CreateRandomIdentityAddress(); + var relationship = CreateActiveRelationship((peer, identityToBeDeleted)); + + // Act + relationship.DeletionOfParticipantCancelled(identityToBeDeleted); + + // Assert + var domainEvent = relationship.Should().HaveASingleDomainEvent(); + domainEvent.PeerOfIdentityWithDeletionCancelled.Should().Be(peer); + domainEvent.RelationshipId.Should().Be(relationship.Id); + domainEvent.IdentityWithDeletionCancelled.Should().Be(identityToBeDeleted); + } + + [Fact] + public void Raises_PeerDeletedDomainEvent() + { + // Arrange + var identityToBeDeleted = TestDataGenerator.CreateRandomIdentityAddress(); + var peer = TestDataGenerator.CreateRandomIdentityAddress(); + var relationship = CreateActiveRelationship((peer, identityToBeDeleted)); + + // Act + relationship.DeletionOfParticipantStarted(identityToBeDeleted); + + // Assert + var domainEvent = relationship.Should().HaveASingleDomainEvent(); + domainEvent.PeerOfDeletedIdentity.Should().Be(peer); + domainEvent.RelationshipId.Should().Be(relationship.Id); + domainEvent.DeletedIdentity.Should().Be(identityToBeDeleted); + } + + #endregion } #region Extensions diff --git a/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/PeerDeleted/PeerDeletedDomainEventHandler.cs b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/PeerDeleted/PeerDeletedDomainEventHandler.cs new file mode 100644 index 0000000000..6e3ea9d09f --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/PeerDeleted/PeerDeletedDomainEventHandler.cs @@ -0,0 +1,39 @@ +using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; +using Backbone.Modules.Synchronization.Application.Infrastructure; +using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.PeerDeleted; +using Backbone.Modules.Synchronization.Domain.Entities.Sync; +using Microsoft.Extensions.Logging; + +namespace Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.PeerDeleted; +public class PeerDeletedDomainEventHandler : IDomainEventHandler +{ + private readonly ISynchronizationDbContext _dbContext; + private readonly ILogger _logger; + + public PeerDeletedDomainEventHandler(ISynchronizationDbContext dbContext, ILogger logger) + { + _dbContext = dbContext; + _logger = logger; + } + + public async Task Handle(PeerDeletedDomainEvent domainEvent) + { + await CreateExternalEvent(domainEvent); + } + + private async Task CreateExternalEvent(PeerDeletedDomainEvent @event) + { +#pragma warning disable IDE0037 + var payload = new { RelationshipId = @event.RelationshipId }; +#pragma warning restore IDE0037 + try + { + await _dbContext.CreateExternalEvent(@event.PeerOfDeletedIdentity, ExternalEventType.PeerDeleted, payload); + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occured while processing a domain event."); + throw; + } + } +} diff --git a/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/PeerDeletionCancelled/PeerDeletionCancelledDomainEventHandler.cs b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/PeerDeletionCancelled/PeerDeletionCancelledDomainEventHandler.cs new file mode 100644 index 0000000000..ac7ebf728c --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/PeerDeletionCancelled/PeerDeletionCancelledDomainEventHandler.cs @@ -0,0 +1,40 @@ +using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; +using Backbone.Modules.Synchronization.Application.Infrastructure; +using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.PeerDeletionCancelled; +using Backbone.Modules.Synchronization.Domain.Entities.Sync; +using Microsoft.Extensions.Logging; + +namespace Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.PeerDeletionCancelled; + +public class PeerDeletionCancelledDomainEventHandler : IDomainEventHandler +{ + private readonly ISynchronizationDbContext _dbContext; + private readonly ILogger _logger; + + public PeerDeletionCancelledDomainEventHandler(ISynchronizationDbContext dbContext, ILogger logger) + { + _dbContext = dbContext; + _logger = logger; + } + + public async Task Handle(PeerDeletionCancelledDomainEvent domainEvent) + { + await CreateExternalEvent(domainEvent); + } + + private async Task CreateExternalEvent(PeerDeletionCancelledDomainEvent @event) + { +#pragma warning disable IDE0037 + var payload = new { RelationshipId = @event.RelationshipId }; +#pragma warning restore IDE0037 + try + { + await _dbContext.CreateExternalEvent(@event.PeerOfIdentityWithDeletionCancelled, ExternalEventType.PeerDeletionCancelled, payload); + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occured while processing a domain event."); + throw; + } + } +} diff --git a/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/PeerToBeDeleted/PeerToBeDeletedDomainEventHandler.cs b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/PeerToBeDeleted/PeerToBeDeletedDomainEventHandler.cs new file mode 100644 index 0000000000..8708595ef5 --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/PeerToBeDeleted/PeerToBeDeletedDomainEventHandler.cs @@ -0,0 +1,39 @@ +using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; +using Backbone.Modules.Synchronization.Application.Infrastructure; +using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.PeerToBeDeleted; +using Backbone.Modules.Synchronization.Domain.Entities.Sync; +using Microsoft.Extensions.Logging; + +namespace Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.PeerToBeDeleted; +public class PeerToBeDeletedDomainEventHandler : IDomainEventHandler +{ + private readonly ISynchronizationDbContext _dbContext; + private readonly ILogger _logger; + + public PeerToBeDeletedDomainEventHandler(ISynchronizationDbContext dbContext, ILogger logger) + { + _dbContext = dbContext; + _logger = logger; + } + + public async Task Handle(PeerToBeDeletedDomainEvent domainEvent) + { + await CreateExternalEvent(domainEvent); + } + + private async Task CreateExternalEvent(PeerToBeDeletedDomainEvent @event) + { +#pragma warning disable IDE0037 + var payload = new { RelationshipId = @event.RelationshipId }; +#pragma warning restore IDE0037 + try + { + await _dbContext.CreateExternalEvent(@event.PeerOfIdentityToBeDeleted, ExternalEventType.PeerToBeDeleted, payload); + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occured while processing a domain event."); + throw; + } + } +} diff --git a/Modules/Synchronization/src/Synchronization.Application/Extensions/IEventBusExtensions.cs b/Modules/Synchronization/src/Synchronization.Application/Extensions/IEventBusExtensions.cs index 7d7a2faf3f..40b2645cd5 100644 --- a/Modules/Synchronization/src/Synchronization.Application/Extensions/IEventBusExtensions.cs +++ b/Modules/Synchronization/src/Synchronization.Application/Extensions/IEventBusExtensions.cs @@ -2,11 +2,17 @@ using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.IdentityDeletionProcessStarted; using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.IdentityDeletionProcessStatusChanged; using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.MessageCreated; +using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.PeerDeleted; +using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.PeerDeletionCancelled; +using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.PeerToBeDeleted; using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.RelationshipChangeCompleted; using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.RelationshipChangeCreated; using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.IdentityDeletionProcessStarted; using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.IdentityDeletionProcessStatusChanged; using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.MessageCreated; +using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.PeerDeleted; +using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.PeerDeletionCancelled; +using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.PeerToBeDeleted; using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.RelationshipChangeCompleted; using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.RelationshipChangeCreated; @@ -34,5 +40,8 @@ private static void SubscribeToRelationshipsEvents(IEventBus eventBus) { eventBus.Subscribe(); eventBus.Subscribe(); + eventBus.Subscribe(); + eventBus.Subscribe(); + eventBus.Subscribe(); } } diff --git a/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/PeerDeleted/PeerDeletedDomainEvent.cs b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/PeerDeleted/PeerDeletedDomainEvent.cs new file mode 100644 index 0000000000..65bf51ddd7 --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/PeerDeleted/PeerDeletedDomainEvent.cs @@ -0,0 +1,16 @@ +using Backbone.BuildingBlocks.Domain.Events; + +namespace Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.PeerDeleted; +public class PeerDeletedDomainEvent : DomainEvent +{ + public PeerDeletedDomainEvent(string peerOfDeletedIdentity, string relationshipId, string deletedIdentity) + { + PeerOfDeletedIdentity = peerOfDeletedIdentity; + RelationshipId = relationshipId; + DeletedIdentity = deletedIdentity; + } + + public string PeerOfDeletedIdentity { get; } + public string RelationshipId { get; } + public string DeletedIdentity { get; } +} diff --git a/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/PeerDeletionCancelled/PeerDeletionCancelledDomainEvent.cs b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/PeerDeletionCancelled/PeerDeletionCancelledDomainEvent.cs new file mode 100644 index 0000000000..68d4c521e7 --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/PeerDeletionCancelled/PeerDeletionCancelledDomainEvent.cs @@ -0,0 +1,17 @@ +using Backbone.BuildingBlocks.Domain.Events; + +namespace Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.PeerDeletionCancelled; + +public class PeerDeletionCancelledDomainEvent : DomainEvent +{ + public PeerDeletionCancelledDomainEvent(string peerOfIdentityWithDeletionCancelled, string relationshipId, string identityWithDeletionCancelled) + { + PeerOfIdentityWithDeletionCancelled = peerOfIdentityWithDeletionCancelled; + RelationshipId = relationshipId; + IdentityWithDeletionCancelled = identityWithDeletionCancelled; + } + + public string PeerOfIdentityWithDeletionCancelled { get; } + public string RelationshipId { get; } + public string IdentityWithDeletionCancelled { get; } +} diff --git a/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/PeerToBeDeleted/PeerToBeDeletedDomainEvent.cs b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/PeerToBeDeleted/PeerToBeDeletedDomainEvent.cs new file mode 100644 index 0000000000..ab5db2655b --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/PeerToBeDeleted/PeerToBeDeletedDomainEvent.cs @@ -0,0 +1,16 @@ +using Backbone.BuildingBlocks.Domain.Events; + +namespace Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.PeerToBeDeleted; +public class PeerToBeDeletedDomainEvent : DomainEvent +{ + public PeerToBeDeletedDomainEvent(string peerOfIdentityToBeDeleted, string relationshipId, string identityToBeDeleted) + { + PeerOfIdentityToBeDeleted = peerOfIdentityToBeDeleted; + RelationshipId = relationshipId; + IdentityToBeDeleted = identityToBeDeleted; + } + + public string PeerOfIdentityToBeDeleted { get; } + public string RelationshipId { get; } + public string IdentityToBeDeleted { get; } +} diff --git a/Modules/Synchronization/src/Synchronization.Domain/Entities/Sync/ExternalEvent.cs b/Modules/Synchronization/src/Synchronization.Domain/Entities/Sync/ExternalEvent.cs index 78d6875a9a..152f85a6cd 100644 --- a/Modules/Synchronization/src/Synchronization.Domain/Entities/Sync/ExternalEvent.cs +++ b/Modules/Synchronization/src/Synchronization.Domain/Entities/Sync/ExternalEvent.cs @@ -65,5 +65,8 @@ public enum ExternalEventType RelationshipChangeCreated = 2, RelationshipChangeCompleted = 3, IdentityDeletionProcessStarted = 4, - IdentityDeletionProcessStatusChanged = 5 + IdentityDeletionProcessStatusChanged = 5, + PeerToBeDeleted = 6, + PeerDeletionCancelled = 7, + PeerDeleted = 8 } diff --git a/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/PeerDeletedDomainEventHandlerTests.cs b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/PeerDeletedDomainEventHandlerTests.cs new file mode 100644 index 0000000000..c4285c1cb5 --- /dev/null +++ b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/PeerDeletedDomainEventHandlerTests.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Backbone.DevelopmentKit.Identity.ValueObjects; +using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.PeerDeleted; +using Backbone.Modules.Synchronization.Application.Infrastructure; +using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.PeerDeleted; +using Backbone.Modules.Synchronization.Domain.Entities.Sync; +using Backbone.UnitTestTools.BaseClasses; +using FakeItEasy; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Backbone.Modules.Synchronization.Application.Tests.Tests.DomainEvents; +public class PeerDeletedDomainEventHandlerTests : AbstractTestsBase +{ + [Fact] + public async Task Creates_an_external_event() + { + // Arrange + var peerOfDeletedIdentity = TestDataGenerator.CreateRandomIdentityAddress(); + var peerDeletedDomainEvent = new PeerDeletedDomainEvent(peerOfDeletedIdentity, "some-relationship-id", "some-deletedIdentity-id"); + + var mockDbContext = A.Fake(); + + var externalEvent = new ExternalEvent(ExternalEventType.PeerDeleted, IdentityAddress.Parse(peerOfDeletedIdentity), 1, + new { peerDeletedDomainEvent.RelationshipId }); + + A.CallTo(() => mockDbContext.CreateExternalEvent( + peerOfDeletedIdentity, + ExternalEventType.PeerDeleted, + A._) + ).Returns(externalEvent); + + var handler = new PeerDeletedDomainEventHandler(mockDbContext, + A.Fake>()); + + // Act + await handler.Handle(peerDeletedDomainEvent); + + // Assert + A.CallTo(() => mockDbContext.CreateExternalEvent(peerOfDeletedIdentity, ExternalEventType.PeerDeleted, A._)) + .MustHaveHappenedOnceExactly(); + } +} diff --git a/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/PeerDeletionCancelledDomainEventHandlerTests.cs b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/PeerDeletionCancelledDomainEventHandlerTests.cs new file mode 100644 index 0000000000..3b0af759c9 --- /dev/null +++ b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/PeerDeletionCancelledDomainEventHandlerTests.cs @@ -0,0 +1,43 @@ +using Backbone.DevelopmentKit.Identity.ValueObjects; +using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.PeerDeletionCancelled; +using Backbone.Modules.Synchronization.Application.Infrastructure; +using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.PeerDeletionCancelled; +using Backbone.Modules.Synchronization.Domain.Entities.Sync; +using Backbone.UnitTestTools.BaseClasses; +using FakeItEasy; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Backbone.Modules.Synchronization.Application.Tests.Tests.DomainEvents; + +public class PeerDeletionCancelledDomainEventHandlerTests : AbstractTestsBase +{ + [Fact] + public async Task Creates_an_external_event() + { + // Arrange + var peerOfIdentityWithDeletionCancelled = TestDataGenerator.CreateRandomIdentityAddress(); + var domainEvent = new PeerDeletionCancelledDomainEvent(peerOfIdentityWithDeletionCancelled, "some-relationship-id", "some-deletedIdentity-id"); + + var mockDbContext = A.Fake(); + + var externalEvent = new ExternalEvent(ExternalEventType.PeerDeletionCancelled, IdentityAddress.Parse(peerOfIdentityWithDeletionCancelled), 1, + new { domainEvent.RelationshipId }); + + A.CallTo(() => mockDbContext.CreateExternalEvent( + peerOfIdentityWithDeletionCancelled, + ExternalEventType.PeerDeletionCancelled, + A._) + ).Returns(externalEvent); + + var handler = new PeerDeletionCancelledDomainEventHandler(mockDbContext, + A.Fake>()); + + // Act + await handler.Handle(domainEvent); + + // Assert + A.CallTo(() => mockDbContext.CreateExternalEvent(peerOfIdentityWithDeletionCancelled, ExternalEventType.PeerDeletionCancelled, A._)) + .MustHaveHappenedOnceExactly(); + } +} diff --git a/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/PeerToBeDeletedDomainEventHandlerTests.cs b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/PeerToBeDeletedDomainEventHandlerTests.cs new file mode 100644 index 0000000000..d7e5719be0 --- /dev/null +++ b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/PeerToBeDeletedDomainEventHandlerTests.cs @@ -0,0 +1,42 @@ +using Backbone.DevelopmentKit.Identity.ValueObjects; +using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.PeerToBeDeleted; +using Backbone.Modules.Synchronization.Application.Infrastructure; +using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.PeerToBeDeleted; +using Backbone.Modules.Synchronization.Domain.Entities.Sync; +using Backbone.UnitTestTools.BaseClasses; +using FakeItEasy; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Backbone.Modules.Synchronization.Application.Tests.Tests.DomainEvents; +public class PeerToBeDeletedDomainEventHandlerTests : AbstractTestsBase +{ + [Fact] + public async Task Creates_an_external_event() + { + // Arrange + var peerOfToBeDeletedIdentity = TestDataGenerator.CreateRandomIdentityAddress(); + var peerToBeDeletedDomainEvent = new PeerToBeDeletedDomainEvent(peerOfToBeDeletedIdentity, "some-relationship-id", "some-deletedIdentity-id"); + + var mockDbContext = A.Fake(); + + var externalEvent = new ExternalEvent(ExternalEventType.PeerToBeDeleted, IdentityAddress.Parse(peerOfToBeDeletedIdentity), 1, + new { peerToBeDeletedDomainEvent.RelationshipId }); + + A.CallTo(() => mockDbContext.CreateExternalEvent( + peerOfToBeDeletedIdentity, + ExternalEventType.PeerToBeDeleted, + A._) + ).Returns(externalEvent); + + var handler = new PeerToBeDeletedDomainEventHandler(mockDbContext, + A.Fake>()); + + // Act + await handler.Handle(peerToBeDeletedDomainEvent); + + // Assert + A.CallTo(() => mockDbContext.CreateExternalEvent(peerOfToBeDeletedIdentity, ExternalEventType.PeerToBeDeleted, A._)) + .MustHaveHappenedOnceExactly(); + } +}