1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace Tests\Models\Store;
7
8use App\Exceptions\InsufficientStockException;
9use App\Exceptions\InvariantException;
10use App\Models\Store\OrderItem;
11use App\Models\Store\Product;
12use Tests\TestCase;
13
14class OrderItemTest extends TestCase
15{
16 /**
17 * @dataProvider deleteDataProvider
18 */
19 public function testDelete(string $status, ?string $expectedException)
20 {
21 [, $orderItem] = $this->createProductAndOrderItem(5, false);
22
23 $orderItem->order->update(['status' => $status]);
24
25 if ($expectedException === null) {
26 $this->expectNotToPerformAssertions();
27 } else {
28 $this->expectException($expectedException);
29 }
30
31 $orderItem->delete();
32 }
33
34 public function testReserveUnreservedProduct()
35 {
36 [$product, $orderItem] = $this->createProductAndOrderItem(5, false);
37
38 $this->expectCountChange(fn () => $product->fresh()->stock, -2);
39
40 $orderItem->reserveProduct();
41
42 $this->assertTrue($orderItem->fresh()->reserved);
43 }
44
45 public function testReserveReservedProduct()
46 {
47 [$product, $orderItem] = $this->createProductAndOrderItem(5, true);
48
49 $this->expectCountChange(fn () => $product->fresh()->stock, 0);
50
51 $orderItem->reserveProduct();
52
53 $this->assertTrue($orderItem->fresh()->reserved);
54 }
55
56 public function testReleaseUnreservedProduct()
57 {
58 [$product, $orderItem] = $this->createProductAndOrderItem(5, false);
59
60 $this->expectCountChange(fn () => $product->fresh()->stock, 0);
61
62 $orderItem->releaseProduct();
63
64 $this->assertFalse($orderItem->fresh()->reserved);
65 }
66
67 public function testReleaseReservedProduct()
68 {
69 [$product, $orderItem] = $this->createProductAndOrderItem(5, true);
70
71 $this->expectCountChange(fn () => $product->fresh()->stock, 2);
72
73 $orderItem->releaseProduct();
74
75 $this->assertFalse($orderItem->fresh()->reserved);
76 }
77
78 public function testReserveInsufficientStock()
79 {
80 [, $orderItem] = $this->createProductAndOrderItem(1, false);
81
82 $this->expectException(InsufficientStockException::class);
83 $orderItem->reserveProduct();
84 }
85
86 public function testReleaseWhenStockIsZero()
87 {
88 [$product, $orderItem] = $this->createProductAndOrderItem(0, true);
89
90 $this->expectCountChange(fn () => $product->fresh()->stock, 0);
91
92 $orderItem->releaseProduct();
93
94 $this->assertFalse($orderItem->fresh()->reserved);
95 }
96
97 public static function deleteDataProvider()
98 {
99 return [
100 ['checkout', InvariantException::class],
101 ['incart', null],
102 ['processing', InvariantException::class],
103 ];
104 }
105
106 private function createProductAndOrderItem(int $stock, bool $reserved)
107 {
108 $product = Product::factory()->create(['stock' => $stock, 'max_quantity' => 5]);
109 $orderItem = OrderItem::factory()->create([
110 'product_id' => $product,
111 'quantity' => 2,
112 'reserved' => $reserved,
113 ]);
114
115 return [$product, $orderItem];
116 }
117}