diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/RequestHandlerTests/SupportRequestHandlerTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/RequestHandlerTests/SupportRequestHandlerTests.cs new file mode 100644 index 00000000..7f24ea13 --- /dev/null +++ b/TransactionMobile.Maui.BusinessLogic.Tests/RequestHandlerTests/SupportRequestHandlerTests.cs @@ -0,0 +1,97 @@ +namespace TransactionMobile.Maui.BusinessLogic.Tests.RequestHandlerTests; + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Database; +using Moq; +using RequestHandlers; +using Requests; +using Services; +using Shouldly; +using Xunit; + +public class SupportRequestHandlerTests +{ + [Fact] + public async Task SupportRequestHandlerTests_UploadLogsRequest_NoLogs_Handle_IsHandled() + { + Mock configurationService = new Mock(); + Mock databaseContext = new Mock(); + databaseContext.Setup(d => d.GetLogMessages(It.IsAny())).ReturnsAsync(new List()); + + SupportRequestHandler handler = new SupportRequestHandler(configurationService.Object, databaseContext.Object); + + UploadLogsRequest request = UploadLogsRequest.Create(TestData.DeviceIdentifier); + + Boolean response = await handler.Handle(request, CancellationToken.None); + + response.ShouldBeTrue(); + } + + [Fact] + public async Task SupportRequestHandlerTests_UploadLogsRequest_LogsToUpload_Only10Messages_Handle_IsHandled() + { + Mock configurationService = new Mock(); + Mock databaseContext = new Mock(); + databaseContext.SetupSequence(d => d.GetLogMessages(It.IsAny())).ReturnsAsync(new List() + { + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage() + }).ReturnsAsync(new List()); + + SupportRequestHandler handler = new SupportRequestHandler(configurationService.Object, databaseContext.Object); + + UploadLogsRequest request = UploadLogsRequest.Create(TestData.DeviceIdentifier); + + Boolean response = await handler.Handle(request, CancellationToken.None); + + response.ShouldBeTrue(); + databaseContext.Verify(d => d.RemoveUploadedMessages(It.IsAny>()), Times.Once); + } + + [Fact] + public async Task SupportRequestHandlerTests_UploadLogsRequest_LogsToUpload_15Messages_Handle_IsHandled() + { + Mock configurationService = new Mock(); + Mock databaseContext = new Mock(); + databaseContext.SetupSequence(d => d.GetLogMessages(It.IsAny())).ReturnsAsync(new List() + { + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + }).ReturnsAsync(new List() + { + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + new Database.LogMessage(), + }).ReturnsAsync(new List()); + + SupportRequestHandler handler = new SupportRequestHandler(configurationService.Object, databaseContext.Object); + + UploadLogsRequest request = UploadLogsRequest.Create(TestData.DeviceIdentifier); + + Boolean response = await handler.Handle(request, CancellationToken.None); + + response.ShouldBeTrue(); + databaseContext.Verify(d => d.RemoveUploadedMessages(It.IsAny>()), Times.Exactly(2)); + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/RequestHandlerTests/TransactionRequestHandlerTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/RequestHandlerTests/TransactionRequestHandlerTests.cs index 3602fc27..4b154029 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/RequestHandlerTests/TransactionRequestHandlerTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/RequestHandlerTests/TransactionRequestHandlerTests.cs @@ -20,6 +20,7 @@ public async Task TransactionRequestHandler_LogonTransactionRequest_Handle_IsHan { Mock transactionService = new Mock(); Mock databaseContext = new Mock(); + transactionService.Setup(t => t.PerformLogon(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.PerformLogonResponseModel); TransactionRequestHandler handler = new TransactionRequestHandler(transactionService.Object, databaseContext.Object); @@ -118,4 +119,4 @@ public async Task TransactionRequestHandler_PerformReconciliationRequest_Transac response.ShouldBeTrue(); } -} \ No newline at end of file +} diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/RequestTests/RequestTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/RequestTests/RequestTests.cs index 10932226..864a80f6 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/RequestTests/RequestTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/RequestTests/RequestTests.cs @@ -143,6 +143,14 @@ public void PerformReconciliationRequest_Create_IsCreated() request.TransactionDateTime.ShouldBe(TestData.TransactionDateTime); request.DeviceIdentifier.ShouldBe(TestData.DeviceIdentifier); request.ApplicationVersion.ShouldBe(TestData.ApplicationVersion); + } + + [Fact] + public void UploadLogsRequest_Create_IsCreated() + { + UploadLogsRequest request = UploadLogsRequest.Create(TestData.DeviceIdentifier); + request.ShouldNotBeNull(); + request.DeviceIdentifier.ShouldBe(TestData.DeviceIdentifier); } } \ No newline at end of file diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/AdminPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/AdminPageViewModelTests.cs index d1b8c6ee..6bdd9653 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/AdminPageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/AdminPageViewModelTests.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/LoginPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/LoginPageViewModelTests.cs index 83c3a685..e2fcdbef 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/LoginPageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/LoginPageViewModelTests.cs @@ -8,7 +8,9 @@ namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests; using Models; using Moq; using Requests; +using Shared.Logger; using TransactionMobile.Maui.BusinessLogic.Services; +using TransactionMobile.Maui.Database; using UIServices; using ViewModels; using Xunit; @@ -25,7 +27,7 @@ public void LoginPageViewModel_LoginCommand_Execute_IsExecuted() Mock applicationInfoService = new Mock(); LoginPageViewModel viewModel = new LoginPageViewModel(mediator.Object, navigationService.Object, memoryCacheService.Object, deviceService.Object,applicationInfoService.Object); - + Logger.Initialise(NullLogger.Instance); mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(new Configuration()); mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.AccessToken); mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.PerformLogonResponseModel); diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupFailedPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupFailedPageViewModelTests.cs index f6e9536c..92b8e75a 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupFailedPageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupFailedPageViewModelTests.cs @@ -2,6 +2,7 @@ namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests; using Maui.UIServices; using Moq; +using Shared.Logger; using UIServices; using ViewModels.Transactions; using Xunit; @@ -12,6 +13,7 @@ public class MobileTopupFailedPageViewModelTests public void MobileTopupFailedPageViewModel_CancelledCommand_Execute_IsExecuted() { Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); MobileTopupFailedPageViewModel viewModel = new MobileTopupFailedPageViewModel(navigationService.Object); viewModel.CancelledCommand.Execute(null); diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupPerformTopupPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupPerformTopupPageViewModelTests.cs index c0c44bd1..ea956e8d 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupPerformTopupPageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupPerformTopupPageViewModelTests.cs @@ -7,6 +7,7 @@ namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests; using MediatR; using Moq; using Requests; +using Shared.Logger; using Shouldly; using UIServices; using ViewModels.Transactions; @@ -19,6 +20,7 @@ public void MobileTopupPerformTopupPageViewModel_ApplyQueryAttributes_QueryAttri { Mock mediator = new Mock(); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); MobileTopupPerformTopupPageViewModel viewModel = new MobileTopupPerformTopupPageViewModel(mediator.Object, navigationService.Object); viewModel.ApplyQueryAttributes(new Dictionary @@ -40,6 +42,7 @@ public void MobileTopupPerformTopupPageViewModel_CustomerEmailAddressEntryComple { Mock mediator = new Mock(); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); MobileTopupPerformTopupPageViewModel viewModel = new MobileTopupPerformTopupPageViewModel(mediator.Object, navigationService.Object); Boolean isCompletedCalled = false; viewModel.OnCustomerEmailAddressEntryCompleted = () => @@ -63,6 +66,7 @@ public void MobileTopupPerformTopupPageViewModel_CustomerMobileNumberEntryComple { Mock mediator = new Mock(); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); MobileTopupPerformTopupPageViewModel viewModel = new MobileTopupPerformTopupPageViewModel(mediator.Object, navigationService.Object); Boolean isCompletedCalled = false; viewModel.OnCustomerMobileNumberEntryCompleted = () => @@ -86,6 +90,7 @@ public void MobileTopupPerformTopupPageViewModel_TopupAmountEntryCompletedComman { Mock mediator = new Mock(); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); MobileTopupPerformTopupPageViewModel viewModel = new MobileTopupPerformTopupPageViewModel(mediator.Object, navigationService.Object); Boolean isCompletedCalled = false; viewModel.OnTopupAmountEntryCompleted = () => @@ -110,6 +115,7 @@ public void MobileTopupPerformTopupPageViewModel_PerformTopupCommand_Execute_Suc Mock mediator = new Mock(); mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(true); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); MobileTopupPerformTopupPageViewModel viewModel = new MobileTopupPerformTopupPageViewModel(mediator.Object, navigationService.Object); viewModel.ApplyQueryAttributes(new Dictionary { @@ -129,6 +135,7 @@ public void MobileTopupPerformTopupPageViewModel_PerformTopupCommand_Execute_Fai Mock mediator = new Mock(); mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(false); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); MobileTopupPerformTopupPageViewModel viewModel = new MobileTopupPerformTopupPageViewModel(mediator.Object, navigationService.Object); viewModel.ApplyQueryAttributes(new Dictionary { diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupSelectOperatorPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupSelectOperatorPageViewModelTests.cs index f0ec1937..f71e9d2f 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupSelectOperatorPageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupSelectOperatorPageViewModelTests.cs @@ -7,6 +7,7 @@ namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests; using Models; using Moq; using Requests; +using Shared.Logger; using Shouldly; using UIServices; using ViewModels.Transactions; @@ -34,6 +35,7 @@ public async Task MobileTopupSelectOperatorPageViewModel_OperatorSelectedCommand Mock mediator = new Mock(); mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.ContractProductList); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); MobileTopupSelectOperatorPageViewModel viewModel = new MobileTopupSelectOperatorPageViewModel(mediator.Object, navigationService.Object); await viewModel.Initialise(CancellationToken.None); diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupSelectProductPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupSelectProductPageViewModelTests.cs index 1c09a60e..9bba5aec 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupSelectProductPageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupSelectProductPageViewModelTests.cs @@ -9,6 +9,7 @@ namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests; using Models; using Moq; using Requests; +using Shared.Logger; using Shouldly; using UIServices; using ViewModels.Transactions; @@ -55,6 +56,7 @@ public async Task MobileTopupSelectProductPageViewModel_ProductSelectedCommand_E Mock mediator = new Mock(); mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.ContractProductList); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); MobileTopupSelectProductPageViewModel viewModel = new MobileTopupSelectProductPageViewModel(mediator.Object, navigationService.Object); viewModel.ApplyQueryAttributes(new Dictionary diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupSuccessPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupSuccessPageViewModelTests.cs index 59ea016c..2ba7c465 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupSuccessPageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/MobileTopupSuccessPageViewModelTests.cs @@ -2,6 +2,7 @@ namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests; using Maui.UIServices; using Moq; +using Shared.Logger; using UIServices; using ViewModels.Transactions; using Xunit; @@ -12,6 +13,7 @@ public class MobileTopupSuccessPageViewModelTests public void MobileTopupSuccessPageViewModel_CompletedCommand_Execute_IsExecuted() { Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); MobileTopupSuccessPageViewModel viewModel = new MobileTopupSuccessPageViewModel(navigationService.Object); viewModel.CompletedCommand.Execute(null); diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/SupportPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/SupportPageViewModelTests.cs new file mode 100644 index 00000000..43c67609 --- /dev/null +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/SupportPageViewModelTests.cs @@ -0,0 +1,34 @@ +using System; + +namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests +{ + using Maui.UIServices; + using MediatR; + using Moq; + using UIServices; + using Xunit; + using TransactionMobile.Maui.BusinessLogic.ViewModels.Support; + using TransactionMobile.Maui.Database; + using Shared.Logger; + using System.Collections.Generic; + + public class SupportPageViewModelTests + { + [Fact] + public void SupportPageViewModel_UploadLogsCommand_Execute_IsExecuted() + { + Mock navigationService = new Mock(); + Mock databaseContext = new Mock(); + Mock mediator = new Mock(); + Mock deviceService = new Mock(); + Mock applicationInfoService = new Mock(); + Logger.Initialise(NullLogger.Instance); + SupportPageViewModel viewModel = new SupportPageViewModel(deviceService.Object,applicationInfoService.Object, + databaseContext.Object, mediator.Object,navigationService.Object); + + viewModel.UploadLogsCommand.Execute(null); + + navigationService.Verify(n => n.GoToHome(), Times.Once); + } + } +} diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/TransactionsPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/TransactionsPageViewModelTests.cs index c2430d6c..55f49cd8 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/TransactionsPageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/TransactionsPageViewModelTests.cs @@ -2,6 +2,7 @@ namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests; using Maui.UIServices; using Moq; +using Shared.Logger; using UIServices; using ViewModels.Transactions; using Xunit; @@ -22,6 +23,7 @@ public void TransactionsPageViewModel_AdminCommand_Execute_IsExecuted() public void TransactionsPageViewModel_BillPaymentCommand_Execute_IsExecuted() { Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); TransactionsPageViewModel viewModel = new TransactionsPageViewModel(navigationService.Object); viewModel.BillPaymentCommand.Execute(null); @@ -32,6 +34,7 @@ public void TransactionsPageViewModel_BillPaymentCommand_Execute_IsExecuted() public void TransactionsPageViewModel_MobileTopupCommand_Execute_IsExecuted() { Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); TransactionsPageViewModel viewModel = new TransactionsPageViewModel(navigationService.Object); viewModel.MobileTopupCommand.Execute(null); @@ -42,6 +45,7 @@ public void TransactionsPageViewModel_MobileTopupCommand_Execute_IsExecuted() public void TransactionsPageViewModel_MobileWalletCommand_Execute_IsExecuted() { Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); TransactionsPageViewModel viewModel = new TransactionsPageViewModel(navigationService.Object); viewModel.MobileWalletCommand.Execute(null); @@ -52,6 +56,7 @@ public void TransactionsPageViewModel_MobileWalletCommand_Execute_IsExecuted() public void TransactionsPageViewModel_VoucherCommand_Execute_IsExecuted() { Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); TransactionsPageViewModel viewModel = new TransactionsPageViewModel(navigationService.Object); viewModel.VoucherCommand.Execute(null); diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherIssueFailedPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherIssueFailedPageViewModelTests.cs index c7150f57..06aa1cd3 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherIssueFailedPageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherIssueFailedPageViewModelTests.cs @@ -2,6 +2,7 @@ namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests; using Maui.UIServices; using Moq; +using Shared.Logger; using UIServices; using ViewModels.Transactions; using Xunit; @@ -12,6 +13,7 @@ public class VoucherIssueFailedPageViewModelTests public void VoucherIssueFailedPageViewModel_CancelledCommand_Execute_IsExecuted() { Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); VoucherIssueFailedPageViewModel viewModel = new VoucherIssueFailedPageViewModel(navigationService.Object); viewModel.CancelledCommand.Execute(null); diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherIssueSelectProductPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherIssueSelectProductPageViewModelTests.cs index 1e496d76..6fa260fb 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherIssueSelectProductPageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherIssueSelectProductPageViewModelTests.cs @@ -9,6 +9,7 @@ namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests; using Models; using Moq; using Requests; +using Shared.Logger; using Shouldly; using UIServices; using ViewModels.Transactions; @@ -55,6 +56,7 @@ public async Task VoucherIssueSelectProductPageViewModel_ProductSelectedCommand_ Mock mediator = new Mock(); mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.ContractProductList); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); VoucherSelectProductPageViewModel viewModel = new VoucherSelectProductPageViewModel(mediator.Object, navigationService.Object); viewModel.ApplyQueryAttributes(new Dictionary diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherIssueSuccessPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherIssueSuccessPageViewModelTests.cs index 90871977..3b2515d1 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherIssueSuccessPageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherIssueSuccessPageViewModelTests.cs @@ -2,6 +2,7 @@ namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests; using Maui.UIServices; using Moq; +using Shared.Logger; using UIServices; using ViewModels.Transactions; using Xunit; @@ -12,6 +13,7 @@ public class VoucherIssueSuccessPageViewModelTests public void VoucherIssueSuccessPageViewModel_CompletedCommand_Execute_IsExecuted() { Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); VoucherIssueSuccessPageViewModel viewModel = new VoucherIssueSuccessPageViewModel(navigationService.Object); viewModel.CompletedCommand.Execute(null); diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherPerformIssuePageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherPerformIssuePageViewModelTests.cs index ec4db14e..07623395 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherPerformIssuePageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherPerformIssuePageViewModelTests.cs @@ -7,6 +7,7 @@ namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests; using MediatR; using Moq; using Requests; +using Shared.Logger; using Shouldly; using UIServices; using ViewModels.Transactions; @@ -40,6 +41,7 @@ public void VoucherPerformIssuePageViewModel_CustomerEmailAddressEntryCompletedC { Mock mediator = new Mock(); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); VoucherPerformIssuePageViewModel viewModel = new VoucherPerformIssuePageViewModel(mediator.Object, navigationService.Object); Boolean isCompletedCalled = false; viewModel.OnCustomerEmailAddressEntryCompleted = () => @@ -63,6 +65,7 @@ public void VoucherPerformIssuePageViewModel_RecipientMobileNumberEntryCompleted { Mock mediator = new Mock(); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); VoucherPerformIssuePageViewModel viewModel = new VoucherPerformIssuePageViewModel(mediator.Object, navigationService.Object); Boolean isCompletedCalled = false; viewModel.OnRecipientMobileNumberEntryCompleted = () => @@ -86,6 +89,7 @@ public void VoucherPerformIssuePageViewModel_RecipientEmailAddressEntryCompleted { Mock mediator = new Mock(); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); VoucherPerformIssuePageViewModel viewModel = new VoucherPerformIssuePageViewModel(mediator.Object, navigationService.Object); Boolean isCompletedCalled = false; viewModel.OnRecipientEmailAddressEntryCompleted = () => @@ -109,6 +113,7 @@ public void VoucherPerformIssuePageViewModel_VoucherAmountEntryCompletedCommand_ { Mock mediator = new Mock(); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); VoucherPerformIssuePageViewModel viewModel = new VoucherPerformIssuePageViewModel(mediator.Object, navigationService.Object); Boolean isCompletedCalled = false; viewModel.OnVoucherAmountEntryCompleted = () => @@ -133,6 +138,7 @@ public void VoucherPerformIssuePageViewModel_IssueVoucherCommand_Execute_Success Mock mediator = new Mock(); mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(true); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); VoucherPerformIssuePageViewModel viewModel = new VoucherPerformIssuePageViewModel(mediator.Object, navigationService.Object); viewModel.ApplyQueryAttributes(new Dictionary { @@ -152,6 +158,8 @@ public void VoucherPerformIssuePageViewModel_IssueVoucherCommand_Execute_FailedV Mock mediator = new Mock(); mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(false); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); + VoucherPerformIssuePageViewModel viewModel = new VoucherPerformIssuePageViewModel(mediator.Object, navigationService.Object); viewModel.ApplyQueryAttributes(new Dictionary { diff --git a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherSelectOperatorPageViewModelTests.cs b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherSelectOperatorPageViewModelTests.cs index fd5036bf..c0386809 100644 --- a/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherSelectOperatorPageViewModelTests.cs +++ b/TransactionMobile.Maui.BusinessLogic.Tests/ViewModelTests/VoucherSelectOperatorPageViewModelTests.cs @@ -7,6 +7,7 @@ namespace TransactionMobile.Maui.BusinessLogic.Tests.ViewModelTests; using Models; using Moq; using Requests; +using Shared.Logger; using Shouldly; using UIServices; using ViewModels.Transactions; @@ -34,6 +35,7 @@ public async Task VoucherSelectOperatorPageViewModel_OperatorSelectedCommand_Exe Mock mediator = new Mock(); mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.ContractProductList); Mock navigationService = new Mock(); + Logger.Initialise(NullLogger.Instance); VoucherSelectOperatorPageViewModel viewModel = new VoucherSelectOperatorPageViewModel(mediator.Object, navigationService.Object); await viewModel.Initialise(CancellationToken.None); diff --git a/TransactionMobile.Maui.BusinessLogic/Logging/ILogger.cs b/TransactionMobile.Maui.BusinessLogic/Logging/ILogger.cs new file mode 100644 index 00000000..1d040d80 --- /dev/null +++ b/TransactionMobile.Maui.BusinessLogic/Logging/ILogger.cs @@ -0,0 +1,63 @@ +namespace Shared.Logger +{ + using System; + using LogLevel = Microsoft.Extensions.Logging.LogLevel; + + /// + /// + /// + public interface ILogger + { + #region Properties + + /// + /// Gets or sets a value indicating whether this instance is initialised. + /// + /// + /// true if this instance is initialised; otherwise, false. + /// + Boolean IsInitialised { get; set; } + + #endregion + + #region Methods + + /// + /// Logs the critical. + /// + /// The exception. + void LogCritical(Exception exception); + + /// + /// Logs the debug. + /// + /// The message. + void LogDebug(String message); + + /// + /// Logs the error. + /// + /// The exception. + void LogError(Exception exception); + + /// + /// Logs the information. + /// + /// The message. + void LogInformation(String message); + + /// + /// Logs the trace. + /// + /// The message. + void LogTrace(String message); + + /// + /// Logs the warning. + /// + /// The message. + void LogWarning(String message); + + #endregion + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.BusinessLogic/Logging/Logger.cs b/TransactionMobile.Maui.BusinessLogic/Logging/Logger.cs new file mode 100644 index 00000000..cc8279ca --- /dev/null +++ b/TransactionMobile.Maui.BusinessLogic/Logging/Logger.cs @@ -0,0 +1,125 @@ +namespace Shared.Logger +{ + using System; + + /// + /// + /// + public static class Logger + { + #region Fields + + /// + /// The logger object + /// + private static ILogger LoggerObject; + + #endregion + + #region Properties + + /// + /// Gets or sets a value indicating whether this instance is initialised. + /// + /// + /// true if this instance is initialised; otherwise, false. + /// + public static Boolean IsInitialised { get; set; } + + #endregion + + #region Methods + + /// + /// Initialises the specified logger object. + /// + /// The logger object. + /// loggerObject + public static void Initialise(ILogger loggerObject) + { + Logger.LoggerObject = loggerObject ?? throw new ArgumentNullException(nameof(loggerObject)); + + Logger.IsInitialised = true; + } + + /// + /// Logs the critical. + /// + /// The exception. + public static void LogCritical(Exception exception) + { + Logger.ValidateLoggerObject(); + + Logger.LoggerObject.LogCritical(exception); + } + + /// + /// Logs the debug. + /// + /// The message. + public static void LogDebug(String message) + { + Logger.ValidateLoggerObject(); + + Logger.LoggerObject.LogDebug(message); + } + + /// + /// Logs the error. + /// + /// The exception. + public static void LogError(Exception exception) + { + Logger.ValidateLoggerObject(); + + Logger.LoggerObject.LogError(exception); + } + + /// + /// Logs the information. + /// + /// The message. + public static void LogInformation(String message) + { + Logger.ValidateLoggerObject(); + + Logger.LoggerObject.LogInformation(message); + } + + /// + /// Logs the trace. + /// + /// The message. + public static void LogTrace(String message) + { + Logger.ValidateLoggerObject(); + + Logger.LoggerObject.LogTrace(message); + } + + /// + /// Logs the warning. + /// + /// The message. + public static void LogWarning(String message) + { + Logger.ValidateLoggerObject(); + + Logger.LoggerObject.LogWarning(message); + } + + /// + /// Validates the logger object. + /// + /// Logger has not been initialised + private static void ValidateLoggerObject() + { + if (Logger.LoggerObject == null) + { + throw new InvalidOperationException("Logger has not been initialised"); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.BusinessLogic/Logging/NullLogger.cs b/TransactionMobile.Maui.BusinessLogic/Logging/NullLogger.cs new file mode 100644 index 00000000..9c416f14 --- /dev/null +++ b/TransactionMobile.Maui.BusinessLogic/Logging/NullLogger.cs @@ -0,0 +1,95 @@ +namespace Shared.Logger +{ + using System; + + /// + /// + /// + /// + public class NullLogger : ILogger + { + #region Properties + + /// + /// Gets or sets a value indicating whether this instance is initialised. + /// + /// + /// true if this instance is initialised; otherwise, false. + /// + public Boolean IsInitialised { get; set; } + + #endregion + + #region Methods + + /// + /// Initialises this instance. + /// + public void Initialise() + { + this.IsInitialised = true; + } + + /// + /// Instances this instance. + /// + /// + public static NullLogger Instance + { + get + { + return new NullLogger(); + } + } + + /// + /// Logs the critical. + /// + /// The exception. + public void LogCritical(Exception exception) + { + } + + /// + /// Logs the debug. + /// + /// The message. + public void LogDebug(String message) + { + } + + /// + /// Logs the error. + /// + /// The exception. + public void LogError(Exception exception) + { + } + + /// + /// Logs the information. + /// + /// The message. + public void LogInformation(String message) + { + } + + /// + /// Logs the trace. + /// + /// The message. + public void LogTrace(String message) + { + } + + /// + /// Logs the warning. + /// + /// The message. + public void LogWarning(String message) + { + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.BusinessLogic/Models/LogMessage.cs b/TransactionMobile.Maui.BusinessLogic/Models/LogMessage.cs index a737a3c2..84bad1e2 100644 --- a/TransactionMobile.Maui.BusinessLogic/Models/LogMessage.cs +++ b/TransactionMobile.Maui.BusinessLogic/Models/LogMessage.cs @@ -19,5 +19,114 @@ public class LogMessage public String Message { get; set; } #endregion + + private static LogMessage CreateLogMessage(String message, + LogLevel logLevel) + { + return new LogMessage + { + EntryDateTime = DateTime.UtcNow, + Message = message, + LogLevel = logLevel.ToString() + }; + } + + /// + /// Creates the debug log message. + /// + /// The message. + /// + public static LogMessage CreateDebugLogMessage(String message) + { + return LogMessage.CreateLogMessage(message, Models.LogLevel.Debug); + } + + /// + /// Creates the error log message. + /// + /// The message. + /// + public static LogMessage CreateErrorLogMessage(String message) + { + return LogMessage.CreateLogMessage(message, Models.LogLevel.Error); + } + + /// + /// Creates the error log messages. + /// + /// The exception. + /// + public static List CreateErrorLogMessages(Exception exception) + { + List logMessages = new List(); + + Exception e = exception; + while (e != null) + { + logMessages.Add(LogMessage.CreateLogMessage(e.Message, Models.LogLevel.Error)); + e = e.InnerException; + } + + return logMessages; + } + + /// + /// Creates the fatal log message. + /// + /// The message. + /// + public static LogMessage CreateFatalLogMessage(String message) + { + return LogMessage.CreateLogMessage(message, Models.LogLevel.Fatal); + } + + /// + /// Creates the fatal log messages. + /// + /// The exception. + /// + public static List CreateFatalLogMessages(Exception exception) + { + List logMessages = new List(); + + Exception e = exception; + while (e != null) + { + logMessages.Add(LogMessage.CreateLogMessage(e.Message, Models.LogLevel.Fatal)); + e = e.InnerException; + } + + return logMessages; + } + + /// + /// Creates the information log message. + /// + /// The message. + /// + public static LogMessage CreateInformationLogMessage(String message) + { + return LogMessage.CreateLogMessage(message, Models.LogLevel.Info); + } + + /// + /// Creates the trace log message. + /// + /// The message. + /// + public static LogMessage CreateTraceLogMessage(String message) + { + return LogMessage.CreateLogMessage(message, Models.LogLevel.Trace); + } + + /// + /// Creates the warning log message. + /// + /// The message. + /// + public static LogMessage CreateWarningLogMessage(String message) + { + return LogMessage.CreateLogMessage(message, Models.LogLevel.Warn); + } } } diff --git a/TransactionMobile.Maui.BusinessLogic/RequestHandlers/SupportRequestHandler.cs b/TransactionMobile.Maui.BusinessLogic/RequestHandlers/SupportRequestHandler.cs new file mode 100644 index 00000000..770ecc1b --- /dev/null +++ b/TransactionMobile.Maui.BusinessLogic/RequestHandlers/SupportRequestHandler.cs @@ -0,0 +1,55 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TransactionMobile.Maui.BusinessLogic.Requests; +using TransactionMobile.Maui.BusinessLogic.Services; +using TransactionMobile.Maui.Database; + +namespace TransactionMobile.Maui.BusinessLogic.RequestHandlers +{ + public class SupportRequestHandler : IRequestHandler + { + private readonly IConfigurationService ConfigurationService; + private readonly IDatabaseContext DatabaseContext; + public SupportRequestHandler(IConfigurationService configurationService, + IDatabaseContext databaseContext) + { + this.ConfigurationService = configurationService; + this.DatabaseContext = databaseContext; + } + + public async Task Handle(UploadLogsRequest request, CancellationToken cancellationToken) + { + while (true) + { + var logEntries = await this.DatabaseContext.GetLogMessages(10); // TODO: Configurable batch size + + if (logEntries.Any() == false) + { + break; + } + + // TODO: Translate log messages + List logMessageModels = new List(); + + logEntries.ForEach(l => logMessageModels.Add(new Models.LogMessage + { + LogLevel = l.LogLevel, + Message = l.Message, + EntryDateTime = l.EntryDateTime, + Id = l.Id + })); + + await this.ConfigurationService.PostDiagnosticLogs(request.DeviceIdentifier, logMessageModels, CancellationToken.None); + + // Clear the logs that have been uploaded + await this.DatabaseContext.RemoveUploadedMessages(logEntries); + } + + return true; + } + } +} diff --git a/TransactionMobile.Maui.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs b/TransactionMobile.Maui.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs index 406f9a4e..61faa9c2 100644 --- a/TransactionMobile.Maui.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs +++ b/TransactionMobile.Maui.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs @@ -9,12 +9,11 @@ public class TransactionRequestHandler : IRequestHandler, IRequestHandler, IRequestHandler, - IRequestHandler + IRequestHandler { #region Fields private readonly ITransactionService TransactionService; - private readonly IDatabaseContext DatabaseContext; #endregion diff --git a/TransactionMobile.Maui.BusinessLogic/Requests/UploadLogsRequest.cs b/TransactionMobile.Maui.BusinessLogic/Requests/UploadLogsRequest.cs new file mode 100644 index 00000000..bd6c8c9d --- /dev/null +++ b/TransactionMobile.Maui.BusinessLogic/Requests/UploadLogsRequest.cs @@ -0,0 +1,25 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TransactionMobile.Maui.BusinessLogic.Requests +{ + + public class UploadLogsRequest : IRequest + { + private UploadLogsRequest(String deviceIdentifier) + { + DeviceIdentifier = deviceIdentifier; + } + + public string DeviceIdentifier { get; } + + public static UploadLogsRequest Create(String deviceIdentifier) + { + return new UploadLogsRequest(deviceIdentifier); + } + } +} diff --git a/TransactionMobile.Maui.BusinessLogic/Services/AuthenticationService.cs b/TransactionMobile.Maui.BusinessLogic/Services/AuthenticationService.cs index d38ec9bd..e760654a 100644 --- a/TransactionMobile.Maui.BusinessLogic/Services/AuthenticationService.cs +++ b/TransactionMobile.Maui.BusinessLogic/Services/AuthenticationService.cs @@ -8,6 +8,7 @@ namespace TransactionMobile.Maui.BusinessLogic.Services { using Microsoft.Extensions.Caching.Memory; using Models; + using Newtonsoft.Json; using SecurityService.Client; using SecurityService.DataTransferObjects.Responses; @@ -34,9 +35,15 @@ public async Task GetToken(String username, username = "merchantuser@v28emulatormerchant.co.uk"; password = "123456"; + Shared.Logger.Logger.LogInformation($"About to request token for {username}"); + Shared.Logger.Logger.LogDebug($"Token Request details UserName: {username} Password: {password} ClientId: {configuration.ClientId} ClientSecret: {configuration.ClientSecret}"); + TokenResponse token = await this.SecurityServiceClient.GetToken(username, password, configuration.ClientId, configuration.ClientSecret, cancellationToken); + Shared.Logger.Logger.LogInformation($"Token for {username} requested successfully"); + Shared.Logger.Logger.LogDebug($"Token Response: [{JsonConvert.SerializeObject(token)}]"); + return new TokenResponseModel { AccessToken = token.AccessToken, @@ -46,6 +53,7 @@ public async Task GetToken(String username, } catch(Exception ex) { + Shared.Logger.Logger.LogError(new Exception($"Error getting Token", ex)); return null; } @@ -56,15 +64,28 @@ public async Task RefreshAccessToken(String refreshToken, CancellationToken cancellationToken) { this.MemoryCacheService.TryGetValue("Configuration", out Configuration configuration); + try + { + Shared.Logger.Logger.LogInformation($"About to request refresh token"); + Shared.Logger.Logger.LogDebug($"Refresh Token Request details Token: {refreshToken} ClientId: {configuration.ClientId} ClientSecret: {configuration.ClientSecret}"); - TokenResponse token = await this.SecurityServiceClient.GetToken(configuration.ClientId,configuration.ClientSecret, refreshToken,cancellationToken); + TokenResponse token = await this.SecurityServiceClient.GetToken(configuration.ClientId, configuration.ClientSecret, refreshToken, cancellationToken); - return new TokenResponseModel - { - AccessToken = token.AccessToken, - ExpiryInMinutes = token.ExpiresIn, - RefreshToken = token.RefreshToken - }; + Shared.Logger.Logger.LogInformation($"Refresh Token requested successfully"); + Shared.Logger.Logger.LogDebug($"Token Response: [{JsonConvert.SerializeObject(token)}]"); + + return new TokenResponseModel + { + AccessToken = token.AccessToken, + ExpiryInMinutes = token.ExpiresIn, + RefreshToken = token.RefreshToken + }; + } + catch (Exception ex) + { + Shared.Logger.Logger.LogError(new Exception($"Error refreshing Token", ex)); + return null; + } } } } diff --git a/TransactionMobile.Maui.BusinessLogic/Services/ConfigurationService.cs b/TransactionMobile.Maui.BusinessLogic/Services/ConfigurationService.cs index 6a78ba2d..5ffca327 100644 --- a/TransactionMobile.Maui.BusinessLogic/Services/ConfigurationService.cs +++ b/TransactionMobile.Maui.BusinessLogic/Services/ConfigurationService.cs @@ -53,6 +53,9 @@ public async Task GetConfiguration(String deviceIdentifier, try { + Shared.Logger.Logger.LogInformation($"About to request configuration for device identifier {deviceIdentifier}"); + Shared.Logger.Logger.LogDebug($"Configuration Request details: Uri {requestUri}"); + // Make the Http Call here HttpResponseMessage httpResponse = await this.HttpClient.GetAsync(requestUri, cancellationToken); @@ -61,12 +64,17 @@ public async Task GetConfiguration(String deviceIdentifier, // call was successful so now deserialise the body to the response object response = JsonConvert.DeserializeObject(content); + + Shared.Logger.Logger.LogInformation($"Configuration for device identifier {deviceIdentifier} requested successfully"); + Shared.Logger.Logger.LogDebug($"Configuration Response: [{content}]"); } catch (Exception ex) { // An exception has occurred, add some additional information to the message Exception exception = new Exception($"Error getting configuration for device Id {deviceIdentifier}.", ex); + Shared.Logger.Logger.LogError(exception); + throw exception; } @@ -77,7 +85,18 @@ public async Task PostDiagnosticLogs(String deviceIdentifier, List logMessages, CancellationToken cancellationToken) { - throw new NotImplementedException(); + String requestUri = this.BuildRequestUrl($"/logging/{deviceIdentifier}"); + + // Create a container + var container = new + { + messages = logMessages + }; + StringContent content = new StringContent(JsonConvert.SerializeObject(container), Encoding.UTF8, "application/json"); + + HttpResponseMessage httpResponse = await this.HttpClient.PostAsync(requestUri, content, cancellationToken); + + await this.HandleResponse(httpResponse, cancellationToken); } } } diff --git a/TransactionMobile.Maui.BusinessLogic/Services/DatabaseLogger.cs b/TransactionMobile.Maui.BusinessLogic/Services/DatabaseLogger.cs new file mode 100644 index 00000000..13b78c1c --- /dev/null +++ b/TransactionMobile.Maui.BusinessLogic/Services/DatabaseLogger.cs @@ -0,0 +1,103 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Shared.Logger; +using TransactionMobile.Maui.Database; + +namespace TransactionMobile.Maui.BusinessLogic.Services +{ + public class DatabaseLogger : ILogger + { + private readonly IDatabaseContext DatabaseContext; + public DatabaseLogger(IDatabaseContext databaseContext) + { + this.DatabaseContext = databaseContext; + this.IsInitialised = true; + } + + public bool IsInitialised { get; set; } + + public void LogCritical(Exception exception) + { + var logMessageModels = Models.LogMessage.CreateFatalLogMessages(exception); + var logMessages = new List(); + foreach (var item in logMessageModels) + { + logMessages.Add(new LogMessage + { + EntryDateTime = item.EntryDateTime, + LogLevel = item.LogLevel.ToString(), + Message = item.Message + }); + } + this.DatabaseContext.InsertLogMessages(logMessages); + } + + public void LogDebug(string message) + { + var logMessageModel = Models.LogMessage.CreateDebugLogMessage(message); + var logMessage = new LogMessage + { + EntryDateTime = logMessageModel.EntryDateTime, + LogLevel = logMessageModel.LogLevel.ToString(), + Message = logMessageModel.Message + }; + this.DatabaseContext.InsertLogMessage(logMessage); + } + + public void LogError(Exception exception) + { + var logMessageModels = Models.LogMessage.CreateErrorLogMessages(exception); + var logMessages = new List(); + foreach (var item in logMessageModels) + { + logMessages.Add(new LogMessage + { + EntryDateTime = item.EntryDateTime, + LogLevel = item.LogLevel.ToString(), + Message = item.Message + }); + } + this.DatabaseContext.InsertLogMessages(logMessages); + } + + public void LogInformation(string message) + { + var logMessageModel = Models.LogMessage.CreateInformationLogMessage(message); + var logMessage = new LogMessage + { + EntryDateTime = logMessageModel.EntryDateTime, + LogLevel = logMessageModel.LogLevel.ToString(), + Message = logMessageModel.Message + }; + this.DatabaseContext.InsertLogMessage(logMessage); + } + + public void LogTrace(string message) + { + var logMessageModel = Models.LogMessage.CreateTraceLogMessage(message); + var logMessage = new LogMessage + { + EntryDateTime = logMessageModel.EntryDateTime, + LogLevel = logMessageModel.LogLevel.ToString(), + Message = logMessageModel.Message + }; + this.DatabaseContext.InsertLogMessage(logMessage); + } + + public void LogWarning(string message) + { + var logMessageModel = Models.LogMessage.CreateWarningLogMessage(message); + var logMessage = new LogMessage + { + EntryDateTime = logMessageModel.EntryDateTime, + LogLevel = logMessageModel.LogLevel.ToString(), + Message = logMessageModel.Message + }; + this.DatabaseContext.InsertLogMessage(logMessage); + } + } +} diff --git a/TransactionMobile.Maui.BusinessLogic/Services/IMerchantService.cs b/TransactionMobile.Maui.BusinessLogic/Services/IMerchantService.cs index 8db46d17..4bf933d3 100644 --- a/TransactionMobile.Maui.BusinessLogic/Services/IMerchantService.cs +++ b/TransactionMobile.Maui.BusinessLogic/Services/IMerchantService.cs @@ -15,92 +15,3 @@ public interface IMerchantService #endregion } - -public class MerchantService : IMerchantService -{ - private readonly IEstateClient EstateClient; - - private readonly IMemoryCacheService MemoryCacheService; - - public MerchantService(IEstateClient estateClient, IMemoryCacheService memoryCacheService) - { - this.EstateClient = estateClient; - this.MemoryCacheService = memoryCacheService; - } - public async Task> GetContractProducts(CancellationToken cancellationToken) - { - List result = new List(); - - this.MemoryCacheService.TryGetValue("AccessToken", out TokenResponseModel accessToken); - this.MemoryCacheService.TryGetValue("EstateId", out Guid estateId); - this.MemoryCacheService.TryGetValue("MerchantId", out Guid merchantId); - - List merchantContracts = await this.EstateClient.GetMerchantContracts(accessToken.AccessToken, estateId, merchantId, cancellationToken); - - foreach (ContractResponse contractResponse in merchantContracts) - { - foreach (ContractProduct contractResponseProduct in contractResponse.Products) - { - result.Add(new ContractProductModel - { - OperatorId = contractResponse.OperatorId, - ContractId = contractResponse.ContractId, - ProductId = contractResponseProduct.ProductId, - OperatorIdentfier = contractResponse.OperatorName, - OperatorName = this.GetOperatorName(contractResponse, contractResponseProduct), - Value = contractResponseProduct.Value ?? 0, - IsFixedValue = contractResponseProduct.Value.HasValue, - ProductDisplayText = contractResponseProduct.DisplayText, - ProductType = this.GetProductType(contractResponse.OperatorName) - }); - } - } - - return result; - } - - private String GetOperatorName(ContractResponse contractResponse, ContractProduct contractProduct) - { - String operatorName = null; - ProductType productType = this.GetProductType(contractResponse.OperatorName); - switch (productType) - { - case ProductType.Voucher: - operatorName = contractResponse.Description; - break; - default: - operatorName = contractResponse.OperatorName; - break; - - } - - return operatorName; - } - - private ProductType GetProductType(String operatorName) - { - ProductType productType = ProductType.NotSet; - switch (operatorName) - { - case "Safaricom": - productType = ProductType.MobileTopup; - break; - case "Voucher": - productType = ProductType.Voucher; - break; - } - - return productType; - } - - public async Task GetMerchantBalance(CancellationToken cancellationToken) - { - this.MemoryCacheService.TryGetValue("AccessToken", out TokenResponseModel accessToken); - this.MemoryCacheService.TryGetValue("EstateId", out Guid estateId); - this.MemoryCacheService.TryGetValue("MerchantId", out Guid merchantId); - - MerchantBalanceResponse merchantBalance = await this.EstateClient.GetMerchantBalance(accessToken.AccessToken, estateId, merchantId, cancellationToken); - - return merchantBalance.AvailableBalance; - } -} \ No newline at end of file diff --git a/TransactionMobile.Maui.BusinessLogic/Services/MerchantService.cs b/TransactionMobile.Maui.BusinessLogic/Services/MerchantService.cs new file mode 100644 index 00000000..cfbc3c0c --- /dev/null +++ b/TransactionMobile.Maui.BusinessLogic/Services/MerchantService.cs @@ -0,0 +1,107 @@ +namespace TransactionMobile.Maui.BusinessLogic.Services; + +using EstateManagement.Client; +using EstateManagement.DataTransferObjects.Responses; +using Models; +using Newtonsoft.Json; + +public class MerchantService : IMerchantService +{ + private readonly IEstateClient EstateClient; + + private readonly IMemoryCacheService MemoryCacheService; + + public MerchantService(IEstateClient estateClient, IMemoryCacheService memoryCacheService) + { + this.EstateClient = estateClient; + this.MemoryCacheService = memoryCacheService; + } + public async Task> GetContractProducts(CancellationToken cancellationToken) + { + List result = new List(); + + this.MemoryCacheService.TryGetValue("AccessToken", out TokenResponseModel accessToken); + this.MemoryCacheService.TryGetValue("EstateId", out Guid estateId); + this.MemoryCacheService.TryGetValue("MerchantId", out Guid merchantId); + + Shared.Logger.Logger.LogInformation($"About to request merchant contracts"); + Shared.Logger.Logger.LogDebug($"Merchant Contract Request details: Estate Id {estateId} Merchant Id {merchantId} Access Token {accessToken.AccessToken}"); + + List merchantContracts = await this.EstateClient.GetMerchantContracts(accessToken.AccessToken, estateId, merchantId, cancellationToken); + + Shared.Logger.Logger.LogInformation($"{merchantContracts.Count} for merchant requested successfully"); + Shared.Logger.Logger.LogDebug($"Merchant Contract Response: [{JsonConvert.SerializeObject(merchantContracts)}]"); + + foreach (ContractResponse contractResponse in merchantContracts) + { + foreach (ContractProduct contractResponseProduct in contractResponse.Products) + { + result.Add(new ContractProductModel + { + OperatorId = contractResponse.OperatorId, + ContractId = contractResponse.ContractId, + ProductId = contractResponseProduct.ProductId, + OperatorIdentfier = contractResponse.OperatorName, + OperatorName = this.GetOperatorName(contractResponse, contractResponseProduct), + Value = contractResponseProduct.Value ?? 0, + IsFixedValue = contractResponseProduct.Value.HasValue, + ProductDisplayText = contractResponseProduct.DisplayText, + ProductType = this.GetProductType(contractResponse.OperatorName) + }); + } + } + + return result; + } + + private String GetOperatorName(ContractResponse contractResponse, ContractProduct contractProduct) + { + String operatorName = null; + ProductType productType = this.GetProductType(contractResponse.OperatorName); + switch (productType) + { + case ProductType.Voucher: + operatorName = contractResponse.Description; + break; + default: + operatorName = contractResponse.OperatorName; + break; + + } + + return operatorName; + } + + private ProductType GetProductType(String operatorName) + { + ProductType productType = ProductType.NotSet; + switch (operatorName) + { + case "Safaricom": + productType = ProductType.MobileTopup; + break; + case "Voucher": + productType = ProductType.Voucher; + break; + } + + return productType; + } + + public async Task GetMerchantBalance(CancellationToken cancellationToken) + { + this.MemoryCacheService.TryGetValue("AccessToken", out TokenResponseModel accessToken); + this.MemoryCacheService.TryGetValue("EstateId", out Guid estateId); + this.MemoryCacheService.TryGetValue("MerchantId", out Guid merchantId); + + Shared.Logger.Logger.LogInformation($"About to request merchant merchant balance"); + Shared.Logger.Logger.LogDebug($"Merchant Balance Request details: Estate Id {estateId} Merchant Id {merchantId} Access Token {accessToken.AccessToken}"); + + MerchantBalanceResponse merchantBalance = await this.EstateClient.GetMerchantBalance(accessToken.AccessToken, estateId, merchantId, cancellationToken); + + Shared.Logger.Logger.LogInformation($"Balance for merchant requested successfully"); + Shared.Logger.Logger.LogDebug($"Merchant Balance Response: [{JsonConvert.SerializeObject(merchantBalance)}]"); + + return merchantBalance.AvailableBalance; + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.BusinessLogic/Services/TransactionService.cs b/TransactionMobile.Maui.BusinessLogic/Services/TransactionService.cs index bf41625d..bc1a0ce9 100644 --- a/TransactionMobile.Maui.BusinessLogic/Services/TransactionService.cs +++ b/TransactionMobile.Maui.BusinessLogic/Services/TransactionService.cs @@ -64,6 +64,8 @@ public async Task PerformLogon(PerformLogonRequestMod try { + Shared.Logger.Logger.LogInformation($"About to perform logon transaction"); + LogonTransactionRequestMessage logonTransactionRequest = new LogonTransactionRequestMessage { ApplicationVersion = "1.0.5", //model.ApplicationVersion, @@ -82,6 +84,9 @@ public async Task PerformLogon(PerformLogonRequestMod // Add the access token to the client headers this.MemoryCacheService.TryGetValue("AccessToken", out TokenResponseModel accessToken); + + Shared.Logger.Logger.LogDebug($"Logon Transaction Request details: Uri {requestUri} Payload {requestSerialised} Access Token {accessToken.AccessToken}"); + this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken); // Make the Http Call here @@ -102,12 +107,17 @@ public async Task PerformLogon(PerformLogonRequestMod RequireApplicationUpdate = responseMessage.RequiresApplicationUpdate, ResponseMessage = responseMessage.ResponseMessage }; + + Shared.Logger.Logger.LogInformation($"Logon transaction performed successfully"); + Shared.Logger.Logger.LogDebug($"Logon Transaction Response details: Status {httpResponse.StatusCode} Payload {content}"); } catch (Exception ex) { // An exception has occurred, add some additional information to the message Exception exception = new Exception("Error posting logon transaction.", ex); + Shared.Logger.Logger.LogError(exception); + throw exception; } @@ -123,6 +133,8 @@ public async Task PerformMobileTopup(PerformMobileTopupRequestModel mod try { + Shared.Logger.Logger.LogInformation($"About to perform mobile topup transaction"); + SaleTransactionRequestMessage saleTransactionRequest = new SaleTransactionRequestMessage { ProductId = model.ProductId, @@ -150,6 +162,9 @@ public async Task PerformMobileTopup(PerformMobileTopupRequestModel mod // Add the access token to the client headers this.MemoryCacheService.TryGetValue("AccessToken", out TokenResponseModel accessToken); + + Shared.Logger.Logger.LogDebug($"Mobile Topup Transaction Request details: Uri {requestUri} Payload {requestSerialised} Access Token {accessToken.AccessToken}"); + this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken); // Make the Http Call here @@ -162,12 +177,17 @@ public async Task PerformMobileTopup(PerformMobileTopupRequestModel mod SaleTransactionResponseMessage responseMessage = JsonConvert.DeserializeObject(content); response = responseMessage.ResponseCode == "0000"; + + Shared.Logger.Logger.LogInformation($"Mobile Topup transaction performed successfully"); + Shared.Logger.Logger.LogDebug($"Mobile Topup Transaction Response details: Status {httpResponse.StatusCode} Payload {content}"); } catch (Exception ex) { // An exception has occurred, add some additional information to the message Exception exception = new Exception("Error posting sale transaction.", ex); + Shared.Logger.Logger.LogError(exception); + throw exception; } @@ -181,6 +201,7 @@ public async Task PerformReconciliation(PerformReconciliationRequestMod try { + Shared.Logger.Logger.LogInformation($"About to perform reconciliation transaction"); ReconciliationRequestMessage reconciliationRequest = new ReconciliationRequestMessage { ApplicationVersion = model.ApplicationVersion, @@ -211,6 +232,9 @@ public async Task PerformReconciliation(PerformReconciliationRequestMod // Add the access token to the client headers this.MemoryCacheService.TryGetValue("AccessToken", out TokenResponseModel accessToken); + + Shared.Logger.Logger.LogDebug($"Reconciliation Transaction Request details: Uri {requestUri} Payload {requestSerialised} Access Token {accessToken.AccessToken}"); + this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken); // Make the Http Call here @@ -223,12 +247,17 @@ public async Task PerformReconciliation(PerformReconciliationRequestMod ReconciliationResponseMessage responseMessage = JsonConvert.DeserializeObject(content); response = responseMessage.ResponseCode == "0000"; + + Shared.Logger.Logger.LogInformation($"Reconciliation transaction performed successfully"); + Shared.Logger.Logger.LogDebug($"Reconciliation Transaction Response details: Status {httpResponse.StatusCode} Payload {content}"); } catch (Exception ex) { // An exception has occurred, add some additional information to the message Exception exception = new Exception("Error posting reconciliation transaction.", ex); + Shared.Logger.Logger.LogError(exception); + throw exception; } @@ -243,6 +272,8 @@ public async Task PerformVoucherIssue(PerformVoucherIssueRequestModel m try { + Shared.Logger.Logger.LogInformation($"About to perform voucher transaction"); + SaleTransactionRequestMessage saleTransactionRequest = new SaleTransactionRequestMessage { ProductId = model.ProductId, @@ -271,6 +302,9 @@ public async Task PerformVoucherIssue(PerformVoucherIssueRequestModel m // Add the access token to the client headers this.MemoryCacheService.TryGetValue("AccessToken", out TokenResponseModel accessToken); + + Shared.Logger.Logger.LogDebug($"Voucher Transaction Request details: Uri {requestUri} Payload {requestSerialised} Access Token {accessToken.AccessToken}"); + this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken); // Make the Http Call here @@ -283,12 +317,17 @@ public async Task PerformVoucherIssue(PerformVoucherIssueRequestModel m SaleTransactionResponseMessage responseMessage = JsonConvert.DeserializeObject(content); response = responseMessage.ResponseCode == "0000"; + + Shared.Logger.Logger.LogInformation($"Voucher transaction performed successfully"); + Shared.Logger.Logger.LogDebug($"Voucher Transaction Response details: Status {httpResponse.StatusCode} Payload {content}"); } catch (Exception ex) { // An exception has occurred, add some additional information to the message Exception exception = new Exception("Error posting sale transaction.", ex); + Shared.Logger.Logger.LogError(exception); + throw exception; } diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/LoginPageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/LoginPageViewModel.cs index a80c970e..13ea8b6c 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/LoginPageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/LoginPageViewModel.cs @@ -10,6 +10,7 @@ using MvvmHelpers.Commands; using Requests; using Services; + using TransactionMobile.Maui.Database; using UIServices; public class LoginPageViewModel : BaseViewModel @@ -65,10 +66,13 @@ public String Password private async Task LoginCommandExecute() { + Shared.Logger.Logger.LogInformation("LoginCommandExecute called"); + // TODO: this method needs refactored String deviceIdentifier = this.DeviceService.GetIdentifier(); GetConfigurationRequest getConfigurationRequest = GetConfigurationRequest.Create(deviceIdentifier); Configuration configuration = await this.Mediator.Send(getConfigurationRequest); + // Cache the config object this.MemoryCacheService.Set("Configuration", configuration); diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/Support/SupportPageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/Support/SupportPageViewModel.cs index 429475f3..551a36fd 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/Support/SupportPageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/Support/SupportPageViewModel.cs @@ -7,7 +7,12 @@ namespace TransactionMobile.Maui.BusinessLogic.ViewModels.Support { using Database; + using MediatR; using MvvmHelpers; + using MvvmHelpers.Commands; + using System.Windows.Input; + using TransactionMobile.Maui.BusinessLogic.Requests; + using TransactionMobile.Maui.UIServices; using UIServices; public class SupportPageViewModel : BaseViewModel @@ -18,19 +23,38 @@ public class SupportPageViewModel : BaseViewModel private readonly IDatabaseContext DatabaseContext; - public SupportPageViewModel(IDeviceService deviceService,IApplicationInfoService applicationInfoService,IDatabaseContext databaseContext) + private readonly IMediator Mediator; + + private readonly INavigationService NavigationService; + + public SupportPageViewModel(IDeviceService deviceService,IApplicationInfoService applicationInfoService,IDatabaseContext databaseContext, + IMediator mediator,INavigationService navigationService) { this.DeviceService = deviceService; this.ApplicationInfoService = applicationInfoService; this.DatabaseContext = databaseContext; + this.UploadLogsCommand = new AsyncCommand(this.UploadLogsCommandExecute); + this.Mediator = mediator; + this.NavigationService = navigationService; } + public ICommand UploadLogsCommand { get; } + public String NumberTransactionsStored => $"Transactions Stored: {this.DatabaseContext.GetTransactions().Result.Count()}"; public String ApplicationName => $"{this.ApplicationInfoService.ApplicationName} v{this.ApplicationInfoService.VersionString}"; - //public string AppVersion => $"Version: {this.ApplicationInfoService.VersionString}{Environment.NewLine}Copyright © 2022 Stuart Ferguson"; - //public string Description => "A playground for experiments with .Net MAUI. All code is available on GitHub and development is documented on my blog 'Sailing the Sharp Sea'."; + private async Task UploadLogsCommandExecute() + { + Shared.Logger.Logger.LogInformation("UploadLogsCommandExecute called"); + + UploadLogsRequest uploadLogsRequest = UploadLogsRequest.Create(this.DeviceService.GetIdentifier()); + + Boolean response = await this.Mediator.Send(uploadLogsRequest, CancellationToken.None); + + // TODO: Act on the response (display message or something)... + await this.NavigationService.GoToHome(); + } public string Platform { diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupFailedPageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupFailedPageViewModel.cs index 3032ffe6..36cf4b01 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupFailedPageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupFailedPageViewModel.cs @@ -31,6 +31,7 @@ public MobileTopupFailedPageViewModel(INavigationService navigationService) private async Task CancelledCommandExecute() { + Shared.Logger.Logger.LogInformation("CancelledCommandExecute called"); await this.NavigationService.PopToRoot(); } diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupPerformTopupPageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupPerformTopupPageViewModel.cs index 33c8875e..4cb361d9 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupPerformTopupPageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupPerformTopupPageViewModel.cs @@ -96,16 +96,19 @@ public Decimal TopupAmount private void CustomerEmailAddressEntryCompletedCommandExecute() { + Shared.Logger.Logger.LogInformation("CustomerEmailAddressEntryCompletedCommandExecute called"); this.OnCustomerEmailAddressEntryCompleted(); } private void CustomerMobileNumberEntryCompletedCommandExecute() { + Shared.Logger.Logger.LogInformation("CustomerMobileNumberEntryCompletedCommandExecute called"); this.OnCustomerMobileNumberEntryCompleted(); } private async Task PerformTopupCommandExecute() { + Shared.Logger.Logger.LogInformation("PerformTopupCommandExecute called"); // TODO: Create Command and Send PerformMobileTopupRequest request = PerformMobileTopupRequest.Create(DateTime.Now, "1", @@ -133,6 +136,7 @@ private async Task PerformTopupCommandExecute() private void TopupAmountEntryCompletedCommandExecute() { + Shared.Logger.Logger.LogInformation("TopupAmountEntryCompletedCommandExecute called"); this.OnTopupAmountEntryCompleted(); } diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupSelectOperatorPageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupSelectOperatorPageViewModel.cs index 9c63cb04..b7521cc1 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupSelectOperatorPageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupSelectOperatorPageViewModel.cs @@ -43,7 +43,6 @@ public MobileTopupSelectOperatorPageViewModel(IMediator mediator, INavigationSer public async Task Initialise(CancellationToken cancellationToken) { - GetContractProductsRequest request = GetContractProductsRequest.Create(ProductType.MobileTopup); List products = await this.Mediator.Send(request, cancellationToken); @@ -66,6 +65,7 @@ public async Task Initialise(CancellationToken cancellationToken) private async Task OperatorSelectedCommandExecute(ItemSelected e) { + Shared.Logger.Logger.LogInformation("OperatorSelectedCommandExecute called"); await this.NavigationService.GoToMobileTopupSelectProductPage(e.SelectedItem.OperatorIdentfier); } diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupSelectProductPageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupSelectProductPageViewModel.cs index 79468dc4..04974cfe 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupSelectProductPageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupSelectProductPageViewModel.cs @@ -63,6 +63,7 @@ public async Task Initialise(CancellationToken cancellationToken) private async Task ProductSelectedCommandExecute(ItemSelected e) { + Shared.Logger.Logger.LogInformation("ProductSelectedCommandExecute called"); await this.NavigationService.GoToMobileTopupPerformTopupPage(e.SelectedItem.OperatorIdentfier, e.SelectedItem.ContractId, e.SelectedItem.ProductId, e.SelectedItem.Value); } diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupSuccessPageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupSuccessPageViewModel.cs index fbf58c7b..7ae52bf6 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupSuccessPageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/MobileTopupSuccessPageViewModel.cs @@ -31,6 +31,7 @@ public MobileTopupSuccessPageViewModel(INavigationService navigationService) private async Task CompletedCommandExecute() { + Shared.Logger.Logger.LogInformation("CompletedCommandExecute called"); await this.NavigationService.PopToRoot(); } diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/TransactionsPageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/TransactionsPageViewModel.cs index b2462706..59ae0517 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/TransactionsPageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/TransactionsPageViewModel.cs @@ -43,26 +43,31 @@ public TransactionsPageViewModel(INavigationService navigationService) private async Task AdminCommandExecute() { + Shared.Logger.Logger.LogInformation("AdminCommandExecute called"); await this.NavigationService.GoToAdminPage(); } private async Task BillPaymentCommandExecute() { + Shared.Logger.Logger.LogInformation("AdminCommandExecute called"); await this.NavigationService.GoToHome(); } private async Task MobileTopupCommandExecute() { + Shared.Logger.Logger.LogInformation("MobileTopupCommandExecute called"); await this.NavigationService.GoToMobileTopupSelectOperatorPage(); } private async Task MobileWalletCommandExecute() { + Shared.Logger.Logger.LogInformation("MobileWalletCommandExecute called"); await this.NavigationService.GoToHome(); } private async Task VoucherCommandExecute() { + Shared.Logger.Logger.LogInformation("VoucherCommandExecute called"); await this.NavigationService.GoToVoucherSelectOperatorPage(); } diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherIssueFailedPageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherIssueFailedPageViewModel.cs index f96eca15..2e9391a5 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherIssueFailedPageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherIssueFailedPageViewModel.cs @@ -31,6 +31,7 @@ public VoucherIssueFailedPageViewModel(INavigationService navigationService) private async Task CancelledCommandExecute() { + Shared.Logger.Logger.LogInformation("CancelledCommandExecute called"); await this.NavigationService.PopToRoot(); } diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherIssueSuccessPageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherIssueSuccessPageViewModel.cs index ca52db12..d73ab776 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherIssueSuccessPageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherIssueSuccessPageViewModel.cs @@ -31,6 +31,7 @@ public VoucherIssueSuccessPageViewModel(INavigationService navigationService) private async Task CompletedCommandExecute() { + Shared.Logger.Logger.LogInformation("CompletedCommandExecute called"); await this.NavigationService.PopToRoot(); } diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherPerformIssuePageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherPerformIssuePageViewModel.cs index fa29608c..88e46c16 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherPerformIssuePageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherPerformIssuePageViewModel.cs @@ -108,21 +108,25 @@ public Decimal VoucherAmount private void CustomerEmailAddressEntryCompletedCommandExecute() { + Shared.Logger.Logger.LogInformation("CustomerEmailAddressEntryCompletedCommandExecute called"); this.OnCustomerEmailAddressEntryCompleted(); } private void RecipientMobileNumberEntryCompletedCommandExecute() { + Shared.Logger.Logger.LogInformation("RecipientMobileNumberEntryCompletedCommandExecute called"); this.OnRecipientMobileNumberEntryCompleted(); } private void RecipientEmailAddressEntryCompletedCommandExecute() { + Shared.Logger.Logger.LogInformation("RecipientEmailAddressEntryCompletedCommandExecute called"); this.OnRecipientEmailAddressEntryCompleted(); } private async Task IssueVoucherCommandExecute() { + Shared.Logger.Logger.LogInformation("IssueVoucherCommandExecute called"); // TODO: Create Command and Send PerformVoucherIssueRequest request = PerformVoucherIssueRequest.Create(DateTime.Now, "1", @@ -151,6 +155,7 @@ private async Task IssueVoucherCommandExecute() private void VoucherAmountEntryCompletedCommandExecute() { + Shared.Logger.Logger.LogInformation("VoucherAmountEntryCompletedCommandExecute called"); this.OnVoucherAmountEntryCompleted(); } diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherSelectOperatorPageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherSelectOperatorPageViewModel.cs index 679233f9..048406cd 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherSelectOperatorPageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherSelectOperatorPageViewModel.cs @@ -65,6 +65,7 @@ public async Task Initialise(CancellationToken cancellationToken) private async Task OperatorSelectedCommandExecute(ItemSelected e) { + Shared.Logger.Logger.LogInformation("OperatorSelectedCommandExecute called"); await this.NavigationService.GoToVoucherSelectProductPage(e.SelectedItem.OperatorIdentfier); } diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherSelectProductPageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherSelectProductPageViewModel.cs index c04c9f38..2f7054f0 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherSelectProductPageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/Transactions/VoucherSelectProductPageViewModel.cs @@ -62,6 +62,7 @@ public async Task Initialise(CancellationToken cancellationToken) private async Task ProductSelectedCommandExecute(ItemSelected e) { + Shared.Logger.Logger.LogInformation("ProductSelectedCommandExecute called"); await this.NavigationService.GoToVoucherIssueVoucherPage(e.SelectedItem.OperatorIdentfier, e.SelectedItem.ContractId, e.SelectedItem.ProductId, e.SelectedItem.Value); } diff --git a/TransactionMobile.Maui.Database/DatabaseContext.cs b/TransactionMobile.Maui.Database/DatabaseContext.cs new file mode 100644 index 00000000..c12d3425 --- /dev/null +++ b/TransactionMobile.Maui.Database/DatabaseContext.cs @@ -0,0 +1,85 @@ +namespace TransactionMobile.Maui.Database +{ + using SQLite; + + public class DatabaseContext : IDatabaseContext + { + private readonly SQLiteConnection Connection; + + private readonly Func LogLevelResolver; + + public DatabaseContext(String connectionString, Func logLevelResolver) + { + this.Connection = new SQLiteConnection(connectionString); + this.LogLevelResolver = logLevelResolver; + } + + public async Task InitialiseDatabase() + { + this.Connection.CreateTable(); + this.Connection.CreateTable(); + } + + public async Task CreateTransaction(TransactionRecord transactionRecord) + { + this.Connection.Insert(transactionRecord); + + return SQLite3.LastInsertRowid(this.Connection.Handle); + } + + public async Task UpdateTransaction(TransactionRecord transactionRecord) + { + this.Connection.Update(transactionRecord); + } + + public async Task> GetTransactions() + { + return this.Connection.Table().ToList(); + } + + public async Task ClearStoredTransactions() + { + this.Connection.DeleteAll(); + } + + public async Task> GetLogMessages(int batchSize) + { + if (this.Connection == null) + return new List(); + + List messages = this.Connection.Table().OrderBy(l => l.EntryDateTime).Take(batchSize).ToList(); + + return messages; + } + + public async Task InsertLogMessage(LogMessage logMessage) + { + if (this.Connection == null) + return; + + LogLevel messageLevel = (LogLevel)Enum.Parse(typeof(LogLevel), logMessage.LogLevel, true); + if (messageLevel <= this.LogLevelResolver()) + { + this.Connection.Insert(logMessage); + } + } + + public async Task InsertLogMessages(List logMessages) + { + foreach (LogMessage logMessage in logMessages) + { + await this.InsertLogMessage(logMessage); + } + } + + public async Task RemoveUploadedMessages(List logMessagesToRemove) + { + if (this.Connection == null) + return; + foreach (LogMessage logMessage in logMessagesToRemove) + { + this.Connection.Delete(logMessage); + } + } + } +} diff --git a/TransactionMobile.Maui.Database/IDatabaseContext.cs b/TransactionMobile.Maui.Database/IDatabaseContext.cs index 6685a08f..c59de2a8 100644 --- a/TransactionMobile.Maui.Database/IDatabaseContext.cs +++ b/TransactionMobile.Maui.Database/IDatabaseContext.cs @@ -6,8 +6,6 @@ namespace TransactionMobile.Maui.Database { - using SQLite; - public interface IDatabaseContext { Task InitialiseDatabase(); @@ -18,78 +16,13 @@ public interface IDatabaseContext Task> GetTransactions(); Task ClearStoredTransactions(); - } - - public class DatabaseContext : IDatabaseContext - { - private readonly SQLiteConnection Connection; - - public DatabaseContext(String connectionString) - { - this.Connection = new SQLiteConnection(connectionString); - } - - public async Task InitialiseDatabase() - { - this.Connection.CreateTable(); - } - - public async Task CreateTransaction(TransactionRecord transactionRecord) - { - this.Connection.Insert(transactionRecord); - - return SQLite3.LastInsertRowid(this.Connection.Handle); - } - - public async Task UpdateTransaction(TransactionRecord transactionRecord) - { - this.Connection.Update(transactionRecord); - } - - public async Task> GetTransactions() - { - return this.Connection.Table().ToList(); - } - - public async Task ClearStoredTransactions() - { - this.Connection.DeleteAll(); - } - } - - public class TransactionRecord - { - public String ApplicationVersion { get; set; } - - public Guid ContractId { get; set; } - - public String CustomerAccountNumber { get; set; } - - public String CustomerEmailAddress { get; set; } - - public String DeviceIdentifier { get; set; } - - public String OperatorIdentifier { get; set; } - - public Guid ProductId { get; set; } - - public Decimal Amount { get; set; } - - public DateTime TransactionDateTime { get; set; } - - [PrimaryKey, AutoIncrement] - public Int32 TransactionNumber { get; set; } - - public String RecipientMobileNumber { get; set; } - public String RecipientEmailAddress { get; set; } - public Int32 TransactionType { get; set; } + Task> GetLogMessages(Int32 batchSize); - public Guid EstateId { get; set; } - public Guid MerchantId { get; set; } - public Boolean IsSuccessful { get; set; } + Task InsertLogMessage(LogMessage logMessage); - public String ResponseMessage { get; set; } + Task InsertLogMessages(List logMessages); + Task RemoveUploadedMessages(List logMessagesToRemove); } } diff --git a/TransactionMobile.Maui.Database/LogMessage.cs b/TransactionMobile.Maui.Database/LogMessage.cs new file mode 100644 index 00000000..8549e23f --- /dev/null +++ b/TransactionMobile.Maui.Database/LogMessage.cs @@ -0,0 +1,76 @@ +namespace TransactionMobile.Maui.Database +{ + using SQLite; + public class LogMessage + { + #region Properties + + /// + /// Gets or sets the entry date time. + /// + /// + /// The entry date time. + /// + public DateTime EntryDateTime { get; set; } + + /// + /// Gets or sets the identifier. + /// + /// + /// The identifier. + /// + [PrimaryKey, AutoIncrement] + public Int32 Id { get; set; } + + /// + /// Gets or sets the log level. + /// + /// + /// The log level. + /// + public String LogLevel { get; set; } + + /// + /// Gets or sets the message. + /// + /// + /// The message. + /// + public String Message { get; set; } + + #endregion + } + + public enum LogLevel + { + /// + /// The fatal + /// + Fatal, + + /// + /// The error + /// + Error, + + /// + /// The warn + /// + Warn, + + /// + /// The information + /// + Info, + + /// + /// The debug + /// + Debug, + + /// + /// The trace + /// + Trace + } +} diff --git a/TransactionMobile.Maui.Database/TransactionRecord.cs b/TransactionMobile.Maui.Database/TransactionRecord.cs new file mode 100644 index 00000000..f645b240 --- /dev/null +++ b/TransactionMobile.Maui.Database/TransactionRecord.cs @@ -0,0 +1,40 @@ +namespace TransactionMobile.Maui.Database +{ + using SQLite; + + public class TransactionRecord + { + public String ApplicationVersion { get; set; } + + public Guid ContractId { get; set; } + + public String CustomerAccountNumber { get; set; } + + public String CustomerEmailAddress { get; set; } + + public String DeviceIdentifier { get; set; } + + public String OperatorIdentifier { get; set; } + + public Guid ProductId { get; set; } + + public Decimal Amount { get; set; } + + public DateTime TransactionDateTime { get; set; } + + [PrimaryKey, AutoIncrement] + public Int32 TransactionNumber { get; set; } + + public String RecipientMobileNumber { get; set; } + public String RecipientEmailAddress { get; set; } + + public Int32 TransactionType { get; set; } + + public Guid EstateId { get; set; } + public Guid MerchantId { get; set; } + public Boolean IsSuccessful { get; set; } + + public String ResponseMessage { get; set; } + + } +} diff --git a/TransactionMobile.Maui/Extensions/MauiAppBuilderExtensions.cs b/TransactionMobile.Maui/Extensions/MauiAppBuilderExtensions.cs index 80f0f393..e52b4984 100644 --- a/TransactionMobile.Maui/Extensions/MauiAppBuilderExtensions.cs +++ b/TransactionMobile.Maui/Extensions/MauiAppBuilderExtensions.cs @@ -24,6 +24,7 @@ using TransactionMobile.Maui.Pages.Transactions.Voucher; using TransactionMobile.Maui.Pages.Transactions.Admin; using TransactionMobile.Maui.Pages.Support; + using System; public static class MauiAppBuilderExtensions { @@ -32,10 +33,15 @@ public static class MauiAppBuilderExtensions public static MauiAppBuilder ConfigureDatabase(this MauiAppBuilder builder) { String connectionString = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "transactionpos.db"); - IDatabaseContext database = new DatabaseContext(connectionString); + Func logLevelFunc = new Func( () => + { + return Database.LogLevel.Debug; + }); + + IDatabaseContext database = new DatabaseContext(connectionString, logLevelFunc); database.InitialiseDatabase(); builder.Services.AddSingleton(database); - + return builder; } @@ -136,6 +142,7 @@ public static MauiAppBuilder ConfigureRequestHandlers(this MauiAppBuilder builde builder.Services.AddSingleton, TransactionRequestHandler>(); builder.Services.AddSingleton, TransactionRequestHandler>(); builder.Services.AddSingleton, TransactionRequestHandler>(); + builder.Services.AddSingleton, SupportRequestHandler>(); builder.Services.AddSingleton(ctx => { return t => ctx.GetService(t); }); diff --git a/TransactionMobile.Maui/MauiProgram.cs b/TransactionMobile.Maui/MauiProgram.cs index f428364e..343cd5bf 100644 --- a/TransactionMobile.Maui/MauiProgram.cs +++ b/TransactionMobile.Maui/MauiProgram.cs @@ -6,6 +6,9 @@ namespace TransactionMobile.Maui; using CommunityToolkit.Maui; using Microsoft.Extensions.Caching.Memory; using UIServices; +using TransactionMobile.Maui.BusinessLogic.Services; +using TransactionMobile.Maui.Database; +using Shared.Logger; public static class MauiProgram { @@ -33,9 +36,13 @@ public static MauiApp CreateMauiApp() }) .Services.AddTransient() .AddMemoryCache(); - + Container = builder.Build(); + // Setup static logger + IDatabaseContext databaseContext = MauiProgram.Container.Services.GetService(); + Logger.Initialise(new DatabaseLogger(databaseContext)); + return Container; } } diff --git a/TransactionMobile.Maui/Pages/LoginPage.xaml.cs b/TransactionMobile.Maui/Pages/LoginPage.xaml.cs index c11097b1..0d29304a 100644 --- a/TransactionMobile.Maui/Pages/LoginPage.xaml.cs +++ b/TransactionMobile.Maui/Pages/LoginPage.xaml.cs @@ -15,6 +15,5 @@ public LoginPage(LoginPageViewModel vm) protected override async void OnAppearing() { base.OnAppearing(); - //await viewModel.InitializeAsync(); } } \ No newline at end of file diff --git a/TransactionMobile.Maui/Pages/Support/SupportPage.xaml b/TransactionMobile.Maui/Pages/Support/SupportPage.xaml index fdf3f2ea..492357a9 100644 --- a/TransactionMobile.Maui/Pages/Support/SupportPage.xaml +++ b/TransactionMobile.Maui/Pages/Support/SupportPage.xaml @@ -25,6 +25,8 @@