diff --git a/CallbackHander.Testing/TestData.cs b/CallbackHander.Testing/TestData.cs index 3e670b5..f1b2f8e 100644 --- a/CallbackHander.Testing/TestData.cs +++ b/CallbackHander.Testing/TestData.cs @@ -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 { @@ -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 DefaultAppSettings => + new Dictionary + { + ["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, @@ -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); diff --git a/CallbackHandler.BusinessLogic.Tests/Mediator/MediatorTests.cs b/CallbackHandler.BusinessLogic.Tests/Mediator/MediatorTests.cs index 472b6bc..2041ca2 100644 --- a/CallbackHandler.BusinessLogic.Tests/Mediator/MediatorTests.cs +++ b/CallbackHandler.BusinessLogic.Tests/Mediator/MediatorTests.cs @@ -17,6 +17,7 @@ namespace CallbackHandler.BusinessLogic.Tests.Mediator; using BusinessLogic.Services; using Lamar; using Microsoft.Extensions.DependencyInjection; +using Shared.General; public class MediatorTests { @@ -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); diff --git a/CallbackHandler.BusinessLogic.Tests/Services/CallbackDomainServiceTests.cs b/CallbackHandler.BusinessLogic.Tests/Services/CallbackDomainServiceTests.cs index 82b6052..6ec1c57 100644 --- a/CallbackHandler.BusinessLogic.Tests/Services/CallbackDomainServiceTests.cs +++ b/CallbackHandler.BusinessLogic.Tests/Services/CallbackDomainServiceTests.cs @@ -1,21 +1,27 @@ +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 @@ -23,15 +29,28 @@ public class CallbackDomainServiceTests private readonly ICallbackDomainService DomainService; private readonly Mock> AggregateRepository; + private readonly Mock SecurityServiceClient; + private readonly Mock TransactionProcessorClient; public CallbackDomainServiceTests() { this.AggregateRepository = new Mock>(); - this.DomainService = new CallbackDomainService(this.AggregateRepository.Object); + this.SecurityServiceClient = new Mock(); + this.TransactionProcessorClient = new Mock(); + this.DomainService = new CallbackDomainService(this.AggregateRepository.Object, this.SecurityServiceClient.Object, + this.TransactionProcessorClient.Object); this.AggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())) .ReturnsAsync(TestData.EmptyCallbackMessageAggregate()); this.AggregateRepository.Setup(a => a.SaveChanges(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Success); + this.SecurityServiceClient.Setup(s => s.GetToken(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(TestData.TokenResponse())); + this.TransactionProcessorClient.Setup(t => t.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).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); @@ -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(), It.IsAny(), It.IsAny())) + .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(), It.IsAny(), It.IsAny(), It.IsAny())).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(), It.IsAny())) + .ReturnsAsync(Result.Failure); + Result result = await this.DomainService.RecordCallback(TestData.RecordCallbackCommand, CancellationToken.None); + result.IsFailed.ShouldBeTrue(); + } } diff --git a/CallbackHandler.BusinessLogic/CallbackHandler.BusinessLogic.csproj b/CallbackHandler.BusinessLogic/CallbackHandler.BusinessLogic.csproj index 0e65c63..83f1de6 100644 --- a/CallbackHandler.BusinessLogic/CallbackHandler.BusinessLogic.csproj +++ b/CallbackHandler.BusinessLogic/CallbackHandler.BusinessLogic.csproj @@ -5,9 +5,11 @@ + + diff --git a/CallbackHandler.BusinessLogic/Services/CallbackDomainService.cs b/CallbackHandler.BusinessLogic/Services/CallbackDomainService.cs index c3881d2..e11ed04 100644 --- a/CallbackHandler.BusinessLogic/Services/CallbackDomainService.cs +++ b/CallbackHandler.BusinessLogic/Services/CallbackDomainService.cs @@ -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; @@ -20,9 +27,15 @@ Task RecordCallback(CallbackCommands.RecordCallbackCommand command, public class CallbackDomainService : ICallbackDomainService { private readonly IAggregateRepository AggregateRepository; + private readonly ISecurityServiceClient SecurityServiceClient; + private readonly ITransactionProcessorClient TransactionProcessorClient; - public CallbackDomainService(IAggregateRepository aggregateRepository) { + public CallbackDomainService(IAggregateRepository aggregateRepository, + ISecurityServiceClient securityServiceClient, + ITransactionProcessorClient transactionProcessorClient) { this.AggregateRepository = aggregateRepository; + this.SecurityServiceClient = securityServiceClient; + this.TransactionProcessorClient = transactionProcessorClient; } public async Task RecordCallback(CallbackCommands.RecordCallbackCommand command, @@ -43,8 +56,21 @@ public async Task 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 getTokenResult = await Helpers.GetToken(this.TokenResponse, this.SecurityServiceClient, cancellationToken); + if (getTokenResult.IsFailed) + return ResultHelpers.CreateFailure(getTokenResult); + this.TokenResponse = getTokenResult.Data; + + Result result = await this.TransactionProcessorClient.GetMerchant(this.TokenResponse.AccessToken, estateId, merchantId, cancellationToken); + if (result.IsFailed) + return ResultHelpers.CreateFailure(result); + Result getResult = await this.AggregateRepository.GetLatestVersion(command.CallbackId, cancellationToken); Result callbackMessageAggregateResult = DomainServiceHelper.HandleGetAggregateResult(getResult, command.CallbackId, false); @@ -56,4 +82,40 @@ public async Task RecordCallback(CallbackCommands.RecordCallbackCommand return stateResult; return await this.AggregateRepository.SaveChanges(aggregate, cancellationToken); } + + private TokenResponse TokenResponse; +} + +public static class Helpers { + public static async Task> 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 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 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); + } } \ No newline at end of file diff --git a/CallbackHandler.IntegrationTests/CallbackHandler.IntegrationTests.csproj b/CallbackHandler.IntegrationTests/CallbackHandler.IntegrationTests.csproj index 5af009b..824c269 100644 --- a/CallbackHandler.IntegrationTests/CallbackHandler.IntegrationTests.csproj +++ b/CallbackHandler.IntegrationTests/CallbackHandler.IntegrationTests.csproj @@ -9,14 +9,19 @@ + + + + + @@ -27,4 +32,31 @@ + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + diff --git a/CallbackHandler.IntegrationTests/Common/DockerHelper.cs b/CallbackHandler.IntegrationTests/Common/DockerHelper.cs index a9b024d..c6a3202 100644 --- a/CallbackHandler.IntegrationTests/Common/DockerHelper.cs +++ b/CallbackHandler.IntegrationTests/Common/DockerHelper.cs @@ -1,22 +1,76 @@ +using EventStore.Client; +using Newtonsoft.Json.Bson; +using SecurityService.Client; +using Shared.IntegrationTesting; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; -using Newtonsoft.Json.Bson; +using TransactionProcessor.Client; namespace CallbackHandler.IntegrationTests.Common; public class DockerHelper : global::Shared.IntegrationTesting.TestContainers.DockerHelper { + public ISecurityServiceClient SecurityServiceClient; + public ITransactionProcessorClient TransactionProcessorClient; + public EventStoreProjectionManagementClient ProjectionManagementClient; + public HttpClient TestHostHttpClient; public override async Task CreateSubscriptions() { - // Nothing to do here + List<(String streamName, String groupName, Int32 maxRetries)> subscriptions = new(); + subscriptions.AddRange(TransactionProcessor.IntegrationTesting.Helpers.SubscriptionsHelper.GetSubscriptions()); + foreach ((String streamName, String groupName, Int32 maxRetries) subscription in subscriptions) + { + var x = subscription; + x.maxRetries = 2; + await this.CreatePersistentSubscription(x); + } + } + + protected override List GetRequiredProjections() + { + List requiredProjections = new List(); + + requiredProjections.Add("CallbackHandlerEnricher.js"); + requiredProjections.Add("EstateAggregator.js"); + requiredProjections.Add("MerchantAggregator.js"); + requiredProjections.Add("MerchantBalanceCalculator.js"); + requiredProjections.Add("MerchantBalanceProjection.js"); + + return requiredProjections; } public Int32 GetCallbackHandlerPort() { return this.CallbackHandlerPort; } + + public override async Task StartContainersForScenarioRun(String scenarioName, + DockerServices dockerServices) { + await base.StartContainersForScenarioRun(scenarioName, dockerServices); + + // Setup the base address resolvers + String SecurityServiceBaseAddressResolver(String api) => $"https://127.0.0.1:{this.SecurityServicePort}"; + String TransactionProcessorBaseAddressResolver(String api) => $"http://127.0.0.1:{this.TransactionProcessorPort}"; + + HttpClientHandler clientHandler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (message, + certificate2, + arg3, + arg4) => + { + return true; + } + }; + HttpClient httpClient = new HttpClient(clientHandler); + this.SecurityServiceClient = new SecurityServiceClient(SecurityServiceBaseAddressResolver, httpClient); + this.TransactionProcessorClient = new TransactionProcessorClient(TransactionProcessorBaseAddressResolver, httpClient); + this.ProjectionManagementClient = new EventStoreProjectionManagementClient(ConfigureEventStoreSettings()); + this.TestHostHttpClient = new HttpClient(clientHandler); + this.TestHostHttpClient.BaseAddress = new Uri($"http://127.0.0.1:{this.TestHostServicePort}"); + } } \ No newline at end of file diff --git a/CallbackHandler.IntegrationTests/Common/GenericSteps.cs b/CallbackHandler.IntegrationTests/Common/GenericSteps.cs index 890cfbe..3ad6ba6 100644 --- a/CallbackHandler.IntegrationTests/Common/GenericSteps.cs +++ b/CallbackHandler.IntegrationTests/Common/GenericSteps.cs @@ -29,7 +29,9 @@ public async Task StartSystem() logger.Initialise(LogManager.GetLogger(scenarioName), scenarioName); LogManager.AddHiddenAssembly(typeof(NlogLogger).Assembly); - DockerServices dockerServices = DockerServices.CallbackHandler | DockerServices.EventStore | DockerServices.SqlServer; + DockerServices dockerServices = DockerServices.SecurityService | DockerServices.TransactionProcessor | + DockerServices.CallbackHandler | DockerServices.EventStore | + DockerServices.SqlServer | DockerServices.TestHost; this.TestingContext.DockerHelper = new DockerHelper(); this.TestingContext.DockerHelper.Logger = logger; diff --git a/CallbackHandler.IntegrationTests/Common/TestingContext.cs b/CallbackHandler.IntegrationTests/Common/TestingContext.cs index 05d2a76..a07ac56 100644 --- a/CallbackHandler.IntegrationTests/Common/TestingContext.cs +++ b/CallbackHandler.IntegrationTests/Common/TestingContext.cs @@ -1,10 +1,16 @@ using CallbackHandler.DataTransferObjects; +using Reqnroll; +using SecurityService.DataTransferObjects.Responses; using Shared.Logger; +using Shouldly; +using TransactionProcessor.DataTransferObjects.Responses.Merchant; +using TransactionProcessor.IntegrationTesting.Helpers; namespace CallbackHandler.IntegrationTests.Common; public class TestingContext { + public DockerHelper DockerHelper { get; set; } public NlogLogger Logger { get; set; } @@ -15,5 +21,157 @@ public class TestingContext public TestingContext() { this.SentCallbacks = new Dictionary(); + this.Estates = new List(); + this.Clients = new List(); } + + public String AccessToken { get; set; } + internal readonly List Clients; + internal readonly List Estates; + public void AddClientDetails(String clientId, + String clientSecret, + String grantType) + { + this.Clients.Add(ClientDetails.Create(clientId, clientSecret, grantType)); + } + + public ClientDetails GetClientDetails(String clientId) + { + ClientDetails clientDetails = this.Clients.SingleOrDefault(c => c.ClientId == clientId); + + clientDetails.ShouldNotBeNull(); + + return clientDetails; + } + public List GetAllEstateIds() + { + return this.Estates.Select(e => e.EstateId).ToList(); + } + + public EstateDetails GetEstateDetails(DataTableRow tableRow) + { + String estateName = ReqnrollTableHelper.GetStringRowValue(tableRow, "EstateName"); + EstateDetails estateDetails = null; + + estateDetails = this.Estates.SingleOrDefault(e => e.EstateName == estateName); + + if (estateDetails == null && estateName == "InvalidEstate") + { + estateDetails = EstateDetails.Create(Guid.Parse("79902550-64DF-4491-B0C1-4E78943928A3"), estateName, "estateRef1"); + MerchantResponse merchantResponse = new MerchantResponse + { + MerchantId = Guid.Parse("36AA0109-E2E3-4049-9575-F507A887BB1F"), + MerchantName = "Test Merchant 1" + }; + estateDetails.AddMerchant(merchantResponse); + this.Estates.Add(estateDetails); + } + + estateDetails.ShouldNotBeNull(); + + return estateDetails; + } + + /// + /// Gets the estate details. + /// + /// Name of the estate. + /// + public EstateDetails GetEstateDetails(String estateName) + { + EstateDetails estateDetails = this.Estates.SingleOrDefault(e => e.EstateName == estateName); + + estateDetails.ShouldNotBeNull(); + + return estateDetails; + } + + /// + /// Gets the estate details. + /// + /// The estate identifier. + /// + public EstateDetails GetEstateDetails(Guid estateId) + { + EstateDetails estateDetails = this.Estates.SingleOrDefault(e => e.EstateId == estateId); + + estateDetails.ShouldNotBeNull(); + + return estateDetails; + } + + public void AddEstateDetails(Guid estateId, + String estateName, + String estateReference) + { + this.Estates.Add(EstateDetails.Create(estateId, estateName, estateReference)); + } +} + +public class ClientDetails +{ + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The client identifier. + /// The client secret. + /// Type of the grant. + private ClientDetails(String clientId, + String clientSecret, + String grantType) + { + this.ClientId = clientId; + this.ClientSecret = clientSecret; + this.GrantType = grantType; + } + + #endregion + + #region Properties + + /// + /// Gets the client identifier. + /// + /// + /// The client identifier. + /// + public String ClientId { get; } + + /// + /// Gets the client secret. + /// + /// + /// The client secret. + /// + public String ClientSecret { get; } + + /// + /// Gets the type of the grant. + /// + /// + /// The type of the grant. + /// + public String GrantType { get; } + + #endregion + + #region Methods + + /// + /// Creates the specified client identifier. + /// + /// The client identifier. + /// The client secret. + /// Type of the grant. + /// + public static ClientDetails Create(String clientId, + String clientSecret, + String grantType) + { + return new ClientDetails(clientId, clientSecret, grantType); + } + + #endregion } \ No newline at end of file diff --git a/CallbackHandler.IntegrationTests/Features/BankDepositCallback.feature b/CallbackHandler.IntegrationTests/Features/BankDepositCallback.feature index cbe08de..e979a04 100644 --- a/CallbackHandler.IntegrationTests/Features/BankDepositCallback.feature +++ b/CallbackHandler.IntegrationTests/Features/BankDepositCallback.feature @@ -1,9 +1,40 @@ @base @shared Feature: BankDepositCallback +Background: +Given I create the following api scopes + | Name | DisplayName | Description | + | estateManagement | Estate Managememt REST Scope | A scope for Estate Managememt REST | + | transactionProcessor | Transaction Processor REST Scope | A scope for Transaction Processor REST | + | voucherManagement | Voucher Management REST Scope | A scope for Voucher Management REST | + | messagingService | Scope for Messaging REST | Scope for Messaging REST | + + Given the following api resources exist + | Name | DisplayName | Secret | Scopes | UserClaims | + | estateManagement | Estate Managememt REST | Secret1 | estateManagement | MerchantId, EstateId, role | + | transactionProcessor | Transaction Processor REST | Secret1 | transactionProcessor | | + | voucherManagement | Voucher Management REST | Secret1 | voucherManagement | | + | messagingService | Messaging REST | Secret | messagingService | | + + Given the following clients exist + | ClientId | ClientName | Secret | Scopes | GrantTypes | + | serviceClient | Service Client | Secret1 | estateManagement,transactionProcessor,voucherManagement,messagingService | client_credentials | + + Given I have a token to access the estate management and transaction processor resources + | ClientId | + | serviceClient | + + Given I have created the following estates + | EstateName | + | Test Estate 1 | + + Given I create the following merchants + | MerchantName | AddressLine1 | Town | Region | Country | ContactName | EmailAddress | EstateName | + | Test Merchant 1 | Address Line 1 | TestTown | Test Region | United Kingdom | Test Contact 1 | testcontact1@merchant1.co.uk | Test Estate 1 | + Scenario: Process a Bank Deposit Callback Given I have the following Bank Deposit Callbacks - | Amount | DateTime | DepositId | HostIdentifier | Reference | SortCode | AccountNumber | - | 100.00 | Today | 6AE04AFC-D7F8-4936-A3A2-DCA177CAA106 | DC4A7DDA-45A1-4D5B-8D46-21FA99A3868E | DC4A7DDA45A14D5B8D4621FA99A3868E-6AE04AFCD7F84936A3A2DCA177CAA106 | 11-22-33 | 12345678 | + | Amount | DateTime | DepositId | HostIdentifier | SortCode | AccountNumber | + | 100.00 | Today | 6AE04AFC-D7F8-4936-A3A2-DCA177CAA106 | DC4A7DDA-45A1-4D5B-8D46-21FA99A3868E | 11-22-33 | 12345678 | When I send the requests to the callback handler for deposits Then the deposit records are recorded diff --git a/CallbackHandler.IntegrationTests/Features/BankDepositCallback.feature.cs b/CallbackHandler.IntegrationTests/Features/BankDepositCallback.feature.cs index d803a4a..eee5b03 100644 --- a/CallbackHandler.IntegrationTests/Features/BankDepositCallback.feature.cs +++ b/CallbackHandler.IntegrationTests/Features/BankDepositCallback.feature.cs @@ -107,6 +107,119 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa await testRunner.CollectScenarioErrorsAsync(); } + public virtual async global::System.Threading.Tasks.Task FeatureBackgroundAsync() + { +#line 4 +#line hidden + global::Reqnroll.Table table1 = new global::Reqnroll.Table(new string[] { + "Name", + "DisplayName", + "Description"}); + table1.AddRow(new string[] { + "estateManagement", + "Estate Managememt REST Scope", + "A scope for Estate Managememt REST"}); + table1.AddRow(new string[] { + "transactionProcessor", + "Transaction Processor REST Scope", + "A scope for Transaction Processor REST"}); + table1.AddRow(new string[] { + "voucherManagement", + "Voucher Management REST Scope", + "A scope for Voucher Management REST"}); + table1.AddRow(new string[] { + "messagingService", + "Scope for Messaging REST", + "Scope for Messaging REST"}); +#line 5 +await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table1, "Given "); +#line hidden + global::Reqnroll.Table table2 = new global::Reqnroll.Table(new string[] { + "Name", + "DisplayName", + "Secret", + "Scopes", + "UserClaims"}); + table2.AddRow(new string[] { + "estateManagement", + "Estate Managememt REST", + "Secret1", + "estateManagement", + "MerchantId, EstateId, role"}); + table2.AddRow(new string[] { + "transactionProcessor", + "Transaction Processor REST", + "Secret1", + "transactionProcessor", + ""}); + table2.AddRow(new string[] { + "voucherManagement", + "Voucher Management REST", + "Secret1", + "voucherManagement", + ""}); + table2.AddRow(new string[] { + "messagingService", + "Messaging REST", + "Secret", + "messagingService", + ""}); +#line 12 + await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table2, "Given "); +#line hidden + global::Reqnroll.Table table3 = new global::Reqnroll.Table(new string[] { + "ClientId", + "ClientName", + "Secret", + "Scopes", + "GrantTypes"}); + table3.AddRow(new string[] { + "serviceClient", + "Service Client", + "Secret1", + "estateManagement,transactionProcessor,voucherManagement,messagingService", + "client_credentials"}); +#line 19 + await testRunner.GivenAsync("the following clients exist", ((string)(null)), table3, "Given "); +#line hidden + global::Reqnroll.Table table4 = new global::Reqnroll.Table(new string[] { + "ClientId"}); + table4.AddRow(new string[] { + "serviceClient"}); +#line 23 + await testRunner.GivenAsync("I have a token to access the estate management and transaction processor resource" + + "s", ((string)(null)), table4, "Given "); +#line hidden + global::Reqnroll.Table table5 = new global::Reqnroll.Table(new string[] { + "EstateName"}); + table5.AddRow(new string[] { + "Test Estate 1"}); +#line 27 + await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table5, "Given "); +#line hidden + global::Reqnroll.Table table6 = new global::Reqnroll.Table(new string[] { + "MerchantName", + "AddressLine1", + "Town", + "Region", + "Country", + "ContactName", + "EmailAddress", + "EstateName"}); + table6.AddRow(new string[] { + "Test Merchant 1", + "Address Line 1", + "TestTown", + "Test Region", + "United Kingdom", + "Test Contact 1", + "testcontact1@merchant1.co.uk", + "Test Estate 1"}); +#line 31 + await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table6, "Given "); +#line hidden + } + private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() { return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("Features/BankDepositCallback.feature.ndjson", 3); @@ -122,7 +235,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Process a Bank Deposit Callback", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); string[] tagsOfRule = ((string[])(null)); global::Reqnroll.RuleInfo ruleInfo = null; -#line 4 +#line 35 this.ScenarioInitialize(scenarioInfo, ruleInfo); #line hidden if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) @@ -132,29 +245,30 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa else { await this.ScenarioStartAsync(); - global::Reqnroll.Table table1 = new global::Reqnroll.Table(new string[] { +#line 4 +await this.FeatureBackgroundAsync(); +#line hidden + global::Reqnroll.Table table7 = new global::Reqnroll.Table(new string[] { "Amount", "DateTime", "DepositId", "HostIdentifier", - "Reference", "SortCode", "AccountNumber"}); - table1.AddRow(new string[] { + table7.AddRow(new string[] { "100.00", "Today", "6AE04AFC-D7F8-4936-A3A2-DCA177CAA106", "DC4A7DDA-45A1-4D5B-8D46-21FA99A3868E", - "DC4A7DDA45A14D5B8D4621FA99A3868E-6AE04AFCD7F84936A3A2DCA177CAA106", "11-22-33", "12345678"}); -#line 5 - await testRunner.GivenAsync("I have the following Bank Deposit Callbacks", ((string)(null)), table1, "Given "); +#line 36 + await testRunner.GivenAsync("I have the following Bank Deposit Callbacks", ((string)(null)), table7, "Given "); #line hidden -#line 8 +#line 39 await testRunner.WhenAsync("I send the requests to the callback handler for deposits", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); #line hidden -#line 9 +#line 40 await testRunner.ThenAsync("the deposit records are recorded", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); #line hidden } diff --git a/CallbackHandler.IntegrationTests/Shared/SharedSteps.cs b/CallbackHandler.IntegrationTests/Shared/SharedSteps.cs index 3920777..28d8b13 100644 --- a/CallbackHandler.IntegrationTests/Shared/SharedSteps.cs +++ b/CallbackHandler.IntegrationTests/Shared/SharedSteps.cs @@ -1,6 +1,5 @@ using CallbackHandler.IntegrationTests.Common; using Reqnroll; -using Shared.IntegrationTesting; using System; using System.Collections.Generic; using System.Linq; @@ -11,7 +10,17 @@ using System.Threading.Tasks; using CallbackHandler.DataTransferObjects; using Newtonsoft.Json; +using SecurityService.DataTransferObjects.Requests; +using SecurityService.DataTransferObjects.Responses; +using SecurityService.IntegrationTesting.Helpers; using Shouldly; +using TransactionProcessor.DataTransferObjects.Requests.Estate; +using TransactionProcessor.DataTransferObjects.Requests.Merchant; +using TransactionProcessor.DataTransferObjects.Responses.Estate; +using TransactionProcessor.DataTransferObjects.Responses.Merchant; +using TransactionProcessor.IntegrationTesting.Helpers; +using ClientDetails = CallbackHandler.IntegrationTests.Common.ClientDetails; +using ReqnrollTableHelper = Shared.IntegrationTesting.ReqnrollTableHelper; namespace CallbackHandler.IntegrationTests.Shared { @@ -23,17 +32,35 @@ public class SharedSteps private readonly TestingContext TestingContext; + private readonly SecurityServiceSteps SecurityServiceSteps; + + private readonly TransactionProcessorSteps TransactionProcessorSteps; + public SharedSteps(ScenarioContext scenarioContext, TestingContext testingContext) { this.ScenarioContext = scenarioContext; this.TestingContext = testingContext; + this.SecurityServiceSteps = new SecurityServiceSteps(testingContext.DockerHelper.SecurityServiceClient); + this.TransactionProcessorSteps = new TransactionProcessorSteps(testingContext.DockerHelper.TransactionProcessorClient, testingContext.DockerHelper.TestHostHttpClient, + testingContext.DockerHelper.ProjectionManagementClient); + } [Given(@"I have the following Bank Deposit Callbacks")] public async Task GivenIHaveTheFollowingBankDepositCallbacks(DataTable table) { List requests = table.Rows.ToDepositRequests(); + + List allEstateIds = this.TestingContext.GetAllEstateIds(); + var estateId = allEstateIds.First(); + var estateDetails = this.TestingContext.GetEstateDetails(estateId); + var merchantList = estateDetails.GetMerchants(); + var merchantId = merchantList.First().MerchantId; + foreach (Deposit request in requests) { + request.Reference = $"{estateId:N}-{merchantId:N}"; + } + this.TestingContext.Deposits = requests; } @@ -84,6 +111,79 @@ public async Task ThenTheDepositRecordsAreRecorded() callbackDeposit.AccountNumber.ShouldBe(originalDeposit.AccountNumber); } } + + [Given(@"I create the following api scopes")] + public async Task GivenICreateTheFollowingApiScopes(DataTable table) + { + List requests = table.Rows.ToCreateApiScopeRequests(); + await this.SecurityServiceSteps.GivenICreateTheFollowingApiScopes(requests); + } + + [Given(@"the following api resources exist")] + public async Task GivenTheFollowingApiResourcesExist(DataTable table) + { + List requests = table.Rows.ToCreateApiResourceRequests(); + await this.SecurityServiceSteps.GivenTheFollowingApiResourcesExist(requests); + } + + [Given(@"the following clients exist")] + public async Task GivenTheFollowingClientsExist(DataTable table) + { + List requests = table.Rows.ToCreateClientRequests(); + List<(String clientId, String secret, List allowedGrantTypes)> clients = await this.SecurityServiceSteps.GivenTheFollowingClientsExist(requests); + foreach ((String clientId, String secret, List allowedGrantTypes) client in clients) + { + this.TestingContext.AddClientDetails(client.clientId, client.secret, String.Join(",", client.allowedGrantTypes)); + } + } + + [Given(@"I have a token to access the estate management resource")] + [Given(@"I have a token to access the estate management and transaction processor resources")] + public async Task GivenIHaveATokenToAccessTheEstateManagementResource(DataTable table) + { + DataTableRow firstRow = table.Rows.First(); + String clientId = ReqnrollTableHelper.GetStringRowValue(firstRow, "ClientId"); + ClientDetails clientDetails = this.TestingContext.GetClientDetails(clientId); + + //this.TestingContext.AccessToken = await this.SecurityServiceSteps.GetClientToken(clientDetails.ClientId, clientDetails.ClientSecret, CancellationToken.None); + var token = await this.TestingContext.DockerHelper.SecurityServiceClient.GetToken(clientDetails.ClientId, clientDetails.ClientSecret, CancellationToken.None); + token.IsSuccess.ShouldBeTrue(); + this.TestingContext.AccessToken = token.Data.AccessToken; + + } + + [Given(@"I have created the following estates")] + [When(@"I create the following estates")] + public async Task WhenICreateTheFollowingEstates(DataTable table) + { + List requests = table.Rows.ToCreateEstateRequests(); + + var verifiedEstates = await this.TransactionProcessorSteps.WhenICreateTheFollowingEstatesX(this.TestingContext.AccessToken, requests); + + foreach (EstateResponse verifiedEstate in verifiedEstates) + { + this.TestingContext.AddEstateDetails(verifiedEstate.EstateId, verifiedEstate.EstateName, verifiedEstate.EstateReference); + this.TestingContext.Logger.LogInformation($"Estate {verifiedEstate.EstateName} created with Id {verifiedEstate.EstateId}"); + } + } + + [Given("I create the following merchants")] + [When(@"I create the following merchants")] + public async Task WhenICreateTheFollowingMerchants(DataTable table) + { + List<(EstateDetails estate, CreateMerchantRequest)> requests = table.Rows.ToCreateMerchantRequests(this.TestingContext.Estates); + + List verifiedMerchants = await this.TransactionProcessorSteps.WhenICreateTheFollowingMerchants(this.TestingContext.AccessToken, requests); + + foreach (MerchantResponse verifiedMerchant in verifiedMerchants) + { + //await this.TransactionProcessorSteps.WhenICreateTheFollowingMerchants(this.TestingContext.AccessToken, verifiedMerchant.EstateId, verifiedMerchant.MerchantId); + + EstateDetails estateDetails = this.TestingContext.GetEstateDetails(verifiedMerchant.EstateId); + estateDetails.AddMerchant(verifiedMerchant); + this.TestingContext.Logger.LogInformation($"Merchant {verifiedMerchant.MerchantName} created with Id {verifiedMerchant.MerchantId} for Estate {estateDetails.EstateName}"); + } + } } public static class ReqnrollExtensions diff --git a/CallbackHandler/Bootstrapper/ClientRegistry.cs b/CallbackHandler/Bootstrapper/ClientRegistry.cs new file mode 100644 index 0000000..8d8e3a8 --- /dev/null +++ b/CallbackHandler/Bootstrapper/ClientRegistry.cs @@ -0,0 +1,23 @@ +using ClientProxyBase; +using Lamar; +using Microsoft.Extensions.DependencyInjection; +using SecurityService.Client; +using System; +using System.Diagnostics.CodeAnalysis; +using Shared.General; +using TransactionProcessor.Client; + +namespace CallbackHandler.Bootstrapper; + +[ExcludeFromCodeCoverage] +public class ClientRegistry : ServiceRegistry +{ + public ClientRegistry() { + this.AddHttpContextAccessor(); + this.RegisterHttpClient(); + this.RegisterHttpClient(); + + Func resolver(IServiceProvider container) => serviceName => ConfigurationReader.GetBaseServerUri(serviceName).OriginalString; + this.AddSingleton>(resolver); + } +} \ No newline at end of file diff --git a/CallbackHandler/Startup.cs b/CallbackHandler/Startup.cs index 2a42686..5745426 100644 --- a/CallbackHandler/Startup.cs +++ b/CallbackHandler/Startup.cs @@ -60,6 +60,7 @@ public void ConfigureContainer(ServiceRegistry services) services.IncludeRegistry(); services.IncludeRegistry(); services.IncludeRegistry(); + services.IncludeRegistry(); TypeProvider.LoadDomainEventsTypeDynamically();