toRfc4122(); $productIdStr = \Symfony\Component\Uid\Uuid::v7()->toRfc4122(); $basketProduct = new BasketProduct( id: new ProductId(Uuid::fromString($productIdStr)), name: 'Product 1', qty: 2, unitPriceHt: new Money(1000, new Currency('EUR')), unitDiscount: new Money(100, new Currency('EUR')), taxRate: TaxRate::TVA_20, weightGrams: 500, brand: ProductBrand::loue, incoId: new IncoId(12345), orderUnit: OrderUnit::PCE, slug: 'product-1', picture: 'pic.png', description: 'desc', weightVariable: true, discountRate: DiscountRate::fromRatio(0.1), priceKgTtc: new Money(2000, new Currency('EUR')), options: ['color' => 'red'] ); $basket = new Basket( id: new BasketId(Uuid::fromString($basketIdStr)), items: [$productIdStr => $basketProduct] ); // WHEN $array = $serializer->toArray($basket); $array = json_decode(json_encode($array), true); $deserializedBasket = $serializer->fromArray($array); // THEN expect($array)->toBeArray() ->and($array['id'])->toBe($basketIdStr) ->and($array['items'][0]['id'])->toBe($productIdStr) ->and($array['items'][0]['name'])->toBe('Product 1') ->and($array['items'][0]['taxRate'])->toBe(TaxRate::TVA_20->value) ->and($deserializedBasket->id->value->toString())->toBe($basketIdStr) ->and($deserializedBasket->items())->toHaveCount(1) ->and($deserializedBasket->items()[$productIdStr]->name)->toBe('Product 1') ->and($deserializedBasket->items()[$productIdStr]->weightVariable)->toBeTrue() ->and($deserializedBasket->items()[$productIdStr]->options)->toBe(['color' => 'red']) ->and($array['items'][0]['discountRateBp'])->toBe(1000) ->and($deserializedBasket->items()[$productIdStr]->discountRate->basisPoints())->toBe(1000); }); it('hydrates the discount rate from the legacy float ratio stored in older sessions', function (float $legacyRatio, int $expectedBasisPoints) { // GIVEN $serializer = new BasketSerializer(); $productIdStr = \Symfony\Component\Uid\Uuid::v7()->toRfc4122(); $data = [ 'id' => \Symfony\Component\Uid\Uuid::v7()->toRfc4122(), 'items' => [ [ 'id' => $productIdStr, 'name' => 'Test', 'qty' => 1, 'unitPriceHt' => ['amount' => 1000, 'currency' => 'EUR'], 'unitDiscount' => ['amount' => 100, 'currency' => 'EUR'], 'taxRate' => TaxRate::TVA_20->value, 'weightGrams' => 500, 'orderUnit' => OrderUnit::PCE->value, 'slug' => 'test', 'weightVariable' => false, 'discountRate' => $legacyRatio, 'priceKgTtc' => ['amount' => 2000, 'currency' => 'EUR'], ], ], ]; // WHEN $basket = $serializer->fromArray($data); // THEN expect($basket->items()[$productIdStr]->discountRate->basisPoints())->toBe($expectedBasisPoints); })->with([ 'regular ratio' => [0.15, 1500], 'no discount' => [0.0, 0], 'out of range value is clamped' => [20.0, 10000], ]); it('throws exception if currency is missing during deserialization', function () { // GIVEN $serializer = new BasketSerializer(); $productIdStr = \Symfony\Component\Uid\Uuid::v7()->toRfc4122(); $data = [ 'id' => \Symfony\Component\Uid\Uuid::v7()->toRfc4122(), 'items' => [ [ 'id' => $productIdStr, 'name' => 'Test', 'qty' => 1, 'unitPriceHt' => ['amount' => 1000, 'currency' => ''], 'unitDiscount' => ['amount' => 100, 'currency' => 'EUR'], 'taxRate' => TaxRate::TVA_20->value, 'weightGrams' => 500, 'orderUnit' => OrderUnit::PCE->value, 'slug' => 'test', 'weightVariable' => false, 'priceKgTtc' => ['amount' => 2000, 'currency' => 'EUR'], ], ], ]; // WHEN $action = fn () => $serializer->fromArray($data); // THEN expect($action)->toThrow( \DomainException::class, 'Currency is required for unitPriceHt of item with id ' . $productIdStr ); }); it('throws exception if currency is missing for unitDiscount', function () { // GIVEN $serializer = new BasketSerializer(); $productIdStr = \Symfony\Component\Uid\Uuid::v7()->toRfc4122(); $data = [ 'id' => \Symfony\Component\Uid\Uuid::v7()->toRfc4122(), 'items' => [ [ 'id' => $productIdStr, 'name' => 'Test', 'qty' => 1, 'unitPriceHt' => ['amount' => 1000, 'currency' => 'EUR'], 'unitDiscount' => ['amount' => 100, 'currency' => ''], 'taxRate' => TaxRate::TVA_20->value, 'weightGrams' => 500, 'orderUnit' => OrderUnit::PCE->value, 'slug' => 'test', 'weightVariable' => false, 'priceKgTtc' => ['amount' => 2000, 'currency' => 'EUR'], ], ], ]; // WHEN $action = fn () => $serializer->fromArray($data); // THEN expect($action)->toThrow( \DomainException::class, 'Currency is required for unitDiscount of item with id ' . $productIdStr ); }); it('throws exception if currency is missing for priceKgTtc', function () { // GIVEN $serializer = new BasketSerializer(); $productIdStr = \Symfony\Component\Uid\Uuid::v7()->toRfc4122(); $data = [ 'id' => \Symfony\Component\Uid\Uuid::v7()->toRfc4122(), 'items' => [ [ 'id' => $productIdStr, 'name' => 'Test', 'qty' => 1, 'unitPriceHt' => ['amount' => 1000, 'currency' => 'EUR'], 'unitDiscount' => ['amount' => 100, 'currency' => 'EUR'], 'taxRate' => TaxRate::TVA_20->value, 'weightGrams' => 500, 'orderUnit' => OrderUnit::PCE->value, 'slug' => 'test', 'weightVariable' => false, 'priceKgTtc' => ['amount' => 2000, 'currency' => ''], ], ], ]; // WHEN $action = fn () => $serializer->fromArray($data); // THEN expect($action)->toThrow( \DomainException::class, 'Currency is required for priceKgTtc of item with id ' . $productIdStr ); }); });