154 lines
4.6 KiB
PHP
154 lines
4.6 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
use Domain\Models\ValueObjects\DiscountRate;
|
||
|
|
use Money\Money;
|
||
|
|
|
||
|
|
describe(DiscountRate::class, function (): void {
|
||
|
|
it('instantiates from basis points and exposes ratio and percentage', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$basisPoints = 1550;
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$rate = DiscountRate::fromBasisPoints($basisPoints);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($rate->basisPoints())->toBe(1550)
|
||
|
|
->and($rate->ratio())->toBe(0.155)
|
||
|
|
->and($rate->percentage())->toBe(15.5)
|
||
|
|
->and($rate->isZero())->toBeFalse();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('creates a zero rate', function (): void {
|
||
|
|
// GIVEN / WHEN
|
||
|
|
$rate = DiscountRate::zero();
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($rate->basisPoints())->toBe(0)
|
||
|
|
->and($rate->isZero())->toBeTrue();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('creates a rate from a percentage', function (int | float $percentage, int $expectedBasisPoints): void {
|
||
|
|
// GIVEN / WHEN
|
||
|
|
$rate = DiscountRate::fromPercentage($percentage);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($rate->basisPoints())->toBe($expectedBasisPoints);
|
||
|
|
})->with([
|
||
|
|
'integer percent' => [15, 1500],
|
||
|
|
'decimal percent' => [5.5, 550],
|
||
|
|
'zero' => [0, 0],
|
||
|
|
'full' => [100, 10000],
|
||
|
|
]);
|
||
|
|
|
||
|
|
it('creates a rate from a ratio without float drift', function (float $ratio, int $expectedBasisPoints): void {
|
||
|
|
// GIVEN / WHEN
|
||
|
|
$rate = DiscountRate::fromRatio($ratio);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($rate->basisPoints())->toBe($expectedBasisPoints);
|
||
|
|
})->with([
|
||
|
|
'ten percent' => [0.1, 1000],
|
||
|
|
'classic drift' => [0.29, 2900], // 0.29 * 10000 = 2899.9999999999995 in IEEE 754
|
||
|
|
'five point five' => [0.055, 550],
|
||
|
|
]);
|
||
|
|
|
||
|
|
it('computes the rate of a fixed amount discount on a base price', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$basePrice = Money::EUR(1000);
|
||
|
|
$discount = Money::EUR(200);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$rate = DiscountRate::fromAmounts($discount, $basePrice);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($rate->basisPoints())->toBe(2000)
|
||
|
|
->and($rate->ratio())->toBe(0.2);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rounds the rate of a fixed amount discount to the nearest basis point', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$basePrice = Money::EUR(300);
|
||
|
|
$discount = Money::EUR(100); // 33.333...%
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$rate = DiscountRate::fromAmounts($discount, $basePrice);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($rate->basisPoints())->toBe(3333);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('caps a fixed amount discount greater than the base price at 100 percent', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$basePrice = Money::EUR(500);
|
||
|
|
$discount = Money::EUR(900);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$rate = DiscountRate::fromAmounts($discount, $basePrice);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($rate->basisPoints())->toBe(10000);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns a zero rate when the base price or the discount is not positive', function (): void {
|
||
|
|
// GIVEN / WHEN
|
||
|
|
$noPrice = DiscountRate::fromAmounts(Money::EUR(100), Money::EUR(0));
|
||
|
|
$noDiscount = DiscountRate::fromAmounts(Money::EUR(0), Money::EUR(1000));
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($noPrice->isZero())->toBeTrue()
|
||
|
|
->and($noDiscount->isZero())->toBeTrue();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('applies the rate to an amount', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$rate = DiscountRate::fromPercentage(15);
|
||
|
|
$amount = Money::EUR(1000);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$discount = $rate->applyTo($amount);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($discount->getAmount())->toBe('150');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('formats the rate as a percentage', function (int $basisPoints, string $expected): void {
|
||
|
|
// GIVEN
|
||
|
|
$rate = DiscountRate::fromBasisPoints($basisPoints);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$formatted = (string) $rate;
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($formatted)->toBe($expected);
|
||
|
|
})->with([
|
||
|
|
'integer' => [1500, '15 %'],
|
||
|
|
'decimal' => [550, '5.5 %'],
|
||
|
|
'zero' => [0, '0 %'],
|
||
|
|
'hundred' => [10000, '100 %'],
|
||
|
|
]);
|
||
|
|
|
||
|
|
it('compares two rates', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$a = DiscountRate::fromPercentage(10);
|
||
|
|
$b = DiscountRate::fromRatio(0.1);
|
||
|
|
$c = DiscountRate::fromPercentage(20);
|
||
|
|
|
||
|
|
// WHEN / THEN
|
||
|
|
expect($a->equals($b))->toBeTrue()
|
||
|
|
->and($a->equals($c))->toBeFalse();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('throws when the rate is negative or above 100 percent', function (int $basisPoints): void {
|
||
|
|
// GIVEN / WHEN
|
||
|
|
$action = fn () => DiscountRate::fromBasisPoints($basisPoints);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($action)->toThrow(\InvalidArgumentException::class);
|
||
|
|
})->with([
|
||
|
|
'negative' => [-1],
|
||
|
|
'above 100%' => [10001],
|
||
|
|
]);
|
||
|
|
});
|