Bug 27945: Add limit article request feature
[koha.git] / t / db_dependent / Koha / Patron.t
1 #!/usr/bin/perl
2
3 # Copyright 2019 Koha Development team
4 #
5 # This file is part of Koha
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21
22 use Test::More tests => 9;
23 use Test::Exception;
24 use Test::Warn;
25
26 use Koha::Database;
27 use Koha::DateUtils qw(dt_from_string);
28 use Koha::ArticleRequests;
29 use Koha::Patrons;
30 use Koha::Patron::Relationships;
31
32 use t::lib::TestBuilder;
33 use t::lib::Mocks;
34
35 my $schema  = Koha::Database->new->schema;
36 my $builder = t::lib::TestBuilder->new;
37
38 subtest 'add_guarantor() tests' => sub {
39
40     plan tests => 6;
41
42     $schema->storage->txn_begin;
43
44     t::lib::Mocks::mock_preference( 'borrowerRelationship', 'father1|father2' );
45
46     my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' });
47     my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
48
49     throws_ok
50         { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber }); }
51         'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
52         'Exception is thrown as no relationship passed';
53
54     is( $patron_1->guarantee_relationships->count, 0, 'No guarantors added' );
55
56     throws_ok
57         { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father' }); }
58         'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
59         'Exception is thrown as a wrong relationship was passed';
60
61     is( $patron_1->guarantee_relationships->count, 0, 'No guarantors added' );
62
63     $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father1' });
64
65     my $guarantors = $patron_1->guarantor_relationships;
66
67     is( $guarantors->count, 1, 'No guarantors added' );
68
69     {
70         local *STDERR;
71         open STDERR, '>', '/dev/null';
72         throws_ok
73             { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father2' }); }
74             'Koha::Exceptions::Patron::Relationship::DuplicateRelationship',
75             'Exception is thrown for duplicated relationship';
76         close STDERR;
77     }
78
79     $schema->storage->txn_rollback;
80 };
81
82 subtest 'relationships_debt() tests' => sub {
83
84     plan tests => 168;
85
86     $schema->storage->txn_begin;
87
88     t::lib::Mocks::mock_preference( 'borrowerRelationship', 'parent' );
89
90     my $parent_1 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Parent 1" } });
91     my $parent_2 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Parent 2" } });
92     my $child_1 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Child 1" } });
93     my $child_2 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Child 2" } });
94
95     $child_1->add_guarantor({ guarantor_id => $parent_1->borrowernumber, relationship => 'parent' });
96     $child_1->add_guarantor({ guarantor_id => $parent_2->borrowernumber, relationship => 'parent' });
97     $child_2->add_guarantor({ guarantor_id => $parent_1->borrowernumber, relationship => 'parent' });
98     $child_2->add_guarantor({ guarantor_id => $parent_2->borrowernumber, relationship => 'parent' });
99
100     is( $child_1->guarantor_relationships->guarantors->count, 2, 'Child 1 has correct number of guarantors' );
101     is( $child_2->guarantor_relationships->guarantors->count, 2, 'Child 2 has correct number of guarantors' );
102     is( $parent_1->guarantee_relationships->guarantees->count, 2, 'Parent 1 has correct number of guarantees' );
103     is( $parent_2->guarantee_relationships->guarantees->count, 2, 'Parent 2 has correct number of guarantees' );
104
105     my $patrons = [ $parent_1, $parent_2, $child_1, $child_2 ];
106
107     # First test: No debt
108     my ($parent1_debt, $parent2_debt, $child1_debt, $child2_debt) = (0,0,0,0);
109     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
110
111     # Add debt to child_2
112     $child2_debt = 2;
113     $child_2->account->add_debit({ type => 'ACCOUNT', amount => $child2_debt, interface => 'commandline' });
114     is( $child_2->account->non_issues_charges, $child2_debt, 'Debt added to Child 2' );
115     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
116
117     $parent1_debt = 3;
118     $parent_1->account->add_debit({ type => 'ACCOUNT', amount => $parent1_debt, interface => 'commandline' });
119     is( $parent_1->account->non_issues_charges, $parent1_debt, 'Debt added to Parent 1' );
120     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
121
122     $parent2_debt = 5;
123     $parent_2->account->add_debit({ type => 'ACCOUNT', amount => $parent2_debt, interface => 'commandline' });
124     is( $parent_2->account->non_issues_charges, $parent2_debt, 'Parent 2 owes correct amount' );
125     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
126
127     $child1_debt = 7;
128     $child_1->account->add_debit({ type => 'ACCOUNT', amount => $child1_debt, interface => 'commandline' });
129     is( $child_1->account->non_issues_charges, $child1_debt, 'Child 1 owes correct amount' );
130     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
131
132     $schema->storage->txn_rollback;
133 };
134
135 sub _test_combinations {
136     my ( $patrons, $parent1_debt, $parent2_debt, $child1_debt, $child2_debt ) = @_;
137
138     # Options
139     # P1 => P1 + C1 + C2 ( - P1 ) ( + P2 )
140     # P2 => P2 + C1 + C2 ( - P2 ) ( + P1 )
141     # C1 => P1 + P2 + C1 + C2 ( - C1 )
142     # C2 => P1 + P2 + C1 + C2 ( - C2 )
143
144 # 3 params, count from 0 to 7 in binary ( 3 places ) to get the set of switches, then do that 4 times, one for each parent and child
145     for my $i ( 0 .. 7 ) {
146         my ( $only_this_guarantor, $include_guarantors, $include_this_patron )
147           = split '', sprintf( "%03b", $i );
148         for my $patron ( @$patrons ) {
149             if ( $only_this_guarantor
150                 && !$patron->guarantee_relationships->count )
151             {
152                 throws_ok {
153                     $patron->relationships_debt(
154                         {
155                             only_this_guarantor => $only_this_guarantor,
156                             include_guarantors  => $include_guarantors,
157                             include_this_patron => $include_this_patron
158                         }
159                     );
160                 }
161                 'Koha::Exceptions::BadParameter',
162                   'Exception is thrown as patron is not a guarantor';
163
164             }
165             else {
166
167                 my $debt = 0;
168                 if ( $patron->firstname eq 'Parent 1' ) {
169                     $debt += $parent1_debt if ($include_this_patron && $include_guarantors);
170                     $debt += $child1_debt + $child2_debt;
171                     $debt += $parent2_debt unless ($only_this_guarantor || !$include_guarantors);
172                 }
173                 elsif ( $patron->firstname eq 'Parent 2' ) {
174                     $debt += $parent2_debt if ($include_this_patron & $include_guarantors);
175                     $debt += $child1_debt + $child2_debt;
176                     $debt += $parent1_debt unless ($only_this_guarantor || !$include_guarantors);
177                 }
178                 elsif ( $patron->firstname eq 'Child 1' ) {
179                     $debt += $child1_debt if ($include_this_patron);
180                     $debt += $child2_debt;
181                     $debt += $parent1_debt + $parent2_debt if ($include_guarantors);
182                 }
183                 else {
184                     $debt += $child2_debt if ($include_this_patron);
185                     $debt += $child1_debt;
186                     $debt += $parent1_debt + $parent2_debt if ($include_guarantors);
187                 }
188
189                 is(
190                     $patron->relationships_debt(
191                         {
192                             only_this_guarantor => $only_this_guarantor,
193                             include_guarantors  => $include_guarantors,
194                             include_this_patron => $include_this_patron
195                         }
196                     ),
197                     $debt,
198                     $patron->firstname
199                       . " debt of $debt calculated correctly for ( only_this_guarantor: $only_this_guarantor, include_guarantors: $include_guarantors, include_this_patron: $include_this_patron)"
200                 );
201             }
202         }
203     }
204 }
205
206 subtest 'add_enrolment_fee_if_needed() tests' => sub {
207
208     plan tests => 2;
209
210     subtest 'category has enrolment fee' => sub {
211         plan tests => 7;
212
213         $schema->storage->txn_begin;
214
215         my $category = $builder->build_object(
216             {
217                 class => 'Koha::Patron::Categories',
218                 value => {
219                     enrolmentfee => 20
220                 }
221             }
222         );
223
224         my $patron = $builder->build_object(
225             {
226                 class => 'Koha::Patrons',
227                 value => {
228                     categorycode => $category->categorycode
229                 }
230             }
231         );
232
233         my $enrollment_fee = $patron->add_enrolment_fee_if_needed();
234         is( $enrollment_fee * 1, 20, 'Enrolment fee amount is correct' );
235         my $account = $patron->account;
236         is( $patron->account->balance * 1, 20, 'Patron charged the enrolment fee' );
237         # second enrolment fee, new
238         $enrollment_fee = $patron->add_enrolment_fee_if_needed(0);
239         # third enrolment fee, renewal
240         $enrollment_fee = $patron->add_enrolment_fee_if_needed(1);
241         is( $patron->account->balance * 1, 60, 'Patron charged the enrolment fees' );
242
243         my @debits = $account->outstanding_debits;
244         is( scalar @debits, 3, '3 enrolment fees' );
245         is( $debits[0]->debit_type_code, 'ACCOUNT', 'Account type set correctly' );
246         is( $debits[1]->debit_type_code, 'ACCOUNT', 'Account type set correctly' );
247         is( $debits[2]->debit_type_code, 'ACCOUNT_RENEW', 'Account type set correctly' );
248
249         $schema->storage->txn_rollback;
250     };
251
252     subtest 'no enrolment fee' => sub {
253
254         plan tests => 3;
255
256         $schema->storage->txn_begin;
257
258         my $category = $builder->build_object(
259             {
260                 class => 'Koha::Patron::Categories',
261                 value => {
262                     enrolmentfee => 0
263                 }
264             }
265         );
266
267         my $patron = $builder->build_object(
268             {
269                 class => 'Koha::Patrons',
270                 value => {
271                     categorycode => $category->categorycode
272                 }
273             }
274         );
275
276         my $enrollment_fee = $patron->add_enrolment_fee_if_needed();
277         is( $enrollment_fee * 1, 0, 'No enrolment fee' );
278         my $account = $patron->account;
279         is( $patron->account->balance, 0, 'Patron not charged anything' );
280
281         my @debits = $account->outstanding_debits;
282         is( scalar @debits, 0, 'no debits' );
283
284         $schema->storage->txn_rollback;
285     };
286 };
287
288 subtest 'to_api() tests' => sub {
289
290     plan tests => 6;
291
292     $schema->storage->txn_begin;
293
294     my $patron_class = Test::MockModule->new('Koha::Patron');
295     $patron_class->mock(
296         'algo',
297         sub { return 'algo' }
298     );
299
300     my $patron = $builder->build_object(
301         {
302             class => 'Koha::Patrons',
303             value => {
304                 debarred => undef
305             }
306         }
307     );
308
309     my $restricted = $patron->to_api->{restricted};
310     ok( defined $restricted, 'restricted is defined' );
311     ok( !$restricted, 'debarred is undef, restricted evaluates to false' );
312
313     $patron->debarred( dt_from_string->add( days => 1 ) )->store->discard_changes;
314     $restricted = $patron->to_api->{restricted};
315     ok( defined $restricted, 'restricted is defined' );
316     ok( $restricted, 'debarred is defined, restricted evaluates to true' );
317
318     my $patron_json = $patron->to_api({ embed => { algo => {} } });
319     ok( exists $patron_json->{algo} );
320     is( $patron_json->{algo}, 'algo' );
321
322     $schema->storage->txn_rollback;
323 };
324
325 subtest 'login_attempts tests' => sub {
326     plan tests => 1;
327
328     $schema->storage->txn_begin;
329
330     my $patron = $builder->build_object(
331         {
332             class => 'Koha::Patrons',
333         }
334     );
335     my $patron_info = $patron->unblessed;
336     $patron->delete;
337     delete $patron_info->{login_attempts};
338     my $new_patron = Koha::Patron->new($patron_info)->store;
339     is( $new_patron->discard_changes->login_attempts, 0, "login_attempts defaults to 0 as expected");
340
341     $schema->storage->txn_rollback;
342 };
343
344 subtest 'is_superlibrarian() tests' => sub {
345
346     plan tests => 3;
347
348     $schema->storage->txn_begin;
349
350     my $patron = $builder->build_object(
351         {
352             class => 'Koha::Patrons',
353
354             value => {
355                 flags => 16
356             }
357         }
358     );
359
360     is( $patron->is_superlibrarian, 0, 'Patron is not a superlibrarian and the method returns the correct value' );
361
362     $patron->flags(1)->store->discard_changes;
363     is( $patron->is_superlibrarian, 1, 'Patron is a superlibrarian and the method returns the correct value' );
364
365     $patron->flags(0)->store->discard_changes;
366     is( $patron->is_superlibrarian, 0, 'Patron is not a superlibrarian and the method returns the correct value' );
367
368     $schema->storage->txn_rollback;
369 };
370
371 subtest 'extended_attributes' => sub {
372
373     plan tests => 15;
374
375     my $schema = Koha::Database->new->schema;
376     $schema->storage->txn_begin;
377
378     my $patron_1 = $builder->build_object({class=> 'Koha::Patrons'});
379     my $patron_2 = $builder->build_object({class=> 'Koha::Patrons'});
380
381     t::lib::Mocks::mock_userenv({ patron => $patron_1 });
382
383     my $attribute_type1 = Koha::Patron::Attribute::Type->new(
384         {
385             code        => 'my code1',
386             description => 'my description1',
387             unique_id   => 1
388         }
389     )->store;
390     my $attribute_type2 = Koha::Patron::Attribute::Type->new(
391         {
392             code             => 'my code2',
393             description      => 'my description2',
394             opac_display     => 1,
395             staff_searchable => 1
396         }
397     )->store;
398
399     my $new_library = $builder->build( { source => 'Branch' } );
400     my $attribute_type_limited = Koha::Patron::Attribute::Type->new(
401         { code => 'my code3', description => 'my description3' } )->store;
402     $attribute_type_limited->library_limits( [ $new_library->{branchcode} ] );
403
404     my $attributes_for_1 = [
405         {
406             attribute => 'my attribute1',
407             code => $attribute_type1->code(),
408         },
409         {
410             attribute => 'my attribute2',
411             code => $attribute_type2->code(),
412         },
413         {
414             attribute => 'my attribute limited',
415             code => $attribute_type_limited->code(),
416         }
417     ];
418
419     my $attributes_for_2 = [
420         {
421             attribute => 'my attribute12',
422             code => $attribute_type1->code(),
423         },
424         {
425             attribute => 'my attribute limited 2',
426             code => $attribute_type_limited->code(),
427         }
428     ];
429
430     my $extended_attributes = $patron_1->extended_attributes;
431     is( ref($extended_attributes), 'Koha::Patron::Attributes', 'Koha::Patron->extended_attributes must return a Koha::Patron::Attribute set' );
432     is( $extended_attributes->count, 0, 'There should not be attribute yet');
433
434     $patron_1->extended_attributes->filter_by_branch_limitations->delete;
435     $patron_2->extended_attributes->filter_by_branch_limitations->delete;
436     $patron_1->extended_attributes($attributes_for_1);
437     $patron_2->extended_attributes($attributes_for_2);
438
439     my $extended_attributes_for_1 = $patron_1->extended_attributes;
440     is( $extended_attributes_for_1->count, 3, 'There should be 3 attributes now for patron 1');
441
442     my $extended_attributes_for_2 = $patron_2->extended_attributes;
443     is( $extended_attributes_for_2->count, 2, 'There should be 2 attributes now for patron 2');
444
445     my $attribute_12 = $extended_attributes_for_2->search({ code => $attribute_type1->code })->next;
446     is( $attribute_12->attribute, 'my attribute12', 'search by code should return the correct attribute' );
447
448     $attribute_12 = $patron_2->get_extended_attribute( $attribute_type1->code );
449     is( $attribute_12->attribute, 'my attribute12', 'Koha::Patron->get_extended_attribute should return the correct attribute value' );
450
451     my $expected_attributes_for_2 = [
452         {
453             code      => $attribute_type1->code(),
454             attribute => 'my attribute12',
455         },
456         {
457             code      => $attribute_type_limited->code(),
458             attribute => 'my attribute limited 2',
459         }
460     ];
461     # Sorting them by code
462     $expected_attributes_for_2 = [ sort { $a->{code} cmp $b->{code} } @$expected_attributes_for_2 ];
463     my @extended_attributes_for_2 = $extended_attributes_for_2->as_list;
464
465     is_deeply(
466         [
467             {
468                 code      => $extended_attributes_for_2[0]->code,
469                 attribute => $extended_attributes_for_2[0]->attribute
470             },
471             {
472                 code      => $extended_attributes_for_2[1]->code,
473                 attribute => $extended_attributes_for_2[1]->attribute
474             }
475         ],
476         $expected_attributes_for_2
477     );
478
479     # TODO - What about multiple? POD explains the problem
480     my $non_existent = $patron_2->get_extended_attribute( 'not_exist' );
481     is( $non_existent, undef, 'Koha::Patron->get_extended_attribute must return undef if the attribute does not exist' );
482
483     # Test branch limitations
484     t::lib::Mocks::mock_userenv({ patron => $patron_2 });
485     # Return all
486     $extended_attributes_for_1 = $patron_1->extended_attributes;
487     is( $extended_attributes_for_1->count, 3, 'There should be 2 attributes for patron 1, the limited one should be returned');
488
489     # Return filtered
490     $extended_attributes_for_1 = $patron_1->extended_attributes->filter_by_branch_limitations;
491     is( $extended_attributes_for_1->count, 2, 'There should be 2 attributes for patron 1, the limited one should be returned');
492
493     # Not filtered
494     my $limited_value = $patron_1->get_extended_attribute( $attribute_type_limited->code );
495     is( $limited_value->attribute, 'my attribute limited', );
496
497     ## Do we need a filtered?
498     #$limited_value = $patron_1->get_extended_attribute( $attribute_type_limited->code );
499     #is( $limited_value, undef, );
500
501     $schema->storage->txn_rollback;
502
503     subtest 'non-repeatable attributes tests' => sub {
504
505         plan tests => 3;
506
507         $schema->storage->txn_begin;
508
509         my $patron = $builder->build_object({ class => 'Koha::Patrons' });
510         my $attribute_type = $builder->build_object(
511             {
512                 class => 'Koha::Patron::Attribute::Types',
513                 value => { repeatable => 0 }
514             }
515         );
516
517         is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
518
519         throws_ok
520             {
521                 $patron->extended_attributes(
522                     [
523                         { code => $attribute_type->code, attribute => 'a' },
524                         { code => $attribute_type->code, attribute => 'b' }
525                     ]
526                 );
527             }
528             'Koha::Exceptions::Patron::Attribute::NonRepeatable',
529             'Exception thrown on non-repeatable attribute';
530
531         is( $patron->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
532
533         $schema->storage->txn_rollback;
534
535     };
536
537     subtest 'unique attributes tests' => sub {
538
539         plan tests => 5;
540
541         $schema->storage->txn_begin;
542
543         my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' });
544         my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
545
546         my $attribute_type_1 = $builder->build_object(
547             {
548                 class => 'Koha::Patron::Attribute::Types',
549                 value => { unique => 1 }
550             }
551         );
552
553         my $attribute_type_2 = $builder->build_object(
554             {
555                 class => 'Koha::Patron::Attribute::Types',
556                 value => { unique => 0 }
557             }
558         );
559
560         is( $patron_1->extended_attributes->count, 0, 'patron_1 has no extended attributes' );
561         is( $patron_2->extended_attributes->count, 0, 'patron_2 has no extended attributes' );
562
563         $patron_1->extended_attributes(
564             [
565                 { code => $attribute_type_1->code, attribute => 'a' },
566                 { code => $attribute_type_2->code, attribute => 'a' }
567             ]
568         );
569
570         throws_ok
571             {
572                 $patron_2->extended_attributes(
573                     [
574                         { code => $attribute_type_1->code, attribute => 'a' },
575                         { code => $attribute_type_2->code, attribute => 'a' }
576                     ]
577                 );
578             }
579             'Koha::Exceptions::Patron::Attribute::UniqueIDConstraint',
580             'Exception thrown on unique attribute';
581
582         is( $patron_1->extended_attributes->count, 2, 'Extended attributes stored' );
583         is( $patron_2->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
584
585         $schema->storage->txn_rollback;
586
587     };
588
589     subtest 'invalid type attributes tests' => sub {
590
591         plan tests => 3;
592
593         $schema->storage->txn_begin;
594
595         my $patron = $builder->build_object({ class => 'Koha::Patrons' });
596
597         my $attribute_type_1 = $builder->build_object(
598             {
599                 class => 'Koha::Patron::Attribute::Types',
600                 value => { repeatable => 0 }
601             }
602         );
603
604         my $attribute_type_2 = $builder->build_object(
605             {
606                 class => 'Koha::Patron::Attribute::Types'
607             }
608         );
609
610         my $type_2 = $attribute_type_2->code;
611         $attribute_type_2->delete;
612
613         is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
614
615         throws_ok
616             {
617                 $patron->extended_attributes(
618                     [
619                         { code => $attribute_type_1->code, attribute => 'a' },
620                         { code => $attribute_type_2->code, attribute => 'b' }
621                     ]
622                 );
623             }
624             'Koha::Exceptions::Patron::Attribute::InvalidType',
625             'Exception thrown on invalid attribute type';
626
627         is( $patron->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
628
629         $schema->storage->txn_rollback;
630
631     };
632
633     subtest 'globally mandatory attributes tests' => sub {
634
635         plan tests => 5;
636
637         $schema->storage->txn_begin;
638
639         my $patron = $builder->build_object({ class => 'Koha::Patrons' });
640
641         my $attribute_type_1 = $builder->build_object(
642             {
643                 class => 'Koha::Patron::Attribute::Types',
644                 value => { mandatory => 1, class => 'a' }
645             }
646         );
647
648         my $attribute_type_2 = $builder->build_object(
649             {
650                 class => 'Koha::Patron::Attribute::Types',
651                 value => { mandatory => 0, class => 'a' }
652             }
653         );
654
655         is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
656
657         throws_ok
658             {
659                 $patron->extended_attributes(
660                     [
661                         { code => $attribute_type_2->code, attribute => 'b' }
662                     ]
663                 );
664             }
665             'Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute',
666             'Exception thrown on missing mandatory attribute type';
667
668         is( $@->type, $attribute_type_1->code, 'Exception parameters are correct' );
669
670         is( $patron->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
671
672         $patron->extended_attributes(
673             [
674                 { code => $attribute_type_1->code, attribute => 'b' }
675             ]
676         );
677
678         is( $patron->extended_attributes->count, 1, 'Extended attributes succeeded' );
679
680         $schema->storage->txn_rollback;
681
682     };
683
684 };
685
686 subtest 'can_log_into() tests' => sub {
687
688     plan tests => 5;
689
690     $schema->storage->txn_begin;
691
692     my $patron = $builder->build_object(
693         {
694             class => 'Koha::Patrons',
695             value => {
696                 flags => undef
697             }
698         }
699     );
700     my $library = $builder->build_object({ class => 'Koha::Libraries' });
701
702     t::lib::Mocks::mock_preference('IndependentBranches', 1);
703
704     ok( $patron->can_log_into( $patron->library ), 'Patron can log into its own library' );
705     ok( !$patron->can_log_into( $library ), 'Patron cannot log into different library, IndependentBranches on' );
706
707     # make it a superlibrarian
708     $patron->set({ flags => 1 })->store->discard_changes;
709     ok( $patron->can_log_into( $library ), 'Superlibrarian can log into different library, IndependentBranches on' );
710
711     t::lib::Mocks::mock_preference('IndependentBranches', 0);
712
713     # No special permissions
714     $patron->set({ flags => undef })->store->discard_changes;
715     ok( $patron->can_log_into( $patron->library ), 'Patron can log into its own library' );
716     ok( $patron->can_log_into( $library ), 'Patron can log into any library' );
717
718     $schema->storage->txn_rollback;
719 };
720
721 subtest 'can_request_article() tests' => sub {
722
723     plan tests => 13;
724
725     $schema->storage->txn_begin;
726
727     t::lib::Mocks::mock_preference( 'ArticleRequests', 1 );
728
729     my $item = $builder->build_sample_item;
730
731     my $category = $builder->build_object(
732         {
733             class => 'Koha::Patron::Categories',
734             value => {
735                 article_request_limit => 1
736             }
737         }
738     );
739     my $patron = $builder->build_object(
740         {
741             class => 'Koha::Patrons',
742             value => {
743                 categorycode => $category->categorycode
744             },
745         }
746     );
747
748     is( $patron->can_request_article,
749         1, 'There are no AR, so patron can request more articles' );
750
751     my $article_request_1 = Koha::ArticleRequest->new(
752         {
753             borrowernumber => $patron->id,
754             biblionumber   => $item->biblionumber,
755             itemnumber     => $item->itemnumber,
756             title          => 'an article request',
757         }
758     )->request;
759
760     is( $patron->can_request_article,
761         0, 'Limit is 1, so patron cannot request more articles' );
762     is( $patron->article_requests->count,
763         1, 'There is one current article request' );
764
765     throws_ok {
766         Koha::ArticleRequest->new(
767             {
768                 borrowernumber => $patron->id,
769                 biblionumber   => $item->biblionumber,
770                 itemnumber     => $item->itemnumber,
771                 title          => 'a second article request',
772             }
773         )->request;
774     }
775     'Koha::Exceptions::ArticleRequest::LimitReached',
776       'When limit was reached and we ask for a new AR, Limit reached is thrown';
777
778     is( $patron->can_request_article,
779         0, 'There is still an AR, so patron cannot request more articles' );
780     is( $patron->article_requests->count,
781         1, 'There is still one article request' );
782
783     $article_request_1->complete();
784
785     is( $patron->can_request_article, 0,
786 'AR was completed but within one day, so patron cannot request more articles'
787     );
788
789     $article_request_1->updated_on( dt_from_string->add( days => -2 ) )
790       ->store();
791
792     is( $patron->can_request_article, 1,
793 'There are no completed AR within one day, so patron can request more articles'
794     );
795
796     my $article_request_3 = Koha::ArticleRequest->new(
797         {
798             borrowernumber => $patron->id,
799             biblionumber   => $item->biblionumber,
800             itemnumber     => $item->itemnumber,
801             title          => 'a third article request',
802         }
803     )->request;
804
805     is( $patron->can_request_article,
806         0, 'A new AR was created, so patron cannot request more articles' );
807     is( $patron->article_requests->count, 2, 'There are 2 article requests' );
808
809     $article_request_3->cancel();
810
811     is( $patron->can_request_article,
812         1, 'New AR was cancelled, so patron can request more articles' );
813
814     my $article_request_4 = Koha::ArticleRequest->new(
815         {
816             borrowernumber => $patron->id,
817             biblionumber   => $item->biblionumber,
818             itemnumber     => $item->itemnumber,
819             title          => 'an fourth article request',
820         }
821     )->request;
822
823     $article_request_4->updated_on( dt_from_string->add( days => -30 ) )
824       ->store();
825
826     is( $patron->can_request_article, 0,
827 'There is an old AR but not completed or cancelled, so patron cannot request more articles'
828     );
829     is( $patron->article_requests->count,
830         3, 'There are 3 current article requests' );
831
832     $schema->storage->txn_rollback;
833 };