From a790259ea617a352ed9d9516b417a5ad6b1b2f65 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 23 Sep 2026 13:25:19 +0100 Subject: [PATCH 1/2] start to handle FileLineTransactionDispatchFailedEvent also improved the error handling on the event controller --- Directory.Packages.props | 2 +- .../ReadModelDomainEventHandler.cs | 1 + ...TransactionProcessorReadModelRepository.cs | 16 ++++++++ .../DomainEventControllerTests.cs | 6 +-- .../Controllers/DomainEventController.cs | 41 ++++++++++++++----- TransactionProcessor/appsettings.json | 3 +- 6 files changed, 54 insertions(+), 15 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index afe03537..3f2d833d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -16,7 +16,7 @@ - + diff --git a/TransactionProcessor.BusinessLogic/EventHandling/ReadModelDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/ReadModelDomainEventHandler.cs index 6cd7906f..6f9c2ffa 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/ReadModelDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/ReadModelDomainEventHandler.cs @@ -159,6 +159,7 @@ private Task HandleFileDomainEvent(IDomainEvent domainEvent, FileProcessor.File.DomainEvents.FileLineProcessingSuccessfulEvent de => this.EstateReportingRepository.UpdateFileLine(de, cancellationToken), FileProcessor.File.DomainEvents.FileLineProcessingFailedEvent de => this.EstateReportingRepository.UpdateFileLine(de, cancellationToken), FileProcessor.File.DomainEvents.FileLineProcessingIgnoredEvent de => this.EstateReportingRepository.UpdateFileLine(de, cancellationToken), + FileProcessor.File.DomainEvents.FileLineTransactionDispatchFailedEvent de => this.EstateReportingRepository.UpdateFileLine(de, cancellationToken), FileProcessor.File.DomainEvents.FileProcessingCompletedEvent de => this.EstateReportingRepository.UpdateFileAsComplete(de, cancellationToken), _ => null }; diff --git a/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs b/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs index 0cd193bb..a9798ae2 100644 --- a/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs +++ b/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs @@ -182,6 +182,9 @@ Task UpdateFileAsComplete(FileProcessingCompletedEvent domainEvent, Task UpdateFileLine(FileLineProcessingSuccessfulEvent domainEvent, CancellationToken cancellationToken); + + Task UpdateFileLine(FileLineTransactionDispatchFailedEvent domainEvent, + CancellationToken cancellationToken); Task UpdateFileLine(FileLineProcessingFailedEvent domainEvent, CancellationToken cancellationToken); @@ -2052,6 +2055,19 @@ public async Task UpdateFileLine(FileLineProcessingSuccessfulEvent domai cancellationToken); } + public async Task UpdateFileLine(FileLineTransactionDispatchFailedEvent domainEvent, + CancellationToken cancellationToken) { + using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, domainEvent.EstateId.ToString()); + await using EstateManagementContext context = resolvedContext.Context; + + return await this.UpdateFileLineStatus(context, + domainEvent.FileId, + domainEvent.LineNumber, + Guid.Empty, + "D", + cancellationToken); + } + private async Task UpdateFileLineStatus(EstateManagementContext context, Guid fileId, Int32 lineNumber, diff --git a/TransactionProcessor.Tests/ControllerTests/DomainEventControllerTests.cs b/TransactionProcessor.Tests/ControllerTests/DomainEventControllerTests.cs index 00532a39..a0015111 100644 --- a/TransactionProcessor.Tests/ControllerTests/DomainEventControllerTests.cs +++ b/TransactionProcessor.Tests/ControllerTests/DomainEventControllerTests.cs @@ -32,8 +32,8 @@ public async Task PostEventAsync_WhenAllHandlersSucceed_ReturnsOk() domainEvent, new ResultDomainEventHandler(Result.Success())); - IActionResult result = await controller.PostEventAsync(domainEvent, CancellationToken.None); - + IResult result = await controller.PostEventAsync(domainEvent, CancellationToken.None); + result.ShouldBeOfType(); } @@ -44,7 +44,7 @@ public async Task PostEventAsync_WhenNoHandlersAreConfigured_ReturnsOk() DomainEvent domainEvent = CreateDomainEvent(); DomainEventController controller = CreateController(domainEvent); - IActionResult result = await controller.PostEventAsync(domainEvent, CancellationToken.None); + IResult result = await controller.PostEventAsync(domainEvent, CancellationToken.None); result.ShouldBeOfType(); } diff --git a/TransactionProcessor/Controllers/DomainEventController.cs b/TransactionProcessor/Controllers/DomainEventController.cs index 62954d6e..715716bd 100644 --- a/TransactionProcessor/Controllers/DomainEventController.cs +++ b/TransactionProcessor/Controllers/DomainEventController.cs @@ -3,7 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; using Shared.Exceptions; +using Shared.Results; +using Shared.Results.Web; namespace TransactionProcessor.Controllers { @@ -55,10 +59,15 @@ public DomainEventController(IDomainEventHandlerResolver domainEventHandlerResol /// The cancellation token. /// [HttpPost] - public async Task PostEventAsync([FromBody] Object request, + public async Task PostEventAsync([FromBody] Object request, CancellationToken cancellationToken) { - IDomainEvent domainEvent = await this.GetDomainEvent(request); + Result domainEventResult = await this.GetDomainEvent(request); + + if (domainEventResult.IsFailed) + return ResponseFactory.FromResult(domainEventResult, SuccessFactory); + + IDomainEvent domainEvent = domainEventResult.Data; List eventHandlers = this.GetDomainEventHandlers(domainEvent); @@ -72,7 +81,7 @@ public async Task PostEventAsync([FromBody] Object request, { // Log a warning out Logger.LogInformation($"No event handlers configured for Event Type [{domainEvent.GetType().Name}]"); - return this.Ok(); + return Results.Ok(); } List executions = eventHandlers @@ -91,7 +100,7 @@ public async Task PostEventAsync([FromBody] Object request, List failures = executions.SelectMany(this.GetFailures).ToList(); if (failures.Any()) { - return this.Problem(title: "One or more event handlers failed", + return Results.Problem(title: "One or more event handlers failed", statusCode: 500, extensions: new Dictionary { @@ -102,8 +111,8 @@ public async Task PostEventAsync([FromBody] Object request, } Logger.LogWarning($"Finished processing event - ID [{domainEvent.EventId}]"); - - return this.Ok(); + + return Results.Ok(); } catch (Exception ex) { @@ -114,6 +123,10 @@ public async Task PostEventAsync([FromBody] Object request, } } + private IDomainEvent SuccessFactory(IDomainEvent arg) { + return arg; + } + private void Callback(CancellationToken cancellationToken, Guid eventId) { @@ -177,21 +190,29 @@ private List GetDomainEventHandlers(IDomainEvent domainEven return eventHandlersResult.Data; } - private async Task GetDomainEvent(Object domainEvent) + private async Task> GetDomainEvent(Object domainEvent) { String eventType = this.Request.Headers["eventType"].ToString(); - Type type = TypeMap.GetType(eventType); + Type type; + + try { + type = TypeMap.GetType(eventType); + } + catch (Exception ex) { + // Just swallowing exception here... + type = null; + } if (type == null) - throw new NotFoundException($"Failed to find a domain event with type {eventType}"); + return Result.NotFound($"Failed to find a domain event with type {eventType}"); if (type.IsSubclassOf(typeof(DomainEvent))) { String json = StringSerialiser.Serialise(domainEvent); DomainEventFactory domainEventFactory = new(); - return domainEventFactory.CreateDomainEvent(json, type); + return Result.Success(domainEventFactory.CreateDomainEvent(json, type) as IDomainEvent); } return null; diff --git a/TransactionProcessor/appsettings.json b/TransactionProcessor/appsettings.json index dfee7b24..a3b48483 100644 --- a/TransactionProcessor/appsettings.json +++ b/TransactionProcessor/appsettings.json @@ -94,7 +94,8 @@ "FileProcessingCompletedEvent", "MerchantOpeningHoursUpdatedEvent", "MerchantScheduleCreatedEvent", - "MerchantScheduleMonthUpdatedEvent" + "MerchantScheduleMonthUpdatedEvent", + "FileLineTransactionDispatchFailedEvent" ] }, "EventHandlerConfigurationDomain": { From 4ffc44be9c23c1e11a3814c7303c471056a474ce Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 23 Sep 2026 13:47:54 +0100 Subject: [PATCH 2/2] fix controller tests for minimal api results --- .../ControllerTests/DomainEventControllerTests.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/TransactionProcessor.Tests/ControllerTests/DomainEventControllerTests.cs b/TransactionProcessor.Tests/ControllerTests/DomainEventControllerTests.cs index a0015111..c8ec9a66 100644 --- a/TransactionProcessor.Tests/ControllerTests/DomainEventControllerTests.cs +++ b/TransactionProcessor.Tests/ControllerTests/DomainEventControllerTests.cs @@ -1,5 +1,6 @@ using Imposter.Abstractions; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Shouldly; @@ -34,7 +35,7 @@ public async Task PostEventAsync_WhenAllHandlersSucceed_ReturnsOk() IResult result = await controller.PostEventAsync(domainEvent, CancellationToken.None); - result.ShouldBeOfType(); + result.ShouldBeOfType(); } [Fact] @@ -46,7 +47,7 @@ public async Task PostEventAsync_WhenNoHandlersAreConfigured_ReturnsOk() IResult result = await controller.PostEventAsync(domainEvent, CancellationToken.None); - result.ShouldBeOfType(); + result.ShouldBeOfType(); } [Fact] @@ -58,10 +59,10 @@ public async Task PostEventAsync_WhenHandlerReturnsFailure_ReturnsDetailedProble DomainEventController controller = CreateController(domainEvent, handler); - ObjectResult result = (ObjectResult)await controller.PostEventAsync(domainEvent, CancellationToken.None); + ProblemHttpResult result = (ProblemHttpResult)await controller.PostEventAsync(domainEvent, CancellationToken.None); result.StatusCode.ShouldBe(500); - ProblemDetails problemDetails = (ProblemDetails)result.Value; + var problemDetails = result.ProblemDetails; problemDetails.Title.ShouldBe("One or more event handlers failed"); problemDetails.Status.ShouldBe(500); problemDetails.Extensions["eventId"].ShouldBeOfType(); @@ -82,9 +83,9 @@ public async Task PostEventAsync_WhenMultipleHandlersFail_ReturnsEveryFailure() new ResultDomainEventHandler(Result.Failure("First failure")), new ThrowingDomainEventHandler("Second failure")); - ObjectResult result = (ObjectResult)await controller.PostEventAsync(domainEvent, CancellationToken.None); + ProblemHttpResult result = (ProblemHttpResult)await controller.PostEventAsync(domainEvent, CancellationToken.None); - String failuresJson = System.Text.Json.JsonSerializer.Serialize(((ProblemDetails)result.Value).Extensions["failures"]); + String failuresJson = System.Text.Json.JsonSerializer.Serialize(result.ProblemDetails.Extensions["failures"]); result.StatusCode.ShouldBe(500); failuresJson.ShouldContain("First failure"); failuresJson.ShouldContain("Second failure");