From 230acd5c75157acba931d88956480630d49658d4 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Mon, 12 Oct 2026 18:25:23 +0100 Subject: [PATCH 1/6] Harden integration test failure detection --- .../EstateDetails.cs | 34 +- .../SpecflowExtensions.cs | 127 ++++--- .../TransactionProcessorSteps.cs | 349 ++++++++++++------ .../Common/Retry.cs | 31 +- .../Common/RetryTests.cs | 25 ++ .../Common/SpecflowExtensionsTests.cs | 217 +++++++++++ .../Common/TestingContext.cs | 3 +- .../Shared/SharedSteps.cs | 5 + 8 files changed, 600 insertions(+), 191 deletions(-) create mode 100644 TransactionProcessor.IntegrationTests/Common/RetryTests.cs create mode 100644 TransactionProcessor.IntegrationTests/Common/SpecflowExtensionsTests.cs diff --git a/TransactionProcessor.IntegrationTesting.Helpers/EstateDetails.cs b/TransactionProcessor.IntegrationTesting.Helpers/EstateDetails.cs index 4da0b63a..1899e15b 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/EstateDetails.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/EstateDetails.cs @@ -32,7 +32,13 @@ public Guid GetMerchantId(String merchantName) return Guid.Parse("D59320FA-4C3E-4900-A999-483F6A10C69A"); } - return this.Merchants.Single(m => m.MerchantName == merchantName).MerchantId; + TransactionProcessor.DataTransferObjects.Responses.Merchant.MerchantResponse? merchant = this.Merchants.SingleOrDefault(m => m.MerchantName == merchantName); + if (merchant == null) + { + throw new InvalidOperationException($"Merchant '{merchantName}' was not found on estate '{this.EstateName}'."); + } + + return merchant.MerchantId; } public void AddTransactionResponse(Guid merchantId, @@ -104,7 +110,7 @@ public void AddMerchant(TransactionProcessor.DataTransferObjects.Responses.Merch // make sure the merchant does not already exist if (this.Merchants.Any(m => m.MerchantId == merchant.MerchantId)) { - return; + throw new InvalidOperationException($"Merchant [{merchant.MerchantId}] already exists in estate [{this.EstateId}]."); } this.Merchants.Add(merchant); @@ -112,7 +118,13 @@ public void AddMerchant(TransactionProcessor.DataTransferObjects.Responses.Merch public Contract GetContract(String contractName) { - return this.Contracts.Single(c => c.Description == contractName); + Contract? contract = this.Contracts.SingleOrDefault(c => c.Description == contractName); + if (contract == null) + { + throw new InvalidOperationException($"Contract '{contractName}' was not found on estate '{this.EstateName}'."); + } + + return contract; } public Contract GetContract(Guid contractId) { @@ -121,7 +133,13 @@ public Contract GetContract(Guid contractId) public TransactionProcessor.DataTransferObjects.Responses.Merchant.MerchantResponse GetMerchant(String merchantName) { - return this.Merchants.SingleOrDefault(m => m.MerchantName == merchantName); + TransactionProcessor.DataTransferObjects.Responses.Merchant.MerchantResponse? merchant = this.Merchants.SingleOrDefault(m => m.MerchantName == merchantName); + if (merchant == null) + { + throw new InvalidOperationException($"Merchant '{merchantName}' was not found on estate '{this.EstateName}'."); + } + + return merchant; } public List GetMerchants() @@ -238,7 +256,13 @@ public Product GetProduct(Guid productId) public Product GetProduct(String name) { - return this.Products.SingleOrDefault(p => p.Name == name); + Product? product = this.Products.SingleOrDefault(p => p.Name == name); + if (product == null) + { + throw new InvalidOperationException($"Product '{name}' was not found on contract '{this.Description}'."); + } + + return product; } } diff --git a/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs b/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs index 4247ab9e..c08e6a72 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs @@ -124,6 +124,7 @@ public class SettlementFeeDetails public static SettlementDetails ToSettlementDetails(this DataTableRows tableRows, string estateName, List estateDetailsList) { + tableRows.Count.ShouldBe(1, "Settlement details require exactly one table row."); SettlementDetails result = new SettlementDetails(); EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); @@ -152,6 +153,7 @@ public static Guid CalculateSettlementAggregateId(DateTime settlementDate, public static SettlementDetails ToSettlementDetails(this DataTableRows tableRows, string estateName, string merchantName, List estateDetailsList) { + tableRows.Count.ShouldBe(1, "Settlement details require exactly one table row."); SettlementDetails result = new SettlementDetails(); EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); @@ -192,6 +194,7 @@ public static List ToSettlementFeeDetails(this DataTableRo settlementFeeDetails.IsSettled = ReqnrollTableHelper.GetBooleanValue(tableRow, "IsSettled"); settlementFeeDetails.Operator = ReqnrollTableHelper.GetStringRowValue(tableRow, "Operator"); settlementFeeDetails.CalculatedValue = ReqnrollTableHelper.GetDecimalValue(tableRow, "CalculatedValue"); + settlementFeeDetailsList.Add(settlementFeeDetails); } return settlementFeeDetailsList; } @@ -207,27 +210,24 @@ public static List ToSettlementFeeDetails(this DataTableRo EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); estateDetails.ShouldNotBeNull(); - foreach (DataTableRow dataTableRow in tableRows) - { - - String merchantName = ReqnrollTableHelper.GetStringRowValue(tableRow, "MerchantName"); - MerchantResponse merchant = estateDetails.GetMerchant(merchantName); + String merchantName = ReqnrollTableHelper.GetStringRowValue(tableRow, "MerchantName"); + MerchantResponse merchant = estateDetails.GetMerchant(merchantName); - Guid addressId = merchant.Addresses.First().AddressId; + merchant.Addresses.ShouldHaveSingleItem(); + Guid addressId = merchant.Addresses.Single().AddressId; - Address addressUpdateRequest = new Address() - { - AddressLine1 = ReqnrollTableHelper.GetStringRowValue(tableRow, "AddressLine1"), - AddressLine2 = ReqnrollTableHelper.GetStringRowValue(tableRow, "AddressLine2"), - AddressLine3 = ReqnrollTableHelper.GetStringRowValue(tableRow, "AddressLine3"), - AddressLine4 = ReqnrollTableHelper.GetStringRowValue(tableRow, "AddressLine4"), - Town = ReqnrollTableHelper.GetStringRowValue(tableRow, "Town"), - Region = ReqnrollTableHelper.GetStringRowValue(tableRow, "Region"), - Country = ReqnrollTableHelper.GetStringRowValue(tableRow, "Country"), - PostalCode = ReqnrollTableHelper.GetStringRowValue(tableRow, "PostalCode") - }; - result.Add((estateDetails, merchant, addressId, addressUpdateRequest)); - } + Address addressUpdateRequest = new Address() + { + AddressLine1 = ReqnrollTableHelper.GetStringRowValue(tableRow, "AddressLine1"), + AddressLine2 = ReqnrollTableHelper.GetStringRowValue(tableRow, "AddressLine2"), + AddressLine3 = ReqnrollTableHelper.GetStringRowValue(tableRow, "AddressLine3"), + AddressLine4 = ReqnrollTableHelper.GetStringRowValue(tableRow, "AddressLine4"), + Town = ReqnrollTableHelper.GetStringRowValue(tableRow, "Town"), + Region = ReqnrollTableHelper.GetStringRowValue(tableRow, "Region"), + Country = ReqnrollTableHelper.GetStringRowValue(tableRow, "Country"), + PostalCode = ReqnrollTableHelper.GetStringRowValue(tableRow, "PostalCode") + }; + result.Add((estateDetails, merchant, addressId, addressUpdateRequest)); } @@ -245,22 +245,19 @@ public static List ToSettlementFeeDetails(this DataTableRo EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); estateDetails.ShouldNotBeNull(); - foreach (DataTableRow dataTableRow in tableRows) - { - - String merchantName = ReqnrollTableHelper.GetStringRowValue(tableRow, "MerchantName"); - MerchantResponse merchant = estateDetails.GetMerchant(merchantName); + String merchantName = ReqnrollTableHelper.GetStringRowValue(tableRow, "MerchantName"); + MerchantResponse merchant = estateDetails.GetMerchant(merchantName); - Guid contactId = merchant.Contacts.First().ContactId; + merchant.Contacts.ShouldHaveSingleItem(); + Guid contactId = merchant.Contacts.Single().ContactId; - Contact contactUpdateRequest = new Contact() - { - ContactName = ReqnrollTableHelper.GetStringRowValue(tableRow, "ContactName"), - EmailAddress = ReqnrollTableHelper.GetStringRowValue(tableRow, "EmailAddress"), - PhoneNumber = ReqnrollTableHelper.GetStringRowValue(tableRow, "PhoneNumber"), - }; - result.Add((estateDetails, merchant, contactId, contactUpdateRequest)); - } + Contact contactUpdateRequest = new Contact() + { + ContactName = ReqnrollTableHelper.GetStringRowValue(tableRow, "ContactName"), + EmailAddress = ReqnrollTableHelper.GetStringRowValue(tableRow, "EmailAddress"), + PhoneNumber = ReqnrollTableHelper.GetStringRowValue(tableRow, "PhoneNumber"), + }; + result.Add((estateDetails, merchant, contactId, contactUpdateRequest)); } return result; @@ -521,37 +518,51 @@ public static List ToAutomaticDepositRequests(this DataTableRows tableRo Guid contractId = Guid.Empty; Guid productId = Guid.Empty; Contract contract = null; - Product product = null; - try{ - contract = estateDetails.GetContract(contractDescription); - } - catch(Exception ex){ + if (contractDescription == "EmptyContract") + { + contractId = Guid.Empty; } - - if (contract != null){ - contractId = contract.ContractId; - product = contract.GetProduct(productName); - if (product != null){ - productId = product.ProductId; + else if (contractDescription == "InvalidContract") + { + contractId = Guid.Parse("934d8164-f36a-448e-b27b-4d671d41d180"); + } + else + { + try + { + contract = estateDetails.GetContract(contractDescription); } - else{ - productId = productName switch{ - "EmptyProduct" => Guid.Empty, - "InvalidProduct" => Guid.Parse("934d8164-f36a-448e-b27b-4d671d41d180"), - _ => Guid.NewGuid(), - }; + catch (InvalidOperationException) + { + throw new InvalidOperationException($"Contract '{contractDescription}' was not found on estate '{estateDetails.EstateName}'."); } - } - else{ - // This is a nasty hack atm... - contractId = contractDescription switch{ - "EmptyContract" => Guid.Empty, - "InvalidContract" => Guid.Parse("934d8164-f36a-448e-b27b-4d671d41d180"), - _ => Guid.NewGuid(), - }; + contractId = contract.ContractId; + } + if (contract != null) + { + if (productName == "EmptyProduct") + { + productId = Guid.Empty; + } + else if (productName == "InvalidProduct") + { + productId = Guid.Parse("934d8164-f36a-448e-b27b-4d671d41d180"); + } + else + { + try + { + Product product = contract.GetProduct(productName); + productId = product.ProductId; + } + catch (InvalidOperationException) + { + throw new InvalidOperationException($"Product '{productName}' was not found on contract '{contractDescription}'."); + } + } } Guid operatorId = estateDetails.GetOperatorId(operatorName); diff --git a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs index 8585bd81..55235743 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs @@ -54,6 +54,7 @@ await this.TransactionProcessorClient.GetSettlements(accessToken, endDate.ToString("yyyyMMdd"), CancellationToken.None); + settlementList.IsSuccess.ShouldBeTrue(settlementList.Message); settlementList.Data.ShouldNotBeNull(); settlementList.Data.ShouldNotBeEmpty(); @@ -90,6 +91,7 @@ await this.TransactionProcessorClient.GetSettlement(accessToken, CancellationToken.None); settlement.ShouldNotBeNull(); + settlement.IsSuccess.ShouldBeTrue(settlement.Message); settlement.Data.SettlementFees.ShouldNotBeNull(); settlement.Data.SettlementFees.ShouldNotBeEmpty(); @@ -189,13 +191,12 @@ public async Task WhenISetTheMerchantsSettlementSchedule(String accessToken, Lis { foreach ((EstateDetails, Guid, SetSettlementScheduleRequest) request in requests) { - Should.NotThrow(async () => { - await this.TransactionProcessorClient.SetMerchantSettlementSchedule(accessToken, - request.Item1.EstateId, - request.Item2, - request.Item3, - CancellationToken.None); - }); + Result result = await this.TransactionProcessorClient.SetMerchantSettlementSchedule(accessToken, + request.Item1.EstateId, + request.Item2, + request.Item3, + CancellationToken.None); + result.IsSuccess.ShouldBeTrue(result.Message); } } @@ -240,13 +241,14 @@ public async Task WhenISwapTheMerchantDeviceTheDeviceIsSwapped(String accessToke { foreach ((EstateDetails, Guid, String, SwapMerchantDeviceRequest) request in requests) { - await this.TransactionProcessorClient + Result swapResult = await this.TransactionProcessorClient .SwapDeviceForMerchant(accessToken, request.Item1.EstateId, request.Item2, request.Item3, request.Item4, CancellationToken.None).ConfigureAwait(false); + swapResult.IsSuccess.ShouldBeTrue(swapResult.Message); //this.TestingContext.Logger.LogInformation($"Device {newDeviceIdentifier} assigned to Merchant {merchantName}"); @@ -256,7 +258,8 @@ await Retry.For(async () => { .ConfigureAwait(false); getMerchantResult.IsSuccess.ShouldBeTrue(getMerchantResult.Message); MerchantResponse? merchantResponse = getMerchantResult.Data; - merchantResponse.Devices.ContainsValue(request.Item4.NewDeviceIdentifier); + merchantResponse.ShouldNotBeNull(); + merchantResponse.Devices.ContainsValue(request.Item4.NewDeviceIdentifier).ShouldBeTrue(); }); } } @@ -358,13 +361,14 @@ public async Task> WhenICreateTheFollowingMerchants(strin foreach ((Guid, Guid, String) m in merchants) { + MerchantResponse merchant = null; await Retry.For(async () => { Result? getMerchantResult = await this.TransactionProcessorClient .GetMerchant(accessToken, m.Item1, m.Item2, CancellationToken.None) .ConfigureAwait(false); getMerchantResult.IsSuccess.ShouldBeTrue(getMerchantResult.Message); - MerchantResponse merchant = getMerchantResult.Data; - responses.Add(merchant); + merchant = getMerchantResult.Data; + merchant.ShouldNotBeNull(); string projectionName = "MerchantBalanceProjection"; String partitionId = $"MerchantBalance-{m.Item2:N}"; @@ -378,6 +382,8 @@ await Retry.For(async () => { getMerchantBalanceResult2.IsSuccess.ShouldBeTrue(getMerchantBalanceResult2.Message); getMerchantBalanceResult2.Data.ShouldNotBeNull(); }); + merchant.ShouldNotBeNull(); + responses.Add(merchant); } return responses; @@ -425,12 +431,11 @@ public async Task WhenIRequestTheReceiptIsResent(String accessToken, List { - Should.NotThrow(async () => { - await this.TransactionProcessorClient.ResendEmailReceipt(accessToken, - transactionResponse.EstateId, - transactionResponse.TransactionId, - CancellationToken.None); - }); + Result result = await this.TransactionProcessorClient.ResendEmailReceipt(accessToken, + transactionResponse.EstateId, + transactionResponse.TransactionId, + CancellationToken.None); + result.IsSuccess.ShouldBeTrue(result.Message); }); } @@ -565,7 +570,8 @@ await this.TransactionProcessorClient.GetSettlementByDate(accessToken, request.MerchantId, CancellationToken.None); getSettlementByDateResult.IsSuccess.ShouldBeTrue(getSettlementByDateResult.Message); - SettlementResponse settlement = getSettlementByDateResult.Data; + SettlementResponse? settlement = getSettlementByDateResult.Data; + settlement.ShouldNotBeNull(); settlement.NumberOfFeesPendingSettlement.ShouldBe(0); settlement.NumberOfFeesSettled.ShouldBe(expectedNumberFeesSettled); @@ -586,7 +592,8 @@ await this.TransactionProcessorClient.GetSettlementByDate(accessToken, request.Item2, CancellationToken.None); getSettlementByDateResult.IsSuccess.ShouldBeTrue(getSettlementByDateResult.Message); - SettlementResponse settlement = getSettlementByDateResult.Data; + SettlementResponse? settlement = getSettlementByDateResult.Data; + settlement.ShouldNotBeNull(); settlement.NumberOfFeesSettled.ShouldBe(request.Item4, $"Settlement date {request.Item3}"); }, TimeSpan.FromMinutes(3)); @@ -677,21 +684,27 @@ public async Task> WhenIUpdateTheFollowingMerchants(strin foreach ((EstateDetails estate, Guid merchantId, UpdateMerchantRequest request) request in requests) { - await this.TransactionProcessorClient.UpdateMerchant(accessToken, request.estate.EstateId, request.merchantId, request.request, CancellationToken.None).ConfigureAwait(false); + Result updateResult = await this.TransactionProcessorClient + .UpdateMerchant(accessToken, request.estate.EstateId, request.merchantId, request.request, CancellationToken.None) + .ConfigureAwait(false); + updateResult.IsSuccess.ShouldBeTrue(updateResult.Message); } foreach ((EstateDetails estate, Guid merchantId, UpdateMerchantRequest request) request in requests) { + MerchantResponse merchant = null; await Retry.For(async () => { Result? getMerchantResult = await this.TransactionProcessorClient .GetMerchant(accessToken, request.estate.EstateId, request.merchantId, CancellationToken.None) .ConfigureAwait(false); getMerchantResult.IsSuccess.ShouldBeTrue(getMerchantResult.Message); - MerchantResponse merchant = getMerchantResult.Data; + merchant = getMerchantResult.Data; + merchant.ShouldNotBeNull(); merchant.MerchantName.ShouldBe(request.request.Name); merchant.SettlementSchedule.ShouldBe(request.request.SettlementSchedule); - responses.Add(merchant); }); + merchant.ShouldNotBeNull(); + responses.Add(merchant); } return responses; @@ -703,17 +716,22 @@ public async Task> WhenISetTheOpeningHoursOfTheFollowingM foreach ((EstateDetails estate, Guid merchantId, MerchantOpeningRequest request) request in requests) { - await this.TransactionProcessorClient.UpdateMerchantOpeningHours(accessToken, request.estate.EstateId, request.merchantId, request.request, CancellationToken.None).ConfigureAwait(false); + Result updateResult = await this.TransactionProcessorClient + .UpdateMerchantOpeningHours(accessToken, request.estate.EstateId, request.merchantId, request.request, CancellationToken.None) + .ConfigureAwait(false); + updateResult.IsSuccess.ShouldBeTrue(updateResult.Message); } foreach ((EstateDetails estate, Guid merchantId, MerchantOpeningRequest request) request in requests) { + MerchantResponse merchant = null; await Retry.For(async () => { Result? getMerchantResult = await this.TransactionProcessorClient .GetMerchant(accessToken, request.estate.EstateId, request.merchantId, CancellationToken.None) .ConfigureAwait(false); getMerchantResult.IsSuccess.ShouldBeTrue(getMerchantResult.Message); - MerchantResponse merchant = getMerchantResult.Data; + merchant = getMerchantResult.Data; + merchant.ShouldNotBeNull(); merchant.OpeningHours.ShouldNotBeNull(); merchant.OpeningHours[DayOfWeek.Sunday].Opening.ShouldBe(request.request.Sunday.Opening); merchant.OpeningHours[DayOfWeek.Sunday].Closing.ShouldBe(request.request.Sunday.Closing); @@ -731,8 +749,9 @@ await Retry.For(async () => { merchant.OpeningHours[DayOfWeek.Saturday].Closing.ShouldBe(request.request.Saturday.Closing); merchant.OpeningHours[DayOfWeek.Sunday].Opening.ShouldBe(request.request.Sunday.Opening); merchant.OpeningHours[DayOfWeek.Sunday].Closing.ShouldBe(request.request.Sunday.Closing); - responses.Add(merchant); }); + merchant.ShouldNotBeNull(); + responses.Add(merchant); } return responses; @@ -757,25 +776,20 @@ await Retry.For(async () => { foreach ((EstateDetails estate, Guid merchantId, Guid operatorId) m in merchantOperators) { + MerchantOperatorResponse op = null; await Retry.For(async () => { Result? getMerchantResult = await this.TransactionProcessorClient .GetMerchant(accessToken, m.estate.EstateId, m.merchantId, CancellationToken.None) .ConfigureAwait(false); getMerchantResult.IsSuccess.ShouldBeTrue(getMerchantResult.Message); - MerchantResponse merchant = getMerchantResult.Data; - if (merchant.Operators == null) - { - Console.WriteLine($"Merchant {merchant.MerchantName} has null operators"); - } - - foreach (MerchantOperatorResponse merchantOperatorResponse in merchant.Operators) { - Console.WriteLine($"Operator Id {merchantOperatorResponse.OperatorId} Name {merchantOperatorResponse.Name}"); - } - - MerchantOperatorResponse op = merchant.Operators.SingleOrDefault(o => o.OperatorId == m.operatorId); + MerchantResponse? merchant = getMerchantResult.Data; + merchant.ShouldNotBeNull(); + merchant.Operators.ShouldNotBeNull(); + op = merchant.Operators.SingleOrDefault(o => o.OperatorId == m.operatorId); op.ShouldNotBeNull(); - result.Add((m.estate, op)); }); + op.ShouldNotBeNull(); + result.Add((m.estate, op)); } return result; @@ -797,11 +811,13 @@ await Retry.For(async () => { foreach ((EstateDetails estate, CreateOperatorRequest request) request in requests) { + OperatorResponse @operator = null; await Retry.For(async () => { Result>? operators = await this.TransactionProcessorClient .GetOperators(accessToken, request.estate.EstateId, CancellationToken.None).ConfigureAwait(false); operators.IsSuccess.ShouldBeTrue(operators.Message); - OperatorResponse? @operator = operators.Data.SingleOrDefault(o => o.Name == request.request.Name); + operators.Data.ShouldNotBeNull(); + @operator = operators.Data.SingleOrDefault(o => o.Name == request.request.Name); @operator.ShouldNotBeNull(); //@operator.OperatorId.ShouldNotBe(); if (request.request.OperatorId != Guid.Empty) { @@ -811,16 +827,17 @@ await Retry.For(async () => { @operator.OperatorId.ShouldNotBe(Guid.Empty); } - request.estate.AddOperator(@operator.OperatorId, request.request.Name); - results.Add((request.estate.EstateId, - new EstateOperatorResponse - { - OperatorId = @operator.OperatorId, - Name = request.request.Name, - RequireCustomMerchantNumber = request.request.RequireCustomMerchantNumber.GetValueOrDefault(), - RequireCustomTerminalNumber = request.request.RequireCustomTerminalNumber.GetValueOrDefault() - })); }); + @operator.ShouldNotBeNull(); + request.estate.AddOperator(@operator.OperatorId, request.request.Name); + results.Add((request.estate.EstateId, + new EstateOperatorResponse + { + OperatorId = @operator.OperatorId, + Name = request.request.Name, + RequireCustomMerchantNumber = request.request.RequireCustomMerchantNumber.GetValueOrDefault(), + RequireCustomTerminalNumber = request.request.RequireCustomTerminalNumber.GetValueOrDefault() + })); } return results; @@ -831,25 +848,32 @@ public async Task GivenIHaveAssignedTheFollowingOperatorsToTheEstates(String acc List<(Guid, EstateOperatorResponse)> results = new List<(Guid, EstateOperatorResponse)>(); foreach ((EstateDetails estate, DataTransferObjects.Requests.Estate.AssignOperatorRequest request) request in requests) { - await this.TransactionProcessorClient.AssignOperatorToEstate(accessToken, request.estate.EstateId, request.request, CancellationToken.None); + Result result = await this.TransactionProcessorClient.AssignOperatorToEstate(accessToken, + request.estate.EstateId, + request.request, + CancellationToken.None); + result.IsSuccess.ShouldBeTrue(result.Message); } // verify at the read model foreach ((EstateDetails estate, AssignOperatorRequest request) request in requests) { + EstateOperatorResponse operatorResponse = null; await Retry.For(async () => { Result? getEstateResult = await this.TransactionProcessorClient.GetEstate(accessToken, request.estate.EstateId, CancellationToken.None); getEstateResult.IsSuccess.ShouldBeTrue(getEstateResult.Message); - EstateResponse e = getEstateResult.Data; - EstateOperatorResponse operatorResponse = e.Operators.SingleOrDefault(o => o.OperatorId == request.request.OperatorId); + EstateResponse? e = getEstateResult.Data; + e.ShouldNotBeNull(); + e.Operators.ShouldNotBeNull(); + operatorResponse = e.Operators.SingleOrDefault(o => o.OperatorId == request.request.OperatorId); operatorResponse.ShouldNotBeNull(); - results.Add((request.estate.EstateId, operatorResponse)); - - request.estate.AddAssignedOperator(operatorResponse.OperatorId); }, retryFor: TimeSpan.FromSeconds(180)).ConfigureAwait(false); + operatorResponse.ShouldNotBeNull(); + results.Add((request.estate.EstateId, operatorResponse)); + request.estate.AddAssignedOperator(operatorResponse.OperatorId); } } @@ -866,13 +890,15 @@ public async Task> GivenICreateAContractWithTheFollowingV foreach ((EstateDetails, CreateContractRequest) request in requests) { - + ContractResponse contract = null; await Retry.For(async () => { Result> getContractsResult = await this.TransactionProcessorClient.GetContracts(accessToken, request.Item1.EstateId, CancellationToken.None).ConfigureAwait(false); + getContractsResult.IsSuccess.ShouldBeTrue(getContractsResult.Message); getContractsResult.Data.ShouldNotBeNull(); - ContractResponse contract = getContractsResult.Data.Single(c => c.Description == request.Item2.Description); - contractList.Add(contract); + contract = getContractsResult.Data.Single(c => c.Description == request.Item2.Description); }); + contract.ShouldNotBeNull(); + contractList.Add(contract); } return contractList; @@ -924,12 +950,14 @@ public async Task WhenIAddTheFollowingTransactionFees(String accessToken, List<( request.Item4, CancellationToken.None); result.IsSuccess.ShouldBeTrue(result.Message); + estateContractProductsFees.Add(request); } foreach ((EstateDetails, Contract, Product, AddTransactionFeeForProductToContractRequest) estateContractProductsFee in estateContractProductsFees) { await Retry.For(async () => { Result? getContractResult = await this.TransactionProcessorClient.GetContract(accessToken, estateContractProductsFee.Item1.EstateId, estateContractProductsFee.Item2.ContractId, CancellationToken.None).ConfigureAwait(false); + getContractResult.IsSuccess.ShouldBeTrue(getContractResult.Message); ContractResponse contract = getContractResult.Data; contract.ShouldNotBeNull(); @@ -945,17 +973,21 @@ await Retry.For(async () => { estateContractProductsFee.Item4.Description, estateContractProductsFee.Item4.Value); - Result>? feesResult = await this.TransactionProcessorClient.GetTransactionFeesForProduct(accessToken, - estateContractProductsFee.Item1.EstateId, - estateContractProductsFee.Item1.GetMerchants().First().MerchantId, - contract.ContractId, - product.ProductId, - CancellationToken.None); - feesResult.IsSuccess.ShouldBeTrue(feesResult.Message); - List? fees = feesResult.Data; - - ContractProductTransactionFee? fee = fees.SingleOrDefault(f => f.TransactionFeeId == transactionFee.TransactionFeeId); - fee.ShouldNotBeNull(); + foreach (MerchantResponse merchant in estateContractProductsFee.Item1.GetMerchants()) + { + Result>? feesResult = await this.TransactionProcessorClient.GetTransactionFeesForProduct(accessToken, + estateContractProductsFee.Item1.EstateId, + merchant.MerchantId, + contract.ContractId, + product.ProductId, + CancellationToken.None); + feesResult.IsSuccess.ShouldBeTrue(feesResult.Message); + List? fees = feesResult.Data; + fees.ShouldNotBeNull(); + + ContractProductTransactionFee? fee = fees.SingleOrDefault(f => f.TransactionFeeId == transactionFee.TransactionFeeId); + fee.ShouldNotBeNull(); + } }); } } @@ -963,24 +995,38 @@ await Retry.For(async () => { public async Task> GivenIHaveAssignedTheFollowingDevicesToTheMerchants(string accessToken, List<(EstateDetails, Guid, AddMerchantDeviceRequest)> requests) { List<(EstateDetails, MerchantResponse, String)> result = new(); - List<(EstateDetails estate, Guid merchantId, Guid deviceId)> merchantDevices = new(); + List<(EstateDetails estate, Guid merchantId, String deviceIdentifier)> merchantDevices = new(); foreach ((EstateDetails, Guid, AddMerchantDeviceRequest) request in requests) { - await this.TransactionProcessorClient.AddDeviceToMerchant(accessToken, request.Item1.EstateId, request.Item2, request.Item3, CancellationToken.None).ConfigureAwait(false); + Result addDeviceResult = await this.TransactionProcessorClient + .AddDeviceToMerchant(accessToken, + request.Item1.EstateId, + request.Item2, + request.Item3, + CancellationToken.None) + .ConfigureAwait(false); + addDeviceResult.IsSuccess.ShouldBeTrue(addDeviceResult.Message); + merchantDevices.Add((request.Item1, request.Item2, request.Item3.DeviceIdentifier)); } - foreach ((EstateDetails estate, Guid merchantId, Guid deviceId) m in merchantDevices) + foreach ((EstateDetails estate, Guid merchantId, String deviceIdentifier) m in merchantDevices) { + MerchantResponse merchant = null; + String verifiedDeviceIdentifier = null; await Retry.For(async () => { Result? getMerchantResult = await this.TransactionProcessorClient .GetMerchant(accessToken, m.estate.EstateId, m.merchantId, CancellationToken.None) .ConfigureAwait(false); getMerchantResult.IsSuccess.ShouldBeTrue(getMerchantResult.Message); - MerchantResponse merchant = getMerchantResult.Data; - KeyValuePair device = merchant.Devices.SingleOrDefault(o => o.Key == m.deviceId); + merchant = getMerchantResult.Data; + merchant.ShouldNotBeNull(); + KeyValuePair device = merchant.Devices.SingleOrDefault(o => o.Value == m.deviceIdentifier); device.Value.ShouldNotBeNull(); - result.Add((m.estate, merchant, device.Value)); + verifiedDeviceIdentifier = device.Value; }); + merchant.ShouldNotBeNull(); + verifiedDeviceIdentifier.ShouldNotBeNull(); + result.Add((m.estate, merchant, verifiedDeviceIdentifier)); } return result; @@ -994,7 +1040,24 @@ public async Task WhenIAddTheFollowingContractsToTheFollowingMerchants(String ac { ContractId = request.Item3 }; - await this.TransactionProcessorClient.AddContractToMerchant(accessToken, request.Item1.EstateId, request.Item2, addMerchantContractRequest, CancellationToken.None); + Result result = await this.TransactionProcessorClient.AddContractToMerchant(accessToken, + request.Item1.EstateId, + request.Item2, + addMerchantContractRequest, + CancellationToken.None); + result.IsSuccess.ShouldBeTrue(result.Message); + + await Retry.For(async () => + { + Result> merchantContractsResult = await this.TransactionProcessorClient + .GetMerchantContracts(accessToken, request.Item1.EstateId, request.Item2, CancellationToken.None) + .ConfigureAwait(false); + merchantContractsResult.IsSuccess.ShouldBeTrue(merchantContractsResult.Message); + + ContractResponse? contract = merchantContractsResult.Data + .SingleOrDefault(c => c.ContractId == request.Item3); + contract.ShouldNotBeNull(); + }); } } @@ -1061,6 +1124,7 @@ public async Task WhenICreateTheFollowingSecurityUsers(String accessToken, List< public async Task WhenIGetTheEstateTheEstateDetailsAreReturnedAsFollows(String accessToken, String estateName, List estateDetailsList, List expectedEstateDetails) { + expectedEstateDetails.ShouldHaveSingleItem(); Guid estateId = Guid.NewGuid(); EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); estateDetails.ShouldNotBeNull(); @@ -1075,14 +1139,18 @@ public async Task WhenIGetTheEstateTheEstateDetailsAreReturnedAsFollows(String a } } - Result? getEstateResult = await this.TransactionProcessorClient.GetEstate(token, estateId, CancellationToken.None).ConfigureAwait(false); - getEstateResult.IsSuccess.ShouldBeTrue(getEstateResult.Message); - EstateResponse estate = getEstateResult.Data; - estate.EstateName.ShouldBe(expectedEstateDetails.Single()); + await Retry.For(async () => { + Result? getEstateResult = await this.TransactionProcessorClient.GetEstate(token, estateId, CancellationToken.None).ConfigureAwait(false); + getEstateResult.IsSuccess.ShouldBeTrue(getEstateResult.Message); + EstateResponse? estate = getEstateResult.Data; + estate.ShouldNotBeNull(); + estate.EstateName.ShouldBe(expectedEstateDetails.Single()); + }); } public async Task WhenIGetAllTheOperatorsTheFollowingDetailsAreReturned(String accessToken, List<(EstateDetails, Guid, OperatorResponse)> expectedOperatorResponses) { + expectedOperatorResponses.ShouldNotBeEmpty(); IEnumerable distinctEstates = expectedOperatorResponses.Select(e => e.Item1).DistinctBy(e => e.EstateId).Select(e => e.EstateId); foreach (Guid estateId in distinctEstates) @@ -1091,10 +1159,18 @@ await Retry.For(async () => { Result>? getOperatorsResult = await this.TransactionProcessorClient.GetOperators(accessToken, estateId, CancellationToken.None); getOperatorsResult.IsSuccess.ShouldBeTrue(getOperatorsResult.Message); List? operatorList = getOperatorsResult.Data; - foreach (OperatorResponse operatorResponse in operatorList) + operatorList.ShouldNotBeNull(); + + List expectedOperators = expectedOperatorResponses + .Where(e => e.Item1.EstateId == estateId) + .Select(e => e.Item3) + .ToList(); + operatorList.Count.ShouldBe(expectedOperators.Count); + + foreach (OperatorResponse expectedOperator in expectedOperators) { - OperatorResponse? expectedOperator = expectedOperatorResponses.SingleOrDefault(s => s.Item3.OperatorId == operatorResponse.OperatorId).Item3; - expectedOperator.ShouldNotBeNull(); + OperatorResponse? operatorResponse = operatorList.SingleOrDefault(o => o.OperatorId == expectedOperator.OperatorId); + operatorResponse.ShouldNotBeNull(); operatorResponse.RequireCustomMerchantNumber.ShouldBe(expectedOperator.RequireCustomMerchantNumber); operatorResponse.RequireCustomTerminalNumber.ShouldBe(expectedOperator.RequireCustomTerminalNumber); operatorResponse.Name.ShouldBe(expectedOperator.Name); @@ -1104,6 +1180,7 @@ await Retry.For(async () => { } public async Task WhenIGetTheEstateTheEstateOperatorDetailsAreReturnedAsFollows(String accessToken, String estateName, List estateDetailsList, List expectedOperators) { + expectedOperators.ShouldNotBeEmpty(); Guid estateId = Guid.NewGuid(); EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); estateDetails.ShouldNotBeNull(); @@ -1118,19 +1195,23 @@ public async Task WhenIGetTheEstateTheEstateOperatorDetailsAreReturnedAsFollows( } } - Result>? getEstatesResult = await this.TransactionProcessorClient.GetEstates(token, estateId, CancellationToken.None).ConfigureAwait(false); - getEstatesResult.IsSuccess.ShouldBeTrue(getEstatesResult.Message); - getEstatesResult.Data.ShouldNotBeEmpty(); - EstateResponse estate = getEstatesResult.Data.Single(); - foreach (String expectedOperator in expectedOperators) - { - EstateOperatorResponse? op = estate.Operators.SingleOrDefault(o => o.Name == expectedOperator); - op.ShouldNotBeNull(); - } + await Retry.For(async () => { + Result>? getEstatesResult = await this.TransactionProcessorClient.GetEstates(token, estateId, CancellationToken.None).ConfigureAwait(false); + getEstatesResult.IsSuccess.ShouldBeTrue(getEstatesResult.Message); + getEstatesResult.Data.ShouldNotBeEmpty(); + EstateResponse estate = getEstatesResult.Data.Single(); + estate.Operators.ShouldNotBeNull(); + foreach (String expectedOperator in expectedOperators) + { + EstateOperatorResponse? op = estate.Operators.SingleOrDefault(o => o.Name == expectedOperator); + op.ShouldNotBeNull(); + } + }); } public async Task WhenIGetTheEstateTheEstateSecurityUserDetailsAreReturnedAsFollows(String accessToken, String estateName, List estateDetailsList, List expectedSecurityUsers) { + expectedSecurityUsers.ShouldNotBeEmpty(); Guid estateId = Guid.NewGuid(); EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); estateDetails.ShouldNotBeNull(); @@ -1145,15 +1226,18 @@ public async Task WhenIGetTheEstateTheEstateSecurityUserDetailsAreReturnedAsFoll } } - Result? getEstateResult = await this.TransactionProcessorClient.GetEstate(token, estateId, CancellationToken.None).ConfigureAwait(false); - getEstateResult.IsSuccess.ShouldBeTrue(getEstateResult.Message); - EstateResponse? estate = getEstateResult.Data; - estate.ShouldNotBeNull(); - foreach (String expectedSecurityUser in expectedSecurityUsers) - { - SecurityUserResponse? user = estate.SecurityUsers.SingleOrDefault(o => o.EmailAddress == expectedSecurityUser); - user.ShouldNotBeNull(); - } + await Retry.For(async () => { + Result? getEstateResult = await this.TransactionProcessorClient.GetEstate(token, estateId, CancellationToken.None).ConfigureAwait(false); + getEstateResult.IsSuccess.ShouldBeTrue(getEstateResult.Message); + EstateResponse? estate = getEstateResult.Data; + estate.ShouldNotBeNull(); + estate.SecurityUsers.ShouldNotBeNull(); + foreach (String expectedSecurityUser in expectedSecurityUsers) + { + SecurityUserResponse? user = estate.SecurityUsers.SingleOrDefault(o => o.EmailAddress == expectedSecurityUser); + user.ShouldNotBeNull(); + } + }); } public async Task WhenIGetTheEstateAnErrorIsReturned(String accessToken, String estateName, List estateDetailsList, String errorStatus) @@ -1171,7 +1255,9 @@ public async Task WhenIRemoveTheOperatorFromEstateTheOperatorIsRemoved(String ac estateDetails.ShouldNotBeNull(); Guid operatorId = estateDetails.GetOperatorId(operatorName); - await this.TransactionProcessorClient.RemoveOperatorFromEstate(accessToken, estateDetails.EstateId, operatorId, CancellationToken.None); + Result removeResult = await this.TransactionProcessorClient + .RemoveOperatorFromEstate(accessToken, estateDetails.EstateId, operatorId, CancellationToken.None); + removeResult.IsSuccess.ShouldBeTrue(removeResult.Message); await Retry.For(async () => { Result? getEstateResult = await this.TransactionProcessorClient.GetEstate(accessToken, estateDetails.EstateId, CancellationToken.None); @@ -1191,23 +1277,29 @@ public async Task> WhenIUpdateTheOperatorsWithTheFollowin foreach ((EstateDetails estate, Guid operatorId, UpdateOperatorRequest request) request in requests) { - await this.TransactionProcessorClient.UpdateOperator(accessToken, request.estate.EstateId, request.operatorId, request.request, CancellationToken.None).ConfigureAwait(false); + Result updateResult = await this.TransactionProcessorClient + .UpdateOperator(accessToken, request.estate.EstateId, request.operatorId, request.request, CancellationToken.None) + .ConfigureAwait(false); + updateResult.IsSuccess.ShouldBeTrue(updateResult.Message); } foreach ((EstateDetails estate, Guid operatorId, UpdateOperatorRequest request) request in requests) { + OperatorResponse operatorResponse = null; await Retry.For(async () => { Result? getOperatorResult = await this.TransactionProcessorClient .GetOperator(accessToken, request.estate.EstateId, request.operatorId, CancellationToken.None) .ConfigureAwait(false); getOperatorResult.IsSuccess.ShouldBeTrue(getOperatorResult.Message); - OperatorResponse? operatorResponse = getOperatorResult.Data; + operatorResponse = getOperatorResult.Data; + operatorResponse.ShouldNotBeNull(); operatorResponse.Name.ShouldBe(request.request.Name); operatorResponse.RequireCustomTerminalNumber.ShouldBe(request.request.RequireCustomTerminalNumber.Value); operatorResponse.RequireCustomMerchantNumber.ShouldBe(request.request.RequireCustomMerchantNumber.Value); - responses.Add(operatorResponse); }); + operatorResponse.ShouldNotBeNull(); + responses.Add(operatorResponse); request.estate.UpdateOperator(request.operatorId, request.request.Name); } @@ -1216,6 +1308,7 @@ await Retry.For(async () => { public async Task ThenIGetTheContractsForTheFollowingContractDetailsAreReturned(String accessToken, String estateName, List estateDetailsList, List<(String, String)> contractDetails) { + contractDetails.ShouldNotBeEmpty(); Guid estateId = Guid.NewGuid(); EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); @@ -1245,11 +1338,13 @@ await Retry.For(async () => { contract.Description.ShouldBe(contractDetail.Item1); contract.Products.Any(p => p.Name == contractDetail.Item2).ShouldBeTrue(); } + contract.Products.Count.ShouldBe(contractDetails.Select(c => c.Item2).Distinct().Count()); }); } public async Task ThenIGetTheMerchantContractsForForTheFollowingContractDetailsAreReturned(String accessToken, String estateName, String merchantName, List estateDetailsList, List<(String, String)> contractDetails) { + contractDetails.ShouldNotBeEmpty(); Guid estateId = Guid.NewGuid(); EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); estateDetails.ShouldNotBeNull(); @@ -1275,17 +1370,20 @@ await Retry.For(async () => { contracts.ShouldNotBeNull(); contracts.ShouldHaveSingleItem(); ContractResponse contractResponse = contracts.Single(); + contractResponse.Products.ShouldNotBeNull(); foreach ((String, String) contract in contractDetails) { contractResponse.Description.ShouldBe(contract.Item1); contractResponse.Products.Any(p => p.Name == contract.Item2).ShouldBeTrue(); } + contractResponse.Products.Count.ShouldBe(contractDetails.Select(c => c.Item2).Distinct().Count()); }); } public async Task ThenIGetTheTransactionFeesForOnTheContractForTheFollowingFeesAreReturned(String accessToken, String estateName, String contractName, String productName, List estateDetailsList, List<(CalculationType, String, Decimal?, FeeType)> transactionFees) { + transactionFees.ShouldNotBeEmpty(); Guid estateId = Guid.NewGuid(); EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); estateDetails.ShouldNotBeNull(); @@ -1314,7 +1412,9 @@ await this.TransactionProcessorClient.GetTransactionFeesForProduct(token, CancellationToken.None); transactionFeesResult.IsSuccess.ShouldBeTrue(transactionFeesResult.Message); - List transactionFeesList = transactionFeesResult.Data; + List? transactionFeesList = transactionFeesResult.Data; + transactionFeesList.ShouldNotBeNull(); + transactionFeesList.Count.ShouldBe(transactionFees.Count); foreach ((CalculationType, String, Decimal?, FeeType) transactionFee in transactionFees) { @@ -1342,17 +1442,26 @@ public async Task WhenIUpdateTheMerchantsAddressWithTheFollowingDetails(String a { foreach ((EstateDetails, MerchantResponse, Guid, Address) addressUpdate in addressUpdatesList) { - await this.TransactionProcessorClient.UpdateMerchantAddress(accessToken, addressUpdate.Item1.EstateId, addressUpdate.Item2.MerchantId, addressUpdate.Item3, addressUpdate.Item4, CancellationToken.None); + Result updateResult = await this.TransactionProcessorClient + .UpdateMerchantAddress(accessToken, + addressUpdate.Item1.EstateId, + addressUpdate.Item2.MerchantId, + addressUpdate.Item3, + addressUpdate.Item4, + CancellationToken.None); + updateResult.IsSuccess.ShouldBeTrue(updateResult.Message); } foreach ((EstateDetails, MerchantResponse, Guid, Address) addressVerify in addressUpdatesList) { await Retry.For(async () => { Result? getMerchantResult = await this.TransactionProcessorClient.GetMerchant(accessToken, addressVerify.Item1.EstateId, addressVerify.Item2.MerchantId, CancellationToken.None); + getMerchantResult.IsSuccess.ShouldBeTrue(getMerchantResult.Message); MerchantResponse? merchant = getMerchantResult.Data; merchant.ShouldNotBeNull(); - AddressResponse? address = merchant.Addresses.First(); + AddressResponse? address = merchant.Addresses.SingleOrDefault(a => a.AddressId == addressVerify.Item3); + address.ShouldNotBeNull(); address.AddressLine1.ShouldBe(addressVerify.Item4.AddressLine1); address.AddressLine2.ShouldBe(addressVerify.Item4.AddressLine2); address.AddressLine3.ShouldBe(addressVerify.Item4.AddressLine3); @@ -1369,7 +1478,14 @@ public async Task WhenIUpdateTheMerchantsContactWithTheFollowingDetails(String a { foreach ((EstateDetails, MerchantResponse, Guid, Contact) contactUpdate in contactUpdatesList) { - await this.TransactionProcessorClient.UpdateMerchantContact(accessToken, contactUpdate.Item1.EstateId, contactUpdate.Item2.MerchantId, contactUpdate.Item3, contactUpdate.Item4, CancellationToken.None); + Result updateResult = await this.TransactionProcessorClient + .UpdateMerchantContact(accessToken, + contactUpdate.Item1.EstateId, + contactUpdate.Item2.MerchantId, + contactUpdate.Item3, + contactUpdate.Item4, + CancellationToken.None); + updateResult.IsSuccess.ShouldBeTrue(updateResult.Message); } foreach ((EstateDetails, MerchantResponse, Guid, Contact) contactVerify in contactUpdatesList) @@ -1380,7 +1496,8 @@ await Retry.For(async () => { MerchantResponse? merchant = getMerchantResult.Data; merchant.ShouldNotBeNull(); - ContactResponse? contact = merchant.Contacts.First(); + ContactResponse? contact = merchant.Contacts.SingleOrDefault(c => c.ContactId == contactVerify.Item3); + contact.ShouldNotBeNull(); contact.ContactEmailAddress.ShouldBe(contactVerify.Item4.EmailAddress); contact.ContactPhoneNumber.ShouldBe(contactVerify.Item4.PhoneNumber); contact.ContactName.ShouldBe(contactVerify.Item4.ContactName); @@ -1399,7 +1516,13 @@ public async Task WhenIRemoveTheContractFromMerchantOnTheContractIsRemoved(Strin //MerchantContractResponse? contract = merchant.Contracts.SingleOrDefault(c => c.ContractId == estateContract.ContractId); //contract.ShouldNotBeNull(); - await this.TransactionProcessorClient.RemoveContractFromMerchant(accessToken, estateDetails.EstateId, merchant.MerchantId, estateContract.ContractId, CancellationToken.None); + Result removeResult = await this.TransactionProcessorClient + .RemoveContractFromMerchant(accessToken, + estateDetails.EstateId, + merchant.MerchantId, + estateContract.ContractId, + CancellationToken.None); + removeResult.IsSuccess.ShouldBeTrue(removeResult.Message); await Retry.For(async () => { Result? getMerchantResult = await this.TransactionProcessorClient.GetMerchant(accessToken, estateDetails.EstateId, merchant.MerchantId, CancellationToken.None); @@ -1420,7 +1543,13 @@ public async Task WhenIRemoveTheOperatorFromMerchantOnTheOperatorIsRemoved(Strin Guid operatorId = estateDetails.GetOperatorId(operatorName); MerchantResponse merchant = estateDetails.GetMerchant(merchantName); - await this.TransactionProcessorClient.RemoveOperatorFromMerchant(accessToken, estateDetails.EstateId, merchant.MerchantId, operatorId, CancellationToken.None); + Result removeResult = await this.TransactionProcessorClient + .RemoveOperatorFromMerchant(accessToken, + estateDetails.EstateId, + merchant.MerchantId, + operatorId, + CancellationToken.None); + removeResult.IsSuccess.ShouldBeTrue(removeResult.Message); await Retry.For(async () => { var getMerchantResult = await this.TransactionProcessorClient.GetMerchant(accessToken, estateDetails.EstateId, merchant.MerchantId, CancellationToken.None); diff --git a/TransactionProcessor.IntegrationTests/Common/Retry.cs b/TransactionProcessor.IntegrationTests/Common/Retry.cs index fa792cd9..e32f0ca5 100644 --- a/TransactionProcessor.IntegrationTests/Common/Retry.cs +++ b/TransactionProcessor.IntegrationTests/Common/Retry.cs @@ -2,7 +2,7 @@ namespace TransactionProcessor.IntegrationTests { - using System.Threading; + using System.Diagnostics; using System.Threading.Tasks; public static class Retry @@ -34,34 +34,31 @@ public static async Task For(Func action, TimeSpan? retryFor = null, TimeSpan? retryInterval = null) { - DateTime startTime = DateTime.Now; - Exception lastException = null; + TimeSpan retryDuration = retryFor ?? Retry.DefaultRetryFor; + TimeSpan retryDelay = retryInterval ?? Retry.DefaultRetryInterval; + Stopwatch stopwatch = Stopwatch.StartNew(); + Exception lastException; - if (retryFor == null) - { - retryFor = Retry.DefaultRetryFor; - } - - while (DateTime.Now.Subtract(startTime).TotalMilliseconds < retryFor.Value.TotalMilliseconds) + while (true) { try { await action().ConfigureAwait(false); - lastException = null; - break; + return; } catch (Exception e) { lastException = e; + } - // wait before retrying - Thread.Sleep(retryInterval ?? Retry.DefaultRetryInterval); + TimeSpan remaining = retryDuration - stopwatch.Elapsed; + if (remaining <= TimeSpan.Zero) + { + throw lastException; } - } - if (lastException != null) - { - throw lastException; + TimeSpan delay = retryDelay < remaining ? retryDelay : remaining; + await Task.Delay(delay).ConfigureAwait(false); } } diff --git a/TransactionProcessor.IntegrationTests/Common/RetryTests.cs b/TransactionProcessor.IntegrationTests/Common/RetryTests.cs new file mode 100644 index 00000000..b300f617 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Common/RetryTests.cs @@ -0,0 +1,25 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using NUnit.Framework; +using Shouldly; + +namespace TransactionProcessor.IntegrationTests.Common; + +public class RetryTests +{ + [Test] + public async Task For_DoesNotBlockWhileWaitingToRetry() + { + Stopwatch stopwatch = Stopwatch.StartNew(); + + Task retryTask = Retry.For( + () => Task.FromException(new InvalidOperationException("expected failure")), + retryFor: TimeSpan.FromMilliseconds(50), + retryInterval: TimeSpan.FromSeconds(1)); + + stopwatch.Elapsed.ShouldBeLessThan(TimeSpan.FromMilliseconds(500)); + + await Should.ThrowAsync(async () => await retryTask); + } +} diff --git a/TransactionProcessor.IntegrationTests/Common/SpecflowExtensionsTests.cs b/TransactionProcessor.IntegrationTests/Common/SpecflowExtensionsTests.cs new file mode 100644 index 00000000..847548e6 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Common/SpecflowExtensionsTests.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Shouldly; +using TransactionProcessor.DataTransferObjects; +using TransactionProcessor.DataTransferObjects.Responses.Contract; +using TransactionProcessor.DataTransferObjects.Responses.Merchant; +using TransactionProcessor.IntegrationTesting.Helpers; + +namespace TransactionProcessor.IntegrationTests.Common; + +public class SpecflowExtensionsTests +{ + [Test] + public void ToSettlementFeeDetails_ReturnsEveryFeeRow() + { + EstateDetails estate = EstateDetails.Create(Guid.NewGuid(), "Test Estate", "estate-reference"); + estate.AddMerchant(new MerchantResponse + { + MerchantId = Guid.NewGuid(), + MerchantName = "Test Merchant" + }); + + Reqnroll.Table table = new(new[] { "FeeDescription", "IsSettled", "Operator", "CalculatedValue" }); + table.AddRow(new[] { "Commission", "True", "Operator 1", "0.50" }); + table.AddRow(new[] { "Commission", "False", "Operator 2", "0.25" }); + + List result = table.Rows.ToSettlementFeeDetails( + "Test Estate", + "Test Merchant", + "2026-01-13", + new List { estate }); + + result.Count.ShouldBe(2); + result[0].FeeDescription.ShouldBe("Commission"); + result[0].IsSettled.ShouldBeTrue(); + result[0].CalculatedValue.ShouldBe(0.50m); + result[1].IsSettled.ShouldBeFalse(); + result[1].CalculatedValue.ShouldBe(0.25m); + } + + [Test] + public void ToSettlementDetails_RejectsMultipleRows() + { + EstateDetails estate = EstateDetails.Create(Guid.NewGuid(), "Test Estate", "estate-reference"); + estate.AddMerchant(new MerchantResponse + { + MerchantId = Guid.NewGuid(), + MerchantName = "Test Merchant" + }); + + Reqnroll.Table table = new(new[] { "SettlementDate", "NumberOfFeesSettled", "ValueOfFeesSettled", "IsCompleted" }); + table.AddRow(new[] { "2026-01-13", "1", "1.00", "False" }); + table.AddRow(new[] { "2026-01-14", "2", "2.00", "True" }); + + Should.Throw(() => table.Rows.ToSettlementDetails("Test Estate", new List { estate })); + Should.Throw(() => table.Rows.ToSettlementDetails("Test Estate", "Test Merchant", new List { estate })); + } + + [Test] + public void TestingContext_AddVoucher_DoesNotThrow() + { + TestingContext context = new(); + + Should.NotThrow(() => context.AddVoucher((new IssueVoucherRequest(), new IssueVoucherResponse()))); + } + + [Test] + public void EstateDetails_AddMerchant_RejectsDuplicateMerchantId() + { + EstateDetails estate = EstateDetails.Create(Guid.NewGuid(), "Test Estate", "estate-reference"); + Guid merchantId = Guid.NewGuid(); + + estate.AddMerchant(new MerchantResponse { MerchantId = merchantId, MerchantName = "Merchant One" }); + + Should.Throw(() => estate.AddMerchant( + new MerchantResponse { MerchantId = merchantId, MerchantName = "Merchant Two" })); + } + + [Test] + public void ToTransactionRequests_UnknownContractName_FailsFast() + { + EstateDetails estate = CreateTransactionEstate(); + Reqnroll.Table table = CreateSaleTransactionTable("Unknown Contract", "Known Product"); + + Should.Throw(() => table.Rows.ToTransactionRequests(new List { estate })) + .Message.ShouldContain("Unknown Contract"); + } + + [Test] + public void ToTransactionRequests_UnknownProductName_FailsFast() + { + EstateDetails estate = CreateTransactionEstate(); + Reqnroll.Table table = CreateSaleTransactionTable("Known Contract", "Unknown Product"); + + Should.Throw(() => table.Rows.ToTransactionRequests(new List { estate })) + .Message.ShouldContain("Unknown Product"); + } + + [Test] + public void ToAddressUpdates_ProcessesEachTableRowOnce() + { + EstateDetails estate = EstateDetails.Create(Guid.NewGuid(), "Test Estate", "estate-reference"); + MerchantResponse merchantOne = CreateMerchant("Merchant One"); + MerchantResponse merchantTwo = CreateMerchant("Merchant Two"); + estate.AddMerchant(merchantOne); + estate.AddMerchant(merchantTwo); + + Reqnroll.Table table = new(new[] { "EstateName", "MerchantName", "AddressLine1" }); + table.AddRow(new[] { "Test Estate", "Merchant One", "First address" }); + table.AddRow(new[] { "Test Estate", "Merchant Two", "Second address" }); + + List<(EstateDetails, MerchantResponse, Guid, TransactionProcessor.DataTransferObjects.Requests.Merchant.Address)> result = + table.Rows.ToAddressUpdates(new List { estate }); + + result.Count.ShouldBe(2); + result[0].Item2.MerchantName.ShouldBe("Merchant One"); + result[0].Item4.AddressLine1.ShouldBe("First address"); + result[1].Item2.MerchantName.ShouldBe("Merchant Two"); + result[1].Item4.AddressLine1.ShouldBe("Second address"); + } + + [Test] + public void ToContactUpdates_ProcessesEachTableRowOnce() + { + EstateDetails estate = EstateDetails.Create(Guid.NewGuid(), "Test Estate", "estate-reference"); + MerchantResponse merchantOne = CreateMerchant("Merchant One"); + MerchantResponse merchantTwo = CreateMerchant("Merchant Two"); + estate.AddMerchant(merchantOne); + estate.AddMerchant(merchantTwo); + + Reqnroll.Table table = new(new[] { "EstateName", "MerchantName", "ContactName", "EmailAddress", "PhoneNumber" }); + table.AddRow(new[] { "Test Estate", "Merchant One", "First contact", "first@example.com", "111" }); + table.AddRow(new[] { "Test Estate", "Merchant Two", "Second contact", "second@example.com", "222" }); + + List<(EstateDetails, MerchantResponse, Guid, TransactionProcessor.DataTransferObjects.Requests.Merchant.Contact)> result = + table.Rows.ToContactUpdates(new List { estate }); + + result.Count.ShouldBe(2); + result[0].Item2.MerchantName.ShouldBe("Merchant One"); + result[0].Item4.ContactName.ShouldBe("First contact"); + result[1].Item2.MerchantName.ShouldBe("Merchant Two"); + result[1].Item4.ContactName.ShouldBe("Second contact"); + } + + [Test] + public void ToAddressUpdates_RejectsAmbiguousExistingAddresses() + { + EstateDetails estate = EstateDetails.Create(Guid.NewGuid(), "Test Estate", "estate-reference"); + MerchantResponse merchant = CreateMerchant("Test Merchant"); + merchant.Addresses.Add(new AddressResponse { AddressId = Guid.NewGuid() }); + estate.AddMerchant(merchant); + + Reqnroll.Table table = new(new[] { "EstateName", "MerchantName", "AddressLine1" }); + table.AddRow(new[] { "Test Estate", "Test Merchant", "Updated address" }); + + Should.Throw(() => table.Rows.ToAddressUpdates(new List { estate })); + } + + [Test] + public void ToContactUpdates_RejectsAmbiguousExistingContacts() + { + EstateDetails estate = EstateDetails.Create(Guid.NewGuid(), "Test Estate", "estate-reference"); + MerchantResponse merchant = CreateMerchant("Test Merchant"); + merchant.Contacts.Add(new ContactResponse { ContactId = Guid.NewGuid() }); + estate.AddMerchant(merchant); + + Reqnroll.Table table = new(new[] { "EstateName", "MerchantName", "ContactName" }); + table.AddRow(new[] { "Test Estate", "Test Merchant", "Updated contact" }); + + Should.Throw(() => table.Rows.ToContactUpdates(new List { estate })); + } + + private static MerchantResponse CreateMerchant(String merchantName) + { + return new MerchantResponse + { + MerchantId = Guid.NewGuid(), + MerchantName = merchantName, + Addresses = new List { new() { AddressId = Guid.NewGuid() } }, + Contacts = new List { new() { ContactId = Guid.NewGuid() } } + }; + } + + private static EstateDetails CreateTransactionEstate() + { + EstateDetails estate = EstateDetails.Create(Guid.NewGuid(), "Test Estate", "estate-reference"); + estate.AddMerchant(new MerchantResponse + { + MerchantId = Guid.NewGuid(), + MerchantName = "Test Merchant" + }); + estate.AddOperator(Guid.NewGuid(), "Test Operator"); + estate.AddContract(Guid.NewGuid(), "Known Contract", Guid.NewGuid()); + + TransactionProcessor.IntegrationTesting.Helpers.Contract contract = estate.GetContract("Known Contract"); + contract.AddProduct(Guid.NewGuid(), "Known Product", "Known Product"); + + return estate; + } + + private static Reqnroll.Table CreateSaleTransactionTable(String contractName, String productName) + { + Reqnroll.Table table = new(new[] + { + "TransactionType", "DateTime", "TransactionNumber", "DeviceIdentifier", "EstateName", "MerchantName", + "OperatorName", "TransactionAmount", "ContractDescription", "ProductName", "TransactionSource" + }); + table.AddRow(new[] + { + "Sale", "2026-01-13", "transaction-1", "device-1", "Test Estate", "Test Merchant", "Test Operator", + "10.00", contractName, productName, "1" + }); + + return table; + } +} diff --git a/TransactionProcessor.IntegrationTests/Common/TestingContext.cs b/TransactionProcessor.IntegrationTests/Common/TestingContext.cs index 9f21bef4..78ba120f 100644 --- a/TransactionProcessor.IntegrationTests/Common/TestingContext.cs +++ b/TransactionProcessor.IntegrationTests/Common/TestingContext.cs @@ -43,6 +43,7 @@ public TestingContext() { this.Estates = new List(); this.Clients = new List(); + this.Vouchers = new List<(IssueVoucherRequest request, IssueVoucherResponse response)>(); } #endregion @@ -210,4 +211,4 @@ await Retry.For(async () => { return voucher; } } -} \ No newline at end of file +} diff --git a/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs b/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs index d712e176..7af463a0 100644 --- a/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs +++ b/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs @@ -446,6 +446,7 @@ public async Task GivenTheFollowingSecurityRolesExist(DataTable table) [Given(@"I have a token to access the estate management and transaction processor resources")] public async Task GivenIHaveATokenToAccessTheEstateManagementResource(DataTable table) { + table.Rows.Count.ShouldBe(1, "Token setup requires exactly one client row."); DataTableRow firstRow = table.Rows.First(); String clientId = ReqnrollTableHelper.GetStringRowValue(firstRow, "ClientId"); ClientDetails clientDetails = this.TestingContext.GetClientDetails(clientId); @@ -453,6 +454,8 @@ public async Task GivenIHaveATokenToAccessTheEstateManagementResource(DataTable //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(token.Message); + token.Data.ShouldNotBeNull(); + token.Data.AccessToken.ShouldNotBeNullOrWhiteSpace(); this.TestingContext.AccessToken = token.Data.AccessToken; } @@ -518,6 +521,8 @@ public async Task GivenIAmLoggedInAsWithPasswordForEstate(String username, Result token = await this.TestingContext.DockerHelper.SecurityServiceClient.GetToken(username, password, clientId, clientDetails.ClientSecret, CancellationToken.None); token.IsSuccess.ShouldBeTrue(token.Message); + token.Data.ShouldNotBeNull(); + token.Data.AccessToken.ShouldNotBeNullOrWhiteSpace(); this.TestingContext.AccessToken = token.Data.AccessToken; estateDetails.SetEstateUserToken(token.Data.AccessToken); } From 6c65c7c5aa9ed274da83dd4949534329170c882a Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Mon, 12 Oct 2026 18:33:26 +0100 Subject: [PATCH 2/6] Match transaction fees by full identity --- .../TransactionFeeMatcher.cs | 19 +++++++++ .../TransactionProcessorSteps.cs | 2 +- .../Common/SpecflowExtensionsTests.cs | 39 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 TransactionProcessor.IntegrationTesting.Helpers/TransactionFeeMatcher.cs diff --git a/TransactionProcessor.IntegrationTesting.Helpers/TransactionFeeMatcher.cs b/TransactionProcessor.IntegrationTesting.Helpers/TransactionFeeMatcher.cs new file mode 100644 index 00000000..4e0bcdb4 --- /dev/null +++ b/TransactionProcessor.IntegrationTesting.Helpers/TransactionFeeMatcher.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; +using TransactionProcessor.DataTransferObjects.Requests.Contract; +using TransactionProcessor.DataTransferObjects.Responses.Contract; + +namespace TransactionProcessor.IntegrationTesting.Helpers; + +public static class TransactionFeeMatcher +{ + public static ContractProductTransactionFee? Find( + IEnumerable fees, + AddTransactionFeeForProductToContractRequest request) + { + return fees.SingleOrDefault(fee => fee.Description == request.Description && + fee.CalculationType == request.CalculationType && + fee.FeeType == request.FeeType && + fee.Value == request.Value); + } +} diff --git a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs index 55235743..26714ed1 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs @@ -964,7 +964,7 @@ await Retry.For(async () => { ContractProduct product = contract.Products.SingleOrDefault(c => c.ProductId == estateContractProductsFee.Item3.ProductId); product.ShouldNotBeNull(); - ContractProductTransactionFee? transactionFee = product.TransactionFees.SingleOrDefault(f => f.Description == estateContractProductsFee.Item4.Description); + ContractProductTransactionFee? transactionFee = TransactionFeeMatcher.Find(product.TransactionFees, estateContractProductsFee.Item4); transactionFee.ShouldNotBeNull(); estateContractProductsFee.Item3.AddTransactionFee(transactionFee.TransactionFeeId, diff --git a/TransactionProcessor.IntegrationTests/Common/SpecflowExtensionsTests.cs b/TransactionProcessor.IntegrationTests/Common/SpecflowExtensionsTests.cs index 847548e6..13fe4284 100644 --- a/TransactionProcessor.IntegrationTests/Common/SpecflowExtensionsTests.cs +++ b/TransactionProcessor.IntegrationTests/Common/SpecflowExtensionsTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using NUnit.Framework; using Shouldly; +using TransactionProcessor.DataTransferObjects.Requests.Contract; using TransactionProcessor.DataTransferObjects; using TransactionProcessor.DataTransferObjects.Responses.Contract; using TransactionProcessor.DataTransferObjects.Responses.Merchant; @@ -171,6 +172,44 @@ public void ToContactUpdates_RejectsAmbiguousExistingContacts() Should.Throw(() => table.Rows.ToContactUpdates(new List { estate })); } + [Test] + public void TransactionFeeMatcher_MatchesDuplicateDescriptionsByFullIdentity() + { + Guid expectedTransactionFeeId = Guid.NewGuid(); + AddTransactionFeeForProductToContractRequest request = new() + { + CalculationType = CalculationType.Percentage, + FeeType = FeeType.Merchant, + Description = "Merchant Commission", + Value = 0.85m + }; + + List fees = new() + { + new ContractProductTransactionFee + { + TransactionFeeId = Guid.NewGuid(), + CalculationType = CalculationType.Percentage, + FeeType = FeeType.Merchant, + Description = "Merchant Commission", + Value = 0.50m + }, + new ContractProductTransactionFee + { + TransactionFeeId = expectedTransactionFeeId, + CalculationType = CalculationType.Percentage, + FeeType = FeeType.Merchant, + Description = "Merchant Commission", + Value = 0.85m + } + }; + + ContractProductTransactionFee? result = TransactionFeeMatcher.Find(fees, request); + + result.ShouldNotBeNull(); + result.TransactionFeeId.ShouldBe(expectedTransactionFeeId); + } + private static MerchantResponse CreateMerchant(String merchantName) { return new MerchantResponse From a802e569aee771c32b291496b36773870b8aa0d6 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Mon, 12 Oct 2026 18:59:43 +0100 Subject: [PATCH 3/6] Harden integration test failure handling --- .../SpecflowExtensions.cs | 11 +++++- .../TransactionProcessorSteps.cs | 36 ++++++++++++++----- .../Common/SpecflowExtensionsTests.cs | 28 +++++++++++++-- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs b/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs index c08e6a72..1389e4e3 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs @@ -519,7 +519,16 @@ public static List ToAutomaticDepositRequests(this DataTableRows tableRo Guid productId = Guid.Empty; Contract contract = null; - if (contractDescription == "EmptyContract") + // InvalidEstate is a deliberate negative-test fixture. It has enough metadata to + // build the request, but it intentionally has no real contracts or products. + // Keep this explicit exception local to that fixture; unknown names on normal + // estates must still fail fast instead of being converted to synthetic IDs. + if (estateDetails.EstateName == "InvalidEstate") + { + contractId = Guid.NewGuid(); + productId = Guid.NewGuid(); + } + else if (contractDescription == "EmptyContract") { contractId = Guid.Empty; } diff --git a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs index 26714ed1..4febe662 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs @@ -554,13 +554,23 @@ await Retry.For(async () => public async Task WhenIProcessTheSettlementForOnEstateThenFeesAreMarkedAsSettledAndTheSettlementIsCompleted(String accessToken, ReqnrollExtensions.ProcessSettlementRequest request, Int32 expectedNumberFeesSettled) { - var result = await this.TransactionProcessorClient.ProcessSettlement(accessToken, - request.SettlementDate, - request.EstateDetails.EstateId, - request.MerchantId, - CancellationToken.None); - - result.IsSuccess.ShouldBeTrue(result.Message); + // Processing an already-completed settlement is a no-op, so retrying the command is + // safe when the first request fails before the HTTP client can determine its outcome. + await Retry.For(async () => + { + var result = await this.TransactionProcessorClient.ProcessSettlement(accessToken, + request.SettlementDate, + request.EstateDetails.EstateId, + request.MerchantId, + CancellationToken.None); + + result.IsSuccess.ShouldBeTrue( + $"Failed to process settlement for estate '{request.EstateDetails.EstateId}', " + + $"merchant '{request.MerchantId}', date '{request.SettlementDate:yyyy-MM-dd}'. " + + result.Message); + }, + TimeSpan.FromMinutes(2), + TimeSpan.FromSeconds(5)); await Retry.For(async () => { Result? getSettlementByDateResult = @@ -1052,11 +1062,19 @@ await Retry.For(async () => Result> merchantContractsResult = await this.TransactionProcessorClient .GetMerchantContracts(accessToken, request.Item1.EstateId, request.Item2, CancellationToken.None) .ConfigureAwait(false); - merchantContractsResult.IsSuccess.ShouldBeTrue(merchantContractsResult.Message); + merchantContractsResult.IsSuccess.ShouldBeTrue( + $"Unable to read contracts for estate '{request.Item1.EstateId}' and merchant '{request.Item2}' " + + $"after adding contract '{request.Item3}'. {merchantContractsResult.Message}"); + + merchantContractsResult.Data.ShouldNotBeNull( + $"The merchant-contract read model returned no data for estate '{request.Item1.EstateId}' " + + $"and merchant '{request.Item2}' after adding contract '{request.Item3}'."); ContractResponse? contract = merchantContractsResult.Data .SingleOrDefault(c => c.ContractId == request.Item3); - contract.ShouldNotBeNull(); + contract.ShouldNotBeNull( + $"Contract '{request.Item3}' was not present in the merchant-contract read model for " + + $"estate '{request.Item1.EstateId}' and merchant '{request.Item2}'."); }); } } diff --git a/TransactionProcessor.IntegrationTests/Common/SpecflowExtensionsTests.cs b/TransactionProcessor.IntegrationTests/Common/SpecflowExtensionsTests.cs index 13fe4284..d34f6489 100644 --- a/TransactionProcessor.IntegrationTests/Common/SpecflowExtensionsTests.cs +++ b/TransactionProcessor.IntegrationTests/Common/SpecflowExtensionsTests.cs @@ -98,6 +98,25 @@ public void ToTransactionRequests_UnknownProductName_FailsFast() .Message.ShouldContain("Unknown Product"); } + [Test] + public void ToTransactionRequests_InvalidEstate_PreservesInvalidEstateRequest() + { + EstateDetails estate = CreateTransactionEstate(); + Reqnroll.Table table = CreateSaleTransactionTable( + "Known Contract", + "Known Product", + "InvalidEstate", + "Test Merchant 1", + "Safaricom"); + + List<(EstateDetails, Guid, String, TransactionRequest)> result = + table.Rows.ToTransactionRequests(new List { estate }); + + result.ShouldHaveSingleItem(); + result[0].Item1.EstateName.ShouldBe("InvalidEstate"); + result[0].Item1.EstateId.ShouldBe(Guid.Parse("79902550-64DF-4491-B0C1-4E78943928A3")); + } + [Test] public void ToAddressUpdates_ProcessesEachTableRowOnce() { @@ -238,7 +257,12 @@ private static EstateDetails CreateTransactionEstate() return estate; } - private static Reqnroll.Table CreateSaleTransactionTable(String contractName, String productName) + private static Reqnroll.Table CreateSaleTransactionTable( + String contractName, + String productName, + String estateName = "Test Estate", + String merchantName = "Test Merchant", + String operatorName = "Test Operator") { Reqnroll.Table table = new(new[] { @@ -247,7 +271,7 @@ private static Reqnroll.Table CreateSaleTransactionTable(String contractName, St }); table.AddRow(new[] { - "Sale", "2026-01-13", "transaction-1", "device-1", "Test Estate", "Test Merchant", "Test Operator", + "Sale", "2026-01-13", "transaction-1", "device-1", estateName, merchantName, operatorName, "10.00", contractName, productName, "1" }); From f6faff7c943534e1a8310634edcadb21a584da8a Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Mon, 12 Oct 2026 19:46:04 +0100 Subject: [PATCH 4/6] Verify merchant contract assignments from merchant state --- .../TransactionProcessorSteps.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs index 4febe662..b4d14985 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs @@ -1059,21 +1059,26 @@ public async Task WhenIAddTheFollowingContractsToTheFollowingMerchants(String ac await Retry.For(async () => { - Result> merchantContractsResult = await this.TransactionProcessorClient - .GetMerchantContracts(accessToken, request.Item1.EstateId, request.Item2, CancellationToken.None) + Result merchantResult = await this.TransactionProcessorClient + .GetMerchant(accessToken, request.Item1.EstateId, request.Item2, CancellationToken.None) .ConfigureAwait(false); - merchantContractsResult.IsSuccess.ShouldBeTrue( - $"Unable to read contracts for estate '{request.Item1.EstateId}' and merchant '{request.Item2}' " + - $"after adding contract '{request.Item3}'. {merchantContractsResult.Message}"); + merchantResult.IsSuccess.ShouldBeTrue( + $"Unable to read merchant '{request.Item2}' in estate '{request.Item1.EstateId}' " + + $"after adding contract '{request.Item3}'. {merchantResult.Message}"); + + MerchantResponse? merchant = merchantResult.Data; + merchant.ShouldNotBeNull( + $"The merchant response returned no data for estate '{request.Item1.EstateId}' " + + $"and merchant '{request.Item2}' after adding contract '{request.Item3}'."); - merchantContractsResult.Data.ShouldNotBeNull( - $"The merchant-contract read model returned no data for estate '{request.Item1.EstateId}' " + + merchant.Contracts.ShouldNotBeNull( + $"The merchant response returned no contracts for estate '{request.Item1.EstateId}' " + $"and merchant '{request.Item2}' after adding contract '{request.Item3}'."); - ContractResponse? contract = merchantContractsResult.Data - .SingleOrDefault(c => c.ContractId == request.Item3); + MerchantContractResponse? contract = merchant.Contracts + .SingleOrDefault(c => c.ContractId == request.Item3 && c.IsDeleted == false); contract.ShouldNotBeNull( - $"Contract '{request.Item3}' was not present in the merchant-contract read model for " + + $"Contract '{request.Item3}' was not assigned to merchant '{request.Item2}' in " + $"estate '{request.Item1.EstateId}' and merchant '{request.Item2}'."); }); } From 2a7c0497ec2dca9590156806b4bb1014a97bc9fc Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Mon, 12 Oct 2026 19:47:24 +0100 Subject: [PATCH 5/6] Add product to merchant contract fixture --- .../TransactionProcessorSteps.cs | 25 +- .../Features/Merchant.feature | 3 + .../Features/Merchant.feature.cs | 333 +++++++++--------- 3 files changed, 189 insertions(+), 172 deletions(-) diff --git a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs index b4d14985..4febe662 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs @@ -1059,26 +1059,21 @@ public async Task WhenIAddTheFollowingContractsToTheFollowingMerchants(String ac await Retry.For(async () => { - Result merchantResult = await this.TransactionProcessorClient - .GetMerchant(accessToken, request.Item1.EstateId, request.Item2, CancellationToken.None) + Result> merchantContractsResult = await this.TransactionProcessorClient + .GetMerchantContracts(accessToken, request.Item1.EstateId, request.Item2, CancellationToken.None) .ConfigureAwait(false); - merchantResult.IsSuccess.ShouldBeTrue( - $"Unable to read merchant '{request.Item2}' in estate '{request.Item1.EstateId}' " + - $"after adding contract '{request.Item3}'. {merchantResult.Message}"); - - MerchantResponse? merchant = merchantResult.Data; - merchant.ShouldNotBeNull( - $"The merchant response returned no data for estate '{request.Item1.EstateId}' " + - $"and merchant '{request.Item2}' after adding contract '{request.Item3}'."); + merchantContractsResult.IsSuccess.ShouldBeTrue( + $"Unable to read contracts for estate '{request.Item1.EstateId}' and merchant '{request.Item2}' " + + $"after adding contract '{request.Item3}'. {merchantContractsResult.Message}"); - merchant.Contracts.ShouldNotBeNull( - $"The merchant response returned no contracts for estate '{request.Item1.EstateId}' " + + merchantContractsResult.Data.ShouldNotBeNull( + $"The merchant-contract read model returned no data for estate '{request.Item1.EstateId}' " + $"and merchant '{request.Item2}' after adding contract '{request.Item3}'."); - MerchantContractResponse? contract = merchant.Contracts - .SingleOrDefault(c => c.ContractId == request.Item3 && c.IsDeleted == false); + ContractResponse? contract = merchantContractsResult.Data + .SingleOrDefault(c => c.ContractId == request.Item3); contract.ShouldNotBeNull( - $"Contract '{request.Item3}' was not assigned to merchant '{request.Item2}' in " + + $"Contract '{request.Item3}' was not present in the merchant-contract read model for " + $"estate '{request.Item1.EstateId}' and merchant '{request.Item2}'."); }); } diff --git a/TransactionProcessor.IntegrationTests/Features/Merchant.feature b/TransactionProcessor.IntegrationTests/Features/Merchant.feature index 6e92393a..4662f9e5 100644 --- a/TransactionProcessor.IntegrationTests/Features/Merchant.feature +++ b/TransactionProcessor.IntegrationTests/Features/Merchant.feature @@ -173,6 +173,9 @@ Scenario: Update Merchant When I add the following devices to the merchant | DeviceIdentifier | MerchantName | EstateName | | TestDevice1 | Test Merchant 1 | Test Estate 1 | + When I create the following Products + | EstateName | OperatorName | ContractDescription | ProductName | DisplayText | Value | ProductType | + | Test Estate 1 | Test Operator 1 | Safaricom Contract | Safaricom Topup | Topup | 10.00 | MobileTopup | When I add the following contracts to the following merchants | EstateName | MerchantName | ContractDescription | | Test Estate 1 | Test Merchant 1 | Safaricom Contract | diff --git a/TransactionProcessor.IntegrationTests/Features/Merchant.feature.cs b/TransactionProcessor.IntegrationTests/Features/Merchant.feature.cs index 3f323a9b..bfbcd5a7 100644 --- a/TransactionProcessor.IntegrationTests/Features/Merchant.feature.cs +++ b/TransactionProcessor.IntegrationTests/Features/Merchant.feature.cs @@ -111,139 +111,139 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa { #line 4 #line hidden - global::Reqnroll.Table table61 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table1 = new global::Reqnroll.Table(new string[] { "Role Name"}); - table61.AddRow(new string[] { + table1.AddRow(new string[] { "Estate"}); - table61.AddRow(new string[] { + table1.AddRow(new string[] { "Merchant"}); #line 6 - await testRunner.GivenAsync("the following security roles exist", ((string)(null)), table61, "Given "); + await testRunner.GivenAsync("the following security roles exist", ((string)(null)), table1, "Given "); #line hidden - global::Reqnroll.Table table62 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table2 = new global::Reqnroll.Table(new string[] { "Name", "DisplayName", "Description"}); - table62.AddRow(new string[] { + table2.AddRow(new string[] { "transactionProcessor", "Estate Managememt REST Scope", "A scope for Estate Managememt REST"}); #line 11 - await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table62, "Given "); + await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table2, "Given "); #line hidden - global::Reqnroll.Table table63 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table3 = new global::Reqnroll.Table(new string[] { "Name", "DisplayName", "Secret", "Scopes", "UserClaims"}); - table63.AddRow(new string[] { + table3.AddRow(new string[] { "transactionProcessor", "Estate Managememt REST", "Secret1", "transactionProcessor", "merchantId, estateId, role"}); #line 15 - await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table63, "Given "); + await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table3, "Given "); #line hidden - global::Reqnroll.Table table64 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table4 = new global::Reqnroll.Table(new string[] { "ClientId", "ClientName", "Secret", "Scopes", "GrantTypes"}); - table64.AddRow(new string[] { + table4.AddRow(new string[] { "serviceClient", "Service Client", "Secret1", "transactionProcessor", "client_credentials"}); - table64.AddRow(new string[] { + table4.AddRow(new string[] { "estateClient", "Estate Client", "Secret1", "transactionProcessor", "password"}); #line 19 - await testRunner.GivenAsync("the following clients exist", ((string)(null)), table64, "Given "); + await testRunner.GivenAsync("the following clients exist", ((string)(null)), table4, "Given "); #line hidden - global::Reqnroll.Table table65 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table5 = new global::Reqnroll.Table(new string[] { "ClientId"}); - table65.AddRow(new string[] { + table5.AddRow(new string[] { "serviceClient"}); #line 24 - await testRunner.GivenAsync("I have a token to access the estate management resource", ((string)(null)), table65, "Given "); + await testRunner.GivenAsync("I have a token to access the estate management resource", ((string)(null)), table5, "Given "); #line hidden - global::Reqnroll.Table table66 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table6 = new global::Reqnroll.Table(new string[] { "EstateName"}); - table66.AddRow(new string[] { + table6.AddRow(new string[] { "Test Estate 1"}); - table66.AddRow(new string[] { + table6.AddRow(new string[] { "Test Estate 2"}); #line 28 - await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table66, "Given "); + await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table6, "Given "); #line hidden - global::Reqnroll.Table table67 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table7 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "RequireCustomMerchantNumber", "RequireCustomTerminalNumber"}); - table67.AddRow(new string[] { + table7.AddRow(new string[] { "Test Estate 1", "Test Operator 1", "False", "False"}); - table67.AddRow(new string[] { + table7.AddRow(new string[] { "Test Estate 2", "Test Operator 1", "False", "False"}); #line 33 - await testRunner.GivenAsync("I have created the following operators", ((string)(null)), table67, "Given "); + await testRunner.GivenAsync("I have created the following operators", ((string)(null)), table7, "Given "); #line hidden - global::Reqnroll.Table table68 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table8 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName"}); - table68.AddRow(new string[] { + table8.AddRow(new string[] { "Test Estate 1", "Test Operator 1"}); - table68.AddRow(new string[] { + table8.AddRow(new string[] { "Test Estate 2", "Test Operator 1"}); #line 38 - await testRunner.AndAsync("I have assigned the following operators to the estates", ((string)(null)), table68, "And "); + await testRunner.AndAsync("I have assigned the following operators to the estates", ((string)(null)), table8, "And "); #line hidden - global::Reqnroll.Table table69 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table9 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription"}); - table69.AddRow(new string[] { + table9.AddRow(new string[] { "Test Estate 1", "Test Operator 1", "Safaricom Contract"}); #line 43 - await testRunner.GivenAsync("I create a contract with the following values", ((string)(null)), table69, "Given "); + await testRunner.GivenAsync("I create a contract with the following values", ((string)(null)), table9, "Given "); #line hidden - global::Reqnroll.Table table70 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table10 = new global::Reqnroll.Table(new string[] { "EmailAddress", "Password", "GivenName", "FamilyName", "EstateName"}); - table70.AddRow(new string[] { + table10.AddRow(new string[] { "estateuser1@testestate1.co.uk", "123456", "TestEstate", "User1", "Test Estate 1"}); - table70.AddRow(new string[] { + table10.AddRow(new string[] { "estateuser1@testestate2.co.uk", "123456", "TestEstate", "User1", "Test Estate 2"}); #line 47 - await testRunner.GivenAsync("I have created the following security users", ((string)(null)), table70, "Given "); + await testRunner.GivenAsync("I have created the following security users", ((string)(null)), table10, "Given "); #line hidden } @@ -306,7 +306,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa #line 4 await this.FeatureBackgroundAsync(); #line hidden - global::Reqnroll.Table table71 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table11 = new global::Reqnroll.Table(new string[] { "MerchantName", "AddressLine1", "Town", @@ -317,7 +317,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "EmailAddress", "EstateName", "SettlementSchedule"}); - table71.AddRow(new string[] { + table11.AddRow(new string[] { "Test Merchant 1", "Address Line 1", "TestTown", @@ -329,9 +329,9 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "Test Estate 1", "Weekly"}); #line 56 - await testRunner.WhenAsync("I create the following merchants", ((string)(null)), table71, "When "); + await testRunner.WhenAsync("I create the following merchants", ((string)(null)), table11, "When "); #line hidden - global::Reqnroll.Table table72 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table12 = new global::Reqnroll.Table(new string[] { "MerchantName", "MondayOpening", "MondayClosing", @@ -348,7 +348,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "SundayOpening", "SundayClosing", "EstateName"}); - table72.AddRow(new string[] { + table12.AddRow(new string[] { "Test Merchant 1", "0800", "1700", @@ -366,52 +366,52 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "1700", "Test Estate 1"}); #line 59 - await testRunner.WhenAsync("I set the following opening hours", ((string)(null)), table72, "When "); + await testRunner.WhenAsync("I set the following opening hours", ((string)(null)), table12, "When "); #line hidden - global::Reqnroll.Table table73 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table13 = new global::Reqnroll.Table(new string[] { "MerchantName", "EstateName", "Year", "Month", "ClosedDays"}); - table73.AddRow(new string[] { + table13.AddRow(new string[] { "Test Merchant 1", "Test Estate 1", "2026", "1", "1"}); - table73.AddRow(new string[] { + table13.AddRow(new string[] { "Test Merchant 1", "Test Estate 1", "2026", "12", "25,26"}); #line 62 - await testRunner.WhenAsync("I create the following merchant schedules", ((string)(null)), table73, "When "); + await testRunner.WhenAsync("I create the following merchant schedules", ((string)(null)), table13, "When "); #line hidden - global::Reqnroll.Table table74 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table14 = new global::Reqnroll.Table(new string[] { "OperatorName", "MerchantName", "MerchantNumber", "TerminalNumber", "EstateName"}); - table74.AddRow(new string[] { + table14.AddRow(new string[] { "Test Operator 1", "Test Merchant 1", "00000001", "10000001", "Test Estate 1"}); #line 66 - await testRunner.WhenAsync("I assign the following operator to the merchants", ((string)(null)), table74, "When "); + await testRunner.WhenAsync("I assign the following operator to the merchants", ((string)(null)), table14, "When "); #line hidden - global::Reqnroll.Table table75 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table15 = new global::Reqnroll.Table(new string[] { "EmailAddress", "Password", "GivenName", "FamilyName", "MerchantName", "EstateName"}); - table75.AddRow(new string[] { + table15.AddRow(new string[] { "merchantuser1@testmerchant1.co.uk", "123456", "TestMerchant", @@ -419,159 +419,159 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "Test Merchant 1", "Test Estate 1"}); #line 69 - await testRunner.WhenAsync("I create the following security users", ((string)(null)), table75, "When "); + await testRunner.WhenAsync("I create the following security users", ((string)(null)), table15, "When "); #line hidden - global::Reqnroll.Table table76 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table16 = new global::Reqnroll.Table(new string[] { "DeviceIdentifier", "MerchantName", "EstateName"}); - table76.AddRow(new string[] { + table16.AddRow(new string[] { "TestDevice1", "Test Merchant 1", "Test Estate 1"}); #line 72 - await testRunner.WhenAsync("I add the following devices to the merchant", ((string)(null)), table76, "When "); + await testRunner.WhenAsync("I add the following devices to the merchant", ((string)(null)), table16, "When "); #line hidden - global::Reqnroll.Table table77 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table17 = new global::Reqnroll.Table(new string[] { "OriginalDeviceIdentifier", "NewDeviceIdentifier", "MerchantName", "EstateName"}); - table77.AddRow(new string[] { + table17.AddRow(new string[] { "TestDevice1", "TestDevice2", "Test Merchant 1", "Test Estate 1"}); #line 75 - await testRunner.WhenAsync("I swap the merchant device the device is swapped", ((string)(null)), table77, "When "); + await testRunner.WhenAsync("I swap the merchant device the device is swapped", ((string)(null)), table17, "When "); #line hidden - global::Reqnroll.Table table78 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table18 = new global::Reqnroll.Table(new string[] { "Reference", "Amount", "DateTime", "MerchantName", "EstateName"}); - table78.AddRow(new string[] { + table18.AddRow(new string[] { "Deposit1", "500.00", "LastMonth", "Test Merchant 1", "Test Estate 1"}); - table78.AddRow(new string[] { + table18.AddRow(new string[] { "Deposit2", "1000.00", "LastWeek", "Test Merchant 1", "Test Estate 1"}); - table78.AddRow(new string[] { + table18.AddRow(new string[] { "Deposit3", "1000.00", "Yesterday", "Test Merchant 1", "Test Estate 1"}); - table78.AddRow(new string[] { + table18.AddRow(new string[] { "Deposit4", "400.00", "Today", "Test Merchant 1", "Test Estate 1"}); #line 78 - await testRunner.WhenAsync("I make the following manual merchant deposits", ((string)(null)), table78, "When "); + await testRunner.WhenAsync("I make the following manual merchant deposits", ((string)(null)), table18, "When "); #line hidden - global::Reqnroll.Table table79 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table19 = new global::Reqnroll.Table(new string[] { "Amount", "DateTime", "MerchantName", "EstateName"}); - table79.AddRow(new string[] { + table19.AddRow(new string[] { "400.00", "LastMonth", "Test Merchant 1", "Test Estate 1"}); #line 85 - await testRunner.WhenAsync("I make the following merchant withdrawals", ((string)(null)), table79, "When "); + await testRunner.WhenAsync("I make the following merchant withdrawals", ((string)(null)), table19, "When "); #line hidden - global::Reqnroll.Table table80 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table20 = new global::Reqnroll.Table(new string[] { "Amount", "DateTime", "MerchantName", "EstateName"}); - table80.AddRow(new string[] { + table20.AddRow(new string[] { "500.00", "LastMonth", "Test Merchant 1", "Test Estate 1"}); - table80.AddRow(new string[] { + table20.AddRow(new string[] { "1000.00", "LastWeek", "Test Merchant 1", "Test Estate 1"}); - table80.AddRow(new string[] { + table20.AddRow(new string[] { "1000.00", "Yesterday", "Test Merchant 1", "Test Estate 1"}); - table80.AddRow(new string[] { + table20.AddRow(new string[] { "400.00", "Today", "Test Merchant 1", "Test Estate 1"}); #line 89 - await testRunner.WhenAsync("I make the following automatic merchant deposits", ((string)(null)), table80, "When "); + await testRunner.WhenAsync("I make the following automatic merchant deposits", ((string)(null)), table20, "When "); #line hidden - global::Reqnroll.Table table81 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table21 = new global::Reqnroll.Table(new string[] { "Amount", "DateTime", "MerchantName", "EstateName"}); - table81.AddRow(new string[] { + table21.AddRow(new string[] { "0", "LastMonth", "Test Merchant 1", "Test Estate 1"}); #line 96 - await testRunner.WhenAsync("I make the following manual merchant deposits the deposit is rejected", ((string)(null)), table81, "When "); + await testRunner.WhenAsync("I make the following manual merchant deposits the deposit is rejected", ((string)(null)), table21, "When "); #line hidden - global::Reqnroll.Table table82 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table22 = new global::Reqnroll.Table(new string[] { "Amount", "DateTime", "MerchantName", "EstateName"}); - table82.AddRow(new string[] { + table22.AddRow(new string[] { "-100", "LastMonth", "Test Merchant 1", "Test Estate 1"}); #line 100 - await testRunner.WhenAsync("I make the following manual merchant deposits the deposit is rejected", ((string)(null)), table82, "When "); + await testRunner.WhenAsync("I make the following manual merchant deposits the deposit is rejected", ((string)(null)), table22, "When "); #line hidden - global::Reqnroll.Table table83 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table23 = new global::Reqnroll.Table(new string[] { "Amount", "DateTime", "MerchantName", "EstateName"}); - table83.AddRow(new string[] { + table23.AddRow(new string[] { "0", "LastMonth", "Test Merchant 1", "Test Estate 1"}); #line 104 - await testRunner.WhenAsync("I make the following automatic merchant deposits the deposit is rejected", ((string)(null)), table83, "When "); + await testRunner.WhenAsync("I make the following automatic merchant deposits the deposit is rejected", ((string)(null)), table23, "When "); #line hidden - global::Reqnroll.Table table84 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table24 = new global::Reqnroll.Table(new string[] { "Amount", "DateTime", "MerchantName", "EstateName"}); - table84.AddRow(new string[] { + table24.AddRow(new string[] { "-100", "LastMonth", "Test Merchant 1", "Test Estate 1"}); #line 108 - await testRunner.WhenAsync("I make the following automatic merchant deposits the deposit is rejected", ((string)(null)), table84, "When "); + await testRunner.WhenAsync("I make the following automatic merchant deposits the deposit is rejected", ((string)(null)), table24, "When "); #line hidden - global::Reqnroll.Table table85 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table25 = new global::Reqnroll.Table(new string[] { "MerchantName", "AddressLine1", "Town", @@ -581,7 +581,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "ContactName", "EmailAddress", "EstateName"}); - table85.AddRow(new string[] { + table25.AddRow(new string[] { "Test Merchant 2", "Address Line 1", "TestTown", @@ -591,7 +591,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "Test Contact 1", "testcontact1@merchant1.co.uk", "Test Estate 1"}); - table85.AddRow(new string[] { + table25.AddRow(new string[] { "Test Merchant 3", "Address Line 1", "TestTown", @@ -601,7 +601,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "Test Contact 1", "testcontact1@merchant1.co.uk", "Test Estate 1"}); - table85.AddRow(new string[] { + table25.AddRow(new string[] { "Test Merchant 4", "Address Line 1", "TestTown", @@ -612,26 +612,26 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "testcontact1@merchant1.co.uk", "Test Estate 1"}); #line 112 - await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table85, "Given "); + await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table25, "Given "); #line hidden - global::Reqnroll.Table table86 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table26 = new global::Reqnroll.Table(new string[] { "MerchantName", "EstateName", "SettlementSchedule"}); - table86.AddRow(new string[] { + table26.AddRow(new string[] { "Test Merchant 2", "Test Estate 1", "Immediate"}); - table86.AddRow(new string[] { + table26.AddRow(new string[] { "Test Merchant 3", "Test Estate 1", "Weekly"}); - table86.AddRow(new string[] { + table26.AddRow(new string[] { "Test Merchant 4", "Test Estate 1", "Monthly"}); #line 118 - await testRunner.WhenAsync("I set the merchants settlement schedule", ((string)(null)), table86, "When "); + await testRunner.WhenAsync("I set the merchants settlement schedule", ((string)(null)), table26, "When "); #line hidden } await this.ScenarioCleanupAsync(); @@ -662,7 +662,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa #line 4 await this.FeatureBackgroundAsync(); #line hidden - global::Reqnroll.Table table87 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table27 = new global::Reqnroll.Table(new string[] { "MerchantName", "AddressLine1", "Town", @@ -672,7 +672,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "ContactName", "EmailAddress", "EstateName"}); - table87.AddRow(new string[] { + table27.AddRow(new string[] { "Test Merchant 1", "Address Line 1", "TestTown", @@ -682,7 +682,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "Test Contact 1", "testcontact1@merchant1.co.uk", "Test Estate 1"}); - table87.AddRow(new string[] { + table27.AddRow(new string[] { "Test Merchant 2", "Address Line 1", "TestTown", @@ -692,7 +692,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "Test Contact 1", "testcontact1@merchant2.co.uk", "Test Estate 1"}); - table87.AddRow(new string[] { + table27.AddRow(new string[] { "Test Merchant 3", "Address Line 1", "TestTown", @@ -702,7 +702,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "Test Contact 1", "testcontact1@merchant3.co.uk", "Test Estate 1"}); - table87.AddRow(new string[] { + table27.AddRow(new string[] { "Test Merchant 4", "Address Line 1", "TestTown", @@ -712,7 +712,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "Test Contact 1", "testcontact1@merchant4.co.uk", "Test Estate 2"}); - table87.AddRow(new string[] { + table27.AddRow(new string[] { "Test Merchant 5", "Address Line 1", "TestTown", @@ -723,110 +723,110 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "testcontact1@merchant5.co.uk", "Test Estate 2"}); #line 126 - await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table87, "Given "); + await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table27, "Given "); #line hidden - global::Reqnroll.Table table88 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table28 = new global::Reqnroll.Table(new string[] { "OperatorName", "MerchantName", "MerchantNumber", "TerminalNumber", "EstateName"}); - table88.AddRow(new string[] { + table28.AddRow(new string[] { "Test Operator 1", "Test Merchant 1", "00000001", "10000001", "Test Estate 1"}); - table88.AddRow(new string[] { + table28.AddRow(new string[] { "Test Operator 1", "Test Merchant 2", "00000001", "10000001", "Test Estate 1"}); - table88.AddRow(new string[] { + table28.AddRow(new string[] { "Test Operator 1", "Test Merchant 3", "00000001", "10000001", "Test Estate 1"}); - table88.AddRow(new string[] { + table28.AddRow(new string[] { "Test Operator 1", "Test Merchant 4", "00000001", "10000001", "Test Estate 2"}); - table88.AddRow(new string[] { + table28.AddRow(new string[] { "Test Operator 1", "Test Merchant 5", "00000001", "10000001", "Test Estate 2"}); #line 134 - await testRunner.WhenAsync("I assign the following operator to the merchants", ((string)(null)), table88, "When "); + await testRunner.WhenAsync("I assign the following operator to the merchants", ((string)(null)), table28, "When "); #line hidden - global::Reqnroll.Table table89 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table29 = new global::Reqnroll.Table(new string[] { "DeviceIdentifier", "MerchantName", "EstateName"}); - table89.AddRow(new string[] { + table29.AddRow(new string[] { "TestDevice1", "Test Merchant 1", "Test Estate 1"}); - table89.AddRow(new string[] { + table29.AddRow(new string[] { "TestDevice2", "Test Merchant 2", "Test Estate 1"}); - table89.AddRow(new string[] { + table29.AddRow(new string[] { "TestDevice3", "Test Merchant 3", "Test Estate 1"}); - table89.AddRow(new string[] { + table29.AddRow(new string[] { "TestDevice4", "Test Merchant 4", "Test Estate 2"}); - table89.AddRow(new string[] { + table29.AddRow(new string[] { "TestDevice5", "Test Merchant 5", "Test Estate 2"}); #line 142 - await testRunner.WhenAsync("I add the following devices to the merchant", ((string)(null)), table89, "When "); + await testRunner.WhenAsync("I add the following devices to the merchant", ((string)(null)), table29, "When "); #line hidden - global::Reqnroll.Table table90 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table30 = new global::Reqnroll.Table(new string[] { "EmailAddress", "Password", "GivenName", "FamilyName", "MerchantName", "EstateName"}); - table90.AddRow(new string[] { + table30.AddRow(new string[] { "merchantuser1@testmerchant1.co.uk", "123456", "TestMerchant", "User1", "Test Merchant 1", "Test Estate 1"}); - table90.AddRow(new string[] { + table30.AddRow(new string[] { "merchantuser1@testmerchant2.co.uk", "123456", "TestMerchant", "User1", "Test Merchant 2", "Test Estate 1"}); - table90.AddRow(new string[] { + table30.AddRow(new string[] { "merchantuser1@testmerchant3.co.uk", "123456", "TestMerchant", "User1", "Test Merchant 3", "Test Estate 1"}); - table90.AddRow(new string[] { + table30.AddRow(new string[] { "merchantuser1@testmerchant4.co.uk", "123456", "TestMerchant", "User1", "Test Merchant 4", "Test Estate 2"}); - table90.AddRow(new string[] { + table30.AddRow(new string[] { "merchantuser1@testmerchant5.co.uk", "123456", "TestMerchant", @@ -834,7 +834,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "Test Merchant 5", "Test Estate 2"}); #line 150 - await testRunner.WhenAsync("I create the following security users", ((string)(null)), table90, "When "); + await testRunner.WhenAsync("I create the following security users", ((string)(null)), table30, "When "); #line hidden #line 158 await testRunner.WhenAsync("I get the merchants for \'Test Estate 1\' then 3 merchants will be returned", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); @@ -871,7 +871,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa #line 4 await this.FeatureBackgroundAsync(); #line hidden - global::Reqnroll.Table table91 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table31 = new global::Reqnroll.Table(new string[] { "MerchantName", "AddressLine1", "Town", @@ -882,7 +882,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "EmailAddress", "EstateName", "SettlementSchedule"}); - table91.AddRow(new string[] { + table31.AddRow(new string[] { "Test Merchant 1", "Address Line 1", "TestTown", @@ -894,31 +894,31 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "Test Estate 1", "Weekly"}); #line 164 - await testRunner.WhenAsync("I create the following merchants", ((string)(null)), table91, "When "); + await testRunner.WhenAsync("I create the following merchants", ((string)(null)), table31, "When "); #line hidden - global::Reqnroll.Table table92 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table32 = new global::Reqnroll.Table(new string[] { "OperatorName", "MerchantName", "MerchantNumber", "TerminalNumber", "EstateName"}); - table92.AddRow(new string[] { + table32.AddRow(new string[] { "Test Operator 1", "Test Merchant 1", "00000001", "10000001", "Test Estate 1"}); #line 167 - await testRunner.WhenAsync("I assign the following operator to the merchants", ((string)(null)), table92, "When "); + await testRunner.WhenAsync("I assign the following operator to the merchants", ((string)(null)), table32, "When "); #line hidden - global::Reqnroll.Table table93 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table33 = new global::Reqnroll.Table(new string[] { "EmailAddress", "Password", "GivenName", "FamilyName", "MerchantName", "EstateName"}); - table93.AddRow(new string[] { + table33.AddRow(new string[] { "merchantuser1@testmerchant1.co.uk", "123456", "TestMerchant", @@ -926,44 +926,63 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "Test Merchant 1", "Test Estate 1"}); #line 170 - await testRunner.WhenAsync("I create the following security users", ((string)(null)), table93, "When "); + await testRunner.WhenAsync("I create the following security users", ((string)(null)), table33, "When "); #line hidden - global::Reqnroll.Table table94 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table34 = new global::Reqnroll.Table(new string[] { "DeviceIdentifier", "MerchantName", "EstateName"}); - table94.AddRow(new string[] { + table34.AddRow(new string[] { "TestDevice1", "Test Merchant 1", "Test Estate 1"}); #line 173 - await testRunner.WhenAsync("I add the following devices to the merchant", ((string)(null)), table94, "When "); + await testRunner.WhenAsync("I add the following devices to the merchant", ((string)(null)), table34, "When "); #line hidden - global::Reqnroll.Table table95 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table35 = new global::Reqnroll.Table(new string[] { + "EstateName", + "OperatorName", + "ContractDescription", + "ProductName", + "DisplayText", + "Value", + "ProductType"}); + table35.AddRow(new string[] { + "Test Estate 1", + "Test Operator 1", + "Safaricom Contract", + "Safaricom Topup", + "Topup", + "10.00", + "MobileTopup"}); +#line 176 + await testRunner.WhenAsync("I create the following Products", ((string)(null)), table35, "When "); +#line hidden + global::Reqnroll.Table table36 = new global::Reqnroll.Table(new string[] { "EstateName", "MerchantName", "ContractDescription"}); - table95.AddRow(new string[] { + table36.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "Safaricom Contract"}); -#line 176 - await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table95, "When "); +#line 179 + await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table36, "When "); #line hidden - global::Reqnroll.Table table96 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table37 = new global::Reqnroll.Table(new string[] { "UpdateMerchantName", "SettlementSchedule", "EstateName", "MerchantName"}); - table96.AddRow(new string[] { + table37.AddRow(new string[] { "Update Merchant 1", "Monthly", "Test Estate 1", "Test Merchant 1"}); -#line 179 - await testRunner.WhenAsync("I update the merchants with the following details", ((string)(null)), table96, "When "); +#line 182 + await testRunner.WhenAsync("I update the merchants with the following details", ((string)(null)), table37, "When "); #line hidden - global::Reqnroll.Table table97 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table38 = new global::Reqnroll.Table(new string[] { "AddressLine1", "AddressLine2", "AddressLine3", @@ -974,7 +993,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "Country", "EstateName", "MerchantName"}); - table97.AddRow(new string[] { + table38.AddRow(new string[] { "Address Line 1U", "Address Line 2", "Address Line 3", @@ -985,42 +1004,42 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "United KingdomU", "Test Estate 1", "Test Merchant 1"}); -#line 182 - await testRunner.WhenAsync("I update the merchants address with the following details", ((string)(null)), table97, "When "); +#line 185 + await testRunner.WhenAsync("I update the merchants address with the following details", ((string)(null)), table38, "When "); #line hidden - global::Reqnroll.Table table98 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table39 = new global::Reqnroll.Table(new string[] { "ContactName", "EmailAddress", "PhoneNumber", "EstateName", "MerchantName"}); - table98.AddRow(new string[] { + table39.AddRow(new string[] { "Test Contact 1U", "testcontact1update@merchant1.co.uk", "12345678", "Test Estate 1", "Test Merchant 1"}); -#line 185 - await testRunner.WhenAsync("I update the merchants contact with the following details", ((string)(null)), table98, "When "); +#line 188 + await testRunner.WhenAsync("I update the merchants contact with the following details", ((string)(null)), table39, "When "); #line hidden - global::Reqnroll.Table table99 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table40 = new global::Reqnroll.Table(new string[] { "OriginalDeviceIdentifier", "NewDeviceIdentifier", "MerchantName", "EstateName"}); - table99.AddRow(new string[] { + table40.AddRow(new string[] { "TestDevice1", "TestDevice2", "Test Merchant 1", "Test Estate 1"}); -#line 188 - await testRunner.WhenAsync("I swap the merchant device the device is swapped", ((string)(null)), table99, "When "); -#line hidden #line 191 + await testRunner.WhenAsync("I swap the merchant device the device is swapped", ((string)(null)), table40, "When "); +#line hidden +#line 194 await testRunner.WhenAsync("I remove the contract \'Safaricom Contract\' from merchant \'Test Merchant 1\' on \'Te" + "st Estate 1\' the contract is removed", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); #line hidden -#line 192 +#line 195 await testRunner.WhenAsync("I remove the operator \'Test Operator 1\' from merchant \'Test Merchant 1\' on \'Test " + "Estate 1\' the operator is removed", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); #line hidden From bedd277b6edad8bd48bf0db46aefaaeed6cf4eb2 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Mon, 12 Oct 2026 20:25:29 +0100 Subject: [PATCH 6/6] Verify integration setup state changes --- .../TransactionProcessorSteps.cs | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs index 4febe662..542ce5df 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs @@ -198,6 +198,18 @@ public async Task WhenISetTheMerchantsSettlementSchedule(String accessToken, Lis CancellationToken.None); result.IsSuccess.ShouldBeTrue(result.Message); + await Retry.For(async () => + { + Result merchantResult = await this.TransactionProcessorClient.GetMerchant( + accessToken, + request.Item1.EstateId, + request.Item2, + CancellationToken.None); + merchantResult.IsSuccess.ShouldBeTrue(merchantResult.Message); + MerchantResponse? merchant = merchantResult.Data; + merchant.ShouldNotBeNull(); + merchant.SettlementSchedule.ShouldBe(request.Item3.SettlementSchedule); + }); } } @@ -292,6 +304,14 @@ public async Task WhenIMakeTheFollowingMerchantWithdrawals(String accessToken, L { foreach ((EstateDetails, Guid, MakeMerchantWithdrawalRequest) makeMerchantWithdrawalRequest in requests) { + Result balanceBeforeResult = await this.TransactionProcessorClient.GetMerchantBalance( + accessToken, + makeMerchantWithdrawalRequest.Item1.EstateId, + makeMerchantWithdrawalRequest.Item2, + CancellationToken.None); + balanceBeforeResult.IsSuccess.ShouldBeTrue(balanceBeforeResult.Message); + MerchantBalanceResponse? balanceBefore = balanceBeforeResult.Data; + balanceBefore.ShouldNotBeNull(); Result? result = await this.TransactionProcessorClient .MakeMerchantWithdrawal(accessToken, @@ -301,6 +321,19 @@ public async Task WhenIMakeTheFollowingMerchantWithdrawals(String accessToken, L CancellationToken.None).ConfigureAwait(false); result.IsSuccess.ShouldBeTrue(result.Message); + + await Retry.For(async () => + { + Result balanceAfterResult = await this.TransactionProcessorClient.GetMerchantBalance( + accessToken, + makeMerchantWithdrawalRequest.Item1.EstateId, + makeMerchantWithdrawalRequest.Item2, + CancellationToken.None); + balanceAfterResult.IsSuccess.ShouldBeTrue(balanceAfterResult.Message); + MerchantBalanceResponse? balanceAfter = balanceAfterResult.Data; + balanceAfter.ShouldNotBeNull(); + balanceAfter.Balance.ShouldBe(balanceBefore.Balance - makeMerchantWithdrawalRequest.Item3.Amount); + }); } } @@ -332,17 +365,51 @@ public async Task> WhenICreateTheFollowingMerchants(strin }; Result? enableAgencyBankingResult = await this.AgencyBankingClient.CreateRetailAgent(createAgentRequest, CancellationToken.None); enableAgencyBankingResult.IsSuccess.ShouldBeTrue(enableAgencyBankingResult.Message); + + await Retry.For(async () => + { + Result agentResult = await this.AgencyBankingClient.GetAgentById(createAgentRequest.AgentId, CancellationToken.None); + agentResult.IsSuccess.ShouldBeTrue(agentResult.Message); + AgentResponse? agent = agentResult.Data; + agent.ShouldNotBeNull(); + agent.AgentId.ShouldBe(createAgentRequest.AgentId); + agent.Name.ShouldBe(createAgentRequest.Name); + }); // Activate the agent ActivateAgentRequest activateAgentRequest = new ActivateAgentRequest { ActivatedBy = "INTEGRATIONTEST" }; var activateAgentResult = await this.AgencyBankingClient.ActivateAgent(createAgentRequest.AgentId, activateAgentRequest, CancellationToken.None); activateAgentResult.IsSuccess.ShouldBeTrue(activateAgentResult.Message); + await Retry.For(async () => + { + Result agentResult = await this.AgencyBankingClient.GetAgentById(createAgentRequest.AgentId, CancellationToken.None); + agentResult.IsSuccess.ShouldBeTrue(agentResult.Message); + AgentResponse? agent = agentResult.Data; + agent.ShouldNotBeNull(); + agent.Active.ShouldBeTrue(); + agent.ActivatedBy.ShouldBe(activateAgentRequest.ActivatedBy); + }); + ConfigureFloatRequest configureFloatRequest = new ConfigureFloatRequest { AgentId = createAgentRequest.AgentId, DailyFloatLimit = 100000, MaximumFloat = 100000, MinimumFloat = 10000, }; var configureFloatResult = await this.AgencyBankingClient.ConfigureFloat(configureFloatRequest, CancellationToken.None); configureFloatResult.IsSuccess.ShouldBeTrue(configureFloatResult.Message); + + await Retry.For(async () => + { + Result floatConfigurationResult = await this.AgencyBankingClient.GetFloatConfigurationByAgentId( + createAgentRequest.AgentId, + CancellationToken.None); + floatConfigurationResult.IsSuccess.ShouldBeTrue(floatConfigurationResult.Message); + FloatConfigurationResponse? floatConfiguration = floatConfigurationResult.Data; + floatConfiguration.ShouldNotBeNull(); + floatConfiguration.AgentId.ShouldBe(createAgentRequest.AgentId); + floatConfiguration.DailyFloatLimit.ShouldBe(configureFloatRequest.DailyFloatLimit); + floatConfiguration.MaximumFloat.ShouldBe(configureFloatRequest.MaximumFloat); + floatConfiguration.MinimumFloat.ShouldBe(configureFloatRequest.MinimumFloat); + }); FloatCreditRequest floatCreditRequest = new FloatCreditRequest() { AgentId = createAgentRequest.AgentId, @@ -353,6 +420,22 @@ public async Task> WhenICreateTheFollowingMerchants(strin }; var floatCreditResult = await this.AgencyBankingClient.CreditFloat(floatCreditRequest, CancellationToken.None); floatCreditResult.IsSuccess.ShouldBeTrue(floatCreditResult.Message); + + await Retry.For(async () => + { + Result floatHistoryResult = await this.AgencyBankingClient.GetLastFloatEntries( + createAgentRequest.AgentId, + 1, + CancellationToken.None); + floatHistoryResult.IsSuccess.ShouldBeTrue(floatHistoryResult.Message); + FloatHistoryResponse[]? floatHistory = floatHistoryResult.Data; + floatHistory.ShouldNotBeNull(); + floatHistory.ShouldNotBeEmpty(); + FloatHistoryResponse latestFloatEntry = floatHistory.Single(); + latestFloatEntry.AgentId.ShouldBe(createAgentRequest.AgentId); + latestFloatEntry.TransactionId.ShouldBe(floatCreditRequest.TransactionId); + latestFloatEntry.Amount.ShouldBe(floatCreditRequest.Amount); + }); } @@ -1081,8 +1164,30 @@ await Retry.For(async () => public async Task GivenIMakeTheFollowingManualMerchantDeposits(String accessToken, (EstateDetails, Guid, MakeMerchantDepositRequest) request) { + Result balanceBeforeResult = await this.TransactionProcessorClient.GetMerchantBalance( + accessToken, + request.Item1.EstateId, + request.Item2, + CancellationToken.None); + balanceBeforeResult.IsSuccess.ShouldBeTrue(balanceBeforeResult.Message); + MerchantBalanceResponse? balanceBefore = balanceBeforeResult.Data; + balanceBefore.ShouldNotBeNull(); + Result? result = await this.TransactionProcessorClient.MakeMerchantDeposit(accessToken, request.Item1.EstateId, request.Item2, request.Item3, CancellationToken.None).ConfigureAwait(false); result.IsSuccess.ShouldBeTrue(result.Message); + + await Retry.For(async () => + { + Result balanceAfterResult = await this.TransactionProcessorClient.GetMerchantBalance( + accessToken, + request.Item1.EstateId, + request.Item2, + CancellationToken.None); + balanceAfterResult.IsSuccess.ShouldBeTrue(balanceAfterResult.Message); + MerchantBalanceResponse? balanceAfter = balanceAfterResult.Data; + balanceAfter.ShouldNotBeNull(); + balanceAfter.Balance.ShouldBe(balanceBefore.Balance + request.Item3.Amount); + }); } public async Task WhenICreateTheFollowingSecurityUsers(String accessToken, List requests, List estateDetailsList)