Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DebugType>Full</DebugType>
<DebugType>None</DebugType>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
Expand Down
226 changes: 226 additions & 0 deletions TransactionProcessor.Aggregates/MerchantDepositListAggregate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text;
using Shared.DomainDrivenDesign.EventSourcing;
using Shared.EventStore.Aggregate;
using Shared.General;
using Shared.ValueObjects;
using TransactionProcessor.DomainEvents;
using TransactionProcessor.Models.Merchant;

namespace TransactionProcessor.Aggregates{
public static class MerchantDepositListAggregateExtensions{
#region Methods

public static void Create(this MerchantDepositListAggregate aggregate,
MerchantAggregate merchantAggregate,
DateTime dateCreated){
aggregate.EnsureMerchantHasBeenCreated(merchantAggregate);
// Ensure this aggregate has not already been created
if (aggregate.IsCreated)

Check failure on line 20 in TransactionProcessor.Aggregates/MerchantDepositListAggregate.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

TransactionProcessor.Aggregates/MerchantDepositListAggregate.cs#L20

Add curly braces around the nested statement(s) in this 'if' block.
return;

MerchantDomainEvents.MerchantDepositListCreatedEvent merchantDepositListCreatedEvent =
new MerchantDomainEvents.MerchantDepositListCreatedEvent(aggregate.AggregateId, merchantAggregate.EstateId, dateCreated);

aggregate.ApplyAndAppend(merchantDepositListCreatedEvent);
}

public static List<TransactionProcessor.Models.Merchant.Deposit> GetDeposits(this MerchantDepositListAggregate aggregate){
List<TransactionProcessor.Models.Merchant.Deposit> deposits = new ();
if (aggregate.Deposits.Any()){
aggregate.Deposits.ForEach(d => deposits.Add(new TransactionProcessor.Models.Merchant.Deposit {
Amount = d.Amount,
DepositDateTime = d.DepositDateTime,
DepositId = d.DepositId,
Reference = d.Reference,
Source = d.Source
}));
}

return deposits;
}

public static List<Withdrawal> GetWithdrawals(this MerchantDepositListAggregate aggregate){
List<Withdrawal> withdrawals = new List<Withdrawal>();
if (aggregate.Withdrawals.Any()){
aggregate.Withdrawals.ForEach(d => withdrawals.Add(new Withdrawal{
WithdrawalDateTime = d.WithdrawalDateTime,
WithdrawalId = d.WithdrawalId,
Amount = d.Amount
}));
}

return withdrawals;
}

public static void MakeDeposit(this MerchantDepositListAggregate aggregate,
MerchantDepositSource source,
String reference,
DateTime depositDateTime,
PositiveMoney amount){
String depositData = $"{depositDateTime.ToString("yyyyMMdd hh:mm:ss.fff")}-{reference}-{amount:N2}-{source}";
Guid depositId = aggregate.GenerateGuidFromString(depositData);

aggregate.EnsureMerchantDepositListHasBeenCreated();
aggregate.EnsureNotDuplicateDeposit(depositId);
// TODO: Change amount to a value object (PositiveAmount VO)
aggregate.EnsureDepositSourceHasBeenSet(source);

if (source == MerchantDepositSource.Manual){
MerchantDomainEvents.ManualDepositMadeEvent manualDepositMadeEvent =
new MerchantDomainEvents.ManualDepositMadeEvent(aggregate.AggregateId, aggregate.EstateId, depositId, reference, depositDateTime, amount.Value);
aggregate.ApplyAndAppend(manualDepositMadeEvent);
}
else if (source == MerchantDepositSource.Automatic){
MerchantDomainEvents.AutomaticDepositMadeEvent automaticDepositMadeEvent =
new MerchantDomainEvents.AutomaticDepositMadeEvent(aggregate.AggregateId, aggregate.EstateId, depositId, reference, depositDateTime, amount.Value);
aggregate.ApplyAndAppend(automaticDepositMadeEvent);
}
}

public static void MakeWithdrawal(this MerchantDepositListAggregate aggregate,
DateTime withdrawalDateTime,
PositiveMoney amount){
String depositData = $"{withdrawalDateTime.ToString("yyyyMMdd hh:mm:ss.fff")}-{amount:N2}";
Guid withdrawalId = aggregate.GenerateGuidFromString(depositData);

aggregate.EnsureMerchantDepositListHasBeenCreated();
aggregate.EnsureNotDuplicateWithdrawal(withdrawalId);

MerchantDomainEvents.WithdrawalMadeEvent withdrawalMadeEvent = new(aggregate.AggregateId, aggregate.EstateId, withdrawalId, withdrawalDateTime, amount.Value);
aggregate.ApplyAndAppend(withdrawalMadeEvent);
}

public static void PlayEvent(this MerchantDepositListAggregate aggregate, MerchantDomainEvents.MerchantDepositListCreatedEvent merchantDepositListCreatedEvent){
aggregate.IsCreated = true;
aggregate.EstateId = merchantDepositListCreatedEvent.EstateId;
aggregate.DateCreated = merchantDepositListCreatedEvent.DateCreated;
aggregate.AggregateId = merchantDepositListCreatedEvent.AggregateId;
}

public static void PlayEvent(this MerchantDepositListAggregate aggregate, MerchantDomainEvents.ManualDepositMadeEvent manualDepositMadeEvent){
Models.Deposit deposit = new(manualDepositMadeEvent.DepositId,
MerchantDepositSource.Manual,
manualDepositMadeEvent.Reference,
manualDepositMadeEvent.DepositDateTime,
manualDepositMadeEvent.Amount);
aggregate.Deposits.Add(deposit);
}

public static void PlayEvent(this MerchantDepositListAggregate aggregate, MerchantDomainEvents.AutomaticDepositMadeEvent automaticDepositMadeEvent){
Models.Deposit deposit = new(automaticDepositMadeEvent.DepositId,
MerchantDepositSource.Automatic,
automaticDepositMadeEvent.Reference,
automaticDepositMadeEvent.DepositDateTime,
automaticDepositMadeEvent.Amount);
aggregate.Deposits.Add(deposit);
}

public static void PlayEvent(this MerchantDepositListAggregate aggregate, MerchantDomainEvents.WithdrawalMadeEvent withdrawalMadeEvent){
Models.Withdrawal withdrawal = new(withdrawalMadeEvent.WithdrawalId, withdrawalMadeEvent.WithdrawalDateTime, withdrawalMadeEvent.Amount);
aggregate.Withdrawals.Add(withdrawal);
}

private static void EnsureDepositSourceHasBeenSet(this MerchantDepositListAggregate aggregate, MerchantDepositSource merchantDepositSource){
if (merchantDepositSource == MerchantDepositSource.NotSet){
throw new InvalidOperationException("Merchant deposit source must be set");
}
}

private static void EnsureMerchantDepositListHasBeenCreated(this MerchantDepositListAggregate aggregate){
if (aggregate.IsCreated == false){
throw new InvalidOperationException("Merchant Deposit List has not been created");
}
}

private static void EnsureMerchantHasBeenCreated(this MerchantDepositListAggregate aggregate, MerchantAggregate merchantAggregate){
if (merchantAggregate.IsCreated == false){
throw new InvalidOperationException("Merchant has not been created");
}
}

private static void EnsureNotDuplicateDeposit(this MerchantDepositListAggregate aggregate, Guid depositId){
if (aggregate.Deposits.Any(d => d.DepositId == depositId)){
throw new InvalidOperationException($"Deposit Id [{depositId}] already made for merchant [{aggregate.AggregateId}]");
}
}

private static void EnsureNotDuplicateWithdrawal(this MerchantDepositListAggregate aggregate, Guid withdrawalId){
if (aggregate.Withdrawals.Any(d => d.WithdrawalId == withdrawalId)){
throw new InvalidOperationException($"Withdrawal Id [{withdrawalId}] already made for merchant [{aggregate.AggregateId}]");
}
}

private static Guid GenerateGuidFromString(this MerchantDepositListAggregate aggregate, String input){
using(SHA256 sha256Hash = SHA256.Create()){
//Generate hash from the key
Byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(input));

Byte[] j = bytes.Skip(Math.Max(0, bytes.Count() - 16)).ToArray(); //Take last 16

//Create our Guid.
return new Guid(j);
}
}

#endregion
}

public record MerchantDepositListAggregate : Aggregate{
#region Fields

internal readonly List<Models.Deposit> Deposits;

internal readonly List<Models.Withdrawal> Withdrawals;

#endregion

#region Constructors

[ExcludeFromCodeCoverage]
public MerchantDepositListAggregate(){
// Nothing here
this.Deposits = new List<Models.Deposit>();
this.Withdrawals = new List<Models.Withdrawal>();
}

private MerchantDepositListAggregate(Guid aggregateId){
Guard.ThrowIfInvalidGuid(aggregateId, "Aggregate Id cannot be an Empty Guid");

this.AggregateId = aggregateId;
this.Deposits = new List<Models.Deposit>();
this.Withdrawals = new List<Models.Withdrawal>();
}

#endregion

#region Properties

public DateTime DateCreated{ get; internal set; }

public Guid EstateId{ get; internal set; }

public Boolean IsCreated{ get; internal set; }

#endregion

#region Methods

public static MerchantDepositListAggregate Create(Guid aggregateId){
return new MerchantDepositListAggregate(aggregateId);
}

public override void PlayEvent(IDomainEvent domainEvent) => MerchantDepositListAggregateExtensions.PlayEvent(this, (dynamic)domainEvent);

[ExcludeFromCodeCoverage]
protected override Object GetMetadata(){
return new{
this.EstateId,
MerchantId = this.AggregateId
};
}

#endregion
}
}
5 changes: 4 additions & 1 deletion TransactionProcessor.Aggregates/Models/Contract.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace TransactionProcessor.Aggregates.Models;
using System.Diagnostics.CodeAnalysis;

namespace TransactionProcessor.Aggregates.Models;

[ExcludeFromCodeCoverage]
internal record Contract
{
public Contract(Guid ContractId, Boolean IsDeleted = false)
Expand Down
12 changes: 12 additions & 0 deletions TransactionProcessor.Aggregates/Models/Deposit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using TransactionProcessor.Models.Merchant;

namespace TransactionProcessor.Aggregates.Models
{
[ExcludeFromCodeCoverage]
internal record Deposit(Guid DepositId,
MerchantDepositSource Source,
String Reference,
DateTime DepositDateTime,
Decimal Amount);
}
5 changes: 4 additions & 1 deletion TransactionProcessor.Aggregates/Models/Device.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
namespace TransactionProcessor.Aggregates.Models;
using System.Diagnostics.CodeAnalysis;

namespace TransactionProcessor.Aggregates.Models;

[ExcludeFromCodeCoverage]
internal record Device(Guid DeviceId, String DeviceIdentifier, Boolean IsEnabled = true);
5 changes: 4 additions & 1 deletion TransactionProcessor.Aggregates/Models/Operator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace TransactionProcessor.Aggregates.Models
using System.Diagnostics.CodeAnalysis;

namespace TransactionProcessor.Aggregates.Models
{
[ExcludeFromCodeCoverage]
internal record Operator(Guid OperatorId, String Name, String MerchantNumber, String TerminalNumber, Boolean IsDeleted = false);
}
5 changes: 4 additions & 1 deletion TransactionProcessor.Aggregates/Models/SecurityUser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace TransactionProcessor.Aggregates.Models
using System.Diagnostics.CodeAnalysis;

namespace TransactionProcessor.Aggregates.Models
{
[ExcludeFromCodeCoverage]
internal record SecurityUser(Guid SecurityUserId, String EmailAddress);
}
9 changes: 9 additions & 0 deletions TransactionProcessor.Aggregates/Models/Withdrawal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Diagnostics.CodeAnalysis;

namespace TransactionProcessor.Aggregates.Models
{
[ExcludeFromCodeCoverage]
internal record Withdrawal(Guid WithdrawalId,
DateTime WithdrawalDateTime,
Decimal Amount);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using SimpleResults;
using TransactionProcessor.Aggregates;
using TransactionProcessor.BusinessLogic.EventHandling;
using TransactionProcessor.BusinessLogic.Events;
using TransactionProcessor.BusinessLogic.Requests;
using TransactionProcessor.DomainEvents;
using TransactionProcessor.Repository;
Expand Down Expand Up @@ -38,43 +39,43 @@
this.Mediator.Object);
}

//[Fact]
//public async Task MerchantDomainEventHandler_Handle_CallbackReceivedEnrichedEvent_Deposit_EventIsHandled()
//{
// this.EstateManagementRepository.Setup(e => e.GetMerchantFromReference(It.IsAny<Guid>(), It.IsAny<String>(), It.IsAny<CancellationToken>()))
// .ReturnsAsync(Result.Success(TestData.MerchantModelWithAddressesContactsDevicesAndOperatorsAndContracts()));
// this.Mediator.Setup(m => m.Send(It.IsAny<MerchantCommands.MakeMerchantDepositCommand>(), It.IsAny<CancellationToken>())).ReturnsAsync(Result.Success);
// CallbackReceivedEnrichedEvent domainEvent = TestData.CallbackReceivedEnrichedEventDeposit;
[Fact]
public async Task MerchantDomainEventHandler_Handle_CallbackReceivedEnrichedEvent_Deposit_EventIsHandled()
{
this.EstateManagementRepository.Setup(e => e.GetMerchantFromReference(It.IsAny<Guid>(), It.IsAny<String>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(TestData.MerchantModelWithAddressesContactsDevicesAndOperatorsAndContracts()));
this.Mediator.Setup(m => m.Send(It.IsAny<MerchantCommands.MakeMerchantDepositCommand>(), It.IsAny<CancellationToken>())).ReturnsAsync(Result.Success);
CallbackReceivedEnrichedEvent domainEvent = TestData.DomainEvents.CallbackReceivedEnrichedEventDeposit;

// var result = await this.DomainEventHandler.Handle(domainEvent, CancellationToken.None);
// result.IsSuccess.ShouldBeTrue();
var result = await this.DomainEventHandler.Handle(domainEvent, CancellationToken.None);
result.IsSuccess.ShouldBeTrue();

//}
}

//[Fact]
//public async Task MerchantDomainEventHandler_Handle_CallbackReceivedEnrichedEvent_OtherType_EventIsHandled()
//{
// this.EstateManagementRepository.Setup(e => e.GetMerchantFromReference(It.IsAny<Guid>(), It.IsAny<String>(), It.IsAny<CancellationToken>()))
// .ReturnsAsync(TestData.MerchantModelWithAddressesContactsDevicesAndOperatorsAndContracts());
[Fact]
public async Task MerchantDomainEventHandler_Handle_CallbackReceivedEnrichedEvent_OtherType_EventIsHandled()
{
//this.EstateManagementRepository.Setup(e => e.GetMerchantFromReference(It.IsAny<Guid>(), It.IsAny<String>(), It.IsAny<CancellationToken>()))
// .ReturnsAsync(TestData.MerchantModelWithAddressesContactsDevicesAndOperatorsAndContracts());

Check warning on line 59 in TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantDomainEventHandlerTests.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantDomainEventHandlerTests.cs#L59

Remove this commented out code.

// CallbackReceivedEnrichedEvent domainEvent = TestData.CallbackReceivedEnrichedEventOtherType;
CallbackReceivedEnrichedEvent domainEvent = TestData.DomainEvents.CallbackReceivedEnrichedEventOtherType;

// var result = await this.DomainEventHandler.Handle(domainEvent, CancellationToken.None);
// result.IsSuccess.ShouldBeTrue();
//}
var result = await this.DomainEventHandler.Handle(domainEvent, CancellationToken.None);
result.IsSuccess.ShouldBeTrue();
}

//[Fact]
//public async Task MerchantDomainEventHandler_Handle_CallbackReceivedEnrichedEvent_Deposit_GetMerchantFailed_ResultIsFailure()
//{
// this.EstateManagementRepository.Setup(e => e.GetMerchantFromReference(It.IsAny<Guid>(), It.IsAny<String>(), It.IsAny<CancellationToken>()))
// .ReturnsAsync(Result.Failure());
[Fact]
public async Task MerchantDomainEventHandler_Handle_CallbackReceivedEnrichedEvent_Deposit_GetMerchantFailed_ResultIsFailure()
{
this.EstateManagementRepository.Setup(e => e.GetMerchantFromReference(It.IsAny<Guid>(), It.IsAny<String>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Failure());

// CallbackReceivedEnrichedEvent domainEvent = TestData.CallbackReceivedEnrichedEventDeposit;
CallbackReceivedEnrichedEvent domainEvent = TestData.DomainEvents.CallbackReceivedEnrichedEventDeposit;

// var result = await this.DomainEventHandler.Handle(domainEvent, CancellationToken.None);
// result.IsFailed.ShouldBeTrue();
var result = await this.DomainEventHandler.Handle(domainEvent, CancellationToken.None);
result.IsFailed.ShouldBeTrue();

//}
}

#region Methods

Expand Down
Loading
Loading