Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create External Events for peers of Identity to be deleted, Identity with canceled deletion process and deleted Identity #699

Merged
merged 41 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
f92ff49
refactor: remove event bus from StartDeletionProcessAsOwnerCommand ha…
NikolaDmitrasinovic Jun 13, 2024
dbb33c8
feat: add outgoing domain event for `ToBeDeleted` status change
NikolaDmitrasinovic Jun 13, 2024
af21f86
Merge branch 'main' into external-events-for-peers-of-identity-to-be-…
NikolaDmitrasinovic Jun 13, 2024
bea474f
feat: add domain event for ToBeDeleted status
NikolaDmitrasinovic Jun 13, 2024
ae25211
test: add domain event test
NikolaDmitrasinovic Jun 13, 2024
74ce597
test: remove test
NikolaDmitrasinovic Jun 13, 2024
cf69ad3
feat: raise domain event when identity deletion stats
NikolaDmitrasinovic Jun 14, 2024
c3c61cd
refactor: remove domain event from actual deletion worker
NikolaDmitrasinovic Jun 14, 2024
3680600
feat: raise doman event when deletion process is canceled
NikolaDmitrasinovic Jun 14, 2024
e23e719
refactor: improve code structure of Devices\Domain Tests
NikolaDmitrasinovic Jun 14, 2024
8fc67fe
feat: handle IdentityToBeDeleted domain event, Relationship module
NikolaDmitrasinovic Jun 14, 2024
4654391
feat: raise PeerDeletionCanceledDomainEvent
NikolaDmitrasinovic Jun 14, 2024
5a2168e
feat: raise PeerDeletedDomainEvent
NikolaDmitrasinovic Jun 14, 2024
60b80af
feat: handle PeerToBeDeleted domain event and create external event
NikolaDmitrasinovic Jun 14, 2024
e7df6fa
feat: handle PeerDeletionCanceledDomainEvent and create external event
NikolaDmitrasinovic Jun 14, 2024
81cbe3a
feat: handle PeerDeletedDomainEvent and create external event
NikolaDmitrasinovic Jun 14, 2024
1f4ba98
chore: fix formatting issues
NikolaDmitrasinovic Jun 14, 2024
51af859
refactor: various small improvements
NikolaDmitrasinovic Jun 14, 2024
bfa2b5f
refactor: improve code structure of Identity
NikolaDmitrasinovic Jun 14, 2024
a558f2a
chore: fix message and method name in EntityAssertions
NikolaDmitrasinovic Jun 14, 2024
44aeadc
chore: change type
NikolaDmitrasinovic Jun 14, 2024
73a1968
chore: remove empty lines
NikolaDmitrasinovic Jun 14, 2024
954906b
chroe: rename var
NikolaDmitrasinovic Jun 14, 2024
b84e170
chore: rename var
NikolaDmitrasinovic Jun 14, 2024
7082125
Merge branch 'main' into external-events-for-peers-of-identity-to-be-…
mergify[bot] Jun 17, 2024
c07cd1a
Merge branch 'main' into external-events-for-peers-of-identity-to-be-…
mergify[bot] Jun 17, 2024
6b60ca4
Merge branch 'main' into external-events-for-peers-of-identity-to-be-…
mergify[bot] Jun 17, 2024
7e18f89
test: improve HaveDomainEvents and its usings
tnotheis Jun 17, 2024
39c3ed2
test: directly test for QueuedForDeletion tier
tnotheis Jun 17, 2024
79c91f3
refactor: rename Canceled to Cancelled
tnotheis Jun 17, 2024
716db9c
Merge branch 'main' into external-events-for-peers-of-identity-to-be-…
mergify[bot] Jun 17, 2024
e013401
refactor: renaming of private methods
tnotheis Jun 17, 2024
b88a449
chore: remove IChangeLogExtensions
tnotheis Jun 17, 2024
1f0a1c6
test: rename some variables
tnotheis Jun 17, 2024
d7b5fcf
refactor: rename canceled to cancelled
tnotheis Jun 17, 2024
8548996
ci: let publish-helm-chart depend on publish-sse-server, publish-iden…
tnotheis Jun 18, 2024
dd565fa
Merge branch 'main' into external-events-for-peers-of-identity-to-be-…
mergify[bot] Jun 18, 2024
c837b84
Merge branch 'main' into external-events-for-peers-of-identity-to-be-…
mergify[bot] Jun 18, 2024
fc8cf69
test: minor simplification in EntitiesShouldHaveEmptyDefaultConstruct…
tnotheis Jun 18, 2024
0436558
feat: subscribe for identities events in relationships module
tnotheis Jun 18, 2024
44ca54e
feat: call Update in Relationships event handlers in order to publish…
tnotheis Jun 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,35 @@ public TEvent HaveASingleDomainEvent<TEvent>(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<TEvent1, TEvent2>(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))
);
}
}
19 changes: 1 addition & 18 deletions Jobs/src/Job.IdentityDeletion/Workers/ActualDeletionWorker.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
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;

namespace Backbone.Job.IdentityDeletion.Workers;

public class ActualDeletionWorker : IHostedService
{
private readonly IEventBus _eventBus;
private readonly IHostApplicationLifetime _host;
private readonly IEnumerable<IIdentityDeleter> _identityDeleters;
private readonly IMediator _mediator;
Expand All @@ -25,14 +21,12 @@ public ActualDeletionWorker(IHostApplicationLifetime host,
IEnumerable<IIdentityDeleter> identityDeleters,
IMediator mediator,
IPushNotificationSender pushNotificationSender,
IEventBus eventBus,
ILogger<ActualDeletionWorker> logger)
{
_host = host;
_identityDeleters = identityDeleters;
_mediator = mediator;
_pushNotificationSender = pushNotificationSender;
_eventBus = eventBus;
_logger = logger;
}

Expand Down Expand Up @@ -70,7 +64,6 @@ private async Task ExecuteDeletion(IEnumerable<IdentityAddress> addresses, Cance
private async Task ExecuteDeletion(CancellationToken cancellationToken, IdentityAddress identityAddress)
{
await NotifyIdentityAboutStartingDeletion(cancellationToken, identityAddress);
await NotifyRelationshipsAboutStartingDeletion(identityAddress, cancellationToken);
await Delete(identityAddress);
}

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<IIdentityDeleter>? identityDeleters = null,
IEventBus? eventBus = null,
IPushNotificationSender? pushNotificationSender = null)
{
var hostApplicationLifetime = A.Dummy<IHostApplicationLifetime>();
identityDeleters ??= [A.Dummy<IIdentityDeleter>()];
eventBus ??= A.Dummy<IEventBus>();
pushNotificationSender ??= A.Dummy<IPushNotificationSender>();
var logger = A.Dummy<ILogger<ActualDeletionWorker>>();
return new ActualDeletionWorker(hostApplicationLifetime, identityDeleters, mediator, pushNotificationSender, eventBus, logger);
return new ActualDeletionWorker(hostApplicationLifetime, identityDeleters, mediator, pushNotificationSender, logger);
}
}
Original file line number Diff line number Diff line change
@@ -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; }
}
Original file line number Diff line number Diff line change
@@ -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; }
}
Original file line number Diff line number Diff line change
@@ -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; }
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public IdentityDeletionProcess StartDeletionProcessAsOwner(DeviceId asDevice)
DeletionGracePeriodEndsAt = deletionProcess.GracePeriodEndsAt;
TierId = Tier.QUEUED_FOR_DELETION.Id;
Status = IdentityStatus.ToBeDeleted;
RaiseDomainEvent(new IdentityToBeDeletedDomainEvent(Address));

return deletionProcess;
}
Expand Down Expand Up @@ -156,6 +157,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;

Expand All @@ -169,22 +171,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)
Expand Down Expand Up @@ -264,47 +251,41 @@ public static Expression<Func<Identity, bool>> 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)));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Identity>.That.Matches(i =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -74,15 +73,15 @@ 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<NotFoundException, StartDeletionProcessAsOwnerResponse>().Which.Message.Should().Contain("Identity");
}

private static Handler CreateHandler(IIdentitiesRepository identitiesRepository, IUserContext userContext, IPushNotificationSender? pushNotificationSender = null)
{
return new Handler(identitiesRepository, userContext, pushNotificationSender ?? A.Dummy<IPushNotificationSender>());
pushNotificationSender ??= A.Dummy<IPushNotificationSender>();
return new Handler(identitiesRepository, userContext, pushNotificationSender);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand All @@ -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);
Expand Down
Loading