From d1e179bc8571f50872d11641a39afaf4d8f3d59b Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 28 Aug 2026 17:11:05 +0200 Subject: [PATCH] test: add order service tests --- tests/OrderServiceTest.php | 119 +++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/OrderServiceTest.php diff --git a/tests/OrderServiceTest.php b/tests/OrderServiceTest.php new file mode 100644 index 0000000..5465123 --- /dev/null +++ b/tests/OrderServiceTest.php @@ -0,0 +1,119 @@ +orders = new OrderRepository($this->db); + $this->products = new ProductRepository($this->db); + $this->service = new OrderService($this->db, $this->orders, $this->products); + $this->userId = (new UserRepository($this->db))->create( + 'buyer@bazaar.test', + password_hash('correcthorse', PASSWORD_DEFAULT), + 'Buyer' + ); + } + + private function cartWith(int $productId, int $quantity): Cart + { + $cart = new Cart(new Session(), $this->products); + $cart->add($productId, $quantity); + return $cart; + } + + public function testCreateFromCartSnapshotsItemsAndTotal(): void + { + $productId = $this->seedProduct('Brass Pen', 6400, 10); + $orderId = $this->service->createFromCart($this->userId, $this->cartWith($productId, 2), 'usd'); + + $order = $this->orders->find($orderId); + $items = $this->orders->items($orderId); + + $this->assertSame('pending', $order['status']); + $this->assertSame(6400 * 2, (int) $order['totalCents']); + $this->assertCount(1, $items); + $this->assertSame('Brass Pen', $items[0]['productName']); + $this->assertSame(6400, (int) $items[0]['unitPriceCents']); + $this->assertSame(2, (int) $items[0]['quantity']); + } + + public function testCreateFromEmptyCartThrows(): void + { + $emptyCart = new Cart(new Session(), $this->products); + + $this->expectException(\RuntimeException::class); + $this->service->createFromCart($this->userId, $emptyCart, 'usd'); + } + + public function testMarkPaidTransitionsAndDecrementsStock(): void + { + $productId = $this->seedProduct('Soy Candle', 2200, 8); + $orderId = $this->service->createFromCart($this->userId, $this->cartWith($productId, 3), 'usd'); + + $this->service->markPaid($orderId, 'cs_test_123'); + + $order = $this->orders->find($orderId); + $this->assertSame('paid', $order['status']); + $this->assertNotEmpty($order['paidAt']); + $this->assertSame('cs_test_123', $order['stripeSessionId']); + $this->assertSame(5, (int) $this->products->find($productId)['stockQuantity']); + } + + public function testMarkPaidIsIdempotent(): void + { + $productId = $this->seedProduct('Planter', 2900, 6); + $orderId = $this->service->createFromCart($this->userId, $this->cartWith($productId, 2), 'usd'); + + $this->service->markPaid($orderId); + $this->service->markPaid($orderId); + + $this->assertSame(4, (int) $this->products->find($productId)['stockQuantity']); + } + + public function testMarkPaidFailsWhenStockDisappeared(): void + { + $productId = $this->seedProduct('Kettle', 5900, 4); + $orderId = $this->service->createFromCart($this->userId, $this->cartWith($productId, 4), 'usd'); + $this->products->decrementStock($productId, 3); + + $this->expectException(InsufficientStockException::class); + $this->service->markPaid($orderId); + } + + public function testMarkFulfilledOnlyFromPaid(): void + { + $productId = $this->seedProduct('Organizer', 2600, 10); + $orderId = $this->service->createFromCart($this->userId, $this->cartWith($productId, 1), 'usd'); + + $pendingFailed = false; + try { + $this->service->markFulfilled($orderId); + } catch (\RuntimeException) { + $pendingFailed = true; + } + $this->assertTrue($pendingFailed, 'A pending order must not be fulfillable'); + + $this->service->markPaid($orderId); + $this->service->markFulfilled($orderId); + + $order = $this->orders->find($orderId); + $this->assertSame('fulfilled', $order['status']); + $this->assertNotEmpty($order['fulfilledAt']); + } +}