Issues (46)

tests/ApplicationTest/Service/InvoicerTest.php (2 issues)

Labels
Severity
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
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
    public static function providerCreateOrder(): iterable
40
    {
41
        return [
42
            'free product should create order, even with transactions for zero dollars' => [
43
                [
44
                    'paymentMethod' => PaymentMethod::Bvr,
45
                    'orderLines' => [
46
                        [
47
                            'quantity' => 1,
48
                            'isCHF' => true,
49
                            'type' => ProductType::Digital,
50
                            'product' => [
51
                                'name' => 'My product',
52
                                'pricePerUnitCHF' => Money::CHF(0),
53
                                'pricePerUnitEUR' => Money::EUR(0),
54
                            ],
55
                            'additionalEmails' => [],
56
                        ],
57
                    ],
58
                ],
59
                [
60
                    [
61
                        'My product',
62
                        1,
63
                        '0',
64
                        '0',
65
                        true,
66
                        ProductType::Digital,
67
                    ],
68
                ],
69
            ],
70
            'normal' => [
71
                [
72
                    'paymentMethod' => PaymentMethod::Bvr,
73
                    'orderLines' => [
74
                        [
75
                            'quantity' => 3,
76
                            'isCHF' => true,
77
                            'type' => ProductType::Digital,
78
                            'product' => [
79
                                'name' => 'My product 1',
80
                                'pricePerUnitCHF' => Money::CHF(275),
81
                                'pricePerUnitEUR' => Money::EUR(280),
82
                            ],
83
                            'additionalEmails' => [],
84
                        ],
85
                        [
86
                            'quantity' => 1,
87
                            'isCHF' => true,
88
                            'type' => ProductType::Digital,
89
                            'product' => [
90
                                'name' => 'My product 2',
91
                                'pricePerUnitCHF' => Money::CHF(20000),
92
                                'pricePerUnitEUR' => Money::EUR(25000),
93
                            ],
94
                            'additionalEmails' => [],
95
                        ],
96
                    ],
97
                ],
98
                [
99
                    [
100
                        'My product 1',
101
                        3,
102
                        '825',
103
                        '0',
104
                        true,
105
                        ProductType::Digital,
106
                    ],
107
                    [
108
                        'My product 2',
109
                        1,
110
                        '20000',
111
                        '0',
112
                        true,
113
                        ProductType::Digital,
114
                    ],
115
                ],
116
            ],
117
            'with mixed CHF/EURO prices' => [
118
                [
119
                    'paymentMethod' => PaymentMethod::Bvr,
120
                    'orderLines' => [
121
                        [
122
                            'quantity' => 3,
123
                            'isCHF' => false,
124
                            'type' => ProductType::Digital,
125
                            'product' => [
126
                                'name' => 'My product 1',
127
                                'pricePerUnitCHF' => Money::CHF(275),
128
                                'pricePerUnitEUR' => Money::EUR(280),
129
                            ],
130
                            'additionalEmails' => [],
131
                        ],
132
                        [
133
                            'quantity' => 1,
134
                            'isCHF' => true,
135
                            'type' => ProductType::Paper,
136
                            'product' => [
137
                                'name' => 'My product 2',
138
                                'pricePerUnitCHF' => Money::CHF(20000),
139
                                'pricePerUnitEUR' => Money::EUR(25000),
140
                            ],
141
                            'additionalEmails' => [],
142
                        ],
143
                    ],
144
                ],
145
                [
146
                    [
147
                        'My product 1',
148
                        3,
149
                        '0',
150
                        '840',
151
                        false,
152
                        ProductType::Digital,
153
                    ],
154
                    [
155
                        'My product 2',
156
                        1,
157
                        '20000',
158
                        '0',
159
                        true,
160
                        ProductType::Paper,
161
                    ],
162
                ],
163
            ],
164
            'negative balance should swap accounts' => [
165
                [
166
                    'paymentMethod' => PaymentMethod::Bvr,
167
                    'orderLines' => [
168
                        [
169
                            'quantity' => 1,
170
                            'isCHF' => true,
171
                            'type' => ProductType::Digital,
172
                            'product' => [
173
                                'name' => 'My product',
174
                                'pricePerUnitCHF' => Money::CHF(-10000),
175
                                'pricePerUnitEUR' => Money::EUR(-15000),
176
                            ],
177
                            'additionalEmails' => [],
178
                        ],
179
                    ],
180
                ],
181
                [
182
                    [
183
                        'My product',
184
                        1,
185
                        '-10000',
186
                        '0',
187
                        true,
188
                        ProductType::Digital,
189
                    ],
190
                ],
191
            ],
192
            'can create order for subscription' => [
193
                [
194
                    'paymentMethod' => PaymentMethod::Bvr,
195
                    'orderLines' => [
196
                        [
197
                            'quantity' => 1,
198
                            'isCHF' => true,
199
                            'type' => ProductType::Digital,
200
                            'subscription' => [
201
                                'name' => 'My subscription',
202
                                'pricePerUnitCHF' => Money::CHF(10000),
203
                                'pricePerUnitEUR' => Money::EUR(15000),
204
                                'type' => ProductType::Both,
205
                            ],
206
                            'additionalEmails' => [],
207
                        ],
208
                    ],
209
                ],
210
                [
211
                    [
212
                        'My subscription',
213
                        1,
214
                        '10000',
215
                        '0',
216
                        true,
217
                        ProductType::Both,
218
                    ],
219
                ],
220
            ],
221
            'can create order for donation' => [
222
                [
223
                    'paymentMethod' => PaymentMethod::Bvr,
224
                    'orderLines' => [
225
                        [
226
                            'quantity' => 1,
227
                            'isCHF' => true,
228
                            'type' => ProductType::Digital,
229
                            'pricePerUnit' => Money::CHF(10000),
230
                            'additionalEmails' => [],
231
                        ],
232
                    ],
233
                ],
234
                [
235
                    [
236
                        'Don',
237
                        1,
238
                        '10000',
239
                        '0',
240
                        true,
241
                        ProductType::Digital,
242
                    ],
243
                ],
244
            ],
245
        ];
246
    }
247
248
    /**
249
     * @dataProvider providerUpdateOrderLineAndTransactionLine
250
     */
251
    public function testUpdateOrderLineAndTransactionLine(string $originalOrder, ?array $newProduct, array $expectedOrderLines): void
252
    {
253
        $input = $this->providerCreateOrder()[$originalOrder][0];
254
        $input['orderLines'] = $this->hydrateTestData($input['orderLines']);
255
256
        global $container;
257
        /** @var Invoicer $invoicer */
258
        $invoicer = $container->get(Invoicer::class);
259
        $order = $invoicer->createOrder($input);
260
261
        if ($newProduct) {
262
            $product = $this->hydrateProduct($newProduct);
263
        } else {
264
            $product = $input['orderLines'][0]['product'];
265
        }
266
267
        $line = [
268
            'quantity' => 100,
269
            'isCHF' => true,
270
            'type' => ProductType::Digital,
271
            'product' => $product,
272
            'additionalEmails' => [],
273
        ];
274
275
        $invoicer->updateOrderLineAndTransactionLine($order->getOrderLines()->first(), $line);
276
277
        $actualOrderLines = $this->extractOrderLines($order);
0 ignored issues
show
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

277
        $actualOrderLines = $this->extractOrderLines(/** @scrutinizer ignore-type */ $order);
Loading history...
278
        self::assertSame($expectedOrderLines, $actualOrderLines);
279
    }
280
281
    public static function providerUpdateOrderLineAndTransactionLine(): iterable
282
    {
283
        yield 'more quantity of same product' => [
284
            'normal',
285
            null,
286
            [
287
                [
288
                    'My product 1',
289
                    100,
290
                    '27500',
291
                    '0',
292
                    true,
293
                    ProductType::Digital,
294
                ],
295
                [
296
                    'My product 2',
297
                    1,
298
                    '20000',
299
                    '0',
300
                    true,
301
                    ProductType::Digital,
302
                ],
303
            ],
304
        ];
305
        yield 'more quantity of different, negative product' => [
306
            'normal',
307
            [
308
                'name' => 'My negative product',
309
                'pricePerUnitCHF' => Money::CHF(-10000),
310
                'pricePerUnitEUR' => Money::EUR(-15000),
311
            ],
312
            [
313
                [
314
                    'My negative product',
315
                    100,
316
                    '-1000000',
317
                    '0',
318
                    true,
319
                    ProductType::Digital,
320
                ],
321
                [
322
                    'My product 2',
323
                    1,
324
                    '20000',
325
                    '0',
326
                    true,
327
                    ProductType::Digital,
328
                ],
329
            ],
330
        ];
331
        yield 'from negative goes back to positive' => [
332
            'negative balance should swap accounts',
333
            [
334
                'name' => 'My positive product',
335
                'pricePerUnitCHF' => Money::CHF(10000),
336
                'pricePerUnitEUR' => Money::EUR(15000),
337
            ],
338
            [
339
                [
340
                    'My positive product',
341
                    100,
342
                    '1000000',
343
                    '0',
344
                    true,
345
                    ProductType::Digital,
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