using FluentAssertions; using Microsoft.Extensions.Logging; using NSubstitute; using PromotionService.API.Application.Commands; using PromotionService.API.Application.Services; using PromotionService.Domain.AggregatesModel.CampaignAggregate; using PromotionService.Domain.Exceptions; using PromotionService.Domain.SeedWork; using Xunit; namespace PromotionService.UnitTests.Application; /// /// EN: Unit tests for CreateCampaignCommandHandler. /// VI: Unit tests cho CreateCampaignCommandHandler. /// public class CreateCampaignCommandHandlerTests { private readonly ICampaignRepository _campaignRepository; private readonly IWalletServiceClient _walletService; private readonly ILogger _logger; private readonly CreateCampaignCommandHandler _handler; public CreateCampaignCommandHandlerTests() { _campaignRepository = Substitute.For(); _walletService = Substitute.For(); _logger = Substitute.For>(); // Setup UnitOfWork var unitOfWork = Substitute.For(); unitOfWork.SaveEntitiesAsync(Arg.Any()).Returns(true); _campaignRepository.UnitOfWork.Returns(unitOfWork); _handler = new CreateCampaignCommandHandler( _campaignRepository, _walletService, _logger); } private static CreateCampaignCommand CreateValidCommand() => new CreateCampaignCommand( MerchantId: Guid.NewGuid(), MerchantWalletId: Guid.NewGuid(), Name: "Test Campaign", Description: "Test Description", BackingAssetType: "Currency", BackingAssetCode: "VND", FaceValue: 100_000m, AcquisitionType: "Free", AcquisitionPrice: 0m, TotalVouchers: 100, StartDate: DateTime.UtcNow.AddDays(1), EndDate: DateTime.UtcNow.AddDays(30), VoucherValidityDays: 30, MaxPerUser: 1); [Fact] public async Task Handle_ValidCommand_CreatesCampaignAndReturnsDto() { // Arrange var command = CreateValidCommand(); var holdResult = new HoldResult( HoldId: Guid.NewGuid(), WalletId: command.MerchantWalletId, Amount: command.FaceValue * command.TotalVouchers, CurrencyType: command.BackingAssetCode, ReferenceType: "CAMPAIGN", ReferenceId: Guid.NewGuid(), Status: "Active"); _walletService.CreateHoldAsync( Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(holdResult); _campaignRepository.Add(Arg.Any()) .Returns(callInfo => callInfo.Arg()); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Should().NotBeNull(); result.Name.Should().Be(command.Name); result.TotalVouchers.Should().Be(command.TotalVouchers); result.Status.Should().Be("Draft"); // Verify repository called _campaignRepository.Received(1).Add(Arg.Is(c => c.Name == command.Name)); await _campaignRepository.UnitOfWork.Received(1).SaveEntitiesAsync(Arg.Any()); } [Fact] public async Task Handle_ValidCommand_CallsWalletServiceToCreateEscrow() { // Arrange var command = CreateValidCommand(); var expectedEscrowAmount = command.FaceValue * command.TotalVouchers; _walletService.CreateHoldAsync( Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(new HoldResult( Guid.NewGuid(), command.MerchantWalletId, expectedEscrowAmount, "VND", "CAMPAIGN", Guid.NewGuid(), "Active")); _campaignRepository.Add(Arg.Any()) .Returns(callInfo => callInfo.Arg()); // Act await _handler.Handle(command, CancellationToken.None); // Assert - Verify wallet service was called with correct escrow amount await _walletService.Received(1).CreateHoldAsync( command.MerchantWalletId, expectedEscrowAmount, command.BackingAssetCode, "CAMPAIGN", Arg.Any(), Arg.Is(s => s.Contains(command.Name)), Arg.Any()); } [Fact] public async Task Handle_InvalidAcquisitionType_ThrowsDomainException() { // Arrange var command = new CreateCampaignCommand( MerchantId: Guid.NewGuid(), MerchantWalletId: Guid.NewGuid(), Name: "Test Campaign", Description: null, BackingAssetType: "Currency", BackingAssetCode: "VND", FaceValue: 100_000m, AcquisitionType: "InvalidType", // Invalid! AcquisitionPrice: 0m, TotalVouchers: 100, StartDate: DateTime.UtcNow.AddDays(1), EndDate: DateTime.UtcNow.AddDays(30)); // Act & Assert var act = async () => await _handler.Handle(command, CancellationToken.None); await act.Should().ThrowAsync() .WithMessage("*Invalid acquisition type*"); // Verify wallet service was NOT called await _walletService.DidNotReceive().CreateHoldAsync( Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); } }