ldc-boutique-cnc-sable/tests/Unit/Domain/Services/OrderServiceTest.php

297 lines
10 KiB
PHP
Raw Permalink Normal View History

2026-06-14 21:32:07 +02:00
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Services;
use Carbon\CarbonImmutable;
use Domain\Enums\OrderStatus;
use Domain\Enums\ProductBrand;
use Domain\Enums\TaxRate;
use Domain\Enums\Units\OrderUnit;
use Domain\Models\Entities\Basket;
use Domain\Models\Entities\Order;
use Domain\Models\Entities\Shop;
use Domain\Models\Entities\Shopper;
use Domain\Models\ValueObjects\BasketProduct;
use Domain\Models\ValueObjects\DeliveryAddress;
use Domain\Models\ValueObjects\DiscountRate;
2026-06-14 21:32:07 +02:00
use Domain\Models\ValueObjects\Email;
use Domain\Models\ValueObjects\Entities\BasketId;
use Domain\Models\ValueObjects\Entities\OrderId;
use Domain\Models\ValueObjects\Entities\ProductId;
use Domain\Models\ValueObjects\Entities\ShopId;
use Domain\Models\ValueObjects\Entities\ShopperId;
use Domain\Models\ValueObjects\IncoId;
use Domain\Models\ValueObjects\Matricule;
use Domain\Models\ValueObjects\OrderEmployee;
use Domain\Models\ValueObjects\OrderLine;
use Domain\Models\ValueObjects\OrderLineProduct;
use Domain\Models\ValueObjects\OrderReference;
use Domain\Models\ValueObjects\PickupTimeSlot;
use Domain\Models\ValueObjects\ProductWeight;
use Domain\Models\ValueObjects\ShopperEstablishmentCode;
use Domain\Models\ValueObjects\ShopperServiceCode;
use Domain\Models\ValueObjects\ShopperSiteCode;
2026-06-14 21:32:07 +02:00
use Domain\Ports\Repositories\OrderRepository;
use Domain\Services\OrderService;
use Mockery;
use Money\Money;
use Ramsey\Uuid\Uuid;
use Shared\Helpers\UuidHelper;
2026-06-14 21:32:07 +02:00
describe('Order Cancellation', function () {
it('cancels a validated order successfully', function () {
// GIVEN
$orderId = new OrderId(Uuid::uuid7());
$order = new Order(
id: $orderId,
shopId: new ShopId(Uuid::uuid7()),
reference: new OrderReference(Uuid::uuid7()),
2026-06-14 21:32:07 +02:00
employee: new OrderEmployee(
new Matricule('1234567'),
'Jean',
'Dupont',
new ShopperEstablishmentCode('ETAB01'),
new ShopperSiteCode('SITEA'),
new ShopperServiceCode('101')
2026-06-14 21:32:07 +02:00
),
deliveryAddress: new DeliveryAddress(
'Cocotte Lyon',
'12 rue de la Paix',
'69001',
'Lyon',
'0478123456'
),
pickupTimeSlot: new PickupTimeSlot(
new \DateTimeImmutable('2023-12-01 10:00:00'),
new \DateInterval('PT10M')
),
status: OrderStatus::VALIDATED,
total: Money::EUR(1899),
lines: [
new OrderLine(
product: new OrderLineProduct(
incoId: new IncoId(45),
originalId: new ProductId(Uuid::uuid7()),
name: 'Poulet Rôti',
weight: new ProductWeight(1200, false),
brand: ProductBrand::marie,
orderUnit: OrderUnit::PCE,
price: Money::EUR(1000),
discountedPrice: Money::EUR(900),
priceKgTtc: Money::EUR(1500),
taxRate: TaxRate::TVA_5_5,
),
quantity: 2,
unitPriceHt: Money::EUR(1000),
unitDiscount: Money::EUR(100),
unitTax: Money::EUR(50),
totalHt: Money::EUR(2000),
totalDiscount: Money::EUR(200),
totalTax: Money::EUR(99),
totalTtc: Money::EUR(1899)
),
]
);
/** @var OrderRepository|\Mockery\MockInterface $repository */
$repository = Mockery::mock(OrderRepository::class);
$repository->shouldReceive('find')->with($orderId)->andReturn($order);
$repository->shouldReceive('persist')->with($order)->once();
$service = new OrderService($repository);
2026-06-14 21:32:07 +02:00
// WHEN
$service->cancelOrder($orderId);
// THEN
expect($order->status)->toBe(OrderStatus::CANCELED);
});
it('throws exception if order not found', function () {
// GIVEN
$orderId = new OrderId(Uuid::uuid7());
/** @var OrderRepository|\Mockery\MockInterface $repository */
$repository = Mockery::mock(OrderRepository::class);
$repository->shouldReceive('find')->with($orderId)->andReturn(null);
$service = new OrderService($repository);
2026-06-14 21:32:07 +02:00
// WHEN
$action = fn () => $service->cancelOrder($orderId);
// THEN
expect($action)->toThrow(\DomainException::class, 'Order not found');
});
});
describe('Order Creation', function () {
it('creates an order from a basket correctly', function () {
// GIVEN
$basketId = new BasketId(Uuid::uuid7());
$basket = new Basket($basketId);
$basketProduct = new BasketProduct(
id: new ProductId(Uuid::uuid7()),
name: 'Poulet Rôti',
qty: 2,
unitPriceHt: Money::EUR(1000),
unitDiscount: Money::EUR(100),
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',
weightVariable: false,
discountRate: DiscountRate::fromRatio(0.1),
2026-06-14 21:32:07 +02:00
priceKgTtc: Money::EUR(1500)
);
$basket->addOrUpdate($basketProduct);
$pickupTimeSlot = new PickupTimeSlot(
new \DateTimeImmutable('2023-12-01 10:00:00'),
new \DateInterval('PT10M')
);
$shopper = new Shopper(
id: new ShopperId(Uuid::uuid7()),
email: new Email('jean.dupont@example.com'),
matricule: new Matricule('1234567'),
nom: 'Dupont',
prenom: 'Jean',
startDate: CarbonImmutable::now(),
endDate: null,
etablissement: new ShopperEstablishmentCode('ETAB01'),
site: new ShopperSiteCode('SITEA'),
service: new ShopperServiceCode('101')
2026-06-14 21:32:07 +02:00
);
$shop = new Shop(
id: new ShopId(Uuid::uuid7()),
name: 'Cocotte Lyon',
address: '12 rue de la Paix',
postcode: '69001',
city: 'Lyon',
phone: '0478123456'
);
/** @var OrderRepository|\Mockery\MockInterface $repository */
$repository = Mockery::mock(OrderRepository::class);
$service = new OrderService($repository);
2026-06-14 21:32:07 +02:00
// WHEN
$order = $service->createOrderFromBasket($basket, $pickupTimeSlot, $shopper, $shop);
// THEN
expect($order)->toBeInstanceOf(Order::class);
expect($order->status)->toBe(OrderStatus::VALIDATED);
expect(UuidHelper::isV7($order->reference->value))->toBeTrue();
2026-06-14 21:32:07 +02:00
// * Employee
$employee = $order->employee;
expect($employee->matricule->value)->toBe('1234567');
expect($employee->firstname)->toBe('Jean');
expect($employee->lastname)->toBe('Dupont');
expect($employee->facility->value)->toBe('ETAB01');
expect($employee->site->value)->toBe('SITEA');
expect($employee->service->value)->toBe('101');
2026-06-14 21:32:07 +02:00
// * Delivery Address
$delivery = $order->deliveryAddress;
expect($delivery->name)->toBe('Cocotte Lyon');
expect($delivery->address)->toBe('12 rue de la Paix');
expect($delivery->postcode)->toBe('69001');
expect($delivery->city)->toBe('Lyon');
expect($delivery->phone)->toBe('0478123456');
// * Total
expect($order->getTotal()->getAmount())->toBe('1899');
// * Lines
$lines = $order->getLines();
expect($lines)->toHaveCount(1);
$line = $lines[0];
expect($line->quantity)->toBe(2);
expect($line->unitPriceHt->getAmount())->toBe('1000');
expect($line->unitDiscount->getAmount())->toBe('100');
expect($line->unitTtc->getAmount())->toBe('950');
expect($line->totalHt->getAmount())->toBe('1800');
expect($line->totalDiscount->getAmount())->toBe('200');
expect($line->totalTtc->getAmount())->toBe('1899');
// * product mapping
$product = $line->product;
expect($product->name)->toBe('Poulet Rôti');
expect($product->weight->toGrams())->toBe(1200);
expect($product->brand)->toBe(ProductBrand::marie);
expect($product->incoId->value)->toBe(45);
expect($product->orderUnit)->toBe(OrderUnit::PCE);
expect($product->picture)->toBe('images/poulet.jpg');
});
it('throws exception if basket product has no incoId', function () {
// GIVEN
$basketId = new BasketId(Uuid::uuid7());
$basket = new Basket($basketId);
$basketProduct = new BasketProduct(
id: new ProductId(Uuid::uuid7()),
name: 'Poulet Rôti',
qty: 2,
unitPriceHt: Money::EUR(1000),
unitDiscount: Money::EUR(100),
taxRate: TaxRate::TVA_5_5,
weightGrams: 1200,
brand: ProductBrand::marie,
incoId: null, // NO INCO ID
orderUnit: OrderUnit::PCE,
slug: 'poulet-roti',
picture: null,
description: null,
weightVariable: false,
discountRate: DiscountRate::fromRatio(0.1),
2026-06-14 21:32:07 +02:00
priceKgTtc: Money::EUR(1500)
);
$basket->addOrUpdate($basketProduct);
$pickupTimeSlot = new PickupTimeSlot(new \DateTimeImmutable(), new \DateInterval('PT10M'));
$shopper = new Shopper(
id: new ShopperId(Uuid::uuid7()),
email: new Email('a@a.com'),
matricule: new Matricule('1234567'),
nom: 'A',
prenom: 'B',
startDate: CarbonImmutable::now(),
endDate: null,
etablissement: new ShopperEstablishmentCode('ETAB01'),
site: new ShopperSiteCode('SITEA'),
service: new ShopperServiceCode('101')
2026-06-14 21:32:07 +02:00
);
$shop = new Shop(
id: new ShopId(Uuid::uuid7()),
name: 'S',
address: 'A',
postcode: 'P',
city: 'C'
);
$repository = Mockery::mock(OrderRepository::class);
$service = new OrderService($repository);
2026-06-14 21:32:07 +02:00
// WHEN
$action = fn () => $service->createOrderFromBasket($basket, $pickupTimeSlot, $shopper, $shop);
// THEN
expect($action)->toThrow(\DomainException::class, 'incoId cannot be null for an order');
});
});