diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsEditPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsEditPageTests.cs new file mode 100644 index 00000000..e60e7ff7 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsEditPageTests.cs @@ -0,0 +1,788 @@ +using AngleSharp.Dom; +using Bunit; +using EstateManagementUI.BlazorServer.Common; +using EstateManagementUI.BlazorServer.Models; +using EstateManagementUI.BusinessLogic.Requests; +using Microsoft.AspNetCore.Components.Web; +using Moq; +using Shouldly; +using SimpleResults; +using MerchantsEdit = EstateManagementUI.BlazorServer.Components.Pages.Merchants.Edit; + +namespace EstateManagementUI.BlazorServer.Tests.Pages.Merchants; + +public class MerchantsEditPageTests : BaseTest +{ + [Fact] + public void MerchantsEdit_RendersCorrectly() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + + // Act + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + + // Assert + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + cut.Markup.ShouldContain("Edit Merchant"); + } + + [Fact] + public void MerchantsEdit_HasCorrectPageTitle() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + + // Act + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + + // Assert + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + IRenderedComponent pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } + + [Fact] + public void MerchantsEdit_DisplaysMerchantDetails() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId, "Test Merchant", "MERCH001"); + + // Act + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + + // Assert + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + cut.Markup.ShouldContain("Test Merchant"); + cut.Markup.ShouldContain("MERCH001"); + } + + [Fact] + public void MerchantsEdit_BackToListButton_NavigatesToMerchantsList() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Act - Find Back to List button and click it + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? backButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Back to List")); + backButton?.Click(); + + // Assert + _fakeNavigationManager.Uri.ShouldContain("/merchants"); + } + + [Fact] + public void MerchantsEdit_TabSwitch_ToAddress_UpdatesActiveTab() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Act - Find address button and click it + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? addressButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Address Details")); + addressButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Address Line 1"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_TabSwitch_ToContact_UpdatesActiveTab() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Act - Find contact button and click it + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? contactButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Contact Details")); + contactButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Contact Name"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_TabSwitch_ToOperators_UpdatesActiveTab() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Act - Find operators button and click it + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? operatorsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Operators")); + operatorsButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Assigned Operators"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_TabSwitch_ToContracts_UpdatesActiveTab() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Act - Find contracts button and click it + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? contractsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Contracts")); + contractsButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Assigned Contracts"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_TabSwitch_ToDevices_UpdatesActiveTab() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Act - Find devices button and click it + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? devicesButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Devices")); + devicesButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Assigned Devices"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_TabSwitch_BackToDetails_UpdatesActiveTab() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Switch to address first + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? addressButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Address Details")); + addressButton?.Click(); + + // Act - switch back to details + IElement? detailsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Merchant Details")); + detailsButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Merchant Name"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_LoadMerchantData_LoadFails_NavigatesToError() + { + // Arrange + var merchantId = Guid.NewGuid(); + this.MerchantUIService.Setup(m => m.GetMerchant(It.IsAny(), It.IsAny(), merchantId)) + .ReturnsAsync(Result.Failure()); + + // Act + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + + // Assert + cut.WaitForState(() => _fakeNavigationManager.Uri.Contains("error"), TimeSpan.FromSeconds(5)); + _fakeNavigationManager.Uri.ShouldContain("error"); + } + + [Fact] + public void MerchantsEdit_DisplaysNoOperators_WhenNoneAssigned() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Act - Switch to operators tab + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? operatorsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Operators")); + operatorsButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("No operators assigned"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_DisplaysNoContracts_WhenNoneAssigned() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Act - Switch to contracts tab + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? contractsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Contracts")); + contractsButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("No contracts assigned"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_DisplaysNoDevices_WhenNoneAssigned() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Act - Switch to devices tab + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? devicesButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Devices")); + devicesButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("No devices assigned"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_AddOperatorButton_TogglesAddOperatorForm() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + 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("Assigned Operators")); + operatorsButton?.Click(); + + // Act - Click Add Operator button + IElement addOperatorButton = cut.Find("#addOperatorButton"); + addOperatorButton.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Select Operator"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_AddContractButton_TogglesAddContractForm() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Switch to contracts tab + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? contractsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Contracts")); + contractsButton?.Click(); + + // Act - Click Add Contract button + IElement addContractButton = cut.Find("#addContractButton"); + addContractButton.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Select Contract"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_AddDeviceButton_TogglesAddDeviceForm() + { + // Arrange + var merchantId = Guid.NewGuid(); + SetupSuccessfulDataLoad(merchantId); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Switch to devices tab + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? devicesButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Devices")); + devicesButton?.Click(); + + // Act - Click Add Device button + IElement addDeviceButton = cut.Find("#addDeviceButton"); + addDeviceButton.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Device Identifier"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_AddOperator_Success_ShowsSuccessMessage() + { + // Arrange + var merchantId = Guid.NewGuid(); + Guid operatorId = Guid.NewGuid(); + OperatorModels.OperatorDropDownModel operatorToAdd = new() { + OperatorId = operatorId, + OperatorName = "Test Operator" + }; + + SetupSuccessfulDataLoadWithOperators(merchantId, new List { operatorToAdd }); + + OperatorModels.OperatorModel operatorDetails = new() { + OperatorId = operatorId, + Name = "Test Operator", + RequireCustomMerchantNumber = false, + RequireCustomTerminalNumber = false + }; + + this.OperatorUIService.Setup(o => o.GetOperator(It.IsAny(), It.IsAny(), operatorId)) + .ReturnsAsync(Result.Success(operatorDetails)); + this.MerchantUIService.Setup(m => m.AddOperatorToMerchant(It.IsAny(), It.IsAny(), merchantId, operatorId, It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + 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("Assigned 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()); + cut.WaitForState(() => cut.Markup.Contains("Add"), TimeSpan.FromSeconds(5)); + + IElement addButton = cut.FindAll("button") + .First(b => b.TextContent.Trim() == "Add" && (b.GetAttribute("id") ?? "") != "addOperatorButton"); + addButton.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Operator added successfully"), timeout: TimeSpan.FromSeconds(10)); + } + + [Fact] + public void MerchantsEdit_AddOperator_Failure_ShowsErrorMessage() + { + // Arrange + var merchantId = Guid.NewGuid(); + Guid operatorId = Guid.NewGuid(); + OperatorModels.OperatorDropDownModel operatorToAdd = new() { + OperatorId = operatorId, + OperatorName = "Test Operator" + }; + + SetupSuccessfulDataLoadWithOperators(merchantId, new List { operatorToAdd }); + + OperatorModels.OperatorModel operatorDetails = new() { + OperatorId = operatorId, + Name = "Test Operator", + RequireCustomMerchantNumber = false, + RequireCustomTerminalNumber = false + }; + + this.OperatorUIService.Setup(o => o.GetOperator(It.IsAny(), It.IsAny(), operatorId)) + .ReturnsAsync(Result.Success(operatorDetails)); + this.MerchantUIService.Setup(m => m.AddOperatorToMerchant(It.IsAny(), It.IsAny(), merchantId, operatorId, It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Failure()); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + 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("Assigned 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()); + cut.WaitForState(() => cut.Markup.Contains("Add"), TimeSpan.FromSeconds(5)); + + IElement addButton = cut.FindAll("button") + .First(b => b.TextContent.Trim() == "Add" && (b.GetAttribute("id") ?? "") != "addOperatorButton"); + addButton.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Failed to add operator"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_RemoveOperator_Success_ShowsSuccessMessage() + { + // Arrange + var merchantId = Guid.NewGuid(); + Guid operatorId = Guid.NewGuid(); + MerchantModels.MerchantOperatorModel assignedOperator = new() { + OperatorId = operatorId, + OperatorName = "Test Operator" + }; + + SetupSuccessfulDataLoadWithAssignedOperators(merchantId, new List { assignedOperator }); + + this.MerchantUIService.Setup(m => m.RemoveOperatorFromMerchant(It.IsAny(), It.IsAny(), merchantId, operatorId)) + .ReturnsAsync(Result.Success); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + 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("Assigned 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("Operator removed successfully"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_RemoveOperator_Failure_ShowsErrorMessage() + { + // Arrange + var merchantId = Guid.NewGuid(); + Guid operatorId = Guid.NewGuid(); + MerchantModels.MerchantOperatorModel assignedOperator = new() { + OperatorId = operatorId, + OperatorName = "Test Operator" + }; + + SetupSuccessfulDataLoadWithAssignedOperators(merchantId, new List { assignedOperator }); + + this.MerchantUIService.Setup(m => m.RemoveOperatorFromMerchant(It.IsAny(), It.IsAny(), merchantId, operatorId)) + .ReturnsAsync(Result.Failure(String.Empty)); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + 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("Assigned 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("Failed to remove operator"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_AssignContract_Success_ShowsSuccessMessage() + { + // Arrange + var merchantId = Guid.NewGuid(); + Guid contractId = Guid.NewGuid(); + ContractModels.ContractDropDownModel contractToAdd = new() { + ContractId = contractId, + Description = "Test Contract", + OperatorName = "Test Operator" + }; + + SetupSuccessfulDataLoadWithContracts(merchantId, new List { contractToAdd }); + + this.MerchantUIService.Setup(m => m.AssignContractToMerchant(It.IsAny(), It.IsAny(), merchantId, contractId)) + .ReturnsAsync(Result.Success); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Switch to contracts tab + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? contractsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Contracts")); + contractsButton?.Click(); + + // Click Add Contract button + IElement addContractButton = cut.Find("#addContractButton"); + addContractButton.Click(); + + // Act - Select contract and assign + IElement selectElement = cut.Find("select"); + selectElement.Change(contractId.ToString()); + IElement assignButton = cut.FindAll("button") + .First(b => b.TextContent.Trim() == "Assign"); + assignButton.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Contract assigned successfully"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_AssignContract_Failure_ShowsErrorMessage() + { + // Arrange + var merchantId = Guid.NewGuid(); + Guid contractId = Guid.NewGuid(); + ContractModels.ContractDropDownModel contractToAdd = new() { + ContractId = contractId, + Description = "Test Contract", + OperatorName = "Test Operator" + }; + + SetupSuccessfulDataLoadWithContracts(merchantId, new List { contractToAdd }); + + this.MerchantUIService.Setup(m => m.AssignContractToMerchant(It.IsAny(), It.IsAny(), merchantId, contractId)) + .ReturnsAsync(Result.Failure()); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Switch to contracts tab + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? contractsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Contracts")); + contractsButton?.Click(); + + // Click Add Contract button + IElement addContractButton = cut.Find("#addContractButton"); + addContractButton.Click(); + + // Act - Select contract and assign + IElement selectElement = cut.Find("select"); + selectElement.Change(contractId.ToString()); + IElement assignButton = cut.FindAll("button") + .First(b => b.TextContent.Trim() == "Assign"); + assignButton.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Failed to assign contract"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_RemoveContract_Success_ShowsSuccessMessage() + { + // Arrange + var merchantId = Guid.NewGuid(); + Guid contractId = Guid.NewGuid(); + MerchantModels.MerchantContractModel assignedContract = new() { + ContractId = contractId, + ContractName = "Test Contract", + OperatorName = "Test Operator" + }; + + SetupSuccessfulDataLoadWithAssignedContracts(merchantId, new List { assignedContract }); + + this.MerchantUIService.Setup(m => m.RemoveContractFromMerchant(It.IsAny(), It.IsAny(), merchantId, contractId)) + .ReturnsAsync(Result.Success); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Switch to contracts tab + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? contractsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Contracts")); + contractsButton?.Click(); + + // Act - Remove contract + IRefreshableElementCollection removeButtons = cut.FindAll("button"); + IElement? removeButton = removeButtons.FirstOrDefault(b => b.TextContent.Contains("Remove")); + removeButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Contract removed successfully"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_RemoveContract_Failure_ShowsErrorMessage() + { + // Arrange + var merchantId = Guid.NewGuid(); + Guid contractId = Guid.NewGuid(); + MerchantModels.MerchantContractModel assignedContract = new() { + ContractId = contractId, + ContractName = "Test Contract", + OperatorName = "Test Operator" + }; + + SetupSuccessfulDataLoadWithAssignedContracts(merchantId, new List { assignedContract }); + + this.MerchantUIService.Setup(m => m.RemoveContractFromMerchant(It.IsAny(), It.IsAny(), merchantId, contractId)) + .ReturnsAsync(Result.Failure(String.Empty)); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Switch to contracts tab + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? contractsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Contracts")); + contractsButton?.Click(); + + // Act - Remove contract + IRefreshableElementCollection removeButtons = cut.FindAll("button"); + IElement? removeButton = removeButtons.FirstOrDefault(b => b.TextContent.Contains("Remove")); + removeButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Failed to remove contract"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_DisplaysAssignedOperators_WhenPresent() + { + // Arrange + var merchantId = Guid.NewGuid(); + Guid operatorId = Guid.NewGuid(); + MerchantModels.MerchantOperatorModel assignedOperator = new() { + OperatorId = operatorId, + OperatorName = "Test Operator" + }; + + SetupSuccessfulDataLoadWithAssignedOperators(merchantId, new List { assignedOperator }); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Act - Switch to operators tab + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? operatorsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Operators")); + operatorsButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Test Operator"), timeout: TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsEdit_DisplaysAssignedContracts_WhenPresent() + { + // Arrange + var merchantId = Guid.NewGuid(); + Guid contractId = Guid.NewGuid(); + MerchantModels.MerchantContractModel assignedContract = new() { + ContractId = contractId, + ContractName = "Test Contract", + OperatorName = "Test Operator" + }; + + SetupSuccessfulDataLoadWithAssignedContracts(merchantId, new List { assignedContract }); + + IRenderedComponent cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Act - Switch to contracts tab + IRefreshableElementCollection buttons = cut.FindAll("button"); + IElement? contractsButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Contracts")); + contractsButton?.Click(); + + // Assert + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Test Contract"), timeout: TimeSpan.FromSeconds(5)); + } + + // Helper methods + private void SetupSuccessfulDataLoad(Guid merchantId, + string merchantName = "Test Merchant", + string merchantReference = "MERCH001", + List? assignedOperators = null, + List? availableOperators = null, + List? assignedContracts = null, + List? availableContracts = null, + List? assignedDevices = null) + { + MerchantModels.MerchantModel merchant = new() { + MerchantId = merchantId, + MerchantName = merchantName, + MerchantReference = merchantReference, + AddressId = Guid.NewGuid(), + AddressLine1 = "123 Test St", + AddressLine2 = "Suite 100", + Town = "Test Town", + Region = "Test Region", + PostalCode = "12345", + Country = "GB", + ContactId = Guid.NewGuid(), + ContactName = "Test Contact", + ContactEmailAddress = "test@example.com", + ContactPhoneNumber = "1234567890", + SettlementSchedule = "Immediate", + Balance = 1000.00m, + AvailableBalance = 500.00m + }; + + this.MerchantUIService.Setup(m => m.GetMerchant(It.IsAny(), It.IsAny(), merchantId)) + .ReturnsAsync(Result.Success(merchant)); + + this.MerchantUIService.Setup(m => m.GetMerchantOperators(It.IsAny(), It.IsAny(), merchantId)) + .ReturnsAsync(Result.Success(assignedOperators ?? new List())); + + this.MerchantUIService.Setup(m => m.GetMerchantContracts(It.IsAny(), It.IsAny(), merchantId)) + .ReturnsAsync(Result.Success(assignedContracts ?? new List())); + + this.MerchantUIService.Setup(m => m.GetMerchantDevices(It.IsAny(), It.IsAny(), merchantId)) + .ReturnsAsync(Result.Success(assignedDevices ?? new List())); + + this.OperatorUIService.Setup(o => o.GetOperatorsForDropDown(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(availableOperators ?? new List())); + + this.ContractUIService.Setup(c => c.GetContractsForDropDown(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(availableContracts ?? new List())); + } + + private void SetupSuccessfulDataLoadWithOperators(Guid merchantId, List operators) + => SetupSuccessfulDataLoad(merchantId, availableOperators: operators); + + private void SetupSuccessfulDataLoadWithAssignedOperators(Guid merchantId, List assignedOperators) + => SetupSuccessfulDataLoad(merchantId, assignedOperators: assignedOperators); + + private void SetupSuccessfulDataLoadWithContracts(Guid merchantId, List contracts) + => SetupSuccessfulDataLoad(merchantId, availableContracts: contracts); + + private void SetupSuccessfulDataLoadWithAssignedContracts(Guid merchantId, List assignedContracts) + => SetupSuccessfulDataLoad(merchantId, assignedContracts: assignedContracts); +} diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor.cs b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor.cs index c47b3e28..d597f5c2 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor.cs +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor.cs @@ -20,9 +20,6 @@ public partial class Edit { private MerchantModels.MerchantModel? merchant; private bool isLoading = true; private bool isSaving = false; - private string? errorMessage; - private string? successMessage; - private string activeTab = "details"; // Unified model for editing private MerchantModels.MerchantEditModel merchantEditModel = new(); @@ -49,6 +46,11 @@ public partial class Edit { private bool showAddDevice = false; private string? deviceIdentifier; + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + this.SetActiveTab("details"); + } + protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) { return;