1 | <?php |
||||
2 | |||||
3 | namespace SilverShop\Tests\Model; |
||||
4 | |||||
5 | use SilverShop\Checkout\OrderProcessor; |
||||
6 | use SilverShop\Model\Address; |
||||
7 | use SilverShop\Model\Modifiers\OrderModifier; |
||||
8 | use SilverShop\Model\Modifiers\Tax\FlatTax; |
||||
9 | use SilverShop\Model\Order; |
||||
10 | use SilverShop\Model\OrderStatusLog; |
||||
11 | use SilverShop\Model\Product\OrderItem; |
||||
12 | use SilverShop\Page\Product; |
||||
13 | use SilverShop\Tests\Model\Product\CustomProduct_OrderItem; |
||||
14 | use SilverShop\Tests\ShopTest; |
||||
15 | use SilverStripe\Core\Config\Config; |
||||
16 | use SilverStripe\Dev\SapphireTest; |
||||
17 | use SilverStripe\Omnipay\Model\Payment; |
||||
18 | use SilverStripe\ORM\DataObject; |
||||
19 | |||||
20 | /** |
||||
21 | * Order Unit Tests |
||||
22 | * |
||||
23 | * @package silvershop |
||||
24 | * @subpackage tests |
||||
25 | */ |
||||
26 | class OrderTest extends SapphireTest |
||||
27 | { |
||||
28 | public static $fixture_file = __DIR__ . '/../Fixtures/shop.yml'; |
||||
29 | |||||
30 | // This seems to be required, because we query the OrderItem table and thus this gets included… |
||||
31 | // TODO: Remove once we figure out how to circumvent that… |
||||
32 | protected static $extra_dataobjects = [ |
||||
33 | CustomProduct_OrderItem::class, |
||||
34 | ]; |
||||
35 | |||||
36 | /** |
||||
37 | * @var Product |
||||
38 | */ |
||||
39 | protected $mp3player; |
||||
40 | |||||
41 | /** |
||||
42 | * @var Product |
||||
43 | */ |
||||
44 | protected $socks; |
||||
45 | |||||
46 | /** |
||||
47 | * @var Product |
||||
48 | */ |
||||
49 | protected $beachball; |
||||
50 | |||||
51 | |||||
52 | public function setUp() |
||||
53 | { |
||||
54 | parent::setUp(); |
||||
55 | ShopTest::setConfiguration(); |
||||
56 | $this->logInWithPermission('ADMIN'); |
||||
57 | $this->mp3player = $this->objFromFixture(Product::class, 'mp3player'); |
||||
58 | $this->mp3player->publishSingle(); |
||||
59 | $this->socks = $this->objFromFixture(Product::class, 'socks'); |
||||
60 | $this->socks->publishSingle(); |
||||
61 | $this->beachball = $this->objFromFixture(Product::class, 'beachball'); |
||||
62 | $this->beachball->publishSingle(); |
||||
63 | } |
||||
64 | |||||
65 | public function tearDown() |
||||
66 | { |
||||
67 | parent::tearDown(); |
||||
68 | unset($this->mp3player); |
||||
69 | unset($this->socks); |
||||
70 | unset($this->beachball); |
||||
71 | } |
||||
72 | |||||
73 | public function testCMSFields() |
||||
74 | { |
||||
75 | //singleton(Order::class)->getCMSFields(); |
||||
76 | $this->markTestIncomplete('assertions!'); |
||||
77 | } |
||||
78 | |||||
79 | public function testSearchFields() |
||||
80 | { |
||||
81 | //singleton(Order::class)->scaffoldSearchFields(); |
||||
82 | $this->markTestIncomplete('assertions!'); |
||||
83 | } |
||||
84 | |||||
85 | public function testDebug() |
||||
86 | { |
||||
87 | //$order = $this->objFromFixture(Order::class, "cart"); |
||||
88 | //$order->debug(); |
||||
89 | $this->markTestIncomplete('assertions!'); |
||||
90 | } |
||||
91 | |||||
92 | public function testOrderItems() |
||||
93 | { |
||||
94 | $order = $this->objFromFixture(Order::class, "paid"); |
||||
95 | $items = $order->Items(); |
||||
96 | $this->assertNotNull($items); |
||||
97 | $this->assertListEquals( |
||||
98 | array( |
||||
99 | array('ProductID' => $this->mp3player->ID, 'Quantity' => 2, 'CalculatedTotal' => 400), |
||||
100 | array('ProductID' => $this->socks->ID, 'Quantity' => 1, 'CalculatedTotal' => 8), |
||||
101 | ), |
||||
102 | $items |
||||
103 | ); |
||||
104 | $this->assertEquals(3, $items->Quantity(), "Quantity is 3"); |
||||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
105 | $this->assertTrue($items->Plural(), "There is more than one item"); |
||||
0 ignored issues
–
show
The method
Plural() does not exist on SilverStripe\ORM\SS_List . It seems like you code against a sub-type of said class. However, the method does not exist in SilverStripe\ORM\Sortable or SilverStripe\ORM\Filterable or SilverStripe\ORM\Relation or SilverStripe\ORM\Limitable or SilverStripe\ORM\Relation or SilverStripe\ORM\Relation or SilverStripe\ORM\Relation . Are you sure you never get one of those?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
106 | $this->assertEquals(0.7, $items->Sum('Weight', true), "Total order weight sums correctly", 0.0001); |
||||
0 ignored issues
–
show
The method
Sum() does not exist on SilverStripe\ORM\SS_List . It seems like you code against a sub-type of said class. However, the method does not exist in SilverStripe\ORM\Sortable or SilverStripe\ORM\Filterable or SilverStripe\ORM\Relation or SilverStripe\ORM\Limitable or SilverStripe\ORM\Relation or SilverStripe\ORM\Relation or SilverStripe\ORM\Relation . Are you sure you never get one of those?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
107 | } |
||||
108 | |||||
109 | public function testTotals() |
||||
110 | { |
||||
111 | $order = $this->objFromFixture(Order::class, "paid"); |
||||
112 | $this->assertEquals(408, $order->SubTotal(), "Subtotal is correct"); // 200 + 200 + 8 |
||||
113 | $this->assertEquals(408, $order->GrandTotal(), "Grand total is correct"); |
||||
114 | $this->assertEquals(200, $order->TotalPaid(), "Outstanding total is correct"); |
||||
115 | $this->assertEquals(208, $order->TotalOutstanding(), "Outstanding total is correct"); |
||||
116 | } |
||||
117 | |||||
118 | public function testRounding() |
||||
119 | { |
||||
120 | //create an order with unrounded total |
||||
121 | $order = new Order( |
||||
122 | array( |
||||
123 | 'Total' => 123.257323, |
||||
124 | //NOTE: setTotal isn't called here, so un-rounded data *could* get in to the object |
||||
125 | 'Status' => 'Unpaid', |
||||
126 | ) |
||||
127 | ); |
||||
128 | $order->Total = 123.257323; //setTotal IS called here |
||||
0 ignored issues
–
show
It seems like
123.257323 of type double is incompatible with the declared type SilverStripe\ORM\FieldType\DBCurrency of property $Total .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..
Loading history...
|
|||||
129 | $this->assertEquals(123.26, $order->Total(), "Check total rounds appropriately"); |
||||
130 | $this->assertEquals(123.26, $order->TotalOutstanding(), "Check total outstanding rounds appropriately"); |
||||
131 | } |
||||
132 | |||||
133 | public function testPlacedOrderImmutability() |
||||
134 | { |
||||
135 | |||||
136 | $order = $this->objFromFixture(Order::class, "paid"); |
||||
137 | $processor = OrderProcessor::create($order)->placeOrder(); |
||||
0 ignored issues
–
show
|
|||||
138 | $this->assertEquals(408, $order->Total(), "check totals"); |
||||
139 | |||||
140 | //make a changes to existing products |
||||
141 | $this->mp3player->BasePrice = 100; |
||||
0 ignored issues
–
show
It seems like
100 of type integer is incompatible with the declared type SilverStripe\ORM\FieldType\DBCurrency of property $BasePrice .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..
Loading history...
|
|||||
142 | $this->mp3player->write(); |
||||
143 | $this->socks->BasePrice = 20; |
||||
0 ignored issues
–
show
It seems like
20 of type integer is incompatible with the declared type SilverStripe\ORM\FieldType\DBCurrency of property $BasePrice .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..
Loading history...
|
|||||
144 | $this->socks->write(); |
||||
145 | |||||
146 | //total doesn't change |
||||
147 | $this->assertEquals(408, $order->Total()); |
||||
148 | $this->assertFalse($order->isCart()); |
||||
149 | |||||
150 | //item values don't change |
||||
151 | $items = $order->Items() |
||||
152 | //hack join to make thigns work |
||||
153 | ->innerJoin( |
||||
154 | "SilverShop_Product_OrderItem", |
||||
155 | '"SilverShop_OrderItem"."ID" = "SilverShop_Product_OrderItem"."ID"' |
||||
156 | ); |
||||
157 | $this->assertNotNull($items); |
||||
158 | $this->assertListEquals( |
||||
159 | array( |
||||
160 | array('ProductID' => $this->mp3player->ID, 'Quantity' => 2, 'CalculatedTotal' => 400), |
||||
161 | array('ProductID' => $this->socks->ID, 'Quantity' => 1, 'CalculatedTotal' => 8), |
||||
162 | ), |
||||
163 | $items |
||||
164 | ); |
||||
165 | |||||
166 | $mp3player = $items->find('ProductID', $this->mp3player->ID);//join needed to provide ProductID |
||||
167 | $this->assertNotNull($mp3player, "MP3 player is in order"); |
||||
168 | $this->assertEquals(200, $mp3player->UnitPrice(), "Unit price remains the same"); |
||||
169 | $this->assertEquals(400, $mp3player->Total(), "Total remains the same"); |
||||
170 | |||||
171 | $socks = $items->find('ProductID', $this->socks->ID); |
||||
172 | $this->assertNotNull($socks, "Socks are in order"); |
||||
173 | $this->assertEquals(8, $socks->UnitPrice(), "Unit price remains the same"); |
||||
174 | $this->assertEquals(8, $socks->Total(), "Total remains the same"); |
||||
175 | } |
||||
176 | |||||
177 | public function testCanFunctions() |
||||
178 | { |
||||
179 | $order = $this->objFromFixture(Order::class, "cart"); |
||||
180 | $order->calculate(); |
||||
181 | $this->assertTrue($order->canPay(), "can pay when order is in cart"); |
||||
182 | $this->assertFalse($order->canCancel(), "can't cancel when order is in cart"); |
||||
183 | $this->assertFalse($order->canDelete(), "never allow deleting orders"); |
||||
184 | $this->assertTrue($order->canEdit(), "orders can be edited by anyone"); |
||||
185 | $this->assertFalse($order->canCreate(), "no body can create orders manually"); |
||||
186 | |||||
187 | $order = $this->objFromFixture(Order::class, "unpaid"); |
||||
188 | $this->assertTrue($order->canPay(), "can pay an order that is unpaid"); |
||||
189 | $this->assertTrue($order->canCancel()); |
||||
190 | $this->assertFalse($order->canDelete(), "never allow deleting orders"); |
||||
191 | |||||
192 | // Override config |
||||
193 | Config::modify()->set(Order::class, 'cancel_before_payment', false); |
||||
194 | $this->assertFalse($order->canCancel()); |
||||
195 | |||||
196 | $order = $this->objFromFixture(Order::class, "paid"); |
||||
197 | $this->assertFalse($order->canPay(), "paid order can't be paid for"); |
||||
198 | $this->assertFalse($order->canCancel(), "paid order can't be cancelled"); |
||||
199 | $this->assertFalse($order->canDelete(), "never allow deleting orders"); |
||||
200 | |||||
201 | Config::modify()->set(Order::class, 'cancel_before_processing', true); |
||||
202 | $this->assertTrue($order->canCancel(), "paid order can be cancelled when expcicitly set via config"); |
||||
203 | |||||
204 | $order->Status = 'Processing'; |
||||
205 | $this->assertFalse($order->canPay(), "Processing order can't be paid for"); |
||||
206 | $this->assertFalse($order->canCancel(), "Processing order can't be cancelled"); |
||||
207 | $this->assertFalse($order->canDelete(), "never allow deleting orders"); |
||||
208 | |||||
209 | Config::modify()->set(Order::class, 'cancel_before_sending', true); |
||||
210 | $this->assertTrue($order->canCancel(), "Processing order can be cancelled when expcicitly set via config"); |
||||
211 | |||||
212 | $order->Status = 'Sent'; |
||||
213 | $this->assertFalse($order->canPay(), "Sent order can't be paid for"); |
||||
214 | $this->assertFalse($order->canCancel(), "Sent order can't be cancelled"); |
||||
215 | $this->assertFalse($order->canDelete(), "never allow deleting orders"); |
||||
216 | |||||
217 | Config::modify()->set(Order::class, 'cancel_after_sending', true); |
||||
218 | $this->assertTrue($order->canCancel(), "Sent order can be cancelled when expcicitly set via config"); |
||||
219 | Config::modify()->set(Order::class, 'cancel_after_sending', false); |
||||
220 | |||||
221 | $order->Status = 'Complete'; |
||||
222 | $this->assertFalse($order->canPay(), "Complete order can't be paid for"); |
||||
223 | $this->assertFalse($order->canCancel(), "Complete order can't be cancelled"); |
||||
224 | $this->assertFalse($order->canDelete(), "never allow deleting orders"); |
||||
225 | |||||
226 | Config::modify()->set(Order::class, 'cancel_after_sending', true); |
||||
227 | $this->assertTrue($order->canCancel(), "Completed order can be cancelled when expcicitly set via config"); |
||||
228 | |||||
229 | $order->Status = 'AdminCancelled'; |
||||
230 | $this->assertFalse($order->canPay(), "Cancelled order can't be paid for"); |
||||
231 | $this->assertFalse($order->canCancel(), "Cancelled order can't be cancelled"); |
||||
232 | $this->assertFalse($order->canDelete(), "never allow deleting orders"); |
||||
233 | |||||
234 | $order->Status = 'MemberCancelled'; |
||||
235 | $this->assertFalse($order->canPay(), "Cancelled order can't be paid for"); |
||||
236 | $this->assertFalse($order->canCancel(), "Cancelled order can't be cancelled"); |
||||
237 | $this->assertFalse($order->canDelete(), "never allow deleting orders"); |
||||
238 | } |
||||
239 | |||||
240 | public function testDelete() |
||||
241 | { |
||||
242 | Config::modify() |
||||
243 | ->set(FlatTax::class, 'rate', 0.25) |
||||
244 | ->merge(Order::class, 'modifiers', [FlatTax::class]); |
||||
245 | |||||
246 | $order = Order::create(); |
||||
247 | $shirt = $this->objFromFixture(Product::class, "tshirt"); |
||||
248 | $mp3player = $this->objFromFixture(Product::class, "mp3player"); |
||||
249 | $order->Items()->add($shirt->createItem(3)); |
||||
250 | $order->Items()->add($mp3player->createItem(1)); |
||||
251 | $order->write(); |
||||
252 | $order->calculate(); |
||||
253 | |||||
254 | $statusLogId = OrderStatusLog::create()->update( |
||||
255 | [ |
||||
256 | 'Title' => 'Test status log', |
||||
257 | 'OrderID' => $order->ID |
||||
258 | ] |
||||
259 | )->write(); |
||||
260 | |||||
261 | $paymentId = Payment::create()->update( |
||||
262 | [ |
||||
263 | 'OrderID' => $order->ID |
||||
264 | ] |
||||
265 | )->init('Manual', 343.75, 'NZD')->write(); |
||||
266 | |||||
267 | |||||
268 | $this->assertEquals(4, $order->Items()->Quantity()); |
||||
269 | $this->assertEquals(1, $order->Modifiers()->count()); |
||||
270 | $this->assertEquals(1, $order->OrderStatusLogs()->count()); |
||||
271 | $this->assertEquals(1, $order->Payments()->count()); |
||||
272 | |||||
273 | $itemIds = OrderItem::get()->filter('OrderID', $order->ID)->column('ID'); |
||||
274 | $modifierIds = OrderModifier::get()->filter('OrderID', $order->ID)->column('ID'); |
||||
275 | |||||
276 | $order->delete(); |
||||
277 | |||||
278 | // Items should no longer be linked to order |
||||
279 | $this->assertEquals(0, $order->Items()->count()); |
||||
280 | $this->assertEquals(0, $order->Modifiers()->count()); |
||||
281 | $this->assertEquals(0, $order->OrderStatusLogs()->count()); |
||||
282 | $this->assertEquals(0, $order->Payments()->count()); |
||||
283 | |||||
284 | // Ensure the order items have been deleted! |
||||
285 | $this->assertEquals(0, OrderItem::get()->filter('ID', $itemIds)->count()); |
||||
286 | $this->assertEquals(0, OrderModifier::get()->filter('ID', $modifierIds)->count()); |
||||
287 | $this->assertEquals(0, OrderStatusLog::get()->filter('ID', $statusLogId)->count()); |
||||
288 | |||||
289 | // Keep the payment… it might be relevant for book keeping |
||||
290 | $this->assertEquals(1, Payment::get()->filter('ID', $paymentId)->count()); |
||||
291 | } |
||||
292 | |||||
293 | public function testStatusChange() |
||||
294 | { |
||||
295 | Config::modify()->merge(Order::class, 'extensions', [OrderTest_TestStatusChangeExtension::class]); |
||||
296 | |||||
297 | $order = Order::create(); |
||||
298 | $orderId = $order->write(); |
||||
299 | |||||
300 | $order->Status = 'Unpaid'; |
||||
301 | $order->write(); |
||||
302 | |||||
303 | $this->assertEquals( |
||||
304 | array( |
||||
305 | array('Cart' => 'Unpaid') |
||||
306 | ), |
||||
307 | OrderTest_TestStatusChangeExtension::$stack |
||||
308 | ); |
||||
309 | |||||
310 | OrderTest_TestStatusChangeExtension::reset(); |
||||
311 | |||||
312 | $order = Order::get()->byID($orderId); |
||||
313 | $order->Status = 'Paid'; |
||||
314 | $order->write(); |
||||
315 | |||||
316 | $this->assertEquals( |
||||
317 | array( |
||||
318 | array('Unpaid' => 'Paid') |
||||
319 | ), |
||||
320 | OrderTest_TestStatusChangeExtension::$stack |
||||
321 | ); |
||||
322 | |||||
323 | $this->assertTrue((boolean)$order->Paid, 'Order paid date should be set'); |
||||
324 | } |
||||
325 | |||||
326 | public function testOrderAddress() |
||||
327 | { |
||||
328 | $order = $this->objFromFixture(Order::class, 'paid'); |
||||
329 | |||||
330 | // assert that order doesn't contain user information |
||||
331 | $this->assertNull($order->FirstName); |
||||
332 | $this->assertNull($order->Surname); |
||||
333 | $this->assertNull($order->Email); |
||||
334 | |||||
335 | // The shipping address should use the members default shipping address |
||||
336 | $this->assertEquals( |
||||
337 | 'Joe Bloggs, 12 Foo Street, Bar, Farmville, New Sandwich, US', |
||||
338 | $order->getShippingAddress()->toString() |
||||
339 | ); |
||||
340 | |||||
341 | $address = $this->objFromFixture(Address::class, 'pukekohe'); |
||||
342 | $order->ShippingAddressID = $address->ID; |
||||
343 | $order->write(); |
||||
344 | |||||
345 | // Address doesn't have firstname and surname |
||||
346 | $this->assertNull(Address::get()->byID($order->ShippingAddressID)->FirstName); |
||||
347 | $this->assertNull(Address::get()->byID($order->ShippingAddressID)->Surname); |
||||
348 | |||||
349 | // Shipping address should contain the name from the member object and new address information |
||||
350 | $this->assertEquals( |
||||
351 | 'Joe Bloggs, 1 Queen Street, Pukekohe, Auckland, 2120', |
||||
352 | $order->getShippingAddress()->toString() |
||||
353 | ); |
||||
354 | |||||
355 | // changing fields on the Order will have precendence! |
||||
356 | $order->FirstName = 'Tester'; |
||||
357 | $order->Surname = 'Mc. Testerson'; |
||||
358 | $order->write(); |
||||
359 | |||||
360 | // Reset caches, otherwise the previously set name will persist (eg. Joe Bloggs) |
||||
361 | DataObject::reset(); |
||||
362 | |||||
363 | $this->assertEquals( |
||||
364 | 'Tester Mc. Testerson, 1 Queen Street, Pukekohe, Auckland, 2120', |
||||
365 | $order->getShippingAddress()->toString() |
||||
366 | ); |
||||
367 | } |
||||
368 | } |
||||
369 |