ldc-boutique-cnc-sable/tests/Unit/Domain/Models/Entities/BasketTest.php

255 lines
9 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Models\Entities;
use Domain\Enums\ProductBrand;
use Domain\Enums\TaxRate;
use Domain\Enums\Units\OrderUnit;
use Domain\Models\Entities\Basket;
use Domain\Models\ValueObjects\BasketProduct;
use Domain\Models\ValueObjects\DiscountRate;
use Domain\Models\ValueObjects\Entities\BasketId;
use Domain\Models\ValueObjects\Entities\ProductId;
use Domain\Models\ValueObjects\IncoId;
use Money\Money;
use Ramsey\Uuid\Uuid;
describe('Basket and BasketProduct', function (): void {
function getNewBasketProduct(array $overrides = []): BasketProduct
{
$data = array_merge([
'id' => new ProductId(Uuid::fromString('019e9e37-f2a1-74ef-b9c2-66d222470128')),
'name' => 'Poulet Rôti',
'qty' => 2,
'unitPriceHt' => Money::EUR(1000),
'unitDiscount' => Money::EUR(200),
'taxRate' => TaxRate::TVA_5_5,
'weightGrams' => 1200,
'brand' => ProductBrand::marie,
'incoId' => new IncoId(45),
'orderUnit' => OrderUnit::PCE,
'slug' => 'poulet-roti',
'picture' => 'images/poulet.jpg',
'description' => 'Un bon poulet rôti',
'weightVariable' => true,
'discountRate' => DiscountRate::fromRatio(0.2),
'priceKgTtc' => Money::EUR(1500),
'options' => ['extra' => 'crispy'],
], $overrides);
return new BasketProduct(...$data);
}
it('can instantiate a basket product with all new typed properties', function (): void {
// GIVEN
$productId = new ProductId(Uuid::fromString('019e9e37-f2a1-74ef-b9c2-66d222470128'));
$brand = ProductBrand::marie;
$orderUnit = OrderUnit::PCE;
$price = Money::EUR(1000);
$discount = Money::EUR(200);
$priceKgTtc = Money::EUR(1500);
// WHEN
$item = new BasketProduct(
id: $productId,
name: 'Poulet Rôti',
qty: 2,
unitPriceHt: $price,
unitDiscount: $discount,
taxRate: TaxRate::TVA_5_5,
weightGrams: 1200,
brand: $brand,
incoId: new IncoId(45),
orderUnit: $orderUnit,
slug: 'poulet-roti',
picture: 'images/poulet.jpg',
description: 'Un bon poulet rôti',
weightVariable: true,
discountRate: DiscountRate::fromRatio(0.2),
priceKgTtc: $priceKgTtc,
options: ['extra' => 'crispy']
);
// THEN
expect((string) $item->id->value)->toBe('019e9e37-f2a1-74ef-b9c2-66d222470128');
expect($item->name)->toBe('Poulet Rôti');
expect($item->qty)->toBe(2);
expect($item->unitPriceHt->getAmount())->toBe('1000');
expect($item->unitDiscount->getAmount())->toBe('200');
expect($item->taxRate)->toBe(TaxRate::TVA_5_5);
expect($item->weightGrams)->toBe(1200);
expect($item->brand)->toBe(ProductBrand::marie);
expect($item->incoId->value)->toBe(45);
expect($item->orderUnit)->toBe(OrderUnit::PCE);
expect($item->slug)->toBe('poulet-roti');
expect($item->picture)->toBe('images/poulet.jpg');
expect($item->description)->toBe('Un bon poulet rôti');
expect($item->weightVariable)->toBeTrue();
expect($item->discountRate->basisPoints())->toBe(2000);
expect($item->priceKgTtc->getAmount())->toBe('1500');
expect($item->options)->toBe(['extra' => 'crispy']);
});
it('can clone a basket product with new quantity and keep properties', function (): void {
// GIVEN
$productId = new ProductId(Uuid::fromString('019e9e37-f2a1-74ef-b9c2-66d222470128'));
$item = getNewBasketProduct(['id' => $productId, 'qty' => 2]);
$basketId = new BasketId(Uuid::fromString('019e9e37-f2a1-74ef-b9c2-66d222470128'));
$basket = new Basket($basketId);
// WHEN
$basket->addOrUpdate($item);
$basket->addOrUpdate(getNewBasketProduct([
'id' => $productId,
'qty' => 3, // Add 3
]));
// THEN
expect($basket->countItems())->toBe(5);
$updatedItem = $basket->items()[(string) $productId->value];
expect($updatedItem->qty)->toBe(5);
expect($updatedItem->brand)->toBe(ProductBrand::marie);
expect($updatedItem->incoId->value)->toBe(45);
expect($updatedItem->orderUnit)->toBe(OrderUnit::PCE);
expect($updatedItem->slug)->toBe('poulet-roti');
expect($updatedItem->picture)->toBe('images/poulet.jpg');
expect($updatedItem->description)->toBe('Un bon poulet rôti');
expect($updatedItem->weightVariable)->toBeTrue();
expect($updatedItem->discountRate->basisPoints())->toBe(2000);
expect($updatedItem->priceKgTtc->getAmount())->toBe('1500');
expect($updatedItem->options)->toBe(['extra' => 'crispy']);
});
});
describe('Basket additional coverage', function (): void {
it('throws if item is not BasketProduct', function (): void {
// GIVEN
$basketId = new BasketId(Uuid::uuid7());
// WHEN
$action = fn () => new Basket($basketId, ['not-an-item']);
// THEN
expect($action)->toThrow(\DomainException::class);
});
it('returns id', function (): void {
// GIVEN
$basketId = new BasketId(Uuid::uuid7());
$basket = new Basket($basketId);
// WHEN
$id = $basket->id();
// THEN
expect($id)->toBe($basketId);
});
it('removes item completely if quantity is greater or equal', function (): void {
// GIVEN
$basketId = new BasketId(Uuid::uuid7());
$basket = new Basket($basketId);
$productId = new ProductId(Uuid::uuid7());
$item = getNewBasketProduct(['id' => $productId, 'qty' => 2]);
$basket->addOrUpdate($item);
// WHEN
$basket->remove($productId, 2);
// THEN
expect($basket->has($productId))->toBeFalse();
});
it('decreases quantity when removing', function (): void {
// GIVEN
$basketId = new BasketId(Uuid::uuid7());
$basket = new Basket($basketId);
$productId = new ProductId(Uuid::uuid7());
$item = getNewBasketProduct(['id' => $productId, 'qty' => 5]);
$basket->addOrUpdate($item);
// WHEN
$basket->remove($productId, 2);
// THEN
expect($basket->items()[(string) $productId->value]->qty)->toBe(3);
});
it('throws when removing less than 1', function (): void {
// GIVEN
$basketId = new BasketId(Uuid::uuid7());
$basket = new Basket($basketId);
$productId = new ProductId(Uuid::uuid7());
// WHEN
$action = fn () => $basket->remove($productId, 0);
// THEN
expect($action)->toThrow(\DomainException::class);
});
it('throws when removing non-existing item', function (): void {
// GIVEN
$basketId = new BasketId(Uuid::uuid7());
$basket = new Basket($basketId);
$productId = new ProductId(Uuid::uuid7());
// WHEN
$action = fn () => $basket->remove($productId, 1);
// THEN
expect($action)->toThrow(\DomainException::class);
});
it('removes line directly', function (): void {
// GIVEN
$basketId = new BasketId(Uuid::uuid7());
$basket = new Basket($basketId);
$productId = new ProductId(Uuid::uuid7());
$item = getNewBasketProduct(['id' => $productId, 'qty' => 5]);
$basket->addOrUpdate($item);
// WHEN
$basket->removeLine($productId);
// THEN
expect($basket->has($productId))->toBeFalse();
});
it('throws when removeLine on non-existing item', function (): void {
// GIVEN
$basketId = new BasketId(Uuid::uuid7());
$basket = new Basket($basketId);
$productId = new ProductId(Uuid::uuid7());
// WHEN
$action = fn () => $basket->removeLine($productId);
// THEN
expect($action)->toThrow(\DomainException::class);
});
it('computes totals', function (): void {
// GIVEN
$basketId = new BasketId(Uuid::uuid7());
$basket = new Basket($basketId);
$productId = new ProductId(Uuid::uuid7());
$item = getNewBasketProduct(['id' => $productId, 'qty' => 1]);
$basket->addOrUpdate($item);
// WHEN
$taxInc = $basket->totalTaxIncluded();
$vat = $basket->totalVat();
$raw = $basket->totalRaw();
$discount = $basket->totalDiscount();
$weight = $basket->totalWeight();
// THEN
expect($taxInc->getAmount())->toBe('844') // (1000 - 200) * 1.055 = 844
->and($vat->getAmount())->toBe('44')
->and($raw->getAmount())->toBe('800')
->and($discount->getAmount())->toBe('200')
->and($weight)->toBe(1200);
});
});