diff --git a/src/Sales/Interfaces/Http/Api/V1/OrderController.php b/src/Sales/Interfaces/Http/Api/V1/OrderController.php new file mode 100644 index 0000000..b2a6fa5 --- /dev/null +++ b/src/Sales/Interfaces/Http/Api/V1/OrderController.php @@ -0,0 +1,84 @@ +} $data */ + $data = json_decode($request->getContent(), true, flags: JSON_THROW_ON_ERROR); + + $orderId = UuidGenerator::generate(); + + ($this->placeOrderHandler)(new PlaceOrder( + orderId: $orderId, + customerId: $data['customerId'], + lines: $data['lines'], + )); + + $order = ($this->getOrderByIdHandler)(new GetOrderById($orderId)); + + return new JsonResponse($this->assembler->toView($order), Response::HTTP_CREATED); + } + + #[Route('/orders/{orderId}', name: 'ohs_sales_get_order', methods: ['GET'])] + public function getOrder(string $orderId): JsonResponse + { + $order = ($this->getOrderByIdHandler)(new GetOrderById($orderId)); + + return new JsonResponse($this->assembler->toView($order)); + } + + #[Route('/orders/{orderId}/confirm', name: 'ohs_sales_confirm_order', methods: ['POST'])] + public function confirmOrder(string $orderId): JsonResponse + { + ($this->confirmOrderHandler)(new ConfirmOrder(orderId: $orderId)); + + $order = ($this->getOrderByIdHandler)(new GetOrderById($orderId)); + + return new JsonResponse($this->assembler->toView($order)); + } + + #[Route('/customers/{customerId}/orders', name: 'ohs_sales_list_orders', methods: ['GET'])] + public function listOrders(string $customerId): JsonResponse + { + $orders = ($this->listOrdersByCustomerHandler)(new ListOrdersByCustomer($customerId)); + + return new JsonResponse(array_map( + fn ($order) => $this->assembler->toView($order), + $orders, + )); + } +} diff --git a/src/Sales/Interfaces/Http/Api/V1/OrderViewAssembler.php b/src/Sales/Interfaces/Http/Api/V1/OrderViewAssembler.php new file mode 100644 index 0000000..b3899cf --- /dev/null +++ b/src/Sales/Interfaces/Http/Api/V1/OrderViewAssembler.php @@ -0,0 +1,37 @@ +id->toString(), + customerId: $order->customerId->toString(), + status: $order->status()->value, + totalInCents: $order->total()->amount, + currency: $order->total()->currency, + lines: array_map( + static fn (OrderLine $line): array => [ + 'productName' => $line->productName, + 'quantity' => $line->quantity, + 'unitPriceInCents' => $line->unitPrice->amount, + 'currency' => $line->unitPrice->currency, + 'lineTotalInCents' => $line->lineTotal()->amount, + ], + $order->lines(), + ), + ); + } +} diff --git a/tests/Integration/Sales/OhsApiTest.php b/tests/Integration/Sales/OhsApiTest.php new file mode 100644 index 0000000..8219b36 --- /dev/null +++ b/tests/Integration/Sales/OhsApiTest.php @@ -0,0 +1,94 @@ + 'Widget', 'quantity' => 2, 'unitPriceInCents' => 1500, 'currency' => 'EUR'], + ], + )); + + $order = ($getHandler)(new GetOrderById($orderId)); + $view = $assembler->toView($order); + + // Verifie que le retour est un DTO du Published Language + self::assertInstanceOf(OrderView::class, $view); + self::assertSame($orderId, $view->orderId); + self::assertSame('placed', $view->status); + self::assertSame(3000, $view->totalInCents); + self::assertSame('EUR', $view->currency); + self::assertCount(1, $view->lines); + } + + public function test_order_view_does_not_expose_internal_model(): void + { + $assembler = new OrderViewAssembler(); + $orderRepo = new InMemoryOrderRepository(); + $clock = new SystemClock(); + $publisher = new class implements \MiniShop\Sales\Application\Port\SalesEventPublisher { + public function publishOrderPlaced(\MiniShop\Sales\Domain\Event\OrderPlaced $event): void {} + public function publishOrderConfirmed(\MiniShop\Sales\Domain\Event\OrderConfirmed $event): void {} + public function publishOrderCancelled(\MiniShop\Sales\Domain\Event\OrderCancelled $event): void {} + }; + + ($placeHandler = new PlaceOrderHandler($orderRepo, $publisher, $clock))(new PlaceOrder( + orderId: 'ohs-test-002', + customerId: 'cust-001', + lines: [['productName' => 'Widget', 'quantity' => 1, 'unitPriceInCents' => 1000, 'currency' => 'EUR']], + )); + + $order = (new GetOrderByIdHandler($orderRepo))(new GetOrderById('ohs-test-002')); + $view = $assembler->toView($order); + + $json = json_encode($view, JSON_THROW_ON_ERROR); + $decoded = json_decode($json, true, flags: JSON_THROW_ON_ERROR); + + // Le JSON ne doit contenir que les champs du Published Language + $allowedKeys = ['orderId', 'customerId', 'status', 'totalInCents', 'currency', 'lines']; + self::assertSame($allowedKeys, array_keys($decoded)); + + // Les lignes ne doivent pas contenir de types internes (Money, OrderLine) + $lineKeys = array_keys($decoded['lines'][0]); + self::assertContains('productName', $lineKeys); + self::assertContains('lineTotalInCents', $lineKeys); + self::assertNotContains('unitPrice', $lineKeys); // pas d'objet Money + } +}