200 lines
5.1 KiB
C#
200 lines
5.1 KiB
C#
using FluentAssertions;
|
|
using PromotionService.Domain.AggregatesModel.CampaignAggregate;
|
|
using PromotionService.Domain.Exceptions;
|
|
using Xunit;
|
|
|
|
namespace PromotionService.UnitTests.Domain;
|
|
|
|
/// <summary>
|
|
/// EN: Unit tests for Voucher entity.
|
|
/// VI: Unit tests cho Voucher entity.
|
|
/// </summary>
|
|
public class VoucherEntityTests
|
|
{
|
|
private const int DefaultValidityDays = 30;
|
|
|
|
#region Helper Methods
|
|
|
|
private static Voucher CreateAvailableVoucher() => new Voucher(
|
|
campaignId: Guid.NewGuid(),
|
|
code: "TEST-VOUCHER-001",
|
|
faceValue: 100_000m,
|
|
validityDays: DefaultValidityDays);
|
|
|
|
private static Voucher CreateClaimedVoucher()
|
|
{
|
|
var voucher = CreateAvailableVoucher();
|
|
voucher.Claim(Guid.NewGuid(), DefaultValidityDays);
|
|
return voucher;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructor Tests
|
|
|
|
[Fact]
|
|
public void Constructor_ValidParameters_CreatesVoucherInAvailableStatus()
|
|
{
|
|
// Arrange & Act
|
|
var voucher = CreateAvailableVoucher();
|
|
|
|
// Assert
|
|
voucher.Id.Should().NotBeEmpty();
|
|
voucher.Code.Should().Be("TEST-VOUCHER-001");
|
|
voucher.FaceValue.Should().Be(100_000m);
|
|
voucher.RemainingValue.Should().Be(100_000m);
|
|
voucher.StatusId.Should().Be(VoucherStatus.Available.Id);
|
|
voucher.OwnerId.Should().BeNull();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Claim Tests
|
|
|
|
[Fact]
|
|
public void Claim_AvailableVoucher_SetsOwnerAndClaimedStatus()
|
|
{
|
|
// Arrange
|
|
var voucher = CreateAvailableVoucher();
|
|
var userId = Guid.NewGuid();
|
|
|
|
// Act
|
|
voucher.Claim(userId, DefaultValidityDays);
|
|
|
|
// Assert
|
|
voucher.OwnerId.Should().Be(userId);
|
|
voucher.StatusId.Should().Be(VoucherStatus.Claimed.Id);
|
|
voucher.ClaimedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
|
voucher.ExpiresAt.Should().BeCloseTo(DateTime.UtcNow.AddDays(DefaultValidityDays), TimeSpan.FromSeconds(5));
|
|
}
|
|
|
|
[Fact]
|
|
public void Claim_AlreadyClaimedVoucher_ThrowsException()
|
|
{
|
|
// Arrange
|
|
var voucher = CreateClaimedVoucher();
|
|
|
|
// Act & Assert
|
|
var act = () => voucher.Claim(Guid.NewGuid(), DefaultValidityDays);
|
|
act.Should().Throw<VoucherAlreadyClaimedException>();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Redeem Tests
|
|
|
|
[Fact]
|
|
public void Redeem_FullValue_SetsStatusToFullyRedeemed()
|
|
{
|
|
// Arrange
|
|
var voucher = CreateClaimedVoucher();
|
|
|
|
// Act
|
|
voucher.Redeem(100_000m); // Full face value
|
|
|
|
// Assert
|
|
voucher.RemainingValue.Should().Be(0);
|
|
voucher.StatusId.Should().Be(VoucherStatus.FullyRedeemed.Id);
|
|
voucher.RedeemedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
|
}
|
|
|
|
[Fact]
|
|
public void Redeem_PartialValue_SetsStatusToPartiallyRedeemed()
|
|
{
|
|
// Arrange
|
|
var voucher = CreateClaimedVoucher();
|
|
|
|
// Act
|
|
voucher.Redeem(50_000m); // Half
|
|
|
|
// Assert
|
|
voucher.RemainingValue.Should().Be(50_000m);
|
|
voucher.StatusId.Should().Be(VoucherStatus.PartiallyRedeemed.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void Redeem_MoreThanRemainingValue_CapsAtRemainingValue()
|
|
{
|
|
// Arrange
|
|
var voucher = CreateClaimedVoucher();
|
|
|
|
// Act - Request more than available
|
|
var actualRedeemed = voucher.Redeem(150_000m);
|
|
|
|
// Assert - Only redeems what's available
|
|
actualRedeemed.Should().Be(100_000m);
|
|
voucher.RemainingValue.Should().Be(0);
|
|
voucher.StatusId.Should().Be(VoucherStatus.FullyRedeemed.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void Redeem_AvailableVoucher_ThrowsException()
|
|
{
|
|
// Arrange
|
|
var voucher = CreateAvailableVoucher(); // Not claimed
|
|
|
|
// Act & Assert
|
|
var act = () => voucher.Redeem(50_000m);
|
|
act.Should().Throw<PromotionDomainException>()
|
|
.WithMessage("*not been claimed*");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Expire Tests
|
|
|
|
[Fact]
|
|
public void Expire_ClaimedVoucher_SetsStatusToExpired()
|
|
{
|
|
// Arrange
|
|
var voucher = CreateClaimedVoucher();
|
|
|
|
// Act
|
|
voucher.Expire();
|
|
|
|
// Assert
|
|
voucher.StatusId.Should().Be(VoucherStatus.Expired.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void Expire_FullyRedeemedVoucher_DoesNotChangeStatus()
|
|
{
|
|
// Arrange
|
|
var voucher = CreateClaimedVoucher();
|
|
voucher.Redeem(100_000m);
|
|
|
|
// Act
|
|
voucher.Expire();
|
|
|
|
// Assert - Status remains FullyRedeemed
|
|
voucher.StatusId.Should().Be(VoucherStatus.FullyRedeemed.Id);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Validation Tests
|
|
|
|
[Fact]
|
|
public void IsValidForRedemption_ClaimedNotExpired_ReturnsTrue()
|
|
{
|
|
// Arrange
|
|
var voucher = CreateClaimedVoucher();
|
|
|
|
// Act & Assert
|
|
voucher.IsValidForRedemption().Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void IsValidForRedemption_FullyRedeemed_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var voucher = CreateClaimedVoucher();
|
|
voucher.Redeem(100_000m);
|
|
|
|
// Act & Assert
|
|
voucher.IsValidForRedemption().Should().BeFalse();
|
|
}
|
|
|
|
#endregion
|
|
}
|