Skip to content

Commit

Permalink
Return the reason why the relationship cannot be created from CanCrea…
Browse files Browse the repository at this point in the history
…teRelationship (#903)

* refactor: simplify usage of test container in MigrationReaderTests

* feat: return code

* test: add/update integration tests

* chore: remove unused ApplicationErrors

* feat: don't mention "relationshipRequest" in error codes

* test: minor changes

* test: inherit from AbstractTestsBase
  • Loading branch information
tnotheis authored Oct 8, 2024
1 parent 9cecd90 commit dcb523c
Show file tree
Hide file tree
Showing 16 changed files with 197 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@ Feature: GET /Relationships/CanCreate
When i1 sends a GET request to the /Relationships/CanCreate?peer={id} endpoint with id=i2.id
Then the response status code is 200 (OK)
And a Relationship can be established
And the response does not contain a relationship creation check code

Scenario: Two identities with an active Relationship can't create another one
Given Identities i1 and i2
And an active Relationship r between i1 and i2
When i1 sends a GET request to the /Relationships/CanCreate?peer={id} endpoint with id=i2.id
Then the response status code is 200 (OK)
And a Relationship can not be established
And the relationship creation check code is "error.platform.validation.relationship.relationshipToTargetAlreadyExists"

Scenario: Two identities with a rejected Relationship can create one
Given Identities i1 and i2
And a rejected Relationship r between i1 and i2
When i1 sends a GET request to the /Relationships/CanCreate?peer={id} endpoint with id=i2.id
Then the response status code is 200 (OK)
And a Relationship can be established
And the response does not contain a relationship creation check code

Scenario: Two identities with a rejected and an active Relationship can't create one
Given Identities i1 and i2
Expand All @@ -28,6 +31,7 @@ Feature: GET /Relationships/CanCreate
When i1 sends a GET request to the /Relationships/CanCreate?peer={id} endpoint with id=i2.id
Then the response status code is 200 (OK)
And a Relationship can not be established
And the relationship creation check code is "error.platform.validation.relationship.relationshipToTargetAlreadyExists"

Scenario: Two identities with two rejected Relationships can create one
Given Identities i1 and i2
Expand All @@ -36,10 +40,12 @@ Feature: GET /Relationships/CanCreate
When i1 sends a GET request to the /Relationships/CanCreate?peer={id} endpoint with id=i2.id
Then the response status code is 200 (OK)
And a Relationship can be established
And the response does not contain a relationship creation check code

Scenario: Cannot create Relationship if peer is to be deleted
Given Identities i1 and i2
And i2 is in status "ToBeDeleted"
When i1 sends a GET request to the /Relationships/CanCreate?peer={id} endpoint with id=i2.id
Then the response status code is 200 (OK)
And a Relationship can not be established
And the relationship creation check code is "error.platform.validation.relationship.peerIsToBeDeleted"
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,25 @@ public async Task WhenAGetRequestIsSentToTheCanCreateEndpointByIdentityForIdenti
[Then("a Relationship can be established")]
public void ThenARelationshipCanBeEstablished()
{
if (_canEstablishRelationshipResponse != null)
_canEstablishRelationshipResponse.Result!.CanCreate.Should().BeTrue();
_canEstablishRelationshipResponse!.Result!.CanCreate.Should().BeTrue();
}

[Then("a Relationship can not be established")]
public void ThenARelationshipCanNotBeEstablished()
{
if (_canEstablishRelationshipResponse != null)
_canEstablishRelationshipResponse.Result!.CanCreate.Should().BeFalse();
_canEstablishRelationshipResponse!.Result!.CanCreate.Should().BeFalse();
}

[Then(@"the relationship creation check code is ""(.+)""")]
public void ThenTheCodeIs(string code)
{
_canEstablishRelationshipResponse!.Result!.Code.Should().Be(code);
}

[Then(@"the response does not contain a relationship creation check code")]
public void ThenThereIsNoCode()
{
_canEstablishRelationshipResponse!.Result!.Code.Should().BeNull();
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ public async Task Returns_migrations_in_correct_order()

private static async Task<MigrationReader> CreateMigrationReader()
{
await StartPostgres("postgres", "admin", 5444);
var dbConnectionString = await StartPostgres();

var services = new ServiceCollection();

services.AddSingleton<IEventBus, DummyEventBus>();
services.AddAllDbContexts(new SqlDatabaseConfiguration { Provider = "Postgres", ConnectionString = "User ID=postgres;Password=admin;Server=localhost;Port=5444;Database=enmeshed;" });
services.AddAllDbContexts(new SqlDatabaseConfiguration { Provider = "Postgres", ConnectionString = dbConnectionString });
services.AddSingleton<DbContextProvider>();
services.AddSingleton<MigrationReader>();

Expand All @@ -62,15 +62,15 @@ private static async Task<MigrationReader> CreateMigrationReader()
return migrationReader;
}

private static async Task StartPostgres(string username, string password, int port)
private static async Task<string> StartPostgres()
{
var postgreSqlContainer = new PostgreSqlBuilder()
.WithImage("postgres:16")
.WithUsername(username)
.WithPassword(password)
.WithPortBinding(port, 5432)
.WithDatabase("enmeshed")
.Build();

await postgreSqlContainer.StartAsync();

return postgreSqlContainer.GetConnectionString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,4 @@ public static IQueryable<Relationship> WithParticipants(this IQueryable<Relation
{
return query.Where(r => r.From == participant1 && r.To == participant2 || r.From == participant2 && r.To == participant1);
}

public static IQueryable<Relationship> Active(this IQueryable<Relationship> query)
{
return query.Where(r => r.Status == RelationshipStatus.Active);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Messages.Application.Infrastructure.Persistence.Repository;
using Backbone.Modules.Messages.Domain.Entities;
using Backbone.Modules.Messages.Domain.Ids;
using Backbone.Modules.Messages.Infrastructure.Persistence.Database.QueryableExtensions;
using Microsoft.EntityFrameworkCore;

Expand All @@ -16,14 +15,6 @@ public RelationshipsRepository(MessagesDbContext dbContext)
_readOnlyRelationships = dbContext.Relationships.AsNoTracking();
}

public Task<RelationshipId?> GetIdOfRelationshipBetweenSenderAndRecipient(IdentityAddress sender, IdentityAddress recipient)
{
return _readOnlyRelationships
.WithParticipants(sender, recipient)
.Select(r => r.Id)
.FirstOrDefaultAsync();
}

public Task<Relationship?> FindYoungestRelationship(IdentityAddress sender, IdentityAddress recipient, CancellationToken cancellationToken)
{
return _readOnlyRelationships
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ public static class ApplicationErrors
{
public static class Relationship
{
public static ApplicationError PeerIsToBeDeleted(string peerToBeDeleted)
public static ApplicationError PeerIsToBeDeleted()
{
return new ApplicationError("error.platform.validation.relationship.peerIsToBeDeleted",
$"Cannot establish relationship with '{peerToBeDeleted}' because they are in status 'ToBeDeleted'.");
"Cannot establish relationship with the owner of the template because the owner is in status 'ToBeDeleted'.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ private async Task ReadTemplateFromDb()
private async Task CreateAndSaveRelationship()
{
var existingRelationships = await _relationshipsRepository.FindRelationships(
r =>
(r.From == _activeIdentity && r.To == _template.CreatedBy) ||
(r.From == _template.CreatedBy && r.To == _activeIdentity),
Relationship.IsBetween(_activeIdentity, _template.CreatedBy),
_cancellationToken
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
public class CanEstablishRelationshipResponse
{
public required bool CanCreate { get; set; }
public required string? Code { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.UserContext;
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Relationships.Application.Infrastructure.Persistence.Repository;
using Backbone.Modules.Relationships.Domain.Aggregates.Relationships;
using MediatR;

namespace Backbone.Modules.Relationships.Application.Relationships.Queries.CanEstablishRelationship;
Expand All @@ -18,8 +18,13 @@ public Handler(IUserContext userContext, IRelationshipsRepository relationshipsR

public async Task<CanEstablishRelationshipResponse> Handle(CanEstablishRelationshipQuery request, CancellationToken cancellationToken)
{
var hasActiveRelationship = await _relationshipsRepository.RelationshipBetweenTwoIdentitiesExists(_userContext.GetAddress(), IdentityAddress.Parse(request.PeerAddress), cancellationToken);
var existingRelationships = await _relationshipsRepository.FindRelationships(
Relationship.IsBetween(request.PeerAddress, _userContext.GetAddress()),
cancellationToken
);

return new CanEstablishRelationshipResponse { CanCreate = !hasActiveRelationship };
var error = Relationship.CanEstablish(existingRelationships.ToList());

return new CanEstablishRelationshipResponse { CanCreate = error == null, Code = error?.Code };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ public async Task<IActionResult> CanEstablishRelationship([FromQuery(Name = "pee
var peerIdentity = await _mediator.Send(new GetIdentityQuery(peerAddress), cancellationToken);

var response = peerIdentity.Status is IdentityStatus.ToBeDeleted
? new CanEstablishRelationshipResponse { CanCreate = false }
? new CanEstablishRelationshipResponse { CanCreate = false, Code = ApplicationErrors.Relationship.PeerIsToBeDeleted().Code }
: await _mediator.Send(new CanEstablishRelationshipQuery { PeerAddress = peerAddress }, cancellationToken);

return Ok(response);
Expand All @@ -213,7 +213,7 @@ private async Task EnsurePeerIsNotToBeDeleted(string peerIdentityAddress, Cancel
{
var peerIdentity = await _mediator.Send(new GetIdentityQuery(peerIdentityAddress), cancellationToken);
if (peerIdentity.Status is IdentityStatus.ToBeDeleted)
throw new ApplicationException(ApplicationErrors.Relationship.PeerIsToBeDeleted(peerIdentity.Address));
throw new ApplicationException(ApplicationErrors.Relationship.PeerIsToBeDeleted());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Backbone.Modules.Relationships.Domain.TestHelpers;
using Backbone.UnitTestTools.BaseClasses;
using FluentAssertions;
using Xunit;

namespace Backbone.Modules.Relationships.Domain.Aggregates.Relationships;

public class RelationshipCanEstablishTests : AbstractTestsBase
{
[Fact]
public void Returns_null_when_no_relationships_exists()
{
// Act
var error = Relationship.CanEstablish([]);

// Assert
error.Should().BeNull();
}

[Fact]
public void Returns_null_when_a_relationship_ready_for_deletion_exists()
{
// Act
var error = Relationship.CanEstablish([TestData.CreateRelationshipInStatus(RelationshipStatus.ReadyForDeletion)]);

// Assert
error.Should().BeNull();
}

[Fact]
public void Returns_null_when_a_rejected_relationship_exists()
{
// Act
var error = Relationship.CanEstablish([TestData.CreateRelationshipInStatus(RelationshipStatus.Rejected)]);

// Assert
error.Should().BeNull();
}

[Fact]
public void Returns_null_when_a_revoked_relationship_exists()
{
// Act
var error = Relationship.CanEstablish([TestData.CreateRelationshipInStatus(RelationshipStatus.Revoked)]);

// Assert
error.Should().BeNull();
}

[Theory]
[InlineData(RelationshipStatus.Pending)]
[InlineData(RelationshipStatus.Active)]
[InlineData(RelationshipStatus.Terminated)]
[InlineData(RelationshipStatus.DeletionProposed)]
public void Returns_an_error_when_a_relationship_exists(RelationshipStatus status)
{
// Act
var error = Relationship.CanEstablish([TestData.CreateRelationshipInStatus(status)]);

// Assert
error.Should().NotBeNull();
error!.Code.Should().Be("error.platform.validation.relationship.relationshipToTargetAlreadyExists");
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Backbone.Modules.Relationships.Domain.TestHelpers;
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Relationships.Domain.TestHelpers;
using Backbone.UnitTestTools.BaseClasses;
using FluentAssertions;
using Xunit;
using static Backbone.UnitTestTools.Data.TestDataGenerator;

namespace Backbone.Modules.Relationships.Domain.Aggregates.Relationships;

Expand Down Expand Up @@ -106,6 +108,49 @@ public void HasParticipant_recognizes_foreign_addresses()

#endregion

#region IsBetween

[Fact]
public void IsBetween_returns_true_when_both_identities_are_part_of_relationship()
{
// Arrange
var participant1 = CreateRandomIdentityAddress();
var participant2 = CreateRandomIdentityAddress();
var relationship = TestData.CreateActiveRelationship(participant1, participant2);

// Act
var result1 = relationship.EvaluateIsBetween(participant1, participant2);
var result2 = relationship.EvaluateIsBetween(participant2, participant1);

// Assert
result1.Should().BeTrue();
result2.Should().BeTrue();
}

[Fact]
public void IsBetween_returns_false_when_at_least_one_identity_is_not_part_of_relationship()
{
// Arrange
var participant1 = CreateRandomIdentityAddress();
var participant2 = CreateRandomIdentityAddress();
var otherIdentity1 = CreateRandomIdentityAddress();
var otherIdentity2 = CreateRandomIdentityAddress();

var relationship = TestData.CreateActiveRelationship(participant1, participant2);

// Act
var result1 = relationship.EvaluateIsBetween(participant1, otherIdentity1);
var result2 = relationship.EvaluateIsBetween(participant2, otherIdentity1);
var result3 = relationship.EvaluateIsBetween(otherIdentity1, otherIdentity2);

// Assert
result1.Should().BeFalse();
result2.Should().BeFalse();
result3.Should().BeFalse();
}

#endregion

#region HasStatusInWhichPeerShouldBeNotifiedAboutDeletion

[Theory]
Expand Down Expand Up @@ -150,4 +195,10 @@ public static bool EvaluateHasStatusInWhichPeerShouldBeNotifiedAboutDeletion(thi
var expression = Relationship.HasStatusInWhichPeerShouldBeNotifiedAboutDeletion();
return expression.Compile()(relationship);
}

public static bool EvaluateIsBetween(this Relationship relationship, IdentityAddress identity1, IdentityAddress identity2)
{
var expression = Relationship.IsBetween(identity1, identity2);
return expression.Compile()(relationship);
}
}
Loading

0 comments on commit dcb523c

Please sign in to comment.