diff --git a/EstateManagementUI.BlazorServer.Tests/Common/AuthenticationHelpersTests.cs b/EstateManagementUI.BlazorServer.Tests/Common/AuthenticationHelpersTests.cs new file mode 100644 index 00000000..f5945852 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Common/AuthenticationHelpersTests.cs @@ -0,0 +1,117 @@ +using EstateManagementUI.BlazorServer.Common; +using Shouldly; + +namespace EstateManagementUI.BlazorServer.Tests.Common; + +public class AuthenticationHelpersTests +{ + [Fact] + public void GetSecurityServiceAddresses_WithDefaultPorts_ReturnsCorrectAddresses() + { + // Arrange + var authority = "https://localhost:5001"; + + // Act + var (authorityAddress, issuerAddress) = AuthenticationHelpers.GetSecurityServiceAddresses( + authority, null, null); + + // Assert + authorityAddress.ShouldBe("https://localhost:5001"); + issuerAddress.ShouldBe("https://localhost:5001"); + } + + [Fact] + public void GetSecurityServiceAddresses_WithCustomLocalPort_ReturnsCorrectAuthorityAddress() + { + // Arrange + var authority = "https://localhost:5001"; + var securityServiceLocalPort = "6001"; + + // Act + var (authorityAddress, issuerAddress) = AuthenticationHelpers.GetSecurityServiceAddresses( + authority, securityServiceLocalPort, null); + + // Assert + authorityAddress.ShouldBe("https://localhost:6001"); + issuerAddress.ShouldBe("https://localhost:5001"); + } + + [Fact] + public void GetSecurityServiceAddresses_WithCustomPort_ReturnsCorrectIssuerAddress() + { + // Arrange + var authority = "https://localhost:5001"; + var securityServicePort = "7001"; + + // Act + var (authorityAddress, issuerAddress) = AuthenticationHelpers.GetSecurityServiceAddresses( + authority, null, securityServicePort); + + // Assert + authorityAddress.ShouldBe("https://localhost:5001"); + issuerAddress.ShouldBe("https://localhost:7001"); + } + + [Fact] + public void GetSecurityServiceAddresses_WithBothCustomPorts_ReturnsCorrectAddresses() + { + // Arrange + var authority = "https://localhost:5001"; + var securityServiceLocalPort = "6001"; + var securityServicePort = "7001"; + + // Act + var (authorityAddress, issuerAddress) = AuthenticationHelpers.GetSecurityServiceAddresses( + authority, securityServiceLocalPort, securityServicePort); + + // Assert + authorityAddress.ShouldBe("https://localhost:6001"); + issuerAddress.ShouldBe("https://localhost:7001"); + } + + [Fact] + public void GetSecurityServiceAddresses_WithTrailingSlash_RemovesTrailingSlash() + { + // Arrange + var authority = "https://localhost:5001/"; + + // Act + var (authorityAddress, issuerAddress) = AuthenticationHelpers.GetSecurityServiceAddresses( + authority, null, null); + + // Assert + authorityAddress.ShouldBe("https://localhost:5001"); + issuerAddress.ShouldBe("https://localhost:5001"); + } + + [Fact] + public void GetSecurityServiceAddresses_WithPath_PreservesPath() + { + // Arrange + var authority = "https://localhost:5001/auth"; + var securityServiceLocalPort = "6001"; + + // Act + var (authorityAddress, issuerAddress) = AuthenticationHelpers.GetSecurityServiceAddresses( + authority, securityServiceLocalPort, null); + + // Assert + authorityAddress.ShouldBe("https://localhost:6001/auth"); + issuerAddress.ShouldBe("https://localhost:5001/auth"); + } + + [Fact] + public void GetSecurityServiceAddresses_WithEmptyPortStrings_UsesDefaultPort() + { + // Arrange + var authority = "https://localhost:5001"; + + // Act + var (authorityAddress, issuerAddress) = AuthenticationHelpers.GetSecurityServiceAddresses( + authority, string.Empty, string.Empty); + + // Assert + authorityAddress.ShouldBe("https://localhost:5001"); + issuerAddress.ShouldBe("https://localhost:5001"); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Common/TestAuthenticationHandlerTests.cs b/EstateManagementUI.BlazorServer.Tests/Common/TestAuthenticationHandlerTests.cs new file mode 100644 index 00000000..761d6259 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Common/TestAuthenticationHandlerTests.cs @@ -0,0 +1,241 @@ +using EstateManagementUI.BlazorServer.Common; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using Shouldly; +using System.Security.Claims; +using System.Text.Encodings.Web; + +namespace EstateManagementUI.BlazorServer.Tests.Common; + +public class TestAuthenticationHandlerTests +{ + private readonly Mock> _options; + private readonly Mock _loggerFactory; + private readonly IConfiguration _configuration; + private readonly Mock _httpContextAccessor; + private readonly UrlEncoder _encoder; + + public TestAuthenticationHandlerTests() + { + _options = new Mock>(); + _loggerFactory = new Mock(); + _httpContextAccessor = new Mock(); + _encoder = UrlEncoder.Default; + + _options.Setup(x => x.Get(It.IsAny())).Returns(new AuthenticationSchemeOptions()); + _loggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(Mock.Of()); + + // Create a real configuration + var configBuilder = new ConfigurationBuilder(); + configBuilder.AddInMemoryCollection(new Dictionary + { + ["AppSettings:TestUserRole"] = "Administrator" + }); + _configuration = configBuilder.Build(); + } + + [Fact] + public void SchemeName_ReturnsTestAuthentication() + { + // Assert + TestAuthenticationHandler.SchemeName.ShouldBe("TestAuthentication"); + } + + [Fact] + public async Task HandleAuthenticateAsync_WithDefaultConfig_UsesAdministratorRole() + { + // Arrange + var httpContext = new DefaultHttpContext(); + httpContext.Request.QueryString = new QueryString(""); + + // Configure session + var sessionFeature = new Mock(); + var session = new Mock(); + sessionFeature.Setup(s => s.Session).Returns(session.Object); + httpContext.Features.Set(sessionFeature.Object); + + _httpContextAccessor.Setup(x => x.HttpContext).Returns(httpContext); + + var handler = new TestAuthenticationHandler( + _options.Object, + _loggerFactory.Object, + _encoder, + _configuration, + _httpContextAccessor.Object); + + await handler.InitializeAsync( + new AuthenticationScheme(TestAuthenticationHandler.SchemeName, null, typeof(TestAuthenticationHandler)), + httpContext); + + // Act + var result = await handler.AuthenticateAsync(); + + // Assert + result.Succeeded.ShouldBeTrue(); + result.Principal.ShouldNotBeNull(); + var roleClaim = result.Principal.FindFirst(ClaimTypes.Role); + roleClaim.ShouldNotBeNull(); + roleClaim.Value.ShouldBe("Administrator"); + } + + [Fact] + public async Task HandleAuthenticateAsync_CreatesAdministratorUserClaims() + { + // Arrange + var httpContext = new DefaultHttpContext(); + httpContext.Request.QueryString = new QueryString(""); + + // Configure session + var sessionFeature = new Mock(); + var session = new Mock(); + sessionFeature.Setup(s => s.Session).Returns(session.Object); + httpContext.Features.Set(sessionFeature.Object); + + _httpContextAccessor.Setup(x => x.HttpContext).Returns(httpContext); + + var handler = new TestAuthenticationHandler( + _options.Object, + _loggerFactory.Object, + _encoder, + _configuration, + _httpContextAccessor.Object); + + await handler.InitializeAsync( + new AuthenticationScheme(TestAuthenticationHandler.SchemeName, null, typeof(TestAuthenticationHandler)), + httpContext); + + // Act + var result = await handler.AuthenticateAsync(); + + // Assert + result.Succeeded.ShouldBeTrue(); + result.Principal.ShouldNotBeNull(); + + var roleClaim = result.Principal.FindFirst(ClaimTypes.Role); + roleClaim?.Value.ShouldBe("Administrator"); + + var nameClaim = result.Principal.FindFirst(ClaimTypes.Name); + nameClaim?.Value.ShouldBe("Admin User"); + + var emailClaim = result.Principal.FindFirst(ClaimTypes.Email); + emailClaim?.Value.ShouldBe("administrator@test.com"); + + var estateIdClaim = result.Principal.FindFirst("estateId"); + estateIdClaim?.Value.ShouldBe("11111111-1111-1111-1111-111111111111"); + } + + [Fact] + public async Task HandleAuthenticateAsync_CreatesAllRequiredClaims() + { + // Arrange + var httpContext = new DefaultHttpContext(); + httpContext.Request.QueryString = new QueryString(""); + + // Configure session + var sessionFeature = new Mock(); + var session = new Mock(); + sessionFeature.Setup(s => s.Session).Returns(session.Object); + httpContext.Features.Set(sessionFeature.Object); + + _httpContextAccessor.Setup(x => x.HttpContext).Returns(httpContext); + + var handler = new TestAuthenticationHandler( + _options.Object, + _loggerFactory.Object, + _encoder, + _configuration, + _httpContextAccessor.Object); + + await handler.InitializeAsync( + new AuthenticationScheme(TestAuthenticationHandler.SchemeName, null, typeof(TestAuthenticationHandler)), + httpContext); + + // Act + var result = await handler.AuthenticateAsync(); + + // Assert + result.Succeeded.ShouldBeTrue(); + result.Principal.ShouldNotBeNull(); + + result.Principal.FindFirst(ClaimTypes.NameIdentifier).ShouldNotBeNull(); + result.Principal.FindFirst(ClaimTypes.Name).ShouldNotBeNull(); + result.Principal.FindFirst(ClaimTypes.Email).ShouldNotBeNull(); + result.Principal.FindFirst("estateId").ShouldNotBeNull(); + result.Principal.FindFirst(ClaimTypes.Role).ShouldNotBeNull(); + result.Principal.FindFirst("role").ShouldNotBeNull(); + } + + [Fact] + public async Task HandleAuthenticateAsync_AlwaysSucceeds() + { + // Arrange + var httpContext = new DefaultHttpContext(); + httpContext.Request.QueryString = new QueryString(""); + + // Configure session + var sessionFeature = new Mock(); + var session = new Mock(); + sessionFeature.Setup(s => s.Session).Returns(session.Object); + httpContext.Features.Set(sessionFeature.Object); + + _httpContextAccessor.Setup(x => x.HttpContext).Returns(httpContext); + + var handler = new TestAuthenticationHandler( + _options.Object, + _loggerFactory.Object, + _encoder, + _configuration, + _httpContextAccessor.Object); + + await handler.InitializeAsync( + new AuthenticationScheme(TestAuthenticationHandler.SchemeName, null, typeof(TestAuthenticationHandler)), + httpContext); + + // Act + var result = await handler.AuthenticateAsync(); + + // Assert + result.Succeeded.ShouldBeTrue(); + result.Failure.ShouldBeNull(); + } + + [Fact] + public async Task HandleAuthenticateAsync_CreatesAuthenticationTicket() + { + // Arrange + var httpContext = new DefaultHttpContext(); + httpContext.Request.QueryString = new QueryString(""); + + // Configure session + var sessionFeature = new Mock(); + var session = new Mock(); + sessionFeature.Setup(s => s.Session).Returns(session.Object); + httpContext.Features.Set(sessionFeature.Object); + + _httpContextAccessor.Setup(x => x.HttpContext).Returns(httpContext); + + var handler = new TestAuthenticationHandler( + _options.Object, + _loggerFactory.Object, + _encoder, + _configuration, + _httpContextAccessor.Object); + + await handler.InitializeAsync( + new AuthenticationScheme(TestAuthenticationHandler.SchemeName, null, typeof(TestAuthenticationHandler)), + httpContext); + + // Act + var result = await handler.AuthenticateAsync(); + + // Assert + result.Succeeded.ShouldBeTrue(); + result.Ticket.ShouldNotBeNull(); + result.Ticket.AuthenticationScheme.ShouldBe(TestAuthenticationHandler.SchemeName); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/EstateManagementUI.BlazorServer.Tests.csproj b/EstateManagementUI.BlazorServer.Tests/EstateManagementUI.BlazorServer.Tests.csproj new file mode 100644 index 00000000..08e3531c --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/EstateManagementUI.BlazorServer.Tests.csproj @@ -0,0 +1,41 @@ + + + + net10.0 + enable + enable + None + false + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + \ No newline at end of file diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsIndexPageTests.cs new file mode 100644 index 00000000..61a0a9a8 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsIndexPageTests.cs @@ -0,0 +1,162 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Permissions; +using EstateManagementUI.BlazorServer.Models; +using EstateManagementUI.BlazorServer.Permissions; +using MediatR; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Shouldly; +using static EstateManagementUI.BlazorServer.Requests.Queries; +using ContractsIndex = EstateManagementUI.BlazorServer.Components.Pages.Contracts.Index; + +namespace EstateManagementUI.BlazorServer.Tests.Pages.Contracts; + +public class ContractsIndexPageTests : TestContext +{ + private readonly Mock _mockMediator; + private readonly Mock _mockPermissionKeyProvider; + private readonly Mock _mockNavigationManager; + + public ContractsIndexPageTests() + { + _mockMediator = new Mock(); + _mockPermissionKeyProvider = new Mock(); + _mockNavigationManager = new Mock(); + + _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); + + Services.AddSingleton(_mockMediator.Object); + Services.AddSingleton(_mockPermissionKeyProvider.Object); + Services.AddSingleton(_mockNavigationManager.Object); + + // Add required permission components + ComponentFactories.AddStub(); + } + + [Fact] + public void ContractsIndex_RendersCorrectly() + { + // Arrange + var contracts = new List + { + new ContractModel + { + ContractId = Guid.NewGuid(), + Description = "Test Contract", + OperatorName = "Test Operator", + OperatorId = Guid.NewGuid(), + Products = new List() + } + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(contracts)); + + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Contract Management"); + } + + [Fact] + public void ContractsIndex_WithNoContracts_ShowsEmptyState() + { + // Arrange + var emptyList = new List(); + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(emptyList)); + + // Act + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin")); + + // Assert + cut.Markup.ShouldContain("No contracts found"); + } + + [Fact] + public void ContractsIndex_WithContracts_DisplaysContractList() + { + // Arrange + var contracts = new List + { + new ContractModel + { + ContractId = Guid.NewGuid(), + Description = "Contract 1", + OperatorName = "Operator A", + OperatorId = Guid.NewGuid(), + Products = new List() + }, + new ContractModel + { + ContractId = Guid.NewGuid(), + Description = "Contract 2", + OperatorName = "Operator B", + OperatorId = Guid.NewGuid(), + Products = new List() + } + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(contracts)); + + // Act + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert + cut.Markup.ShouldContain("Contract 1"); + cut.Markup.ShouldContain("Contract 2"); + cut.Markup.ShouldContain("Operator A"); + cut.Markup.ShouldContain("Operator B"); + } + + [Fact] + public void ContractsIndex_DisplaysProductCount() + { + // Arrange + var contracts = new List + { + new ContractModel + { + ContractId = Guid.NewGuid(), + Description = "Test Contract", + OperatorName = "Test Operator", + OperatorId = Guid.NewGuid(), + Products = new List + { + new ContractProductModel { ContractProductId = Guid.NewGuid(), ProductName = "Product 1" }, + new ContractProductModel { ContractProductId = Guid.NewGuid(), ProductName = "Product 2" } + } + } + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(contracts)); + + // Act + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert + cut.Markup.ShouldContain("Product(s)"); + } + + [Fact] + public void ContractsIndex_HasCorrectPageTitle() + { + // Arrange + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(new List())); + + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsViewPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsViewPageTests.cs new file mode 100644 index 00000000..8888f45e --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Contracts/ContractsViewPageTests.cs @@ -0,0 +1,99 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Pages.Contracts; +using EstateManagementUI.BlazorServer.Components.Permissions; +using EstateManagementUI.BlazorServer.Models; +using EstateManagementUI.BlazorServer.Permissions; +using MediatR; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Shouldly; +using static EstateManagementUI.BlazorServer.Requests.Queries; + +namespace EstateManagementUI.BlazorServer.Tests.Pages.Contracts; + +public class ContractsViewPageTests : TestContext +{ + private readonly Mock _mockMediator; + private readonly Mock _mockNavigationManager; + private readonly Mock _mockPermissionKeyProvider; + + public ContractsViewPageTests() + { + _mockMediator = new Mock(); + _mockNavigationManager = new Mock(); + _mockPermissionKeyProvider = new Mock(); + + _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); + + Services.AddSingleton(_mockMediator.Object); + Services.AddSingleton(_mockNavigationManager.Object); + Services.AddSingleton(_mockPermissionKeyProvider.Object); + + ComponentFactories.AddStub(); + } + + [Fact] + public void ContractsView_RendersCorrectly() + { + // Arrange + var contractId = Guid.NewGuid(); + var contract = new ContractModel + { + ContractId = contractId, + Description = "Test Contract", + OperatorName = "Test Operator" + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(contract)); + + // Act + var cut = RenderComponent(parameters => parameters + .Add(p => p.ContractId, contractId)); + + // Assert + cut.Markup.ShouldContain("View Contract"); + } + + [Fact] + public void ContractsView_DisplaysContractDetails() + { + // Arrange + var contractId = Guid.NewGuid(); + var contract = new ContractModel + { + ContractId = contractId, + Description = "Test Contract", + OperatorName = "Test Operator" + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(contract)); + + // Act + var cut = RenderComponent(parameters => parameters + .Add(p => p.ContractId, contractId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert + cut.Markup.ShouldContain("Test Contract"); + } + + [Fact] + public void ContractsView_HasCorrectPageTitle() + { + // Arrange + var contractId = Guid.NewGuid(); + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(new ContractModel { ContractId = contractId })); + + // Act + var cut = RenderComponent(parameters => parameters + .Add(p => p.ContractId, contractId)); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/CounterPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/CounterPageTests.cs new file mode 100644 index 00000000..8769a007 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/CounterPageTests.cs @@ -0,0 +1,61 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Pages; +using Shouldly; + +namespace EstateManagementUI.BlazorServer.Tests.Pages; + +public class CounterPageTests : TestContext +{ + [Fact] + public void Counter_RendersCorrectly() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Find("h1").TextContent.ShouldBe("Counter"); + cut.Find("p[role='status']").TextContent.ShouldBe("Current count: 0"); + cut.Find("button").TextContent.Trim().ShouldBe("Click me"); + } + + [Fact] + public void Counter_IncrementButton_IncrementsCount() + { + // Arrange + var cut = RenderComponent(); + var button = cut.Find("button"); + + // Act + button.Click(); + + // Assert + cut.Find("p[role='status']").TextContent.ShouldBe("Current count: 1"); + } + + [Fact] + public void Counter_MultipleClicks_IncrementsCorrectly() + { + // Arrange + var cut = RenderComponent(); + var button = cut.Find("button"); + + // Act + button.Click(); + button.Click(); + button.Click(); + + // Assert + cut.Find("p[role='status']").TextContent.ShouldBe("Current count: 3"); + } + + [Fact] + public void Counter_HasCorrectPageTitle() + { + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/EntryScreenPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/EntryScreenPageTests.cs new file mode 100644 index 00000000..fb6ea708 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/EntryScreenPageTests.cs @@ -0,0 +1,77 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Pages; +using Shouldly; + +namespace EstateManagementUI.BlazorServer.Tests.Pages; + +public class EntryScreenPageTests : TestContext +{ + [Fact] + public void EntryScreen_RendersCorrectly() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Estate Management"); + cut.Markup.ShouldContain("Merchant Management"); + cut.Markup.ShouldContain("File Processing"); + } + + [Fact] + public void EntryScreen_HasEstateManagementCard() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Manage estate details"); + cut.Markup.ShouldContain("Manage estate users"); + cut.Markup.ShouldContain("Operator Management"); + } + + [Fact] + public void EntryScreen_HasMerchantManagementCard() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Manage Merchant Details"); + cut.Markup.ShouldContain("Balance Management"); + } + + [Fact] + public void EntryScreen_HasFileProcessingCard() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Transaction Files"); + cut.Markup.ShouldContain("Settlement Processing"); + } + + [Fact] + public void EntryScreen_HasViewMoreLinks() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.FindAll("a[href='/estate-info']").Count.ShouldBeGreaterThan(0); + cut.FindAll("a[href='/merchant-info']").Count.ShouldBeGreaterThan(0); + cut.FindAll("a[href='/file-info']").Count.ShouldBeGreaterThan(0); + } + + [Fact] + public void EntryScreen_HasCorrectPageTitle() + { + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/ErrorPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/ErrorPageTests.cs new file mode 100644 index 00000000..1a37fc8e --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/ErrorPageTests.cs @@ -0,0 +1,51 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Pages; +using Shouldly; + +namespace EstateManagementUI.BlazorServer.Tests.Pages; + +public class ErrorPageTests : TestContext +{ + [Fact] + public void Error_RendersCorrectly() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Find("h1").TextContent.ShouldBe("Error."); + cut.Find("h2").TextContent.ShouldBe("An error occurred while processing your request."); + } + + [Fact] + public void Error_WithoutRequestId_DoesNotShowRequestId() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.FindAll("strong").ShouldNotContain(e => e.TextContent.Contains("Request ID:")); + } + + [Fact] + public void Error_ShowsDevelopmentModeInformation() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Development Mode"); + cut.Markup.ShouldContain("ASPNETCORE_ENVIRONMENT"); + } + + [Fact] + public void Error_HasCorrectPageTitle() + { + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Estate/EstateIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Estate/EstateIndexPageTests.cs new file mode 100644 index 00000000..234176c4 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Estate/EstateIndexPageTests.cs @@ -0,0 +1,85 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Models; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Shouldly; +using static EstateManagementUI.BlazorServer.Requests.Queries; +using EstateIndex = EstateManagementUI.BlazorServer.Components.Pages.Estate.Index; + +namespace EstateManagementUI.BlazorServer.Tests.Pages.Estate; + +public class EstateIndexPageTests : TestContext +{ + private readonly Mock _mockMediator; + + public EstateIndexPageTests() + { + _mockMediator = new Mock(); + Services.AddSingleton(_mockMediator.Object); + } + + [Fact] + public void EstateIndex_RendersCorrectly() + { + // Arrange + var estate = new EstateModel + { + EstateId = Guid.NewGuid(), + EstateName = "Test Estate", + Reference = "EST001" + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(estate)); + + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Estate Management"); + } + + [Fact] + public void EstateIndex_DisplaysEstateDetails() + { + // Arrange + var estate = new EstateModel + { + 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())); + + // Act + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert + cut.Markup.ShouldContain("Test Estate"); + } + + [Fact] + public void EstateIndex_HasCorrectPageTitle() + { + // Arrange + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(new EstateModel { EstateId = Guid.NewGuid() })); + + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/EstateInfoPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/EstateInfoPageTests.cs new file mode 100644 index 00000000..c2b32858 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/EstateInfoPageTests.cs @@ -0,0 +1,64 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Pages; +using Shouldly; + +namespace EstateManagementUI.BlazorServer.Tests.Pages; + +public class EstateInfoPageTests : TestContext +{ + [Fact] + public void EstateInfo_RendersCorrectly() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Estate Management"); + cut.Markup.ShouldContain("Comprehensive estate management and configuration"); + } + + [Fact] + public void EstateInfo_HasFeatureSections() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Manage Estate Details"); + cut.Markup.ShouldContain("Manage Estate Users"); + cut.Markup.ShouldContain("Operator Management"); + } + + [Fact] + public void EstateInfo_HasSignInButton() + { + // Act + var cut = RenderComponent(); + + // Assert + var loginButton = cut.Find("a#loginButton"); + loginButton.GetAttribute("href").ShouldBe("/login"); + loginButton.TextContent.ShouldContain("Sign In"); + } + + [Fact] + public void EstateInfo_HasBackButton() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.FindAll("a[href='/entry']").Count.ShouldBeGreaterThan(0); + } + + [Fact] + public void EstateInfo_HasCorrectPageTitle() + { + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/FileInfoPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/FileInfoPageTests.cs new file mode 100644 index 00000000..8966157f --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/FileInfoPageTests.cs @@ -0,0 +1,64 @@ +using Bunit; +using Shouldly; +using FileInfoPage = EstateManagementUI.BlazorServer.Components.Pages.FileInfo; + +namespace EstateManagementUI.BlazorServer.Tests.Pages; + +public class FileInfoPageTests : TestContext +{ + [Fact] + public void FileInfo_RendersCorrectly() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("File Processing"); + cut.Markup.ShouldContain("Automated file processing and settlement management"); + } + + [Fact] + public void FileInfo_HasFeatureSections() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Transaction Files"); + cut.Markup.ShouldContain("Settlement Processing"); + cut.Markup.ShouldContain("Bulk Processing"); + } + + [Fact] + public void FileInfo_HasSignInButton() + { + // Act + var cut = RenderComponent(); + + // Assert + var loginButton = cut.Find("a#loginButton"); + loginButton.GetAttribute("href").ShouldBe("/login"); + loginButton.TextContent.ShouldContain("Sign In"); + } + + [Fact] + public void FileInfo_HasBackButton() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.FindAll("a[href='/entry']").Count.ShouldBeGreaterThan(0); + } + + [Fact] + public void FileInfo_HasCorrectPageTitle() + { + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/FileProcessing/FileProcessingIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/FileProcessing/FileProcessingIndexPageTests.cs new file mode 100644 index 00000000..7ebc34b9 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/FileProcessing/FileProcessingIndexPageTests.cs @@ -0,0 +1,79 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Permissions; +using EstateManagementUI.BlazorServer.Models; +using EstateManagementUI.BlazorServer.Permissions; +using MediatR; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Shouldly; +using static EstateManagementUI.BlazorServer.Requests.Queries; +using FileProcessingIndex = EstateManagementUI.BlazorServer.Components.Pages.FileProcessing.Index; + +namespace EstateManagementUI.BlazorServer.Tests.Pages.FileProcessing; + +public class FileProcessingIndexPageTests : TestContext +{ + private readonly Mock _mockMediator; + private readonly Mock _mockNavigationManager; + private readonly Mock _mockPermissionKeyProvider; + + public FileProcessingIndexPageTests() + { + _mockMediator = new Mock(); + _mockNavigationManager = new Mock(); + _mockPermissionKeyProvider = new Mock(); + + _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); + + Services.AddSingleton(_mockMediator.Object); + Services.AddSingleton(_mockNavigationManager.Object); + Services.AddSingleton(_mockPermissionKeyProvider.Object); + + ComponentFactories.AddStub(); + } + + [Fact] + public void FileProcessingIndex_RendersCorrectly() + { + // Arrange + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(new List())); + + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("File Processing"); + } + + [Fact] + public void FileProcessingIndex_WithNoFiles_ShowsEmptyState() + { + // Arrange + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(new List())); + + // Act + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert + cut.Markup.ShouldContain("No file import logs found"); + } + + [Fact] + public void FileProcessingIndex_HasCorrectPageTitle() + { + // Arrange + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(new List())); + + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/HomePageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/HomePageTests.cs new file mode 100644 index 00000000..e05b5ab4 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/HomePageTests.cs @@ -0,0 +1,80 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Pages; +using EstateManagementUI.BlazorServer.Components.Permissions; +using EstateManagementUI.BlazorServer.Permissions; +using MediatR; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.JSInterop; +using Moq; +using Shouldly; +using System.Security.Claims; + +namespace EstateManagementUI.BlazorServer.Tests.Pages; + +public class HomePageTests : TestContext +{ + private readonly Mock _mockMediator; + private readonly Mock _mockAuthStateProvider; + private readonly Mock _mockNavigationManager; + private readonly Mock _mockJSRuntime; + private readonly Mock _mockPermissionService; + + public HomePageTests() + { + _mockMediator = new Mock(); + _mockAuthStateProvider = new Mock(); + _mockNavigationManager = new Mock(); + _mockJSRuntime = new Mock(); + _mockPermissionService = new Mock(); + + Services.AddSingleton(_mockMediator.Object); + Services.AddSingleton(_mockAuthStateProvider.Object); + Services.AddSingleton(_mockNavigationManager.Object); + Services.AddSingleton(_mockJSRuntime.Object); + Services.AddSingleton(_mockPermissionService.Object); + + // Add required permission components + ComponentFactories.AddStub(); + ComponentFactories.AddStub(); + } + + [Fact] + public void Home_RendersCorrectly() + { + // Arrange + var claims = new[] { new Claim(ClaimTypes.Role, "Estate") }; + var identity = new ClaimsIdentity(claims, "Test"); + var user = new ClaimsPrincipal(identity); + var authState = Task.FromResult(new AuthenticationState(user)); + + _mockAuthStateProvider.Setup(x => x.GetAuthenticationStateAsync()).Returns(authState); + + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Dashboard"); + cut.Markup.ShouldContain("Welcome to Estate Management System"); + } + + [Fact] + public void Home_HasCorrectPageTitle() + { + // Arrange + var claims = new[] { new Claim(ClaimTypes.Role, "Estate") }; + var identity = new ClaimsIdentity(claims, "Test"); + var user = new ClaimsPrincipal(identity); + var authState = Task.FromResult(new AuthenticationState(user)); + + _mockAuthStateProvider.Setup(x => x.GetAuthenticationStateAsync()).Returns(authState); + + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/MerchantInfoPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/MerchantInfoPageTests.cs new file mode 100644 index 00000000..16949bae --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/MerchantInfoPageTests.cs @@ -0,0 +1,62 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Pages; +using Shouldly; + +namespace EstateManagementUI.BlazorServer.Tests.Pages; + +public class MerchantInfoPageTests : TestContext +{ + [Fact] + public void MerchantInfo_RendersCorrectly() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Merchant Management"); + } + + [Fact] + public void MerchantInfo_HasFeatureSections() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Manage Merchant Details"); + cut.Markup.ShouldContain("Balance Management"); + } + + [Fact] + public void MerchantInfo_HasSignInButton() + { + // Act + var cut = RenderComponent(); + + // Assert + var loginButtons = cut.FindAll("a#loginButton"); + loginButtons.Count.ShouldBeGreaterThan(0); + loginButtons[0].GetAttribute("href").ShouldBe("/login"); + } + + [Fact] + public void MerchantInfo_HasBackButton() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.FindAll("a[href='/entry']").Count.ShouldBeGreaterThan(0); + } + + [Fact] + public void MerchantInfo_HasCorrectPageTitle() + { + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsIndexPageTests.cs new file mode 100644 index 00000000..ad44e408 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsIndexPageTests.cs @@ -0,0 +1,171 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Permissions; +using EstateManagementUI.BlazorServer.Models; +using EstateManagementUI.BlazorServer.Permissions; +using MediatR; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Shouldly; +using static EstateManagementUI.BlazorServer.Requests.Queries; +using MerchantsIndex = EstateManagementUI.BlazorServer.Components.Pages.Merchants.Index; + +namespace EstateManagementUI.BlazorServer.Tests.Pages.Merchants; + +public class MerchantsIndexPageTests : TestContext +{ + private readonly Mock _mockMediator; + private readonly Mock _mockPermissionKeyProvider; + private readonly Mock _mockNavigationManager; + + public MerchantsIndexPageTests() + { + _mockMediator = new Mock(); + _mockPermissionKeyProvider = new Mock(); + _mockNavigationManager = new Mock(); + + _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); + + Services.AddSingleton(_mockMediator.Object); + Services.AddSingleton(_mockPermissionKeyProvider.Object); + Services.AddSingleton(_mockNavigationManager.Object); + + // Add required permission components + ComponentFactories.AddStub(); + } + + [Fact] + public void MerchantsIndex_InitialState_ShowsLoadingIndicator() + { + // Arrange + var merchants = new List + { + new MerchantModel + { + MerchantId = Guid.NewGuid(), + MerchantName = "Test Merchant", + MerchantReference = "REF001", + Balance = 1000m, + AvailableBalance = 500m, + SettlementSchedule = "Daily" + } + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(merchants)); + + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Merchant Management"); + } + + [Fact] + public void MerchantsIndex_WithNoMerchants_ShowsEmptyState() + { + // Arrange + var emptyList = new List(); + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(emptyList)); + + // Act + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin")); + + // Assert + cut.Markup.ShouldContain("No merchants found"); + } + + [Fact] + public void MerchantsIndex_WithMerchants_DisplaysMerchantList() + { + // Arrange + var merchants = new List + { + new MerchantModel + { + MerchantId = Guid.NewGuid(), + MerchantName = "Test Merchant 1", + MerchantReference = "REF001", + Balance = 1000m, + AvailableBalance = 500m, + SettlementSchedule = "Daily" + }, + new MerchantModel + { + MerchantId = Guid.NewGuid(), + MerchantName = "Test Merchant 2", + MerchantReference = "REF002", + Balance = 2000m, + AvailableBalance = 1500m, + SettlementSchedule = "Weekly" + } + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(merchants)); + + // Act + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert + cut.Markup.ShouldContain("Test Merchant 1"); + cut.Markup.ShouldContain("Test Merchant 2"); + cut.Markup.ShouldContain("REF001"); + cut.Markup.ShouldContain("REF002"); + } + + [Fact] + public void MerchantsIndex_WithMerchants_DisplaysSummaryCards() + { + // Arrange + var merchants = new List + { + new MerchantModel + { + MerchantId = Guid.NewGuid(), + MerchantName = "Merchant 1", + MerchantReference = "REF001", + Balance = 1000m, + AvailableBalance = 500m + }, + new MerchantModel + { + MerchantId = Guid.NewGuid(), + MerchantName = "Merchant 2", + MerchantReference = "REF002", + Balance = 2000m, + AvailableBalance = 1500m + } + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(merchants)); + + // Act + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert + cut.Markup.ShouldContain("Total Merchants"); + cut.Markup.ShouldContain("Total Balance"); + cut.Markup.ShouldContain("Available Balance"); + } + + [Fact] + public void MerchantsIndex_HasCorrectPageTitle() + { + // Arrange + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(new List())); + + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsViewPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsViewPageTests.cs new file mode 100644 index 00000000..b4c3070f --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsViewPageTests.cs @@ -0,0 +1,114 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Pages.Merchants; +using EstateManagementUI.BlazorServer.Models; +using MediatR; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Shouldly; +using static EstateManagementUI.BlazorServer.Requests.Queries; + +namespace EstateManagementUI.BlazorServer.Tests.Pages.Merchants; + +public class MerchantsViewPageTests : TestContext +{ + private readonly Mock _mockMediator; + private readonly Mock _mockNavigationManager; + + public MerchantsViewPageTests() + { + _mockMediator = new Mock(); + _mockNavigationManager = new Mock(); + + Services.AddSingleton(_mockMediator.Object); + Services.AddSingleton(_mockNavigationManager.Object); + } + + [Fact] + public void MerchantsView_InitialState_ShowsLoadingIndicator() + { + // Arrange + var merchantId = Guid.NewGuid(); + var merchant = new MerchantModel + { + MerchantId = merchantId, + MerchantName = "Test Merchant", + MerchantReference = "REF001" + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(merchant)); + + // Act + var cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + + // Assert + cut.Markup.ShouldContain("View Merchant"); + } + + [Fact] + public void MerchantsView_WithMerchant_DisplaysMerchantName() + { + // Arrange + var merchantId = Guid.NewGuid(); + var merchant = new MerchantModel + { + MerchantId = merchantId, + MerchantName = "Test Merchant", + MerchantReference = "REF001" + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(merchant)); + + // Act + var cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert + cut.Markup.ShouldContain("Test Merchant"); + cut.Markup.ShouldContain("REF001"); + } + + [Fact] + public void MerchantsView_HasCorrectPageTitle() + { + // Arrange + var merchantId = Guid.NewGuid(); + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(new MerchantModel { MerchantId = merchantId })); + + // Act + var cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } + + [Fact] + public void MerchantsView_HasBackButton() + { + // Arrange + var merchantId = Guid.NewGuid(); + var merchant = new MerchantModel + { + MerchantId = merchantId, + MerchantName = "Test Merchant" + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(merchant)); + + // Act + var cut = RenderComponent(parameters => parameters + .Add(p => p.MerchantId, merchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert + cut.Markup.ShouldContain("Back to List"); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/NotFoundPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/NotFoundPageTests.cs new file mode 100644 index 00000000..fc35376d --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/NotFoundPageTests.cs @@ -0,0 +1,29 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Pages; +using Shouldly; + +namespace EstateManagementUI.BlazorServer.Tests.Pages; + +public class NotFoundPageTests : TestContext +{ + [Fact] + public void NotFound_RendersCorrectly() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Find("h3").TextContent.ShouldBe("Not Found"); + cut.Find("p").TextContent.ShouldBe("Sorry, the content you are looking for does not exist."); + } + + [Fact] + public void NotFound_HasCorrectLayout() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldNotBeNullOrEmpty(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsIndexPageTests.cs new file mode 100644 index 00000000..ee3f10c1 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsIndexPageTests.cs @@ -0,0 +1,153 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Permissions; +using EstateManagementUI.BlazorServer.Models; +using EstateManagementUI.BlazorServer.Permissions; +using MediatR; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Shouldly; +using static EstateManagementUI.BlazorServer.Requests.Queries; +using OperatorsIndex = EstateManagementUI.BlazorServer.Components.Pages.Operators.Index; + +namespace EstateManagementUI.BlazorServer.Tests.Pages.Operators; + +public class OperatorsIndexPageTests : TestContext +{ + private readonly Mock _mockMediator; + private readonly Mock _mockPermissionKeyProvider; + private readonly Mock _mockNavigationManager; + + public OperatorsIndexPageTests() + { + _mockMediator = new Mock(); + _mockPermissionKeyProvider = new Mock(); + _mockNavigationManager = new Mock(); + + _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); + + Services.AddSingleton(_mockMediator.Object); + Services.AddSingleton(_mockPermissionKeyProvider.Object); + Services.AddSingleton(_mockNavigationManager.Object); + + // Add required permission components + ComponentFactories.AddStub(); + } + + [Fact] + public void OperatorsIndex_RendersCorrectly() + { + // Arrange + var operators = new List + { + new OperatorModel + { + OperatorId = Guid.NewGuid(), + Name = "Test Operator", + RequireCustomMerchantNumber = true, + RequireCustomTerminalNumber = false + } + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(operators)); + + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Operator Management"); + } + + [Fact] + public void OperatorsIndex_WithNoOperators_ShowsEmptyState() + { + // Arrange + var emptyList = new List(); + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(emptyList)); + + // Act + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin")); + + // Assert + cut.Markup.ShouldContain("No operators found"); + } + + [Fact] + public void OperatorsIndex_WithOperators_DisplaysOperatorList() + { + // Arrange + var operators = new List + { + new OperatorModel + { + OperatorId = Guid.NewGuid(), + Name = "Operator 1", + RequireCustomMerchantNumber = true, + RequireCustomTerminalNumber = false + }, + new OperatorModel + { + OperatorId = Guid.NewGuid(), + Name = "Operator 2", + RequireCustomMerchantNumber = false, + RequireCustomTerminalNumber = true + } + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(operators)); + + // Act + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert + cut.Markup.ShouldContain("Operator 1"); + cut.Markup.ShouldContain("Operator 2"); + } + + [Fact] + public void OperatorsIndex_DisplaysCustomNumberRequirements() + { + // Arrange + var operators = new List + { + new OperatorModel + { + OperatorId = Guid.NewGuid(), + Name = "Test Operator", + RequireCustomMerchantNumber = true, + RequireCustomTerminalNumber = false + } + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(operators)); + + // Act + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert + cut.Markup.ShouldContain("Custom Merchant Number"); + cut.Markup.ShouldContain("Custom Terminal Number"); + } + + [Fact] + public void OperatorsIndex_HasCorrectPageTitle() + { + // Arrange + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result>.Success(new List())); + + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsViewPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsViewPageTests.cs new file mode 100644 index 00000000..46ecee12 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Operators/OperatorsViewPageTests.cs @@ -0,0 +1,88 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Pages.Operators; +using EstateManagementUI.BlazorServer.Models; +using MediatR; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Shouldly; +using static EstateManagementUI.BlazorServer.Requests.Queries; + +namespace EstateManagementUI.BlazorServer.Tests.Pages.Operators; + +public class OperatorsViewPageTests : TestContext +{ + private readonly Mock _mockMediator; + private readonly Mock _mockNavigationManager; + + public OperatorsViewPageTests() + { + _mockMediator = new Mock(); + _mockNavigationManager = new Mock(); + + Services.AddSingleton(_mockMediator.Object); + Services.AddSingleton(_mockNavigationManager.Object); + } + + [Fact] + public void OperatorsView_RendersCorrectly() + { + // Arrange + var operatorId = Guid.NewGuid(); + var operatorModel = new OperatorModel + { + OperatorId = operatorId, + Name = "Test Operator" + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(operatorModel)); + + // Act + var cut = RenderComponent(parameters => parameters + .Add(p => p.OperatorId, operatorId)); + + // Assert + cut.Markup.ShouldContain("View Operator"); + } + + [Fact] + public void OperatorsView_DisplaysOperatorName() + { + // Arrange + var operatorId = Guid.NewGuid(); + var operatorModel = new OperatorModel + { + OperatorId = operatorId, + Name = "Test Operator" + }; + + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(operatorModel)); + + // Act + var cut = RenderComponent(parameters => parameters + .Add(p => p.OperatorId, operatorId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert + cut.Markup.ShouldContain("Test Operator"); + } + + [Fact] + public void OperatorsView_HasCorrectPageTitle() + { + // Arrange + var operatorId = Guid.NewGuid(); + _mockMediator.Setup(x => x.Send(It.IsAny(), default)) + .ReturnsAsync(Result.Success(new OperatorModel { OperatorId = operatorId })); + + // Act + var cut = RenderComponent(parameters => parameters + .Add(p => p.OperatorId, operatorId)); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Permissions/PermissionsIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Permissions/PermissionsIndexPageTests.cs new file mode 100644 index 00000000..3d4d83f7 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Permissions/PermissionsIndexPageTests.cs @@ -0,0 +1,63 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Permissions; +using EstateManagementUI.BlazorServer.Permissions; +using MediatR; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Shouldly; +using PermissionsIndex = EstateManagementUI.BlazorServer.Components.Pages.Permissions.Index; + +namespace EstateManagementUI.BlazorServer.Tests.Pages.Permissions; + +public class PermissionsIndexPageTests : TestContext +{ + private readonly Mock _mockPermissionStore; + private readonly Mock _mockNavigationManager; + private readonly Mock _mockPermissionKeyProvider; + + public PermissionsIndexPageTests() + { + _mockPermissionStore = new Mock(); + _mockNavigationManager = new Mock(); + _mockPermissionKeyProvider = new Mock(); + + _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); + + Services.AddSingleton(_mockPermissionStore.Object); + Services.AddSingleton(_mockNavigationManager.Object); + Services.AddSingleton(_mockPermissionKeyProvider.Object); + + ComponentFactories.AddStub(); + ComponentFactories.AddStub(); + } + + [Fact] + public void PermissionsIndex_RendersCorrectly() + { + // Arrange + _mockPermissionStore.Setup(x => x.GetAllRolesAsync()) + .ReturnsAsync(new List()); + + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Permission Management"); + } + + [Fact] + public void PermissionsIndex_HasCorrectPageTitle() + { + // Arrange + _mockPermissionStore.Setup(x => x.GetAllRolesAsync()) + .ReturnsAsync(new List()); + + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/PermissionsDebugPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/PermissionsDebugPageTests.cs new file mode 100644 index 00000000..bf7a9691 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/PermissionsDebugPageTests.cs @@ -0,0 +1,29 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Pages; +using Shouldly; + +namespace EstateManagementUI.BlazorServer.Tests.Pages; + +public class PermissionsDebugPageTests : TestContext +{ + [Fact] + public void PermissionsDebug_RendersCorrectly() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldNotBeNullOrEmpty(); + } + + [Fact] + public void PermissionsDebug_HasCorrectPageTitle() + { + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Reporting/ReportingIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Reporting/ReportingIndexPageTests.cs new file mode 100644 index 00000000..6b1a15ea --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Reporting/ReportingIndexPageTests.cs @@ -0,0 +1,45 @@ +using Bunit; +using EstateManagementUI.BlazorServer.Components.Permissions; +using EstateManagementUI.BlazorServer.Permissions; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Shouldly; +using ReportingIndex = EstateManagementUI.BlazorServer.Components.Pages.Reporting.Index; + +namespace EstateManagementUI.BlazorServer.Tests.Pages.Reporting; + +public class ReportingIndexPageTests : TestContext +{ + private readonly Mock _mockPermissionKeyProvider; + + public ReportingIndexPageTests() + { + _mockPermissionKeyProvider = new Mock(); + _mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key"); + + Services.AddSingleton(_mockPermissionKeyProvider.Object); + + ComponentFactories.AddStub(); + } + + [Fact] + public void ReportingIndex_RendersCorrectly() + { + // Act + var cut = RenderComponent(); + + // Assert + cut.Markup.ShouldContain("Reporting"); + } + + [Fact] + public void ReportingIndex_HasCorrectPageTitle() + { + // Act + var cut = RenderComponent(); + + // Assert + var pageTitle = cut.FindComponent(); + pageTitle.Instance.ChildContent.ShouldNotBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Permissions/PermissionKeyProviderTests.cs b/EstateManagementUI.BlazorServer.Tests/Permissions/PermissionKeyProviderTests.cs new file mode 100644 index 00000000..c94fef9b --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Permissions/PermissionKeyProviderTests.cs @@ -0,0 +1,67 @@ +using EstateManagementUI.BlazorServer.Permissions; +using Shouldly; + +namespace EstateManagementUI.BlazorServer.Tests.Permissions; + +public class PermissionKeyProviderTests +{ + [Fact] + public void GetKey_ReturnsNonEmptyKey() + { + // Arrange + var provider = new PermissionKeyProvider(); + + // Act + var key = provider.GetKey(); + + // Assert + key.ShouldNotBeNullOrEmpty(); + } + + [Fact] + public void GetKey_ReturnsSameKeyWhenCalledMultipleTimes() + { + // Arrange + var provider = new PermissionKeyProvider(); + + // Act + var key1 = provider.GetKey(); + var key2 = provider.GetKey(); + + // Assert + key1.ShouldBe(key2); + } + + [Fact] + public void RefreshKey_ChangesKey() + { + // Arrange + var provider = new PermissionKeyProvider(); + var originalKey = provider.GetKey(); + + // Act + provider.RefreshKey(); + var newKey = provider.GetKey(); + + // Assert + newKey.ShouldNotBe(originalKey); + } + + [Fact] + public void RefreshKey_GeneratesUniqueKeys() + { + // Arrange + var provider = new PermissionKeyProvider(); + var keys = new HashSet(); + + // Act + for (int i = 0; i < 100; i++) + { + provider.RefreshKey(); + keys.Add(provider.GetKey()); + } + + // Assert + keys.Count.ShouldBe(100); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Permissions/PermissionServiceTests.cs b/EstateManagementUI.BlazorServer.Tests/Permissions/PermissionServiceTests.cs new file mode 100644 index 00000000..6c8ccb6b --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Permissions/PermissionServiceTests.cs @@ -0,0 +1,224 @@ +using EstateManagementUI.BlazorServer.Permissions; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.Logging; +using Moq; +using Shouldly; +using System.Security.Claims; + +namespace EstateManagementUI.BlazorServer.Tests.Permissions; + +public class PermissionServiceTests +{ + private readonly Mock _authStateProvider; + private readonly Mock _permissionStore; + private readonly Mock> _logger; + private readonly PermissionService _permissionService; + + public PermissionServiceTests() + { + _authStateProvider = new Mock(); + _permissionStore = new Mock(); + _logger = new Mock>(); + _permissionService = new PermissionService(_authStateProvider.Object, _permissionStore.Object, _logger.Object); + } + + [Fact] + public async Task HasPermissionAsync_WithValidPermission_ReturnsTrue() + { + // Arrange + var permissions = new List + { + new Permission(PermissionSection.Merchant, PermissionFunction.View), + new Permission(PermissionSection.Merchant, PermissionFunction.Create) + }; + var role = new Role("Estate", permissions); + + var claims = new List { new Claim(ClaimTypes.Role, "Estate") }; + var identity = new ClaimsIdentity(claims, "TestAuth"); + var user = new ClaimsPrincipal(identity); + var authState = new AuthenticationState(user); + + _authStateProvider.Setup(x => x.GetAuthenticationStateAsync()).ReturnsAsync(authState); + _permissionStore.Setup(x => x.GetRoleAsync("Estate")).ReturnsAsync(role); + + // Act + var result = await _permissionService.HasPermissionAsync(PermissionSection.Merchant, PermissionFunction.View); + + // Assert + result.ShouldBeTrue(); + } + + [Fact] + public async Task HasPermissionAsync_WithInvalidPermission_ReturnsFalse() + { + // Arrange + var permissions = new List + { + new Permission(PermissionSection.Merchant, PermissionFunction.View) + }; + var role = new Role("Viewer", permissions); + + var claims = new List { new Claim(ClaimTypes.Role, "Viewer") }; + var identity = new ClaimsIdentity(claims, "TestAuth"); + var user = new ClaimsPrincipal(identity); + var authState = new AuthenticationState(user); + + _authStateProvider.Setup(x => x.GetAuthenticationStateAsync()).ReturnsAsync(authState); + _permissionStore.Setup(x => x.GetRoleAsync("Viewer")).ReturnsAsync(role); + + // Act + var result = await _permissionService.HasPermissionAsync(PermissionSection.Merchant, PermissionFunction.Create); + + // Assert + result.ShouldBeFalse(); + } + + [Fact] + public async Task HasPermissionAsync_WithUnauthenticatedUser_ReturnsFalse() + { + // Arrange + var identity = new ClaimsIdentity(); + var user = new ClaimsPrincipal(identity); + var authState = new AuthenticationState(user); + + _authStateProvider.Setup(x => x.GetAuthenticationStateAsync()).ReturnsAsync(authState); + + // Act + var result = await _permissionService.HasPermissionAsync(PermissionSection.Merchant, PermissionFunction.View); + + // Assert + result.ShouldBeFalse(); + } + + [Fact] + public async Task HasSectionAccessAsync_WithAccessToSection_ReturnsTrue() + { + // Arrange + var permissions = new List + { + new Permission(PermissionSection.Merchant, PermissionFunction.View), + new Permission(PermissionSection.Merchant, PermissionFunction.Create) + }; + var role = new Role("Estate", permissions); + + var claims = new List { new Claim(ClaimTypes.Role, "Estate") }; + var identity = new ClaimsIdentity(claims, "TestAuth"); + var user = new ClaimsPrincipal(identity); + var authState = new AuthenticationState(user); + + _authStateProvider.Setup(x => x.GetAuthenticationStateAsync()).ReturnsAsync(authState); + _permissionStore.Setup(x => x.GetRoleAsync("Estate")).ReturnsAsync(role); + + // Act + var result = await _permissionService.HasSectionAccessAsync(PermissionSection.Merchant); + + // Assert + result.ShouldBeTrue(); + } + + [Fact] + public async Task HasSectionAccessAsync_WithoutAccessToSection_ReturnsFalse() + { + // Arrange + var permissions = new List + { + new Permission(PermissionSection.Merchant, PermissionFunction.View) + }; + var role = new Role("Viewer", permissions); + + var claims = new List { new Claim(ClaimTypes.Role, "Viewer") }; + var identity = new ClaimsIdentity(claims, "TestAuth"); + var user = new ClaimsPrincipal(identity); + var authState = new AuthenticationState(user); + + _authStateProvider.Setup(x => x.GetAuthenticationStateAsync()).ReturnsAsync(authState); + _permissionStore.Setup(x => x.GetRoleAsync("Viewer")).ReturnsAsync(role); + + // Act + var result = await _permissionService.HasSectionAccessAsync(PermissionSection.Operator); + + // Assert + result.ShouldBeFalse(); + } + + [Fact] + public async Task GetUserPermissionsAsync_WithValidRole_ReturnsPermissions() + { + // Arrange + var permissions = new List + { + new Permission(PermissionSection.Merchant, PermissionFunction.View), + new Permission(PermissionSection.Operator, PermissionFunction.View) + }; + var role = new Role("Viewer", permissions); + + var claims = new List { new Claim(ClaimTypes.Role, "Viewer") }; + var identity = new ClaimsIdentity(claims, "TestAuth"); + var user = new ClaimsPrincipal(identity); + var authState = new AuthenticationState(user); + + _authStateProvider.Setup(x => x.GetAuthenticationStateAsync()).ReturnsAsync(authState); + _permissionStore.Setup(x => x.GetRoleAsync("Viewer")).ReturnsAsync(role); + + // Act + var result = await _permissionService.GetUserPermissionsAsync(); + + // Assert + result.ShouldNotBeNull(); + result.Count.ShouldBe(2); + } + + [Fact] + public async Task GetUserPermissionsAsync_WithNoRole_ReturnsEmptyList() + { + // Arrange + var claims = new List(); + var identity = new ClaimsIdentity(claims, "TestAuth"); + var user = new ClaimsPrincipal(identity); + var authState = new AuthenticationState(user); + + _authStateProvider.Setup(x => x.GetAuthenticationStateAsync()).ReturnsAsync(authState); + + // Act + var result = await _permissionService.GetUserPermissionsAsync(); + + // Assert + result.ShouldNotBeNull(); + result.ShouldBeEmpty(); + } + + [Fact] + public async Task GetUserRoleAsync_WithValidRole_ReturnsRoleName() + { + // Arrange + var claims = new List { new Claim(ClaimTypes.Role, "Estate") }; + var identity = new ClaimsIdentity(claims, "TestAuth"); + var user = new ClaimsPrincipal(identity); + var authState = new AuthenticationState(user); + + _authStateProvider.Setup(x => x.GetAuthenticationStateAsync()).ReturnsAsync(authState); + + // Act + var result = await _permissionService.GetUserRoleAsync(); + + // Assert + result.ShouldBe("Estate"); + } + + [Fact] + public async Task GetUserRoleAsync_WithUnauthenticatedUser_ReturnsNull() + { + // Arrange + var identity = new ClaimsIdentity(); + var user = new ClaimsPrincipal(identity); + var authState = new AuthenticationState(user); + + _authStateProvider.Setup(x => x.GetAuthenticationStateAsync()).ReturnsAsync(authState); + + // Act + var result = await _permissionService.GetUserRoleAsync(); + + // Assert + result.ShouldBeNull(); + } +} diff --git a/EstateManagementUI.BlazorServer.Tests/Services/TestDataStoreTests.cs b/EstateManagementUI.BlazorServer.Tests/Services/TestDataStoreTests.cs new file mode 100644 index 00000000..7a2c6eaf --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Services/TestDataStoreTests.cs @@ -0,0 +1,291 @@ +using EstateManagementUI.BlazorServer.Models; +using EstateManagementUI.BlazorServer.Services; +using Shouldly; + +namespace EstateManagementUI.BlazorServer.Tests.Services; + +public class TestDataStoreTests +{ + [Fact] + public void Constructor_InitializesWithDefaultData() + { + // Act + var dataStore = new TestDataStore(); + var testEstateId = new Guid("11111111-1111-1111-1111-111111111111"); + var estate = dataStore.GetEstate(testEstateId); + + // Assert + estate.ShouldNotBeNull(); + estate.EstateName.ShouldBe("Test Estate"); + } + + [Fact] + public void GetEstate_WithNonExistentId_ReturnsUnknownEstate() + { + // Arrange + var dataStore = new TestDataStore(); + var nonExistentId = Guid.NewGuid(); + + // Act + var estate = dataStore.GetEstate(nonExistentId); + + // Assert + estate.ShouldNotBeNull(); + estate.EstateName.ShouldBe("Unknown Estate"); + estate.Reference.ShouldBe("Unknown"); + } + + [Fact] + public void SetEstate_AddsNewEstate() + { + // Arrange + var dataStore = new TestDataStore(); + var estateId = Guid.NewGuid(); + var estate = new EstateModel + { + EstateId = estateId, + EstateName = "New Estate", + Reference = "NEW001" + }; + + // Act + dataStore.SetEstate(estate); + var result = dataStore.GetEstate(estateId); + + // Assert + result.ShouldNotBeNull(); + result.EstateName.ShouldBe("New Estate"); + result.Reference.ShouldBe("NEW001"); + } + + [Fact] + public void GetMerchants_WithExistingEstate_ReturnsMerchantList() + { + // Arrange + var dataStore = new TestDataStore(); + var testEstateId = new Guid("11111111-1111-1111-1111-111111111111"); + + // Act + var merchants = dataStore.GetMerchants(testEstateId); + + // Assert + merchants.ShouldNotBeNull(); + merchants.ShouldNotBeEmpty(); + } + + [Fact] + public void GetMerchants_WithNonExistentEstate_ReturnsEmptyList() + { + // Arrange + var dataStore = new TestDataStore(); + var nonExistentId = Guid.NewGuid(); + + // Act + var merchants = dataStore.GetMerchants(nonExistentId); + + // Assert + merchants.ShouldNotBeNull(); + merchants.ShouldBeEmpty(); + } + + [Fact] + public void AddMerchant_AddsNewMerchant() + { + // Arrange + var dataStore = new TestDataStore(); + var estateId = Guid.NewGuid(); + var merchantId = Guid.NewGuid(); + var merchant = new MerchantModel + { + MerchantId = merchantId, + MerchantName = "Test Merchant", + MerchantReference = "MERCH001" + }; + + // Act + dataStore.AddMerchant(estateId, merchant); + var result = dataStore.GetMerchant(estateId, merchantId); + + // Assert + result.ShouldNotBeNull(); + result.MerchantName.ShouldBe("Test Merchant"); + result.MerchantReference.ShouldBe("MERCH001"); + } + + [Fact] + public void UpdateMerchant_UpdatesExistingMerchant() + { + // Arrange + var dataStore = new TestDataStore(); + var estateId = Guid.NewGuid(); + var merchantId = Guid.NewGuid(); + var merchant = new MerchantModel + { + MerchantId = merchantId, + MerchantName = "Original Name", + MerchantReference = "ORIG001" + }; + dataStore.AddMerchant(estateId, merchant); + + // Act + merchant.MerchantName = "Updated Name"; + dataStore.UpdateMerchant(estateId, merchant); + var result = dataStore.GetMerchant(estateId, merchantId); + + // Assert + result.ShouldNotBeNull(); + result.MerchantName.ShouldBe("Updated Name"); + } + + [Fact] + public void RemoveMerchant_RemovesExistingMerchant() + { + // Arrange + var dataStore = new TestDataStore(); + var estateId = Guid.NewGuid(); + var merchantId = Guid.NewGuid(); + var merchant = new MerchantModel + { + MerchantId = merchantId, + MerchantName = "Test Merchant", + MerchantReference = "MERCH001" + }; + dataStore.AddMerchant(estateId, merchant); + + // Act + dataStore.RemoveMerchant(estateId, merchantId); + var result = dataStore.GetMerchant(estateId, merchantId); + + // Assert + result.ShouldBeNull(); + } + + [Fact] + public void GetOperators_WithExistingEstate_ReturnsOperatorList() + { + // Arrange + var dataStore = new TestDataStore(); + var testEstateId = new Guid("11111111-1111-1111-1111-111111111111"); + + // Act + var operators = dataStore.GetOperators(testEstateId); + + // Assert + operators.ShouldNotBeNull(); + operators.ShouldNotBeEmpty(); + } + + [Fact] + public void AddOperator_AddsNewOperator() + { + // Arrange + var dataStore = new TestDataStore(); + var estateId = Guid.NewGuid(); + var operatorId = Guid.NewGuid(); + var operatorModel = new OperatorModel + { + OperatorId = operatorId, + Name = "Test Operator", + RequireCustomMerchantNumber = true, + RequireCustomTerminalNumber = false + }; + + // Act + dataStore.AddOperator(estateId, operatorModel); + var result = dataStore.GetOperator(estateId, operatorId); + + // Assert + result.ShouldNotBeNull(); + result.Name.ShouldBe("Test Operator"); + result.RequireCustomMerchantNumber.ShouldBeTrue(); + } + + [Fact] + public void UpdateOperator_UpdatesExistingOperator() + { + // Arrange + var dataStore = new TestDataStore(); + var estateId = Guid.NewGuid(); + var operatorId = Guid.NewGuid(); + var operatorModel = new OperatorModel + { + OperatorId = operatorId, + Name = "Original Operator", + RequireCustomMerchantNumber = false + }; + dataStore.AddOperator(estateId, operatorModel); + + // Act + operatorModel.Name = "Updated Operator"; + operatorModel.RequireCustomMerchantNumber = true; + dataStore.UpdateOperator(estateId, operatorModel); + var result = dataStore.GetOperator(estateId, operatorId); + + // Assert + result.ShouldNotBeNull(); + result.Name.ShouldBe("Updated Operator"); + result.RequireCustomMerchantNumber.ShouldBeTrue(); + } + + [Fact] + public void RemoveOperator_RemovesExistingOperator() + { + // Arrange + var dataStore = new TestDataStore(); + var estateId = Guid.NewGuid(); + var operatorId = Guid.NewGuid(); + var operatorModel = new OperatorModel + { + OperatorId = operatorId, + Name = "Test Operator" + }; + dataStore.AddOperator(estateId, operatorModel); + + // Act + dataStore.RemoveOperator(estateId, operatorId); + var result = dataStore.GetOperator(estateId, operatorId); + + // Assert + result.ShouldBeNull(); + } + + [Fact] + public void GetContracts_WithExistingEstate_ReturnsContractList() + { + // Arrange + var dataStore = new TestDataStore(); + var testEstateId = new Guid("11111111-1111-1111-1111-111111111111"); + + // Act + var contracts = dataStore.GetContracts(testEstateId); + + // Assert + contracts.ShouldNotBeNull(); + contracts.ShouldNotBeEmpty(); + } + + [Fact] + public void Reset_ClearsAllDataAndReinitializes() + { + // Arrange + var dataStore = new TestDataStore(); + var estateId = Guid.NewGuid(); + var merchant = new MerchantModel + { + MerchantId = Guid.NewGuid(), + MerchantName = "Test Merchant" + }; + dataStore.AddMerchant(estateId, merchant); + + // Act + dataStore.Reset(); + var merchants = dataStore.GetMerchants(estateId); + var testEstateId = new Guid("11111111-1111-1111-1111-111111111111"); + var defaultEstate = dataStore.GetEstate(testEstateId); + + // Assert + merchants.ShouldBeEmpty(); + defaultEstate.ShouldNotBeNull(); + defaultEstate.EstateName.ShouldBe("Test Estate"); + } +} diff --git a/EstateManagementUI.sln b/EstateManagementUI.sln index f63f518e..605dfc64 100644 --- a/EstateManagementUI.sln +++ b/EstateManagementUI.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.1.11312.151 d18.0 +VisualStudioVersion = 18.1.11312.151 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EstateManagementUI", "EstateManagementUI\EstateManagementUI.csproj", "{2EA6492F-70A7-41EB-B816-A9854C0DB47B}" EndProject @@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EstateManagementUI.UITests" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EstateManagementUI.BlazorServer", "EstateManagementUI.BlazorServer\EstateManagementUI.BlazorServer.csproj", "{C52FE3F3-E999-4A12-A20B-0D5C6DA34AA9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EstateManagementUI.BlazorServer.Tests", "EstateManagementUI.BlazorServer.Tests\EstateManagementUI.BlazorServer.Tests.csproj", "{55431CBE-C879-47B9-9607-A5822DE9B856}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -129,6 +131,18 @@ Global {C52FE3F3-E999-4A12-A20B-0D5C6DA34AA9}.Release|x64.Build.0 = Release|Any CPU {C52FE3F3-E999-4A12-A20B-0D5C6DA34AA9}.Release|x86.ActiveCfg = Release|Any CPU {C52FE3F3-E999-4A12-A20B-0D5C6DA34AA9}.Release|x86.Build.0 = Release|Any CPU + {55431CBE-C879-47B9-9607-A5822DE9B856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55431CBE-C879-47B9-9607-A5822DE9B856}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55431CBE-C879-47B9-9607-A5822DE9B856}.Debug|x64.ActiveCfg = Debug|Any CPU + {55431CBE-C879-47B9-9607-A5822DE9B856}.Debug|x64.Build.0 = Debug|Any CPU + {55431CBE-C879-47B9-9607-A5822DE9B856}.Debug|x86.ActiveCfg = Debug|Any CPU + {55431CBE-C879-47B9-9607-A5822DE9B856}.Debug|x86.Build.0 = Debug|Any CPU + {55431CBE-C879-47B9-9607-A5822DE9B856}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55431CBE-C879-47B9-9607-A5822DE9B856}.Release|Any CPU.Build.0 = Release|Any CPU + {55431CBE-C879-47B9-9607-A5822DE9B856}.Release|x64.ActiveCfg = Release|Any CPU + {55431CBE-C879-47B9-9607-A5822DE9B856}.Release|x64.Build.0 = Release|Any CPU + {55431CBE-C879-47B9-9607-A5822DE9B856}.Release|x86.ActiveCfg = Release|Any CPU + {55431CBE-C879-47B9-9607-A5822DE9B856}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -141,6 +155,7 @@ Global {661A3E05-A174-4930-8171-5EDC19D1D267} = {05420FAB-1C50-4AAD-9E7C-2A86184019B7} {245519C4-A8C6-40A4-AE09-9431DC1845EF} = {05420FAB-1C50-4AAD-9E7C-2A86184019B7} {CC0964BD-CDF0-4976-AF28-B28800FDC286} = {E7671C23-F30C-471A-A9EF-AC85DC607B55} + {55431CBE-C879-47B9-9607-A5822DE9B856} = {E7671C23-F30C-471A-A9EF-AC85DC607B55} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C5AE6DA4-B167-44E5-9B63-61C7E8D1BC9F}