diff --git a/EstateManagementUI.BlazorServer.Tests/EstateManagementUI.BlazorServer.Tests.csproj b/EstateManagementUI.BlazorServer.Tests/EstateManagementUI.BlazorServer.Tests.csproj index 335d3e57..22656015 100644 --- a/EstateManagementUI.BlazorServer.Tests/EstateManagementUI.BlazorServer.Tests.csproj +++ b/EstateManagementUI.BlazorServer.Tests/EstateManagementUI.BlazorServer.Tests.csproj @@ -38,4 +38,8 @@ + + + + \ No newline at end of file diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/BaseTest.cs b/EstateManagementUI.BlazorServer.Tests/Pages/BaseTest.cs index 7f8b9676..1ec82ac5 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/BaseTest.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/BaseTest.cs @@ -3,6 +3,7 @@ using Bunit.TestDoubles; using EstateManagementUI.BlazorServer.Components.Permissions; using EstateManagementUI.BlazorServer.Permissions; +using EstateManagementUI.BlazorServer.UIServices; using MediatR; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; @@ -20,6 +21,7 @@ protected BaseTest() { this._mockPermissionService = new Mock(); this._mockPermissionStore = new Mock(); this._fakeNavigationManager = new FakeNavigationManager(); + this.EstateUIService = new Mock(); this._mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); this._mockPermissionService.Setup(x => x.HasPermissionAsync(It.IsAny(), It.IsAny())).ReturnsAsync(true); @@ -31,6 +33,7 @@ protected BaseTest() { this.Services.AddSingleton(this._mockPermissionService.Object); this.Services.AddSingleton(this._mockAuthStateProvider.Object); this.Services.AddSingleton(this._mockPermissionStore.Object); + this.Services.AddSingleton(this.EstateUIService.Object); // Add required permission components that render their children @@ -50,6 +53,7 @@ protected BaseTest() { protected readonly Mock _mockAuthStateProvider; protected readonly Mock _mockPermissionStore; protected readonly FakeNavigationManager _fakeNavigationManager; + protected readonly Mock EstateUIService; /// /// Minimal test double for NavigationManager. diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Estate/EstateIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Estate/EstateIndexPageTests.cs index 4b6dd9d5..b1cb3081 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Estate/EstateIndexPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Estate/EstateIndexPageTests.cs @@ -1,8 +1,10 @@ using AngleSharp.Dom; using Bunit; +using Castle.Components.DictionaryAdapter; using EstateManagementUI.BlazorServer.Common; +using EstateManagementUI.BlazorServer.Models; using EstateManagementUI.BlazorServer.Tests.Pages.FileProcessing; -using EstateManagementUI.BusinessLogic.Models; +using EstateManagementUI.BusinessLogic.BackendAPI.DataTransferObjects; using EstateManagementUI.BusinessLogic.Requests; using Microsoft.AspNetCore.Components.Web; using Moq; @@ -18,22 +20,19 @@ public class EstateIndexPageTests : BaseTest public void EstateIndex_RendersCorrectly() { // Arrange - EstateModel estate = new() { - EstateId = Guid.NewGuid(), - EstateName = "Test Estate", - Reference = "EST001" - }; - - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(estate)); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); + BlazorServer.Models.EstateModel estate = new(Guid.NewGuid(), "Test Estate", "EST001"); + estate = estate with { + ContractCount = 0, + RecentContracts = new List(), + OperatorCount = 0, + AllOperators = new List(), + AssignedOperators = new List(), + MerchantCount = 0, + RecentMerchants = new List(), + UserCount = 0 + }; + + this.EstateUIService.Setup(e => e.LoadEstate(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(estate)); // Act IRenderedComponent cut = RenderComponent(); @@ -46,23 +45,19 @@ public void EstateIndex_RendersCorrectly() public void EstateIndex_DisplaysEstateDetails() { // Arrange - EstateModel estate = new() { - EstateId = Guid.NewGuid(), - EstateName = "Test Estate", - Reference = "EST001", - Operators = new List() + BlazorServer.Models.EstateModel estate = new(Guid.NewGuid(), "Test Estate", "EST001"); + estate = estate with + { + ContractCount = 0, + RecentContracts = new List(), + OperatorCount = 0, + AllOperators = new List(), + AssignedOperators = new List(), + MerchantCount = 0, + RecentMerchants = new List(), + UserCount = 0 }; - - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(estate)); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); + this.EstateUIService.Setup(e => e.LoadEstate(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(estate)); // Act IRenderedComponent cut = RenderComponent(); @@ -75,16 +70,19 @@ public void EstateIndex_DisplaysEstateDetails() public void EstateIndex_HasCorrectPageTitle() { // Arrange - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new EstateModel { EstateId = Guid.NewGuid() })); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); + BlazorServer.Models.EstateModel estate = new(Guid.NewGuid(), "Test Estate", "EST001"); + estate = estate with + { + ContractCount = 0, + RecentContracts = new List(), + OperatorCount = 0, + AllOperators = new List(), + AssignedOperators = new List(), + MerchantCount = 0, + RecentMerchants = new List(), + UserCount = 0 + }; + this.EstateUIService.Setup(e => e.LoadEstate(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(estate)); // Act IRenderedComponent cut = RenderComponent(); @@ -150,13 +148,13 @@ public void EstateIndex_AddOperator_Success_UpdatesAssignedOperators() RequireCustomMerchantNumber = true, RequireCustomTerminalNumber = false }; - - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(operatorDetails)); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success()); - + + this.EstateUIService.Setup(e => e.AddOperatorToEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success); + IRenderedComponent cut = RenderComponent(); + cut.Instance.SetDelayOverride(0); + cut.Render(); // required to trigger re-render + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); // Switch to operators tab @@ -176,7 +174,7 @@ public void EstateIndex_AddOperator_Success_UpdatesAssignedOperators() addButton.Click(); // Assert - cut.WaitForAssertion(() => cut.Markup.ShouldContain("Operator added successfully"), timeout: TimeSpan.FromSeconds(5)); + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Operator added successfully"), timeout: TimeSpan.FromSeconds(10)); } @@ -191,10 +189,9 @@ public void EstateIndex_AddOperator_Failure_ShowsErrorMessage() }; SetupSuccessfulDataLoadWithOperators(new List { operatorToAdd }); - - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Failure("Failed to add operator")); - + + this.EstateUIService.Setup(e => e.AddOperatorToEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Failure); + IRenderedComponent cut = RenderComponent(); cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); @@ -215,7 +212,6 @@ public void EstateIndex_AddOperator_Failure_ShowsErrorMessage() addButton.Click(); // Assert - _mockMediator.Verify(x => x.Send(It.IsAny(), default), Times.AtLeastOnce()); cut.WaitForAssertion(() => cut.Markup.ShouldContain("Failed to add operator"), timeout: TimeSpan.FromSeconds(5)); } @@ -232,11 +228,12 @@ public void EstateIndex_RemoveOperator_Success_RemovesFromList() }; SetupSuccessfulDataLoadWithAssignedOperators(new List { assignedOperator }); - - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success()); - + + this.EstateUIService.Setup(e => e.RemoveOperatorFromEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success); + IRenderedComponent cut = RenderComponent(); + cut.Instance.SetDelayOverride(0); + cut.Render(); // required to trigger re-render cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); // Switch to operators tab @@ -266,10 +263,9 @@ public void EstateIndex_RemoveOperator_Failure_ShowsErrorMessage() }; SetupSuccessfulDataLoadWithAssignedOperators(new List { assignedOperator }); - - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Failure("Failed to remove operator")); - + + this.EstateUIService.Setup(e => e.RemoveOperatorFromEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Failure(String.Empty)); + IRenderedComponent cut = RenderComponent(); cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); @@ -362,117 +358,15 @@ public void EstateIndex_DisplaysNoContracts_WhenEmpty() // Assert cut.Markup.ShouldContain("No contracts found"); } - - [Fact] - public void EstateIndex_LoadEstateData_EstateQueryFails_NavigatesToError() - { - // Arrange - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Failure("Failed to load estate")); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - - // Act - IRenderedComponent cut = RenderComponent(); - cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); - - // Assert - _fakeNavigationManager.Uri.ShouldContain("error"); - } - - [Fact] - public void EstateIndex_LoadEstateData_MerchantQueryFails_NavigatesToError() - { - // Arrange - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new EstateModel { EstateId = Guid.NewGuid() })); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Failure("Failed to load merchants")); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - - // Act - IRenderedComponent cut = RenderComponent(); - cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); - - // Assert - _fakeNavigationManager.Uri.ShouldContain("error"); - } - - [Fact] - public void EstateIndex_LoadEstateData_ContractQueryFails_NavigatesToError() - { - // Arrange - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new EstateModel { EstateId = Guid.NewGuid() })); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Failure("Failed to load contracts")); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - - // Act - IRenderedComponent cut = RenderComponent(); - cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); - - // Assert - _fakeNavigationManager.Uri.ShouldContain("error"); - } - + [Fact] - public void EstateIndex_LoadEstateData_AssignedOperatorsQueryFails_NavigatesToError() + public void EstateIndex_LoadEstateData_LoadFails_NavigatesToError() { // Arrange - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new EstateModel { EstateId = Guid.NewGuid() })); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Failure("Failed to load assigned operators")); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - - // Act - IRenderedComponent cut = RenderComponent(); - cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); - - // Assert - _fakeNavigationManager.Uri.ShouldContain("error"); - } + this.EstateUIService.Setup(e => e.LoadEstate(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Failure()); - [Fact] - public void EstateIndex_LoadEstateData_AllOperatorsQueryFails_NavigatesToError() - { - // Arrange - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new EstateModel { EstateId = Guid.NewGuid() })); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Failure("Failed to load operators")); - // Act IRenderedComponent cut = RenderComponent(); - cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); // Assert _fakeNavigationManager.Uri.ShouldContain("error"); @@ -524,119 +418,7 @@ public void EstateIndex_DisplaysNoOperators_WhenNoneAssigned() // Assert cut.WaitForAssertion(() => cut.Markup.ShouldContain("No operators assigned"), timeout: TimeSpan.FromSeconds(5)); } - - [Fact] - public void EstateIndex_AddOperator_WhenGetOperatorQueryFails_NavigatesToError() - { - // Arrange - Guid operatorId = Guid.NewGuid(); - OperatorDropDownModel operatorToAdd = new() { - OperatorId = operatorId, - OperatorName = "Test Operator" - }; - - SetupSuccessfulDataLoadWithOperators(new List { operatorToAdd }); - - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success()); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Failure("Failed to get operator details")); - - IRenderedComponent cut = RenderComponent(); - cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); - - // Switch to operators tab - IRefreshableElementCollection buttons = cut.FindAll("button"); - IElement? operatorsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Operators")); - operatorsButton?.Click(); - - // Click Add Operator button - IElement addOperatorButton = cut.Find("#addOperatorButton"); - addOperatorButton.Click(); - - // Act - Select operator and add - IElement selectElement = cut.Find("select"); - selectElement.Change(operatorId.ToString()); - IElement addButton = cut.FindAll("button") - .First(b => b.TextContent.Trim() == "Add" && (b.GetAttribute("id") ?? "") != "addOperatorButton"); - addButton.Click(); - - // Assert - Should navigate to error page - _fakeNavigationManager.Uri.ShouldContain("error"); - } - - [Fact] - public void EstateIndex_AddOperator_WhenException_ShowsErrorMessage() - { - // Arrange - Guid operatorId = Guid.NewGuid(); - OperatorDropDownModel operatorToAdd = new() { - OperatorId = operatorId, - OperatorName = "Test Operator" - }; - - SetupSuccessfulDataLoadWithOperators(new List { operatorToAdd }); - - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ThrowsAsync(new Exception("Test exception")); - - IRenderedComponent cut = RenderComponent(); - cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); - - // Switch to operators tab - IRefreshableElementCollection buttons = cut.FindAll("button"); - IElement? operatorsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Operators")); - operatorsButton?.Click(); - - // Click Add Operator button - IElement addOperatorButton = cut.Find("#addOperatorButton"); - addOperatorButton.Click(); - - // Act - Select operator and add - IElement selectElement = cut.Find("select"); - selectElement.Change(operatorId.ToString()); - IElement addButton = cut.FindAll("button") - .First(b => b.TextContent.Trim() == "Add" && (b.GetAttribute("id") ?? "") != "addOperatorButton"); - addButton.Click(); - - // Assert - cut.WaitForAssertion(() => cut.Markup.ShouldContain("An error occurred: Test exception"), timeout: TimeSpan.FromSeconds(5)); - } - - [Fact] - public void EstateIndex_RemoveOperator_WhenException_ShowsErrorMessage() - { - // Arrange - Guid operatorId = Guid.NewGuid(); - OperatorModel assignedOperator = new() { - OperatorId = operatorId, - Name = "Test Operator", - RequireCustomMerchantNumber = true, - RequireCustomTerminalNumber = false - }; - - SetupSuccessfulDataLoadWithAssignedOperators(new List { assignedOperator }); - - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ThrowsAsync(new Exception("Test exception")); - - IRenderedComponent cut = RenderComponent(); - cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); - - // Switch to operators tab - IRefreshableElementCollection buttons = cut.FindAll("button"); - IElement? operatorsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Operators")); - operatorsButton?.Click(); - - // Act - Remove operator - IRefreshableElementCollection removeButtons = cut.FindAll("button"); - IElement? removeButton = removeButtons.FirstOrDefault(b => b.TextContent.Contains("Remove")); - removeButton?.Click(); - - // Assert - cut.WaitForAssertion(() => cut.Markup.ShouldContain("An error occurred: Test exception"), timeout: TimeSpan.FromSeconds(5)); - } - + [Fact] public void EstateIndex_SuccessMessage_ClearsWhenSwitchingTabs() { @@ -650,10 +432,9 @@ public void EstateIndex_SuccessMessage_ClearsWhenSwitchingTabs() }; SetupSuccessfulDataLoadWithAssignedOperators(new List { assignedOperator }); - - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success()); - + + this.EstateUIService.Setup(e => e.RemoveOperatorFromEstate(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success); + IRenderedComponent cut = RenderComponent(); cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); @@ -684,16 +465,19 @@ private void SetupSuccessfulDataLoad(List? merchants = nul List? assignedOperators = null, List? operators = null) { - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(new EstateModel { EstateId = Guid.NewGuid(), EstateName = "Test Estate" })); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(merchants ?? new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(contracts ?? new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(assignedOperators ?? new List())); - _mockMediator.Setup(x => x.Send(It.IsAny(), default)) - .ReturnsAsync(Result.Success(operators ?? new List())); + BlazorServer.Models.EstateModel estate = new(Guid.NewGuid(), "Test Estate", "EST001"); + estate = estate with + { + ContractCount = 5, + RecentContracts = contracts ?? new List(), + OperatorCount = 3, + AllOperators = operators ?? new List(), + AssignedOperators = assignedOperators ?? new List(), + MerchantCount = 10, + RecentMerchants = merchants ?? new List(), + UserCount = 2 + }; + this.EstateUIService.Setup(e => e.LoadEstate(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(estate)); } private void SetupSuccessfulDataLoadWithOperators(List operators) diff --git a/EstateManagementUI.BlazorServer.Tests/UIServices/EstateUIServiceTests.cs b/EstateManagementUI.BlazorServer.Tests/UIServices/EstateUIServiceTests.cs new file mode 100644 index 00000000..199c35ed --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/UIServices/EstateUIServiceTests.cs @@ -0,0 +1,301 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using EstateManagementUI.BlazorServer.UIServices; +using EstateManagementUI.BusinessLogic.Models; +using EstateManagementUI.BusinessLogic.Requests; +using Moq; +using Shouldly; +using SimpleResults; +using Xunit; +using MediatR; + +namespace EstateManagementUI.BlazorServer.Tests.UIServices +{ + public class EstateUIServiceTests + { + private readonly Mock _mockMediator; + private readonly EstateUIService _service; + + public EstateUIServiceTests() + { + _mockMediator = new Mock(); + _service = new EstateUIService(_mockMediator.Object); + } + + [Fact] + public async Task LoadEstate_ReturnsMappedModel_WhenAllQueriesSucceed() + { + // Arrange + var estateId = Guid.NewGuid(); + var correlationId = CorrelationIdHelper.New(); + + var bizEstate = new BusinessLogic.Models.EstateModel + { + EstateId = estateId, + EstateName = "Test Estate", + Reference = "REF001", + Merchants = new List { new() { MerchantId = Guid.NewGuid(), Name = "M1", Reference = "R1" } }, + Contracts = new List { new() { ContractId = Guid.NewGuid(), Name = "C1", OperatorName = "Op" } }, + Operators = new List { new() { OperatorId = Guid.NewGuid(), Name = "OpAssigned" } }, + Users = new List { new() { UserId = Guid.NewGuid(), EmailAddress = "u@x" } } + }; + + var recentMerchants = new List + { + new() { MerchantId = Guid.NewGuid(), Name = "RecentM", Reference = "RM", CreatedDateTime = DateTime.UtcNow } + }; + + var recentContracts = new List + { + new() { ContractId = Guid.NewGuid(), Description = "RecentC", OperatorName = "Op" } + }; + + var assignedOperators = new List + { + new() { OperatorId = bizEstate.Operators!.First().OperatorId, Name = "OpAssigned" } + }; + + var allOperators = new List + { + new() { OperatorId = Guid.NewGuid(), OperatorName = "OpA" }, + new() { OperatorId = assignedOperators[0].OperatorId, OperatorName = "OpAssigned" } + }; + + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(bizEstate)); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(recentMerchants)); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(recentContracts)); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(assignedOperators)); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(allOperators)); + + // Act + var result = await _service.LoadEstate(correlationId, estateId); + + // Assert + result.IsSuccess.ShouldBeTrue(); + var model = result.Data!; + model.EstateId.ShouldBe(estateId); + model.EstateName.ShouldBe("Test Estate"); + model.MerchantCount.ShouldBe(1); + model.ContractCount.ShouldBe(1); + model.UserCount.ShouldBe(1); + model.AllOperators.ShouldNotBeNull(); + model.AllOperators.ShouldContain(op => op.OperatorName == "OpA"); + model.AssignedOperators.ShouldNotBeEmpty(); + model.RecentMerchants.ShouldNotBeEmpty(); + model.RecentContracts.ShouldNotBeEmpty(); + } + + [Fact] + public async Task LoadEstate_ReturnsFailure_WhenEstateQueryFails() + { + // Arrange + var estateId = Guid.NewGuid(); + var correlationId = CorrelationIdHelper.New(); + + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Failure("estate fail")); + + // Provide success for other queries so the service behaviour is isolated + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + + // Act + var result = await _service.LoadEstate(correlationId, estateId); + + // Assert + result.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task LoadEstate_ReturnsFailure_WhenRecentMerchantQueryFails() + { + // Arrange + var estateId = Guid.NewGuid(); + var correlationId = CorrelationIdHelper.New(); + + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success()); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Failure()); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + + // Act + var result = await _service.LoadEstate(correlationId, estateId); + + // Assert + result.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task LoadEstate_ReturnsFailure_WhenRecentContractsQueryFails() + { + // Arrange + var estateId = Guid.NewGuid(); + var correlationId = CorrelationIdHelper.New(); + + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new EstateModel())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Failure()); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + + // Act + var result = await _service.LoadEstate(correlationId, estateId); + + // Assert + result.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task LoadEstate_ReturnsFailure_WhenGetAssignedOperatorsQueryFails() + { + // Arrange + var estateId = Guid.NewGuid(); + var correlationId = CorrelationIdHelper.New(); + + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new EstateModel())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Failure()); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + + // Act + var result = await _service.LoadEstate(correlationId, estateId); + + // Assert + result.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task LoadEstate_ReturnsFailure_WhenGetOperatorsForDropDownQueryFails() + { + // Arrange + var estateId = Guid.NewGuid(); + var correlationId = CorrelationIdHelper.New(); + + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new EstateModel())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(new List())); + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Failure()); + + // Act + var result = await _service.LoadEstate(correlationId, estateId); + + // Assert + result.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task AddOperatorToEstate_CallsMediatorWithAddCommand_OnSuccess() + { + // Arrange + var estateId = Guid.NewGuid(); + var correlationId = CorrelationIdHelper.New(); + var operatorId = Guid.NewGuid(); + var operatorIdString = operatorId.ToString(); + + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success); + + // Act + var result = await _service.AddOperatorToEstate(correlationId, estateId, operatorIdString); + + // Assert + result.IsSuccess.ShouldBeTrue(); + _mockMediator.Verify(m => m.Send(It.Is(c => + c.EstateId == estateId && c.OperatorId == operatorId), It.IsAny()), Times.Once); + } + + [Fact] + public async Task AddOperatorToEstate_ReturnsFailure_WhenMediatorFails() + { + // Arrange + var estateId = Guid.NewGuid(); + var correlationId = CorrelationIdHelper.New(); + var operatorId = Guid.NewGuid(); + + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Failure); + + // Act + var result = await _service.AddOperatorToEstate(correlationId, estateId, operatorId.ToString()); + + // Assert + result.IsFailed.ShouldBeTrue(); + } + + [Fact] + public async Task RemoveOperatorFromEstate_ShouldCallRemoveCommand() + { + // Arrange + var estateId = Guid.NewGuid(); + var correlationId = CorrelationIdHelper.New(); + var operatorId = Guid.NewGuid(); + + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success); + + // Act + var result = await _service.RemoveOperatorFromEstate(correlationId, estateId, operatorId); + + // Assert - the service should send a RemoveOperatorFromEstateCommand + result.IsSuccess.ShouldBeTrue(); + _mockMediator.Verify(m => m.Send(It.Is(c => + c.EstateId == estateId && c.OperatorId == operatorId), It.IsAny()), Times.Once); + } + + + [Fact] + public async Task RemoveOperatorFromEstate_ReturnsFailure_WhenMediatorFails() + { + // Arrange + var estateId = Guid.NewGuid(); + var correlationId = CorrelationIdHelper.New(); + var operatorId = Guid.NewGuid(); + + _mockMediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Failure); + + // Act + var result = await _service.RemoveOperatorFromEstate(correlationId, estateId, operatorId); + + // Assert - the service should send a RemoveOperatorFromEstateCommand + result.IsFailed.ShouldBeTrue(); + } + } +} \ No newline at end of file diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Edit.razor.cs b/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Edit.razor.cs index 9b4b5d16..29d1e4ab 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Edit.razor.cs +++ b/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Edit.razor.cs @@ -131,7 +131,7 @@ private async Task HandleAddProduct() CloseAddProductModal(); // Small delay so user sees confirmation (adjust duration as needed) - await Task.Delay(2500); + await this.WaitOnUIRefresh(); await LoadContract(); @@ -196,7 +196,7 @@ private async Task HandleAddFee() CloseAddFeeModal(); // Small delay so user sees confirmation (adjust duration as needed) - await Task.Delay(2500); + await this.WaitOnUIRefresh(); await LoadContract(); @@ -252,9 +252,9 @@ private async Task RemoveFee(Guid productId, Guid feeId) if (result.IsSuccess) { successMessage = "Transaction fee removed successfully"; - + // Small delay so user sees confirmation (adjust duration as needed) - await Task.Delay(2500); + await this.WaitOnUIRefresh(); await LoadContract(); } diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Estate/Index.razor b/EstateManagementUI.BlazorServer/Components/Pages/Estate/Index.razor index f27c9f20..61ea565d 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Estate/Index.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Estate/Index.razor @@ -1,11 +1,10 @@ @page "/estate" @using EstateManagementUI.BlazorServer.Common +@using EstateManagementUI.BlazorServer.UIServices @inherits AuthorizedComponentBase @rendermode InteractiveServer -@inject AuthenticationStateProvider AuthenticationStateProvider @inject NavigationManager NavigationManager -@inject IMediator Mediator -@inject IPermissionService PermissionService +@inject IEstateUIService EstateUIService Estate Management @@ -21,7 +20,7 @@
} - else if (estate != null) + else {
@@ -62,7 +61,7 @@

Total Merchants

-

@(estate.Merchants?.Count ?? 0)

+

@(estate.MerchantCount)

@@ -74,7 +73,7 @@

Total Operators

-

@(estate.Operators?.Count ?? 0)

+

@(estate.OperatorCount)

@@ -86,7 +85,7 @@

Total Contracts

-

@(estate.Operators?.Count ?? 0)

+

@(estate.ContractCount)

@@ -98,7 +97,7 @@

Total Users

-

@(estate.Users?.Count ?? 0)

+

@(estate.UserCount)

@@ -115,10 +114,10 @@

Recent Merchants

View All →
- @if (merchants != null && merchants.Any()) + @if (@estate.RecentMerchants != null && @estate.RecentMerchants.Any()) {
- @foreach (var merchant in merchants) + @foreach (var merchant in @estate.RecentMerchants) {
@@ -146,10 +145,10 @@

Contracts

View All →
- @if (contracts != null && contracts.Any()) + @if (estate.RecentContracts != null && estate.RecentContracts.Any()) {
- @foreach (var contract in contracts.Take(5)) + @foreach (var contract in estate.RecentContracts.Take(5)) {
@@ -204,9 +203,9 @@