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
40 changes: 39 additions & 1 deletion CallbackHander.Testing/TestData.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using CallbackHandlers.Models;
using System;
using System.Collections.Generic;
using Xunit;

namespace CallbackHander.Testing;

using CallbackHandler.BusinessLogic.Requests;
using CallbackHandler.CallbackMessageAggregate;
using SecurityService.DataTransferObjects.Responses;

public class TestData
{
Expand All @@ -22,7 +24,27 @@ public class TestData
public static String Reference = "640E863C23E244BDB9717C92733FFD4C-9D20A3961CF645EDAA7BDD436318BA29";
public static Guid EstateReference = Guid.Parse("640E863C-23E2-44BD-B971-7C92733FFD4C");
public static Guid MerchantReference = Guid.Parse("9D20A396-1CF6-45ED-AA7B-DD436318BA29");
public static TokenResponse TokenResponse()
{
return SecurityService.DataTransferObjects.Responses.TokenResponse.Create("AccessToken", string.Empty, 100);
}

public static IReadOnlyDictionary<String, String> DefaultAppSettings =>
new Dictionary<String, String>
{
["AppSettings:ClientId"] = "clientId",
["AppSettings:ClientSecret"] = "clientSecret",
["AppSettings:UseConnectionStringConfig"] = "false",
["EventStoreSettings:ConnectionString"] = "esdb://127.0.0.1:2113",
["SecurityConfiguration:Authority"] = "https://127.0.0.1",
["AppSettings:EstateManagementApi"] = "http://127.0.0.1",
["AppSettings:SecurityService"] = "http://127.0.0.1",
["AppSettings:ContractProductFeeCacheExpiryInHours"] = "",
["AppSettings:ContractProductFeeCacheEnabled"] = "",
["ConnectionStrings:HealthCheck"] = "HealthCheck",
["ConnectionStrings:EstateReportingReadModel"] = "",
["ConnectionStrings:TransactionProcessorReadModel"] = ""
};
public static CallbackCommands.RecordCallbackCommand RecordCallbackCommand =>
new (TestData.CallbackId,
TestData.CallbackMessage,
Expand All @@ -47,6 +69,22 @@ public class TestData
TestData.TypeString,
"reference");

public static CallbackCommands.RecordCallbackCommand RecordCallbackCommandInvalidEstateIdInReference =>
new(TestData.CallbackId,
TestData.CallbackMessage,
TestData.Destinations,
(MessageFormat)TestData.MessageFormat,
TestData.TypeString,
"reference-71AA10137C9341C793EDDDE89C549455");

public static CallbackCommands.RecordCallbackCommand RecordCallbackCommandInvalidMerchantIdInReference =>
new(TestData.CallbackId,
TestData.CallbackMessage,
TestData.Destinations,
(MessageFormat)TestData.MessageFormat,
TestData.TypeString,
"71AA10137C9341C793EDDDE89C549455-reference");

public static CallbackQueries.GetCallbackQuery GetCallbackQuery =>
new CallbackQueries.GetCallbackQuery(TestData.CallbackId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace CallbackHandler.BusinessLogic.Tests.Mediator;
using BusinessLogic.Services;
using Lamar;
using Microsoft.Extensions.DependencyInjection;
using Shared.General;

public class MediatorTests
{
Expand All @@ -38,7 +39,10 @@ public async Task Mediator_Send_RequestHandled()

ServiceRegistry services = new();
Startup s = new(hostingEnvironment.Object);
Startup.Configuration = this.SetupMemoryConfiguration();
IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build();
//ConfigurationReader.Initialise(configurationRoot);

Startup.Configuration = configurationRoot;

this.AddTestRegistrations(services, hostingEnvironment.Object);
s.ConfigureContainer(services);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,56 @@
using SecurityService.Client;
using SecurityService.DataTransferObjects.Responses;
using Shared.General;
using SimpleResults;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SimpleResults;
using Shared.Logger;
using TransactionProcessor.Client;

namespace CallbackHandler.BusinessLogic.Tests.Services;

using System.Threading;
using BusinessLogic.Services;
using CallbackHander.Testing;
using CallbackHandlers.Models;
using CallbackMessageAggregate;
using Microsoft.Extensions.Configuration;
using Moq;
using Shared.DomainDrivenDesign.EventSourcing;
using Shared.EventStore.Aggregate;
using Shouldly;
using System.Threading;
using Xunit;

public class CallbackDomainServiceTests
{
private readonly ICallbackDomainService DomainService;

private readonly Mock<IAggregateRepository<CallbackMessageAggregate, DomainEvent>> AggregateRepository;
private readonly Mock<ISecurityServiceClient> SecurityServiceClient;
private readonly Mock<ITransactionProcessorClient> TransactionProcessorClient;
public CallbackDomainServiceTests() {
this.AggregateRepository = new Mock<IAggregateRepository<CallbackMessageAggregate, DomainEvent>>();
this.DomainService = new CallbackDomainService(this.AggregateRepository.Object);
this.SecurityServiceClient = new Mock<ISecurityServiceClient>();
this.TransactionProcessorClient = new Mock<ITransactionProcessorClient>();
this.DomainService = new CallbackDomainService(this.AggregateRepository.Object, this.SecurityServiceClient.Object,
this.TransactionProcessorClient.Object);
this.AggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(TestData.EmptyCallbackMessageAggregate());
this.AggregateRepository.Setup(a => a.SaveChanges(It.IsAny<CallbackMessageAggregate>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success);
this.SecurityServiceClient.Setup(s => s.GetToken(It.IsAny<String>(), It.IsAny<String>(), It.IsAny<CancellationToken>())).ReturnsAsync(Result.Success(TestData.TokenResponse()));
this.TransactionProcessorClient.Setup(t => t.GetMerchant(It.IsAny<String>(), It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())).ReturnsAsync(Result.Success());

IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(TestData.DefaultAppSettings).Build();
ConfigurationReader.Initialise(configurationRoot);

Logger.Initialise(NullLogger.Instance);
}


[Fact]
public async Task CallbackDomainService_RecordCallback_CallbackRecorded() {
Result result = await this.DomainService.RecordCallback(TestData.RecordCallbackCommand, CancellationToken.None);
Expand All @@ -49,4 +68,45 @@ public async Task CallbackDomainService_RecordCallback_InvalidReference_ResultFa
Result result = await this.DomainService.RecordCallback(TestData.RecordCallbackCommandInvalidReference, CancellationToken.None);
result.IsFailed.ShouldBeTrue();
}

[Fact]
public async Task CallbackDomainService_RecordCallback_EstateIdNotValidGuid_ResultFailed()
{
Result result = await this.DomainService.RecordCallback(TestData.RecordCallbackCommandInvalidEstateIdInReference, CancellationToken.None);
result.IsFailed.ShouldBeTrue();
}

[Fact]
public async Task CallbackDomainService_RecordCallback_MerchantIdNotValidGuid_ResultFailed()
{
Result result = await this.DomainService.RecordCallback(TestData.RecordCallbackCommandInvalidMerchantIdInReference, CancellationToken.None);
result.IsFailed.ShouldBeTrue();
}

[Fact]
public async Task CallbackDomainService_RecordCallback_GetTokenFailed_ResultFailed()
{
this.SecurityServiceClient.Setup(s => s.GetToken(It.IsAny<String>(), It.IsAny<String>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Failure());
Result result = await this.DomainService.RecordCallback(TestData.RecordCallbackCommand, CancellationToken.None);
result.IsFailed.ShouldBeTrue();
}

[Fact]
public async Task CallbackDomainService_RecordCallback_GetMerchantFailed_ResultFailed()
{
this.TransactionProcessorClient.Setup(t => t.GetMerchant(It.IsAny<String>(), It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())).ReturnsAsync(Result.Failure());

Result result = await this.DomainService.RecordCallback(TestData.RecordCallbackCommand, CancellationToken.None);
result.IsFailed.ShouldBeTrue();
}

[Fact]
public async Task CallbackDomainService_RecordCallback_SaveFailed_ResultFailed()
{
this.AggregateRepository.Setup(a => a.SaveChanges(It.IsAny<CallbackMessageAggregate>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Failure);
Result result = await this.DomainService.RecordCallback(TestData.RecordCallbackCommand, CancellationToken.None);
result.IsFailed.ShouldBeTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="14.0.0" />
<PackageReference Include="SecurityService.Client" Version="2025.12.2" />
<PackageReference Include="Shared" Version="2025.12.1" />
<PackageReference Include="Shared.DomainDrivenDesign" Version="2025.12.1" />
<PackageReference Include="Shared.EventStore" Version="2025.12.1" />
<PackageReference Include="TransactionProcessor.Client" Version="2025.12.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CallbackHandler.CallbackMessageAggregate\CallbackHandler.CallbackMessageAggregate.csproj" />
Expand Down
72 changes: 67 additions & 5 deletions CallbackHandler.BusinessLogic/Services/CallbackDomainService.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
using Shared.EventStore.Helpers;
using SecurityService.Client;
using Shared.EventStore.Helpers;
using Shared.Logger;
using SimpleResults;
using TransactionProcessor.Client;
using TransactionProcessor.DataTransferObjects.Responses.Merchant;

namespace CallbackHandler.BusinessLogic.Services;

using Requests;
using CallbackMessageAggregate;
using Requests;
using SecurityService.DataTransferObjects.Responses;
using Shared.DomainDrivenDesign.EventSourcing;
using Shared.EventStore.Aggregate;
using Shared.General;
using Shared.Results;
using System;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -20,9 +27,15 @@ Task<Result> RecordCallback(CallbackCommands.RecordCallbackCommand command,
public class CallbackDomainService : ICallbackDomainService
{
private readonly IAggregateRepository<CallbackMessageAggregate, DomainEvent> AggregateRepository;
private readonly ISecurityServiceClient SecurityServiceClient;
private readonly ITransactionProcessorClient TransactionProcessorClient;

public CallbackDomainService(IAggregateRepository<CallbackMessageAggregate, DomainEvent> aggregateRepository) {
public CallbackDomainService(IAggregateRepository<CallbackMessageAggregate, DomainEvent> aggregateRepository,
ISecurityServiceClient securityServiceClient,
ITransactionProcessorClient transactionProcessorClient) {
this.AggregateRepository = aggregateRepository;
this.SecurityServiceClient = securityServiceClient;
this.TransactionProcessorClient = transactionProcessorClient;
}

public async Task<Result> RecordCallback(CallbackCommands.RecordCallbackCommand command,
Expand All @@ -43,8 +56,21 @@ public async Task<Result> RecordCallback(CallbackCommands.RecordCallbackCommand
String estateReference = referenceData[0];
String merchantReference = referenceData[1];

// TODO: Validate the reference data

// Validate the reference data
// Validate the estate and merchant references are valid GUIDs
if (!Guid.TryParse(estateReference, out Guid estateId) || !Guid.TryParse(merchantReference, out Guid merchantId)) {
return Result.Invalid("Estate or Merchant reference is not a valid GUID.");
}

Result<TokenResponse> getTokenResult = await Helpers.GetToken(this.TokenResponse, this.SecurityServiceClient, cancellationToken);
if (getTokenResult.IsFailed)
return ResultHelpers.CreateFailure(getTokenResult);
this.TokenResponse = getTokenResult.Data;

Result<MerchantResponse> result = await this.TransactionProcessorClient.GetMerchant(this.TokenResponse.AccessToken, estateId, merchantId, cancellationToken);
if (result.IsFailed)
return ResultHelpers.CreateFailure(result);

Result<CallbackMessageAggregate> getResult = await this.AggregateRepository.GetLatestVersion(command.CallbackId, cancellationToken);
Result<CallbackMessageAggregate> callbackMessageAggregateResult =
DomainServiceHelper.HandleGetAggregateResult(getResult, command.CallbackId, false);
Expand All @@ -56,4 +82,40 @@ public async Task<Result> RecordCallback(CallbackCommands.RecordCallbackCommand
return stateResult;
return await this.AggregateRepository.SaveChanges(aggregate, cancellationToken);
}

private TokenResponse TokenResponse;
}

public static class Helpers {
public static async Task<Result<TokenResponse>> GetToken(TokenResponse currentToken, ISecurityServiceClient securityServiceClient, CancellationToken cancellationToken)
{
// Get a token to talk to the estate service
String clientId = ConfigurationReader.GetValue("AppSettings", "ClientId");
String clientSecret = ConfigurationReader.GetValue("AppSettings", "ClientSecret");
Logger.LogDebug($"Client Id is {clientId}");
Logger.LogDebug($"Client Secret is {clientSecret}");

if (currentToken == null)
{
Result<TokenResponse> tokenResult = await securityServiceClient.GetToken(clientId, clientSecret, cancellationToken);
if (tokenResult.IsFailed)
return ResultHelpers.CreateFailure(tokenResult);
TokenResponse token = tokenResult.Data;
Logger.LogDebug($"Token is {token.AccessToken}");
return Result.Success(token);
}

if (currentToken.Expires.UtcDateTime.Subtract(DateTime.UtcNow) < TimeSpan.FromMinutes(2))
{
Logger.LogDebug($"Token is about to expire at {currentToken.Expires.DateTime:O}");
Result<TokenResponse> tokenResult = await securityServiceClient.GetToken(clientId, clientSecret, cancellationToken);
if (tokenResult.IsFailed)
return ResultHelpers.CreateFailure(tokenResult);
TokenResponse token = tokenResult.Data;
Logger.LogDebug($"Token is {token.AccessToken}");
return Result.Success(token);
}

return Result.Success(currentToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="EventStoreProjections" Version="2023.12.3" />
<PackageReference Include="Reqnroll.Tools.MsBuild.Generation" Version="3.2.1" />
<PackageReference Include="Reqnroll" Version="3.2.1" />
<PackageReference Include="Reqnroll.NUnit" Version="3.2.1" />
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="NUnit3TestAdapter" Version="5.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="SecurityService.Client" Version="2025.12.2" />
<PackageReference Include="SecurityService.IntegrationTesting.Helpers" Version="2025.12.2" />
<PackageReference Include="Shared" Version="2025.12.1" />
<PackageReference Include="Shared.IntegrationTesting" Version="2025.12.1" />
<PackageReference Include="TransactionProcessor.Client" Version="2025.12.1" />
<PackageReference Include="TransactionProcessor.IntegrationTesting.Helpers" Version="2025.12.1" />
</ItemGroup>

<ItemGroup>
Expand All @@ -27,4 +32,31 @@
<ProjectReference Include="..\CallbackHandler.DataTransferObjects\CallbackHandler.DataTransferObjects.csproj" />
</ItemGroup>

<ItemGroup>
<Content Update="C:\Users\stuar\.nuget\packages\eventstoreprojections\2023.12.3\contentFiles\any\net6.0\projections\continuous\CallbackHandlerEnricher.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="C:\Users\stuar\.nuget\packages\eventstoreprojections\2023.12.3\contentFiles\any\net6.0\projections\continuous\EstateAggregator.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="C:\Users\stuar\.nuget\packages\eventstoreprojections\2023.12.3\contentFiles\any\net6.0\projections\continuous\EstateManagementSubscriptionStreamBuilder.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="C:\Users\stuar\.nuget\packages\eventstoreprojections\2023.12.3\contentFiles\any\net6.0\projections\continuous\FileProcessorSubscriptionStreamBuilder.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="C:\Users\stuar\.nuget\packages\eventstoreprojections\2023.12.3\contentFiles\any\net6.0\projections\continuous\MerchantAggregator.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="C:\Users\stuar\.nuget\packages\eventstoreprojections\2023.12.3\contentFiles\any\net6.0\projections\continuous\MerchantBalanceAggregator.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="C:\Users\stuar\.nuget\packages\eventstoreprojections\2023.12.3\contentFiles\any\net6.0\projections\continuous\MerchantBalanceProjection.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="C:\Users\stuar\.nuget\packages\eventstoreprojections\2023.12.3\contentFiles\any\net6.0\projections\continuous\TransactionProcessorSubscriptionStreamBuilder.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>

</Project>
Loading
Loading