namespace WalletService.UnitTests.Application; using FluentAssertions; using Microsoft.Extensions.Logging; using NSubstitute; using WalletService.API.Application.Commands; using WalletService.Domain.AggregatesModel.WalletAggregate; using WalletService.Domain.Exceptions; using WalletService.Domain.SeedWork; using Xunit; public class EscrowCommandHandlersTests { private readonly IWalletRepository _walletRepository; private readonly ILogger _createLogger; private readonly ILogger _executeLogger; private readonly IUnitOfWork _unitOfWork; public EscrowCommandHandlersTests() { _walletRepository = Substitute.For(); _unitOfWork = Substitute.For(); _walletRepository.UnitOfWork.Returns(_unitOfWork); _unitOfWork.SaveEntitiesAsync(Arg.Any()).Returns(true); _createLogger = Substitute.For>(); _executeLogger = Substitute.For>(); } [Fact] public async Task CreateHold_ValidCommand_ShouldSucceed() { // Arrange var walletId = Guid.NewGuid(); var userId = Guid.NewGuid(); var wallet = new Wallet(userId, CurrencyType.VND); wallet.Deposit(1000m, CurrencyType.VND, "Init"); // Mock repository to return our wallet when searching by UserId (CreateHoldCommand uses UserId) _walletRepository.GetByUserIdAsync(userId).Returns(wallet); var handler = new CreateHoldCommandHandler(_walletRepository, _createLogger); var command = new CreateHoldCommand( userId, 100m, "VND", "CAMPAIGN", Guid.NewGuid(), "Test Hold"); // Act var result = await handler.Handle(command, CancellationToken.None); // Assert result.Should().NotBeNull(); result.RemainingAmount.Should().Be(100m); wallet.GetBalance(CurrencyType.VND).Should().Be(900m); _walletRepository.Received(1).Update(wallet); await _unitOfWork.Received(1).SaveEntitiesAsync(Arg.Any()); } [Fact] public async Task ExecuteHold_ValidCommand_ShouldSucceed() { // Arrange var userId = Guid.NewGuid(); var wallet = new Wallet(userId, CurrencyType.VND); wallet.Deposit(1000m, CurrencyType.VND, "Init"); var hold = wallet.Hold(100m, CurrencyType.VND, "REF", Guid.NewGuid(), "Desc"); // Mock repository to return wallet by ID (ExecuteHoldCommand uses WalletId) _walletRepository.GetByIdAsync(wallet.Id).Returns(wallet); var handler = new ExecuteHoldCommandHandler(_walletRepository, _executeLogger); var command = new ExecuteHoldCommand( wallet.Id, hold.Id, 40m, "EXEC_REF"); // Act var result = await handler.Handle(command, CancellationToken.None); // Assert result.ExecutedAmount.Should().Be(40m); result.RemainingAmount.Should().Be(60m); await _unitOfWork.Received(1).SaveEntitiesAsync(Arg.Any()); } [Fact] public async Task ExecuteHold_InsufficientHoldAmount_ShouldThrow() { // Arrange var userId = Guid.NewGuid(); var wallet = new Wallet(userId, CurrencyType.VND); wallet.Deposit(1000m, CurrencyType.VND, "Init"); var hold = wallet.Hold(100m, CurrencyType.VND, "REF", Guid.NewGuid(), "Desc"); _walletRepository.GetByIdAsync(wallet.Id).Returns(wallet); var handler = new ExecuteHoldCommandHandler(_walletRepository, _executeLogger); var command = new ExecuteHoldCommand( wallet.Id, hold.Id, 150m, // Try to execute more than held "EXEC_REF"); // Act & Assert await Assert.ThrowsAsync(() => handler.Handle(command, CancellationToken.None)); } }