From c74f57ed6e7b6d87577d5f7991d90260c5954e6b Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Thu, 1 Dec 2022 21:41:43 +0000 Subject: [PATCH] Enforce event Id on event controller --- .../ControllerTests/ControllerTests.cs | 71 +++++++++++++++++++ .../Controllers/DomainEventController.cs | 36 +++++----- 2 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 TransactionProcessor.Tests/ControllerTests/ControllerTests.cs diff --git a/TransactionProcessor.Tests/ControllerTests/ControllerTests.cs b/TransactionProcessor.Tests/ControllerTests/ControllerTests.cs new file mode 100644 index 00000000..bfeab949 --- /dev/null +++ b/TransactionProcessor.Tests/ControllerTests/ControllerTests.cs @@ -0,0 +1,71 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Moq; +using Newtonsoft.Json; +using Shared.EventStore.EventHandling; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TransactionProcessor.Controllers; +using TransactionProcessor.Transaction.DomainEvents; +using Xunit; + +namespace TransactionProcessor.Tests.ControllerTests +{ + using System.Threading; + using Shared.General; + using Shared.Logger; + + public class ControllerTests + { + public ControllerTests() + { + Logger.Initialise(new NullLogger()); + } + [Fact] + public async Task DomainEventController_EventIdNotPresentInJson_ErrorThrown() + { + Mock resolver = new Mock(); + TypeMap.AddType("TransactionHasBeenCompletedEvent"); + DefaultHttpContext httpContext = new DefaultHttpContext(); + httpContext.Request.Headers["eventType"] = "TransactionHasBeenCompletedEvent"; + DomainEventController controller = new DomainEventController(resolver.Object) + { + ControllerContext = new ControllerContext() + { + HttpContext = httpContext + } + }; + String json = "{\r\n \"completedDateTime\": \"2022-11-08T15:40:07\",\r\n \"estateId\": \"435613ac-a468-47a3-ac4f-649d89764c22\",\r\n \"isAuthorised\": true,\r\n \"transactionAmount\": 35.0,\r\n \"merchantId\": \"8bc8434d-41f9-4cc3-83bc-e73f20c02e1d\",\r\n \"responseCode\": \"0000\",\r\n \"responseMessage\": \"SUCCESS\",\r\n \"transactionId\": \"626644c5-bb7b-40ca-821e-cf115488867b\",\r\n}"; + Object request = JsonConvert.DeserializeObject(json); + ArgumentException ex = Should.Throw(async () => { + await controller.PostEventAsync(request, CancellationToken.None); + }); + ex.Message.ShouldBe("Domain Event must contain an Event Id"); + } + + [Fact] + public async Task DomainEventController_EventIdPresentInJson_NoErrorThrown() + { + Mock resolver = new Mock(); + TypeMap.AddType("TransactionHasBeenCompletedEvent"); + DefaultHttpContext httpContext = new DefaultHttpContext(); + httpContext.Request.Headers["eventType"] = "TransactionHasBeenCompletedEvent"; + DomainEventController controller = new DomainEventController(resolver.Object) + { + ControllerContext = new ControllerContext() + { + HttpContext = httpContext + } + }; + String json = "{\r\n \"completedDateTime\": \"2022-11-08T15:40:07\",\r\n \"estateId\": \"435613ac-a468-47a3-ac4f-649d89764c22\",\r\n \"isAuthorised\": true,\r\n \"transactionAmount\": 35.0,\r\n \"merchantId\": \"8bc8434d-41f9-4cc3-83bc-e73f20c02e1d\",\r\n \"responseCode\": \"0000\",\r\n \"responseMessage\": \"SUCCESS\",\r\n \"transactionId\": \"626644c5-bb7b-40ca-821e-cf115488867b\",\r\n \"eventId\": \"9840045a-df9f-4ae3-879d-db205a744bf3\"\r\n}"; + Object request = JsonConvert.DeserializeObject(json); + Should.NotThrow(async () => { + await controller.PostEventAsync(request, CancellationToken.None); + }); + } + } +} diff --git a/TransactionProcessor/Controllers/DomainEventController.cs b/TransactionProcessor/Controllers/DomainEventController.cs index 1b7759cd..42c6fc28 100644 --- a/TransactionProcessor/Controllers/DomainEventController.cs +++ b/TransactionProcessor/Controllers/DomainEventController.cs @@ -9,6 +9,7 @@ namespace TransactionProcessor.Controllers using System.Threading; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; + using Newtonsoft.Json.Linq; using Shared.DomainDrivenDesign.EventSourcing; using Shared.EventStore.Aggregate; using Shared.EventStore.EventHandling; @@ -94,11 +95,6 @@ public async Task PostEventAsync([FromBody] Object request, } } - /// - /// Callbacks the specified cancellation token. - /// - /// The cancellation token. - /// The event identifier. private void Callback(CancellationToken cancellationToken, Guid eventId) { @@ -109,22 +105,17 @@ private void Callback(CancellationToken cancellationToken, } } - /// - /// Gets the domain event. - /// - /// The domain event. - /// private async Task GetDomainEvent(Object domainEvent) { - String eventType = this.Request.Query["eventType"].ToString(); + String eventType = this.Request.Headers["eventType"].ToString(); - var type = TypeMap.GetType(eventType); + Type type = TypeMap.GetType(eventType); if (type == null) throw new Exception($"Failed to find a domain event with type {eventType}"); JsonIgnoreAttributeIgnorerContractResolver jsonIgnoreAttributeIgnorerContractResolver = new JsonIgnoreAttributeIgnorerContractResolver(); - var jsonSerialiserSettings = new JsonSerializerSettings + JsonSerializerSettings jsonSerialiserSettings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, TypeNameHandling = TypeNameHandling.All, @@ -135,15 +126,28 @@ private async Task GetDomainEvent(Object domainEvent) if (type.IsSubclassOf(typeof(DomainEvent))) { - var json = JsonConvert.SerializeObject(domainEvent, jsonSerialiserSettings); - DomainEventFactory domainEventFactory = new(); + String json = JsonConvert.SerializeObject(domainEvent, jsonSerialiserSettings); - return domainEventFactory.CreateDomainEvent(json, type); + DomainEventFactory domainEventFactory = new(); + String validatedJson = this.ValidateEvent(json); + return domainEventFactory.CreateDomainEvent(validatedJson, type); } return null; } + private String ValidateEvent(String domainEventJson) + { + JObject domainEvent = JObject.Parse(domainEventJson); + + if (domainEvent.ContainsKey("eventId") == false || domainEvent["eventId"].ToObject() == Guid.Empty) + { + throw new ArgumentException("Domain Event must contain an Event Id"); + } + + return domainEventJson; + } + #endregion #region Others