142 lines
5.9 KiB
C#
142 lines
5.9 KiB
C#
using After;
|
|
|
|
namespace BeforeAfter.Tests;
|
|
|
|
/// <summary>
|
|
/// Unit tests for <see cref="DiscountCalculator.CalculateDiscount"/>:
|
|
/// barrier semantics (strictly greater), integer-percent units, half-up
|
|
/// rounding of the discount amount, input validation, and the guarantee that
|
|
/// the calculation never mutates the order.
|
|
/// </summary>
|
|
public class DiscountCalculatorTests
|
|
{
|
|
// ---------------------------------------------------------------------
|
|
// The barrier: only a total strictly greater than it is discounted.
|
|
// ---------------------------------------------------------------------
|
|
[Fact]
|
|
public void A_total_below_the_barrier_gets_no_discount()
|
|
{
|
|
var order = new Order { Id = Guid.NewGuid(), Description = "small", TotalInCents = 50_00 };
|
|
|
|
var discounted = DiscountCalculator.CalculateDiscount(order, discountBarrierCents: 100_00, discountPercentage: 10);
|
|
|
|
Assert.Equal(0, discounted.DiscountPercentage);
|
|
Assert.Equal(50_00, discounted.DiscountedTotalInCents);
|
|
Assert.Equal(50_00, discounted.TotalInCents);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_total_exactly_at_the_barrier_gets_no_discount()
|
|
{
|
|
var order = new Order { Id = Guid.NewGuid(), TotalInCents = 100_00 };
|
|
|
|
var discounted = DiscountCalculator.CalculateDiscount(order, discountBarrierCents: 100_00, discountPercentage: 10);
|
|
|
|
Assert.Equal(0, discounted.DiscountPercentage);
|
|
Assert.Equal(100_00, discounted.DiscountedTotalInCents);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Above the barrier: percentage applied, total reduced.
|
|
// ---------------------------------------------------------------------
|
|
[Fact]
|
|
public void A_total_above_the_barrier_gets_the_percentage_discount()
|
|
{
|
|
var order = new Order { Id = Guid.NewGuid(), TotalInCents = 150_00 };
|
|
|
|
var discounted = DiscountCalculator.CalculateDiscount(order, discountBarrierCents: 100_00, discountPercentage: 10);
|
|
|
|
Assert.Equal(10, discounted.DiscountPercentage);
|
|
Assert.Equal(135_00, discounted.DiscountedTotalInCents); // 10 % of 150 = 15 off
|
|
Assert.Equal(150_00, discounted.TotalInCents);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_zero_percentage_discounts_nothing_even_above_the_barrier()
|
|
{
|
|
var order = new Order { Id = Guid.NewGuid(), TotalInCents = 150_00 };
|
|
|
|
var discounted = DiscountCalculator.CalculateDiscount(order, discountBarrierCents: 100_00, discountPercentage: 0);
|
|
|
|
Assert.Equal(0, discounted.DiscountPercentage);
|
|
Assert.Equal(150_00, discounted.DiscountedTotalInCents);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_hundred_percent_discount_leaves_nothing()
|
|
{
|
|
var order = new Order { Id = Guid.NewGuid(), TotalInCents = 150_00 };
|
|
|
|
var discounted = DiscountCalculator.CalculateDiscount(order, discountBarrierCents: 100_00, discountPercentage: 100);
|
|
|
|
Assert.Equal(100, discounted.DiscountPercentage);
|
|
Assert.Equal(0, discounted.DiscountedTotalInCents);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Half-up rounding of the discount amount (integer math, no float).
|
|
// ---------------------------------------------------------------------
|
|
[Theory]
|
|
[InlineData(155, 16, 139)] // 15.5 rounds UP to 16
|
|
[InlineData(145, 15, 130)] // 14.5 rounds UP to 15
|
|
[InlineData(154, 15, 139)] // 15.4 rounds down to 15
|
|
[InlineData(151, 15, 136)] // 15.1 rounds down to 15
|
|
public void The_discount_amount_is_rounded_half_up(int totalInCents, int expectedDiscountCents, int expectedDiscountedTotal)
|
|
{
|
|
var order = new Order { Id = Guid.NewGuid(), TotalInCents = totalInCents };
|
|
|
|
var discounted = DiscountCalculator.CalculateDiscount(order, discountBarrierCents: 100, discountPercentage: 10);
|
|
|
|
Assert.Equal(totalInCents - expectedDiscountCents, discounted.DiscountedTotalInCents);
|
|
Assert.Equal(expectedDiscountedTotal, discounted.DiscountedTotalInCents);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Correlation and purity.
|
|
// ---------------------------------------------------------------------
|
|
[Fact]
|
|
public void The_result_is_correlated_to_the_order_by_id()
|
|
{
|
|
var id = Guid.NewGuid();
|
|
var order = new Order { Id = id, TotalInCents = 150_00 };
|
|
|
|
var discounted = DiscountCalculator.CalculateDiscount(order, discountBarrierCents: 100_00, discountPercentage: 10);
|
|
|
|
Assert.Equal(id, discounted.OrderId);
|
|
}
|
|
|
|
[Fact]
|
|
public void Calculating_does_not_mutate_the_order()
|
|
{
|
|
var order = new Order { Id = Guid.NewGuid(), Description = "one", TotalInCents = 150_00 };
|
|
|
|
DiscountCalculator.CalculateDiscount(order, discountBarrierCents: 100_00, discountPercentage: 10);
|
|
|
|
Assert.Equal("one", order.Description);
|
|
Assert.Equal(150_00, order.TotalInCents);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Input validation: invalid arguments throw (loudly, not silently).
|
|
// ---------------------------------------------------------------------
|
|
[Theory]
|
|
[InlineData(-1, 100_00, 10)] // negative total
|
|
[InlineData(150_00, -1, 10)] // negative barrier
|
|
[InlineData(150_00, 100_00, -1)]// percentage below 0
|
|
[InlineData(150_00, 100_00, 101)]// percentage above 100
|
|
public void Invalid_arguments_throw(int totalInCents, int discountBarrierCents, int discountPercentage)
|
|
{
|
|
var order = new Order { Id = Guid.NewGuid(), TotalInCents = totalInCents };
|
|
|
|
Assert.Throws<ArgumentOutOfRangeException>(
|
|
() => DiscountCalculator.CalculateDiscount(order, discountBarrierCents, discountPercentage));
|
|
}
|
|
|
|
[Fact]
|
|
public void A_null_order_throws()
|
|
{
|
|
Assert.Throws<ArgumentNullException>(
|
|
() => DiscountCalculator.CalculateDiscount(null!, discountBarrierCents: 100_00, discountPercentage: 10));
|
|
}
|
|
}
|