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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace TransactionMobile.Maui.BusinessLogic.Tests.RequestHandlerTests;

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Database;
Expand Down Expand Up @@ -80,4 +81,41 @@ public async Task TransactionRequestHandler_PerformVoucherIssueRequest_Handle_Is

response.ShouldBeTrue();
}

[Fact]
public async Task TransactionRequestHandler_PerformReconciliationRequest_NoTransactions_Handle_IsHandled()
{
Mock<ITransactionService> transactionService = new Mock<ITransactionService>();
Mock<IDatabaseContext> databaseContext = new Mock<IDatabaseContext>();
transactionService.Setup(t => t.PerformReconciliation(It.IsAny<PerformReconciliationRequestModel>(), It.IsAny<CancellationToken>())).ReturnsAsync(true);
databaseContext.Setup(d => d.GetTransactions()).ReturnsAsync(new List<TransactionRecord>());
TransactionRequestHandler handler = new TransactionRequestHandler(transactionService.Object, databaseContext.Object);

PerformReconciliationRequest request = PerformReconciliationRequest.Create(TestData.TransactionDateTime,
TestData.DeviceIdentifier,
TestData.ApplicationVersion);

Boolean response = await handler.Handle(request, CancellationToken.None);

response.ShouldBeTrue();
}

[Fact]
public async Task TransactionRequestHandler_PerformReconciliationRequest_TransactionsStored_Handle_IsHandled()
{
Mock<ITransactionService> transactionService = new Mock<ITransactionService>();
Mock<IDatabaseContext> databaseContext = new Mock<IDatabaseContext>();
transactionService.Setup(t => t.PerformReconciliation(It.IsAny<PerformReconciliationRequestModel>(), It.IsAny<CancellationToken>())).ReturnsAsync(true);

databaseContext.Setup(d => d.GetTransactions()).ReturnsAsync(TestData.StoredTransactions);
TransactionRequestHandler handler = new TransactionRequestHandler(transactionService.Object, databaseContext.Object);

PerformReconciliationRequest request = PerformReconciliationRequest.Create(TestData.TransactionDateTime,
TestData.DeviceIdentifier,
TestData.ApplicationVersion);

Boolean response = await handler.Handle(request, CancellationToken.None);

response.ShouldBeTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,17 @@ public void PerformVoucherIssueRequest_Create_IsCreated()
request.VoucherAmount.ShouldBe(TestData.Operator3Product_200KES.Value);
request.CustomerEmailAddress.ShouldBe(TestData.CustomerEmailAddress);
}

[Fact]
public void PerformReconciliationRequest_Create_IsCreated()
{
PerformReconciliationRequest request = PerformReconciliationRequest.Create(TestData.TransactionDateTime,TestData.DeviceIdentifier,
TestData.ApplicationVersion);

request.ShouldNotBeNull();
request.TransactionDateTime.ShouldBe(TestData.TransactionDateTime);
request.DeviceIdentifier.ShouldBe(TestData.DeviceIdentifier);
request.ApplicationVersion.ShouldBe(TestData.ApplicationVersion);

}
}
24 changes: 24 additions & 0 deletions TransactionMobile.Maui.BusinessLogic.Tests/TestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace TransactionMobile.Maui.BusinessLogic.Tests
{
using Database;
using Microsoft.Win32.SafeHandles;
using Models;

Expand Down Expand Up @@ -141,6 +142,29 @@ public static class TestData

public static Decimal MerchantBalance = 199.99m;

public static List<TransactionRecord> StoredTransactions =>
new List<TransactionRecord>
{
new TransactionRecord
{
Amount = TestData.Operator1Product_100KES.Value,
ContractId = TestData.Operator1Product_100KES.ContractId,
OperatorIdentifier = TestData.Operator1Product_100KES.OperatorIdentfier
},
new TransactionRecord
{
Amount = TestData.Operator1Product_100KES.Value,
ContractId = TestData.Operator1Product_100KES.ContractId,
OperatorIdentifier = TestData.Operator1Product_100KES.OperatorIdentfier
},
new TransactionRecord
{
Amount = TestData.Operator1Product_100KES.Value,
ContractId = TestData.Operator1Product_100KES.ContractId,
OperatorIdentifier = TestData.Operator1Product_100KES.OperatorIdentfier
}
};

public static PerformLogonResponseModel PerformLogonResponseModel =>
new PerformLogonResponseModel
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests
{
using Maui.UIServices;
using MediatR;
using Microsoft.Extensions.Caching.Memory;
using Moq;
using UIServices;
using ViewModels.Admin;
using ViewModels.Transactions;
using Xunit;

public class AdminPageViewModelTests
{
[Fact]
public void TransactionsPageViewModel_AdminCommand_Execute_IsExecuted()
{
Mock<INavigationService> navigationService = new Mock<INavigationService>();
Mock<IMediator> mediator = new Mock<IMediator>();
Mock<IMemoryCache> userDetailsCache = new Mock<IMemoryCache>();
Mock<IMemoryCache> configurationCache = new Mock<IMemoryCache>();
Mock<IDeviceService> deviceService = new Mock<IDeviceService>();
Mock<IApplicationInfoService> applicationInfoService = new Mock<IApplicationInfoService>();
AdminPageViewModel viewModel = new AdminPageViewModel(mediator.Object,
navigationService.Object,
userDetailsCache.Object,
configurationCache.Object,
deviceService.Object,
applicationInfoService.Object);

viewModel.ReconciliationCommand.Execute(null);
navigationService.Verify(n => n.GoToHome(), Times.Once);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public void TransactionsPageViewModel_AdminCommand_Execute_IsExecuted()
TransactionsPageViewModel viewModel = new TransactionsPageViewModel(navigationService.Object);

viewModel.AdminCommand.Execute(null);
navigationService.Verify(n => n.GoToHome(), Times.Once);
navigationService.Verify(n => n.GoToAdminPage(), Times.Once);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace TransactionMobile.Maui.BusinessLogic.Models;

public class OperatorTotalModel
{
public Guid ContractId { get; set; }
public String OperatorIdentifier { get; set; }
public Decimal TransactionValue { get; set; }
public Int32 TransactionCount { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace TransactionMobile.Maui.BusinessLogic.Models;

public class PerformReconciliationRequestModel
{
public List<OperatorTotalModel> OperatorTotals { get; set; }
public Decimal TransactionValue { get; set; }
public Int32 TransactionCount { get; set; }
public String DeviceIdentifier { get; set; }
public DateTime TransactionDateTime { get; set; }
public String ApplicationVersion { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
using Requests;
using Services;

public class TransactionRequestHandler : IRequestHandler<PerformMobileTopupRequest, Boolean>,
public class TransactionRequestHandler : IRequestHandler<PerformMobileTopupRequest, Boolean>,
IRequestHandler<LogonTransactionRequest, PerformLogonResponseModel>,
IRequestHandler<PerformVoucherIssueRequest, Boolean>
IRequestHandler<PerformVoucherIssueRequest, Boolean>,
IRequestHandler<PerformReconciliationRequest, Boolean>
{
#region Fields

Expand All @@ -20,7 +21,8 @@ public class TransactionRequestHandler : IRequestHandler<PerformMobileTopupReque

#region Constructors

public TransactionRequestHandler(ITransactionService transactionService, IDatabaseContext databaseContext)
public TransactionRequestHandler(ITransactionService transactionService,
IDatabaseContext databaseContext)
{
this.TransactionService = transactionService;
this.DatabaseContext = databaseContext;
Expand Down Expand Up @@ -132,7 +134,7 @@ private async Task UpdateTransactionRecord(TransactionRecord transactionRecord,
CancellationToken cancellationToken)
{
transactionRecord.IsSuccessful = result;

await this.DatabaseContext.UpdateTransaction(transactionRecord);
}

Expand All @@ -148,8 +150,8 @@ public async Task<PerformLogonResponseModel> Handle(LogonTransactionRequest requ
DeviceIdentifier = request.DeviceIdentifier,
TransactionDateTime = request.TransactionDateTime,
TransactionNumber = transaction.transactionNumber.ToString()
};
};

PerformLogonResponseModel result = await this.TransactionService.PerformLogon(model, cancellationToken);

await this.UpdateTransactionRecord(transaction.transactionRecord, result, cancellationToken);
Expand All @@ -165,24 +167,79 @@ public async Task<Boolean> Handle(PerformVoucherIssueRequest request,
(TransactionRecord transactionRecord, Int64 transactionNumber) transaction = await this.CreateTransactionRecord(request, cancellationToken);
// TODO: Factory
PerformVoucherIssueRequestModel model = new PerformVoucherIssueRequestModel
{
ApplicationVersion = request.ApplicationVersion,
ContractId = request.ContractId,
RecipientEmailAddress = request.RecipientEmailAddress,
RecipientMobileNumber = request.RecipientMobileNumber,
CustomerEmailAddress = request.CustomerEmailAddress,
DeviceIdentifier = request.DeviceIdentifier,
OperatorIdentifier = request.OperatorIdentifier,
ProductId = request.ProductId,
VoucherAmount = request.VoucherAmount,
TransactionDateTime = request.TransactionDateTime,
TransactionNumber = transaction.transactionNumber.ToString()
};
{
ApplicationVersion = request.ApplicationVersion,
ContractId = request.ContractId,
RecipientEmailAddress = request.RecipientEmailAddress,
RecipientMobileNumber = request.RecipientMobileNumber,
CustomerEmailAddress = request.CustomerEmailAddress,
DeviceIdentifier = request.DeviceIdentifier,
OperatorIdentifier = request.OperatorIdentifier,
ProductId = request.ProductId,
VoucherAmount = request.VoucherAmount,
TransactionDateTime = request.TransactionDateTime,
TransactionNumber = transaction.transactionNumber.ToString()
};

Boolean result = await this.TransactionService.PerformVoucherIssue(model, cancellationToken);

await this.UpdateTransactionRecord(transaction.transactionRecord, result, cancellationToken);

return result;
}

public async Task<Boolean> Handle(PerformReconciliationRequest request,
CancellationToken cancellationToken)
{
List<TransactionRecord> storedTransactions = await this.DatabaseContext.GetTransactions();

if (storedTransactions.Any() == false)
{
return true;
}

// TODO: convert these to operator totals
List<OperatorTotalModel> operatorTotals = (from t in storedTransactions
where t.IsSuccessful = true &&
t.TransactionType != 1 // Filter out logons
group t by new
{
t.ContractId,
t.OperatorIdentifier
}
into tempOperatorTotals
select new OperatorTotalModel
{
ContractId = tempOperatorTotals.Key.ContractId,
OperatorIdentifier = tempOperatorTotals.Key.OperatorIdentifier,
TransactionValue = tempOperatorTotals.Sum(t => t.Amount),
TransactionCount = tempOperatorTotals.Count()
}).ToList();

var grandTotals = new
{
TransactionValue = operatorTotals.Sum(t => t.TransactionValue),
TransactionCount = operatorTotals.Sum(t => t.TransactionCount)
};

PerformReconciliationRequestModel model = new PerformReconciliationRequestModel
{
ApplicationVersion = request.ApplicationVersion,
DeviceIdentifier = request.DeviceIdentifier,
TransactionDateTime = request.TransactionDateTime,
TransactionValue = grandTotals.TransactionValue,
TransactionCount = grandTotals.TransactionCount,
OperatorTotals = operatorTotals
};
// Send to the host
Boolean result = await this.TransactionService.PerformReconciliation(model, cancellationToken);

// Clear store (if successful)
if (result)
{
await this.DatabaseContext.ClearStoredTransactions();
}

return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TransactionMobile.Maui.BusinessLogic.Requests
{
using MediatR;

public class PerformReconciliationRequest : IRequest<Boolean>
{
public String DeviceIdentifier { get; private set; }
public DateTime TransactionDateTime { get; private set; }
public String ApplicationVersion { get; private set; }


private PerformReconciliationRequest(DateTime transactionDateTime,
String deviceIdentifier,
String applicationVersion)
{
this.TransactionDateTime = transactionDateTime;
this.DeviceIdentifier = deviceIdentifier;
this.ApplicationVersion = applicationVersion;
}

public static PerformReconciliationRequest Create(DateTime transactionDateTime,
String deviceIdentifier,
String applicationVersion)
{
return new PerformReconciliationRequest(transactionDateTime, deviceIdentifier, applicationVersion);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public async Task<Boolean> PerformMobileTopup(PerformMobileTopupRequestModel mod
return true;
}

public async Task<Boolean> PerformReconciliation(CancellationToken cancellationToken)
public async Task<Boolean> PerformReconciliation(PerformReconciliationRequestModel model,CancellationToken cancellationToken)
{
return true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace TransactionMobile.Maui.BusinessLogic.Services
{
using Models;
using RequestHandlers;

public interface ITransactionService
{
Expand All @@ -10,7 +11,7 @@ public interface ITransactionService

Task<Boolean> PerformMobileTopup(PerformMobileTopupRequestModel model, CancellationToken cancellationToken);

Task<Boolean> PerformReconciliation(CancellationToken cancellationToken);
Task<Boolean> PerformReconciliation(PerformReconciliationRequestModel model, CancellationToken cancellationToken);

Task<Boolean> PerformVoucherIssue(PerformVoucherIssueRequestModel model, CancellationToken cancellationToken);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Task GoToMobileTopupPerformTopupPage(String operatorIdentifier,

Task GoToMobileTopupSelectOperatorPage();

Task GoToAdminPage();

Task GoToMobileTopupSelectProductPage(String operatorIdentifier);

Task GoToMobileTopupSuccessPage();
Expand Down
Loading