diff --git a/AdminApi/src/AdminApi/ClientApp/package-lock.json b/AdminApi/src/AdminApi/ClientApp/package-lock.json index a1d2d0d177..2f8b19386e 100644 --- a/AdminApi/src/AdminApi/ClientApp/package-lock.json +++ b/AdminApi/src/AdminApi/ClientApp/package-lock.json @@ -6226,12 +6226,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -8491,9 +8491,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" diff --git a/Backbone.Tests.ArchUnit/CleanArchitecture.cs b/Backbone.Tests.ArchUnit/CleanArchitecture.cs index 91aa76ec3a..9b1895537a 100644 --- a/Backbone.Tests.ArchUnit/CleanArchitecture.cs +++ b/Backbone.Tests.ArchUnit/CleanArchitecture.cs @@ -12,6 +12,11 @@ public class CleanArchitecture .ResideInAssembly("Backbone.Modules.*", true) .As("All Modules"); + private static readonly IObjectProvider CONSUMER_API_ASSEMBLIES = + Types().That() + .ResideInAssembly("Backbone.Modules.*.ConsumerApi", true) + .As("ConsumerApi Assemblies"); + private static readonly IObjectProvider APPLICATION_ASSEMBLIES = Types().That() .ResideInAssembly("Backbone.Modules.*.Application", true) @@ -34,6 +39,7 @@ public void ModulesShouldNotDependOnOtherModules(IObjectProvider module) Types() .That().Are(module) .And().AreNot(Backbone.TEST_TYPES) + .And().AreNot(CONSUMER_API_ASSEMBLIES) .Should().NotDependOnAny(otherModules) .Because("modules should be self-contained.") .Check(Backbone.ARCHITECTURE); diff --git a/BuildingBlocks/src/BuildingBlocks.API/Mvc/ExceptionFilters/CustomExceptionFilter.cs b/BuildingBlocks/src/BuildingBlocks.API/Mvc/ExceptionFilters/CustomExceptionFilter.cs index e7e80b1f99..12895b6b71 100644 --- a/BuildingBlocks/src/BuildingBlocks.API/Mvc/ExceptionFilters/CustomExceptionFilter.cs +++ b/BuildingBlocks/src/BuildingBlocks.API/Mvc/ExceptionFilters/CustomExceptionFilter.cs @@ -143,7 +143,7 @@ private HttpError CreateHttpErrorForDomainException(DomainException domainExcept }); } - return null; + return applicationException.AdditionalData; } private static HttpStatusCode GetStatusCodeForInfrastructureException(InfrastructureException _) diff --git a/BuildingBlocks/src/BuildingBlocks.Application.Abstractions/Exceptions/ApplicationError.cs b/BuildingBlocks/src/BuildingBlocks.Application.Abstractions/Exceptions/ApplicationError.cs index 074bb43cb8..66a1ecd14f 100644 --- a/BuildingBlocks/src/BuildingBlocks.Application.Abstractions/Exceptions/ApplicationError.cs +++ b/BuildingBlocks/src/BuildingBlocks.Application.Abstractions/Exceptions/ApplicationError.cs @@ -2,12 +2,14 @@ namespace Backbone.BuildingBlocks.Application.Abstractions.Exceptions; public class ApplicationError { - public ApplicationError(string code, string message) + public ApplicationError(string code, string message, dynamic? additionalData = null) { Code = code; Message = message; + AdditionalData = additionalData; } public string Code { get; } public string Message { get; } + public dynamic? AdditionalData { get; } } diff --git a/BuildingBlocks/src/BuildingBlocks.Application.Abstractions/Exceptions/ApplicationException.cs b/BuildingBlocks/src/BuildingBlocks.Application.Abstractions/Exceptions/ApplicationException.cs index 96bcabce4d..01501576a9 100644 --- a/BuildingBlocks/src/BuildingBlocks.Application.Abstractions/Exceptions/ApplicationException.cs +++ b/BuildingBlocks/src/BuildingBlocks.Application.Abstractions/Exceptions/ApplicationException.cs @@ -5,13 +5,16 @@ public class ApplicationException : Exception public ApplicationException(ApplicationError error) : base(error.Message) { Code = error.Code; + AdditionalData = error.AdditionalData; } public ApplicationException(ApplicationError error, Exception innerException) : base(error.Message, innerException) { Code = error.Code; + AdditionalData = error.AdditionalData; } public string Code { get; set; } + public dynamic? AdditionalData { get; } } diff --git a/BuildingBlocks/src/BuildingBlocks.SDK/Endpoints/Common/EndpointClient.cs b/BuildingBlocks/src/BuildingBlocks.SDK/Endpoints/Common/EndpointClient.cs index ff1dd1aad1..49c9e18a0d 100644 --- a/BuildingBlocks/src/BuildingBlocks.SDK/Endpoints/Common/EndpointClient.cs +++ b/BuildingBlocks/src/BuildingBlocks.SDK/Endpoints/Common/EndpointClient.cs @@ -5,7 +5,6 @@ using System.Text.Json; using System.Web; using Backbone.BuildingBlocks.SDK.Endpoints.Common.Types; -using Backbone.Tooling.JsonConverters; using JsonSerializer = System.Text.Json.JsonSerializer; namespace Backbone.BuildingBlocks.SDK.Endpoints.Common; @@ -34,7 +33,6 @@ public EndpointClient(HttpClient httpClient, IAuthenticator authenticator, JsonS _httpClient = httpClient; _authenticator = authenticator; _jsonSerializerOptions = jsonSerializerOptions; - jsonSerializerOptions.Converters.Add(new UrlSafeBase64ToByteArrayJsonConverter()); } public async Task> Post(string url, object? requestContent = null) diff --git a/BuildingBlocks/src/BuildingBlocks.SDK/Endpoints/Common/Types/ApiError.cs b/BuildingBlocks/src/BuildingBlocks.SDK/Endpoints/Common/Types/ApiError.cs index 861252dcf0..2b105083e4 100644 --- a/BuildingBlocks/src/BuildingBlocks.SDK/Endpoints/Common/Types/ApiError.cs +++ b/BuildingBlocks/src/BuildingBlocks.SDK/Endpoints/Common/Types/ApiError.cs @@ -1,4 +1,7 @@ -namespace Backbone.BuildingBlocks.SDK.Endpoints.Common.Types; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Backbone.BuildingBlocks.SDK.Endpoints.Common.Types; public class ApiError { @@ -7,4 +10,37 @@ public class ApiError public required string Message { get; set; } public required string Docs { get; set; } public required DateTime Time { get; set; } + public ApiErrorData? Data { get; set; } +} + +public class ApiErrorData +{ + private readonly JsonElement _data; + private readonly JsonSerializerOptions _optionsUsedToDeserializeThis; + + private ApiErrorData(JsonElement data, JsonSerializerOptions options) + { + _data = data; + _optionsUsedToDeserializeThis = options; // we need to keep the options to be able to deserialize the data later + } + + public T As() + { + var json = _data.GetRawText(); + return JsonSerializer.Deserialize(json, _optionsUsedToDeserializeThis)!; + } + + public class JsonConverter : JsonConverter + { + public override ApiErrorData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var element = JsonSerializer.Deserialize(ref reader, options); + return new ApiErrorData(element, options); + } + + public override void Write(Utf8JsonWriter writer, ApiErrorData value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.As(), options); + } + } } diff --git a/ConsumerApi.Sdk/Configuration.cs b/ConsumerApi.Sdk/Configuration.cs index f5758ab9a2..0b995d1d0e 100644 --- a/ConsumerApi.Sdk/Configuration.cs +++ b/ConsumerApi.Sdk/Configuration.cs @@ -1,12 +1,21 @@ using System.Text.Json; +using Backbone.BuildingBlocks.SDK.Endpoints.Common.Types; using Backbone.ConsumerApi.Sdk.Authentication; +using Backbone.Tooling.JsonConverters; namespace Backbone.ConsumerApi.Sdk; public class Configuration { + public Configuration() + { + JsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + JsonSerializerOptions.Converters.Add(new UrlSafeBase64ToByteArrayJsonConverter()); + JsonSerializerOptions.Converters.Add(new ApiErrorData.JsonConverter()); + } + public required AuthenticationConfiguration Authentication { get; init; } - public JsonSerializerOptions JsonSerializerOptions { get; init; } = new() { PropertyNameCaseInsensitive = true }; + public JsonSerializerOptions JsonSerializerOptions { get; init; } public class AuthenticationConfiguration { diff --git a/ConsumerApi.Tests.Integration/Features/Messages/POST.feature b/ConsumerApi.Tests.Integration/Features/Messages/POST.feature new file mode 100644 index 0000000000..98adf73139 --- /dev/null +++ b/ConsumerApi.Tests.Integration/Features/Messages/POST.feature @@ -0,0 +1,18 @@ +@Integration +Feature: POST Message + +User sends a Message + +Scenario: Sending a Message + Given Identities i1 and i2 with an established Relationship + When i1 sends a POST request to the /Messages endpoint with i2 as recipient + Then the response status code is 201 (Created) + And the response contains a SendMessageResponse + +Scenario: Sending a Message to Identity to be deleted + Given Identities i1 and i2 with an established Relationship + 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 diff --git a/ConsumerApi.Tests.Integration/Helpers/Utils.cs b/ConsumerApi.Tests.Integration/Helpers/Utils.cs new file mode 100644 index 0000000000..dc80a43546 --- /dev/null +++ b/ConsumerApi.Tests.Integration/Helpers/Utils.cs @@ -0,0 +1,38 @@ +using Backbone.ConsumerApi.Sdk; +using Backbone.ConsumerApi.Sdk.Endpoints.Relationships.Types.Requests; +using Backbone.ConsumerApi.Sdk.Endpoints.RelationshipTemplates.Types.Requests; +using Backbone.ConsumerApi.Tests.Integration.Extensions; +using Backbone.Crypto; + +namespace Backbone.ConsumerApi.Tests.Integration.Helpers; + +public static class Utils +{ + public static async Task EstablishRelationshipBetween(Client client1, Client client2) + { + var createRelationshipTemplateRequest = new CreateRelationshipTemplateRequest + { + Content = ConvertibleString.FromUtf8("AAA").BytesRepresentation + }; + + var relationshipTemplateResponse = await client1.RelationshipTemplates.CreateTemplate(createRelationshipTemplateRequest); + relationshipTemplateResponse.Should().BeASuccess(); + + var createRelationshipRequest = new CreateRelationshipRequest + { + RelationshipTemplateId = relationshipTemplateResponse.Result!.Id, + Content = ConvertibleString.FromUtf8("AAA").BytesRepresentation + }; + + var createRelationshipResponse = await client2.Relationships.CreateRelationship(createRelationshipRequest); + createRelationshipResponse.Should().BeASuccess(); + + var completeRelationshipChangeRequest = new CompleteRelationshipChangeRequest + { + Content = ConvertibleString.FromUtf8("AAA").BytesRepresentation + }; + var acceptRelationChangeResponse = + await client1.Relationships.AcceptChange(createRelationshipResponse.Result!.Id, createRelationshipResponse.Result.Changes.First().Id, completeRelationshipChangeRequest); + acceptRelationChangeResponse.Should().BeASuccess(); + } +} diff --git a/ConsumerApi.Tests.Integration/StepDefinitions/MessagesStepDefinitions.cs b/ConsumerApi.Tests.Integration/StepDefinitions/MessagesStepDefinitions.cs new file mode 100644 index 0000000000..d83cc389cb --- /dev/null +++ b/ConsumerApi.Tests.Integration/StepDefinitions/MessagesStepDefinitions.cs @@ -0,0 +1,99 @@ +using Backbone.BuildingBlocks.SDK.Endpoints.Common.Types; +using Backbone.ConsumerApi.Sdk; +using Backbone.ConsumerApi.Sdk.Authentication; +using Backbone.ConsumerApi.Sdk.Endpoints.Messages.Types.Requests; +using Backbone.ConsumerApi.Sdk.Endpoints.Messages.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.Crypto; +using Microsoft.Extensions.Options; + +namespace Backbone.ConsumerApi.Tests.Integration.StepDefinitions; + +[Binding] +[Scope(Feature = "POST Message")] +internal class MessagesStepDefinitions +{ + private Client _client1 = null!; + private Client _client2 = null!; + private ApiResponse? _sendMessageResponse; + private readonly ClientCredentials _clientCredentials; + private readonly HttpClient _httpClient; + + public MessagesStepDefinitions(HttpClientFactory factory, IOptions httpConfiguration) + { + _httpClient = factory.CreateClient(); + _clientCredentials = new ClientCredentials(httpConfiguration.Value.ClientCredentials.ClientId, httpConfiguration.Value.ClientCredentials.ClientSecret); + } + + [Given("Identities i1 and i2 with an established Relationship")] + public async Task GivenIdentitiesI1AndI2WithAnEstablishedRelationship() + { + _client1 = await Client.CreateForNewIdentity(_httpClient, _clientCredentials, Constants.DEVICE_PASSWORD); + _client2 = await Client.CreateForNewIdentity(_httpClient, _clientCredentials, Constants.DEVICE_PASSWORD); + + await Utils.EstablishRelationshipBetween(_client1, _client2); + } + + [Given("i2 is in status \"ToBeDeleted\"")] + public async Task GivenIdentityI2IsToBeDeleted() + { + var startDeletionProcessResponse = await _client2.Identities.StartDeletionProcess(); + startDeletionProcessResponse.Should().BeASuccess(); + } + + [When("i1 sends a POST request to the /Messages endpoint with i2 as recipient")] + public async Task WhenAPostRequestIsSentToTheMessagesEndpoint() + { + var sendMessageRequest = new SendMessageRequest + { + Attachments = [], + Body = ConvertibleString.FromUtf8("Some Message").BytesRepresentation, + Recipients = + [ + new SendMessageRequestRecipientInformation + { + Address = _client2.IdentityData!.Address, + EncryptedKey = ConvertibleString.FromUtf8("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").BytesRepresentation + } + ] + }; + _sendMessageResponse = await _client1.Messages.SendMessage(sendMessageRequest); + } + + [Then(@"the response status code is (\d\d\d) \(.+\)")] + public void ThenTheResponseStatusCodeIs(int expectedStatusCode) + { + ((int)_sendMessageResponse!.Status).Should().Be(expectedStatusCode); + } + + [Then("the response contains a SendMessageResponse")] + public void ThenTheResponseContainsASendMessageResponse() + { + _sendMessageResponse!.Result.Should().NotBeNull(); + _sendMessageResponse.Should().BeASuccess(); + _sendMessageResponse.Should().ComplyWithSchema(); + } + + [Then(@"the response content contains an error with the error code ""([^""]*)""")] + public void ThenTheResponseContentIncludesAnErrorWithTheErrorCode(string errorCode) + { + _sendMessageResponse!.Error.Should().NotBeNull(); + _sendMessageResponse.Error!.Code.Should().Be(errorCode); + } + + [Then(@"the error contains a list of Identities to be deleted that includes i2")] + public void ThenTheErrorContainsAListOfIdentitiesToBeDeletedThatIncludesIdentityI2() + { + var data = _sendMessageResponse!.Error!.Data?.As(); + data.Should().NotBeNull(); + data!.PeersToBeDeleted.Contains(_client2.IdentityData!.Address).Should().BeTrue(); + } +} + +public class PeersToBeDeletedErrorData +{ + public required List PeersToBeDeleted { get; set; } +} diff --git a/Modules/Devices/src/Devices.Application/DTOs/IdentitySummaryDTO.cs b/Modules/Devices/src/Devices.Application/DTOs/IdentitySummaryDTO.cs index b28744e4f2..2354585c44 100644 --- a/Modules/Devices/src/Devices.Application/DTOs/IdentitySummaryDTO.cs +++ b/Modules/Devices/src/Devices.Application/DTOs/IdentitySummaryDTO.cs @@ -12,6 +12,7 @@ public IdentitySummaryDTO(Identity identity) Address = identity.Address.ToString(); PublicKey = identity.PublicKey; CreatedAt = identity.CreatedAt; + Status = identity.Status; Devices = identity.Devices.Select(d => new DeviceDTO { @@ -33,6 +34,7 @@ public IdentitySummaryDTO(Identity identity) public string? ClientId { get; set; } public byte[] PublicKey { get; set; } public DateTime CreatedAt { get; set; } + public IdentityStatus Status { get; set; } public IEnumerable Devices { get; set; } public int NumberOfDevices { get; set; } diff --git a/Modules/Devices/src/Devices.Application/Identities/Queries/ListIdentities/Handler.cs b/Modules/Devices/src/Devices.Application/Identities/Queries/ListIdentities/Handler.cs index 19bdda8145..fa77915539 100644 --- a/Modules/Devices/src/Devices.Application/Identities/Queries/ListIdentities/Handler.cs +++ b/Modules/Devices/src/Devices.Application/Identities/Queries/ListIdentities/Handler.cs @@ -1,8 +1,11 @@ +using System.Linq.Expressions; using Backbone.Modules.Devices.Application.DTOs; using Backbone.Modules.Devices.Application.Infrastructure.Persistence.Repository; +using Backbone.Modules.Devices.Domain.Entities.Identities; using MediatR; namespace Backbone.Modules.Devices.Application.Identities.Queries.ListIdentities; + public class Handler : IRequestHandler { private readonly IIdentitiesRepository _identitiesRepository; @@ -14,9 +17,12 @@ public Handler(IIdentitiesRepository repository) public async Task Handle(ListIdentitiesQuery request, CancellationToken cancellationToken) { - var dbPaginationResult = await _identitiesRepository.FindAll(request.PaginationFilter, cancellationToken); - var identityDtos = dbPaginationResult.ItemsOnPage.Select(el => new IdentitySummaryDTO(el)).ToList(); + Expression> filter = i => (request.Addresses == null || request.Addresses.Contains(i.Address)) && + (request.Status == null || i.Status == request.Status); + + var identities = await _identitiesRepository.Find(filter, cancellationToken); + var identityDtos = identities.Select(i => new IdentitySummaryDTO(i)).ToList(); - return new ListIdentitiesResponse(identityDtos, request.PaginationFilter, dbPaginationResult.TotalNumberOfItems); + return new ListIdentitiesResponse(identityDtos); } } diff --git a/Modules/Devices/src/Devices.Application/Identities/Queries/ListIdentities/ListIdentitiesQuery.cs b/Modules/Devices/src/Devices.Application/Identities/Queries/ListIdentities/ListIdentitiesQuery.cs index 9b2c2d3c35..0c66a83832 100644 --- a/Modules/Devices/src/Devices.Application/Identities/Queries/ListIdentities/ListIdentitiesQuery.cs +++ b/Modules/Devices/src/Devices.Application/Identities/Queries/ListIdentities/ListIdentitiesQuery.cs @@ -1,12 +1,17 @@ -using Backbone.BuildingBlocks.Application.Pagination; +using Backbone.DevelopmentKit.Identity.ValueObjects; +using Backbone.Modules.Devices.Domain.Entities.Identities; using MediatR; namespace Backbone.Modules.Devices.Application.Identities.Queries.ListIdentities; + public class ListIdentitiesQuery : IRequest { - public ListIdentitiesQuery(PaginationFilter paginationFilter) + public ListIdentitiesQuery(IEnumerable? addresses = null, IdentityStatus? status = null) { - PaginationFilter = paginationFilter; + Addresses = addresses; + Status = status; } - public PaginationFilter PaginationFilter { get; set; } + + public IEnumerable? Addresses { get; set; } + public IdentityStatus? Status { get; set; } } diff --git a/Modules/Devices/src/Devices.Application/Identities/Queries/ListIdentities/ListIdentitiesResponse.cs b/Modules/Devices/src/Devices.Application/Identities/Queries/ListIdentities/ListIdentitiesResponse.cs index 7352959245..3cbe69e6c5 100644 --- a/Modules/Devices/src/Devices.Application/Identities/Queries/ListIdentities/ListIdentitiesResponse.cs +++ b/Modules/Devices/src/Devices.Application/Identities/Queries/ListIdentities/ListIdentitiesResponse.cs @@ -1,8 +1,11 @@ -using Backbone.BuildingBlocks.Application.Pagination; +using Backbone.BuildingBlocks.Application.CQRS.BaseClasses; using Backbone.Modules.Devices.Application.DTOs; namespace Backbone.Modules.Devices.Application.Identities.Queries.ListIdentities; -public class ListIdentitiesResponse : PagedResponse + +public class ListIdentitiesResponse : CollectionResponseBase { - public ListIdentitiesResponse(IEnumerable items, PaginationFilter previousPaginationFilter, int totalRecords) : base(items, previousPaginationFilter, totalRecords) { } + public ListIdentitiesResponse(IEnumerable items) : base(items) + { + } } diff --git a/Modules/Devices/src/Devices.Application/Infrastructure/Persistence/Repository/IIdentitiesRepository.cs b/Modules/Devices/src/Devices.Application/Infrastructure/Persistence/Repository/IIdentitiesRepository.cs index 1b93b7428b..922a03c60c 100644 --- a/Modules/Devices/src/Devices.Application/Infrastructure/Persistence/Repository/IIdentitiesRepository.cs +++ b/Modules/Devices/src/Devices.Application/Infrastructure/Persistence/Repository/IIdentitiesRepository.cs @@ -9,7 +9,6 @@ namespace Backbone.Modules.Devices.Application.Infrastructure.Persistence.Reposi public interface IIdentitiesRepository { #region Identities - Task> FindAll(PaginationFilter paginationFilter, CancellationToken cancellationToken); Task Update(Identity identity, CancellationToken cancellationToken); Task FindByAddress(IdentityAddress address, CancellationToken cancellationToken, bool track = false); Task Exists(IdentityAddress address, CancellationToken cancellationToken); diff --git a/Modules/Devices/src/Devices.Infrastructure/Persistence/Repository/IdentitiesRepository.cs b/Modules/Devices/src/Devices.Infrastructure/Persistence/Repository/IdentitiesRepository.cs index 67a89cd342..334ca0d7a3 100644 --- a/Modules/Devices/src/Devices.Infrastructure/Persistence/Repository/IdentitiesRepository.cs +++ b/Modules/Devices/src/Devices.Infrastructure/Persistence/Repository/IdentitiesRepository.cs @@ -35,14 +35,6 @@ public IdentitiesRepository(DevicesDbContext dbContext, UserManager> FindAll(PaginationFilter paginationFilter, CancellationToken cancellationToken) - { - var paginationResult = await _readonlyIdentities - .IncludeAll(_dbContext) - .OrderAndPaginate(d => d.CreatedAt, paginationFilter, cancellationToken); - return paginationResult; - } - public async Task FindByAddress(IdentityAddress address, CancellationToken cancellationToken, bool track = false) { return await (track ? _identities : _readonlyIdentities) @@ -59,8 +51,7 @@ public async Task> GetIdentity public async Task Exists(IdentityAddress address, CancellationToken cancellationToken) { - return await _readonlyIdentities - .AnyAsync(i => i.Address == address, cancellationToken); + return await _readonlyIdentities.AnyAsync(i => i.Address == address, cancellationToken); } public async Task> FindAllWithDeletionProcessInStatus(DeletionProcessStatus status, CancellationToken cancellationToken, bool track = false) @@ -73,8 +64,7 @@ public async Task> FindAllWithDeletionProcessInStatus(Dele public async Task CountByClientId(string clientId, CancellationToken cancellationToken) { - return await _readonlyIdentities - .CountAsync(i => i.ClientId == clientId, cancellationToken); + return await _readonlyIdentities.CountAsync(i => i.ClientId == clientId, cancellationToken); } public async Task AddUser(ApplicationUser user, string password) diff --git a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetIdentity/FindByAddressStubRepository.cs b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetIdentity/FindByAddressStubRepository.cs index 2ab66cc455..2bb2977be9 100644 --- a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetIdentity/FindByAddressStubRepository.cs +++ b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetIdentity/FindByAddressStubRepository.cs @@ -36,11 +36,6 @@ public Task AddUser(ApplicationUser user, string password) throw new NotImplementedException(); } - public Task> FindAll(PaginationFilter paginationFilter, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - public Task> FindAllDevicesOfIdentity(IdentityAddress identity, IEnumerable ids, PaginationFilter paginationFilter, CancellationToken cancellationToken) { throw new NotImplementedException(); diff --git a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/ListIdentities/FindAllStubRepository.cs b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/ListIdentities/FindAllStubRepository.cs index 48bdf55caa..e6bfb91410 100644 --- a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/ListIdentities/FindAllStubRepository.cs +++ b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/ListIdentities/FindAllStubRepository.cs @@ -9,9 +9,9 @@ namespace Backbone.Modules.Devices.Application.Tests.Tests.Identities.Queries.Li public class FindAllStubRepository : IIdentitiesRepository { - private readonly DbPaginationResult _identities; + private readonly IEnumerable _identities; - public FindAllStubRepository(DbPaginationResult identities) + public FindAllStubRepository(IEnumerable identities) { _identities = identities; } @@ -36,11 +36,6 @@ public Task AddUser(ApplicationUser user, string password) throw new NotImplementedException(); } - public Task> FindAll(PaginationFilter paginationFilter, CancellationToken cancellationToken) - { - return Task.FromResult(_identities); - } - public Task> FindAllDevicesOfIdentity(IdentityAddress identity, IEnumerable ids, PaginationFilter paginationFilter, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -73,7 +68,7 @@ public Task Update(Identity identity, CancellationToken cancellationToken) public Task> Find(Expression> filter, CancellationToken cancellationToken, bool track = false) { - throw new NotImplementedException(); + return Task.FromResult(_identities); } public Task Delete(Expression> filter, CancellationToken cancellationToken) diff --git a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/ListIdentities/HandlerTests.cs b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/ListIdentities/HandlerTests.cs index 836714f08f..a2ea0c1ca0 100644 --- a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/ListIdentities/HandlerTests.cs +++ b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/ListIdentities/HandlerTests.cs @@ -1,10 +1,7 @@ -using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.Persistence.Database; -using Backbone.BuildingBlocks.Application.Pagination; using Backbone.Modules.Devices.Application.Identities.Queries.ListIdentities; using Backbone.Modules.Devices.Domain.Entities.Identities; using Backbone.UnitTestTools.BaseClasses; using FluentAssertions; -using FluentAssertions.Execution; using Xunit; using static Backbone.UnitTestTools.Data.TestDataGenerator; @@ -12,32 +9,25 @@ namespace Backbone.Modules.Devices.Application.Tests.Tests.Identities.Queries.Li public class HandlerTests : AbstractTestsBase { - public HandlerTests() - { - AssertionScope.Current.FormattingOptions.MaxLines = 1000; - } - [Fact] public async Task Returns_an_empty_list_when_no_identities_exist() { // Arrange - var identitiesList = new List(); - var request = new PaginationFilter() { PageSize = 5 }; - var handler = CreateHandler(new FindAllStubRepository(MakeDbPaginationResult(identitiesList))); + var identities = new List(); + var handler = CreateHandler(new FindAllStubRepository(identities)); // Act - var result = await handler.Handle(new ListIdentitiesQuery(request), CancellationToken.None); + var result = await handler.Handle(new ListIdentitiesQuery(), CancellationToken.None); // Assert - result.Should().HaveCount(0); + result.Should().HaveCount(identities.Count); } [Fact] - public async Task Returns_a_list_of_all_existing_identities() + public async Task? Returns_a_list_of_all_existing_identities() { // Arrange - var request = new PaginationFilter(); - List identitiesList = + List identities = [ new Identity(CreateRandomDeviceId(), CreateRandomIdentityAddress(), @@ -51,30 +41,28 @@ public async Task Returns_a_list_of_all_existing_identities() TestDataGenerator.CreateRandomTierId(), 1) ]; - - var handler = CreateHandler(new FindAllStubRepository(MakeDbPaginationResult(identitiesList))); + var handler = CreateHandler(new FindAllStubRepository(identities)); // Act - var result = await handler.Handle(new ListIdentitiesQuery(request), CancellationToken.None); + var result = await handler.Handle(new ListIdentitiesQuery(), CancellationToken.None); // Assert - result.Should().HaveCount(2); + result.Should().HaveCount(identities.Count); } [Fact] public async Task Returned_identities_have_all_properties_filled_as_expected() { // Arrange - var request = new PaginationFilter(); var expectedClientId = CreateRandomDeviceId(); var expectedAddress = CreateRandomIdentityAddress(); var expectedTierId = TestDataGenerator.CreateRandomTierId(); - List identitiesList = [new(expectedClientId, expectedAddress, [], expectedTierId, 1)]; + List identities = [new Identity(expectedClientId, expectedAddress, [], expectedTierId, 1)]; - var handler = CreateHandler(new FindAllStubRepository(MakeDbPaginationResult(identitiesList))); + var handler = CreateHandler(new FindAllStubRepository(identities)); // Act - var result = await handler.Handle(new ListIdentitiesQuery(request), CancellationToken.None); + var result = await handler.Handle(new ListIdentitiesQuery(), CancellationToken.None); // Assert result.Should().HaveCount(1); @@ -82,6 +70,7 @@ public async Task Returned_identities_have_all_properties_filled_as_expected() result.First().Address.Should().Be(expectedAddress); result.First().PublicKey.Should().BeEquivalentTo(Array.Empty()); result.First().TierId.Should().BeEquivalentTo(expectedTierId); + result.First().Status.Should().Be(IdentityStatus.Active); result.First().IdentityVersion.Should().Be(1); } @@ -89,9 +78,4 @@ private Handler CreateHandler(FindAllStubRepository findAllStubRepository) { return new Handler(findAllStubRepository); } - - private DbPaginationResult MakeDbPaginationResult(List identities) - { - return new DbPaginationResult(identities, identities.Count); - } } diff --git a/Modules/Messages/src/Messages.Application/ApplicationErrors.cs b/Modules/Messages/src/Messages.Application/ApplicationErrors.cs index 9a0ca52e26..c759f97632 100644 --- a/Modules/Messages/src/Messages.Application/ApplicationErrors.cs +++ b/Modules/Messages/src/Messages.Application/ApplicationErrors.cs @@ -4,6 +4,13 @@ namespace Backbone.Modules.Messages.Application; public static class ApplicationErrors { + public static ApplicationError RecipientsToBeDeleted(IEnumerable 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; diff --git a/Modules/Messages/src/Messages.ConsumerApi/Controllers/MessagesController.cs b/Modules/Messages/src/Messages.ConsumerApi/Controllers/MessagesController.cs index d3aa296a81..7402f651e9 100644 --- a/Modules/Messages/src/Messages.ConsumerApi/Controllers/MessagesController.cs +++ b/Modules/Messages/src/Messages.ConsumerApi/Controllers/MessagesController.cs @@ -3,6 +3,8 @@ 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; @@ -60,6 +62,11 @@ public async Task GetMessage(MessageId id, [FromQuery] bool? noBo [ProducesError(StatusCodes.Status400BadRequest)] public async Task SendMessage(SendMessageCommand request, CancellationToken cancellationToken) { + var recipientAddresses = request.Recipients.Select(r => r.Address); + 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); }