Completed
Push — master ( 3c661d...2a57f9 )
by Will
26s queued 12s
created

tests/php/Model/OrderTest.php (7 issues)

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
The method Quantity() 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 ignore-call  annotation

104
        $this->assertEquals(3, $items->/** @scrutinizer ignore-call */ Quantity(), "Quantity is 3");
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 ignore-call  annotation

105
        $this->assertTrue($items->/** @scrutinizer ignore-call */ Plural(), "There is more than one item");
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 ignore-call  annotation

106
        $this->assertEquals(0.7, $items->/** @scrutinizer ignore-call */ Sum('Weight', true), "Total order weight sums correctly", 0.0001);
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
Documentation Bug introduced by
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
The assignment to $processor is dead and can be removed.
Loading history...
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
Documentation Bug introduced by
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
Documentation Bug introduced by
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