Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
<PackageVersion Include="TransactionProcessor.Client" Version="2026.5.2" />
<PackageVersion Include="TransactionProcessor.Database" Version="2026.5.2" />
<PackageVersion Include="TransactionProcessor.IntegrationTesting.Helpers" Version="2026.5.2" />
<PackageVersion Include="TransactionProcessorACL.DataTransferObjects" Version="2026.5.2" />
<PackageVersion Include="TransactionProcessorACL.DataTransferObjects" Version="2026.7.1" />
<PackageVersion Include="xunit.v3" Version="3.2.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using MediatR;
using Moq;
using SimpleResults;
using Shouldly;
using TransactionProcessor.Mobile.BusinessLogic.RequestHandlers;
using TransactionProcessor.Mobile.BusinessLogic.Requests;
using TransactionProcessor.Mobile.BusinessLogic.Models;
using TransactionProcessor.Mobile.BusinessLogic.Services;

namespace TransactionProcessor.Mobile.BusinessLogic.Tests.RequestHandlerTests;

Expand All @@ -12,13 +14,36 @@ public class ReportRequestHandlerTests
[Fact]
public async Task GetDailyPerformanceSummaryQuery_ReturnsMockedSummaryForToday()
{
ReportRequestHandler handler = new();
Mock<IReportsService> reportsService = new();
Mock<IApplicationCache> applicationCache = new();
MerchantDetailsModel merchantDetails = new()
{
MerchantReportingId = 12345
};

applicationCache.Setup(a => a.GetMerchantDetails()).Returns(merchantDetails);
reportsService.Setup(r => r.GetDailyPerformanceSummary(
PerformanceSummaryPeriod.Today,
merchantDetails.MerchantReportingId,
It.IsAny<DateTime>(),
It.IsAny<DateTime>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(DailyPerformanceSummaryModel.CreateMock(PerformanceSummaryPeriod.Today)));

ReportRequestHandler handler = new(reportsService.Object, applicationCache.Object);

Result<DailyPerformanceSummaryModel> result = await handler.Handle(new ReportQueries.GetDailyPerformanceSummaryQuery(PerformanceSummaryPeriod.Today), CancellationToken.None);

result.IsSuccess.ShouldBeTrue();
result.Data.Period.ShouldBe(PerformanceSummaryPeriod.Today);
result.Data.Metrics.ShouldContain(m => m.Title == "Total transaction count" && m.Value == "48");
result.Data.DrillDownTransactions.ShouldContain(t => t.Reference == "TXN-00048");
reportsService.Verify(r => r.GetDailyPerformanceSummary(
PerformanceSummaryPeriod.Today,
merchantDetails.MerchantReportingId,
It.IsAny<DateTime>(),
It.IsAny<DateTime>(),
It.IsAny<CancellationToken>()),
Times.Once);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public async Task MerchantService_GetContractProducts_ContractProductsAreReturne
});


this.MockHttpMessageHandler.When($"http://localhost/api/merchants/contracts?application_version=1.0.0")
this.MockHttpMessageHandler.When($"http://localhost/api/merchants/contracts?applicationVersion=1.0.0")
.Respond("application/json", StringSerialiser.Serialise(contracts)); // Respond with JSON

var result = await this.MerchantService.GetContractProducts(CancellationToken.None);
Expand All @@ -86,7 +86,7 @@ public async Task MerchantService_GetContractProducts_ContractProductsAreReturne

[Fact]
public async Task MerchantService_GetContractProducts_HttpCallFailed_FailureReturned(){
this.MockHttpMessageHandler.When($"http://localhost/api/merchants/contracts?application_version=1.0.0")
this.MockHttpMessageHandler.When($"http://localhost/api/merchants/contracts?applicationVersion=1.0.0")
.Respond(HttpStatusCode.InternalServerError);

Result<List<ContractProductModel>> result = await this.MerchantService.GetContractProducts(CancellationToken.None);
Expand Down Expand Up @@ -122,7 +122,7 @@ public async Task MerchantService_GetMerchantDetails_MerchantDetailsReturned(){
}
};

this.MockHttpMessageHandler.When($"http://localhost/api/merchants?application_version=1.0.0")
this.MockHttpMessageHandler.When($"http://localhost/api/merchants?applicationVersion=1.0.0")
.Respond("application/json", StringSerialiser.Serialise(merchantResponse)); // Respond with JSON

Result<MerchantDetailsModel> merchantDetails = await this.MerchantService.GetMerchantDetails(CancellationToken.None);
Expand All @@ -147,7 +147,7 @@ public async Task MerchantService_GetMerchantDetails_MerchantDetailsReturned(){
[Fact]
public async Task MerchantService_GetMerchantDetails_ResultFailed_FailedResultIsReturned()
{
this.MockHttpMessageHandler.When($"http://localhost/api/merchants?application_version=1.0.0")
this.MockHttpMessageHandler.When($"http://localhost/api/merchants?applicationVersion=1.0.0")
.Respond(HttpStatusCode.BadRequest);

Result<MerchantDetailsModel> merchantDetails = await this.MerchantService.GetMerchantDetails(CancellationToken.None);
Expand All @@ -156,7 +156,7 @@ public async Task MerchantService_GetMerchantDetails_ResultFailed_FailedResultIs

[Fact]
public async Task MerchantService_GetMerchantDetails_ExceptionThrown_FailedResultReturned(){
this.MockHttpMessageHandler.When($"http://localhost/api/merchants?application_version=1.0.0")
this.MockHttpMessageHandler.When($"http://localhost/api/merchants?applicationVersion=1.0.0")
.Respond(HttpStatusCode.InternalServerError);

Result<MerchantDetailsModel> merchantDetails = await this.MerchantService.GetMerchantDetails(CancellationToken.None);
Expand Down Expand Up @@ -196,4 +196,4 @@ public void GetProductSubType_CorrectProductSubTypeReturned(String operatorName,
Models.ProductSubType result = Services.MerchantService.GetProductSubType(operatorName);
result.ShouldBe(expectedProductSubType);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using TransactionProcessor.Mobile.BusinessLogic.Services;

namespace TransactionProcessor.Mobile.BusinessLogic.Models;

public enum PerformanceSummaryPeriod
Expand All @@ -8,14 +10,15 @@ public enum PerformanceSummaryPeriod
MonthToDate = 3,
}

public sealed record DailyPerformanceSummaryModel(
PerformanceSummaryPeriod Period,
DateTime FromDate,
DateTime ToDate,
string PeriodLabel,
IReadOnlyList<DailyPerformanceMetricModel> Metrics,
IReadOnlyList<DailyPerformanceTransactionModel> DrillDownTransactions)
public sealed record DailyPerformanceSummaryModel()
{
public PerformanceSummaryPeriod Period { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public string PeriodLabel { get; set; }
public List<DailyPerformanceMetricModel> Metrics { get; set; }
public List<Models.DailyPerformanceTransactionModel> DrillDownTransactions { get; set; }

public static DailyPerformanceSummaryModel CreateMock(PerformanceSummaryPeriod period)
{
DateTime today = DateTime.Today;
Expand Down Expand Up @@ -46,15 +49,22 @@ public static DailyPerformanceSummaryModel CreateMock(PerformanceSummaryPeriod p

List<DailyPerformanceTransactionModel> drillDownTransactions = new()
{
new DailyPerformanceTransactionModel("TXN-00048", "Mobile Topup", "Success", "250.00 KES", today.AddHours(-1)),
new DailyPerformanceTransactionModel("TXN-00047", "Bill Payment", "Success", "1,500.00 KES", today.AddHours(-2)),
new DailyPerformanceTransactionModel("TXN-00046", "Voucher Issue", "Failed", "0.00 KES", today.AddHours(-3)),
new DailyPerformanceTransactionModel("TXN-00048", "Mobile Topup", "Success", 250.00m, today.AddHours(-1)),
new DailyPerformanceTransactionModel("TXN-00047", "Bill Payment", "Success", 1500.00m, today.AddHours(-2)),
new DailyPerformanceTransactionModel("TXN-00046", "Voucher Issue", "Failed", 0.00m, today.AddHours(-3)),
};

return new DailyPerformanceSummaryModel(period, fromDate, toDate, period.ToString(), metrics, drillDownTransactions);
return new DailyPerformanceSummaryModel() {
DrillDownTransactions = drillDownTransactions,
FromDate = fromDate,
ToDate = toDate,
Metrics = metrics,
Period = period,
PeriodLabel = period.ToString()
};
}

private static DateTime StartOfWeek(DateTime date)
internal static DateTime StartOfWeek(DateTime date)
{
int daysSinceSunday = (int)date.DayOfWeek;
return date.Date.AddDays(-daysSinceSunday);
Expand All @@ -67,8 +77,10 @@ public enum DailyPerformanceMetricCategory
Total = 1,
Success = 2,
Failure = 3,
Average = 4,
TopSalesCount = 5
}

public sealed record DailyPerformanceMetricModel(string Title, string Value, string Description, DailyPerformanceMetricCategory Category = DailyPerformanceMetricCategory.Neutral);

public sealed record DailyPerformanceTransactionModel(string Reference, string Product, string Status, string Amount, DateTime TransactionDateTime);
public sealed record DailyPerformanceTransactionModel(string Reference, string Product, string Status, Decimal Amount, DateTime TransactionDateTime);
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ public class MerchantDetailsModel
public String SettlementSchedule { get; set; }
public AddressModel Address { get; set; }
public ContactModel Contact { get; set; }
public int MerchantReportingId { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,39 @@
using SimpleResults;
using TransactionProcessor.Mobile.BusinessLogic.Models;
using TransactionProcessor.Mobile.BusinessLogic.Requests;
using TransactionProcessor.Mobile.BusinessLogic.Services;

namespace TransactionProcessor.Mobile.BusinessLogic.RequestHandlers;

public sealed class ReportRequestHandler : IRequestHandler<ReportQueries.GetDailyPerformanceSummaryQuery, Result<DailyPerformanceSummaryModel>>
{
public Task<Result<DailyPerformanceSummaryModel>> Handle(ReportQueries.GetDailyPerformanceSummaryQuery request, CancellationToken cancellationToken)
{
DailyPerformanceSummaryModel summary = DailyPerformanceSummaryModel.CreateMock(request.Period);
return Task.FromResult(Result.Success(summary));
public sealed class ReportRequestHandler : IRequestHandler<ReportQueries.GetDailyPerformanceSummaryQuery, Result<DailyPerformanceSummaryModel>> {
private readonly IReportsService ReportsService;
private readonly IApplicationCache ApplicationCache;

public ReportRequestHandler(IReportsService reportsService, IApplicationCache applicationCache) {
ReportsService = reportsService;
this.ApplicationCache = applicationCache;
}


public async Task<Result<DailyPerformanceSummaryModel>> Handle(ReportQueries.GetDailyPerformanceSummaryQuery request,
CancellationToken cancellationToken) {

DateTime current = DateTime.Now.Date;

(DateTime startDate, DateTime endDate) dates = request.Period switch {
PerformanceSummaryPeriod.Today => (current, current),
PerformanceSummaryPeriod.Yesterday => (current.AddDays(-1), current.AddDays(-1)),
PerformanceSummaryPeriod.ThisWeek => (DailyPerformanceSummaryModel.StartOfWeek(current), current),
PerformanceSummaryPeriod.MonthToDate => (new DateTime(current.Year, current.Month, 1), current),
_ => throw new ArgumentOutOfRangeException(nameof(request.Period), request.Period, "Invalid performance summary period.")
};

MerchantDetailsModel merchant = this.ApplicationCache.GetMerchantDetails();
if (merchant == null)
{
return Result.Failure("Merchant details are not available.");
}

return await this.ReportsService.GetDailyPerformanceSummary(request.Period, merchant.MerchantReportingId, dates.startDate, dates.endDate, cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public async Task<Result<PerformLogonResponseModel>> Handle(TransactionCommands.
TransactionDateTime = request.TransactionDateTime,
TransactionNumber = transaction.transactionNumber.ToString(),
DeviceIdentifier = this.DeviceService.GetIdentifier(),
ApplicationVersion = "1.0.5"//this.ApplicationInfoService.VersionString
ApplicationVersion = this.ApplicationInfoService.VersionString
};

Result<PerformLogonResponseModel> result = await transactionService.PerformLogon(model, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public async Task<Result<List<ContractProductModel>>> GetContractProducts(Cancel

TokenResponseModel accessToken = this.ApplicationCache.GetAccessToken();

String requestUri = this.BuildRequestUrl($"/api/merchants/contracts?application_version={this.ApplicationInfoService.VersionString}");
String requestUri = this.BuildRequestUrl($"/api/merchants/contracts?applicationVersion={this.ApplicationInfoService.VersionString}");

Logger.LogInformation("About to request merchant contracts");
Logger.LogDebug($"Merchant Contract Request details: Access Token {accessToken.AccessToken}");
Expand Down Expand Up @@ -122,7 +122,7 @@ public async Task<Result<Decimal>> GetMerchantBalance(CancellationToken cancella
public async Task<Result<MerchantDetailsModel>> GetMerchantDetails(CancellationToken cancellationToken) {
TokenResponseModel accessToken = this.ApplicationCache.GetAccessToken();

String requestUri = this.BuildRequestUrl($"/api/merchants?application_version={this.ApplicationInfoService.VersionString}");
String requestUri = this.BuildRequestUrl($"/api/merchants?applicationVersion={this.ApplicationInfoService.VersionString}");

Logger.LogInformation("About to request merchant details");
Logger.LogDebug($"Merchant Details Request details: Access Token {accessToken.AccessToken}");
Expand All @@ -141,6 +141,7 @@ public async Task<Result<MerchantDetailsModel>> GetMerchantDetails(CancellationT
MerchantDetailsModel model = new MerchantDetailsModel {
EstateId = responseDataResult.Data.EstateId,
MerchantId = responseDataResult.Data.MerchantId,
MerchantReportingId = responseDataResult.Data.MerchantReportingId,
MerchantName = responseDataResult.Data.MerchantName,
NextStatementDate = responseDataResult.Data.NextStatementDate,
LastStatementDate = new DateTime(),
Expand Down Expand Up @@ -202,4 +203,4 @@ public static ProductSubType GetProductSubType(String operatorName)
}

#endregion
}
}
Loading
Loading