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
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,45 @@ public async Task TransactionDomainService_ProcessSaleTransaction_InvalidEstate_
this.ValidateResponse(response, TransactionResponseCode.InvalidEstateId);
}

[Fact]
public async Task TransactionDomainService_ProcessSaleTransaction_NotEnoughCredit_TransactionIsProcessed()
{
IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build();
ConfigurationReader.Initialise(configurationRoot);

Logger.Initialise(NullLogger.Instance);

Mock<ITransactionAggregateManager> transactionAggregateManager = new Mock<ITransactionAggregateManager>();
Mock<IEstateClient> estateClient = new Mock<IEstateClient>();
Mock<ISecurityServiceClient> securityServiceClient = new Mock<ISecurityServiceClient>();
Mock<IOperatorProxy> operatorProxy = new Mock<IOperatorProxy>();
Func<String, IOperatorProxy> operatorProxyResolver = (operatorName) => { return operatorProxy.Object; };

securityServiceClient.Setup(s => s.GetToken(It.IsAny<String>(), It.IsAny<String>(), It.IsAny<CancellationToken>())).ReturnsAsync(TestData.TokenResponse);
estateClient.Setup(e => e.GetEstate(It.IsAny<String>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())).ReturnsAsync(TestData.GetEstateResponseWithOperator1);
estateClient.Setup(e => e.GetMerchant(It.IsAny<String>(), It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.GetMerchantResponseWithZeroAvailableBalance);
transactionAggregateManager.Setup(t => t.GetAggregate(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.GetLocallyDeclinedTransactionAggregate(TransactionResponseCode.MerchantDoesNotHaveEnoughCredit));

TransactionDomainService transactionDomainService =
new TransactionDomainService(transactionAggregateManager.Object, estateClient.Object, securityServiceClient.Object,
operatorProxyResolver);

ProcessSaleTransactionResponse response = await transactionDomainService.ProcessSaleTransaction(TestData.TransactionId,
TestData.EstateId,
TestData.MerchantId,
TestData.TransactionDateTime,
TestData.TransactionNumber,
TestData.DeviceIdentifier,
TestData.OperatorIdentifier1,
TestData.CustomerEmailAddress,
TestData.AdditionalTransactionMetaData,
CancellationToken.None);

this.ValidateResponse(response, TransactionResponseCode.MerchantDoesNotHaveEnoughCredit);
}

[Fact]
public async Task TransactionDomainService_ProcessSaleTransaction_InvalidMerchant_TransactionIsProcessed()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Shouldly" Version="3.0.2" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" Version="2.8.0">
<PackageReference Include="coverlet.msbuild" Version="2.9.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ public async Task<ProcessSaleTransactionResponse> ProcessSaleTransaction(Guid tr
// Extract the transaction amount from the metadata
Decimal transactionAmount = this.ExtractFieldFromMetadata<Decimal>("Amount", additionalTransactionMetadata);

(String responseMessage, TransactionResponseCode responseCode) validationResult =
await this.ValidateSaleTransaction(estateId, merchantId, deviceIdentifier, operatorIdentifier, transactionAmount, cancellationToken);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


await this.TransactionAggregateManager.StartTransaction(transactionId,
transactionDateTime,
transactionNumber,
Expand All @@ -176,10 +179,7 @@ await this.TransactionAggregateManager.StartTransaction(transactionId,
deviceIdentifier,
transactionAmount,
cancellationToken);

(String responseMessage, TransactionResponseCode responseCode) validationResult =
await this.ValidateSaleTransaction(estateId, merchantId, deviceIdentifier, operatorIdentifier, cancellationToken);


if (validationResult.responseCode == TransactionResponseCode.Success)
{
// Record any additional request metadata
Expand Down Expand Up @@ -420,10 +420,10 @@ private async Task<TokenResponse> GetToken(CancellationToken cancellationToken)
/// <param name="merchantId">The merchant identifier.</param>
/// <param name="deviceIdentifier">The device identifier.</param>
/// <param name="operatorIdentifier">The operator identifier.</param>
/// <param name="transactionAmount">The transaction amount.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
/// <exception cref="TransactionValidationException">
/// Merchant {merchant.MerchantName} has no valid Devices for this transaction.
/// <exception cref="TransactionValidationException">Merchant {merchant.MerchantName} has no valid Devices for this transaction.
/// or
/// Device Identifier {deviceIdentifier} not valid for Merchant {merchant.MerchantName}
/// or
Expand All @@ -433,12 +433,12 @@ private async Task<TokenResponse> GetToken(CancellationToken cancellationToken)
/// or
/// Merchant {merchant.MerchantName} has no operators defined
/// or
/// Operator {operatorIdentifier} not configured for Merchant [{merchant.MerchantName}]
/// </exception>
/// Operator {operatorIdentifier} not configured for Merchant [{merchant.MerchantName}]</exception>
private async Task<(String responseMessage, TransactionResponseCode responseCode)> ValidateSaleTransaction(Guid estateId,
Guid merchantId,
String deviceIdentifier,
String operatorIdentifier,
Decimal transactionAmount,
CancellationToken cancellationToken)
{
try
Expand Down Expand Up @@ -496,6 +496,13 @@ private async Task<TokenResponse> GetToken(CancellationToken cancellationToken)
}
}

// Check the merchant has enough balance to perform the sale
if (merchant.AvailableBalance < transactionAmount)
{
throw new TransactionValidationException($"Merchant [{merchant.MerchantName}] does not have enough credit available [{merchant.AvailableBalance}] to perform transaction amount [{transactionAmount}]",
TransactionResponseCode.MerchantDoesNotHaveEnoughCredit);
}

// If we get here everything is good
return ("SUCCESS", TransactionResponseCode.Success);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum TransactionResponseCode
NoMerchantOperators = 1006,
OperatorNotValidForMerchant = 1007,
TransactionDeclinedByOperator = 1008,
MerchantDoesNotHaveEnoughCredit = 1009,

// A Catch All generic Error where reason has not been identified
UnknownFailure = 9999
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="EstateManagement.Client" Version="0.0.10" />
<PackageReference Include="SecurityService.Client" Version="0.0.9" />
<PackageReference Include="Shared" Version="0.0.12" />
<PackageReference Include="Shared.DomainDrivenDesign" Version="0.0.12" />
<PackageReference Include="Shared.EventStore" Version="0.0.12" />
<PackageReference Include="MediatR" Version="8.0.0" />
<PackageReference Include="EstateManagement.Client" Version="1.0.0.1" />
<PackageReference Include="SecurityService.Client" Version="1.0.0" />
<PackageReference Include="Shared" Version="0.0.14" />
<PackageReference Include="Shared.DomainDrivenDesign" Version="0.0.14" />
<PackageReference Include="Shared.EventStore" Version="0.0.14" />
<PackageReference Include="MediatR" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ClientProxyBase" Version="0.0.12" />
<PackageReference Include="ClientProxyBase" Version="0.0.14" />
</ItemGroup>

<ItemGroup>
Expand Down
58 changes: 52 additions & 6 deletions TransactionProcessor.IntegrationTests/Common/DockerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -15,6 +17,9 @@
using Ductus.FluentDocker.Services.Extensions;
using EstateManagement.Client;
using EstateReporting.Database;
using EventStore.ClientAPI.Common.Log;
using EventStore.ClientAPI.Projections;
using EventStore.ClientAPI.SystemData;
using global::Shared.Logger;
using Microsoft.Data.SqlClient;
using SecurityService.Client;
Expand Down Expand Up @@ -144,6 +149,45 @@ public static IContainerService SetupTestHostContainer(String containerName, ILo
return builtContainer;
}

private async Task LoadEventStoreProjections()
{
//Start our Continous Projections - we might decide to do this at a different stage, but now lets try here
String projectionsFolder = "../../../projections/continuous";
IPAddress[] ipAddresses = Dns.GetHostAddresses("127.0.0.1");
IPEndPoint endpoint = new IPEndPoint(ipAddresses.First(), this.EventStoreHttpPort);

if (!String.IsNullOrWhiteSpace(projectionsFolder))
{
DirectoryInfo di = new DirectoryInfo(projectionsFolder);

if (di.Exists)
{
FileInfo[] files = di.GetFiles();

// TODO: possibly need to change timeout and logger here
ProjectionsManager projectionManager = new ProjectionsManager(new ConsoleLogger(), endpoint, TimeSpan.FromSeconds(30));

foreach (FileInfo file in files)
{
String projection = File.ReadAllText(file.FullName);
String projectionName = file.Name.Replace(".js", String.Empty);

try
{
Logger.LogInformation($"Creating projection [{projectionName}]");
await projectionManager.CreateContinuousAsync(projectionName, projection, new UserCredentials("admin", "changeit")).ConfigureAwait(false);
}
catch (Exception e)
{
Logger.LogError(new Exception($"Projection [{projectionName}] error", e));
}
}
}
}

Logger.LogInformation("Loaded projections");
}

#region Methods

/// <summary>
Expand Down Expand Up @@ -185,9 +229,9 @@ public override async Task StartContainersForScenarioRun(String scenarioName)
}, traceFolder, null,
this.SecurityServiceContainerName,
this.EventStoreContainerName,
Setup.SqlServerContainerName,
(Setup.SqlServerContainerName,
"sa",
"thisisalongpassword123!",
"thisisalongpassword123!"),
("serviceClient", "Secret1"));

IContainerService securityServiceContainer = DockerHelper.SetupSecurityServiceContainer(this.SecurityServiceContainerName,
Expand Down Expand Up @@ -224,9 +268,9 @@ public override async Task StartContainersForScenarioRun(String scenarioName)
traceFolder,
dockerCredentials,
this.SecurityServiceContainerName,
Setup.SqlServerContainerName,
(Setup.SqlServerContainerName,
"sa",
"thisisalongpassword123!",
"thisisalongpassword123!"),
("serviceClient", "Secret1"),
true);

Expand Down Expand Up @@ -267,6 +311,8 @@ public override async Task StartContainersForScenarioRun(String scenarioName)
this.SecurityServiceClient = new SecurityServiceClient(SecurityServiceBaseAddressResolver, httpClient);
this.TransactionProcessorClient = new TransactionProcessorClient(TransactionProcessorBaseAddressResolver, httpClient);

await this.LoadEventStoreProjections().ConfigureAwait(false);

await PopulateSubscriptionServiceConfiguration().ConfigureAwait(false);

IContainerService subscriptionServiceContainer = DockerHelper.SetupSubscriptionServiceContainer(this.SubscriptionServiceContainerName,
Expand All @@ -280,9 +326,9 @@ public override async Task StartContainersForScenarioRun(String scenarioName)
traceFolder,
dockerCredentials,
this.SecurityServiceContainerName,
Setup.SqlServerContainerName,
(Setup.SqlServerContainerName,
"sa",
"thisisalongpassword123!",
"thisisalongpassword123!"),
this.TestId,
("serviceClient", "Secret1"),
true);
Expand Down
Loading