Files
pos-system/services/promotion-service-net/tests/PromotionService.UnitTests/Domain/CampaignAggregateTests.cs

327 lines
9.4 KiB
C#

using FluentAssertions;
using PromotionService.Domain.AggregatesModel.CampaignAggregate;
using PromotionService.Domain.Events;
using PromotionService.Domain.Exceptions;
using Xunit;
namespace PromotionService.UnitTests.Domain;
/// <summary>
/// EN: Unit tests for Campaign aggregate root.
/// VI: Unit tests cho Campaign aggregate root.
/// </summary>
public class CampaignAggregateTests
{
#region Helper Methods
/// <summary>
/// EN: Creates a campaign with future start date (for constructor/lifecycle tests).
/// VI: Tạo campaign với ngày bắt đầu trong tương lai (cho constructor/lifecycle tests).
/// </summary>
private static Campaign CreateValidCampaign() => new Campaign(
merchantId: Guid.NewGuid(),
name: "Test Campaign",
description: "Test Description",
backingAssetType: AssetType.Currency,
backingAssetCode: "VND",
faceValue: 100_000m,
acquisitionType: AcquisitionType.Free,
acquisitionPrice: 0m,
totalVouchers: 100,
startDate: DateTime.UtcNow.AddDays(1),
endDate: DateTime.UtcNow.AddDays(30),
voucherValidityDays: 30,
maxPerUser: 1);
/// <summary>
/// EN: Creates a campaign with past start date (for issuing vouchers tests).
/// VI: Tạo campaign với ngày bắt đầu trong quá khứ (cho tests phát hành voucher).
/// </summary>
private static Campaign CreateActiveCampaign() => new Campaign(
merchantId: Guid.NewGuid(),
name: "Active Campaign",
description: "Active Description",
backingAssetType: AssetType.Currency,
backingAssetCode: "VND",
faceValue: 100_000m,
acquisitionType: AcquisitionType.Free,
acquisitionPrice: 0m,
totalVouchers: 100,
startDate: DateTime.UtcNow.AddDays(-1), // Started yesterday
endDate: DateTime.UtcNow.AddDays(30),
voucherValidityDays: 30,
maxPerUser: 1);
#endregion
#region Constructor Tests
[Fact]
public void Constructor_ValidParameters_CreatesCampaignInDraftStatus()
{
// Arrange & Act
var campaign = CreateValidCampaign();
// Assert
campaign.Id.Should().NotBeEmpty();
campaign.StatusId.Should().Be(CampaignStatus.Draft.Id);
campaign.TotalVouchers.Should().Be(100);
campaign.IssuedVouchers.Should().Be(0);
campaign.Vouchers.Should().BeEmpty();
}
[Fact]
public void Constructor_ZeroTotalVouchers_ThrowsException()
{
// Arrange & Act
var act = () => new Campaign(
merchantId: Guid.NewGuid(),
name: "Test Campaign",
description: null,
backingAssetType: AssetType.Currency,
backingAssetCode: "VND",
faceValue: 100_000m,
acquisitionType: AcquisitionType.Free,
acquisitionPrice: 0m,
totalVouchers: 0, // Invalid!
startDate: DateTime.UtcNow.AddDays(1),
endDate: DateTime.UtcNow.AddDays(30),
voucherValidityDays: 30,
maxPerUser: 1);
// Assert
act.Should().Throw<PromotionDomainException>()
.WithMessage("*Total vouchers*");
}
[Fact]
public void Constructor_EndDateBeforeStartDate_ThrowsException()
{
// Arrange & Act
var act = () => new Campaign(
merchantId: Guid.NewGuid(),
name: "Test Campaign",
description: null,
backingAssetType: AssetType.Currency,
backingAssetCode: "VND",
faceValue: 100_000m,
acquisitionType: AcquisitionType.Free,
acquisitionPrice: 0m,
totalVouchers: 100,
startDate: DateTime.UtcNow.AddDays(30),
endDate: DateTime.UtcNow.AddDays(1), // Before start!
voucherValidityDays: 30,
maxPerUser: 1);
// Assert
act.Should().Throw<PromotionDomainException>()
.WithMessage("*End date*");
}
#endregion
#region Lifecycle Tests
[Fact]
public void Activate_DraftCampaign_ChangesStatusToActive()
{
// Arrange
var campaign = CreateValidCampaign();
campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
// Act
campaign.Activate();
// Assert
campaign.StatusId.Should().Be(CampaignStatus.Active.Id);
}
[Fact]
public void Activate_WithoutEscrow_ThrowsException()
{
// Arrange
var campaign = CreateValidCampaign();
// Act & Assert
var act = () => campaign.Activate();
act.Should().Throw<PromotionDomainException>()
.WithMessage("*escrow*");
}
[Fact]
public void Pause_ActiveCampaign_ChangesStatusToPaused()
{
// Arrange
var campaign = CreateValidCampaign();
campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
campaign.Activate();
// Act
campaign.Pause();
// Assert
campaign.StatusId.Should().Be(CampaignStatus.Paused.Id);
}
[Fact]
public void Cancel_DraftCampaign_ChangesStatusToCancelled()
{
// Arrange
var campaign = CreateValidCampaign();
// Act
campaign.Cancel();
// Assert
campaign.StatusId.Should().Be(CampaignStatus.Cancelled.Id);
}
[Fact]
public void Cancel_ActiveCampaign_ChangesStatusToCancelled()
{
// Arrange
var campaign = CreateValidCampaign();
campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
campaign.Activate();
// Act
campaign.Cancel();
// Assert
campaign.StatusId.Should().Be(CampaignStatus.Cancelled.Id);
campaign.DomainEvents.Should().ContainSingle(e => e is CampaignCancelledDomainEvent);
}
#endregion
#region Voucher Generation Tests
[Fact]
public void GenerateVouchers_ValidCount_CreatesVouchersWithUniqueCodes()
{
// Arrange
var campaign = CreateValidCampaign();
// Act
campaign.GenerateVouchers(10);
// Assert
campaign.Vouchers.Should().HaveCount(10);
campaign.Vouchers.Select(v => v.Code).Should().OnlyHaveUniqueItems();
campaign.Vouchers.All(v => v.FaceValue == campaign.FaceValue).Should().BeTrue();
}
[Fact]
public void GenerateVouchers_ExceedsTotalLimit_ThrowsException()
{
// Arrange
var campaign = CreateValidCampaign(); // totalVouchers = 100
// Act & Assert
var act = () => campaign.GenerateVouchers(150);
act.Should().Throw<PromotionDomainException>()
.WithMessage("*Maximum*");
}
#endregion
#region Issue Voucher Tests
[Fact]
public void IssueVoucher_ActiveCampaign_ReturnsClaimedVoucher()
{
// Arrange
var campaign = CreateActiveCampaign();
campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
campaign.GenerateVouchers(10);
campaign.Activate();
var userId = Guid.NewGuid();
// Act
var voucher = campaign.IssueVoucher(userId);
// Assert
voucher.Should().NotBeNull();
voucher.OwnerId.Should().Be(userId);
voucher.StatusId.Should().Be(VoucherStatus.Claimed.Id);
campaign.IssuedVouchers.Should().Be(1);
}
[Fact]
public void IssueVoucher_DraftCampaign_ThrowsException()
{
// Arrange
var campaign = CreateValidCampaign();
campaign.GenerateVouchers(10);
// Act & Assert
var act = () => campaign.IssueVoucher(Guid.NewGuid());
act.Should().Throw<CampaignNotActiveException>();
}
[Fact]
public void IssueVoucher_NoAvailableVouchers_ThrowsException()
{
// Arrange
var campaign = new Campaign(
merchantId: Guid.NewGuid(),
name: "Small Campaign",
description: null,
backingAssetType: AssetType.Currency,
backingAssetCode: "VND",
faceValue: 50_000m,
acquisitionType: AcquisitionType.Free,
acquisitionPrice: 0m,
totalVouchers: 1,
startDate: DateTime.UtcNow.AddDays(-1),
endDate: DateTime.UtcNow.AddDays(30),
voucherValidityDays: 30,
maxPerUser: 1);
campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
campaign.GenerateVouchers(1);
campaign.Activate();
campaign.IssueVoucher(Guid.NewGuid()); // Take the only one
// Act & Assert
var act = () => campaign.IssueVoucher(Guid.NewGuid());
act.Should().Throw<PromotionDomainException>()
.WithMessage("*voucher*");
}
#endregion
#region Domain Events Tests
[Fact]
public void Activate_AddsCampaignActivatedDomainEvent()
{
// Arrange
var campaign = CreateValidCampaign();
campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
// Act
campaign.Activate();
// Assert
campaign.DomainEvents.Should().ContainSingle(e => e is CampaignActivatedDomainEvent);
}
[Fact]
public void IssueVoucher_AddsVoucherClaimedDomainEvent()
{
// Arrange
var campaign = CreateActiveCampaign();
campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
campaign.GenerateVouchers(10);
campaign.Activate();
// Act
campaign.IssueVoucher(Guid.NewGuid());
// Assert
campaign.DomainEvents.Should().Contain(e => e is VoucherClaimedDomainEvent);
}
#endregion
}