InvoicerTest::extractOrderLines()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 20
rs 9.8333
c 0
b 0
f 0
cc 4
nc 3
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ApplicationTest\Service;
6
7
use Application\Enum\PaymentMethod;
8
use Application\Enum\ProductType;
9
use Application\Model\AbstractProduct;
10
use Application\Model\Order;
11
use Application\Model\OrderLine;
12
use Application\Model\Product;
13
use Application\Model\Subscription;
14
use Application\Service\Invoicer;
15
use ApplicationTest\Traits\TestWithTransactionAndUser;
16
use Money\Money;
17
use PHPUnit\Framework\TestCase;
18
19
class InvoicerTest extends TestCase
20
{
21
    use TestWithTransactionAndUser;
22
23
    /**
24
     * @dataProvider providerCreateOrder
25
     */
26
    public function testCreateOrder(array $input, array $expectedOrderLines): void
27
    {
28
        $input['orderLines'] = $this->hydrateTestData($input['orderLines']);
29
30
        global $container;
31
        /** @var Invoicer $invoicer */
32
        $invoicer = $container->get(Invoicer::class);
33
        $order = $invoicer->createOrder($input);
34
35
        $actualOrderLines = $this->extractOrderLines($order);
0 ignored issues
show
Bug introduced by
It seems like $order can also be of type null; however, parameter $order of ApplicationTest\Service\...st::extractOrderLines() does only seem to accept Application\Model\Order, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

35
        $actualOrderLines = $this->extractOrderLines(/** @scrutinizer ignore-type */ $order);
Loading history...
36
        self::assertSame($expectedOrderLines, $actualOrderLines);
37
    }
38
39
    /**
40
     * @dataProvider providerUpdateOrderLineAndTransactionLine
41
     */
42
    public function testUpdateOrderLineAndTransactionLine(string $originalOrder, ?array $newProduct, array $expectedOrderLines): void
43
    {
44
        $input = $this->providerCreateOrder()[$originalOrder][0];
45
        $input['orderLines'] = $this->hydrateTestData($input['orderLines']);
46
47
        global $container;
48
        /** @var Invoicer $invoicer */
49
        $invoicer = $container->get(Invoicer::class);
50
        $order = $invoicer->createOrder($input);
51
52
        if ($newProduct) {
53
            $product = $this->hydrateProduct($newProduct);
54
        } else {
55
            $product = $input['orderLines'][0]['product'];
56
        }
57
58
        $line = [
59
            'quantity' => 100,
60
            'isCHF' => true,
61
            'type' => ProductType::Digital,
62
            'product' => $product,
63
            'additionalEmails' => [],
64
        ];
65
66
        $invoicer->updateOrderLineAndTransactionLine($order->getOrderLines()->first(), $line);
67
68
        $actualOrderLines = $this->extractOrderLines($order);
0 ignored issues
show
Bug introduced by
It seems like $order can also be of type null; however, parameter $order of ApplicationTest\Service\...st::extractOrderLines() does only seem to accept Application\Model\Order, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

68
        $actualOrderLines = $this->extractOrderLines(/** @scrutinizer ignore-type */ $order);
Loading history...
69
        self::assertSame($expectedOrderLines, $actualOrderLines);
70
    }
71
72
    public function providerUpdateOrderLineAndTransactionLine(): iterable
73
    {
74
        yield 'more quantity of same product' => [
75
            'normal',
76
            null,
77
            [
78
                [
79
                    'My product 1',
80
                    100,
81
                    '27500',
82
                    '0',
83
                    true,
84
                    ProductType::Digital,
85
                ],
86
                [
87
                    'My product 2',
88
                    1,
89
                    '20000',
90
                    '0',
91
                    true,
92
                    ProductType::Digital,
93
                ],
94
            ],
95
        ];
96
        yield 'more quantity of different, negative product' => [
97
            'normal',
98
            [
99
                'name' => 'My negative product',
100
                'pricePerUnitCHF' => Money::CHF(-10000),
101
                'pricePerUnitEUR' => Money::EUR(-15000),
102
            ],
103
            [
104
                [
105
                    'My negative product',
106
                    100,
107
                    '-1000000',
108
                    '0',
109
                    true,
110
                    ProductType::Digital,
111
                ],
112
                [
113
                    'My product 2',
114
                    1,
115
                    '20000',
116
                    '0',
117
                    true,
118
                    ProductType::Digital,
119
                ],
120
            ],
121
        ];
122
        yield 'from negative goes back to positive' => [
123
            'negative balance should swap accounts',
124
            [
125
                'name' => 'My positive product',
126
                'pricePerUnitCHF' => Money::CHF(10000),
127
                'pricePerUnitEUR' => Money::EUR(15000),
128
            ],
129
            [
130
                [
131
                    'My positive product',
132
                    100,
133
                    '1000000',
134
                    '0',
135
                    true,
136
                    ProductType::Digital,
137
                ],
138
            ],
139
        ];
140
    }
141
142
    public function providerCreateOrder(): array
143
    {
144
        return [
145
            'free product should create order, even with transactions for zero dollars' => [
146
                [
147
                    'paymentMethod' => PaymentMethod::Bvr,
148
                    'orderLines' => [
149
                        [
150
                            'quantity' => 1,
151
                            'isCHF' => true,
152
                            'type' => ProductType::Digital,
153
                            'product' => [
154
                                'name' => 'My product',
155
                                'pricePerUnitCHF' => Money::CHF(0),
156
                                'pricePerUnitEUR' => Money::EUR(0),
157
                            ],
158
                            'additionalEmails' => [],
159
                        ],
160
                    ],
161
                ],
162
                [
163
                    [
164
                        'My product',
165
                        1,
166
                        '0',
167
                        '0',
168
                        true,
169
                        ProductType::Digital,
170
                    ],
171
                ],
172
            ],
173
            'normal' => [
174
                [
175
                    'paymentMethod' => PaymentMethod::Bvr,
176
                    'orderLines' => [
177
                        [
178
                            'quantity' => 3,
179
                            'isCHF' => true,
180
                            'type' => ProductType::Digital,
181
                            'product' => [
182
                                'name' => 'My product 1',
183
                                'pricePerUnitCHF' => Money::CHF(275),
184
                                'pricePerUnitEUR' => Money::EUR(280),
185
                            ],
186
                            'additionalEmails' => [],
187
                        ],
188
                        [
189
                            'quantity' => 1,
190
                            'isCHF' => true,
191
                            'type' => ProductType::Digital,
192
                            'product' => [
193
                                'name' => 'My product 2',
194
                                'pricePerUnitCHF' => Money::CHF(20000),
195
                                'pricePerUnitEUR' => Money::EUR(25000),
196
                            ],
197
                            'additionalEmails' => [],
198
                        ],
199
                    ],
200
                ],
201
                [
202
                    [
203
                        'My product 1',
204
                        3,
205
                        '825',
206
                        '0',
207
                        true,
208
                        ProductType::Digital,
209
                    ],
210
                    [
211
                        'My product 2',
212
                        1,
213
                        '20000',
214
                        '0',
215
                        true,
216
                        ProductType::Digital,
217
                    ],
218
                ],
219
            ],
220
            'with mixed CHF/EURO prices' => [
221
                [
222
                    'paymentMethod' => PaymentMethod::Bvr,
223
                    'orderLines' => [
224
                        [
225
                            'quantity' => 3,
226
                            'isCHF' => false,
227
                            'type' => ProductType::Digital,
228
                            'product' => [
229
                                'name' => 'My product 1',
230
                                'pricePerUnitCHF' => Money::CHF(275),
231
                                'pricePerUnitEUR' => Money::EUR(280),
232
                            ],
233
                            'additionalEmails' => [],
234
                        ],
235
                        [
236
                            'quantity' => 1,
237
                            'isCHF' => true,
238
                            'type' => ProductType::Paper,
239
                            'product' => [
240
                                'name' => 'My product 2',
241
                                'pricePerUnitCHF' => Money::CHF(20000),
242
                                'pricePerUnitEUR' => Money::EUR(25000),
243
                            ],
244
                            'additionalEmails' => [],
245
                        ],
246
                    ],
247
                ],
248
                [
249
                    [
250
                        'My product 1',
251
                        3,
252
                        '0',
253
                        '840',
254
                        false,
255
                        ProductType::Digital,
256
                    ],
257
                    [
258
                        'My product 2',
259
                        1,
260
                        '20000',
261
                        '0',
262
                        true,
263
                        ProductType::Paper,
264
                    ],
265
                ],
266
            ],
267
            'negative balance should swap accounts' => [
268
                [
269
                    'paymentMethod' => PaymentMethod::Bvr,
270
                    'orderLines' => [
271
                        [
272
                            'quantity' => 1,
273
                            'isCHF' => true,
274
                            'type' => ProductType::Digital,
275
                            'product' => [
276
                                'name' => 'My product',
277
                                'pricePerUnitCHF' => Money::CHF(-10000),
278
                                'pricePerUnitEUR' => Money::EUR(-15000),
279
                            ],
280
                            'additionalEmails' => [],
281
                        ],
282
                    ],
283
                ],
284
                [
285
                    [
286
                        'My product',
287
                        1,
288
                        '-10000',
289
                        '0',
290
                        true,
291
                        ProductType::Digital,
292
                    ],
293
                ],
294
            ],
295
            'can create order for subscription' => [
296
                [
297
                    'paymentMethod' => PaymentMethod::Bvr,
298
                    'orderLines' => [
299
                        [
300
                            'quantity' => 1,
301
                            'isCHF' => true,
302
                            'type' => ProductType::Digital,
303
                            'subscription' => [
304
                                'name' => 'My subscription',
305
                                'pricePerUnitCHF' => Money::CHF(10000),
306
                                'pricePerUnitEUR' => Money::EUR(15000),
307
                                'type' => ProductType::Both,
308
                            ],
309
                            'additionalEmails' => [],
310
                        ],
311
                    ],
312
                ],
313
                [
314
                    [
315
                        'My subscription',
316
                        1,
317
                        '10000',
318
                        '0',
319
                        true,
320
                        ProductType::Both,
321
                    ],
322
                ],
323
            ],
324
            'can create order for donation' => [
325
                [
326
                    'paymentMethod' => PaymentMethod::Bvr,
327
                    'orderLines' => [
328
                        [
329
                            'quantity' => 1,
330
                            'isCHF' => true,
331
                            'type' => ProductType::Digital,
332
                            'pricePerUnit' => Money::CHF(10000),
333
                            'additionalEmails' => [],
334
                        ],
335
                    ],
336
                ],
337
                [
338
                    [
339
                        'Don',
340
                        1,
341
                        '10000',
342
                        '0',
343
                        true,
344
                        ProductType::Digital,
345
                    ],
346
                ],
347
            ],
348
        ];
349
    }
350
351
    private function hydrateTestData(array $input): array
352
    {
353
        foreach ($input as &$i) {
354
            if (array_key_exists('product', $i)) {
355
                $i['product'] = $this->hydrateProduct($i['product']);
356
            }
357
            if (array_key_exists('subscription', $i)) {
358
                $i['subscription'] = $this->hydrateSubscription($i['subscription']);
359
            }
360
        }
361
362
        return $input;
363
    }
364
365
    private function extractOrderLines(Order $order): array
366
    {
367
        $actualOrderLines = [];
368
        /** @var OrderLine $orderLine */
369
        foreach ($order->getOrderLines() as $orderLine) {
370
            $abstractProduct = $orderLine->getProduct() ?: $orderLine->getSubscription();
371
            $expectedName = $abstractProduct ? $abstractProduct->getName() : 'Don';
372
            self::assertSame($expectedName, $orderLine->getName());
373
374
            $actualOrderLines[] = [
375
                $orderLine->getName(),
376
                $orderLine->getQuantity(),
377
                $orderLine->getBalanceCHF()->getAmount(),
378
                $orderLine->getBalanceEUR()->getAmount(),
379
                $orderLine->isCHF(),
380
                $orderLine->getType(),
381
            ];
382
        }
383
384
        return $actualOrderLines;
385
    }
386
387
    private function hydrateProduct(array $p): Product
388
    {
389
        $product = new Product();
390
        $this->hydrateAbstractProduct($product, $p);
391
392
        return $product;
393
    }
394
395
    private function hydrateSubscription(array $s): Subscription
396
    {
397
        $subscription = new Subscription();
398
        $this->hydrateAbstractProduct($subscription, $s);
399
400
        return $subscription;
401
    }
402
403
    private function hydrateAbstractProduct(AbstractProduct $product, array $p): void
404
    {
405
        $product->setName($p['name']);
406
        $product->setPricePerUnitCHF($p['pricePerUnitCHF']);
407
        $product->setPricePerUnitEUR($p['pricePerUnitEUR']);
408
        $product->setType($p['type'] ?? ProductType::Digital);
409
    }
410
}
411