Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<CoverletExcludeByFile Include="**/*.razor*" />
</ItemGroup>

</Project>
4 changes: 4 additions & 0 deletions EstateManagementUI.BlazorServer.Tests/Pages/BaseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Bunit.TestDoubles;
using EstateManagementUI.BlazorServer.Components.Permissions;
using EstateManagementUI.BlazorServer.Permissions;
using EstateManagementUI.BlazorServer.UIServices;
using MediatR;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
Expand All @@ -20,6 +21,7 @@ protected BaseTest() {
this._mockPermissionService = new Mock<IPermissionService>();
this._mockPermissionStore = new Mock<IPermissionStore>();
this._fakeNavigationManager = new FakeNavigationManager();
this.EstateUIService = new Mock<IEstateUIService>();

this._mockPermissionKeyProvider.Setup(x => x.GetKey()).Returns("test-key");
this._mockPermissionService.Setup(x => x.HasPermissionAsync(It.IsAny<PermissionSection>(), It.IsAny<PermissionFunction>())).ReturnsAsync(true);
Expand All @@ -31,6 +33,7 @@ protected BaseTest() {
this.Services.AddSingleton(this._mockPermissionService.Object);
this.Services.AddSingleton(this._mockAuthStateProvider.Object);
this.Services.AddSingleton(this._mockPermissionStore.Object);
this.Services.AddSingleton(this.EstateUIService.Object);


// Add required permission components that render their children
Expand All @@ -50,6 +53,7 @@ protected BaseTest() {
protected readonly Mock<AuthenticationStateProvider> _mockAuthStateProvider;
protected readonly Mock<IPermissionStore> _mockPermissionStore;
protected readonly FakeNavigationManager _fakeNavigationManager;
protected readonly Mock<IEstateUIService> EstateUIService;

/// <summary>
/// Minimal test double for NavigationManager.
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EstateManagementUI.BlazorServer.UIServices;
using EstateManagementUI.BusinessLogic.Models;
using EstateManagementUI.BusinessLogic.Requests;
using Moq;
using Shouldly;
using SimpleResults;
using Xunit;
using MediatR;

namespace EstateManagementUI.BlazorServer.Tests.UIServices
{
public class EstateUIServiceTests
{
private readonly Mock<IMediator> _mockMediator;
private readonly EstateUIService _service;

public EstateUIServiceTests()
{
_mockMediator = new Mock<IMediator>();
_service = new EstateUIService(_mockMediator.Object);
}

[Fact]
public async Task LoadEstate_ReturnsMappedModel_WhenAllQueriesSucceed()
{
// Arrange
var estateId = Guid.NewGuid();
var correlationId = CorrelationIdHelper.New();

var bizEstate = new BusinessLogic.Models.EstateModel
{
EstateId = estateId,
EstateName = "Test Estate",
Reference = "REF001",
Merchants = new List<BusinessLogic.Models.EstateMerchantModel> { new() { MerchantId = Guid.NewGuid(), Name = "M1", Reference = "R1" } },
Contracts = new List<BusinessLogic.Models.EstateContractModel> { new() { ContractId = Guid.NewGuid(), Name = "C1", OperatorName = "Op" } },
Operators = new List<BusinessLogic.Models.EstateOperatorModel> { new() { OperatorId = Guid.NewGuid(), Name = "OpAssigned" } },
Users = new List<BusinessLogic.Models.EstateUserModel> { new() { UserId = Guid.NewGuid(), EmailAddress = "u@x" } }
};

var recentMerchants = new List<BusinessLogic.Models.RecentMerchantsModel>
{
new() { MerchantId = Guid.NewGuid(), Name = "RecentM", Reference = "RM", CreatedDateTime = DateTime.UtcNow }
};

var recentContracts = new List<BusinessLogic.Models.RecentContractModel>
{
new() { ContractId = Guid.NewGuid(), Description = "RecentC", OperatorName = "Op" }
};

var assignedOperators = new List<BusinessLogic.Models.OperatorModel>
{
new() { OperatorId = bizEstate.Operators!.First().OperatorId, Name = "OpAssigned" }
};

var allOperators = new List<BusinessLogic.Models.OperatorDropDownModel>
{
new() { OperatorId = Guid.NewGuid(), OperatorName = "OpA" },
new() { OperatorId = assignedOperators[0].OperatorId, OperatorName = "OpAssigned" }
};

_mockMediator.Setup(m => m.Send(It.IsAny<EstateQueries.GetEstateQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(bizEstate));
_mockMediator.Setup(m => m.Send(It.IsAny<MerchantQueries.GetRecentMerchantsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(recentMerchants));
_mockMediator.Setup(m => m.Send(It.IsAny<ContractQueries.GetRecentContractsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(recentContracts));
_mockMediator.Setup(m => m.Send(It.IsAny<EstateQueries.GetAssignedOperatorsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(assignedOperators));
_mockMediator.Setup(m => m.Send(It.IsAny<OperatorQueries.GetOperatorsForDropDownQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(allOperators));

// Act
var result = await _service.LoadEstate(correlationId, estateId);

// Assert
result.IsSuccess.ShouldBeTrue();
var model = result.Data!;
model.EstateId.ShouldBe(estateId);
model.EstateName.ShouldBe("Test Estate");
model.MerchantCount.ShouldBe(1);
model.ContractCount.ShouldBe(1);
model.UserCount.ShouldBe(1);
model.AllOperators.ShouldNotBeNull();
model.AllOperators.ShouldContain(op => op.OperatorName == "OpA");
model.AssignedOperators.ShouldNotBeEmpty();
model.RecentMerchants.ShouldNotBeEmpty();
model.RecentContracts.ShouldNotBeEmpty();
}

[Fact]
public async Task LoadEstate_ReturnsFailure_WhenEstateQueryFails()
{
// Arrange
var estateId = Guid.NewGuid();
var correlationId = CorrelationIdHelper.New();

_mockMediator.Setup(m => m.Send(It.IsAny<EstateQueries.GetEstateQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Failure("estate fail"));

// Provide success for other queries so the service behaviour is isolated
_mockMediator.Setup(m => m.Send(It.IsAny<MerchantQueries.GetRecentMerchantsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<BusinessLogic.Models.RecentMerchantsModel>()));
_mockMediator.Setup(m => m.Send(It.IsAny<ContractQueries.GetRecentContractsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<BusinessLogic.Models.RecentContractModel>()));
_mockMediator.Setup(m => m.Send(It.IsAny<EstateQueries.GetAssignedOperatorsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<BusinessLogic.Models.OperatorModel>()));
_mockMediator.Setup(m => m.Send(It.IsAny<OperatorQueries.GetOperatorsForDropDownQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<BusinessLogic.Models.OperatorDropDownModel>()));

// Act
var result = await _service.LoadEstate(correlationId, estateId);

// Assert
result.IsFailed.ShouldBeTrue();
}

[Fact]
public async Task LoadEstate_ReturnsFailure_WhenRecentMerchantQueryFails()
{
// Arrange
var estateId = Guid.NewGuid();
var correlationId = CorrelationIdHelper.New();

_mockMediator.Setup(m => m.Send(It.IsAny<EstateQueries.GetEstateQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success());
_mockMediator.Setup(m => m.Send(It.IsAny<MerchantQueries.GetRecentMerchantsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Failure());
_mockMediator.Setup(m => m.Send(It.IsAny<ContractQueries.GetRecentContractsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<BusinessLogic.Models.RecentContractModel>()));
_mockMediator.Setup(m => m.Send(It.IsAny<EstateQueries.GetAssignedOperatorsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<BusinessLogic.Models.OperatorModel>()));
_mockMediator.Setup(m => m.Send(It.IsAny<OperatorQueries.GetOperatorsForDropDownQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<BusinessLogic.Models.OperatorDropDownModel>()));

// Act
var result = await _service.LoadEstate(correlationId, estateId);

// Assert
result.IsFailed.ShouldBeTrue();
}

[Fact]
public async Task LoadEstate_ReturnsFailure_WhenRecentContractsQueryFails()
{
// Arrange
var estateId = Guid.NewGuid();
var correlationId = CorrelationIdHelper.New();

_mockMediator.Setup(m => m.Send(It.IsAny<EstateQueries.GetEstateQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new EstateModel()));
_mockMediator.Setup(m => m.Send(It.IsAny<MerchantQueries.GetRecentMerchantsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<RecentMerchantsModel>()));
_mockMediator.Setup(m => m.Send(It.IsAny<ContractQueries.GetRecentContractsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Failure());
_mockMediator.Setup(m => m.Send(It.IsAny<EstateQueries.GetAssignedOperatorsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<BusinessLogic.Models.OperatorModel>()));
_mockMediator.Setup(m => m.Send(It.IsAny<OperatorQueries.GetOperatorsForDropDownQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<BusinessLogic.Models.OperatorDropDownModel>()));

// Act
var result = await _service.LoadEstate(correlationId, estateId);

// Assert
result.IsFailed.ShouldBeTrue();
}

[Fact]
public async Task LoadEstate_ReturnsFailure_WhenGetAssignedOperatorsQueryFails()
{
// Arrange
var estateId = Guid.NewGuid();
var correlationId = CorrelationIdHelper.New();

_mockMediator.Setup(m => m.Send(It.IsAny<EstateQueries.GetEstateQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new EstateModel()));
_mockMediator.Setup(m => m.Send(It.IsAny<MerchantQueries.GetRecentMerchantsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<RecentMerchantsModel>()));
_mockMediator.Setup(m => m.Send(It.IsAny<ContractQueries.GetRecentContractsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<RecentContractModel>()));
_mockMediator.Setup(m => m.Send(It.IsAny<EstateQueries.GetAssignedOperatorsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Failure());
_mockMediator.Setup(m => m.Send(It.IsAny<OperatorQueries.GetOperatorsForDropDownQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<BusinessLogic.Models.OperatorDropDownModel>()));

// Act
var result = await _service.LoadEstate(correlationId, estateId);

// Assert
result.IsFailed.ShouldBeTrue();
}

[Fact]
public async Task LoadEstate_ReturnsFailure_WhenGetOperatorsForDropDownQueryFails()
{
// Arrange
var estateId = Guid.NewGuid();
var correlationId = CorrelationIdHelper.New();

_mockMediator.Setup(m => m.Send(It.IsAny<EstateQueries.GetEstateQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new EstateModel()));
_mockMediator.Setup(m => m.Send(It.IsAny<MerchantQueries.GetRecentMerchantsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<RecentMerchantsModel>()));
_mockMediator.Setup(m => m.Send(It.IsAny<ContractQueries.GetRecentContractsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<RecentContractModel>()));
_mockMediator.Setup(m => m.Send(It.IsAny<EstateQueries.GetAssignedOperatorsQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success(new List<OperatorModel>()));
_mockMediator.Setup(m => m.Send(It.IsAny<OperatorQueries.GetOperatorsForDropDownQuery>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Failure());

// Act
var result = await _service.LoadEstate(correlationId, estateId);

// Assert
result.IsFailed.ShouldBeTrue();
}

[Fact]
public async Task AddOperatorToEstate_CallsMediatorWithAddCommand_OnSuccess()
{
// Arrange
var estateId = Guid.NewGuid();
var correlationId = CorrelationIdHelper.New();
var operatorId = Guid.NewGuid();
var operatorIdString = operatorId.ToString();

_mockMediator.Setup(m => m.Send(It.IsAny<EstateCommands.AddOperatorToEstateCommand>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success);

// Act
var result = await _service.AddOperatorToEstate(correlationId, estateId, operatorIdString);

// Assert
result.IsSuccess.ShouldBeTrue();
_mockMediator.Verify(m => m.Send(It.Is<EstateCommands.AddOperatorToEstateCommand>(c =>
c.EstateId == estateId && c.OperatorId == operatorId), It.IsAny<CancellationToken>()), Times.Once);
}

[Fact]
public async Task AddOperatorToEstate_ReturnsFailure_WhenMediatorFails()
{
// Arrange
var estateId = Guid.NewGuid();
var correlationId = CorrelationIdHelper.New();
var operatorId = Guid.NewGuid();

_mockMediator.Setup(m => m.Send(It.IsAny<EstateCommands.AddOperatorToEstateCommand>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Failure);

// Act
var result = await _service.AddOperatorToEstate(correlationId, estateId, operatorId.ToString());

// Assert
result.IsFailed.ShouldBeTrue();
}

[Fact]
public async Task RemoveOperatorFromEstate_ShouldCallRemoveCommand()
{
// Arrange
var estateId = Guid.NewGuid();
var correlationId = CorrelationIdHelper.New();
var operatorId = Guid.NewGuid();

_mockMediator.Setup(m => m.Send(It.IsAny<EstateCommands.RemoveOperatorFromEstateCommand>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success);

// Act
var result = await _service.RemoveOperatorFromEstate(correlationId, estateId, operatorId);

// Assert - the service should send a RemoveOperatorFromEstateCommand
result.IsSuccess.ShouldBeTrue();
_mockMediator.Verify(m => m.Send(It.Is<EstateCommands.RemoveOperatorFromEstateCommand>(c =>
c.EstateId == estateId && c.OperatorId == operatorId), It.IsAny<CancellationToken>()), Times.Once);
}


[Fact]
public async Task RemoveOperatorFromEstate_ReturnsFailure_WhenMediatorFails()
{
// Arrange
var estateId = Guid.NewGuid();
var correlationId = CorrelationIdHelper.New();
var operatorId = Guid.NewGuid();

_mockMediator.Setup(m => m.Send(It.IsAny<EstateCommands.RemoveOperatorFromEstateCommand>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Failure);

// Act
var result = await _service.RemoveOperatorFromEstate(correlationId, estateId, operatorId);

// Assert - the service should send a RemoveOperatorFromEstateCommand
result.IsFailed.ShouldBeTrue();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ private async Task HandleAddProduct()
CloseAddProductModal();

// Small delay so user sees confirmation (adjust duration as needed)
await Task.Delay(2500);
await this.WaitOnUIRefresh();

await LoadContract();

Expand Down Expand Up @@ -196,7 +196,7 @@ private async Task HandleAddFee()
CloseAddFeeModal();

// Small delay so user sees confirmation (adjust duration as needed)
await Task.Delay(2500);
await this.WaitOnUIRefresh();

await LoadContract();

Expand Down Expand Up @@ -252,9 +252,9 @@ private async Task RemoveFee(Guid productId, Guid feeId)
if (result.IsSuccess)
{
successMessage = "Transaction fee removed successfully";

// Small delay so user sees confirmation (adjust duration as needed)
await Task.Delay(2500);
await this.WaitOnUIRefresh();

await LoadContract();
}
Expand Down
Loading
Loading