Skip to content

Commit

Permalink
Queueing of external events while identity is in status ToBeDeleted (
Browse files Browse the repository at this point in the history
…#910)

* feat: enable sending messages to peers that are in status `ToBeDeleted`

* feat: allow changing relationships with `ToBeDeleted` peers

* chore: update integration tests

* feat: identities in status `ToBeDeleted` can't start a new sync run

* refactor: remove address variable

* feat: identities in status `ToBeDeleted` are not notified via push notifications

* fix: actual deletion worker

* test: fix tests

* refactor: use void instead `IEventBus`

* test: fix missing parameter

* test: dont send notifications when identity is in status to be deleted

* fix: relationship created by i1

* test: fix & improve tests

* refactor: utilize `await` and remove `.Result` when finding an `Identity`

* chore: test formatting

* refactor: utilize `await` and rename application error

* chore: formatting

* chore: remove unused using

* chore: remove unused unsings

* chore: remove unused using

* refactor: utilize `await` instead of `.Result`

* chore: rename application error

* chore: remove redundant identity status check

* chore: formatting

* chore: rename

* chore: rename error message

* chore: remove redundant `TestDataGenerator`

* test: update test

* refactor: status check

* refactor: implement EnsureIdentityIsToBeDeleted

* refactor: remove redundant method

* refactor: implement not found exception

* fix: unblock notifications

* chore: refactor

* test: improve tests

* chore: rename test

* test: fix tests

* chore: fix formatting

* test: refactor tests & add `TestDataGenerator`

* fix: remove unnecessary null check

* chore: fix domain error message

* chore: fix accidentally removed blank line

* chore: fix formatting

* chore: rename variable

* chore: rename method

* chore: revert & remove code

* fix: remove status check from PNS handle

* chore: remove unused code

* refactor: SyncRunsController

* chore: undo unnecessary changes to this file

* chore: cleanup

* feat: block push notifications

* refactor: block starting a sync run for identities in status `ToBeDeleted`

* chore: fix formatting

* test: improve & fix tests

* test: add new test

* fix: remove user context

* refactor: split step definitions

* test: add assignment to WhenResponse

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Timo Notheisen <[email protected]>
  • Loading branch information
3 people authored Oct 23, 2024
1 parent 90d6f9b commit 9dbd785
Show file tree
Hide file tree
Showing 15 changed files with 98 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ Identity sends a Message
And an active Relationship r12 between i1 and i2
And i2 is in status "ToBeDeleted"
When i1 sends a POST request to the /Messages endpoint with i2 as recipient
Then the response status code is 400 (Bad Request)
And the response content contains an error with the error code "error.platform.validation.message.recipientToBeDeleted"
And the error contains a list of Identities to be deleted that includes i2
Then the response status code is 201 (Created)
And the response contains a SendMessageResponse

Scenario: Sending a Message to a terminated Relationship
Given Identities i1 and i2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ User accepts a Relationship
And a pending Relationship r between i1 and i2 created by i2
And i2 is in status "ToBeDeleted"
When i1 sends a PUT request to the /Relationships/{r.Id}/Accept endpoint
Then the response status code is 400 (Bad Request)
And the response content contains an error with the error code "error.platform.validation.relationship.peerIsToBeDeleted"
Then the response status code is 200 (OK)
And the response contains a RelationshipMetadata
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ User rejects a Relationship
And a pending Relationship r between i1 and i2 created by i2
And i2 is in status "ToBeDeleted"
When i1 sends a PUT request to the /Relationships/{r.Id}/Reject endpoint
Then the response status code is 400 (Bad Request)
And the response content contains an error with the error code "error.platform.validation.relationship.peerIsToBeDeleted"
Then the response status code is 200 (OK)
And the response contains a RelationshipMetadata
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ User revokes a Relationship

Scenario: Revoke Relationship to an Identity in status "ToBeDeleted"
Given Identities i1 and i2
And a pending Relationship r between i1 and i2 created by i2
And a pending Relationship r between i1 and i2 created by i1
And i2 is in status "ToBeDeleted"
When i1 sends a PUT request to the /Relationships/{r.Id}/Revoke endpoint
Then the response status code is 400 (Bad Request)
And the response content contains an error with the error code "error.platform.validation.relationship.peerIsToBeDeleted"
Then the response status code is 200 (OK)
And the response contains a RelationshipMetadata
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,28 @@ public async Task WhenIdentitySendsAPostRequestToTheRelationshipsEndpointWithRel
await client.Relationships.CreateRelationship(new CreateRelationshipRequest { RelationshipTemplateId = relationshipTemplateId, Content = TestData.SOME_BYTES });
}

[When($"{RegexFor.SINGLE_THING} sends a PUT request to the /Relationships/{{{RegexFor.SINGLE_THING}.Id}}/(Accept|Reject|Revoke) endpoint")]
public async Task WhenIdentitySendsAPostRequestToTheRelationshipsIdEndpoint(string identityName, string relationshipName, string requestType)
[When($"{RegexFor.SINGLE_THING} sends a PUT request to the /Relationships/{{{RegexFor.SINGLE_THING}.Id}}/Accept endpoint")]
public async Task WhenIdentitySendsAPostRequestToTheRelationshipsIdAcceptEndpoint(string identityName, string relationshipName)
{
var client = _clientPool.FirstForIdentityName(identityName);
var relationship = _relationshipsContext.Relationships[relationshipName];
_responseContext.WhenResponse = await client.Relationships.AcceptRelationship(relationship.Id, new AcceptRelationshipRequest { CreationResponseContent = TestData.SOME_BYTES });
}

_responseContext.WhenResponse = requestType switch
{
"Accept" => await client.Relationships.AcceptRelationship(_relationshipsContext.Relationships[relationshipName].Id,
new AcceptRelationshipRequest { CreationResponseContent = TestData.SOME_BYTES }),
"Reject" => await client.Relationships.RejectRelationship(_relationshipsContext.Relationships[relationshipName].Id,
new RejectRelationshipRequest { CreationResponseContent = TestData.SOME_BYTES }),
"Revoke" => await client.Relationships.RevokeRelationship(_relationshipsContext.Relationships[relationshipName].Id,
new RevokeRelationshipRequest { CreationResponseContent = TestData.SOME_BYTES }),
_ => throw new NotSupportedException($"Unsupported request type: {requestType}")
};
[When($"{RegexFor.SINGLE_THING} sends a PUT request to the /Relationships/{{{RegexFor.SINGLE_THING}.Id}}/Reject endpoint")]
public async Task WhenIdentitySendsAPostRequestToTheRelationshipsIdRejectEndpoint(string identityName, string relationshipName)
{
var client = _clientPool.FirstForIdentityName(identityName);
var relationship = _relationshipsContext.Relationships[relationshipName];
_responseContext.WhenResponse = await client.Relationships.RejectRelationship(relationship.Id, new RejectRelationshipRequest { CreationResponseContent = TestData.SOME_BYTES });
}

[When($"{RegexFor.SINGLE_THING} sends a PUT request to the /Relationships/{{{RegexFor.SINGLE_THING}.Id}}/Revoke endpoint")]
public async Task WhenIdentitySendsAPostRequestToTheRelationshipsIdRevokeEndpoint(string identityName, string relationshipName)
{
var client = _clientPool.FirstForIdentityName(identityName);
var relationship = _relationshipsContext.Relationships[relationshipName];
_responseContext.WhenResponse = await client.Relationships.RevokeRelationship(relationship.Id, new RevokeRelationshipRequest { CreationResponseContent = TestData.SOME_BYTES });
}

[When($"{RegexFor.SINGLE_THING} sends a PUT request to the /Relationships/{{{RegexFor.SINGLE_THING}.Id}}/Terminate endpoint")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Backbone.Job.IdentityDeletion.Workers;
using Backbone.Modules.Devices.Application.Identities.Commands.TriggerRipeDeletionProcesses;
using Backbone.Modules.Relationships.Application.Relationships.Queries.FindRelationshipsOfIdentity;
using Backbone.Modules.Relationships.Domain.Aggregates.Relationships;
using CSharpFunctionalExtensions;
using FakeItEasy;
using MediatR;
Expand Down Expand Up @@ -45,7 +44,7 @@ public async Task Calls_Deleters_For_Each_Identity()
var worker = CreateWorker(fakeMediator, [mockIdentityDeleter]);

A.CallTo(() => fakeMediator.Send(A<FindRelationshipsOfIdentityQuery>._, A<CancellationToken>._))
.Returns(new FindRelationshipsOfIdentityResponse(new List<Relationship>()));
.Returns(new FindRelationshipsOfIdentityResponse([]));

// Act
await worker.StartProcessing(CancellationToken.None);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
using Backbone.BuildingBlocks.Application.Abstractions.Exceptions;
using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus;
using Backbone.BuildingBlocks.Application.PushNotifications;
using Backbone.Modules.Devices.Application.Infrastructure.Persistence.Repository;
using Backbone.Modules.Devices.Application.Infrastructure.PushNotifications.ExternalEvents;
using Backbone.Modules.Devices.Domain.DomainEvents.Incoming.ExternalEventCreated;
using Backbone.Modules.Devices.Domain.Entities.Identities;

namespace Backbone.Modules.Devices.Application.DomainEvents.Incoming.ExternalEventCreated;

public class ExternalEventCreatedDomainEventHandler : IDomainEventHandler<ExternalEventCreatedDomainEvent>
{
private readonly IPushNotificationSender _pushSenderService;
private readonly IIdentitiesRepository _identitiesRepository;

public ExternalEventCreatedDomainEventHandler(IPushNotificationSender pushSenderService)
public ExternalEventCreatedDomainEventHandler(IPushNotificationSender pushSenderService, IIdentitiesRepository identitiesRepository)
{
_pushSenderService = pushSenderService;
_identitiesRepository = identitiesRepository;
}

public async Task Handle(ExternalEventCreatedDomainEvent @event)
{
if (@event.IsDeliveryBlocked)
return;

await _pushSenderService.SendNotification(@event.Owner, new ExternalEventCreatedPushNotification(), CancellationToken.None);
var identity = await _identitiesRepository.FindByAddress(@event.Owner, CancellationToken.None) ?? throw new NotFoundException(nameof(Identity));

if (identity.Status != IdentityStatus.ToBeDeleted)
await _pushSenderService.SendNotification(@event.Owner, new ExternalEventCreatedPushNotification(), CancellationToken.None);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Backbone.BuildingBlocks.Application.PushNotifications;
using Backbone.Modules.Devices.Application.DomainEvents.Incoming.ExternalEventCreated;
using Backbone.Modules.Devices.Application.Infrastructure.Persistence.Repository;
using Backbone.Modules.Devices.Application.Infrastructure.PushNotifications.ExternalEvents;
using Backbone.Modules.Devices.Domain.DomainEvents.Incoming.ExternalEventCreated;
using FakeItEasy;
Expand All @@ -13,28 +14,59 @@ public async Task Sends_a_push_notification_to_the_owner_of_the_external_event()
{
// Arrange
var mockPushSender = A.Fake<IPushNotificationSender>();
var fakeIdentitiesRepository = A.Fake<IIdentitiesRepository>();
var identity = TestDataGenerator.CreateIdentity();

var handler = new ExternalEventCreatedDomainEventHandler(mockPushSender);
var handler = new ExternalEventCreatedDomainEventHandler(mockPushSender, fakeIdentitiesRepository);

var externalEventOwner = CreateRandomIdentityAddress();

A.CallTo(() => fakeIdentitiesRepository.FindByAddress(externalEventOwner, A<CancellationToken>._, A<bool>._)).Returns(identity);

// Act
await handler.Handle(new ExternalEventCreatedDomainEvent { Owner = externalEventOwner, IsDeliveryBlocked = false });

// Assert
A.CallTo(() => mockPushSender.SendNotification(externalEventOwner, A<ExternalEventCreatedPushNotification>._, A<CancellationToken>._)).MustHaveHappenedOnceExactly();
}

[Fact]
public async Task Does_not_send_push_notification_if_owner_is_in_status_to_be_deleted()
{
// Arrange
var mockPushSender = A.Fake<IPushNotificationSender>();
var fakeIdentitiesRepository = A.Fake<IIdentitiesRepository>();
var identity = TestDataGenerator.CreateIdentity();

var handler = new ExternalEventCreatedDomainEventHandler(mockPushSender, fakeIdentitiesRepository);

var externalEventOwner = CreateRandomIdentityAddress();

identity.StartDeletionProcessAsOwner(identity.Devices[0].Id);

A.CallTo(() => fakeIdentitiesRepository.FindByAddress(externalEventOwner, A<CancellationToken>._, A<bool>._)).Returns(identity);

// Act
await handler.Handle(new ExternalEventCreatedDomainEvent { Owner = externalEventOwner, IsDeliveryBlocked = false });

// Assert
A.CallTo(() => mockPushSender.SendNotification(externalEventOwner, A<ExternalEventCreatedPushNotification>._, A<CancellationToken>._)).MustNotHaveHappened();
}

[Fact]
public async Task Does_not_send_a_push_notification_when_delivery_of_the_external_event_is_blocked()
{
// Arrange
var mockPushSender = A.Fake<IPushNotificationSender>();
var fakeIdentitiesRepository = A.Fake<IIdentitiesRepository>();
var identity = TestDataGenerator.CreateIdentity();

var handler = new ExternalEventCreatedDomainEventHandler(mockPushSender);
var handler = new ExternalEventCreatedDomainEventHandler(mockPushSender, fakeIdentitiesRepository);

var externalEventOwner = CreateRandomIdentityAddress();

A.CallTo(() => fakeIdentitiesRepository.FindByAddress(externalEventOwner, A<CancellationToken>._, A<bool>._)).Returns(identity);

// Act
await handler.Handle(new ExternalEventCreatedDomainEvent { Owner = externalEventOwner, IsDeliveryBlocked = true });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@ namespace Backbone.Modules.Messages.Application;

public static class ApplicationErrors
{
public static ApplicationError RecipientsToBeDeleted(IEnumerable<string> peersToBeDeleted)
{
return new ApplicationError("error.platform.validation.message.recipientToBeDeleted",
$"Cannot send message to {peersToBeDeleted.Count()} of the recipients because they are in status 'ToBeDeleted'.",
new { PeersToBeDeleted = peersToBeDeleted });
}

public static ApplicationError NoRelationshipToRecipientExists(string recipient = "")
{
var recipientText = string.IsNullOrEmpty(recipient) ? "one of the recipients" : recipient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
using Backbone.BuildingBlocks.API.Mvc.ControllerAttributes;
using Backbone.BuildingBlocks.Application.Abstractions.Exceptions;
using Backbone.BuildingBlocks.Application.Pagination;
using Backbone.Modules.Devices.Application.Identities.Queries.ListIdentities;
using Backbone.Modules.Devices.Domain.Entities.Identities;
using Backbone.Modules.Messages.Application;
using Backbone.Modules.Messages.Application.Messages.Commands.SendMessage;
using Backbone.Modules.Messages.Application.Messages.DTOs;
Expand Down Expand Up @@ -61,11 +59,6 @@ public async Task<IActionResult> GetMessage(string id, [FromQuery] bool? noBody,
[ProducesError(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> SendMessage(SendMessageCommand request, CancellationToken cancellationToken)
{
var recipientAddresses = request.Recipients.Select(r => r.Address).ToList();
var identitiesToBeDeleted = await _mediator.Send(new ListIdentitiesQuery(recipientAddresses, IdentityStatus.ToBeDeleted), cancellationToken);
if (identitiesToBeDeleted.Any())
throw new ApplicationException(ApplicationErrors.RecipientsToBeDeleted(identitiesToBeDeleted.Select(i => i.Address)));

var response = await _mediator.Send(request, cancellationToken);
return CreatedAtAction(nameof(GetMessage), new { id = response.Id }, response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ namespace Backbone.Modules.Relationships.Application.Extensions;

public static class IEventBusExtensions
{
public static IEventBus AddRelationshipsDomainEventSubscriptions(this IEventBus eventBus)
public static void AddRelationshipsDomainEventSubscriptions(this IEventBus eventBus)
{
eventBus.SubscribeToIdentitiesEvents();
return eventBus;
SubscribeToIdentitiesEvents(eventBus);
}

private static void SubscribeToIdentitiesEvents(this IEventBus eventBus)
private static void SubscribeToIdentitiesEvents(IEventBus eventBus)
{
eventBus.Subscribe<IdentityToBeDeletedDomainEvent, IdentityToBeDeletedDomainEventHandler>();
eventBus.Subscribe<IdentityDeletionCancelledDomainEvent, IdentityDeletionCancelledDomainEventHandler>();
Expand Down
Loading

0 comments on commit 9dbd785

Please sign in to comment.