Passed
Push — master ( 38d154...429a57 )
by Adrien
07:44
created

InvoicerTest::providerShouldInvoiceInitial()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 199
Code Lines 127

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 127
c 0
b 0
f 0
dl 0
loc 199
rs 8
cc 1
nc 1
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace ApplicationTest\Service;
6
7
use Application\DBAL\Types\BookingStatusType;
8
use Application\DBAL\Types\BookingTypeType;
9
use Application\Model\Account;
10
use Application\Model\Bookable;
11
use Application\Model\Booking;
12
use Application\Model\TransactionLine;
13
use Application\Model\User;
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
    public function testInvoice(): void
24
    {
25
        global $container;
26
27
        /** @var Invoicer $invoicer */
28
        $invoicer = $container->get(Invoicer::class);
29
        $actual = $invoicer->invoicePeriodic();
30
        self::assertSame(3, $actual);
31
32
        $this->getEntityManager()->flush();
33
34
        $actual2 = $invoicer->invoicePeriodic();
35
        self::assertSame(0, $actual2, 'should not invoice things that are already invoiced');
36
    }
37
38
    /**
39
     * @dataProvider providerInvoiceInitial
40
     */
41
    public function testInvoiceInitial(Money $initialPrice, Money $periodicPrice, array $expected): void
42
    {
43
        $user = new User();
44
        $user->setFirstName('John');
45
        $user->setLastName('Doe');
46
47
        $bookable = new Bookable();
48
        $bookable->setName('My bookable');
49
        $bookable->setInitialPrice($initialPrice);
50
        $bookable->setPeriodicPrice($periodicPrice);
51
52
        $bookableAccount = new Account();
53
        $bookableAccount->setName('Bookable account');
54
        $bookable->setCreditAccount($bookableAccount);
55
56
        // Creation of booking will implicitly call the invoicer
57
        $booking = new Booking();
58
        $booking->setOwner($user);
59
        $booking->setBookable($bookable);
60
        $booking->setStatus(BookingStatusType::BOOKED);
61
62
        $account = $user->getAccount();
63
64
        if ($expected === []) {
65
            self::assertNull($account);
66
        } else {
67
            $all = array_merge(
68
                $account->getCreditTransactionLines()->toArray(),
69
                $account->getDebitTransactionLines()->toArray()
70
            );
71
            $actual = [];
72
73
            $transaction = null;
74
            /** @var TransactionLine $t */
75
            foreach ($all as $t) {
76
                if (!$transaction) {
77
                    $transaction = $t->getTransaction();
78
                    self::assertNotNull($transaction, 'must belong to a transaction');
79
                } else {
80
                    self::assertSame($transaction, $t->getTransaction(), 'all lines should belong to same transaction');
81
                }
82
83
                $actual[] = [
84
                    $t->getName(),
85
                    $t->getBookable()->getName(),
86
                    $t->getDebit()->getName(),
87
                    $t->getCredit()->getName(),
88
                    $t->getBalance()->getAmount(),
89
                ];
90
            }
91
92
            self::assertSame($expected, $actual);
93
        }
94
    }
95
96
    public function providerInvoiceInitial(): array
97
    {
98
        return [
99
            'free booking should create nothing' => [
100
                Money::CHF(0),
101
                Money::CHF(0),
102
                [],
103
            ],
104
            'only initial' => [
105
                Money::CHF(1025),
106
                Money::CHF(0),
107
                [
108
                    [
109
                        'Prestation ponctuelle',
110
                        'My bookable',
111
                        'John Doe',
112
                        'Bookable account',
113
                        '1025',
114
                    ],
115
                ],
116
            ],
117
            'only periodic' => [
118
                Money::CHF(0),
119
                Money::CHF(9025),
120
                [
121
                    [
122
                        'Prestation annuelle',
123
                        'My bookable',
124
                        'John Doe',
125
                        'Bookable account',
126
                        '9025',
127
                    ],
128
                ],
129
            ],
130
            'both initial and periodic should create two lines' => [
131
                Money::CHF(1025),
132
                Money::CHF(9025),
133
                [
134
                    [
135
                        'Prestation ponctuelle',
136
                        'My bookable',
137
                        'John Doe',
138
                        'Bookable account',
139
                        '1025',
140
                    ],
141
                    [
142
                        'Prestation annuelle',
143
                        'My bookable',
144
                        'John Doe',
145
                        'Bookable account',
146
                        '9025',
147
                    ],
148
                ],
149
            ],
150
            'negative balance should swap accounts' => [
151
                Money::CHF(-1025),
152
                Money::CHF(-9025),
153
                [
154
                    [
155
                        'Prestation ponctuelle',
156
                        'My bookable',
157
                        'Bookable account',
158
                        'John Doe',
159
                        '1025',
160
                    ],
161
                    [
162
                        'Prestation annuelle',
163
                        'My bookable',
164
                        'Bookable account',
165
                        'John Doe',
166
                        '9025',
167
                    ],
168
                ],
169
            ],
170
        ];
171
    }
172
173
    public function testInvoicerNotCalled(): void
174
    {
175
        $user = new User();
176
        $bookable = new Bookable();
177
        $bookable->setInitialPrice(Money::CHF(100));
178
        $bookable->setPeriodicPrice(Money::CHF(100));
179
180
        $bookingWithoutOwner = new Booking();
181
        $bookingWithoutOwner->setBookable($bookable);
182
        $bookingWithoutOwner->setStatus(BookingStatusType::BOOKED);
183
184
        $bookingWithoutBookable = new Booking();
185
        $bookingWithoutBookable->setOwner($user);
186
        $bookingWithoutBookable->setStatus(BookingStatusType::BOOKED);
187
188
        $bookingWithoutStatus = new Booking();
189
        $bookingWithoutStatus->setBookable($bookable);
190
        $bookingWithoutStatus->setOwner($user);
191
192
        self::assertNull($user->getAccount(), 'invoicer is only called when we have both an owner and a bookable');
193
    }
194
195
    /**
196
     * @dataProvider providerShouldInvoiceInitial
197
     */
198
    public function testShouldInvoiceInitial(array $data, int $expected): void
199
    {
200
        $user = new User();
201
        $bookable = $this->createMock(Bookable::class);
202
        $bookable->expects(self::any())
203
            ->method('getCreditAccount')
204
            ->willReturn($data['bookable']['creditAccount'] ? new Account() : null);
205
206
        $bookable->expects(self::any())
207
            ->method('getInitialPrice')
208
            ->willReturn($data['bookable']['initialPrice']);
209
210
        $bookable->expects(self::any())
211
            ->method('getPeriodicPrice')
212
            ->willReturn($data['bookable']['periodicPrice']);
213
214
        $bookable->expects(self::any())
215
            ->method('getBookingType')
216
            ->willReturn($data['bookable']['bookingType']);
217
218
        $booking = $this->createMock(Booking::class);
219
        $booking->expects(self::any())
220
            ->method('getId')
221
            ->willReturn($data['id']);
222
223
        $booking->expects(self::any())
224
            ->method('getStatus')
225
            ->willReturn($data['status']);
226
227
        $booking->expects(self::any())
228
            ->method('getPeriodicPrice')
229
            ->willReturn($data['bookable']['periodicPrice']);
230
231
        $booking->expects(self::any())
232
            ->method('getBookable')
233
            ->willReturn($bookable);
234
235
        global $container;
236
237
        /** @var Invoicer $invoicer */
238
        $invoicer = $container->get(Invoicer::class);
239
        $actual = $invoicer->invoiceInitial($user, $booking, $data['previousStatus']);
240
241
        self::assertSame($expected, $actual);
242
    }
243
244
    public function providerShouldInvoiceInitial(): array
245
    {
246
        return [
247
            'create MANDATORY booking should invoice' => [
248
                [
249
                    'id' => null,
250
                    'previousStatus' => null,
251
                    'status' => BookingStatusType::BOOKED,
252
                    'bookable' => [
253
                        'creditAccount' => true,
254
                        'initialPrice' => Money::CHF(100),
255
                        'periodicPrice' => Money::CHF(100),
256
                        'bookingType' => BookingTypeType::MANDATORY,
257
                    ],
258
                ],
259
                1,
260
            ],
261
            'update MANDATORY booking should not invoice' => [
262
                [
263
                    'id' => 123,
264
                    'previousStatus' => null,
265
                    'status' => BookingStatusType::BOOKED,
266
                    'bookable' => [
267
                        'creditAccount' => true,
268
                        'initialPrice' => Money::CHF(100),
269
                        'periodicPrice' => Money::CHF(100),
270
                        'bookingType' => BookingTypeType::MANDATORY,
271
                    ],
272
                ],
273
                0,
274
            ],
275
            'create MANDATORY booking that is processed (not booked) should not invoice' => [
276
                [
277
                    'id' => null,
278
                    'previousStatus' => null,
279
                    'status' => BookingStatusType::PROCESSED,
280
                    'bookable' => [
281
                        'creditAccount' => true,
282
                        'initialPrice' => Money::CHF(100),
283
                        'periodicPrice' => Money::CHF(100),
284
                        'bookingType' => BookingTypeType::MANDATORY,
285
                    ],
286
                ],
287
                0,
288
            ],
289
            'create MANDATORY booking that is application (not booked) should not invoice' => [
290
                [
291
                    'id' => null,
292
                    'previousStatus' => null,
293
                    'status' => BookingStatusType::APPLICATION,
294
                    'bookable' => [
295
                        'creditAccount' => true,
296
                        'initialPrice' => Money::CHF(100),
297
                        'periodicPrice' => Money::CHF(100),
298
                        'bookingType' => BookingTypeType::MANDATORY,
299
                    ],
300
                ],
301
                0,
302
            ],
303
            'create MANDATORY booking without creditAccount should not invoice' => [
304
                [
305
                    'id' => null,
306
                    'previousStatus' => null,
307
                    'status' => BookingStatusType::BOOKED,
308
                    'bookable' => [
309
                        'creditAccount' => false,
310
                        'initialPrice' => Money::CHF(100),
311
                        'periodicPrice' => Money::CHF(100),
312
                        'bookingType' => BookingTypeType::MANDATORY,
313
                    ],
314
                ],
315
                0,
316
            ],
317
            'create MANDATORY free booking should not invoice' => [
318
                [
319
                    'id' => null,
320
                    'previousStatus' => null,
321
                    'status' => BookingStatusType::BOOKED,
322
                    'bookable' => [
323
                        'creditAccount' => true,
324
                        'initialPrice' => Money::CHF(0),
325
                        'periodicPrice' => Money::CHF(0),
326
                        'bookingType' => BookingTypeType::MANDATORY,
327
                    ],
328
                ],
329
                0,
330
            ],
331
            'create MANDATORY booking with free initialPrice and non-free periodicPrice should still actually invoice, because we also invoice periodicPrice' => [
332
                [
333
                    'id' => null,
334
                    'previousStatus' => null,
335
                    'status' => BookingStatusType::BOOKED,
336
                    'bookable' => [
337
                        'creditAccount' => true,
338
                        'initialPrice' => Money::CHF(0),
339
                        'periodicPrice' => Money::CHF(100),
340
                        'bookingType' => BookingTypeType::MANDATORY,
341
                    ],
342
                ],
343
                1,
344
            ],
345
            'create APPLICATION booking should not invoice' => [
346
                [
347
                    'id' => null,
348
                    'previousStatus' => null,
349
                    'status' => BookingStatusType::BOOKED,
350
                    'bookable' => [
351
                        'creditAccount' => true,
352
                        'initialPrice' => Money::CHF(100),
353
                        'periodicPrice' => Money::CHF(100),
354
                        'bookingType' => BookingTypeType::APPLICATION,
355
                    ],
356
                ],
357
                0,
358
            ],
359
            'update MANDATORY booking to change status from APPLICATION to BOOKED should invoice' => [
360
                [
361
                    'id' => 123,
362
                    'previousStatus' => BookingStatusType::APPLICATION,
363
                    'status' => BookingStatusType::BOOKED,
364
                    'bookable' => [
365
                        'creditAccount' => true,
366
                        'initialPrice' => Money::CHF(100),
367
                        'periodicPrice' => Money::CHF(100),
368
                        'bookingType' => BookingTypeType::MANDATORY,
369
                    ],
370
                ],
371
                1,
372
            ],
373
            'update MANDATORY booking to change status from BOOKED to BOOKED should not invoice' => [
374
                [
375
                    'id' => 123,
376
                    'previousStatus' => BookingStatusType::BOOKED,
377
                    'status' => BookingStatusType::BOOKED,
378
                    'bookable' => [
379
                        'creditAccount' => true,
380
                        'initialPrice' => Money::CHF(100),
381
                        'periodicPrice' => Money::CHF(100),
382
                        'bookingType' => BookingTypeType::MANDATORY,
383
                    ],
384
                ],
385
                0,
386
            ],
387
            'update MANDATORY booking to change status from PROCESSED to BOOKED should not invoice' => [
388
                [
389
                    'id' => 123,
390
                    'previousStatus' => BookingStatusType::PROCESSED,
391
                    'status' => BookingStatusType::BOOKED,
392
                    'bookable' => [
393
                        'creditAccount' => true,
394
                        'initialPrice' => Money::CHF(100),
395
                        'periodicPrice' => Money::CHF(100),
396
                        'bookingType' => BookingTypeType::MANDATORY,
397
                    ],
398
                ],
399
                0,
400
            ],
401
402
            'create ADMIN_APPROVED booking should invoice' => [
403
                [
404
                    'id' => null,
405
                    'previousStatus' => null,
406
                    'status' => BookingStatusType::BOOKED,
407
                    'bookable' => [
408
                        'creditAccount' => true,
409
                        'initialPrice' => Money::CHF(100),
410
                        'periodicPrice' => Money::CHF(100),
411
                        'bookingType' => BookingTypeType::ADMIN_APPROVED,
412
                    ],
413
                ],
414
                1,
415
            ],
416
            'update ADMIN_APPROVED booking should not invoice' => [
417
                [
418
                    'id' => 123,
419
                    'previousStatus' => null,
420
                    'status' => BookingStatusType::BOOKED,
421
                    'bookable' => [
422
                        'creditAccount' => true,
423
                        'initialPrice' => Money::CHF(100),
424
                        'periodicPrice' => Money::CHF(100),
425
                        'bookingType' => BookingTypeType::ADMIN_APPROVED,
426
                    ],
427
                ],
428
                0,
429
            ],
430
            'update ADMIN_APPROVED booking to change status from APPLICATION to BOOKED should invoice' => [
431
                [
432
                    'id' => 123,
433
                    'previousStatus' => BookingStatusType::APPLICATION,
434
                    'status' => BookingStatusType::BOOKED,
435
                    'bookable' => [
436
                        'creditAccount' => true,
437
                        'initialPrice' => Money::CHF(100),
438
                        'periodicPrice' => Money::CHF(100),
439
                        'bookingType' => BookingTypeType::ADMIN_APPROVED,
440
                    ],
441
                ],
442
                1,
443
            ],
444
        ];
445
    }
446
}
447