InvoicerTest::testInvoiceInitial()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 51
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 35
c 1
b 0
f 0
dl 0
loc 51
rs 9.36
cc 4
nc 4
nop 3

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