Bug 32437: Add replace method to Koha::Import::Record objects
[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 => 20;
23 use Test::Exception;
24 use Test::Warn;
25
26 use Koha::CirculationRules;
27 use Koha::Database;
28 use Koha::DateUtils qw(dt_from_string);
29 use Koha::ArticleRequests;
30 use Koha::Patrons;
31 use Koha::Patron::Relationships;
32 use C4::Circulation qw( AddIssue AddReturn );
33
34 use t::lib::TestBuilder;
35 use t::lib::Mocks;
36
37 my $schema  = Koha::Database->new->schema;
38 my $builder = t::lib::TestBuilder->new;
39
40 subtest 'add_guarantor() tests' => sub {
41
42     plan tests => 6;
43
44     $schema->storage->txn_begin;
45
46     t::lib::Mocks::mock_preference( 'borrowerRelationship', 'father1|father2' );
47
48     my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' });
49     my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
50
51     throws_ok
52         { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber }); }
53         'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
54         'Exception is thrown as no relationship passed';
55
56     is( $patron_1->guarantee_relationships->count, 0, 'No guarantors added' );
57
58     throws_ok
59         { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father' }); }
60         'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
61         'Exception is thrown as a wrong relationship was passed';
62
63     is( $patron_1->guarantee_relationships->count, 0, 'No guarantors added' );
64
65     $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father1' });
66
67     my $guarantors = $patron_1->guarantor_relationships;
68
69     is( $guarantors->count, 1, 'No guarantors added' );
70
71     {
72         local *STDERR;
73         open STDERR, '>', '/dev/null';
74         throws_ok
75             { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father2' }); }
76             'Koha::Exceptions::Patron::Relationship::DuplicateRelationship',
77             'Exception is thrown for duplicated relationship';
78         close STDERR;
79     }
80
81     $schema->storage->txn_rollback;
82 };
83
84 subtest 'relationships_debt() tests' => sub {
85
86     plan tests => 168;
87
88     $schema->storage->txn_begin;
89
90     t::lib::Mocks::mock_preference( 'borrowerRelationship', 'parent' );
91
92     my $parent_1 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Parent 1" } });
93     my $parent_2 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Parent 2" } });
94     my $child_1 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => " Child 1" } });
95     my $child_2 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => " Child 2" } });
96
97     $child_1->add_guarantor({ guarantor_id => $parent_1->borrowernumber, relationship => 'parent' });
98     $child_1->add_guarantor({ guarantor_id => $parent_2->borrowernumber, relationship => 'parent' });
99     $child_2->add_guarantor({ guarantor_id => $parent_1->borrowernumber, relationship => 'parent' });
100     $child_2->add_guarantor({ guarantor_id => $parent_2->borrowernumber, relationship => 'parent' });
101
102     is( $child_1->guarantor_relationships->guarantors->count, 2, 'Child 1 has correct number of guarantors' );
103     is( $child_2->guarantor_relationships->guarantors->count, 2, 'Child 2 has correct number of guarantors' );
104     is( $parent_1->guarantee_relationships->guarantees->count, 2, 'Parent 1 has correct number of guarantees' );
105     is( $parent_2->guarantee_relationships->guarantees->count, 2, 'Parent 2 has correct number of guarantees' );
106
107     my $patrons = [ $parent_1, $parent_2, $child_1, $child_2 ];
108
109     # First test: No debt
110     my ($parent1_debt, $parent2_debt, $child1_debt, $child2_debt) = (0,0,0,0);
111     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
112
113     # Add debt to child_2
114     $child2_debt = 2;
115     $child_2->account->add_debit({ type => 'ACCOUNT', amount => $child2_debt, interface => 'commandline' });
116     is( $child_2->account->non_issues_charges, $child2_debt, 'Debt added to Child 2' );
117     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
118
119     $parent1_debt = 3;
120     $parent_1->account->add_debit({ type => 'ACCOUNT', amount => $parent1_debt, interface => 'commandline' });
121     is( $parent_1->account->non_issues_charges, $parent1_debt, 'Debt added to Parent 1' );
122     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
123
124     $parent2_debt = 5;
125     $parent_2->account->add_debit({ type => 'ACCOUNT', amount => $parent2_debt, interface => 'commandline' });
126     is( $parent_2->account->non_issues_charges, $parent2_debt, 'Parent 2 owes correct amount' );
127     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
128
129     $child1_debt = 7;
130     $child_1->account->add_debit({ type => 'ACCOUNT', amount => $child1_debt, interface => 'commandline' });
131     is( $child_1->account->non_issues_charges, $child1_debt, 'Child 1 owes correct amount' );
132     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
133
134     $schema->storage->txn_rollback;
135 };
136
137 sub _test_combinations {
138     my ( $patrons, $parent1_debt, $parent2_debt, $child1_debt, $child2_debt ) = @_;
139     note("Testing with parent 1 debt $parent1_debt | Parent 2 debt $parent2_debt | Child 1 debt $child1_debt | Child 2 debt $child2_debt");
140     # Options
141     # P1 => P1 + C1 + C2 ( - P1 ) ( + P2 )
142     # P2 => P2 + C1 + C2 ( - P2 ) ( + P1 )
143     # C1 => P1 + P2 + C1 + C2 ( - C1 )
144     # C2 => P1 + P2 + C1 + C2 ( - C2 )
145
146 # 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
147     for my $i ( 0 .. 7 ) {
148         my ( $only_this_guarantor, $include_guarantors, $include_this_patron )
149           = split '', sprintf( "%03b", $i );
150         note("---------------------");
151         for my $patron ( @$patrons ) {
152             if ( $only_this_guarantor
153                 && !$patron->guarantee_relationships->count )
154             {
155                 throws_ok {
156                     $patron->relationships_debt(
157                         {
158                             only_this_guarantor => $only_this_guarantor,
159                             include_guarantors  => $include_guarantors,
160                             include_this_patron => $include_this_patron
161                         }
162                     );
163                 }
164                 'Koha::Exceptions::BadParameter',
165                   'Exception is thrown as patron is not a guarantor';
166
167             }
168             else {
169
170                 my $debt = 0;
171                 if ( $patron->firstname eq 'Parent 1' ) {
172                     $debt += $parent1_debt if ($include_this_patron && $include_guarantors);
173                     $debt += $child1_debt + $child2_debt;
174                     $debt += $parent2_debt unless ($only_this_guarantor || !$include_guarantors);
175                 }
176                 elsif ( $patron->firstname eq 'Parent 2' ) {
177                     $debt += $parent2_debt if ($include_this_patron & $include_guarantors);
178                     $debt += $child1_debt + $child2_debt;
179                     $debt += $parent1_debt unless ($only_this_guarantor || !$include_guarantors);
180                 }
181                 elsif ( $patron->firstname eq ' Child 1' ) {
182                     $debt += $child1_debt if ($include_this_patron);
183                     $debt += $child2_debt;
184                     $debt += $parent1_debt + $parent2_debt if ($include_guarantors);
185                 }
186                 else {
187                     $debt += $child2_debt if ($include_this_patron);
188                     $debt += $child1_debt;
189                     $debt += $parent1_debt + $parent2_debt if ($include_guarantors);
190                 }
191
192                 is(
193                     $patron->relationships_debt(
194                         {
195                             only_this_guarantor => $only_this_guarantor,
196                             include_guarantors  => $include_guarantors,
197                             include_this_patron => $include_this_patron
198                         }
199                     ),
200                     $debt,
201                     $patron->firstname
202                       . " debt of " . sprintf('%02d',$debt) . " calculated correctly for ( only_this_guarantor: $only_this_guarantor, include_guarantors: $include_guarantors, include_this_patron: $include_this_patron)"
203                 );
204             }
205         }
206     }
207 }
208
209 subtest 'add_enrolment_fee_if_needed() tests' => sub {
210
211     plan tests => 2;
212
213     subtest 'category has enrolment fee' => sub {
214         plan tests => 7;
215
216         $schema->storage->txn_begin;
217
218         my $category = $builder->build_object(
219             {
220                 class => 'Koha::Patron::Categories',
221                 value => {
222                     enrolmentfee => 20
223                 }
224             }
225         );
226
227         my $patron = $builder->build_object(
228             {
229                 class => 'Koha::Patrons',
230                 value => {
231                     categorycode => $category->categorycode
232                 }
233             }
234         );
235
236         my $enrollment_fee = $patron->add_enrolment_fee_if_needed();
237         is( $enrollment_fee * 1, 20, 'Enrolment fee amount is correct' );
238         my $account = $patron->account;
239         is( $patron->account->balance * 1, 20, 'Patron charged the enrolment fee' );
240         # second enrolment fee, new
241         $enrollment_fee = $patron->add_enrolment_fee_if_needed(0);
242         # third enrolment fee, renewal
243         $enrollment_fee = $patron->add_enrolment_fee_if_needed(1);
244         is( $patron->account->balance * 1, 60, 'Patron charged the enrolment fees' );
245
246         my @debits = $account->outstanding_debits->as_list;
247         is( scalar @debits, 3, '3 enrolment fees' );
248         is( $debits[0]->debit_type_code, 'ACCOUNT', 'Account type set correctly' );
249         is( $debits[1]->debit_type_code, 'ACCOUNT', 'Account type set correctly' );
250         is( $debits[2]->debit_type_code, 'ACCOUNT_RENEW', 'Account type set correctly' );
251
252         $schema->storage->txn_rollback;
253     };
254
255     subtest 'no enrolment fee' => sub {
256
257         plan tests => 3;
258
259         $schema->storage->txn_begin;
260
261         my $category = $builder->build_object(
262             {
263                 class => 'Koha::Patron::Categories',
264                 value => {
265                     enrolmentfee => 0
266                 }
267             }
268         );
269
270         my $patron = $builder->build_object(
271             {
272                 class => 'Koha::Patrons',
273                 value => {
274                     categorycode => $category->categorycode
275                 }
276             }
277         );
278
279         my $enrollment_fee = $patron->add_enrolment_fee_if_needed();
280         is( $enrollment_fee * 1, 0, 'No enrolment fee' );
281         my $account = $patron->account;
282         is( $patron->account->balance, 0, 'Patron not charged anything' );
283
284         my @debits = $account->outstanding_debits->as_list;
285         is( scalar @debits, 0, 'no debits' );
286
287         $schema->storage->txn_rollback;
288     };
289 };
290
291 subtest 'to_api() tests' => sub {
292
293     plan tests => 6;
294
295     $schema->storage->txn_begin;
296
297     my $patron_class = Test::MockModule->new('Koha::Patron');
298     $patron_class->mock(
299         'algo',
300         sub { return 'algo' }
301     );
302
303     my $patron = $builder->build_object(
304         {
305             class => 'Koha::Patrons',
306             value => {
307                 debarred => undef
308             }
309         }
310     );
311
312     my $restricted = $patron->to_api->{restricted};
313     ok( defined $restricted, 'restricted is defined' );
314     ok( !$restricted, 'debarred is undef, restricted evaluates to false' );
315
316     $patron->debarred( dt_from_string->add( days => 1 ) )->store->discard_changes;
317     $restricted = $patron->to_api->{restricted};
318     ok( defined $restricted, 'restricted is defined' );
319     ok( $restricted, 'debarred is defined, restricted evaluates to true' );
320
321     my $patron_json = $patron->to_api({ embed => { algo => {} } });
322     ok( exists $patron_json->{algo} );
323     is( $patron_json->{algo}, 'algo' );
324
325     $schema->storage->txn_rollback;
326 };
327
328 subtest 'login_attempts tests' => sub {
329     plan tests => 1;
330
331     $schema->storage->txn_begin;
332
333     my $patron = $builder->build_object(
334         {
335             class => 'Koha::Patrons',
336         }
337     );
338     my $patron_info = $patron->unblessed;
339     $patron->delete;
340     delete $patron_info->{login_attempts};
341     my $new_patron = Koha::Patron->new($patron_info)->store;
342     is( $new_patron->discard_changes->login_attempts, 0, "login_attempts defaults to 0 as expected");
343
344     $schema->storage->txn_rollback;
345 };
346
347 subtest 'is_superlibrarian() tests' => sub {
348
349     plan tests => 3;
350
351     $schema->storage->txn_begin;
352
353     my $patron = $builder->build_object(
354         {
355             class => 'Koha::Patrons',
356
357             value => {
358                 flags => 16
359             }
360         }
361     );
362
363     is( $patron->is_superlibrarian, 0, 'Patron is not a superlibrarian and the method returns the correct value' );
364
365     $patron->flags(1)->store->discard_changes;
366     is( $patron->is_superlibrarian, 1, 'Patron is a superlibrarian and the method returns the correct value' );
367
368     $patron->flags(0)->store->discard_changes;
369     is( $patron->is_superlibrarian, 0, 'Patron is not a superlibrarian and the method returns the correct value' );
370
371     $schema->storage->txn_rollback;
372 };
373
374 subtest 'extended_attributes' => sub {
375
376     plan tests => 16;
377
378     my $schema = Koha::Database->new->schema;
379     $schema->storage->txn_begin;
380
381     Koha::Patron::Attribute::Types->search->delete;
382
383     my $patron_1 = $builder->build_object({class=> 'Koha::Patrons'});
384     my $patron_2 = $builder->build_object({class=> 'Koha::Patrons'});
385
386     t::lib::Mocks::mock_userenv({ patron => $patron_1 });
387
388     my $attribute_type1 = Koha::Patron::Attribute::Type->new(
389         {
390             code        => 'my code1',
391             description => 'my description1',
392             unique_id   => 1
393         }
394     )->store;
395     my $attribute_type2 = Koha::Patron::Attribute::Type->new(
396         {
397             code             => 'my code2',
398             description      => 'my description2',
399             opac_display     => 1,
400             staff_searchable => 1
401         }
402     )->store;
403
404     my $new_library = $builder->build( { source => 'Branch' } );
405     my $attribute_type_limited = Koha::Patron::Attribute::Type->new(
406         { code => 'my code3', description => 'my description3' } )->store;
407     $attribute_type_limited->library_limits( [ $new_library->{branchcode} ] );
408
409     my $attributes_for_1 = [
410         {
411             attribute => 'my attribute1',
412             code => $attribute_type1->code(),
413         },
414         {
415             attribute => 'my attribute2',
416             code => $attribute_type2->code(),
417         },
418         {
419             attribute => 'my attribute limited',
420             code => $attribute_type_limited->code(),
421         }
422     ];
423
424     my $attributes_for_2 = [
425         {
426             attribute => 'my attribute12',
427             code => $attribute_type1->code(),
428         },
429         {
430             attribute => 'my attribute limited 2',
431             code => $attribute_type_limited->code(),
432         }
433     ];
434
435     my $extended_attributes = $patron_1->extended_attributes;
436     is( ref($extended_attributes), 'Koha::Patron::Attributes', 'Koha::Patron->extended_attributes must return a Koha::Patron::Attribute set' );
437     is( $extended_attributes->count, 0, 'There should not be attribute yet');
438
439     $patron_1->extended_attributes->filter_by_branch_limitations->delete;
440     $patron_2->extended_attributes->filter_by_branch_limitations->delete;
441     $patron_1->extended_attributes($attributes_for_1);
442     $patron_2->extended_attributes($attributes_for_2);
443
444     my $extended_attributes_for_1 = $patron_1->extended_attributes;
445     is( $extended_attributes_for_1->count, 3, 'There should be 3 attributes now for patron 1');
446
447     my $extended_attributes_for_2 = $patron_2->extended_attributes;
448     is( $extended_attributes_for_2->count, 2, 'There should be 2 attributes now for patron 2');
449
450     my $attribute_12 = $extended_attributes_for_2->search({ code => $attribute_type1->code })->next;
451     is( $attribute_12->attribute, 'my attribute12', 'search by code should return the correct attribute' );
452
453     $attribute_12 = $patron_2->get_extended_attribute( $attribute_type1->code );
454     is( $attribute_12->attribute, 'my attribute12', 'Koha::Patron->get_extended_attribute should return the correct attribute value' );
455
456     my $expected_attributes_for_2 = [
457         {
458             code      => $attribute_type1->code(),
459             attribute => 'my attribute12',
460         },
461         {
462             code      => $attribute_type_limited->code(),
463             attribute => 'my attribute limited 2',
464         }
465     ];
466     # Sorting them by code
467     $expected_attributes_for_2 = [ sort { $a->{code} cmp $b->{code} } @$expected_attributes_for_2 ];
468     my @extended_attributes_for_2 = $extended_attributes_for_2->as_list;
469
470     is_deeply(
471         [
472             {
473                 code      => $extended_attributes_for_2[0]->code,
474                 attribute => $extended_attributes_for_2[0]->attribute
475             },
476             {
477                 code      => $extended_attributes_for_2[1]->code,
478                 attribute => $extended_attributes_for_2[1]->attribute
479             }
480         ],
481         $expected_attributes_for_2
482     );
483
484     # TODO - What about multiple? POD explains the problem
485     my $non_existent = $patron_2->get_extended_attribute( 'not_exist' );
486     is( $non_existent, undef, 'Koha::Patron->get_extended_attribute must return undef if the attribute does not exist' );
487
488     # Test branch limitations
489     t::lib::Mocks::mock_userenv({ patron => $patron_2 });
490     # Return all
491     $extended_attributes_for_1 = $patron_1->extended_attributes;
492     is( $extended_attributes_for_1->count, 3, 'There should be 2 attributes for patron 1, the limited one should be returned');
493
494     # Return filtered
495     $extended_attributes_for_1 = $patron_1->extended_attributes->filter_by_branch_limitations;
496     is( $extended_attributes_for_1->count, 2, 'There should be 2 attributes for patron 1, the limited one should be returned');
497
498     # Not filtered
499     my $limited_value = $patron_1->get_extended_attribute( $attribute_type_limited->code );
500     is( $limited_value->attribute, 'my attribute limited', );
501
502     ## Do we need a filtered?
503     #$limited_value = $patron_1->get_extended_attribute( $attribute_type_limited->code );
504     #is( $limited_value, undef, );
505
506     $schema->storage->txn_rollback;
507
508     subtest 'non-repeatable attributes tests' => sub {
509
510         plan tests => 3;
511
512         $schema->storage->txn_begin;
513         Koha::Patron::Attribute::Types->search->delete;
514
515         my $patron = $builder->build_object({ class => 'Koha::Patrons' });
516         my $attribute_type = $builder->build_object(
517             {
518                 class => 'Koha::Patron::Attribute::Types',
519                 value => { repeatable => 0 }
520             }
521         );
522
523         is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
524
525         throws_ok
526             {
527                 $patron->extended_attributes(
528                     [
529                         { code => $attribute_type->code, attribute => 'a' },
530                         { code => $attribute_type->code, attribute => 'b' }
531                     ]
532                 );
533             }
534             'Koha::Exceptions::Patron::Attribute::NonRepeatable',
535             'Exception thrown on non-repeatable attribute';
536
537         is( $patron->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
538
539         $schema->storage->txn_rollback;
540
541     };
542
543     subtest 'unique attributes tests' => sub {
544
545         plan tests => 5;
546
547         $schema->storage->txn_begin;
548         Koha::Patron::Attribute::Types->search->delete;
549
550         my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' });
551         my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
552
553         my $attribute_type_1 = $builder->build_object(
554             {
555                 class => 'Koha::Patron::Attribute::Types',
556                 value => { unique_id => 1 }
557             }
558         );
559
560         my $attribute_type_2 = $builder->build_object(
561             {
562                 class => 'Koha::Patron::Attribute::Types',
563                 value => { unique_id => 0 }
564             }
565         );
566
567         is( $patron_1->extended_attributes->count, 0, 'patron_1 has no extended attributes' );
568         is( $patron_2->extended_attributes->count, 0, 'patron_2 has no extended attributes' );
569
570         $patron_1->extended_attributes(
571             [
572                 { code => $attribute_type_1->code, attribute => 'a' },
573                 { code => $attribute_type_2->code, attribute => 'a' }
574             ]
575         );
576
577         throws_ok
578             {
579                 $patron_2->extended_attributes(
580                     [
581                         { code => $attribute_type_1->code, attribute => 'a' },
582                         { code => $attribute_type_2->code, attribute => 'a' }
583                     ]
584                 );
585             }
586             'Koha::Exceptions::Patron::Attribute::UniqueIDConstraint',
587             'Exception thrown on unique attribute';
588
589         is( $patron_1->extended_attributes->count, 2, 'Extended attributes stored' );
590         is( $patron_2->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
591
592         $schema->storage->txn_rollback;
593
594     };
595
596     subtest 'invalid type attributes tests' => sub {
597
598         plan tests => 3;
599
600         $schema->storage->txn_begin;
601         Koha::Patron::Attribute::Types->search->delete;
602
603         my $patron = $builder->build_object({ class => 'Koha::Patrons' });
604
605         my $attribute_type_1 = $builder->build_object(
606             {
607                 class => 'Koha::Patron::Attribute::Types',
608                 value => { repeatable => 0 }
609             }
610         );
611
612         my $attribute_type_2 = $builder->build_object(
613             {
614                 class => 'Koha::Patron::Attribute::Types'
615             }
616         );
617
618         my $type_2 = $attribute_type_2->code;
619         $attribute_type_2->delete;
620
621         is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
622
623         throws_ok
624             {
625                 $patron->extended_attributes(
626                     [
627                         { code => $attribute_type_1->code, attribute => 'a' },
628                         { code => $attribute_type_2->code, attribute => 'b' }
629                     ]
630                 );
631             }
632             'Koha::Exceptions::Patron::Attribute::InvalidType',
633             'Exception thrown on invalid attribute type';
634
635         is( $patron->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
636
637         $schema->storage->txn_rollback;
638
639     };
640
641     subtest 'globally mandatory attributes tests' => sub {
642
643         plan tests => 5;
644
645         $schema->storage->txn_begin;
646         Koha::Patron::Attribute::Types->search->delete;
647
648         my $patron = $builder->build_object({ class => 'Koha::Patrons' });
649
650         my $attribute_type_1 = $builder->build_object(
651             {
652                 class => 'Koha::Patron::Attribute::Types',
653                 value => { mandatory => 1, class => 'a', category_code => undef }
654             }
655         );
656
657         my $attribute_type_2 = $builder->build_object(
658             {
659                 class => 'Koha::Patron::Attribute::Types',
660                 value => { mandatory => 0, class => 'a', category_code => undef }
661             }
662         );
663
664         is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
665
666         throws_ok
667             {
668                 $patron->extended_attributes(
669                     [
670                         { code => $attribute_type_2->code, attribute => 'b' }
671                     ]
672                 );
673             }
674             'Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute',
675             'Exception thrown on missing mandatory attribute type';
676
677         is( $@->type, $attribute_type_1->code, 'Exception parameters are correct' );
678
679         is( $patron->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
680
681         $patron->extended_attributes(
682             [
683                 { code => $attribute_type_1->code, attribute => 'b' }
684             ]
685         );
686
687         is( $patron->extended_attributes->count, 1, 'Extended attributes succeeded' );
688
689         $schema->storage->txn_rollback;
690
691     };
692
693     subtest 'limited category mandatory attributes tests' => sub {
694
695         plan tests => 2;
696
697         $schema->storage->txn_begin;
698         Koha::Patron::Attribute::Types->search->delete;
699
700         my $patron = $builder->build_object({ class => 'Koha::Patrons' });
701
702         my $attribute_type_1 = $builder->build_object(
703             {
704                 class => 'Koha::Patron::Attribute::Types',
705                 value => { mandatory => 1, class => 'a', category_code => $patron->categorycode }
706             }
707         );
708
709         $patron->extended_attributes(
710             [
711                 { code => $attribute_type_1->code, attribute => 'a' }
712             ]
713         );
714
715         is( $patron->extended_attributes->count, 1, 'Extended attributes succeeded' );
716
717         $patron = $builder->build_object({ class => 'Koha::Patrons' });
718         # new patron, new category - they shouldn't be required to have any attributes
719
720
721         ok( $patron->extended_attributes([]), "We can set no attributes, mandatory attribute for other category not required");
722
723
724     };
725
726
727
728 };
729
730 subtest 'can_log_into() tests' => sub {
731
732     plan tests => 5;
733
734     $schema->storage->txn_begin;
735
736     my $patron = $builder->build_object(
737         {
738             class => 'Koha::Patrons',
739             value => {
740                 flags => undef
741             }
742         }
743     );
744     my $library = $builder->build_object({ class => 'Koha::Libraries' });
745
746     t::lib::Mocks::mock_preference('IndependentBranches', 1);
747
748     ok( $patron->can_log_into( $patron->library ), 'Patron can log into its own library' );
749     ok( !$patron->can_log_into( $library ), 'Patron cannot log into different library, IndependentBranches on' );
750
751     # make it a superlibrarian
752     $patron->set({ flags => 1 })->store->discard_changes;
753     ok( $patron->can_log_into( $library ), 'Superlibrarian can log into different library, IndependentBranches on' );
754
755     t::lib::Mocks::mock_preference('IndependentBranches', 0);
756
757     # No special permissions
758     $patron->set({ flags => undef })->store->discard_changes;
759     ok( $patron->can_log_into( $patron->library ), 'Patron can log into its own library' );
760     ok( $patron->can_log_into( $library ), 'Patron can log into any library' );
761
762     $schema->storage->txn_rollback;
763 };
764
765 subtest 'can_request_article() tests' => sub {
766
767     plan tests => 4;
768
769     $schema->storage->txn_begin;
770
771     t::lib::Mocks::mock_preference( 'ArticleRequests', 1 );
772
773     my $item = $builder->build_sample_item;
774
775     my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
776     my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } );
777     my $patron    = $builder->build_object( { class => 'Koha::Patrons' } );
778
779     t::lib::Mocks::mock_userenv( { branchcode => $library_2->id } );
780
781     Koha::CirculationRules->set_rule(
782         {
783             categorycode => undef,
784             branchcode   => $library_1->id,
785             rule_name    => 'open_article_requests_limit',
786             rule_value   => 4,
787         }
788     );
789
790     $builder->build_object(
791         {
792             class => 'Koha::ArticleRequests',
793             value => { status => 'REQUESTED', borrowernumber => $patron->id }
794         }
795     );
796     $builder->build_object(
797         {
798             class => 'Koha::ArticleRequests',
799             value => { status => 'PENDING', borrowernumber => $patron->id }
800         }
801     );
802     $builder->build_object(
803         {
804             class => 'Koha::ArticleRequests',
805             value => { status => 'PROCESSING', borrowernumber => $patron->id }
806         }
807     );
808     $builder->build_object(
809         {
810             class => 'Koha::ArticleRequests',
811             value => { status => 'CANCELED', borrowernumber => $patron->id }
812         }
813     );
814
815     ok(
816         $patron->can_request_article( $library_1->id ),
817         '3 current requests, 4 is the limit: allowed'
818     );
819
820     # Completed request, same day
821     my $completed = $builder->build_object(
822         {
823             class => 'Koha::ArticleRequests',
824             value => {
825                 status         => 'COMPLETED',
826                 borrowernumber => $patron->id
827             }
828         }
829     );
830
831     ok( !$patron->can_request_article( $library_1->id ),
832         '3 current requests and a completed one the same day: denied' );
833
834     $completed->updated_on(
835         dt_from_string->add( days => -1 )->set(
836             hour   => 23,
837             minute => 59,
838             second => 59,
839         )
840     )->store;
841
842     ok( $patron->can_request_article( $library_1->id ),
843         '3 current requests and a completed one the day before: allowed' );
844
845     Koha::CirculationRules->set_rule(
846         {
847             categorycode => undef,
848             branchcode   => $library_2->id,
849             rule_name    => 'open_article_requests_limit',
850             rule_value   => 3,
851         }
852     );
853
854     ok( !$patron->can_request_article,
855         'Not passing the library_id param makes it fallback to userenv: denied'
856     );
857
858     $schema->storage->txn_rollback;
859 };
860
861 subtest 'article_requests() tests' => sub {
862
863     plan tests => 3;
864
865     $schema->storage->txn_begin;
866
867     my $library = $builder->build_object({ class => 'Koha::Libraries' });
868     t::lib::Mocks::mock_userenv( { branchcode => $library->id } );
869
870     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
871
872     my $article_requests = $patron->article_requests;
873     is( ref($article_requests), 'Koha::ArticleRequests',
874         'In scalar context, type is correct' );
875     is( $article_requests->count, 0, 'No article requests' );
876
877     foreach my $i ( 0 .. 3 ) {
878
879         my $item = $builder->build_sample_item;
880
881         Koha::ArticleRequest->new(
882             {
883                 borrowernumber => $patron->id,
884                 biblionumber   => $item->biblionumber,
885                 itemnumber     => $item->id,
886                 title          => "Title",
887             }
888         )->request;
889     }
890
891     $article_requests = $patron->article_requests;
892     is( $article_requests->count, 4, '4 article requests' );
893
894     $schema->storage->txn_rollback;
895
896 };
897
898 subtest 'can_patron_change_staff_only_lists() tests' => sub {
899
900     plan tests => 3;
901
902     $schema->storage->txn_begin;
903
904     # make a user with no special permissions
905     my $patron = $builder->build_object(
906         {
907             class => 'Koha::Patrons',
908             value => {
909                 flags => undef
910             }
911         }
912     );
913     is( $patron->can_patron_change_staff_only_lists(), 0, 'Patron without permissions cannot change staff only lists');
914
915     # make it a 'Catalogue' permission
916     $patron->set({ flags => 4 })->store->discard_changes;
917     is( $patron->can_patron_change_staff_only_lists(), 1, 'Catalogue patron can change staff only lists');
918
919
920     # make it a superlibrarian
921     $patron->set({ flags => 1 })->store->discard_changes;
922     is( $patron->can_patron_change_staff_only_lists(), 1, 'Superlibrarian patron can change staff only lists');
923
924     $schema->storage->txn_rollback;
925 };
926
927 subtest 'password expiration tests' => sub {
928
929     plan tests => 5;
930
931     $schema->storage->txn_begin;
932     my $date = dt_from_string();
933     my $category = $builder->build_object({ class => 'Koha::Patron::Categories', value => {
934             password_expiry_days => 10,
935             require_strong_password => 0,
936         }
937     });
938     my $patron = $builder->build_object({ class=> 'Koha::Patrons', value => {
939             categorycode => $category->categorycode,
940             password => 'hats'
941         }
942     });
943
944     $patron->delete()->store()->discard_changes(); # Make sure we are storing a 'new' patron
945
946     is( $patron->password_expiration_date(), $date->add( days => 10 )->ymd() , "Password expiration date set correctly on patron creation");
947
948     $patron = $builder->build_object({ class => 'Koha::Patrons', value => {
949             categorycode => $category->categorycode,
950             password => undef
951         }
952     });
953     $patron->delete()->store()->discard_changes();
954
955     is( $patron->password_expiration_date(), undef, "Password expiration date is not set if patron does not have a password");
956
957     $category->password_expiry_days(undef)->store();
958     $patron = $builder->build_object({ class => 'Koha::Patrons', value => {
959             categorycode => $category->categorycode
960         }
961     });
962     $patron->delete()->store()->discard_changes();
963     is( $patron->password_expiration_date(), undef, "Password expiration date is not set if category does not have expiry days set");
964
965     $schema->storage->txn_rollback;
966
967     subtest 'password_expired' => sub {
968
969         plan tests => 3;
970
971         $schema->storage->txn_begin;
972         my $date = dt_from_string();
973         $patron = $builder->build_object({ class => 'Koha::Patrons', value => {
974                 password_expiration_date => undef
975             }
976         });
977         is( $patron->password_expired, 0, "Patron with no password expiration date, password not expired");
978         $patron->password_expiration_date( $date )->store;
979         $patron->discard_changes();
980         is( $patron->password_expired, 1, "Patron with password expiration date of today, password expired");
981         $date->subtract( days => 1 );
982         $patron->password_expiration_date( $date )->store;
983         $patron->discard_changes();
984         is( $patron->password_expired, 1, "Patron with password expiration date in past, password expired");
985
986         $schema->storage->txn_rollback;
987     };
988
989     subtest 'set_password' => sub {
990
991         plan tests => 4;
992
993         $schema->storage->txn_begin;
994
995         my $date = dt_from_string();
996         my $category = $builder->build_object({ class => 'Koha::Patron::Categories', value => {
997                 password_expiry_days => 10
998             }
999         });
1000         my $patron = $builder->build_object({ class => 'Koha::Patrons', value => {
1001                 categorycode => $category->categorycode,
1002                 password_expiration_date =>  $date->subtract( days => 1 )
1003             }
1004         });
1005         is( $patron->password_expired, 1, "Patron password is expired");
1006
1007         $date = dt_from_string();
1008         $patron->set_password({ password => "kitten", skip_validation => 1 })->discard_changes();
1009         is( $patron->password_expired, 0, "Patron password no longer expired when new password set");
1010         is( $patron->password_expiration_date(), $date->add( days => 10 )->ymd(), "Password expiration date set correctly on patron creation");
1011
1012
1013         $category->password_expiry_days( undef )->store();
1014         $patron->set_password({ password => "puppies", skip_validation => 1 })->discard_changes();
1015         is( $patron->password_expiration_date(), undef, "Password expiration date is unset if category does not have expiry days");
1016
1017         $schema->storage->txn_rollback;
1018     };
1019
1020 };
1021
1022 subtest 'safe_to_delete() tests' => sub {
1023
1024     plan tests => 14;
1025
1026     $schema->storage->txn_begin;
1027
1028     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1029
1030     ## Make it the anonymous
1031     t::lib::Mocks::mock_preference( 'AnonymousPatron', $patron->id );
1032
1033     ok( !$patron->safe_to_delete, 'Cannot delete, it is the anonymous patron' );
1034     my $message = $patron->safe_to_delete->messages->[0];
1035     is( $message->type, 'error', 'Type is error' );
1036     is( $message->message, 'is_anonymous_patron', 'Cannot delete, it is the anonymous patron' );
1037     # cleanup
1038     t::lib::Mocks::mock_preference( 'AnonymousPatron', 0 );
1039
1040     ## Make it have a checkout
1041     my $checkout = $builder->build_object(
1042         {
1043             class => 'Koha::Checkouts',
1044             value => { borrowernumber => $patron->id }
1045         }
1046     );
1047
1048     ok( !$patron->safe_to_delete, 'Cannot delete, has checkouts' );
1049     $message = $patron->safe_to_delete->messages->[0];
1050     is( $message->type, 'error', 'Type is error' );
1051     is( $message->message, 'has_checkouts', 'Cannot delete, has checkouts' );
1052     # cleanup
1053     $checkout->delete;
1054
1055     ## Make it have a guarantee
1056     t::lib::Mocks::mock_preference( 'borrowerRelationship', 'parent' );
1057     $builder->build_object({ class => 'Koha::Patrons' })
1058             ->add_guarantor({ guarantor_id => $patron->id, relationship => 'parent' });
1059
1060     ok( !$patron->safe_to_delete, 'Cannot delete, has guarantees' );
1061     $message = $patron->safe_to_delete->messages->[0];
1062     is( $message->type, 'error', 'Type is error' );
1063     is( $message->message, 'has_guarantees', 'Cannot delete, has guarantees' );
1064
1065     # cleanup
1066     $patron->guarantee_relationships->delete;
1067
1068     ## Make it have debt
1069     my $debit = $patron->account->add_debit({ amount => 10, interface => 'intranet', type => 'MANUAL' });
1070
1071     ok( !$patron->safe_to_delete, 'Cannot delete, has debt' );
1072     $message = $patron->safe_to_delete->messages->[0];
1073     is( $message->type, 'error', 'Type is error' );
1074     is( $message->message, 'has_debt', 'Cannot delete, has debt' );
1075     # cleanup
1076     $patron->account->pay({ amount => 10, debits => [ $debit ] });
1077
1078     ## Happy case :-D
1079     ok( $patron->safe_to_delete, 'Can delete, all conditions met' );
1080     my $messages = $patron->safe_to_delete->messages;
1081     is_deeply( $messages, [], 'Patron can be deleted, no messages' );
1082 };
1083
1084 subtest 'article_request_fee() tests' => sub {
1085
1086     plan tests => 3;
1087
1088     $schema->storage->txn_begin;
1089
1090     # Cleanup, to avoid interference
1091     Koha::CirculationRules->search( { rule_name => 'article_request_fee' } )->delete;
1092
1093     t::lib::Mocks::mock_preference( 'ArticleRequests', 1 );
1094
1095     my $item = $builder->build_sample_item;
1096
1097     my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
1098     my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } );
1099     my $patron    = $builder->build_object( { class => 'Koha::Patrons' } );
1100
1101     # Rule that should never be picked, because the patron's category is always picked
1102     Koha::CirculationRules->set_rule(
1103         {   categorycode => undef,
1104             branchcode   => undef,
1105             rule_name    => 'article_request_fee',
1106             rule_value   => 1,
1107         }
1108     );
1109
1110     is( $patron->article_request_fee( { library_id => $library_2->id } ), 1, 'library_id used correctly' );
1111
1112     Koha::CirculationRules->set_rule(
1113         {   categorycode => $patron->categorycode,
1114             branchcode   => undef,
1115             rule_name    => 'article_request_fee',
1116             rule_value   => 2,
1117         }
1118     );
1119
1120     Koha::CirculationRules->set_rule(
1121         {   categorycode => $patron->categorycode,
1122             branchcode   => $library_1->id,
1123             rule_name    => 'article_request_fee',
1124             rule_value   => 3,
1125         }
1126     );
1127
1128     is( $patron->article_request_fee( { library_id => $library_2->id } ), 2, 'library_id used correctly' );
1129
1130     t::lib::Mocks::mock_userenv( { branchcode => $library_1->id } );
1131
1132     is( $patron->article_request_fee(), 3, 'env used correctly' );
1133
1134     $schema->storage->txn_rollback;
1135 };
1136
1137 subtest 'add_article_request_fee_if_needed() tests' => sub {
1138
1139     plan tests => 12;
1140
1141     $schema->storage->txn_begin;
1142
1143     my $amount = 0;
1144
1145     my $patron_mock = Test::MockModule->new('Koha::Patron');
1146     $patron_mock->mock( 'article_request_fee', sub { return $amount; } );
1147
1148     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1149
1150     is( $patron->article_request_fee, $amount, 'article_request_fee mocked' );
1151
1152     my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
1153     my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } );
1154     my $staff     = $builder->build_object( { class => 'Koha::Patrons' } );
1155     my $item      = $builder->build_sample_item;
1156
1157     t::lib::Mocks::mock_userenv(
1158         { branchcode => $library_1->id, patron => $staff } );
1159
1160     my $debit = $patron->add_article_request_fee_if_needed();
1161     is( $debit, undef, 'No fee, no debit line' );
1162
1163     # positive value
1164     $amount = 1;
1165
1166     $debit = $patron->add_article_request_fee_if_needed({ item_id => $item->id });
1167     is( ref($debit), 'Koha::Account::Line', 'Debit object type correct' );
1168     is( $debit->amount, $amount,
1169         'amount set to $patron->article_request_fee value' );
1170     is( $debit->manager_id, $staff->id,
1171         'manager_id set to userenv session user' );
1172     is( $debit->branchcode, $library_1->id,
1173         'branchcode set to userenv session library' );
1174     is( $debit->debit_type_code, 'ARTICLE_REQUEST',
1175         'debit_type_code set correctly' );
1176     is( $debit->itemnumber, $item->id,
1177         'itemnumber set correctly' );
1178
1179     $amount = 100;
1180
1181     $debit = $patron->add_article_request_fee_if_needed({ library_id => $library_2->id });
1182     is( ref($debit), 'Koha::Account::Line', 'Debit object type correct' );
1183     is( $debit->amount, $amount,
1184         'amount set to $patron->article_request_fee value' );
1185     is( $debit->branchcode, $library_2->id,
1186         'branchcode set to userenv session library' );
1187     is( $debit->itemnumber, undef,
1188         'itemnumber set correctly to undef' );
1189
1190     $schema->storage->txn_rollback;
1191 };
1192
1193 subtest 'messages' => sub {
1194     plan tests => 4;
1195
1196     $schema->storage->txn_begin;
1197
1198     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1199     my $messages = $patron->messages;
1200     is( $messages->count, 0, "No message yet" );
1201     my $message_1 = $builder->build_object(
1202         {
1203             class => 'Koha::Patron::Messages',
1204             value => { borrowernumber => $patron->borrowernumber }
1205         }
1206     );
1207     my $message_2 = $builder->build_object(
1208         {
1209             class => 'Koha::Patron::Messages',
1210             value => { borrowernumber => $patron->borrowernumber }
1211         }
1212     );
1213
1214     $messages = $patron->messages;
1215     is( $messages->count, 2, "There are two messages for this patron" );
1216     is( $messages->next->message, $message_1->message );
1217     is( $messages->next->message, $message_2->message );
1218     $schema->storage->txn_rollback;
1219 };
1220
1221 subtest 'recalls() tests' => sub {
1222
1223     plan tests => 3;
1224
1225     $schema->storage->txn_begin;
1226
1227     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1228     my $biblio1 = $builder->build_object({ class => 'Koha::Biblios' });
1229     my $item1 = $builder->build_object({ class => 'Koha::Items' }, { value => { biblionumber => $biblio1->biblionumber } });
1230     my $biblio2 = $builder->build_object({ class => 'Koha::Biblios' });
1231     my $item2 = $builder->build_object({ class => 'Koha::Items' }, { value => { biblionumber => $biblio2->biblionumber } });
1232
1233     Koha::Recall->new(
1234         {   biblio_id         => $biblio1->biblionumber,
1235             patron_id         => $patron->borrowernumber,
1236             item_id           => $item1->itemnumber,
1237             pickup_library_id => $patron->branchcode,
1238             created_date      => \'NOW()',
1239             item_level        => 1,
1240         }
1241     )->store;
1242     Koha::Recall->new(
1243         {   biblio_id         => $biblio2->biblionumber,
1244             patron_id         => $patron->borrowernumber,
1245             item_id           => $item2->itemnumber,
1246             pickup_library_id => $patron->branchcode,
1247             created_date      => \'NOW()',
1248             item_level        => 1,
1249         }
1250     )->store;
1251     Koha::Recall->new(
1252         {   biblio_id         => $biblio1->biblionumber,
1253             patron_id         => $patron->borrowernumber,
1254             item_id           => undef,
1255             pickup_library_id => $patron->branchcode,
1256             created_date      => \'NOW()',
1257             item_level        => 0,
1258         }
1259     )->store;
1260     my $recall = Koha::Recall->new(
1261         {   biblio_id         => $biblio1->biblionumber,
1262             patron_id         => $patron->borrowernumber,
1263             item_id           => undef,
1264             pickup_library_id => $patron->branchcode,
1265             created_date      => \'NOW()',
1266             item_level        => 0,
1267         }
1268     )->store;
1269     $recall->set_cancelled;
1270
1271     is( $patron->recalls->count,                                                                       4, "Correctly gets this patron's recalls" );
1272     is( $patron->recalls->filter_by_current->count,                                                    3, "Correctly gets this patron's active recalls" );
1273     is( $patron->recalls->filter_by_current->search( { biblio_id => $biblio1->biblionumber } )->count, 2, "Correctly gets this patron's active recalls on a specific biblio" );
1274
1275     $schema->storage->txn_rollback;
1276 };
1277
1278 subtest 'encode_secret and decoded_secret' => sub {
1279     plan tests => 5;
1280     $schema->storage->txn_begin;
1281
1282     t::lib::Mocks::mock_config('encryption_key', 't0P_secret');
1283
1284     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1285     is( $patron->decoded_secret, undef, 'TestBuilder does not initialize it' );
1286     $patron->secret(q{});
1287     is( $patron->decoded_secret, q{}, 'Empty string case' );
1288
1289     $patron->encode_secret('encrypt_me'); # Note: lazy testing; should be base32 string normally.
1290     is( length($patron->secret) > 0, 1, 'Secret length' );
1291     isnt( $patron->secret, 'encrypt_me', 'Encrypted column' );
1292     is( $patron->decoded_secret, 'encrypt_me', 'Decrypted column' );
1293
1294     $schema->storage->txn_rollback;
1295 };
1296
1297 subtest 'notify_library_of_registration()' => sub {
1298
1299     plan tests => 6;
1300
1301     $schema->storage->txn_begin;
1302     my $dbh = C4::Context->dbh;
1303
1304     my $library = $builder->build_object(
1305         {
1306             class => 'Koha::Libraries',
1307             value => {
1308                 branchemail   => 'from@mybranch.com',
1309                 branchreplyto => 'to@mybranch.com'
1310             }
1311         }
1312     );
1313     my $patron = $builder->build_object(
1314         {
1315             class => 'Koha::Patrons',
1316             value => {
1317                 branchcode => $library->branchcode
1318             }
1319         }
1320     );
1321
1322     t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'root@localhost' );
1323     t::lib::Mocks::mock_preference( 'EmailAddressForPatronRegistrations', 'library@localhost' );
1324
1325     # Test when EmailPatronRegistrations equals BranchEmailAddress
1326     t::lib::Mocks::mock_preference( 'EmailPatronRegistrations', 'BranchEmailAddress' );
1327     is( $patron->notify_library_of_registration(C4::Context->preference('EmailPatronRegistrations')), 1, 'OPAC_REG email is queued if EmailPatronRegistration syspref equals BranchEmailAddress');
1328     my $sth = $dbh->prepare("SELECT to_address FROM message_queue where borrowernumber = ?");
1329     $sth->execute( $patron->borrowernumber );
1330     my $to_address = $sth->fetchrow_array;
1331     is( $to_address, 'to@mybranch.com', 'OPAC_REG email queued to go to branchreplyto address when EmailPatronRegistration equals BranchEmailAddress' );
1332     $dbh->do(q|DELETE FROM message_queue|);
1333
1334     # Test when EmailPatronRegistrations equals EmailAddressForPatronRegistrations
1335     t::lib::Mocks::mock_preference( 'EmailPatronRegistrations', 'EmailAddressForPatronRegistrations' );
1336     is( $patron->notify_library_of_registration(C4::Context->preference('EmailPatronRegistrations')), 1, 'OPAC_REG email is queued if EmailPatronRegistration syspref equals EmailAddressForPatronRegistrations');
1337     $sth->execute( $patron->borrowernumber );
1338     $to_address = $sth->fetchrow_array;
1339     is( $to_address, 'library@localhost', 'OPAC_REG email queued to go to EmailAddressForPatronRegistrations syspref when EmailPatronRegistration equals EmailAddressForPatronRegistrations' );
1340     $dbh->do(q|DELETE FROM message_queue|);
1341
1342     # Test when EmailPatronRegistrations equals KohaAdminEmailAddress
1343     t::lib::Mocks::mock_preference( 'EmailPatronRegistrations', 'KohaAdminEmailAddress' );
1344     t::lib::Mocks::mock_preference( 'ReplyToDefault', 'root@localhost' ); # FIXME Remove localhost
1345     is( $patron->notify_library_of_registration(C4::Context->preference('EmailPatronRegistrations')), 1, 'OPAC_REG email is queued if EmailPatronRegistration syspref equals KohaAdminEmailAddress');
1346     $sth->execute( $patron->borrowernumber );
1347     $to_address = $sth->fetchrow_array;
1348     is( $to_address, 'root@localhost', 'OPAC_REG email queued to go to KohaAdminEmailAddress syspref when EmailPatronRegistration equals KohaAdminEmailAddress' );
1349     $dbh->do(q|DELETE FROM message_queue|);
1350
1351     $schema->storage->txn_rollback;
1352 };
1353
1354 subtest 'get_savings tests' => sub {
1355
1356     plan tests => 4;
1357
1358     $schema->storage->txn_begin;
1359
1360     my $library = $builder->build_object({ class => 'Koha::Libraries' });
1361     my $patron = $builder->build_object({ class => 'Koha::Patrons' }, { value => { branchcode => $library->branchcode } });
1362
1363     t::lib::Mocks::mock_userenv({ patron => $patron, branchcode => $library->branchcode });
1364
1365     my $biblio = $builder->build_sample_biblio;
1366     my $item1 = $builder->build_sample_item(
1367         {
1368             biblionumber     => $biblio->biblionumber,
1369             library          => $library->branchcode,
1370             replacementprice => rand(20),
1371         }
1372     );
1373     my $item2 = $builder->build_sample_item(
1374         {
1375             biblionumber     => $biblio->biblionumber,
1376             library          => $library->branchcode,
1377             replacementprice => rand(20),
1378         }
1379     );
1380
1381     is( $patron->get_savings, 0, 'No checkouts, no savings' );
1382
1383     # Add an old checkout with deleted itemnumber
1384     $builder->build_object({ class => 'Koha::Old::Checkouts', value => { itemnumber => undef, borrowernumber => $patron->id } });
1385
1386     is( $patron->get_savings, 0, 'No checkouts with itemnumber, no savings' );
1387
1388     AddIssue( $patron->unblessed, $item1->barcode );
1389     AddIssue( $patron->unblessed, $item2->barcode );
1390
1391     my $savings = $patron->get_savings;
1392     is( $savings + 0, $item1->replacementprice + $item2->replacementprice, "Savings correctly calculated from current issues" );
1393
1394     AddReturn( $item2->barcode, $item2->homebranch );
1395
1396     $savings = $patron->get_savings;
1397     is( $savings + 0, $item1->replacementprice + $item2->replacementprice, "Savings correctly calculated from current and old issues" );
1398
1399     $schema->storage->txn_rollback;
1400 };