327 lines
9.4 KiB
C#
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
|
|
}
|