From 8d7b33396dcb2708a82a707ebe696838cfef2ccc Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Wed, 22 Jul 2020 14:42:06 +0100 Subject: [PATCH] Aggregate and event work completed --- .../Services/TransactionDomainService.cs | 3 + TransactionProcessor.Testing/TestData.cs | 4 + .../ProductDetailsAddedToTransactionEvent.cs | 98 +++++++++++++++++++ .../DomainEventTests.cs | 16 +++ .../TransactionAggregateTests.cs | 79 ++++++++++++++- .../TransactionAggregate.cs | 59 +++++++++++ 6 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 TransactionProcessor.Transaction.DomainEvents/ProductDetailsAddedToTransactionEvent.cs diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs index f776ef24..cf971820 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Eventing.Reader; using System.Linq; using System.Threading; @@ -267,6 +268,7 @@ await this.TransactionAggregateManager.RecordAdditionalResponseData(estateId, /// Name of the field. /// The additional transaction metadata. /// + [ExcludeFromCodeCoverage] private T ExtractFieldFromMetadata(String fieldName, Dictionary additionalTransactionMetadata) { @@ -304,6 +306,7 @@ await this.EstateClient.AddDeviceToMerchant(this.TokenResponse.AccessToken, /// Generates the transaction reference. /// /// + [ExcludeFromCodeCoverage] private String GenerateTransactionReference() { Int64 i = 1; diff --git a/TransactionProcessor.Testing/TestData.cs b/TransactionProcessor.Testing/TestData.cs index 8bddd476..4e2fe0c4 100644 --- a/TransactionProcessor.Testing/TestData.cs +++ b/TransactionProcessor.Testing/TestData.cs @@ -29,6 +29,10 @@ public class TestData public static Guid EstateId = Guid.Parse("A522FA27-F9D0-470A-A88D-325DED3B62EE"); + public static Guid ContractId = Guid.Parse("97A9ED00-E522-428C-B3C3-5931092DBDCE"); + + public static Guid ProductId = Guid.Parse("ABA0E536-4E43-4E26-8362-7FB549DDA534"); + public static String EstateName = "Test Estate 1"; public static String FailedSafaricomTopup = diff --git a/TransactionProcessor.Transaction.DomainEvents/ProductDetailsAddedToTransactionEvent.cs b/TransactionProcessor.Transaction.DomainEvents/ProductDetailsAddedToTransactionEvent.cs new file mode 100644 index 00000000..3c6614bc --- /dev/null +++ b/TransactionProcessor.Transaction.DomainEvents/ProductDetailsAddedToTransactionEvent.cs @@ -0,0 +1,98 @@ +namespace TransactionProcessor.Transaction.DomainEvents +{ + using System; + using Newtonsoft.Json; + using Shared.DomainDrivenDesign.EventSourcing; + + /// + /// + /// + /// + [JsonObject] + public class ProductDetailsAddedToTransactionEvent : DomainEvent + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The aggregate identifier. + /// The event identifier. + /// The estate identifier. + /// The contract identifier. + /// The product identifier. + private ProductDetailsAddedToTransactionEvent(Guid aggregateId, + Guid eventId, + Guid estateId, + Guid contractId, + Guid productId) : base(aggregateId, eventId) + { + this.TransactionId = aggregateId; + this.EstateId = estateId; + this.ContractId = contractId; + this.ProductId = productId; + } + + #endregion + + #region Properties + + /// + /// Gets the contract identifier. + /// + /// + /// The contract identifier. + /// + [JsonProperty] + public Guid ContractId { get; private set; } + + /// + /// Gets the estate identifier. + /// + /// + /// The estate identifier. + /// + [JsonProperty] + public Guid EstateId { get; private set; } + + /// + /// Gets the product identifier. + /// + /// + /// The product identifier. + /// + [JsonProperty] + public Guid ProductId { get; private set; } + + /// + /// Gets the transaction identifier. + /// + /// + /// The transaction identifier. + /// + [JsonProperty] + public Guid TransactionId { get; private set; } + + #endregion + + #region Methods + + /// + /// Creates the specified aggregate identifier. + /// + /// The aggregate identifier. + /// The estate identifier. + /// The contract identifier. + /// The product identifier. + /// + public static ProductDetailsAddedToTransactionEvent Create(Guid aggregateId, + Guid estateId, + Guid contractId, + Guid productId) + { + return new ProductDetailsAddedToTransactionEvent(aggregateId, Guid.NewGuid(), estateId, contractId, productId); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor.TransactionAggregate.Tests/DomainEventTests.cs b/TransactionProcessor.TransactionAggregate.Tests/DomainEventTests.cs index cb2dcbbc..ab646489 100644 --- a/TransactionProcessor.TransactionAggregate.Tests/DomainEventTests.cs +++ b/TransactionProcessor.TransactionAggregate.Tests/DomainEventTests.cs @@ -230,5 +230,21 @@ public void CustomerEmailReceiptRequestedEvent_CanBeCreated_IsCreated() customerEmailReceiptRequestedEvent.MerchantId.ShouldBe(TestData.MerchantId); customerEmailReceiptRequestedEvent.CustomerEmailAddress.ShouldBe(TestData.CustomerEmailAddress); } + + [Fact] + public void ProductDetailsAddedToTransactionEvent_CanBeCreated_IsCreated() + { + ProductDetailsAddedToTransactionEvent productDetailsAddedToTransactionEvent = ProductDetailsAddedToTransactionEvent.Create(TestData.TransactionId, TestData.EstateId,TestData.ContractId, TestData.ProductId); + + productDetailsAddedToTransactionEvent.ShouldNotBeNull(); + productDetailsAddedToTransactionEvent.AggregateId.ShouldBe(TestData.TransactionId); + productDetailsAddedToTransactionEvent.EventCreatedDateTime.ShouldNotBe(DateTime.MinValue); + productDetailsAddedToTransactionEvent.EventId.ShouldNotBe(Guid.Empty); + productDetailsAddedToTransactionEvent.TransactionId.ShouldBe(TestData.TransactionId); + productDetailsAddedToTransactionEvent.EstateId.ShouldBe(TestData.EstateId); + productDetailsAddedToTransactionEvent.ProductId.ShouldBe(TestData.ProductId); + productDetailsAddedToTransactionEvent.ContractId.ShouldBe(TestData.ContractId); + + } } } diff --git a/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs b/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs index 82678be7..aee7b755 100644 --- a/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs +++ b/TransactionProcessor.TransactionAggregate.Tests/TransactionAggregateTests.cs @@ -18,7 +18,6 @@ public void TransactionAggregate_CanBeCreated_IsCreated() } [Theory] - [InlineData(TransactionType.Logon)] [InlineData(TransactionType.Sale)] public void TransactionAggregate_StartTransaction_TransactionIsStarted(TransactionType transactionType) { @@ -38,6 +37,25 @@ public void TransactionAggregate_StartTransaction_TransactionIsStarted(Transacti transactionAggregate.TransactionAmount.ShouldBe(TestData.TransactionAmount); } + [Theory] + [InlineData(TransactionType.Logon)] + public void TransactionAggregate_StartTransaction_NullAmount_TransactionIsStarted(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, + TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, null); + + transactionAggregate.IsStarted.ShouldBeTrue(); + transactionAggregate.TransactionDateTime.ShouldBe(TestData.TransactionDateTime); + transactionAggregate.TransactionNumber.ShouldBe(TestData.TransactionNumber); + transactionAggregate.TransactionType.ShouldBe(transactionType); + transactionAggregate.EstateId.ShouldBe(TestData.EstateId); + transactionAggregate.MerchantId.ShouldBe(TestData.MerchantId); + transactionAggregate.DeviceIdentifier.ShouldBe(TestData.DeviceIdentifier); + transactionAggregate.TransactionReference.ShouldBe(TestData.TransactionReference); + transactionAggregate.TransactionAmount.ShouldBeNull(); + } + [Theory] [InlineData(TransactionType.Logon)] [InlineData(TransactionType.Sale)] @@ -144,6 +162,65 @@ public void TransactionAggregate_StartTransaction_InvalidData_ErrorThrown(Boolea }); } + [Theory] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_AddProductDetails_ProductDetailsAdded(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, + TestData.TransactionAmount); + + transactionAggregate.AddProductDetails(TestData.ContractId,TestData.ProductId); + + transactionAggregate.IsProductDetailsAdded.ShouldBeTrue(); + transactionAggregate.ContractId.ShouldBe(TestData.ContractId); + transactionAggregate.ProductId.ShouldBe(TestData.ProductId); + } + + [Theory] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_AddProductDetails_InvalidContractId_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, + TestData.TransactionAmount); + + Should.Throw(() => + { + transactionAggregate.AddProductDetails(Guid.Empty, TestData.ProductId); + }); + } + + [Theory] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_AddProductDetails_InvalidProductId_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, + TestData.TransactionAmount); + + Should.Throw(() => + { + transactionAggregate.AddProductDetails(TestData.ContractId, Guid.Empty); + }); + } + + [Theory] + [InlineData(TransactionType.Sale)] + public void TransactionAggregate_AddProductDetails_ProductDetailsAlreadyAdded_ErrorThrown(TransactionType transactionType) + { + TransactionAggregate transactionAggregate = TransactionAggregate.Create(TestData.TransactionId); + transactionAggregate.StartTransaction(TestData.TransactionDateTime, TestData.TransactionNumber, transactionType, TestData.TransactionReference, TestData.EstateId, TestData.MerchantId, TestData.DeviceIdentifier, + TestData.TransactionAmount); + + transactionAggregate.AddProductDetails(TestData.ContractId, TestData.ProductId); + + Should.Throw(() => + { + transactionAggregate.AddProductDetails(TestData.ContractId, TestData.ProductId); + }); + } + [Theory] [InlineData(TransactionType.Logon)] public void TransactionAggregate_AuthoriseTransactionLocally_TransactionIsAuthorised(TransactionType transactionType) diff --git a/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs b/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs index 9362baf5..19de8f09 100644 --- a/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs +++ b/TransactionProcessor.TransactionAgrgegate/TransactionAggregate.cs @@ -88,6 +88,21 @@ private TransactionAggregate(Guid aggregateId) /// public Guid EstateId { get; private set; } + /// + /// Gets the contract identifier. + /// + /// + /// The contract identifier. + /// + public Guid ContractId { get; private set; } + /// + /// Gets the product identifier. + /// + /// + /// The product identifier. + /// + public Guid ProductId { get; private set; } + /// /// Gets a value indicating whether this instance is authorised. /// @@ -471,6 +486,27 @@ public void StartTransaction(DateTime transactionDateTime, this.ApplyAndPend(transactionHasStartedEvent); } + /// + /// Adds the product details. + /// + /// The contract identifier. + /// The product identifier. + public void AddProductDetails(Guid contractId, + Guid productId) + { + Guard.ThrowIfInvalidGuid(contractId, typeof(ArgumentException), $"Contract Id must not be [{Guid.Empty}]"); + Guard.ThrowIfInvalidGuid(productId, typeof(ArgumentException), $"Product Id must not be [{Guid.Empty}]"); + + this.CheckProductDetailsNotAlreadyAdded(); + + ProductDetailsAddedToTransactionEvent productDetailsAddedToTransactionEvent = ProductDetailsAddedToTransactionEvent.Create(this.AggregateId, + this.EstateId, + contractId, + productId); + + this.ApplyAndPend(productDetailsAddedToTransactionEvent); + } + /// /// Gets the metadata. /// @@ -493,6 +529,14 @@ protected override void PlayEvent(DomainEvent domainEvent) this.PlayEvent((dynamic)domainEvent); } + private void CheckProductDetailsNotAlreadyAdded() + { + if (this.IsProductDetailsAdded) + { + throw new InvalidOperationException("Product details already added"); + } + } + /// /// Checks the additional request data not already recorded. /// @@ -737,6 +781,21 @@ private void PlayEvent(TransactionDeclinedByOperatorEvent domainEvent) this.ResponseMessage = domainEvent.ResponseMessage; } + private void PlayEvent(ProductDetailsAddedToTransactionEvent domainEvent) + { + this.IsProductDetailsAdded = true; + this.ContractId = domainEvent.ContractId; + this.ProductId = domainEvent.ProductId; + } + + /// + /// Gets a value indicating whether this instance is product details added. + /// + /// + /// true if this instance is product details added; otherwise, false. + /// + public Boolean IsProductDetailsAdded { get; private set; } + #endregion } } \ No newline at end of file