From 5a29bd556d460214690dccae5d57a129ed141848 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Fri, 3 Jul 2026 23:06:48 +0100 Subject: [PATCH 1/2] Add Merchant Transaction Mix Summary reporting feature Introduced DTOs, endpoints, handlers, and MediatR queries for the new Merchant Transaction Mix Summary report. Updated application service, API client, and tests to support and validate the new reporting functionality. Refactored reporting handlers to use MediatR for consistency. Added supporting test data and infrastructure. --- .../MediatorTests.cs | 9 ++ .../RequestHandlerTests.cs | 62 +++++++++++++- ...tionProcessorACLApplicationServiceTests.cs | 49 +++++++++++ .../BackendAPI/EstateReportingApiClient.cs | 29 +++++++ .../BackendAPI/IEstateReportingApiClient.cs | 5 ++ .../ReportingRequestHandler.cs | 34 ++++++++ .../Requests/ReportingQueries.cs | 20 +++++ ...ansactionProcessorACLApplicationService.cs | 10 ++- ...ansactionProcessorACLApplicationService.cs | 83 ++++--------------- .../MerchantTransactionMixSummaryRequest.cs | 36 ++++++++ ...MerchantDailyPerformanceSummaryResponse.cs | 37 +++++++++ .../MerchantTransactionMixSummaryResponse.cs | 67 +++++++++++++++ TransactionProcessorACL.Testing/TestData.cs | 18 ++++ .../Handlers/ReportingHandlersTests.cs | 69 +++++++++++---- .../Endpoints/ReportingEndpoints.cs | 2 + .../Handlers/ReportingHandlers.cs | 27 ++++-- 16 files changed, 461 insertions(+), 96 deletions(-) create mode 100644 TransactionProcessorACL.BusinessLogic/RequestHandlers/ReportingRequestHandler.cs create mode 100644 TransactionProcessorACL.BusinessLogic/Requests/ReportingQueries.cs create mode 100644 TransactionProcessorACL.DataTransferObjects/Requests/MerchantTransactionMixSummaryRequest.cs create mode 100644 TransactionProcessorACL.DataTransferObjects/Responses/MerchantDailyPerformanceSummaryResponse.cs create mode 100644 TransactionProcessorACL.Models/MerchantTransactionMixSummaryResponse.cs diff --git a/TransactionProcessorACL.BusinessLogic.Tests/MediatorTests.cs b/TransactionProcessorACL.BusinessLogic.Tests/MediatorTests.cs index 57a9c4a..d0a9d7b 100644 --- a/TransactionProcessorACL.BusinessLogic.Tests/MediatorTests.cs +++ b/TransactionProcessorACL.BusinessLogic.Tests/MediatorTests.cs @@ -37,6 +37,8 @@ public MediatorTests() this.Requests.Add(TestData.GetMerchantContractsQuery); this.Requests.Add(TestData.GetMerchantQuery); this.Requests.Add(TestData.GetMerchantScheduleQuery); + this.Requests.Add(TestData.GetMerchantDailyPerformanceSummaryQuery); + this.Requests.Add(TestData.GetMerchantTransactionMixSummaryQuery); } [Fact] @@ -168,5 +170,12 @@ public async Task> GetMerchantDa { return Result.Success(new MerchantDailyPerformanceSummaryResponse()); } + + public async Task> GetMerchantTransactionMixSummary(Guid estateId, + MerchantTransactionMixSummaryRequest request, + CancellationToken cancellationToken) + { + return Result.Success(new MerchantTransactionMixSummaryResponse()); + } } } diff --git a/TransactionProcessorACL.BusinessLogic.Tests/RequestHandlerTests.cs b/TransactionProcessorACL.BusinessLogic.Tests/RequestHandlerTests.cs index c1f0e75..31a7862 100644 --- a/TransactionProcessorACL.BusinessLogic.Tests/RequestHandlerTests.cs +++ b/TransactionProcessorACL.BusinessLogic.Tests/RequestHandlerTests.cs @@ -16,8 +16,11 @@ namespace TransactionProcessorACL.BusinesssLogic.Tests using Shared.General; using Shouldly; using Testing; + using TransactionProcessorACL.DataTransferObjects.Requests; using Xunit; using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database; + using RequestTransactionMixBreakdown = TransactionProcessorACL.DataTransferObjects.Requests.TransactionMixBreakdown; + using RequestTransactionMixMeasure = TransactionProcessorACL.DataTransferObjects.Requests.TransactionMixMeasure; /// /// @@ -196,6 +199,63 @@ public async Task VoucherRequestHandler_RedeemVoucherRequest_Handle_RequestIsHan }); } + [Fact] + public async Task ReportingRequestHandler_GetMerchantTransactionMixSummaryQuery_Handle_RequestIsHandled() + { + Mock applicationService = new Mock(); + applicationService + .Setup(a => a.GetMerchantTransactionMixSummary(It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(new MerchantTransactionMixSummaryResponse()); + + ReportingRequestHandler requestHandler = new ReportingRequestHandler(applicationService.Object); + + ReportingQueries.GetMerchantTransactionMixSummaryQuery query = new( + TestData.EstateId, + new MerchantTransactionMixSummaryRequest + { + MerchantReportingId = 12345, + StartDate = new DateTime(2026, 7, 1), + EndDate = new DateTime(2026, 7, 3), + Breakdown = RequestTransactionMixBreakdown.Product, + Measure = RequestTransactionMixMeasure.Count, + TopN = 5 + }); + + Result result = await requestHandler.Handle(query, CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + result.Data.ShouldNotBeNull(); + } + + [Fact] + public async Task ReportingRequestHandler_GetMerchantDailyPerformanceSummaryQuery_Handle_RequestIsHandled() + { + Mock applicationService = new Mock(); + applicationService + .Setup(a => a.GetMerchantDailyPerformanceSummary(It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(new MerchantDailyPerformanceSummaryResponse()); + + ReportingRequestHandler requestHandler = new ReportingRequestHandler(applicationService.Object); + + ReportingQueries.GetMerchantDailyPerformanceSummaryQuery query = new( + TestData.EstateId, + new MerchantDailyPerformanceSummaryRequest + { + MerchantReportingId = 12345, + StartDate = new DateTime(2026, 7, 1), + EndDate = new DateTime(2026, 7, 1) + }); + + Result result = await requestHandler.Handle(query, CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + result.Data.ShouldNotBeNull(); + } + #endregion } -} \ No newline at end of file +} diff --git a/TransactionProcessorACL.BusinessLogic.Tests/TransactionProcessorACLApplicationServiceTests.cs b/TransactionProcessorACL.BusinessLogic.Tests/TransactionProcessorACLApplicationServiceTests.cs index b614604..4f85a7f 100644 --- a/TransactionProcessorACL.BusinessLogic.Tests/TransactionProcessorACLApplicationServiceTests.cs +++ b/TransactionProcessorACL.BusinessLogic.Tests/TransactionProcessorACLApplicationServiceTests.cs @@ -21,6 +21,8 @@ namespace TransactionProcessorACL.BusinesssLogic.Tests using Xunit; using GetVoucherResponse = Models.GetVoucherResponse; using RedeemVoucherResponse = Models.RedeemVoucherResponse; + using RequestTransactionMixBreakdown = TransactionProcessorACL.DataTransferObjects.Requests.TransactionMixBreakdown; + using RequestTransactionMixMeasure = TransactionProcessorACL.DataTransferObjects.Requests.TransactionMixMeasure; public class TransactionProcessorACLApplicationServiceTests { @@ -649,5 +651,52 @@ public async Task TransactionProcessorACLApplicationService_GetMerchantDailyPerf result.IsFailed.ShouldBeTrue(); } + + [Fact] + public async Task TransactionProcessorACLApplicationService_GetMerchantTransactionMixSummary_ReturnedFromEstateReportingClient() + { + MerchantTransactionMixSummaryRequest capturedRequest = null; + estateReportingApiClient + .Setup(v => v.GetMerchantTransactionMixSummary(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((_, _, request, _) => capturedRequest = request) + .ReturnsAsync(Result.Success(new MerchantTransactionMixSummaryResponse + { + FromDate = new DateTime(2026, 7, 1), + ToDate = new DateTime(2026, 7, 3), + Breakdown = TransactionProcessorACL.Models.TransactionMixBreakdown.Product, + Measure = TransactionProcessorACL.Models.TransactionMixMeasure.Count, + Items = + [ + new TransactionMixSummaryItem + { + Key = "product-1", + Label = "Product 1", + Count = 6, + Value = 42.75M + } + ] + })); + securityServiceClient.Setup(s => s.GetToken(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(TestData.TokenResponse)); + + Result result = await applicationService.GetMerchantTransactionMixSummary( + TestData.EstateId, + new MerchantTransactionMixSummaryRequest + { + MerchantReportingId = 12345, + StartDate = new DateTime(2026, 7, 1), + EndDate = new DateTime(2026, 7, 3), + Breakdown = RequestTransactionMixBreakdown.Product, + Measure = RequestTransactionMixMeasure.Count, + TopN = 5 + }, + CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + result.Data.ShouldNotBeNull(); + result.Data.Items.Count.ShouldBe(1); + capturedRequest.ShouldNotBeNull(); + capturedRequest.MerchantReportingId.ShouldBe(12345); + capturedRequest.Breakdown.ShouldBe(RequestTransactionMixBreakdown.Product); + } } } diff --git a/TransactionProcessorACL.BusinessLogic/BackendAPI/EstateReportingApiClient.cs b/TransactionProcessorACL.BusinessLogic/BackendAPI/EstateReportingApiClient.cs index e5e687d..5c96e19 100644 --- a/TransactionProcessorACL.BusinessLogic/BackendAPI/EstateReportingApiClient.cs +++ b/TransactionProcessorACL.BusinessLogic/BackendAPI/EstateReportingApiClient.cs @@ -54,6 +54,35 @@ public async Task> GetMerchantDa } } + public async Task> GetMerchantTransactionMixSummary(String accessToken, + Guid estateId, + MerchantTransactionMixSummaryRequest request, + CancellationToken cancellationToken) + { + string requestUri = this.BuildRequestUrl("/api/transactions/transactionmixsummary"); + + try + { + List<(string headerName, string headerValue)> additionalHeaders = + [ + (EstateIdHeaderName, estateId.ToString()) + ]; + + Result result = + await this.Post(requestUri, request, accessToken, additionalHeaders, cancellationToken); + + if (result.IsFailed) + return ResultHelpers.CreateFailure(result); + + return result; + } + catch (Exception ex) + { + Exception exception = new($"Error getting merchant transaction mix summary for estate {estateId}.", ex); + return Result.Failure(exception.Message); + } + } + private string BuildRequestUrl(string relativePath) { string baseAddress = this.BaseAddressResolver("EstateReportingApi"); diff --git a/TransactionProcessorACL.BusinessLogic/BackendAPI/IEstateReportingApiClient.cs b/TransactionProcessorACL.BusinessLogic/BackendAPI/IEstateReportingApiClient.cs index e3cbd51..9b1bc59 100644 --- a/TransactionProcessorACL.BusinessLogic/BackendAPI/IEstateReportingApiClient.cs +++ b/TransactionProcessorACL.BusinessLogic/BackendAPI/IEstateReportingApiClient.cs @@ -13,4 +13,9 @@ Task> GetMerchantDailyPerformanc Guid estateId, MerchantDailyPerformanceSummaryRequest request, CancellationToken cancellationToken); + + Task> GetMerchantTransactionMixSummary(String accessToken, + Guid estateId, + MerchantTransactionMixSummaryRequest request, + CancellationToken cancellationToken); } diff --git a/TransactionProcessorACL.BusinessLogic/RequestHandlers/ReportingRequestHandler.cs b/TransactionProcessorACL.BusinessLogic/RequestHandlers/ReportingRequestHandler.cs new file mode 100644 index 0000000..33b88a9 --- /dev/null +++ b/TransactionProcessorACL.BusinessLogic/RequestHandlers/ReportingRequestHandler.cs @@ -0,0 +1,34 @@ +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using SimpleResults; +using TransactionProcessorACL.BusinessLogic.Requests; +using TransactionProcessorACL.BusinessLogic.Services; +using TransactionProcessorACL.Models; +using TransactionProcessorACL.DataTransferObjects.Requests; + +namespace TransactionProcessorACL.BusinessLogic.RequestHandlers; + +public class ReportingRequestHandler : + IRequestHandler>, + IRequestHandler> +{ + private readonly ITransactionProcessorACLApplicationService ApplicationService; + + public ReportingRequestHandler(ITransactionProcessorACLApplicationService applicationService) + { + this.ApplicationService = applicationService; + } + + public async Task> Handle(ReportingQueries.GetMerchantDailyPerformanceSummaryQuery request, + CancellationToken cancellationToken) + { + return await this.ApplicationService.GetMerchantDailyPerformanceSummary(request.EstateId, request.Request, cancellationToken); + } + + public async Task> Handle(ReportingQueries.GetMerchantTransactionMixSummaryQuery request, + CancellationToken cancellationToken) + { + return await this.ApplicationService.GetMerchantTransactionMixSummary(request.EstateId, request.Request, cancellationToken); + } +} diff --git a/TransactionProcessorACL.BusinessLogic/Requests/ReportingQueries.cs b/TransactionProcessorACL.BusinessLogic/Requests/ReportingQueries.cs new file mode 100644 index 0000000..9ccbdb2 --- /dev/null +++ b/TransactionProcessorACL.BusinessLogic/Requests/ReportingQueries.cs @@ -0,0 +1,20 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using MediatR; +using SimpleResults; +using TransactionProcessorACL.DataTransferObjects.Requests; +using TransactionProcessorACL.Models; + +namespace TransactionProcessorACL.BusinessLogic.Requests; + +[ExcludeFromCodeCoverage] +public record ReportingQueries +{ + public record GetMerchantDailyPerformanceSummaryQuery(Guid EstateId, + MerchantDailyPerformanceSummaryRequest Request) + : IRequest>; + + public record GetMerchantTransactionMixSummaryQuery(Guid EstateId, + MerchantTransactionMixSummaryRequest Request) + : IRequest>; +} diff --git a/TransactionProcessorACL.BusinessLogic/Services/ITransactionProcessorACLApplicationService.cs b/TransactionProcessorACL.BusinessLogic/Services/ITransactionProcessorACLApplicationService.cs index c527b60..a7f7a1d 100644 --- a/TransactionProcessorACL.BusinessLogic/Services/ITransactionProcessorACLApplicationService.cs +++ b/TransactionProcessorACL.BusinessLogic/Services/ITransactionProcessorACLApplicationService.cs @@ -62,8 +62,12 @@ Task> GetMerchantSchedule(Guid estateId, Int32 year, CancellationToken cancellationToken); - Task> GetMerchantDailyPerformanceSummary(Guid estateId, - MerchantDailyPerformanceSummaryRequest request, - CancellationToken cancellationToken); + Task> GetMerchantDailyPerformanceSummary(Guid estateId, + MerchantDailyPerformanceSummaryRequest request, + CancellationToken cancellationToken); + + Task> GetMerchantTransactionMixSummary(Guid estateId, + MerchantTransactionMixSummaryRequest request, + CancellationToken cancellationToken); } } diff --git a/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs b/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs index 8e8688b..a301ad9 100644 --- a/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs +++ b/TransactionProcessorACL.BusinessLogic/Services/TransactionProcessorACLApplicationService.cs @@ -22,61 +22,23 @@ namespace TransactionProcessorACL.BusinessLogic.Services using GetVoucherResponse = Models.GetVoucherResponse; using RedeemVoucherResponse = Models.RedeemVoucherResponse; - /// - /// - /// - /// public class TransactionProcessorACLApplicationService : ITransactionProcessorACLApplicationService { - #region Fields - - /// - /// The security service client - /// private readonly ISecurityServiceClient SecurityServiceClient; - /// - /// The transaction processor client - /// private readonly ITransactionProcessorClient TransactionProcessorClient; - /// - /// The estate reporting client - /// - private readonly IEstateReportingApiClient? EstateReportingApiClient; - - #endregion - - #region Constructors + private readonly IEstateReportingApiClient EstateReportingApiClient; - /// - /// Initializes a new instance of the class. - /// - /// The transaction processor client. - /// The security service client. public TransactionProcessorACLApplicationService(ITransactionProcessorClient transactionProcessorClient, ISecurityServiceClient securityServiceClient, - IEstateReportingApiClient? estateReportingApiClient = null) + IEstateReportingApiClient estateReportingApiClient) { this.TransactionProcessorClient = transactionProcessorClient; this.SecurityServiceClient = securityServiceClient; this.EstateReportingApiClient = estateReportingApiClient; } - - #endregion - - #region Methods - - /// - /// Processes the logon transaction. - /// - /// The estate identifier. - /// The merchant identifier. - /// The transaction date time. - /// The transaction number. - /// The device identifier. - /// The cancellation token. - /// + public async Task> ProcessLogonTransaction(Guid estateId, Guid merchantId, DateTime transactionDateTime, @@ -105,21 +67,6 @@ public async Task> ProcessLogonTransacti } } - /// - /// Processes the sale transaction. - /// - /// The estate identifier. - /// The merchant identifier. - /// The transaction date time. - /// The transaction number. - /// The device identifier. - /// The operator identifier. - /// The customer email address. - /// The contract identifier. - /// The product identifier. - /// The additional request metadata. - /// The cancellation token. - /// public async Task> ProcessSaleTransaction((Guid estateId, Guid merchantId) merchantData, DateTime transactionDateTime, String transactionNumber, @@ -128,12 +75,10 @@ public async Task> ProcessSaleTransaction (Guid operatorId, Guid contractId, Guid productId) productData, Dictionary additionalRequestMetadata, CancellationToken cancellationToken) { - Logger.LogWarning("Here 2.1"); Result accessTokenResult = await this.GetAccessToken(cancellationToken); if (accessTokenResult.IsFailed) { return ResultHelpers.CreateFailure(accessTokenResult); } - Logger.LogWarning("Here 2.2"); TokenResponse accessToken = accessTokenResult.Data; SaleTransactionRequest saleTransactionRequest = this.BuildSaleTransactionRequest(merchantData, @@ -148,19 +93,15 @@ public async Task> ProcessSaleTransaction try { - Logger.LogWarning("Here 2.3"); Result transactionResult = await this.TransactionProcessorClient.PerformTransaction(accessToken.AccessToken, saleTransactionRequest, cancellationToken); if (transactionResult.IsFailed) return ResultHelpers.CreateFailure(transactionResult); - Logger.LogWarning("Here 2.4"); response = this.CreateSaleTransactionResponse(transactionResult.Data, merchantData.estateId, merchantData.merchantId); - Logger.LogWarning("Here 2.5"); } catch (Exception ex) { - Logger.LogWarning("Here 2.6"); response = this.CreateSaleTransactionErrorResponse(merchantData.estateId, merchantData.merchantId, ex); } @@ -429,18 +370,26 @@ public async Task> GetMerchantDa MerchantDailyPerformanceSummaryRequest request, CancellationToken cancellationToken) { - if (this.EstateReportingApiClient == null) - { - return Result.Failure("Estate reporting client is not configured"); + Result accessTokenResult = await this.GetAccessToken(cancellationToken); + if (accessTokenResult.IsFailed) { + return ResultHelpers.CreateFailure(accessTokenResult); } + TokenResponse accessToken = accessTokenResult.Data; + return await this.EstateReportingApiClient.GetMerchantDailyPerformanceSummary(accessToken.AccessToken, estateId, request, cancellationToken); + } + + public async Task> GetMerchantTransactionMixSummary(Guid estateId, + MerchantTransactionMixSummaryRequest request, + CancellationToken cancellationToken) + { Result accessTokenResult = await this.GetAccessToken(cancellationToken); if (accessTokenResult.IsFailed) { return ResultHelpers.CreateFailure(accessTokenResult); } TokenResponse accessToken = accessTokenResult.Data; - return await this.EstateReportingApiClient.GetMerchantDailyPerformanceSummary(accessToken.AccessToken, estateId, request, cancellationToken); + return await this.EstateReportingApiClient.GetMerchantTransactionMixSummary(accessToken.AccessToken, estateId, request, cancellationToken); } private static ProcessReconciliationResponse CreateProcessReconciliationResponse(ReconciliationResponse reconciliationResponse) @@ -550,7 +499,5 @@ private static LogonTransactionRequest CreateLogonRequestMessage(Guid estateId, return logonTransactionRequest; } - - #endregion } } diff --git a/TransactionProcessorACL.DataTransferObjects/Requests/MerchantTransactionMixSummaryRequest.cs b/TransactionProcessorACL.DataTransferObjects/Requests/MerchantTransactionMixSummaryRequest.cs new file mode 100644 index 0000000..c94ca56 --- /dev/null +++ b/TransactionProcessorACL.DataTransferObjects/Requests/MerchantTransactionMixSummaryRequest.cs @@ -0,0 +1,36 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace TransactionProcessorACL.DataTransferObjects.Requests; + +[ExcludeFromCodeCoverage] +public class MerchantTransactionMixSummaryRequest +{ + public int MerchantReportingId { get; set; } + + public DateTime StartDate { get; set; } + + public DateTime EndDate { get; set; } + + public TransactionMixBreakdown Breakdown { get; set; } + + public TransactionMixMeasure Measure { get; set; } + + public int TopN { get; set; } = 5; +} + +public enum TransactionMixBreakdown +{ + NotSet = 0, + TransactionType = 1, + Product = 2, + Operator = 3, + Status = 4 +} + +public enum TransactionMixMeasure +{ + NotSet = 0, + Count = 1, + Value = 2 +} diff --git a/TransactionProcessorACL.DataTransferObjects/Responses/MerchantDailyPerformanceSummaryResponse.cs b/TransactionProcessorACL.DataTransferObjects/Responses/MerchantDailyPerformanceSummaryResponse.cs new file mode 100644 index 0000000..3d17a3d --- /dev/null +++ b/TransactionProcessorACL.DataTransferObjects/Responses/MerchantDailyPerformanceSummaryResponse.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TransactionProcessorACL.DataTransferObjects.Responses +{ + public class MerchantDailyPerformanceSummaryResponse + { + public List Metrics { get; set; } = []; + + public List DrillDownTransactions { get; set; } = []; + } + + public class MetricItem + { + public string Title { get; set; } + + public decimal Value { get; set; } + + public string Description { get; set; } + + public int Category { get; set; } + } + + public class DrillDownTransaction + { + public string Reference { get; set; } + + public string Product { get; set; } + + public string Status { get; set; } + + public decimal Amount { get; set; } + + public DateTime TransactionDateTime { get; set; } + } +} diff --git a/TransactionProcessorACL.Models/MerchantTransactionMixSummaryResponse.cs b/TransactionProcessorACL.Models/MerchantTransactionMixSummaryResponse.cs new file mode 100644 index 0000000..88e76a7 --- /dev/null +++ b/TransactionProcessorACL.Models/MerchantTransactionMixSummaryResponse.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; + +namespace TransactionProcessorACL.Models; + +public class MerchantTransactionMixSummaryResponse +{ + public DateTime FromDate { get; set; } + + public DateTime ToDate { get; set; } + + public TransactionMixBreakdown Breakdown { get; set; } + + public TransactionMixMeasure Measure { get; set; } + + public decimal TotalCount { get; set; } + + public decimal TotalValue { get; set; } + + public List Items { get; set; } = []; + + public List DrillDownTransactions { get; set; } = []; +} + +public class TransactionMixSummaryItem +{ + public string Key { get; set; } + + public string Label { get; set; } + + public decimal Count { get; set; } + + public decimal Value { get; set; } +} + +public class TransactionMixDrillDownTransaction +{ + public string Reference { get; set; } + + public string TransactionType { get; set; } + + public string Product { get; set; } + + public string Operator { get; set; } + + public string Status { get; set; } + + public decimal Amount { get; set; } + + public DateTime TransactionDateTime { get; set; } +} + +public enum TransactionMixBreakdown +{ + NotSet = 0, + TransactionType = 1, + Product = 2, + Operator = 3, + Status = 4 +} + +public enum TransactionMixMeasure +{ + NotSet = 0, + Count = 1, + Value = 2 +} diff --git a/TransactionProcessorACL.Testing/TestData.cs b/TransactionProcessorACL.Testing/TestData.cs index 0539d00..d3c198d 100644 --- a/TransactionProcessorACL.Testing/TestData.cs +++ b/TransactionProcessorACL.Testing/TestData.cs @@ -9,6 +9,7 @@ namespace TransactionProcessorACL.Testing using TransactionProcessorACL.BusinessLogic.Requests; using TransactionProcessorACL.Common; using TransactionProcessorACL.DataTransferObjects; + using TransactionProcessorACL.DataTransferObjects.Requests; using GetVoucherResponse = TransactionProcessor.DataTransferObjects.GetVoucherResponse; using RedeemVoucherResponse = TransactionProcessor.DataTransferObjects.RedeemVoucherResponse; @@ -220,6 +221,23 @@ public class TestData public static MerchantQueries.GetMerchantContractsQuery GetMerchantContractsQuery => new(EstateId,MerchantId); public static MerchantQueries.GetMerchantQuery GetMerchantQuery => new(EstateId, MerchantId); public static MerchantQueries.GetMerchantScheduleQuery GetMerchantScheduleQuery => new(EstateId, MerchantId, 2026); + public static ReportingQueries.GetMerchantDailyPerformanceSummaryQuery GetMerchantDailyPerformanceSummaryQuery => new(EstateId, + new MerchantDailyPerformanceSummaryRequest + { + MerchantReportingId = 12345, + StartDate = new DateTime(2026, 7, 1), + EndDate = new DateTime(2026, 7, 1) + }); + public static ReportingQueries.GetMerchantTransactionMixSummaryQuery GetMerchantTransactionMixSummaryQuery => new(EstateId, + new MerchantTransactionMixSummaryRequest + { + MerchantReportingId = 12345, + StartDate = new DateTime(2026, 7, 1), + EndDate = new DateTime(2026, 7, 3), + Breakdown = TransactionProcessorACL.DataTransferObjects.Requests.TransactionMixBreakdown.Product, + Measure = TransactionProcessorACL.DataTransferObjects.Requests.TransactionMixMeasure.Count, + TopN = 5 + }); public static VoucherQueries.GetVoucherQuery GetVoucherQuery => new(TestData.EstateId, TestData.ContractId, TestData.VoucherCode); diff --git a/TransactionProcessorACL.Tests/Handlers/ReportingHandlersTests.cs b/TransactionProcessorACL.Tests/Handlers/ReportingHandlersTests.cs index 5dc03d1..68e4176 100644 --- a/TransactionProcessorACL.Tests/Handlers/ReportingHandlersTests.cs +++ b/TransactionProcessorACL.Tests/Handlers/ReportingHandlersTests.cs @@ -2,10 +2,11 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using MediatR; using Moq; using Shouldly; using SimpleResults; -using TransactionProcessorACL.BusinessLogic.Services; +using TransactionProcessorACL.BusinessLogic.Requests; using TransactionProcessorACL.DataTransferObjects.Requests; using TransactionProcessorACL.Handlers; using Xunit; @@ -15,7 +16,7 @@ namespace TransactionProcessorACL.Tests.Handlers; public class ReportingHandlersTests { [Fact] - public async Task GetMerchantDailyPerformanceSummary_PassesEstateClaimAndRequestToApplicationService() + public async Task GetMerchantDailyPerformanceSummary_PassesEstateClaimAndRequestToMediator() { var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { @@ -29,25 +30,57 @@ public async Task GetMerchantDailyPerformanceSummary_PassesEstateClaimAndRequest EndDate = new DateTime(2026, 7, 1) }; - MerchantDailyPerformanceSummaryRequest? capturedRequest = null; - Guid capturedEstateId = Guid.Empty; - - var applicationService = new Mock(MockBehavior.Strict); - applicationService - .Setup(a => a.GetMerchantDailyPerformanceSummary(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((estateId, payload, _) => - { - capturedEstateId = estateId; - capturedRequest = payload; - }) + ReportingQueries.GetMerchantDailyPerformanceSummaryQuery? capturedQuery = null; + + var mediator = new Mock(MockBehavior.Strict); + mediator + .Setup(m => m.Send(It.IsAny(), It.IsAny())) + .Callback((payload, _) => capturedQuery = (ReportingQueries.GetMerchantDailyPerformanceSummaryQuery)payload) .ReturnsAsync(Result.Success(new TransactionProcessorACL.Models.MerchantDailyPerformanceSummaryResponse())); - var result = await ReportingHandlers.GetMerchantDailyPerformanceSummary(user, request, applicationService.Object, CancellationToken.None); + var result = await ReportingHandlers.GetMerchantDailyPerformanceSummary(user, request, mediator.Object, CancellationToken.None); + + result.ShouldNotBeNull(); + capturedQuery.ShouldNotBeNull(); + capturedQuery!.EstateId.ShouldBe(Guid.Parse("1C8354B7-B97A-46EA-9AD1-C43F33F7E3C3")); + capturedQuery.Request.MerchantReportingId.ShouldBe(12345); + mediator.Verify(m => m.Send(It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public async Task GetMerchantTransactionMixSummary_PassesEstateClaimAndRequestToMediator() + { + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim("estateId", "1C8354B7-B97A-46EA-9AD1-C43F33F7E3C3") + }, "Bearer")); + + var request = new MerchantTransactionMixSummaryRequest + { + MerchantReportingId = 12345, + StartDate = new DateTime(2026, 7, 1), + EndDate = new DateTime(2026, 7, 3), + Breakdown = TransactionMixBreakdown.Product, + Measure = TransactionMixMeasure.Count, + TopN = 5 + }; + + ReportingQueries.GetMerchantTransactionMixSummaryQuery? capturedQuery = null; + + var mediator = new Mock(MockBehavior.Strict); + mediator + .Setup(m => m.Send(It.IsAny(), It.IsAny())) + .Callback((payload, _) => capturedQuery = (ReportingQueries.GetMerchantTransactionMixSummaryQuery)payload) + .ReturnsAsync(Result.Success(new TransactionProcessorACL.Models.MerchantTransactionMixSummaryResponse())); + + var result = await ReportingHandlers.GetMerchantTransactionMixSummary(user, request, mediator.Object, CancellationToken.None); result.ShouldNotBeNull(); - capturedEstateId.ShouldBe(Guid.Parse("1C8354B7-B97A-46EA-9AD1-C43F33F7E3C3")); - capturedRequest.ShouldNotBeNull(); - capturedRequest!.MerchantReportingId.ShouldBe(12345); - applicationService.Verify(a => a.GetMerchantDailyPerformanceSummary(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + capturedQuery.ShouldNotBeNull(); + capturedQuery!.EstateId.ShouldBe(Guid.Parse("1C8354B7-B97A-46EA-9AD1-C43F33F7E3C3")); + capturedQuery.Request.MerchantReportingId.ShouldBe(12345); + capturedQuery.Request.Breakdown.ShouldBe(TransactionMixBreakdown.Product); + capturedQuery.Request.Measure.ShouldBe(TransactionMixMeasure.Count); + mediator.Verify(m => m.Send(It.IsAny(), It.IsAny()), Times.Once); } } diff --git a/TransactionProcessorACL/Endpoints/ReportingEndpoints.cs b/TransactionProcessorACL/Endpoints/ReportingEndpoints.cs index 0ac0d31..968eb4c 100644 --- a/TransactionProcessorACL/Endpoints/ReportingEndpoints.cs +++ b/TransactionProcessorACL/Endpoints/ReportingEndpoints.cs @@ -15,6 +15,8 @@ public static IEndpointRouteBuilder MapReportingEndpoints(this IEndpointRouteBui // POST /api/reporting/dailymerchantprformancesummary group.MapPost("dailymerchantprformancesummary", ReportingHandlers.GetMerchantDailyPerformanceSummary); + // POST /api/reporting/transactionmixsummary + group.MapPost("transactionmixsummary", ReportingHandlers.GetMerchantTransactionMixSummary); return app; } diff --git a/TransactionProcessorACL/Handlers/ReportingHandlers.cs b/TransactionProcessorACL/Handlers/ReportingHandlers.cs index 3911fa1..3ae728d 100644 --- a/TransactionProcessorACL/Handlers/ReportingHandlers.cs +++ b/TransactionProcessorACL/Handlers/ReportingHandlers.cs @@ -1,14 +1,13 @@ -using System; -using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using MediatR; using Microsoft.AspNetCore.Http; using Shared.General; using Shared.Results.Web; using SimpleResults; +using TransactionProcessorACL.BusinessLogic.Requests; using TransactionProcessorACL.DataTransferObjects.Requests; -using TransactionProcessorACL.Models; namespace TransactionProcessorACL.Handlers; @@ -16,14 +15,30 @@ public static class ReportingHandlers { public static async Task GetMerchantDailyPerformanceSummary(ClaimsPrincipal user, MerchantDailyPerformanceSummaryRequest request, - TransactionProcessorACL.BusinessLogic.Services.ITransactionProcessorACLApplicationService applicationService, + IMediator mediator, CancellationToken cancellationToken) { - Result estateIdResult = Helpers.GetRequiredEstateClaim(user); + Result estateIdResult = Helpers.GetRequiredEstateClaim(user); if (estateIdResult.IsFailed) return ResponseFactory.FromResult(Result.Forbidden()); - Result result = await applicationService.GetMerchantDailyPerformanceSummary(estateIdResult.Data, request, cancellationToken); + ReportingQueries.GetMerchantDailyPerformanceSummaryQuery query = new(estateIdResult.Data, request); + Result result = + await mediator.Send(query, cancellationToken); + return ResponseFactory.FromResult(result, response => response); + } + + public static async Task GetMerchantTransactionMixSummary(ClaimsPrincipal user, + MerchantTransactionMixSummaryRequest request, + IMediator mediator, + CancellationToken cancellationToken) + { + Result estateIdResult = Helpers.GetRequiredEstateClaim(user); + if (estateIdResult.IsFailed) + return ResponseFactory.FromResult(Result.Forbidden()); + + ReportingQueries.GetMerchantTransactionMixSummaryQuery query = new(estateIdResult.Data, request); + Result result = await mediator.Send(query, cancellationToken); return ResponseFactory.FromResult(result, response => response); } } From 3c171bfc61264e2a4511207a1fb012b3bcb7e722 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Sat, 4 Jul 2026 15:04:11 +0100 Subject: [PATCH 2/2] Add integration tests for Estate Reporting API - Add scenarios and step definitions for merchant daily performance and transaction mix summary endpoints - Update DockerHelper and test setup for EstateReporting container support - Extend EstateDetails1 to track reporting IDs and responses - Add Reporting.feature and generated .cs for new tests - Add ReportingDtos.cs for reporting API response deserialization - Update Shared.IntegrationTesting and ClientProxyBase package versions - Refactor and clean up code to support new reporting test flows --- Directory.Packages.props | 6 +- .../Common/DockerHelper.cs | 29 +- .../Common/EstateDetails.cs | 309 ++------ .../Common/GenericSteps.cs | 8 +- .../Reporting/Reporting.feature | 130 ++++ .../Reporting/Reporting.feature.cs | 701 ++++++++++++++++++ .../SalesTransaction.feature.cs | 216 +++--- .../Shared/ACLSteps.cs | 129 +++- .../Shared/ReportingDtos.cs | 52 ++ .../Shared/SharedSteps.cs | 35 +- 10 files changed, 1227 insertions(+), 388 deletions(-) create mode 100644 TransactionProcessorACL.IntegrationTests/Reporting/Reporting.feature create mode 100644 TransactionProcessorACL.IntegrationTests/Reporting/Reporting.feature.cs create mode 100644 TransactionProcessorACL.IntegrationTests/Shared/ReportingDtos.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index c4ddb3e..f5a6632 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,7 @@ - + @@ -39,7 +39,7 @@ - + @@ -54,4 +54,4 @@ - \ No newline at end of file + diff --git a/TransactionProcessorACL.IntegrationTests/Common/DockerHelper.cs b/TransactionProcessorACL.IntegrationTests/Common/DockerHelper.cs index d4e47be..2acb6ff 100644 --- a/TransactionProcessorACL.IntegrationTests/Common/DockerHelper.cs +++ b/TransactionProcessorACL.IntegrationTests/Common/DockerHelper.cs @@ -5,10 +5,6 @@ namespace TransactionProcessor.IntegrationTests.Common { using Client; - using Ductus.FluentDocker.Builders; - using Ductus.FluentDocker.Executors; - using Ductus.FluentDocker.Services; - using Ductus.FluentDocker.Services.Extensions; using SecurityService.Client; using Shared.Serialisation; using System; @@ -66,6 +62,15 @@ public DockerHelper() #region Methods + public override DotNet.Testcontainers.Builders.ContainerBuilder SetupEstateReportingContainer() + { + Dictionary environmentVariables = new(); + environmentVariables.Add("ConnectionStrings:TransactionProcessorReadModel", this.SetConnectionString("TransactionProcessorReadModel", this.UseSecureSqlServerDatabase)); + this.AdditionalVariables.Add(ContainerType.EstateReporting, environmentVariables); + + return base.SetupEstateReportingContainer(); + } + public override async Task CreateSubscriptions(){ List<(String streamName, String groupName, Int32 maxRetries)> subscriptions = new List<(String streamName, String groupName, Int32 maxRetries)>(); subscriptions.AddRange(MessagingService.IntegrationTesting.Helpers.SubscriptionsHelper.GetSubscriptions()); @@ -126,8 +131,22 @@ Object Deserialise(String arg, Type type) return StringSerialiser.DeserializeObject(arg, type, new SerialiserOptions(SerialiserPropertyFormat.SnakeCase)); } + public override Dictionary GetAdditionalVariables(ContainerType containerType) + { + Dictionary additionalVariables = base.GetAdditionalVariables(containerType); + + if (containerType == ContainerType.TransactionProcessorAcl && + (this.RequiredDockerServices & DockerServices.EstateReporting) == DockerServices.EstateReporting) + { + additionalVariables["AppSettings:EstateReportingApi"] = + $"http://{this.EstateReportingContainerName}:{DockerPorts.EstateReportingDockerPort}"; + } + + return additionalVariables; + } + #endregion } -} \ No newline at end of file +} diff --git a/TransactionProcessorACL.IntegrationTests/Common/EstateDetails.cs b/TransactionProcessorACL.IntegrationTests/Common/EstateDetails.cs index a152b2d..8cc87ac 100644 --- a/TransactionProcessorACL.IntegrationTests/Common/EstateDetails.cs +++ b/TransactionProcessorACL.IntegrationTests/Common/EstateDetails.cs @@ -1,19 +1,30 @@ -using TransactionProcessor.IntegrationTesting.Helpers; +using TransactionProcessor.IntegrationTesting.Helpers; +using TransactionProcessorACL.DataTransferObjects.Responses; +using TransactionProcessorACL.IntegrationTests.Shared; namespace TransactionProcessor.IntegrationTests.Common { using System; using System.Collections.Generic; using System.Linq; - - public class EstateDetails1{ + + public class EstateDetails1 + { public readonly EstateDetails EstateDetails; private readonly Dictionary> MerchantUsersTokens; + private readonly Dictionary MerchantReportingIds; + private readonly Dictionary MerchantDailyPerformanceSummaryResponses; + private readonly Dictionary MerchantTransactionMixSummaryResponses; private Dictionary> VoucherRedemptionUsersTokens; - public EstateDetails1(EstateDetails estateDetails){ + + public EstateDetails1(EstateDetails estateDetails) + { this.EstateDetails = estateDetails; this.MerchantUsersTokens = new Dictionary>(); + this.MerchantReportingIds = new Dictionary(); + this.MerchantDailyPerformanceSummaryResponses = new Dictionary(); + this.MerchantTransactionMixSummaryResponses = new Dictionary(); this.VoucherRedemptionUsersTokens = new Dictionary>(); } @@ -37,196 +48,42 @@ public void AddMerchantUserToken(Guid merchantId, } } - public void AddVoucherRedemptionUserToken(String operatorName, - String userName, - String token) + public void AddMerchantReportingId(Guid merchantId, + Int32 merchantReportingId) { - if (this.VoucherRedemptionUsersTokens.ContainsKey(operatorName)) + if (this.MerchantReportingIds.ContainsKey(merchantId)) { - Dictionary merchantUsersList = this.VoucherRedemptionUsersTokens[operatorName]; - if (merchantUsersList.ContainsKey(userName) == false) - { - merchantUsersList.Add(userName, token); - } + this.MerchantReportingIds[merchantId] = merchantReportingId; } else { - Dictionary merchantUsersList = new Dictionary(); - merchantUsersList.Add(userName, token); - this.VoucherRedemptionUsersTokens.Add(operatorName, merchantUsersList); + this.MerchantReportingIds.Add(merchantId, merchantReportingId); } } - public String GetMerchantUserToken(Guid merchantId) + public void AddMerchantDailyPerformanceSummaryResponse(Guid merchantId, + MerchantDailyPerformanceSummaryResponse response) { - KeyValuePair> x = this.MerchantUsersTokens.SingleOrDefault(x => x.Key == merchantId); - - if (x.Value != null) + if (this.MerchantDailyPerformanceSummaryResponses.ContainsKey(merchantId)) { - return x.Value.First().Value; - } - - return string.Empty; - } - - public String GetVoucherRedemptionUserToken(String operatorName) - { - KeyValuePair> x = this.VoucherRedemptionUsersTokens.SingleOrDefault(x => x.Key == operatorName); - - if (x.Value != null) - { - return x.Value.First().Value; - } - - return string.Empty; - } - } - - /* - public class EstateDetails1 - { - #region Fields - - /// - /// The contracts - /// - private readonly List Contracts; - - private readonly Dictionary Merchants; - - private readonly Dictionary> MerchantUsers; - - private readonly Dictionary> VoucherRedemptionUsers; - - private readonly Dictionary> MerchantUsersTokens; - - private Dictionary> VoucherRedemptionUsersTokens; - - private readonly Dictionary Operators; - - #endregion - - #region Constructors - - private EstateDetails(Guid estateId, - String estateName) - { - this.EstateId = estateId; - this.EstateName = estateName; - this.Merchants = new Dictionary(); - this.Operators = new Dictionary(); - this.MerchantUsers = new Dictionary>(); - this.MerchantUsersTokens = new Dictionary>(); - this.VoucherRedemptionUsers = new Dictionary>(); - this.VoucherRedemptionUsersTokens = new Dictionary>(); - this.TransactionResponses = new Dictionary<(Guid merchantId, String transactionNumber, String transactionType), String>(); - this.ReconciliationResponses = new Dictionary(); - this.Contracts = new List(); - } - - #endregion - - #region Properties - - public String AccessToken { get; private set; } - - public Guid EstateId { get; } - - public String EstateName { get; } - - public String EstatePassword { get; private set; } - - public String EstateUser { get; private set; } - - private Dictionary<(Guid merchantId, String transactionNumber, String transactionType), String> TransactionResponses { get; } - - private Dictionary ReconciliationResponses { get; } - - #endregion - - #region Methods - - /// - /// Adds the contract. - /// - /// The contract identifier. - /// Name of the contract. - /// The operator identifier. - public void AddContract(Guid contractId, - String contractName, - Guid operatorId) - { - this.Contracts.Add(new Contract - { - ContractId = contractId, - Description = contractName, - OperatorId = operatorId, - }); - } - - public void AddMerchant(Guid merchantId, - String merchantName) - { - this.Merchants.Add(merchantName, merchantId); - } - - public void AddMerchantUser(String merchantName, - String userName, - String password) - { - if (this.MerchantUsers.ContainsKey(merchantName)) - { - Dictionary merchantUsersList = this.MerchantUsers[merchantName]; - if (merchantUsersList.ContainsKey(userName) == false) - { - merchantUsersList.Add(userName, password); - } - } - else - { - Dictionary merchantUsersList = new Dictionary(); - merchantUsersList.Add(userName, password); - this.MerchantUsers.Add(merchantName, merchantUsersList); - } - } - - public void AddVoucherRedemptionUser(String operatorName, - String userName, - String password) - { - if (this.VoucherRedemptionUsers.ContainsKey(operatorName)) - { - Dictionary voucherRedemptionUsersList = this.VoucherRedemptionUsers[operatorName]; - if (voucherRedemptionUsersList.ContainsKey(userName) == false) - { - voucherRedemptionUsersList.Add(userName, password); - } + this.MerchantDailyPerformanceSummaryResponses[merchantId] = response; } else { - Dictionary voucherRedemptionUsersList = new Dictionary(); - voucherRedemptionUsersList.Add(userName, password); - this.VoucherRedemptionUsers.Add(operatorName, voucherRedemptionUsersList); + this.MerchantDailyPerformanceSummaryResponses.Add(merchantId, response); } } - public void AddMerchantUserToken(String merchantName, - String userName, - String token) + public void AddMerchantTransactionMixSummaryResponse(Guid merchantId, + MerchantTransactionMixSummaryResponse response) { - if (this.MerchantUsersTokens.ContainsKey(merchantName)) + if (this.MerchantTransactionMixSummaryResponses.ContainsKey(merchantId)) { - Dictionary merchantUsersList = this.MerchantUsersTokens[merchantName]; - if (merchantUsersList.ContainsKey(userName) == false) - { - merchantUsersList.Add(userName, token); - } + this.MerchantTransactionMixSummaryResponses[merchantId] = response; } else { - Dictionary merchantUsersList = new Dictionary(); - merchantUsersList.Add(userName, token); - this.MerchantUsersTokens.Add(merchantName, merchantUsersList); + this.MerchantTransactionMixSummaryResponses.Add(merchantId, response); } } @@ -250,35 +107,9 @@ public void AddVoucherRedemptionUserToken(String operatorName, } } - public void AddOperator(Guid operatorId, - String operatorName) - { - this.Operators.Add(operatorName, operatorId); - } - - public void AddTransactionResponse(Guid merchantId, - String transactionNumber, - String transactionType, - String transactionResponse) - { - this.TransactionResponses.Add((merchantId, transactionNumber, transactionType), transactionResponse); - } - - public void AddReconciliationResponse(Guid merchantId, - String transactionResponse) - { - this.ReconciliationResponses.Add(merchantId, transactionResponse); - } - - public static EstateDetails Create(Guid estateId, - String estateName) - { - return new EstateDetails(estateId, estateName); - } - - public String GetVoucherRedemptionUserToken(String operatorName) + public String GetMerchantUserToken(Guid merchantId) { - KeyValuePair> x = this.VoucherRedemptionUsersTokens.SingleOrDefault(x => x.Key == operatorName); + KeyValuePair> x = this.MerchantUsersTokens.SingleOrDefault(x => x.Key == merchantId); if (x.Value != null) { @@ -288,39 +119,24 @@ public String GetVoucherRedemptionUserToken(String operatorName) return string.Empty; } - /// - /// Gets the contract. - /// - /// Name of the contract. - /// - public Contract GetContract(String contractName) + public Int32 GetMerchantReportingId(Guid merchantId) { - if (this.Contracts.Any() == false) - { - return null; - } - - return this.Contracts.Single(c => c.Description == contractName); + return this.MerchantReportingIds.Single(x => x.Key == merchantId).Value; } - /// - /// Gets the contract. - /// - /// The contract identifier. - /// - public Contract GetContract(Guid contractId) + public MerchantDailyPerformanceSummaryResponse GetMerchantDailyPerformanceSummaryResponse(Guid merchantId) { - return this.Contracts.Single(c => c.ContractId == contractId); + return this.MerchantDailyPerformanceSummaryResponses.SingleOrDefault(x => x.Key == merchantId).Value; } - public Guid GetMerchantId(String merchantName) + public MerchantTransactionMixSummaryResponse GetMerchantTransactionMixSummaryResponse(Guid merchantId) { - return this.Merchants.Single(m => m.Key == merchantName).Value; + return this.MerchantTransactionMixSummaryResponses.SingleOrDefault(x => x.Key == merchantId).Value; } - public String GetMerchantUserToken(String merchantName) + public String GetVoucherRedemptionUserToken(String operatorName) { - KeyValuePair> x = this.MerchantUsersTokens.SingleOrDefault(x => x.Key == merchantName); + KeyValuePair> x = this.VoucherRedemptionUsersTokens.SingleOrDefault(x => x.Key == operatorName); if (x.Value != null) { @@ -329,46 +145,5 @@ public String GetMerchantUserToken(String merchantName) return string.Empty; } - - public Guid GetOperatorId(String operatorName) - { - return this.Operators.Single(o => o.Key == operatorName).Value; - } - - public String GetReconciliationResponse(Guid merchantId) - { - var reconciliationResponse = - this.ReconciliationResponses - .Where(t => t.Key == merchantId) - .SingleOrDefault(); - - return reconciliationResponse.Value; - } - - public String GetTransactionResponse(Guid merchantId, - String transactionNumber, - String transactionType) - { - KeyValuePair<(Guid merchantId, String transactionNumber, String transactionType), String> transactionResponse = - this.TransactionResponses - .Where(t => t.Key.merchantId == merchantId && t.Key.transactionNumber == transactionNumber && t.Key.transactionType == transactionType) - .SingleOrDefault(); - - return transactionResponse.Value; - } - - public void SetEstateUser(String userName, - String password) - { - this.EstateUser = userName; - this.EstatePassword = password; - } - - public void SetEstateUserToken(String accessToken) - { - this.AccessToken = accessToken; - } - - #endregion - }*/ -} \ No newline at end of file + } +} diff --git a/TransactionProcessorACL.IntegrationTests/Common/GenericSteps.cs b/TransactionProcessorACL.IntegrationTests/Common/GenericSteps.cs index dad5370..729a3d5 100644 --- a/TransactionProcessorACL.IntegrationTests/Common/GenericSteps.cs +++ b/TransactionProcessorACL.IntegrationTests/Common/GenericSteps.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace TransactionProcessor.IntegrationTests.Common @@ -38,7 +39,12 @@ public async Task StartSystem() DockerServices dockerServices = DockerServices.CallbackHandler | DockerServices.EventStore | DockerServices.FileProcessor | DockerServices.MessagingService | DockerServices.SecurityService | DockerServices.SqlServer | DockerServices.TestHost | DockerServices.TransactionProcessor | - DockerServices.TransactionProcessorAcl; + DockerServices.TransactionProcessorAcl | DockerServices.EstateReporting; + + if (this.ScenarioContext.ScenarioInfo.Tags.Any(tag => tag.Equals("reporting", StringComparison.OrdinalIgnoreCase))) + { + dockerServices |= DockerServices.EstateReporting; + } this.TestingContext.DockerHelper = new DockerHelper(); this.TestingContext.DockerHelper.Logger = logger; diff --git a/TransactionProcessorACL.IntegrationTests/Reporting/Reporting.feature b/TransactionProcessorACL.IntegrationTests/Reporting/Reporting.feature new file mode 100644 index 0000000..2c6c852 --- /dev/null +++ b/TransactionProcessorACL.IntegrationTests/Reporting/Reporting.feature @@ -0,0 +1,130 @@ +@base @shared @reporting +Feature: Reporting + +Background: + + Given the following security roles exist + | Role Name | + | Merchant | + + Given I create the following api scopes + | Name | DisplayName | Description | + | transactionProcessor | Transaction Processor REST Scope | A scope for Transaction Processor REST | + | transactionProcessorACL | Transaction Processor ACL REST Scope | A scope for Transaction Processor ACL REST | + | estateReporting | Estate Reporting REST Scope | Scope for Estate Reporting REST | + + Given the following api resources exist + | Name | DisplayName | Secret | Scopes | UserClaims | + | transactionProcessor | Transaction Processor REST | Secret1 | transactionProcessor | merchantId, estateId, role | + | transactionProcessorACL | Transaction Processor ACL REST | Secret1 | transactionProcessorACL | merchantId, estateId, role | + | estateReporting | Estate Reporting REST | Secret1 | estateReporting | merchantId,estateId,role | + + Given the following clients exist + | ClientId | ClientName | Secret | Scopes | GrantTypes | + | serviceClient | Service Client | Secret1 | transactionProcessor,transactionProcessorACL, estateReporting | client_credentials | + | merchantClient | Merchant Client | Secret1 | transactionProcessorACL | password | + + Given I have a token to access the estate management and transaction processor acl resources + | ClientId | + | serviceClient | + + Given I have created the following estates + | EstateName | + | Test Estate 1 | + | Test Estate 2 | + + Given I have created the following operators + | EstateName | OperatorName | RequireCustomMerchantNumber | RequireCustomTerminalNumber | + | Test Estate 1 | Safaricom | True | True | + | Test Estate 1 | Voucher | True | True | + | Test Estate 2 | Safaricom | True | True | + | Test Estate 2 | Voucher | True | True | + + And I have assigned the following operators to the estates + | EstateName | OperatorName | + | Test Estate 1 | Safaricom | + | Test Estate 1 | Voucher | + | Test Estate 2 | Safaricom | + | Test Estate 2 | Voucher | + + Given I create a contract with the following values + | EstateName | OperatorName | ContractDescription | + | Test Estate 1 | Safaricom | Safaricom Contract | + | Test Estate 1 | Voucher | Hospital 1 Contract | + | Test Estate 2 | Safaricom | Safaricom Contract | + | Test Estate 2 | Voucher | Hospital 1 Contract | + + When I create the following Products + | EstateName | OperatorName | ContractDescription | ProductName | DisplayText | Value | ProductType | + | Test Estate 1 | Safaricom | Safaricom Contract | Variable Topup | Custom | | MobileTopup | + | Test Estate 1 | Voucher | Hospital 1 Contract | 10 KES | 10 KES | | Voucher | + | Test Estate 2 | Safaricom | Safaricom Contract | Variable Topup | Custom | | MobileTopup | + | Test Estate 2 | Voucher | Hospital 1 Contract | 10 KES | 10 KES | | Voucher | + + When I add the following Transaction Fees + | EstateName | OperatorName | ContractDescription | ProductName | CalculationType | FeeDescription | Value | + | Test Estate 1 | Safaricom | Safaricom Contract | Variable Topup | Fixed | Merchant Commission | 2.50 | + | Test Estate 2 | Safaricom | Safaricom Contract | Variable Topup | Percentage | Merchant Commission | 0.85 | + + Given I create the following merchants + | MerchantName | AddressLine1 | Town | Region | PostalCode |Country | ContactName | EmailAddress | EstateName | + | Test Merchant 1 | Address Line 1 | TestTown | Test Region | TE57 1NG |United Kingdom | Test Contact 1 | testcontact1@merchant1.co.uk | Test Estate 1 | + | Test Merchant 2 | Address Line 1 | TestTown | Test Region | TE57 2NG |United Kingdom | Test Contact 2 | testcontact2@merchant2.co.uk | Test Estate 1 | + | Test Merchant 3 | Address Line 1 | TestTown | Test Region | TE57 3NG |United Kingdom | Test Contact 3 | testcontact3@merchant2.co.uk | Test Estate 2 | + + Given I have assigned the following operator to the merchants + | OperatorName | MerchantName | MerchantNumber | TerminalNumber | EstateName | + | Safaricom | Test Merchant 1 | 00000001 | 10000001 | Test Estate 1 | + | Voucher | Test Merchant 1 | 00000001 | 10000001 | Test Estate 1 | + | Safaricom | Test Merchant 2 | 00000002 | 10000002 | Test Estate 1 | + | Voucher | Test Merchant 2 | 00000002 | 10000002 | Test Estate 1 | + | Safaricom | Test Merchant 3 | 00000003 | 10000003 | Test Estate 2 | + | Voucher | Test Merchant 3 | 00000003 | 10000003 | Test Estate 2 | + + Given I have assigned the following devices to the merchants + | DeviceIdentifier | MerchantName | EstateName | + | 123456780 | Test Merchant 1 | Test Estate 1 | + | 123456781 | Test Merchant 2 | Test Estate 1 | + | 123456782 | Test Merchant 3 | Test Estate 2 | + + When I add the following contracts to the following merchants + | EstateName | MerchantName | ContractDescription | + | Test Estate 1 | Test Merchant 1 | Safaricom Contract | + | Test Estate 1 | Test Merchant 1 | Hospital 1 Contract | + | Test Estate 1 | Test Merchant 2 | Safaricom Contract | + | Test Estate 1 | Test Merchant 2 | Hospital 1 Contract | + | Test Estate 2 | Test Merchant 3 | Safaricom Contract | + | Test Estate 2 | Test Merchant 3 | Hospital 1 Contract | + + Given I make the following manual merchant deposits + | Reference | Amount | DateTime | MerchantName | EstateName | + | Deposit1 | 210.00 | Today | Test Merchant 1 | Test Estate 1 | + | Deposit1 | 110.00 | Today | Test Merchant 2 | Test Estate 1 | + | Deposit1 | 110.00 | Today | Test Merchant 3 | Test Estate 2 | + + Given I have created the following security users + | EmailAddress | Password | GivenName | FamilyName | EstateName | MerchantName | + | merchantuser@testmerchant1.co.uk | 123456 | TestMerchant | User1 | Test Estate 1 | Test Merchant 1 | + | merchantuser@testmerchant2.co.uk | 123456 | TestMerchant | User2 | Test Estate 1 | Test Merchant 2 | + | merchantuser@testmerchant3.co.uk | 123456 | TestMerchant | User3 | Test Estate 2 | Test Merchant 3 | + +@PRTest +Scenario: Merchant daily performance summary + Given I am logged in as "merchantuser@testmerchant1.co.uk" with password "123456" for Merchant "Test Merchant 1" for Estate "Test Estate 1" with client "merchantClient" + When I perform the following transactions + | DateTime | TransactionNumber | TransactionType | MerchantName | DeviceIdentifier | EstateName | OperatorName | TransactionAmount | CustomerAccountNumber | CustomerEmailAddress | ContractDescription | ProductName | RecipientEmail | RecipientMobile | + | Today | 8 | Sale | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 50.00 | 123456789 | | Safaricom Contract | Variable Topup | | | + + When I get the merchant daily performance summary for Merchant "Test Merchant 1" for Estate "Test Estate 1" + Then the merchant daily performance summary response should contain at least one metric and the sale amount 50.00 + +@PRTest +@ignore +Scenario: Merchant transaction mix summary + Given I am logged in as "merchantuser@testmerchant1.co.uk" with password "123456" for Merchant "Test Merchant 1" for Estate "Test Estate 1" with client "merchantClient" + When I perform the following transactions + | DateTime | TransactionNumber | TransactionType | MerchantName | DeviceIdentifier | EstateName | OperatorName | TransactionAmount | CustomerAccountNumber | CustomerEmailAddress | ContractDescription | ProductName | RecipientEmail | RecipientMobile | + | Today | 9 | Sale | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 25.00 | 123456789 | | Safaricom Contract | Variable Topup | | | + + When I get the merchant transaction mix summary for Merchant "Test Merchant 1" for Estate "Test Estate 1" + Then the merchant transaction mix summary response should contain at least one item and the sale amount 25.00 diff --git a/TransactionProcessorACL.IntegrationTests/Reporting/Reporting.feature.cs b/TransactionProcessorACL.IntegrationTests/Reporting/Reporting.feature.cs new file mode 100644 index 0000000..6da3c19 --- /dev/null +++ b/TransactionProcessorACL.IntegrationTests/Reporting/Reporting.feature.cs @@ -0,0 +1,701 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by Reqnroll (https://reqnroll.net/). +// Reqnroll Version:3.0.0.0 +// Reqnroll Generator Version:3.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +using Reqnroll; +namespace TransactionProcessorACL.IntegrationTests.Reporting +{ + + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Reqnroll", "3.0.0.0")] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::NUnit.Framework.TestFixtureAttribute()] + [global::NUnit.Framework.DescriptionAttribute("Reporting")] + [global::NUnit.Framework.FixtureLifeCycleAttribute(global::NUnit.Framework.LifeCycle.InstancePerTestCase)] + [global::NUnit.Framework.CategoryAttribute("base")] + [global::NUnit.Framework.CategoryAttribute("shared")] + [global::NUnit.Framework.CategoryAttribute("reporting")] + public partial class ReportingFeature + { + + private global::Reqnroll.ITestRunner testRunner; + + private static string[] featureTags = new string[] { + "base", + "shared", + "reporting"}; + + private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "Reporting", "Reporting", null, global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); + +#line 1 "Reporting.feature" +#line hidden + + [global::NUnit.Framework.OneTimeSetUpAttribute()] + public static async global::System.Threading.Tasks.Task FeatureSetupAsync() + { + } + + [global::NUnit.Framework.OneTimeTearDownAttribute()] + public static async global::System.Threading.Tasks.Task FeatureTearDownAsync() + { + await global::Reqnroll.TestRunnerManager.ReleaseFeatureAsync(featureInfo); + } + + [global::NUnit.Framework.SetUpAttribute()] + public async global::System.Threading.Tasks.Task TestInitializeAsync() + { + testRunner = global::Reqnroll.TestRunnerManager.GetTestRunnerForAssembly(featureHint: featureInfo); + try + { + if (((testRunner.FeatureContext != null) + && (testRunner.FeatureContext.FeatureInfo.Equals(featureInfo) == false))) + { + await testRunner.OnFeatureEndAsync(); + } + } + finally + { + if (((testRunner.FeatureContext != null) + && testRunner.FeatureContext.BeforeFeatureHookFailed)) + { + throw new global::Reqnroll.ReqnrollException("Scenario skipped because of previous before feature hook error"); + } + if ((testRunner.FeatureContext == null)) + { + await testRunner.OnFeatureStartAsync(featureInfo); + } + } + } + + [global::NUnit.Framework.TearDownAttribute()] + public async global::System.Threading.Tasks.Task TestTearDownAsync() + { + if ((testRunner == null)) + { + return; + } + try + { + await testRunner.OnScenarioEndAsync(); + } + finally + { + global::Reqnroll.TestRunnerManager.ReleaseTestRunner(testRunner); + testRunner = null; + } + } + + public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, global::Reqnroll.RuleInfo ruleInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo, ruleInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(global::NUnit.Framework.TestContext.CurrentContext); + } + + public async global::System.Threading.Tasks.Task ScenarioStartAsync() + { + await testRunner.OnScenarioStartAsync(); + } + + public async global::System.Threading.Tasks.Task ScenarioCleanupAsync() + { + await testRunner.CollectScenarioErrorsAsync(); + } + + public virtual async global::System.Threading.Tasks.Task FeatureBackgroundAsync() + { +#line 4 +#line hidden + global::Reqnroll.Table table1 = new global::Reqnroll.Table(new string[] { + "Role Name"}); + table1.AddRow(new string[] { + "Merchant"}); +#line 6 + await testRunner.GivenAsync("the following security roles exist", ((string)(null)), table1, "Given "); +#line hidden + global::Reqnroll.Table table2 = new global::Reqnroll.Table(new string[] { + "Name", + "DisplayName", + "Description"}); + table2.AddRow(new string[] { + "transactionProcessor", + "Transaction Processor REST Scope", + "A scope for Transaction Processor REST"}); + table2.AddRow(new string[] { + "transactionProcessorACL", + "Transaction Processor ACL REST Scope", + "A scope for Transaction Processor ACL REST"}); + table2.AddRow(new string[] { + "estateReporting", + "Estate Reporting REST Scope", + "Scope for Estate Reporting REST"}); +#line 10 + await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table2, "Given "); +#line hidden + global::Reqnroll.Table table3 = new global::Reqnroll.Table(new string[] { + "Name", + "DisplayName", + "Secret", + "Scopes", + "UserClaims"}); + table3.AddRow(new string[] { + "transactionProcessor", + "Transaction Processor REST", + "Secret1", + "transactionProcessor", + "merchantId, estateId, role"}); + table3.AddRow(new string[] { + "transactionProcessorACL", + "Transaction Processor ACL REST", + "Secret1", + "transactionProcessorACL", + "merchantId, estateId, role"}); + table3.AddRow(new string[] { + "estateReporting", + "Estate Reporting REST", + "Secret1", + "estateReporting", + "merchantId,estateId,role"}); +#line 16 + await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table3, "Given "); +#line hidden + global::Reqnroll.Table table4 = new global::Reqnroll.Table(new string[] { + "ClientId", + "ClientName", + "Secret", + "Scopes", + "GrantTypes"}); + table4.AddRow(new string[] { + "serviceClient", + "Service Client", + "Secret1", + "transactionProcessor,transactionProcessorACL, estateReporting", + "client_credentials"}); + table4.AddRow(new string[] { + "merchantClient", + "Merchant Client", + "Secret1", + "transactionProcessorACL", + "password"}); +#line 22 + await testRunner.GivenAsync("the following clients exist", ((string)(null)), table4, "Given "); +#line hidden + global::Reqnroll.Table table5 = new global::Reqnroll.Table(new string[] { + "ClientId"}); + table5.AddRow(new string[] { + "serviceClient"}); +#line 27 + await testRunner.GivenAsync("I have a token to access the estate management and transaction processor acl reso" + + "urces", ((string)(null)), table5, "Given "); +#line hidden + global::Reqnroll.Table table6 = new global::Reqnroll.Table(new string[] { + "EstateName"}); + table6.AddRow(new string[] { + "Test Estate 1"}); + table6.AddRow(new string[] { + "Test Estate 2"}); +#line 31 + await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table6, "Given "); +#line hidden + global::Reqnroll.Table table7 = new global::Reqnroll.Table(new string[] { + "EstateName", + "OperatorName", + "RequireCustomMerchantNumber", + "RequireCustomTerminalNumber"}); + table7.AddRow(new string[] { + "Test Estate 1", + "Safaricom", + "True", + "True"}); + table7.AddRow(new string[] { + "Test Estate 1", + "Voucher", + "True", + "True"}); + table7.AddRow(new string[] { + "Test Estate 2", + "Safaricom", + "True", + "True"}); + table7.AddRow(new string[] { + "Test Estate 2", + "Voucher", + "True", + "True"}); +#line 36 + await testRunner.GivenAsync("I have created the following operators", ((string)(null)), table7, "Given "); +#line hidden + global::Reqnroll.Table table8 = new global::Reqnroll.Table(new string[] { + "EstateName", + "OperatorName"}); + table8.AddRow(new string[] { + "Test Estate 1", + "Safaricom"}); + table8.AddRow(new string[] { + "Test Estate 1", + "Voucher"}); + table8.AddRow(new string[] { + "Test Estate 2", + "Safaricom"}); + table8.AddRow(new string[] { + "Test Estate 2", + "Voucher"}); +#line 43 + await testRunner.AndAsync("I have assigned the following operators to the estates", ((string)(null)), table8, "And "); +#line hidden + global::Reqnroll.Table table9 = new global::Reqnroll.Table(new string[] { + "EstateName", + "OperatorName", + "ContractDescription"}); + table9.AddRow(new string[] { + "Test Estate 1", + "Safaricom", + "Safaricom Contract"}); + table9.AddRow(new string[] { + "Test Estate 1", + "Voucher", + "Hospital 1 Contract"}); + table9.AddRow(new string[] { + "Test Estate 2", + "Safaricom", + "Safaricom Contract"}); + table9.AddRow(new string[] { + "Test Estate 2", + "Voucher", + "Hospital 1 Contract"}); +#line 50 + await testRunner.GivenAsync("I create a contract with the following values", ((string)(null)), table9, "Given "); +#line hidden + global::Reqnroll.Table table10 = new global::Reqnroll.Table(new string[] { + "EstateName", + "OperatorName", + "ContractDescription", + "ProductName", + "DisplayText", + "Value", + "ProductType"}); + table10.AddRow(new string[] { + "Test Estate 1", + "Safaricom", + "Safaricom Contract", + "Variable Topup", + "Custom", + "", + "MobileTopup"}); + table10.AddRow(new string[] { + "Test Estate 1", + "Voucher", + "Hospital 1 Contract", + "10 KES", + "10 KES", + "", + "Voucher"}); + table10.AddRow(new string[] { + "Test Estate 2", + "Safaricom", + "Safaricom Contract", + "Variable Topup", + "Custom", + "", + "MobileTopup"}); + table10.AddRow(new string[] { + "Test Estate 2", + "Voucher", + "Hospital 1 Contract", + "10 KES", + "10 KES", + "", + "Voucher"}); +#line 57 + await testRunner.WhenAsync("I create the following Products", ((string)(null)), table10, "When "); +#line hidden + global::Reqnroll.Table table11 = new global::Reqnroll.Table(new string[] { + "EstateName", + "OperatorName", + "ContractDescription", + "ProductName", + "CalculationType", + "FeeDescription", + "Value"}); + table11.AddRow(new string[] { + "Test Estate 1", + "Safaricom", + "Safaricom Contract", + "Variable Topup", + "Fixed", + "Merchant Commission", + "2.50"}); + table11.AddRow(new string[] { + "Test Estate 2", + "Safaricom", + "Safaricom Contract", + "Variable Topup", + "Percentage", + "Merchant Commission", + "0.85"}); +#line 64 + await testRunner.WhenAsync("I add the following Transaction Fees", ((string)(null)), table11, "When "); +#line hidden + global::Reqnroll.Table table12 = new global::Reqnroll.Table(new string[] { + "MerchantName", + "AddressLine1", + "Town", + "Region", + "PostalCode", + "Country", + "ContactName", + "EmailAddress", + "EstateName"}); + table12.AddRow(new string[] { + "Test Merchant 1", + "Address Line 1", + "TestTown", + "Test Region", + "TE57 1NG", + "United Kingdom", + "Test Contact 1", + "testcontact1@merchant1.co.uk", + "Test Estate 1"}); + table12.AddRow(new string[] { + "Test Merchant 2", + "Address Line 1", + "TestTown", + "Test Region", + "TE57 2NG", + "United Kingdom", + "Test Contact 2", + "testcontact2@merchant2.co.uk", + "Test Estate 1"}); + table12.AddRow(new string[] { + "Test Merchant 3", + "Address Line 1", + "TestTown", + "Test Region", + "TE57 3NG", + "United Kingdom", + "Test Contact 3", + "testcontact3@merchant2.co.uk", + "Test Estate 2"}); +#line 69 + await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table12, "Given "); +#line hidden + global::Reqnroll.Table table13 = new global::Reqnroll.Table(new string[] { + "OperatorName", + "MerchantName", + "MerchantNumber", + "TerminalNumber", + "EstateName"}); + table13.AddRow(new string[] { + "Safaricom", + "Test Merchant 1", + "00000001", + "10000001", + "Test Estate 1"}); + table13.AddRow(new string[] { + "Voucher", + "Test Merchant 1", + "00000001", + "10000001", + "Test Estate 1"}); + table13.AddRow(new string[] { + "Safaricom", + "Test Merchant 2", + "00000002", + "10000002", + "Test Estate 1"}); + table13.AddRow(new string[] { + "Voucher", + "Test Merchant 2", + "00000002", + "10000002", + "Test Estate 1"}); + table13.AddRow(new string[] { + "Safaricom", + "Test Merchant 3", + "00000003", + "10000003", + "Test Estate 2"}); + table13.AddRow(new string[] { + "Voucher", + "Test Merchant 3", + "00000003", + "10000003", + "Test Estate 2"}); +#line 75 + await testRunner.GivenAsync("I have assigned the following operator to the merchants", ((string)(null)), table13, "Given "); +#line hidden + global::Reqnroll.Table table14 = new global::Reqnroll.Table(new string[] { + "DeviceIdentifier", + "MerchantName", + "EstateName"}); + table14.AddRow(new string[] { + "123456780", + "Test Merchant 1", + "Test Estate 1"}); + table14.AddRow(new string[] { + "123456781", + "Test Merchant 2", + "Test Estate 1"}); + table14.AddRow(new string[] { + "123456782", + "Test Merchant 3", + "Test Estate 2"}); +#line 84 + await testRunner.GivenAsync("I have assigned the following devices to the merchants", ((string)(null)), table14, "Given "); +#line hidden + global::Reqnroll.Table table15 = new global::Reqnroll.Table(new string[] { + "EstateName", + "MerchantName", + "ContractDescription"}); + table15.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "Safaricom Contract"}); + table15.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "Hospital 1 Contract"}); + table15.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 2", + "Safaricom Contract"}); + table15.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 2", + "Hospital 1 Contract"}); + table15.AddRow(new string[] { + "Test Estate 2", + "Test Merchant 3", + "Safaricom Contract"}); + table15.AddRow(new string[] { + "Test Estate 2", + "Test Merchant 3", + "Hospital 1 Contract"}); +#line 90 + await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table15, "When "); +#line hidden + global::Reqnroll.Table table16 = new global::Reqnroll.Table(new string[] { + "Reference", + "Amount", + "DateTime", + "MerchantName", + "EstateName"}); + table16.AddRow(new string[] { + "Deposit1", + "210.00", + "Today", + "Test Merchant 1", + "Test Estate 1"}); + table16.AddRow(new string[] { + "Deposit1", + "110.00", + "Today", + "Test Merchant 2", + "Test Estate 1"}); + table16.AddRow(new string[] { + "Deposit1", + "110.00", + "Today", + "Test Merchant 3", + "Test Estate 2"}); +#line 99 + await testRunner.GivenAsync("I make the following manual merchant deposits", ((string)(null)), table16, "Given "); +#line hidden + global::Reqnroll.Table table17 = new global::Reqnroll.Table(new string[] { + "EmailAddress", + "Password", + "GivenName", + "FamilyName", + "EstateName", + "MerchantName"}); + table17.AddRow(new string[] { + "merchantuser@testmerchant1.co.uk", + "123456", + "TestMerchant", + "User1", + "Test Estate 1", + "Test Merchant 1"}); + table17.AddRow(new string[] { + "merchantuser@testmerchant2.co.uk", + "123456", + "TestMerchant", + "User2", + "Test Estate 1", + "Test Merchant 2"}); + table17.AddRow(new string[] { + "merchantuser@testmerchant3.co.uk", + "123456", + "TestMerchant", + "User3", + "Test Estate 2", + "Test Merchant 3"}); +#line 105 + await testRunner.GivenAsync("I have created the following security users", ((string)(null)), table17, "Given "); +#line hidden + } + + private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() + { + return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("Reporting/Reporting.feature.ndjson", 4); + } + + [global::NUnit.Framework.TestAttribute()] + [global::NUnit.Framework.DescriptionAttribute("Merchant daily performance summary")] + [global::NUnit.Framework.CategoryAttribute("PRTest")] + public async global::System.Threading.Tasks.Task MerchantDailyPerformanceSummary() + { + string[] tagsOfScenario = new string[] { + "PRTest"}; + global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); + string pickleIndex = "0"; + global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Merchant daily performance summary", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); + string[] tagsOfRule = ((string[])(null)); + global::Reqnroll.RuleInfo ruleInfo = null; +#line 112 +this.ScenarioInitialize(scenarioInfo, ruleInfo); +#line hidden + if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) + { + await testRunner.SkipScenarioAsync(); + } + else + { + await this.ScenarioStartAsync(); +#line 4 +await this.FeatureBackgroundAsync(); +#line hidden +#line 113 + await testRunner.GivenAsync("I am logged in as \"merchantuser@testmerchant1.co.uk\" with password \"123456\" for M" + + "erchant \"Test Merchant 1\" for Estate \"Test Estate 1\" with client \"merchantClient" + + "\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); +#line hidden + global::Reqnroll.Table table18 = new global::Reqnroll.Table(new string[] { + "DateTime", + "TransactionNumber", + "TransactionType", + "MerchantName", + "DeviceIdentifier", + "EstateName", + "OperatorName", + "TransactionAmount", + "CustomerAccountNumber", + "CustomerEmailAddress", + "ContractDescription", + "ProductName", + "RecipientEmail", + "RecipientMobile"}); + table18.AddRow(new string[] { + "Today", + "8", + "Sale", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Safaricom", + "50.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup", + "", + ""}); +#line 114 + await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table18, "When "); +#line hidden +#line 118 + await testRunner.WhenAsync("I get the merchant daily performance summary for Merchant \"Test Merchant 1\" for E" + + "state \"Test Estate 1\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); +#line hidden +#line 119 + await testRunner.ThenAsync("the merchant daily performance summary response should contain at least one metri" + + "c and the sale amount 50.00", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); +#line hidden + } + await this.ScenarioCleanupAsync(); + } + + [global::NUnit.Framework.TestAttribute()] + [global::NUnit.Framework.DescriptionAttribute("Merchant transaction mix summary")] + [global::NUnit.Framework.CategoryAttribute("PRTest")] + public async global::System.Threading.Tasks.Task MerchantTransactionMixSummary() + { + string[] tagsOfScenario = new string[] { + "PRTest"}; + global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); + string pickleIndex = "1"; + global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Merchant transaction mix summary", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); + string[] tagsOfRule = ((string[])(null)); + global::Reqnroll.RuleInfo ruleInfo = null; +#line 122 +this.ScenarioInitialize(scenarioInfo, ruleInfo); +#line hidden + if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) + { + await testRunner.SkipScenarioAsync(); + } + else + { + await this.ScenarioStartAsync(); +#line 4 +await this.FeatureBackgroundAsync(); +#line hidden +#line 123 + await testRunner.GivenAsync("I am logged in as \"merchantuser@testmerchant1.co.uk\" with password \"123456\" for M" + + "erchant \"Test Merchant 1\" for Estate \"Test Estate 1\" with client \"merchantClient" + + "\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); +#line hidden + global::Reqnroll.Table table19 = new global::Reqnroll.Table(new string[] { + "DateTime", + "TransactionNumber", + "TransactionType", + "MerchantName", + "DeviceIdentifier", + "EstateName", + "OperatorName", + "TransactionAmount", + "CustomerAccountNumber", + "CustomerEmailAddress", + "ContractDescription", + "ProductName", + "RecipientEmail", + "RecipientMobile"}); + table19.AddRow(new string[] { + "Today", + "9", + "Sale", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Safaricom", + "25.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup", + "", + ""}); +#line 124 + await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table19, "When "); +#line hidden +#line 128 + await testRunner.WhenAsync("I get the merchant transaction mix summary for Merchant \"Test Merchant 1\" for Est" + + "ate \"Test Estate 1\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); +#line hidden +#line 129 + await testRunner.ThenAsync("the merchant transaction mix summary response should contain at least one item an" + + "d the sale amount 25.00", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); +#line hidden + } + await this.ScenarioCleanupAsync(); + } + } +} +#pragma warning restore +#endregion diff --git a/TransactionProcessorACL.IntegrationTests/SaleTransaction/SalesTransaction.feature.cs b/TransactionProcessorACL.IntegrationTests/SaleTransaction/SalesTransaction.feature.cs index bffc9db..ae0c58a 100644 --- a/TransactionProcessorACL.IntegrationTests/SaleTransaction/SalesTransaction.feature.cs +++ b/TransactionProcessorACL.IntegrationTests/SaleTransaction/SalesTransaction.feature.cs @@ -111,157 +111,157 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa { #line 4 #line hidden - global::Reqnroll.Table table73 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table92 = new global::Reqnroll.Table(new string[] { "Role Name"}); - table73.AddRow(new string[] { + table92.AddRow(new string[] { "Merchant"}); #line 6 - await testRunner.GivenAsync("the following security roles exist", ((string)(null)), table73, "Given "); + await testRunner.GivenAsync("the following security roles exist", ((string)(null)), table92, "Given "); #line hidden - global::Reqnroll.Table table74 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table93 = new global::Reqnroll.Table(new string[] { "Name", "DisplayName", "Description"}); - table74.AddRow(new string[] { + table93.AddRow(new string[] { "transactionProcessor", "Transaction Processor REST Scope", "A scope for Transaction Processor REST"}); - table74.AddRow(new string[] { + table93.AddRow(new string[] { "transactionProcessorACL", "Transaction Processor ACL REST Scope", "A scope for Transaction Processor ACL REST"}); #line 10 - await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table74, "Given "); + await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table93, "Given "); #line hidden - global::Reqnroll.Table table75 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table94 = new global::Reqnroll.Table(new string[] { "Name", "DisplayName", "Secret", "Scopes", "UserClaims"}); - table75.AddRow(new string[] { + table94.AddRow(new string[] { "transactionProcessor", "Transaction Processor REST", "Secret1", "transactionProcessor", "merchantId, estateId, role"}); - table75.AddRow(new string[] { + table94.AddRow(new string[] { "transactionProcessorACL", "Transaction Processor ACL REST", "Secret1", "transactionProcessorACL", "merchantId, estateId, role"}); #line 15 - await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table75, "Given "); + await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table94, "Given "); #line hidden - global::Reqnroll.Table table76 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table95 = new global::Reqnroll.Table(new string[] { "ClientId", "ClientName", "Secret", "Scopes", "GrantTypes"}); - table76.AddRow(new string[] { + table95.AddRow(new string[] { "serviceClient", "Service Client", "Secret1", "transactionProcessor,transactionProcessorACL", "client_credentials"}); - table76.AddRow(new string[] { + table95.AddRow(new string[] { "merchantClient", "Merchant Client", "Secret1", "transactionProcessorACL", "password"}); #line 20 - await testRunner.GivenAsync("the following clients exist", ((string)(null)), table76, "Given "); + await testRunner.GivenAsync("the following clients exist", ((string)(null)), table95, "Given "); #line hidden - global::Reqnroll.Table table77 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table96 = new global::Reqnroll.Table(new string[] { "ClientId"}); - table77.AddRow(new string[] { + table96.AddRow(new string[] { "serviceClient"}); #line 25 await testRunner.GivenAsync("I have a token to access the estate management and transaction processor acl reso" + - "urces", ((string)(null)), table77, "Given "); + "urces", ((string)(null)), table96, "Given "); #line hidden - global::Reqnroll.Table table78 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table97 = new global::Reqnroll.Table(new string[] { "EstateName"}); - table78.AddRow(new string[] { + table97.AddRow(new string[] { "Test Estate 1"}); - table78.AddRow(new string[] { + table97.AddRow(new string[] { "Test Estate 2"}); #line 29 - await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table78, "Given "); + await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table97, "Given "); #line hidden - global::Reqnroll.Table table79 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table98 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "RequireCustomMerchantNumber", "RequireCustomTerminalNumber"}); - table79.AddRow(new string[] { + table98.AddRow(new string[] { "Test Estate 1", "Safaricom", "True", "True"}); - table79.AddRow(new string[] { + table98.AddRow(new string[] { "Test Estate 1", "Voucher", "True", "True"}); - table79.AddRow(new string[] { + table98.AddRow(new string[] { "Test Estate 2", "Safaricom", "True", "True"}); - table79.AddRow(new string[] { + table98.AddRow(new string[] { "Test Estate 2", "Voucher", "True", "True"}); #line 34 - await testRunner.GivenAsync("I have created the following operators", ((string)(null)), table79, "Given "); + await testRunner.GivenAsync("I have created the following operators", ((string)(null)), table98, "Given "); #line hidden - global::Reqnroll.Table table80 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table99 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName"}); - table80.AddRow(new string[] { + table99.AddRow(new string[] { "Test Estate 1", "Safaricom"}); - table80.AddRow(new string[] { + table99.AddRow(new string[] { "Test Estate 1", "Voucher"}); - table80.AddRow(new string[] { + table99.AddRow(new string[] { "Test Estate 2", "Safaricom"}); - table80.AddRow(new string[] { + table99.AddRow(new string[] { "Test Estate 2", "Voucher"}); #line 41 - await testRunner.AndAsync("I have assigned the following operators to the estates", ((string)(null)), table80, "And "); + await testRunner.AndAsync("I have assigned the following operators to the estates", ((string)(null)), table99, "And "); #line hidden - global::Reqnroll.Table table81 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table100 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription"}); - table81.AddRow(new string[] { + table100.AddRow(new string[] { "Test Estate 1", "Safaricom", "Safaricom Contract"}); - table81.AddRow(new string[] { + table100.AddRow(new string[] { "Test Estate 1", "Voucher", "Hospital 1 Contract"}); - table81.AddRow(new string[] { + table100.AddRow(new string[] { "Test Estate 2", "Safaricom", "Safaricom Contract"}); - table81.AddRow(new string[] { + table100.AddRow(new string[] { "Test Estate 2", "Voucher", "Hospital 1 Contract"}); #line 48 - await testRunner.GivenAsync("I create a contract with the following values", ((string)(null)), table81, "Given "); + await testRunner.GivenAsync("I create a contract with the following values", ((string)(null)), table100, "Given "); #line hidden - global::Reqnroll.Table table82 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table101 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription", @@ -269,7 +269,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "DisplayText", "Value", "ProductType"}); - table82.AddRow(new string[] { + table101.AddRow(new string[] { "Test Estate 1", "Safaricom", "Safaricom Contract", @@ -277,7 +277,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Custom", "", "MobileTopup"}); - table82.AddRow(new string[] { + table101.AddRow(new string[] { "Test Estate 1", "Voucher", "Hospital 1 Contract", @@ -285,7 +285,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "10 KES", "", "Voucher"}); - table82.AddRow(new string[] { + table101.AddRow(new string[] { "Test Estate 2", "Safaricom", "Safaricom Contract", @@ -293,7 +293,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Custom", "", "MobileTopup"}); - table82.AddRow(new string[] { + table101.AddRow(new string[] { "Test Estate 2", "Voucher", "Hospital 1 Contract", @@ -302,9 +302,9 @@ await testRunner.GivenAsync("I have a token to access the estate management and "", "Voucher"}); #line 55 - await testRunner.WhenAsync("I create the following Products", ((string)(null)), table82, "When "); + await testRunner.WhenAsync("I create the following Products", ((string)(null)), table101, "When "); #line hidden - global::Reqnroll.Table table83 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table102 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription", @@ -312,7 +312,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "CalculationType", "FeeDescription", "Value"}); - table83.AddRow(new string[] { + table102.AddRow(new string[] { "Test Estate 1", "Safaricom", "Safaricom Contract", @@ -320,7 +320,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Fixed", "Merchant Commission", "2.50"}); - table83.AddRow(new string[] { + table102.AddRow(new string[] { "Test Estate 2", "Safaricom", "Safaricom Contract", @@ -329,9 +329,9 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Merchant Commission", "0.85"}); #line 62 - await testRunner.WhenAsync("I add the following Transaction Fees", ((string)(null)), table83, "When "); + await testRunner.WhenAsync("I add the following Transaction Fees", ((string)(null)), table102, "When "); #line hidden - global::Reqnroll.Table table84 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table103 = new global::Reqnroll.Table(new string[] { "MerchantName", "AddressLine1", "Town", @@ -341,7 +341,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "ContactName", "EmailAddress", "EstateName"}); - table84.AddRow(new string[] { + table103.AddRow(new string[] { "Test Merchant 1", "Address Line 1", "TestTown", @@ -351,7 +351,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Test Contact 1", "testcontact1@merchant1.co.uk", "Test Estate 1"}); - table84.AddRow(new string[] { + table103.AddRow(new string[] { "Test Merchant 2", "Address Line 1", "TestTown", @@ -361,7 +361,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Test Contact 2", "testcontact2@merchant2.co.uk", "Test Estate 1"}); - table84.AddRow(new string[] { + table103.AddRow(new string[] { "Test Merchant 3", "Address Line 1", "TestTown", @@ -372,152 +372,152 @@ await testRunner.GivenAsync("I have a token to access the estate management and "testcontact3@merchant2.co.uk", "Test Estate 2"}); #line 67 - await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table84, "Given "); + await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table103, "Given "); #line hidden - global::Reqnroll.Table table85 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table104 = new global::Reqnroll.Table(new string[] { "OperatorName", "MerchantName", "MerchantNumber", "TerminalNumber", "EstateName"}); - table85.AddRow(new string[] { + table104.AddRow(new string[] { "Safaricom", "Test Merchant 1", "00000001", "10000001", "Test Estate 1"}); - table85.AddRow(new string[] { + table104.AddRow(new string[] { "Voucher", "Test Merchant 1", "00000001", "10000001", "Test Estate 1"}); - table85.AddRow(new string[] { + table104.AddRow(new string[] { "Safaricom", "Test Merchant 2", "00000002", "10000002", "Test Estate 1"}); - table85.AddRow(new string[] { + table104.AddRow(new string[] { "Voucher", "Test Merchant 2", "00000002", "10000002", "Test Estate 1"}); - table85.AddRow(new string[] { + table104.AddRow(new string[] { "Safaricom", "Test Merchant 3", "00000003", "10000003", "Test Estate 2"}); - table85.AddRow(new string[] { + table104.AddRow(new string[] { "Voucher", "Test Merchant 3", "00000003", "10000003", "Test Estate 2"}); #line 73 - await testRunner.GivenAsync("I have assigned the following operator to the merchants", ((string)(null)), table85, "Given "); + await testRunner.GivenAsync("I have assigned the following operator to the merchants", ((string)(null)), table104, "Given "); #line hidden - global::Reqnroll.Table table86 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table105 = new global::Reqnroll.Table(new string[] { "DeviceIdentifier", "MerchantName", "EstateName"}); - table86.AddRow(new string[] { + table105.AddRow(new string[] { "123456780", "Test Merchant 1", "Test Estate 1"}); - table86.AddRow(new string[] { + table105.AddRow(new string[] { "123456781", "Test Merchant 2", "Test Estate 1"}); - table86.AddRow(new string[] { + table105.AddRow(new string[] { "123456782", "Test Merchant 3", "Test Estate 2"}); #line 82 - await testRunner.GivenAsync("I have assigned the following devices to the merchants", ((string)(null)), table86, "Given "); + await testRunner.GivenAsync("I have assigned the following devices to the merchants", ((string)(null)), table105, "Given "); #line hidden - global::Reqnroll.Table table87 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table106 = new global::Reqnroll.Table(new string[] { "EstateName", "MerchantName", "ContractDescription"}); - table87.AddRow(new string[] { + table106.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "Safaricom Contract"}); - table87.AddRow(new string[] { + table106.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "Hospital 1 Contract"}); - table87.AddRow(new string[] { + table106.AddRow(new string[] { "Test Estate 1", "Test Merchant 2", "Safaricom Contract"}); - table87.AddRow(new string[] { + table106.AddRow(new string[] { "Test Estate 1", "Test Merchant 2", "Hospital 1 Contract"}); - table87.AddRow(new string[] { + table106.AddRow(new string[] { "Test Estate 2", "Test Merchant 3", "Safaricom Contract"}); - table87.AddRow(new string[] { + table106.AddRow(new string[] { "Test Estate 2", "Test Merchant 3", "Hospital 1 Contract"}); #line 88 - await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table87, "When "); + await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table106, "When "); #line hidden - global::Reqnroll.Table table88 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table107 = new global::Reqnroll.Table(new string[] { "Reference", "Amount", "DateTime", "MerchantName", "EstateName"}); - table88.AddRow(new string[] { + table107.AddRow(new string[] { "Deposit1", "210.00", "Today", "Test Merchant 1", "Test Estate 1"}); - table88.AddRow(new string[] { + table107.AddRow(new string[] { "Deposit1", "110.00", "Today", "Test Merchant 2", "Test Estate 1"}); - table88.AddRow(new string[] { + table107.AddRow(new string[] { "Deposit1", "110.00", "Today", "Test Merchant 3", "Test Estate 2"}); #line 97 - await testRunner.GivenAsync("I make the following manual merchant deposits", ((string)(null)), table88, "Given "); + await testRunner.GivenAsync("I make the following manual merchant deposits", ((string)(null)), table107, "Given "); #line hidden - global::Reqnroll.Table table89 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table108 = new global::Reqnroll.Table(new string[] { "EmailAddress", "Password", "GivenName", "FamilyName", "EstateName", "MerchantName"}); - table89.AddRow(new string[] { + table108.AddRow(new string[] { "merchantuser@testmerchant1.co.uk", "123456", "TestMerchant", "User1", "Test Estate 1", "Test Merchant 1"}); - table89.AddRow(new string[] { + table108.AddRow(new string[] { "merchantuser@testmerchant2.co.uk", "123456", "TestMerchant", "User2", "Test Estate 1", "Test Merchant 2"}); - table89.AddRow(new string[] { + table108.AddRow(new string[] { "merchantuser@testmerchant3.co.uk", "123456", "TestMerchant", @@ -525,7 +525,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Test Estate 2", "Test Merchant 3"}); #line 103 - await testRunner.GivenAsync("I have created the following security users", ((string)(null)), table89, "Given "); + await testRunner.GivenAsync("I have created the following security users", ((string)(null)), table108, "Given "); #line hidden } @@ -564,7 +564,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "erchant \"Test Merchant 1\" for Estate \"Test Estate 1\" with client \"merchantClient" + "\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); #line hidden - global::Reqnroll.Table table90 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table109 = new global::Reqnroll.Table(new string[] { "DateTime", "TransactionNumber", "TransactionType", @@ -579,7 +579,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "ProductName", "RecipientEmail", "RecipientMobile"}); - table90.AddRow(new string[] { + table109.AddRow(new string[] { "Today", "1", "Sale", @@ -594,7 +594,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Variable Topup", "", ""}); - table90.AddRow(new string[] { + table109.AddRow(new string[] { "Today", "4", "Sale", @@ -609,7 +609,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Variable Topup", "", ""}); - table90.AddRow(new string[] { + table109.AddRow(new string[] { "Today", "5", "Sale", @@ -625,14 +625,14 @@ await testRunner.GivenAsync("I have a token to access the estate management and "test@recipient.co.uk", ""}); #line 112 - await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table90, "When "); + await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table109, "When "); #line hidden #line 118 await testRunner.GivenAsync("I am logged in as \"merchantuser@testmerchant2.co.uk\" with password \"123456\" for M" + "erchant \"Test Merchant 2\" for Estate \"Test Estate 1\" with client \"merchantClient" + "\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); #line hidden - global::Reqnroll.Table table91 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table110 = new global::Reqnroll.Table(new string[] { "DateTime", "TransactionNumber", "TransactionType", @@ -647,7 +647,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "ProductName", "RecipientEmail", "RecipientMobile"}); - table91.AddRow(new string[] { + table110.AddRow(new string[] { "Today", "2", "Sale", @@ -662,7 +662,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Variable Topup", "", ""}); - table91.AddRow(new string[] { + table110.AddRow(new string[] { "Today", "6", "Sale", @@ -678,14 +678,14 @@ await testRunner.GivenAsync("I have a token to access the estate management and "", "123456789"}); #line 119 - await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table91, "When "); + await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table110, "When "); #line hidden #line 124 await testRunner.GivenAsync("I am logged in as \"merchantuser@testmerchant3.co.uk\" with password \"123456\" for M" + "erchant \"Test Merchant 3\" for Estate \"Test Estate 2\" with client \"merchantClient" + "\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); #line hidden - global::Reqnroll.Table table92 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table111 = new global::Reqnroll.Table(new string[] { "DateTime", "TransactionNumber", "TransactionType", @@ -700,7 +700,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "ProductName", "RecipientEmail", "RecipientMobile"}); - table92.AddRow(new string[] { + table111.AddRow(new string[] { "Today", "3", "Sale", @@ -715,7 +715,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Variable Topup", "", ""}); - table92.AddRow(new string[] { + table111.AddRow(new string[] { "Today", "7", "Sale", @@ -731,58 +731,58 @@ await testRunner.GivenAsync("I have a token to access the estate management and "test@recipient.co.uk", ""}); #line 125 - await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table92, "When "); + await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table111, "When "); #line hidden - global::Reqnroll.Table table93 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table112 = new global::Reqnroll.Table(new string[] { "EstateName", "MerchantName", "TransactionNumber", "TransactionType", "ResponseCode", "ResponseMessage"}); - table93.AddRow(new string[] { + table112.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "1", "Sale", "0000", "SUCCESS"}); - table93.AddRow(new string[] { + table112.AddRow(new string[] { "Test Estate 1", "Test Merchant 2", "2", "Sale", "0000", "SUCCESS"}); - table93.AddRow(new string[] { + table112.AddRow(new string[] { "Test Estate 2", "Test Merchant 3", "3", "Sale", "0000", "SUCCESS"}); - table93.AddRow(new string[] { + table112.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "4", "Sale", "0000", "SUCCESS"}); - table93.AddRow(new string[] { + table112.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "5", "Sale", "0000", "SUCCESS"}); - table93.AddRow(new string[] { + table112.AddRow(new string[] { "Test Estate 1", "Test Merchant 2", "6", "Sale", "0000", "SUCCESS"}); - table93.AddRow(new string[] { + table112.AddRow(new string[] { "Test Estate 2", "Test Merchant 3", "7", @@ -790,7 +790,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "0000", "SUCCESS"}); #line 130 - await testRunner.ThenAsync("the sale transaction response should contain the following information", ((string)(null)), table93, "Then "); + await testRunner.ThenAsync("the sale transaction response should contain the following information", ((string)(null)), table112, "Then "); #line hidden } await this.ScenarioCleanupAsync(); diff --git a/TransactionProcessorACL.IntegrationTests/Shared/ACLSteps.cs b/TransactionProcessorACL.IntegrationTests/Shared/ACLSteps.cs index 1327fb7..84e4524 100644 --- a/TransactionProcessorACL.IntegrationTests/Shared/ACLSteps.cs +++ b/TransactionProcessorACL.IntegrationTests/Shared/ACLSteps.cs @@ -13,11 +13,14 @@ namespace TransactionProcessorACL.IntegrationTests.Shared; using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using TransactionProcessor.Client; using TransactionProcessor.DataTransferObjects; using TransactionProcessor.IntegrationTests.Common; +using ReportingDailyPerformanceSummaryResponse = TransactionProcessorACL.DataTransferObjects.Responses.MerchantDailyPerformanceSummaryResponse; +using ReportingMerchantTransactionMixSummaryResponse = TransactionProcessorACL.IntegrationTests.Shared.MerchantTransactionMixSummaryResponse; using static TransactionProcessorACL.IntegrationTests.Shared.ReqnrollExtensions; public class ACLSteps{ @@ -25,6 +28,11 @@ public class ACLSteps{ private readonly ITransactionProcessorClient TransactionProcessorClient; + private EstateDetails1 LastMerchantDailyPerformanceSummaryEstateDetails; + private Guid LastMerchantDailyPerformanceSummaryMerchantId; + private EstateDetails1 LastMerchantTransactionMixSummaryEstateDetails; + private Guid LastMerchantTransactionMixSummaryMerchantId; + public ACLSteps(HttpClient httpClient, ITransactionProcessorClient transactionProcessorClient){ this.HttpClient = httpClient; this.TransactionProcessorClient = transactionProcessorClient; @@ -196,8 +204,8 @@ public async Task WhenIGetTheMerchantInformationForMerchantForEstateTheResponseS public async Task WhenIGetTheMerchantContractInformationForMerchantForEstateTheResponseShouldContainTheFollowingInformation(String estateName, String merchantName, - List estateDetailsList, - List expectedMerchantContractResponses) { + List estateDetailsList, + List expectedMerchantContractResponses) { EstateDetails1 es1 = estateDetailsList.SingleOrDefault(e => e.EstateDetails.EstateName == estateName); es1.ShouldNotBeNull(); Guid merchantId = es1.EstateDetails.GetMerchantId(merchantName); @@ -223,4 +231,119 @@ await Retry.For(async () => { } }); } -} \ No newline at end of file + + public async Task WhenIGetTheMerchantDailyPerformanceSummaryForMerchantForEstate(String estateName, + String merchantName, + List estateDetailsList, + CancellationToken cancellationToken) + { + EstateDetails1 es1 = estateDetailsList.SingleOrDefault(e => e.EstateDetails.EstateName == estateName); + es1.ShouldNotBeNull(); + Guid merchantId = es1.EstateDetails.GetMerchantId(merchantName); + Int32 merchantReportingId = es1.GetMerchantReportingId(merchantId); + + String uri = "api/reporting/dailymerchantprformancesummary"; + String userAccessToken = es1.GetMerchantUserToken(merchantId); + + this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", userAccessToken); + + Object request = new + { + application_version = "1.0.5", + merchant_reporting_id = merchantReportingId, + start_date = DateTime.Today, + end_date = DateTime.Today, + }; + + + StringContent content = new StringContent(StringSerialiser.Serialise(request), Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await this.HttpClient.PostAsync(uri, content, cancellationToken).ConfigureAwait(false); + + response.IsSuccessStatusCode.ShouldBeTrue(); + + String responseContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + responseContent.ShouldNotBeNullOrEmpty("No response message received"); + + ReportingDailyPerformanceSummaryResponse dailyPerformanceSummaryResponse = + StringSerialiser.Deserialise(responseContent, + new SerialiserOptions(SerialiserPropertyFormat.SnakeCase)); + + es1.AddMerchantDailyPerformanceSummaryResponse(merchantId, dailyPerformanceSummaryResponse); + this.LastMerchantDailyPerformanceSummaryEstateDetails = es1; + this.LastMerchantDailyPerformanceSummaryMerchantId = merchantId; + } + + public void ThenTheMerchantDailyPerformanceSummaryResponseShouldContainAtLeastOneMetricAndTheSaleAmount(Decimal saleAmount) + { + this.LastMerchantDailyPerformanceSummaryEstateDetails.ShouldNotBeNull(); + + ReportingDailyPerformanceSummaryResponse merchantDailyPerformanceSummaryResponse = + this.LastMerchantDailyPerformanceSummaryEstateDetails.GetMerchantDailyPerformanceSummaryResponse(this.LastMerchantDailyPerformanceSummaryMerchantId); + + merchantDailyPerformanceSummaryResponse.ShouldNotBeNull(); + merchantDailyPerformanceSummaryResponse!.Metrics.ShouldNotBeEmpty(); + merchantDailyPerformanceSummaryResponse.DrillDownTransactions.ShouldNotBeEmpty(); + merchantDailyPerformanceSummaryResponse.DrillDownTransactions.Any(t => t.Amount == saleAmount) + .ShouldBeTrue($"Expected a drill down transaction with amount {saleAmount}"); + } + + public async Task WhenIGetTheMerchantTransactionMixSummaryForMerchantForEstate(String estateName, + String merchantName, + List estateDetailsList, + CancellationToken cancellationToken) + { + EstateDetails1 es1 = estateDetailsList.SingleOrDefault(e => e.EstateDetails.EstateName == estateName); + es1.ShouldNotBeNull(); + Guid merchantId = es1.EstateDetails.GetMerchantId(merchantName); + Int32 merchantReportingId = es1.GetMerchantReportingId(merchantId); + + String uri = "api/reporting/transactionmixsummary"; + String userAccessToken = es1.GetMerchantUserToken(merchantId); + + this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", userAccessToken); + + Object request = new + { + application_version = "1.0.5", + merchant_reporting_id = merchantReportingId, + start_date = DateTime.Today, + end_date = DateTime.Today, + breakdown = 2, + measure = 1, + top_n = 5, + }; + + StringContent content = new StringContent(StringSerialiser.Serialise(request), Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await this.HttpClient.PostAsync(uri, content, cancellationToken).ConfigureAwait(false); + + response.IsSuccessStatusCode.ShouldBeTrue(); + + String responseContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + responseContent.ShouldNotBeNullOrEmpty("No response message received"); + + ReportingMerchantTransactionMixSummaryResponse transactionMixSummaryResponse = + StringSerialiser.Deserialise(responseContent, + new SerialiserOptions(SerialiserPropertyFormat.SnakeCase)); + + es1.AddMerchantTransactionMixSummaryResponse(merchantId, transactionMixSummaryResponse); + this.LastMerchantTransactionMixSummaryEstateDetails = es1; + this.LastMerchantTransactionMixSummaryMerchantId = merchantId; + } + + public void ThenTheMerchantTransactionMixSummaryResponseShouldContainAtLeastOneItemAndTheSaleAmount(Decimal saleAmount) + { + this.LastMerchantTransactionMixSummaryEstateDetails.ShouldNotBeNull(); + + ReportingMerchantTransactionMixSummaryResponse merchantTransactionMixSummaryResponse = + this.LastMerchantTransactionMixSummaryEstateDetails.GetMerchantTransactionMixSummaryResponse(this.LastMerchantTransactionMixSummaryMerchantId); + + merchantTransactionMixSummaryResponse.ShouldNotBeNull(); + merchantTransactionMixSummaryResponse!.Items.ShouldNotBeEmpty(); + merchantTransactionMixSummaryResponse.TotalCount.ShouldBeGreaterThan(0); + merchantTransactionMixSummaryResponse.DrillDownTransactions.ShouldNotBeEmpty(); + merchantTransactionMixSummaryResponse.DrillDownTransactions.Any(t => t.Amount == saleAmount) + .ShouldBeTrue($"Expected a drill down transaction with amount {saleAmount}"); + } +} diff --git a/TransactionProcessorACL.IntegrationTests/Shared/ReportingDtos.cs b/TransactionProcessorACL.IntegrationTests/Shared/ReportingDtos.cs new file mode 100644 index 0000000..2e808e8 --- /dev/null +++ b/TransactionProcessorACL.IntegrationTests/Shared/ReportingDtos.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using TransactionProcessorACL.DataTransferObjects.Requests; + +namespace TransactionProcessorACL.IntegrationTests.Shared; + +public class MerchantTransactionMixSummaryResponse +{ + public DateTime FromDate { get; set; } + + public DateTime ToDate { get; set; } + + public TransactionMixBreakdown Breakdown { get; set; } + + public TransactionMixMeasure Measure { get; set; } + + public decimal TotalCount { get; set; } + + public decimal TotalValue { get; set; } + + public List Items { get; set; } = []; + + public List DrillDownTransactions { get; set; } = []; +} + +public class TransactionMixSummaryItem +{ + public string Key { get; set; } + + public string Label { get; set; } + + public decimal Count { get; set; } + + public decimal Value { get; set; } +} + +public class TransactionMixDrillDownTransaction +{ + public string Reference { get; set; } + + public string TransactionType { get; set; } + + public string Product { get; set; } + + public string Operator { get; set; } + + public string Status { get; set; } + + public decimal Amount { get; set; } + + public DateTime TransactionDateTime { get; set; } +} diff --git a/TransactionProcessorACL.IntegrationTests/Shared/SharedSteps.cs b/TransactionProcessorACL.IntegrationTests/Shared/SharedSteps.cs index 0168ae5..d05d3f3 100644 --- a/TransactionProcessorACL.IntegrationTests/Shared/SharedSteps.cs +++ b/TransactionProcessorACL.IntegrationTests/Shared/SharedSteps.cs @@ -329,6 +329,7 @@ public async Task WhenICreateTheFollowingMerchants(DataTable table){ foreach (TransactionProcessor.DataTransferObjects.Responses.Merchant.MerchantResponse verifiedMerchant in verifiedMerchants){ EstateDetails1 estateDetails = this.TestingContext.GetEstateDetails(verifiedMerchant.EstateId); estateDetails.EstateDetails.AddMerchant(verifiedMerchant); + estateDetails.AddMerchantReportingId(verifiedMerchant.MerchantId, verifiedMerchant.MerchantReportingId); this.TestingContext.Logger.LogInformation($"Merchant {verifiedMerchant.MerchantName} created with Id {verifiedMerchant.MerchantId} for Estate {estateDetails.EstateDetails.EstateName}"); } } @@ -475,6 +476,38 @@ public async Task WhenIGetTheMerchantContractInformationForMerchantForEstateTheR await this.AclSteps.WhenIGetTheMerchantContractInformationForMerchantForEstateTheResponseShouldContainTheFollowingInformation(estateName, merchantName, this.TestingContext.Estates, expectedMerchantContractResponses); } + [When("I get the merchant daily performance summary for Merchant {string} for Estate {string}")] + public async Task WhenIGetTheMerchantDailyPerformanceSummaryForMerchantForEstate(string merchantName, + string estateName) + { + await this.AclSteps.WhenIGetTheMerchantDailyPerformanceSummaryForMerchantForEstate(estateName, + merchantName, + this.TestingContext.Estates, + CancellationToken.None); + } + + [Then("the merchant daily performance summary response should contain at least one metric and the sale amount {decimal}")] + public void ThenTheMerchantDailyPerformanceSummaryResponseShouldContainAtLeastOneMetricAndTheSaleAmount(decimal saleAmount) + { + this.AclSteps.ThenTheMerchantDailyPerformanceSummaryResponseShouldContainAtLeastOneMetricAndTheSaleAmount(saleAmount); + } + + [When("I get the merchant transaction mix summary for Merchant {string} for Estate {string}")] + public async Task WhenIGetTheMerchantTransactionMixSummaryForMerchantForEstate(string merchantName, + string estateName) + { + await this.AclSteps.WhenIGetTheMerchantTransactionMixSummaryForMerchantForEstate(estateName, + merchantName, + this.TestingContext.Estates, + CancellationToken.None); + } + + [Then("the merchant transaction mix summary response should contain at least one item and the sale amount {decimal}")] + public void ThenTheMerchantTransactionMixSummaryResponseShouldContainAtLeastOneItemAndTheSaleAmount(decimal saleAmount) + { + this.AclSteps.ThenTheMerchantTransactionMixSummaryResponseShouldContainAtLeastOneItemAndTheSaleAmount(saleAmount); + } + #endregion @@ -493,4 +526,4 @@ public async Task WhenIGetTheMerchantContractInformationForMerchantForEstateTheR public record Merchant(string Id, string Name, int numberOfEventsProcessed, decimal balance); [ExcludeFromCodeCoverage] -public record MerchantBalanceProjectionState1(Merchant merchant); \ No newline at end of file +public record MerchantBalanceProjectionState1(Merchant merchant);