Skip to content

Commit

Permalink
Queueing of external events for new messages while relationship is in…
Browse files Browse the repository at this point in the history
… status `Terminated` (#912)

* feat: allow sending messages in status Terminated

* refactor: remove reference to Newtonsoft.Json from ConsumerApi.Sdk

* chore: delete leftover packages.lock.json file

* refactor: rename RelationshipReactivationRequest method to ReactivateRelationship

* refactor: fix typo in ExternalEventIdPayloadEntityFrameworkValueConverter

* refactor: make external event payloads strongly typed

* refactor: add empty constructor to each ExternalEvent subclass to satisfy ArchUnit tests

* chore: fix formatting

* feat: modify setup.sql scripts to allow synchronization user access to Relationships schema

* test: add integration tests

* test: add more integration tests

* feat: add RelationshipId to message recipient

* feat: propagate RelationshipId via DomainEvent

* feat: add migrations for adding the IsDeliveryBlocked and Context columns to the ExternalEvents table

* test: delete unused BaseStepDefinitions

* test: fix reactivation by accepting as peer

* feat: send NewRelationshipStatus as part of RelationshipReactivationCompletedDomainEvent

* feat: update entity type configurations

* feat: update MessageCreatedDomainEventHandler to block external events if the relationship is terminated

* feat: unblock external events after relationship was reactivated

* chore: remove todo list

* test: remove Task.Delay

* chore: add explaining comments to custom SQL in migrations

* test: fix test

* refactor: extract deletion of external events to method in dbcontext

* test: fix tests

* test: add some Task.Delays to avoid failing tests

* fix: use correct type param for logger

* test: add tests for MessageCreatedDomainEventHandler

* chore: remove reactivation part from error message

* test: add tests for RelationshipReactivationCompletedDomainEventHandler

* test: add tests for RelationshipStatusChangedDomainEventHandler

* test: add tests for Relationship

* chore: rename ImplicitUsings to GlobalUsings to better reflect what they contain

* test: inherit from AbstractTestsBase

* chore: fix formatting

* feat: don't send a push notification for blocked external events

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
tnotheis and mergify[bot] authored Oct 22, 2024
1 parent a6ca655 commit f3dbf61
Show file tree
Hide file tree
Showing 76 changed files with 2,188 additions and 575 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Identity gets all its Messages
And i1 has sent a Message m to i2 and i3
And i1 has terminated r12
And i1 has decomposed r12
And 1 second(s) have passed
When i1 sends a GET request to the /Messages endpoint
Then the response status code is 200 (Ok)
And the response contains the Message m
Expand All @@ -31,6 +32,7 @@ Identity gets all its Messages
And i1 has sent a Message m to i2 and i3
And i2 has terminated r12
And i2 has decomposed r12
And 1 second(s) have passed
When i1 sends a GET request to the /Messages endpoint
Then the response status code is 200 (Ok)
And the response contains the Message m
Expand All @@ -43,6 +45,7 @@ Identity gets all its Messages
And i1 has terminated r12
And i1 has decomposed r12
And i2 has decomposed r12
And 1 second(s) have passed
When i1 sends a GET request to the /Messages endpoint
Then the response status code is 200 (Ok)
And the response contains the Message m
Expand All @@ -57,6 +60,7 @@ Identity gets all its Messages
And i1 has decomposed r12
And i1 has terminated r13
And i1 has decomposed r13
And 1 second(s) have passed
When i1 sends a GET request to the /Messages endpoint
Then the response status code is 200 (Ok)
And the response does not contain the Message m
Expand All @@ -68,6 +72,7 @@ Identity gets all its Messages
And i1 has sent a Message m to i2 and i3
And i2 has terminated r12
And i2 has decomposed r12
And 1 second(s) have passed
When i2 sends a GET request to the /Messages endpoint
Then the response status code is 200 (Ok)
And the response does not contain the Message m
Expand All @@ -80,6 +85,7 @@ Identity gets all its Messages
And i1 has terminated r12
And i1 has decomposed r12
And i2 has decomposed r12
And 1 second(s) have passed
When i3 sends a GET request to the /Messages endpoint
Then the response status code is 200 (Ok)
And the response contains the Message m
Expand All @@ -95,6 +101,7 @@ Identity gets all its Messages
And i1 has terminated r13
And i1 has decomposed r13
And i3 has decomposed r13
And 1 second(s) have passed
When i1 sends a GET request to the /Messages endpoint
Then the response status code is 200 (Ok)
And the response does not contain the Message m
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ Identity sends a Message
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

Scenario: Sending a Message to a terminated Relationship
Given Identities i1 and i2
And a terminated Relationship r between i1 and i2
When i1 sends a POST request to the /Messages endpoint with i2 as recipient
Then the response status code is 201 (Created)
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
@Integration
Feature: GET /SyncRuns/{id}/ExternalEvents

Scenario: Getting external events does not return events for messages sent while the Relationship is still terminated
Given Identities i1 and i2
And a terminated Relationship r between i1 and i2
And i1 has sent a Message m to i2
And a sync run sr started by i2
When i2 sends a GET request to the /SyncRuns/sr.id/ExternalEvents endpoint
Then the response status code is 200 (OK)
And the response does not contain an external event for the Message m

Scenario: Getting external events returns events for messages sent while the Relationship was terminated
Given Identities i1 and i2
And a terminated Relationship r between i1 and i2
And i1 has sent a Message m to i2
And r was fully reactivated
And 1 second(s) have passed
And a sync run sr started by i2
When i2 sends a GET request to the /SyncRuns/sr.id/ExternalEvents endpoint
Then the response status code is 200 (OK)
Then the response contains an external event for the Message m

Scenario: Getting external events returns events for messages sent before the Relationship was terminated
Given Identities i1 and i2
And an active Relationship r between i1 and i2
And i1 has sent a Message m to i2
And i1 has terminated r
And a sync run sr started by i2
When i2 sends a GET request to the /SyncRuns/sr.id/ExternalEvents endpoint
Then the response status code is 200 (OK)
Then the response contains an external event for the Message m

Scenario: Getting external events does not return events for messages that were sent with an old Relationship
Given Identities i1 and i2
And an active Relationship r between i1 and i2
And i1 has sent a Message m to i2
And i1 has terminated r
And i1 has decomposed r
And i2 has decomposed r
And an active Relationship r between i1 and i2
And a sync run sr started by i2
When i2 sends a GET request to the /SyncRuns/sr.id/ExternalEvents endpoint
Then the response status code is 200 (OK)
Then the response does not contain an external event for the Message m
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public static async Task<Relationship> CreateTerminatedRelationshipWithReactivat
{
var relationshipMetadata = await CreateTerminatedRelationshipBetween(client1, client2);

var reactivateRelationshipResponse = await client2.Relationships.RelationshipReactivationRequest(relationshipMetadata.Id);
var reactivateRelationshipResponse = await client2.Relationships.ReactivateRelationship(relationshipMetadata.Id);
reactivateRelationshipResponse.Should().BeASuccess();

var getRelationshipResponse = await client2.Relationships.GetRelationship(relationshipMetadata.Id);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using FluentAssertions.Extensions;

namespace Backbone.ConsumerApi.Tests.Integration.StepDefinitions;

[Binding]
internal class CommonStepDefinitions
{
#region Given

[Given(@"(\d+) second\(s\) have passed")]
public async Task GivenXSecondsHavePassed(int numberOfSeconds)
{
await Task.Delay(numberOfSeconds.Seconds());
}

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ public async Task GivenATerminatedRelationshipWithReactivationRequest(string rel
_relationshipsContext.Relationships[relationshipName] = await Utils.CreateTerminatedRelationshipWithReactivationRequestBetween(participant1, participant2);
}

[Given($"{RegexFor.SINGLE_THING} was fully reactivated")]
public async Task GivenRWasFullyReactivated(string relationshipName)
{
var relationship = _relationshipsContext.Relationships[relationshipName];

var clientFrom = _clientPool.FirstForIdentityAddress(relationship.From);
await clientFrom.Relationships.ReactivateRelationship(relationship.Id);

var clientTo = _clientPool.FirstForIdentityAddress(relationship.To);
await clientTo.Relationships.AcceptReactivationOfRelationship(relationship.Id);
}

[Given($"{RegexFor.SINGLE_THING} has terminated {RegexFor.SINGLE_THING}")]
public async Task GivenRelationshipIsTerminated(string terminatorName, string relationshipName)
{
Expand All @@ -100,8 +112,6 @@ public async Task GivenIdentityHasDecomposedItsRelationshipToIdentity(string dec

var response = await decomposer.Relationships.DecomposeRelationship(relationship.Id);
response.Should().BeASuccess();

await Task.Delay(500);
}

#endregion
Expand Down Expand Up @@ -148,7 +158,7 @@ public async Task WhenIdentitySendsAPutRequestToTheRelationshipsIdReactivateEndp
{
var client = _clientPool.FirstForIdentityName(identityName);

_responseContext.WhenResponse = await client.Relationships.RelationshipReactivationRequest(_relationshipsContext.Relationships[relationshipName].Id);
_responseContext.WhenResponse = await client.Relationships.ReactivateRelationship(_relationshipsContext.Relationships[relationshipName].Id);
}

[When($"{RegexFor.SINGLE_THING} sends a PUT request to the /Relationships/{{{RegexFor.SINGLE_THING}.Id}}/Reactivate/(Accept|Reject|Revoke) endpoint")]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Backbone.BuildingBlocks.SDK.Endpoints.Common.Types;
using Backbone.ConsumerApi.Sdk.Endpoints.SyncRuns.Types.Requests;
using Backbone.ConsumerApi.Sdk.Endpoints.SyncRuns.Types.Responses;
using Backbone.ConsumerApi.Tests.Integration.Contexts;
using Backbone.ConsumerApi.Tests.Integration.Helpers;

namespace Backbone.ConsumerApi.Tests.Integration.StepDefinitions;

[Binding]
internal class SynchronizationStepDefinitions
{
private readonly ClientPool _clientPool;
private readonly Dictionary<string, StartSyncRunResponse> _startSyncRunResponses = new();
private readonly ResponseContext _responseContext;
private readonly MessagesContext _messagesContext;
private ApiResponse<ListExternalEventsResponse>? _listExternalEventsOfSyncRunResponse;

public SynchronizationStepDefinitions(ResponseContext responseContext, MessagesContext messagesContext, ClientPool clientPool)
{
_responseContext = responseContext;
_messagesContext = messagesContext;
_clientPool = clientPool;
}

#region Given

[Given($"a sync run {RegexFor.SINGLE_THING} started by {RegexFor.SINGLE_THING}")]
public async Task GivenASyncRunStartedBy(string syncRunName, string identityName)
{
var client = _clientPool.FirstForIdentityName(identityName);

var startSyncRunResponse = await client.SyncRuns.StartSyncRun(new StartSyncRunRequest { Type = SyncRunType.ExternalEventSync }, 1);

_startSyncRunResponses.Add(syncRunName, startSyncRunResponse.Result!);
}

#endregion

#region When

[When($"{RegexFor.SINGLE_THING} sends a GET request to the /SyncRuns/{RegexFor.SINGLE_THING}.id/ExternalEvents endpoint")]
public async Task WhenISendsAGETRequestToTheSyncRunsSrIdExternalEventsEndpoint(string identityName, string syncRunName)
{
var client = _clientPool.FirstForIdentityName(identityName);

var syncRunId = _startSyncRunResponses[syncRunName].SyncRun.Id;

_responseContext.WhenResponse = _listExternalEventsOfSyncRunResponse = await client.SyncRuns.ListExternalEventsOfSyncRun(syncRunId);
}

#endregion

#region Then

[Then($"the response does not contain an external event for the Message {RegexFor.SINGLE_THING}")]
public void ThenTheResponseDoesNotContainAnExternalEventForM(string _)
{
_listExternalEventsOfSyncRunResponse!.Result.Should().NotBeEmpty();
_listExternalEventsOfSyncRunResponse.Result.Should().NotContain(e => e.Type == "MessageReceived");
}

[Then($"the response contains an external event for the Message {RegexFor.SINGLE_THING}")]
public void ThenTheResponseContainsAnExternalEventForM(string messageName)
{
var message = _messagesContext.Messages[messageName];
_listExternalEventsOfSyncRunResponse!.Result.Should().NotBeEmpty();
_listExternalEventsOfSyncRunResponse.Result.Should().ContainSingle(e => e.Type == "MessageReceived");
var messageReceivedExternalEvent = _listExternalEventsOfSyncRunResponse.Result!.Single(e => e.Type == "MessageReceived");
messageReceivedExternalEvent.Payload["id"].GetString().Should().Be(message.Id);
}

#endregion
}
Loading

0 comments on commit f3dbf61

Please sign in to comment.