From 5a4d55ef080ee5899c1febbdb0860acd5b95ebad Mon Sep 17 00:00:00 2001 From: Mika Aaron Herrmann <65243124+MH321Productions@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:12:09 +0200 Subject: [PATCH] Check whether a relationship can be established with a given identity (#808) * feat: Add /Relationships/CanCreate endpoint, query, response and handler * test: Add handler tests * chore: Add bruno file for /Relationships/CanCreate endpoint * chore: Add validator for CanEstablishRelationshipQuery and use string instead of IdentityAddress * chore: Remove unnecessary code * chore: Add CanCreate endpoint to ConsumerApi SDK * chore: Remove unnecessary handler tests * chore: Add integration tests * chore: Use helper method and modify Relationship.CountsAsActive method * chore: Add validator for the identity address format * chore: Remove unnecessary validator check * chore: Remove now obsolete static responses * chore: Use helper methods * chore: Create new helper method for rejected relationships * chore: Move and rename feature file * chore: Extract common helper method code into own method * chore: Don't use the null fallback * chore: Remove unused method * chore: Extract THEN method cluster into two methods * chore: cleanup --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Timo Notheisen --- .../Can Establish Relationship.bru | 19 ++++++++ .../Relationships/CanCreate/GET.feature | 38 +++++++++++++++ .../Helpers/Utils.cs | 31 ++++++++++-- .../RelationshipsStepDefinitions.cs | 48 +++++++++++++++++++ Backbone.sln | 4 -- .../CanEstablishRelationshipQuery.cs | 9 ++++ .../CanEstablishRelationshipQueryValidator.cs | 14 ++++++ .../CanEstablishRelationshipResponse.cs | 6 +++ .../CanEstablishRelationship/Handler.cs | 25 ++++++++++ .../Controllers/RelationshipsController.cs | 11 +++++ .../Aggregates/Relationships/Relationship.cs | 3 +- .../Relationships/RelationshipsEndpoint.cs | 9 ++++ .../CanEstablishRelationshipResponse.cs | 6 +++ 13 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 Applications/ConsumerApi/src/http/Relationships/Can Establish Relationship.bru create mode 100644 Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/Relationships/CanCreate/GET.feature create mode 100644 Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/CanEstablishRelationshipQuery.cs create mode 100644 Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/CanEstablishRelationshipQueryValidator.cs create mode 100644 Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/CanEstablishRelationshipResponse.cs create mode 100644 Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/Handler.cs create mode 100644 Sdks/ConsumerApi.Sdk/src/Endpoints/Relationships/Types/Responses/CanEstablishRelationshipResponse.cs diff --git a/Applications/ConsumerApi/src/http/Relationships/Can Establish Relationship.bru b/Applications/ConsumerApi/src/http/Relationships/Can Establish Relationship.bru new file mode 100644 index 0000000000..46f2da12b6 --- /dev/null +++ b/Applications/ConsumerApi/src/http/Relationships/Can Establish Relationship.bru @@ -0,0 +1,19 @@ +meta { + name: /Relationships/CanCreate + type: http + seq: 4 +} + +get { + url: {{baseUrl}}/Relationships/CanCreate?peer={{PeerId}} + body: none + auth: inherit +} + +params:query { + peer: {{PeerId}} +} + +vars:pre-request { + PeerId: did:e:localhost:dids:8234cca0160ff05c785636 +} diff --git a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/Relationships/CanCreate/GET.feature b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/Relationships/CanCreate/GET.feature new file mode 100644 index 0000000000..ff0a74584a --- /dev/null +++ b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/Relationships/CanCreate/GET.feature @@ -0,0 +1,38 @@ +@Integration +Feature: GET Relationships/CanCreate + + Scenario: Two identities without a relationship can create one + Given Identities i1 and i2 + When a GET request is sent to the /Relationships/CanCreate?peer={i.id} endpoint by i1 for i2 + Then the response status code is 200 (OK) + And a relationship can be established + + Scenario: Two identities with an active relationship can't create another one + Given Identities i1 and i2 + And an active Relationship between i1 and i2 created by i1 + When a GET request is sent to the /Relationships/CanCreate?peer={i.id} endpoint by i1 for i2 + Then the response status code is 200 (OK) + And a relationship can not be established + + Scenario: Two identities with a rejected relationship can create one + Given Identities i1 and i2 + And a rejected Relationship between i1 and i2 created by i1 + When a GET request is sent to the /Relationships/CanCreate?peer={i.id} endpoint by i1 for i2 + Then the response status code is 200 (OK) + And a relationship can be established + + Scenario: Two identities with a rejected and an active relationship can't create one + Given Identities i1 and i2 + And a rejected Relationship between i1 and i2 created by i1 + And an active Relationship between i1 and i2 created by i1 + When a GET request is sent to the /Relationships/CanCreate?peer={i.id} endpoint by i1 for i2 + Then the response status code is 200 (OK) + And a relationship can not be established + + Scenario: Two identities with two rejected relationships can create one + Given Identities i1 and i2 + And a rejected Relationship between i1 and i2 created by i1 + And a rejected Relationship between i1 and i2 created by i1 + When a GET request is sent to the /Relationships/CanCreate?peer={i.id} endpoint by i1 for i2 + Then the response status code is 200 (OK) + And a relationship can be established diff --git a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Helpers/Utils.cs b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Helpers/Utils.cs index ed74dfc9dc..3d23ce37c6 100644 --- a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Helpers/Utils.cs +++ b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Helpers/Utils.cs @@ -12,7 +12,7 @@ namespace Backbone.ConsumerApi.Tests.Integration.Helpers; public static class Utils { - public static async Task EstablishRelationshipBetween(Client client1, Client client2) + public static async Task CreatePendingRelationshipBetween(Client client1, Client client2) { var createRelationshipTemplateRequest = new CreateRelationshipTemplateRequest { @@ -31,15 +31,40 @@ public static async Task EstablishRelationshipBetween(Client clien var createRelationshipResponse = await client2.Relationships.CreateRelationship(createRelationshipRequest); createRelationshipResponse.Should().BeASuccess(); + return createRelationshipResponse.Result!; + } + + public static async Task EstablishRelationshipBetween(Client client1, Client client2) + { + var relationshipMetadata = await CreatePendingRelationshipBetween(client1, client2); + var acceptRelationshipRequest = new AcceptRelationshipRequest { CreationResponseContent = "AAA".GetBytes() }; - var acceptRelationshipResponse = await client1.Relationships.AcceptRelationship(createRelationshipResponse.Result!.Id, acceptRelationshipRequest); + var acceptRelationshipResponse = await client1.Relationships.AcceptRelationship(relationshipMetadata.Id, acceptRelationshipRequest); acceptRelationshipResponse.Should().BeASuccess(); - var getRelationshipResponse = await client1.Relationships.GetRelationship(createRelationshipResponse.Result.Id); + var getRelationshipResponse = await client1.Relationships.GetRelationship(relationshipMetadata.Id); + getRelationshipResponse.Should().BeASuccess(); + + return getRelationshipResponse.Result!; + } + + public static async Task CreateRejectedRelationshipBetween(Client client1, Client client2) + { + var relationshipMetadata = await CreatePendingRelationshipBetween(client1, client2); + + var rejectRelationshipRequest = new RejectRelationshipRequest + { + CreationResponseContent = "AAA".GetBytes() + }; + + var rejectRelationshipResponse = await client1.Relationships.RejectRelationship(relationshipMetadata.Id, rejectRelationshipRequest); + rejectRelationshipResponse.Should().BeASuccess(); + + var getRelationshipResponse = await client1.Relationships.GetRelationship(relationshipMetadata.Id); getRelationshipResponse.Should().BeASuccess(); return getRelationshipResponse.Result!; diff --git a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/RelationshipsStepDefinitions.cs b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/RelationshipsStepDefinitions.cs index ac93752f09..26c8b3dbb0 100644 --- a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/RelationshipsStepDefinitions.cs +++ b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/RelationshipsStepDefinitions.cs @@ -3,10 +3,12 @@ using Backbone.ConsumerApi.Sdk.Authentication; using Backbone.ConsumerApi.Sdk.Endpoints.Relationships.Types; using Backbone.ConsumerApi.Sdk.Endpoints.Relationships.Types.Requests; +using Backbone.ConsumerApi.Sdk.Endpoints.Relationships.Types.Responses; using Backbone.ConsumerApi.Sdk.Endpoints.RelationshipTemplates.Types.Requests; using Backbone.ConsumerApi.Sdk.Endpoints.RelationshipTemplates.Types.Responses; using Backbone.ConsumerApi.Tests.Integration.Configuration; using Backbone.ConsumerApi.Tests.Integration.Extensions; +using Backbone.ConsumerApi.Tests.Integration.Helpers; using Backbone.ConsumerApi.Tests.Integration.Support; using Backbone.Tooling.Extensions; using Microsoft.Extensions.Options; @@ -15,6 +17,7 @@ namespace Backbone.ConsumerApi.Tests.Integration.StepDefinitions; [Binding] [Scope(Feature = "POST Relationship")] +[Scope(Feature = "GET Relationships/CanCreate")] internal class RelationshipsStepDefinitions { private Client _client1 = null!; @@ -26,6 +29,7 @@ internal class RelationshipsStepDefinitions private ApiResponse? _acceptRelationshipResponse; private ApiResponse? _rejectRelationshipResponse; private ApiResponse? _revokeRelationshipResponse; + private ApiResponse? _canEstablishResponse; private string _relationshipId = string.Empty; public RelationshipsStepDefinitions(HttpClientFactory factory, IOptions httpConfiguration) @@ -34,6 +38,8 @@ public RelationshipsStepDefinitions(HttpClientFactory factory, IOptions> CreateRelationshipTemplate(Client client) { var createRelationshipTemplateRequest = new CreateRelationshipTemplateRequest diff --git a/Backbone.sln b/Backbone.sln index 6638d7fffc..f474b76bef 100644 --- a/Backbone.sln +++ b/Backbone.sln @@ -245,10 +245,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerformanceSnapshotCreator" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tokens.Domain.Tests", "Modules\Tokens\test\Tokens.Domain.Tests\Tokens.Domain.Tests.csproj", "{EDCB84BE-54C3-4CAD-977E-45EEBEFA1402}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AAE548AB-4843-476A-BF61-002CF6FEE713}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{824495A9-A255-487D-AF26-6F6CA92BC715}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Challenges.Application.Tests", "Modules\Challenges\test\Challenges.Application.Tests\Challenges.Application.Tests.csproj", "{EAA10D43-BD28-40D8-BE07-B8F6DE47C156}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Others", "Others", "{8B8EE965-0373-46D6-BB30-7C131EE524E4}" diff --git a/Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/CanEstablishRelationshipQuery.cs b/Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/CanEstablishRelationshipQuery.cs new file mode 100644 index 0000000000..5a3d105e04 --- /dev/null +++ b/Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/CanEstablishRelationshipQuery.cs @@ -0,0 +1,9 @@ +using Backbone.DevelopmentKit.Identity.ValueObjects; +using MediatR; + +namespace Backbone.Modules.Relationships.Application.Relationships.Queries.CanEstablishRelationship; + +public class CanEstablishRelationshipQuery : IRequest +{ + public required string PeerAddress { get; set; } +} diff --git a/Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/CanEstablishRelationshipQueryValidator.cs b/Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/CanEstablishRelationshipQueryValidator.cs new file mode 100644 index 0000000000..4283fe71d2 --- /dev/null +++ b/Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/CanEstablishRelationshipQueryValidator.cs @@ -0,0 +1,14 @@ +using Backbone.BuildingBlocks.Application.FluentValidation; +using Backbone.DevelopmentKit.Identity.ValueObjects; +using FluentValidation; + +namespace Backbone.Modules.Relationships.Application.Relationships.Queries.CanEstablishRelationship; + +// ReSharper disable once UnusedType.Global +public class CanEstablishRelationshipQueryValidator : AbstractValidator +{ + public CanEstablishRelationshipQueryValidator() + { + RuleFor(q => q.PeerAddress).Must(IdentityAddress.IsValid); + } +} diff --git a/Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/CanEstablishRelationshipResponse.cs b/Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/CanEstablishRelationshipResponse.cs new file mode 100644 index 0000000000..72e798e013 --- /dev/null +++ b/Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/CanEstablishRelationshipResponse.cs @@ -0,0 +1,6 @@ +namespace Backbone.Modules.Relationships.Application.Relationships.Queries.CanEstablishRelationship; + +public class CanEstablishRelationshipResponse +{ + public required bool CanCreate { get; set; } +} diff --git a/Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/Handler.cs b/Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/Handler.cs new file mode 100644 index 0000000000..3cf60e80c9 --- /dev/null +++ b/Modules/Relationships/src/Relationships.Application/Relationships/Queries/CanEstablishRelationship/Handler.cs @@ -0,0 +1,25 @@ +using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.UserContext; +using Backbone.DevelopmentKit.Identity.ValueObjects; +using Backbone.Modules.Relationships.Application.Infrastructure.Persistence.Repository; +using MediatR; + +namespace Backbone.Modules.Relationships.Application.Relationships.Queries.CanEstablishRelationship; + +public class Handler : IRequestHandler +{ + private readonly IRelationshipsRepository _relationshipsRepository; + private readonly IUserContext _userContext; + + public Handler(IUserContext userContext, IRelationshipsRepository relationshipsRepository) + { + _relationshipsRepository = relationshipsRepository; + _userContext = userContext; + } + + public async Task Handle(CanEstablishRelationshipQuery request, CancellationToken cancellationToken) + { + var hasActiveRelationship = await _relationshipsRepository.RelationshipBetweenTwoIdentitiesExists(_userContext.GetAddress(), IdentityAddress.Parse(request.PeerAddress), cancellationToken); + + return new CanEstablishRelationshipResponse { CanCreate = !hasActiveRelationship }; + } +} diff --git a/Modules/Relationships/src/Relationships.ConsumerApi/Controllers/RelationshipsController.cs b/Modules/Relationships/src/Relationships.ConsumerApi/Controllers/RelationshipsController.cs index ac31312853..15fa4a31c8 100644 --- a/Modules/Relationships/src/Relationships.ConsumerApi/Controllers/RelationshipsController.cs +++ b/Modules/Relationships/src/Relationships.ConsumerApi/Controllers/RelationshipsController.cs @@ -3,6 +3,7 @@ using Backbone.BuildingBlocks.API.Mvc.ControllerAttributes; using Backbone.BuildingBlocks.Application.Abstractions.Exceptions; using Backbone.BuildingBlocks.Application.Pagination; +using Backbone.DevelopmentKit.Identity.ValueObjects; using Backbone.Modules.Devices.Application.Identities.Queries.GetIdentity; using Backbone.Modules.Devices.Domain.Entities.Identities; using Backbone.Modules.Relationships.Application; @@ -17,6 +18,7 @@ using Backbone.Modules.Relationships.Application.Relationships.Commands.RevokeRelationshipReactivation; using Backbone.Modules.Relationships.Application.Relationships.Commands.TerminateRelationship; using Backbone.Modules.Relationships.Application.Relationships.DTOs; +using Backbone.Modules.Relationships.Application.Relationships.Queries.CanEstablishRelationship; using Backbone.Modules.Relationships.Application.Relationships.Queries.GetPeerOfActiveIdentityInRelationship; using Backbone.Modules.Relationships.Application.Relationships.Queries.GetRelationship; using Backbone.Modules.Relationships.Application.Relationships.Queries.ListRelationships; @@ -195,6 +197,15 @@ public async Task DecomposeRelationship([FromRoute] string id, Ca return Ok(response); } + [HttpGet("CanCreate")] + [ProducesResponseType(typeof(HttpResponseEnvelopeResult), StatusCodes.Status200OK)] + [ProducesError(StatusCodes.Status400BadRequest)] + public async Task CanEstablishRelationship([FromQuery] string peer, CancellationToken cancellationToken) + { + var response = await _mediator.Send(new CanEstablishRelationshipQuery { PeerAddress = peer }, cancellationToken); + return Ok(response); + } + private async Task EnsurePeerIsNotToBeDeleted(string peerIdentityAddress, CancellationToken cancellationToken) { var peerIdentity = await _mediator.Send(new GetIdentityQuery(peerIdentityAddress), cancellationToken); diff --git a/Modules/Relationships/src/Relationships.Domain/Aggregates/Relationships/Relationship.cs b/Modules/Relationships/src/Relationships.Domain/Aggregates/Relationships/Relationship.cs index b6f7c691c9..e3b9141537 100644 --- a/Modules/Relationships/src/Relationships.Domain/Aggregates/Relationships/Relationship.cs +++ b/Modules/Relationships/src/Relationships.Domain/Aggregates/Relationships/Relationship.cs @@ -371,7 +371,8 @@ public static Expression> HasParticipant(string identit public static Expression> CountsAsActive() { return r => r.Status != RelationshipStatus.Rejected && - r.Status != RelationshipStatus.Revoked; + r.Status != RelationshipStatus.Revoked && + r.Status != RelationshipStatus.ReadyForDeletion; } #endregion diff --git a/Sdks/ConsumerApi.Sdk/src/Endpoints/Relationships/RelationshipsEndpoint.cs b/Sdks/ConsumerApi.Sdk/src/Endpoints/Relationships/RelationshipsEndpoint.cs index c056997036..470b774cad 100644 --- a/Sdks/ConsumerApi.Sdk/src/Endpoints/Relationships/RelationshipsEndpoint.cs +++ b/Sdks/ConsumerApi.Sdk/src/Endpoints/Relationships/RelationshipsEndpoint.cs @@ -77,4 +77,13 @@ public async Task> DecomposeRelationship(strin { return await _client.Put($"api/{API_VERSION}/Relationships/{relationshipId}/Decompose"); } + + public async Task> CanCreateRelationship(string peerIdentityAddress) + { + return await _client + .Request(HttpMethod.Get, $"api/{API_VERSION}/Relationships/CanCreate") + .Authenticate() + .AddQueryParameter("peer", peerIdentityAddress) + .Execute(); + } } diff --git a/Sdks/ConsumerApi.Sdk/src/Endpoints/Relationships/Types/Responses/CanEstablishRelationshipResponse.cs b/Sdks/ConsumerApi.Sdk/src/Endpoints/Relationships/Types/Responses/CanEstablishRelationshipResponse.cs new file mode 100644 index 0000000000..52c20f37cb --- /dev/null +++ b/Sdks/ConsumerApi.Sdk/src/Endpoints/Relationships/Types/Responses/CanEstablishRelationshipResponse.cs @@ -0,0 +1,6 @@ +namespace Backbone.ConsumerApi.Sdk.Endpoints.Relationships.Types.Responses; + +public class CanEstablishRelationshipResponse +{ + public required bool CanCreate { get; set; } +}