3 # Copyright 2015 Koha Development team
5 # This file is part of Koha
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.
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.
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>.
22 use Test
::More tests
=> 41;
34 use C4
::Auth
qw(checkpw_hash);
40 use Koha
::Old
::Patrons
;
41 use Koha
::Patron
::Attribute
::Types
;
42 use Koha
::Patron
::Categories
;
43 use Koha
::Patron
::Relationship
;
46 use Koha
::Virtualshelves
;
48 use t
::lib
::TestBuilder
;
51 my $schema = Koha
::Database
->new->schema;
52 $schema->storage->txn_begin;
54 my $builder = t
::lib
::TestBuilder
->new;
55 my $library = $builder->build({source
=> 'Branch' });
56 my $category = $builder->build({source
=> 'Category' });
57 my $nb_of_patrons = Koha
::Patrons
->search->count;
58 my $new_patron_1 = Koha
::Patron
->new(
59 { cardnumber
=> 'test_cn_1',
60 branchcode
=> $library->{branchcode
},
61 categorycode
=> $category->{categorycode
},
62 surname
=> 'surname for patron1',
63 firstname
=> 'firstname for patron1',
64 userid
=> 'a_nonexistent_userid_1',
65 flags
=> 1, # Is superlibrarian
68 my $new_patron_2 = Koha
::Patron
->new(
69 { cardnumber
=> 'test_cn_2',
70 branchcode
=> $library->{branchcode
},
71 categorycode
=> $category->{categorycode
},
72 surname
=> 'surname for patron2',
73 firstname
=> 'firstname for patron2',
74 userid
=> 'a_nonexistent_userid_2',
78 t
::lib
::Mocks
::mock_userenv
({ patron
=> $new_patron_1 });
80 is
( Koha
::Patrons
->search->count, $nb_of_patrons + 2, 'The 2 patrons should have been added' );
82 my $retrieved_patron_1 = Koha
::Patrons
->find( $new_patron_1->borrowernumber );
83 is
( $retrieved_patron_1->cardnumber, $new_patron_1->cardnumber, 'Find a patron by borrowernumber should return the correct patron' );
85 subtest
'library' => sub {
87 is
( $retrieved_patron_1->library->branchcode, $library->{branchcode
}, 'Koha::Patron->library should return the correct library' );
88 is
( ref($retrieved_patron_1->library), 'Koha::Library', 'Koha::Patron->library should return a Koha::Library object' );
91 subtest
'sms_provider' => sub {
93 my $sms_provider = $builder->build({source
=> 'SmsProvider' });
94 is
( $retrieved_patron_1->sms_provider, undef, '->sms_provider should return undef if none defined' );
95 $retrieved_patron_1->sms_provider_id( $sms_provider->{id
} )->store;
96 is_deeply
( $retrieved_patron_1->sms_provider->unblessed, $sms_provider, 'Koha::Patron->sms_provider returns the correct SMS provider' );
97 is
( ref($retrieved_patron_1->sms_provider), 'Koha::SMS::Provider', 'Koha::Patron->sms_provider should return a Koha::SMS::Provider object' );
100 subtest
'guarantees' => sub {
103 t
::lib
::Mocks
::mock_preference
( 'borrowerRelationship', 'test|test2' );
105 my $guarantees = $new_patron_1->guarantee_relationships;
106 is
( ref($guarantees), 'Koha::Patron::Relationships', 'Koha::Patron->guarantees should return a Koha::Patrons result set in a scalar context' );
107 is
( $guarantees->count, 0, 'new_patron_1 should have 0 guarantee relationships' );
108 my @guarantees = $new_patron_1->guarantee_relationships;
109 is
( ref(\
@guarantees), 'ARRAY', 'Koha::Patron->guarantee_relationships should return an array in a list context' );
110 is
( scalar(@guarantees), 0, 'new_patron_1 should have 0 guarantee' );
112 my $guarantee_1 = $builder->build({ source
=> 'Borrower' });
113 my $relationship_1 = Koha
::Patron
::Relationship
->new( { guarantor_id
=> $new_patron_1->id, guarantee_id
=> $guarantee_1->{borrowernumber
}, relationship
=> 'test' } )->store();
114 my $guarantee_2 = $builder->build({ source
=> 'Borrower' });
115 my $relationship_2 = Koha
::Patron
::Relationship
->new( { guarantor_id
=> $new_patron_1->id, guarantee_id
=> $guarantee_2->{borrowernumber
}, relationship
=> 'test' } )->store();
117 $guarantees = $new_patron_1->guarantee_relationships;
118 is
( ref($guarantees), 'Koha::Patron::Relationships', 'Koha::Patron->guarantee_relationships should return a Koha::Patrons result set in a scalar context' );
119 is
( $guarantees->count, 2, 'new_patron_1 should have 2 guarantees' );
120 @guarantees = $new_patron_1->guarantee_relationships;
121 is
( ref(\
@guarantees), 'ARRAY', 'Koha::Patron->guarantee_relationships should return an array in a list context' );
122 is
( scalar(@guarantees), 2, 'new_patron_1 should have 2 guarantees' );
123 $_->delete for @guarantees;
125 #Test return order of guarantees BZ 18635
126 my $categorycode = $builder->build({ source
=> 'Category' })->{categorycode
};
127 my $branchcode = $builder->build({ source
=> 'Branch' })->{branchcode
};
129 my $guarantor = $builder->build_object( { class => 'Koha::Patrons' } );
131 my $order_guarantee1 = $builder->build_object(
133 class => 'Koha::Patrons',
139 $builder->build_object(
141 class => 'Koha::Patron::Relationships',
143 guarantor_id
=> $guarantor->id,
144 guarantee_id
=> $order_guarantee1,
145 relationship
=> 'test',
150 my $order_guarantee2 = $builder->build_object(
152 class => 'Koha::Patrons',
158 $builder->build_object(
160 class => 'Koha::Patron::Relationships',
162 guarantor_id
=> $guarantor->id,
163 guarantee_id
=> $order_guarantee2,
164 relationship
=> 'test',
169 my $order_guarantee3 = $builder->build_object(
171 class => 'Koha::Patrons',
174 firstname
=> 'Walrus',
178 $builder->build_object(
180 class => 'Koha::Patron::Relationships',
182 guarantor_id
=> $guarantor->id,
183 guarantee_id
=> $order_guarantee3,
184 relationship
=> 'test',
189 my $order_guarantee4 = $builder->build_object(
191 class => 'Koha::Patrons',
194 firstname
=> 'Vulture',
195 guarantorid
=> $guarantor->borrowernumber
199 $builder->build_object(
201 class => 'Koha::Patron::Relationships',
203 guarantor_id
=> $guarantor->id,
204 guarantee_id
=> $order_guarantee4,
205 relationship
=> 'test',
210 my $order_guarantee5 = $builder->build_object(
212 class => 'Koha::Patrons',
215 firstname
=> 'Unicorn',
216 guarantorid
=> $guarantor->borrowernumber
220 my $r = $builder->build_object(
222 class => 'Koha::Patron::Relationships',
224 guarantor_id
=> $guarantor->id,
225 guarantee_id
=> $order_guarantee5,
226 relationship
=> 'test',
231 $guarantees = $guarantor->guarantee_relationships->guarantees;
233 is
( $guarantees->next()->borrowernumber, $order_guarantee5, "Return first guarantor alphabetically" );
234 is
( $guarantees->next()->borrowernumber, $order_guarantee4, "Return second guarantor alphabetically" );
235 is
( $guarantees->next()->borrowernumber, $order_guarantee3, "Return third guarantor alphabetically" );
236 is
( $guarantees->next()->borrowernumber, $order_guarantee2, "Return fourth guarantor alphabetically" );
237 is
( $guarantees->next()->borrowernumber, $order_guarantee1, "Return fifth guarantor alphabetically" );
240 subtest
'category' => sub {
242 my $patron_category = $new_patron_1->category;
243 is
( ref( $patron_category), 'Koha::Patron::Category', );
244 is
( $patron_category->categorycode, $category->{categorycode
}, );
247 subtest
'siblings' => sub {
249 my $siblings = $new_patron_1->siblings;
250 is
( $siblings, undef, 'Koha::Patron->siblings should not crashed if the patron has no guarantor' );
251 my $guarantee_1 = $builder->build( { source
=> 'Borrower' } );
252 my $relationship_1 = Koha
::Patron
::Relationship
->new( { guarantor_id
=> $new_patron_1->borrowernumber, guarantee_id
=> $guarantee_1->{borrowernumber
}, relationship
=> 'test' } )->store();
253 my $retrieved_guarantee_1 = Koha
::Patrons
->find($guarantee_1);
254 $siblings = $retrieved_guarantee_1->siblings;
255 is
( ref($siblings), 'Koha::Patrons', 'Koha::Patron->siblings should return a Koha::Patrons result set in a scalar context' );
256 my @siblings = $retrieved_guarantee_1->siblings;
257 is
( ref( \
@siblings ), 'ARRAY', 'Koha::Patron->siblings should return an array in a list context' );
258 is
( $siblings->count, 0, 'guarantee_1 should not have siblings yet' );
259 my $guarantee_2 = $builder->build( { source
=> 'Borrower' } );
260 my $relationship_2 = Koha
::Patron
::Relationship
->new( { guarantor_id
=> $new_patron_1->borrowernumber, guarantee_id
=> $guarantee_2->{borrowernumber
}, relationship
=> 'test' } )->store();
261 my $guarantee_3 = $builder->build( { source
=> 'Borrower' } );
262 my $relationship_3 = Koha
::Patron
::Relationship
->new( { guarantor_id
=> $new_patron_1->borrowernumber, guarantee_id
=> $guarantee_3->{borrowernumber
}, relationship
=> 'test' } )->store();
263 $siblings = $retrieved_guarantee_1->siblings;
264 is
( $siblings->count, 2, 'guarantee_1 should have 2 siblings' );
265 is
( $guarantee_2->{borrowernumber
}, $siblings->next->borrowernumber, 'guarantee_2 should exist in the guarantees' );
266 is
( $guarantee_3->{borrowernumber
}, $siblings->next->borrowernumber, 'guarantee_3 should exist in the guarantees' );
267 $_->delete for $retrieved_guarantee_1->siblings;
268 $retrieved_guarantee_1->delete;
271 subtest
'has_overdues' => sub {
274 my $item_1 = $builder->build_sample_item;
275 my $retrieved_patron = Koha
::Patrons
->find( $new_patron_1->borrowernumber );
276 is
( $retrieved_patron->has_overdues, 0, );
278 my $tomorrow = DateTime
->today( time_zone
=> C4
::Context
->tz() )->add( days
=> 1 );
279 my $issue = Koha
::Checkout
->new({ borrowernumber
=> $new_patron_1->id, itemnumber
=> $item_1->itemnumber, date_due
=> $tomorrow, branchcode
=> $library->{branchcode
} })->store();
280 is
( $retrieved_patron->has_overdues, 0, );
282 my $yesterday = DateTime
->today(time_zone
=> C4
::Context
->tz())->add( days
=> -1 );
283 $issue = Koha
::Checkout
->new({ borrowernumber
=> $new_patron_1->id, itemnumber
=> $item_1->itemnumber, date_due
=> $yesterday, branchcode
=> $library->{branchcode
} })->store();
284 $retrieved_patron = Koha
::Patrons
->find( $new_patron_1->borrowernumber );
285 is
( $retrieved_patron->has_overdues, 1, );
289 subtest
'is_expired' => sub {
291 my $patron = $builder->build({ source
=> 'Borrower' });
292 $patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
293 $patron->dateexpiry( undef )->store->discard_changes;
294 is
( $patron->is_expired, 0, 'Patron should not be considered expired if dateexpiry is not set');
295 $patron->dateexpiry( dt_from_string
)->store->discard_changes;
296 is
( $patron->is_expired, 0, 'Patron should not be considered expired if dateexpiry is today');
297 $patron->dateexpiry( dt_from_string
->add( days
=> 1 ) )->store->discard_changes;
298 is
( $patron->is_expired, 0, 'Patron should not be considered expired if dateexpiry is tomorrow');
299 $patron->dateexpiry( dt_from_string
->add( days
=> -1 ) )->store->discard_changes;
300 is
( $patron->is_expired, 1, 'Patron should be considered expired if dateexpiry is yesterday');
305 subtest
'is_going_to_expire' => sub {
308 my $today = dt_from_string
(undef, undef, 'floating');
309 my $patron = $builder->build({ source
=> 'Borrower' });
310 $patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
311 $patron->dateexpiry( undef )->store->discard_changes;
312 is
( $patron->is_going_to_expire, 0, 'Patron should not be considered going to expire if dateexpiry is not set');
314 t
::lib
::Mocks
::mock_preference
('NotifyBorrowerDeparture', 0);
315 $patron->dateexpiry( $today )->store->discard_changes;
316 is
( $patron->is_going_to_expire, 0, 'Patron should not be considered going to expire if dateexpiry is today');
318 $patron->dateexpiry( $today )->store->discard_changes;
319 is
( $patron->is_going_to_expire, 0, 'Patron should not be considered going to expire if dateexpiry is today and pref is 0');
321 t
::lib
::Mocks
::mock_preference
('NotifyBorrowerDeparture', 10);
322 $patron->dateexpiry( $today->clone->add( days
=> 11 ) )->store->discard_changes;
323 is
( $patron->is_going_to_expire, 0, 'Patron should not be considered going to expire if dateexpiry is 11 days ahead and pref is 10');
325 t
::lib
::Mocks
::mock_preference
('NotifyBorrowerDeparture', 0);
326 $patron->dateexpiry( $today->clone->add( days
=> 10 ) )->store->discard_changes;
327 is
( $patron->is_going_to_expire, 0, 'Patron should not be considered going to expire if dateexpiry is 10 days ahead and pref is 0');
329 t
::lib
::Mocks
::mock_preference
('NotifyBorrowerDeparture', 10);
330 $patron->dateexpiry( $today->clone->add( days
=> 10 ) )->store->discard_changes;
331 is
( $patron->is_going_to_expire, 0, 'Patron should not be considered going to expire if dateexpiry is 10 days ahead and pref is 10');
334 t
::lib
::Mocks
::mock_preference
('NotifyBorrowerDeparture', 10);
335 $patron->dateexpiry( $today->clone->add( days
=> 20 ) )->store->discard_changes;
336 is
( $patron->is_going_to_expire, 0, 'Patron should not be considered going to expire if dateexpiry is 20 days ahead and pref is 10');
338 t
::lib
::Mocks
::mock_preference
('NotifyBorrowerDeparture', 20);
339 $patron->dateexpiry( $today->clone->add( days
=> 10 ) )->store->discard_changes;
340 is
( $patron->is_going_to_expire, 1, 'Patron should be considered going to expire if dateexpiry is 10 days ahead and pref is 20');
342 { # Testing invalid is going to expiry date
343 t
::lib
::Mocks
::mock_preference
('NotifyBorrowerDeparture', 30);
344 # mock_config does not work here, because of tz vs timezone subroutines
345 my $context = Test
::MockModule
->new('C4::Context');
346 $context->mock( 'tz', sub {
349 $patron->dateexpiry(DateTime
->new( year
=> 2019, month
=> 12, day
=> 3 ))->store;
350 eval { $patron->is_going_to_expire };
351 is
( $@
, '', 'On invalid "is going to expire" date, the method should not crash with "Invalid local time for date in time zone"');
352 $context->unmock('tz');
359 subtest
'renew_account' => sub {
362 for my $date ( '2016-03-31', '2016-11-30', '2019-01-31', dt_from_string
() ) {
363 my $dt = dt_from_string
( $date, 'iso' );
364 Time
::Fake
->offset( $dt->epoch );
365 my $a_month_ago = $dt->clone->subtract( months
=> 1, end_of_month
=> 'limit' )->truncate( to
=> 'day' );
366 my $a_year_later = $dt->clone->add( months
=> 12, end_of_month
=> 'limit' )->truncate( to
=> 'day' );
367 my $a_year_later_minus_a_month = $a_month_ago->clone->add( months
=> 12, end_of_month
=> 'limit' )->truncate( to
=> 'day' );
368 my $a_month_later = $dt->clone->add( months
=> 1 , end_of_month
=> 'limit' )->truncate( to
=> 'day' );
369 my $a_year_later_plus_a_month = $a_month_later->clone->add( months
=> 12, end_of_month
=> 'limit' )->truncate( to
=> 'day' );
370 my $patron_category = $builder->build(
371 { source
=> 'Category',
373 enrolmentperiod
=> 12,
374 enrolmentperioddate
=> undef,
378 my $patron = $builder->build(
379 { source
=> 'Borrower',
381 dateexpiry
=> $a_month_ago,
382 categorycode
=> $patron_category->{categorycode
},
383 date_renewed
=> undef, # Force builder to not populate the column for new patron
387 my $patron_2 = $builder->build(
388 { source
=> 'Borrower',
390 dateexpiry
=> $a_month_ago,
391 categorycode
=> $patron_category->{categorycode
},
395 my $patron_3 = $builder->build(
396 { source
=> 'Borrower',
398 dateexpiry
=> $a_month_later,
399 categorycode
=> $patron_category->{categorycode
},
403 my $retrieved_patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
404 my $retrieved_patron_2 = Koha
::Patrons
->find( $patron_2->{borrowernumber
} );
405 my $retrieved_patron_3 = Koha
::Patrons
->find( $patron_3->{borrowernumber
} );
407 is
( $retrieved_patron->date_renewed, undef, "Date renewed is not set for patrons that have never been renewed" );
409 t
::lib
::Mocks
::mock_preference
( 'BorrowerRenewalPeriodBase', 'dateexpiry' );
410 t
::lib
::Mocks
::mock_preference
( 'BorrowersLog', 1 );
411 my $expiry_date = $retrieved_patron->renew_account;
412 is
( $expiry_date, $a_year_later_minus_a_month, "$a_month_ago + 12 months must be $a_year_later_minus_a_month" );
413 my $retrieved_expiry_date = Koha
::Patrons
->find( $patron->{borrowernumber
} )->dateexpiry;
414 is
( dt_from_string
($retrieved_expiry_date), $a_year_later_minus_a_month, "$a_month_ago + 12 months must be $a_year_later_minus_a_month" );
415 my $number_of_logs = $schema->resultset('ActionLog')->search( { module
=> 'MEMBERS', action
=> 'RENEW', object
=> $retrieved_patron->borrowernumber } )->count;
416 is
( $number_of_logs, 1, 'With BorrowerLogs, Koha::Patron->renew_account should have logged' );
418 t
::lib
::Mocks
::mock_preference
( 'BorrowerRenewalPeriodBase', 'now' );
419 t
::lib
::Mocks
::mock_preference
( 'BorrowersLog', 0 );
420 $expiry_date = $retrieved_patron->renew_account;
421 is
( $expiry_date, $a_year_later, "today + 12 months must be $a_year_later" );
422 $retrieved_patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
423 is
( $retrieved_patron->date_renewed, output_pref
({ dt
=> $dt, dateformat
=> 'iso', dateonly
=> 1 }), "Date renewed is set when calling renew_account" );
424 $retrieved_expiry_date = $retrieved_patron->dateexpiry;
425 is
( dt_from_string
($retrieved_expiry_date), $a_year_later, "today + 12 months must be $a_year_later" );
426 $number_of_logs = $schema->resultset('ActionLog')->search( { module
=> 'MEMBERS', action
=> 'RENEW', object
=> $retrieved_patron->borrowernumber } )->count;
427 is
( $number_of_logs, 1, 'Without BorrowerLogs, Koha::Patron->renew_account should not have logged' );
429 t
::lib
::Mocks
::mock_preference
( 'BorrowerRenewalPeriodBase', 'combination' );
430 $expiry_date = $retrieved_patron_2->renew_account;
431 is
( $expiry_date, $a_year_later, "today + 12 months must be $a_year_later" );
432 $retrieved_expiry_date = Koha
::Patrons
->find( $patron_2->{borrowernumber
} )->dateexpiry;
433 is
( dt_from_string
($retrieved_expiry_date), $a_year_later, "today + 12 months must be $a_year_later" );
435 $expiry_date = $retrieved_patron_3->renew_account;
436 is
( $expiry_date, $a_year_later_plus_a_month, "$a_month_later + 12 months must be $a_year_later_plus_a_month" );
437 $retrieved_expiry_date = Koha
::Patrons
->find( $patron_3->{borrowernumber
} )->dateexpiry;
438 is
( dt_from_string
($retrieved_expiry_date), $a_year_later_plus_a_month, "$a_month_later + 12 months must be $a_year_later_plus_a_month" );
440 $retrieved_patron->delete;
441 $retrieved_patron_2->delete;
442 $retrieved_patron_3->delete;
447 subtest
"move_to_deleted" => sub {
449 my $originally_updated_on = '2016-01-01 12:12:12';
450 my $patron = $builder->build( { source
=> 'Borrower',value
=> { updated_on
=> $originally_updated_on } } );
451 my $retrieved_patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
452 is
( ref( $retrieved_patron->move_to_deleted ), 'Koha::Schema::Result::Deletedborrower', 'Koha::Patron->move_to_deleted should return the Deleted patron' )
453 ; # FIXME This should be Koha::Deleted::Patron
454 my $deleted_patron = $schema->resultset('Deletedborrower')
455 ->search( { borrowernumber
=> $patron->{borrowernumber
} }, { result_class
=> 'DBIx::Class::ResultClass::HashRefInflator' } )
457 ok
( $retrieved_patron->updated_on, 'updated_on should be set for borrowers table' );
458 ok
( $deleted_patron->{updated_on
}, 'updated_on should be set for deleted_borrowers table' );
459 isnt
( $deleted_patron->{updated_on
}, $retrieved_patron->updated_on, 'Koha::Patron->move_to_deleted should have correctly updated the updated_on column');
460 $deleted_patron->{updated_on
} = $originally_updated_on; #reset for simplicity in comparing all other fields
461 is_deeply
( $deleted_patron, $patron, 'Koha::Patron->move_to_deleted should have correctly moved the patron to the deleted table' );
462 $retrieved_patron->delete( $patron->{borrowernumber
} ); # Cleanup
465 subtest
"delete" => sub {
467 t
::lib
::Mocks
::mock_preference
( 'BorrowersLog', 1 );
468 my $patron = $builder->build( { source
=> 'Borrower' } );
469 my $retrieved_patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
470 my $hold = $builder->build(
471 { source
=> 'Reserve',
472 value
=> { borrowernumber
=> $patron->{borrowernumber
} }
475 my $list = $builder->build(
476 { source
=> 'Virtualshelve',
477 value
=> { owner
=> $patron->{borrowernumber
} }
480 my $modification = $builder->build_object({ class => 'Koha::Patron::Modifications', value
=> { borrowernumber
=> $patron->{borrowernumber
} } });
482 my $deleted = $retrieved_patron->delete;
483 is
( ref($deleted), 'Koha::Patron', 'Koha::Patron->delete should return the deleted patron object if the patron has been correctly deleted' );
485 is
( Koha
::Patrons
->find( $patron->{borrowernumber
} ), undef, 'Koha::Patron->delete should have deleted the patron' );
487 is
(Koha
::Old
::Holds
->search( { reserve_id
=> $hold->{ reserve_id
} } )->count, 1, q
|Koha
::Patron
->delete should have cancelled patron
's holds| );
489 is( Koha::Holds->search( { borrowernumber => $patron->{borrowernumber} } )->count, 0, q|Koha::Patron->delete should have cancelled patron's holds
2| );
491 is
( Koha
::Virtualshelves
->search( { owner
=> $patron->{borrowernumber
} } )->count, 0, q
|Koha
::Patron
->delete should have deleted patron
's lists| );
493 is( Koha::Patron::Modifications->search( { borrowernumber => $patron->{borrowernumber} } )->count, 0, q|Koha::Patron->delete should have deleted patron's modifications
| );
495 my $number_of_logs = $schema->resultset('ActionLog')->search( { module
=> 'MEMBERS', action
=> 'DELETE', object
=> $retrieved_patron->borrowernumber } )->count;
496 is
( $number_of_logs, 1, 'With BorrowerLogs, Koha::Patron->delete should have logged' );
499 subtest
'Koha::Patrons->delete' => sub {
502 my $patron1 = $builder->build_object({ class => 'Koha::Patrons' });
503 my $patron2 = $builder->build_object({ class => 'Koha::Patrons' });
504 my $id1 = $patron1->borrowernumber;
505 my $set = Koha
::Patrons
->search({ borrowernumber
=> { -in => [$patron1->borrowernumber, $patron2->borrowernumber]}});
506 is
( $set->count, 2, 'Two patrons found as expected' );
507 is
( $set->delete({ move
=> 1 }), 2, 'Two patrons deleted' );
508 my $deleted_patrons = Koha
::Old
::Patrons
->search({ borrowernumber
=> { -in => [$patron1->borrowernumber, $patron2->borrowernumber]}});
509 is
( $deleted_patrons->count, 2, 'Patrons moved to deletedborrowers' );
511 # See other tests in t/db_dependent/Koha/Objects.t
514 subtest
'add_enrolment_fee_if_needed' => sub {
517 my $enrolmentfees = { K
=> 5, J
=> 10, YA
=> 20 };
518 foreach( keys %{$enrolmentfees} ) {
519 ( Koha
::Patron
::Categories
->find( $_ ) // $builder->build_object({ class => 'Koha::Patron::Categories', value
=> { categorycode
=> $_ } }) )->enrolmentfee( $enrolmentfees->{$_} )->store;
521 my $enrolmentfee_K = $enrolmentfees->{K
};
522 my $enrolmentfee_J = $enrolmentfees->{J
};
523 my $enrolmentfee_YA = $enrolmentfees->{YA
};
525 my %borrower_data = (
526 firstname
=> 'my firstname',
527 surname
=> 'my surname',
529 branchcode
=> $library->{branchcode
},
532 my $borrowernumber = Koha
::Patron
->new(\
%borrower_data)->store->borrowernumber;
533 $borrower_data{borrowernumber
} = $borrowernumber;
535 my $patron = Koha
::Patrons
->find( $borrowernumber );
536 my $total = $patron->account->balance;
537 is
( int($total), int($enrolmentfee_K), "New kid pay $enrolmentfee_K" );
539 t
::lib
::Mocks
::mock_preference
( 'FeeOnChangePatronCategory', 0 );
540 $borrower_data{categorycode
} = 'J';
541 $patron->set(\
%borrower_data)->store;
542 $total = $patron->account->balance;
543 is
( int($total), int($enrolmentfee_K), "Kid growing and become a juvenile, but shouldn't pay for the upgrade " );
545 $borrower_data{categorycode
} = 'K';
546 $patron->set(\
%borrower_data)->store;
547 t
::lib
::Mocks
::mock_preference
( 'FeeOnChangePatronCategory', 1 );
549 $borrower_data{categorycode
} = 'J';
550 $patron->set(\
%borrower_data)->store;
551 $total = $patron->account->balance;
552 is
( int($total), int($enrolmentfee_K + $enrolmentfee_J), "Kid growing and become a juvenile, they should pay " . ( $enrolmentfee_K + $enrolmentfee_J ) );
554 # Check with calling directly Koha::Patron->get_enrolment_fee_if_needed
555 $patron->categorycode('YA')->store;
556 $total = $patron->account->balance;
558 int($enrolmentfee_K + $enrolmentfee_J + $enrolmentfee_YA),
559 "Juvenile growing and become an young adult, they should pay " . ( $enrolmentfee_K + $enrolmentfee_J + $enrolmentfee_YA )
565 subtest
'checkouts + pending_checkouts + get_overdues + old_checkouts' => sub {
568 my $library = $builder->build( { source
=> 'Branch' } );
569 my ($biblionumber_1) = AddBiblio
( MARC
::Record
->new, '' );
570 my $item_1 = $builder->build_sample_item(
572 library
=> $library->{branchcode
},
573 biblionumber
=> $biblionumber_1,
576 my $item_2 = $builder->build_sample_item(
578 library
=> $library->{branchcode
},
579 biblionumber
=> $biblionumber_1,
582 my ($biblionumber_2) = AddBiblio
( MARC
::Record
->new, '' );
583 my $item_3 = $builder->build_sample_item(
585 library
=> $library->{branchcode
},
586 biblionumber
=> $biblionumber_2,
589 my $patron = $builder->build(
591 source
=> 'Borrower',
592 value
=> { branchcode
=> $library->{branchcode
} }
596 $patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
597 my $checkouts = $patron->checkouts;
598 is
( $checkouts->count, 0, 'checkouts should not return any issues for that patron' );
599 is
( ref($checkouts), 'Koha::Checkouts', 'checkouts should return a Koha::Checkouts object' );
600 my $pending_checkouts = $patron->pending_checkouts;
601 is
( $pending_checkouts->count, 0, 'pending_checkouts should not return any issues for that patron' );
602 is
( ref($pending_checkouts), 'Koha::Checkouts', 'pending_checkouts should return a Koha::Checkouts object' );
603 my $old_checkouts = $patron->old_checkouts;
604 is
( $old_checkouts->count, 0, 'old_checkouts should not return any issues for that patron' );
605 is
( ref($old_checkouts), 'Koha::Old::Checkouts', 'old_checkouts should return a Koha::Old::Checkouts object' );
607 # Not sure how this is useful, but AddIssue pass this variable to different other subroutines
608 $patron = Koha
::Patrons
->find( $patron->borrowernumber )->unblessed;
610 t
::lib
::Mocks
::mock_userenv
({ branchcode
=> $library->{branchcode
} });
612 AddIssue
( $patron, $item_1->barcode, DateTime
->now->subtract( days
=> 1 ) );
613 AddIssue
( $patron, $item_2->barcode, DateTime
->now->subtract( days
=> 5 ) );
614 AddIssue
( $patron, $item_3->barcode );
616 $patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
617 $checkouts = $patron->checkouts;
618 is
( $checkouts->count, 3, 'checkouts should return 3 issues for that patron' );
619 is
( ref($checkouts), 'Koha::Checkouts', 'checkouts should return a Koha::Checkouts object' );
620 $pending_checkouts = $patron->pending_checkouts;
621 is
( $pending_checkouts->count, 3, 'pending_checkouts should return 3 issues for that patron' );
622 is
( ref($pending_checkouts), 'Koha::Checkouts', 'pending_checkouts should return a Koha::Checkouts object' );
624 my $first_checkout = $pending_checkouts->next;
625 is
( $first_checkout->unblessed_all_relateds->{biblionumber
}, $item_3->biblionumber, 'pending_checkouts should prefetch values from other tables (here biblio)' );
627 my $overdues = $patron->get_overdues;
628 is
( $overdues->count, 2, 'Patron should have 2 overdues');
629 is
( ref($overdues), 'Koha::Checkouts', 'Koha::Patron->get_overdues should return Koha::Checkouts' );
630 is
( $overdues->next->itemnumber, $item_1->itemnumber, 'The issue should be returned in the same order as they have been done, first is correct' );
631 is
( $overdues->next->itemnumber, $item_2->itemnumber, 'The issue should be returned in the same order as they have been done, second is correct' );
634 C4
::Circulation
::AddReturn
( $item_1->barcode );
635 C4
::Circulation
::AddReturn
( $item_2->barcode );
636 $old_checkouts = $patron->old_checkouts;
637 is
( $old_checkouts->count, 2, 'old_checkouts should return 2 old checkouts that patron' );
638 is
( ref($old_checkouts), 'Koha::Old::Checkouts', 'old_checkouts should return a Koha::Old::Checkouts object' );
641 Koha
::Checkouts
->search( { borrowernumber
=> $patron->borrowernumber } )->delete;
645 subtest
'get_routing_lists' => sub {
648 my $biblio = Koha
::Biblio
->new()->store();
649 my $subscription = Koha
::Subscription
->new({
650 biblionumber
=> $biblio->biblionumber,
654 my $patron = $builder->build( { source
=> 'Borrower' } );
655 $patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
657 is
( $patron->get_routing_lists->count, 0, 'Retrieves correct number of routing lists: 0' );
659 my $routinglist_count = Koha
::Subscription
::Routinglists
->count;
660 my $routinglist = Koha
::Subscription
::Routinglist
->new({
661 borrowernumber
=> $patron->borrowernumber,
663 subscriptionid
=> $subscription->subscriptionid
666 is
($patron->get_routing_lists->count, 1, "Retrieves correct number of routing lists: 1");
668 my $routinglists = $patron->get_routing_lists;
669 is
($routinglists->next->ranking, 5, "Retrieves ranking: 5");
670 is
( ref($routinglists), 'Koha::Subscription::Routinglists', 'get_routing_lists returns Koha::Subscription::Routinglists' );
672 my $subscription2 = Koha
::Subscription
->new({
673 biblionumber
=> $biblio->biblionumber,
676 my $routinglist2 = Koha
::Subscription
::Routinglist
->new({
677 borrowernumber
=> $patron->borrowernumber,
679 subscriptionid
=> $subscription2->subscriptionid
682 is
($patron->get_routing_lists->count, 2, "Retrieves correct number of routing lists: 2");
684 $patron->delete; # Clean up for later tests
688 subtest
'get_age' => sub {
691 my $patron = $builder->build( { source
=> 'Borrower' } );
692 $patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
696 today
=> '2020-02-28',
697 has_12
=> { date
=> '2007-08-27', expected_age
=> 12 },
698 almost_18
=> { date
=> '2002-03-01', expected_age
=> 17 },
699 has_18_today
=> { date
=> '2002-02-28', expected_age
=> 18 },
700 had_18_yesterday
=> { date
=> '2002-02-27', expected_age
=> 18 },
701 almost_16
=> { date
=> '2004-02-29', expected_age
=> 15 },
702 has_16_today
=> { date
=> '2004-02-28', expected_age
=> 16 },
703 had_16_yesterday
=> { date
=> '2004-02-27', expected_age
=> 16 },
704 new_born
=> { date
=> '2020-01-27', expected_age
=> 0 },
707 today
=> '2020-02-29',
708 has_12
=> { date
=> '2007-08-27', expected_age
=> 12 },
709 almost_18
=> { date
=> '2002-03-01', expected_age
=> 17 },
710 has_18_today
=> { date
=> '2002-02-28', expected_age
=> 18 },
711 had_18_yesterday
=> { date
=> '2002-02-27', expected_age
=> 18 },
712 almost_16
=> { date
=> '2004-03-01', expected_age
=> 15 },
713 has_16_today
=> { date
=> '2004-02-29', expected_age
=> 16 },
714 had_16_yesterday
=> { date
=> '2004-02-28', expected_age
=> 16 },
715 new_born
=> { date
=> '2020-01-27', expected_age
=> 0 },
718 today
=> '2020-03-01',
719 has_12
=> { date
=> '2007-08-27', expected_age
=> 12 },
720 almost_18
=> { date
=> '2002-03-02', expected_age
=> 17 },
721 has_18_today
=> { date
=> '2002-03-01', expected_age
=> 18 },
722 had_18_yesterday
=> { date
=> '2002-02-28', expected_age
=> 18 },
723 almost_16
=> { date
=> '2004-03-02', expected_age
=> 15 },
724 has_16_today
=> { date
=> '2004-03-01', expected_age
=> 16 },
725 had_16_yesterday
=> { date
=> '2004-02-29', expected_age
=> 16 },
728 today
=> '2019-01-31',
729 has_12
=> { date
=> '2006-08-27', expected_age
=> 12 },
730 almost_18
=> { date
=> '2001-02-01', expected_age
=> 17 },
731 has_18_today
=> { date
=> '2001-01-31', expected_age
=> 18 },
732 had_18_yesterday
=> { date
=> '2001-01-30', expected_age
=> 18 },
733 almost_16
=> { date
=> '2003-02-01', expected_age
=> 15 },
734 has_16_today
=> { date
=> '2003-01-31', expected_age
=> 16 },
735 had_16_yesterday
=> { date
=> '2003-01-30', expected_age
=> 16 },
739 $patron->dateofbirth( undef );
740 is
( $patron->get_age, undef, 'get_age should return undef if no dateofbirth is defined' );
742 for my $date ( @dates ) {
744 my $dt = dt_from_string
($date->{today
});
746 Time
::Fake
->offset( $dt->epoch );
748 for my $k ( keys %$date ) {
749 next if $k eq 'today';
751 my $dob = $date->{$k};
752 $patron->dateofbirth( dt_from_string
( $dob->{date
}, 'iso' ) );
755 $dob->{expected_age
},
757 "Today=%s, dob=%s, should be %d",
758 $date->{today
}, $dob->{date
}, $dob->{expected_age
}
770 subtest
'is_valid_age' => sub {
773 my $dt = dt_from_string
('2020-02-28');
775 Time
::Fake
->offset( $dt->epoch );
777 my $category = $builder->build({
778 source
=> 'Category',
780 categorycode
=> 'AGE_5_10',
781 dateofbirthrequired
=> 5,
785 $category = Koha
::Patron
::Categories
->find( $category->{categorycode
} );
787 my $patron = $builder->build({
788 source
=> 'Borrower',
790 categorycode
=> 'AGE_5_10'
793 $patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
796 $patron->dateofbirth( undef );
797 is
( $patron->is_valid_age, 1, 'Patron with no dateofbirth is always valid for any category');
801 today
=> '2020-02-28',
803 { date
=> '2007-08-27', expected_age
=> 12, valid
=> 0 },
805 { date
=> '2016-08-27', expected_age
=> 3, valid
=> 0 },
807 { date
=> '2015-02-28', expected_age
=> 7, valid
=> 1 },
809 { date
=> '2015-02-28', expected_age
=> 5, valid
=> 1 },
811 { date
=> '2015-03-01', expected_age
=> 5, valid
=> 0 },
813 { date
=> '2015-02-27', expected_age
=> 5, valid
=> 1 },
815 { date
=> '2009-02-28', expected_age
=> 11, valid
=> 0 },
817 { date
=> '2009-03-01', expected_age
=> 11, valid
=> 1 },
819 { date
=> '2009-02-27', expected_age
=> 11, valid
=> 0 },
823 for my $date ( @dates ) {
825 my $dt = dt_from_string
($date->{today
});
827 Time
::Fake
->offset( $dt->epoch );
829 for my $k ( keys %$date ) {
830 next if $k eq 'today';
832 my $dob = $date->{$k};
833 $patron->dateofbirth( dt_from_string
( $dob->{date
}, 'iso' ) );
835 $patron->is_valid_age,
838 "Today=%s, dob=%s, is %s, should be valid=%s",
839 $date->{today
}, $dob->{date
}, $dob->{expected_age
}, $dob->{valid
}
852 subtest
'account' => sub {
855 my $patron = $builder->build({source
=> 'Borrower'});
857 $patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
858 my $account = $patron->account;
859 is
( ref($account), 'Koha::Account', 'account should return a Koha::Account object' );
864 subtest
'search_upcoming_membership_expires' => sub {
867 my $expiry_days = 15;
868 t
::lib
::Mocks
::mock_preference
( 'MembershipExpiryDaysNotice', $expiry_days );
869 my $nb_of_days_before = 1;
870 my $nb_of_days_after = 2;
872 my $builder = t
::lib
::TestBuilder
->new();
874 my $library = $builder->build({ source
=> 'Branch' });
876 # before we add borrowers to this branch, add the expires we have now
877 # note that this pertains to the current mocked setting of the pref
878 # for this reason we add the new branchcode to most of the tests
879 my $nb_of_expires = Koha
::Patrons
->search_upcoming_membership_expires->count;
881 my $patron_1 = $builder->build({
882 source
=> 'Borrower',
884 branchcode
=> $library->{branchcode
},
885 dateexpiry
=> dt_from_string
->add( days
=> $expiry_days )
889 my $patron_2 = $builder->build({
890 source
=> 'Borrower',
892 branchcode
=> $library->{branchcode
},
893 dateexpiry
=> dt_from_string
->add( days
=> $expiry_days - $nb_of_days_before )
897 my $patron_3 = $builder->build({
898 source
=> 'Borrower',
900 branchcode
=> $library->{branchcode
},
901 dateexpiry
=> dt_from_string
->add( days
=> $expiry_days + $nb_of_days_after )
905 # Test without extra parameters
906 my $upcoming_mem_expires = Koha
::Patrons
->search_upcoming_membership_expires();
907 is
( $upcoming_mem_expires->count, $nb_of_expires + 1, 'Get upcoming membership expires should return one new borrower.' );
910 $upcoming_mem_expires = Koha
::Patrons
->search_upcoming_membership_expires({ 'me.branchcode' => $library->{branchcode
} });
911 is
( $upcoming_mem_expires->count, 1, 'Test with branch parameter' );
912 my $expired = $upcoming_mem_expires->next;
913 is
( $expired->surname, $patron_1->{surname
}, 'Get upcoming membership expires should return the correct patron.' );
914 is
( $expired->library->branchemail, $library->{branchemail
}, 'Get upcoming membership expires should return the correct patron.' );
915 is
( $expired->branchcode, $patron_1->{branchcode
}, 'Get upcoming membership expires should return the correct patron.' );
917 t
::lib
::Mocks
::mock_preference
( 'MembershipExpiryDaysNotice', 0 );
918 $upcoming_mem_expires = Koha
::Patrons
->search_upcoming_membership_expires({ 'me.branchcode' => $library->{branchcode
} });
919 is
( $upcoming_mem_expires->count, 0, 'Get upcoming membership expires with MembershipExpiryDaysNotice==0 should not return new records.' );
921 # Test MembershipExpiryDaysNotice == undef
922 t
::lib
::Mocks
::mock_preference
( 'MembershipExpiryDaysNotice', undef );
923 $upcoming_mem_expires = Koha
::Patrons
->search_upcoming_membership_expires({ 'me.branchcode' => $library->{branchcode
} });
924 is
( $upcoming_mem_expires->count, 0, 'Get upcoming membership expires without MembershipExpiryDaysNotice should not return new records.' );
926 # Test the before parameter
927 t
::lib
::Mocks
::mock_preference
( 'MembershipExpiryDaysNotice', 15 );
928 $upcoming_mem_expires = Koha
::Patrons
->search_upcoming_membership_expires({ 'me.branchcode' => $library->{branchcode
}, before
=> $nb_of_days_before });
929 is
( $upcoming_mem_expires->count, 2, 'Expect two results for before');
930 # Test after parameter also
931 $upcoming_mem_expires = Koha
::Patrons
->search_upcoming_membership_expires({ 'me.branchcode' => $library->{branchcode
}, before
=> $nb_of_days_before, after
=> $nb_of_days_after });
932 is
( $upcoming_mem_expires->count, 3, 'Expect three results when adding after' );
933 Koha
::Patrons
->search({ borrowernumber
=> { in => [ $patron_1->{borrowernumber
}, $patron_2->{borrowernumber
}, $patron_3->{borrowernumber
} ] } })->delete;
936 subtest
'holds and old_holds' => sub {
939 my $library = $builder->build( { source
=> 'Branch' } );
940 my ($biblionumber_1) = AddBiblio
( MARC
::Record
->new, '' );
941 my $item_1 = $builder->build_sample_item(
943 library
=> $library->{branchcode
},
944 biblionumber
=> $biblionumber_1,
947 my $item_2 = $builder->build_sample_item(
949 library
=> $library->{branchcode
},
950 biblionumber
=> $biblionumber_1,
953 my ($biblionumber_2) = AddBiblio
( MARC
::Record
->new, '' );
954 my $item_3 = $builder->build_sample_item(
956 library
=> $library->{branchcode
},
957 biblionumber
=> $biblionumber_2,
961 my $patron = $builder->build(
963 source
=> 'Borrower',
964 value
=> { branchcode
=> $library->{branchcode
} }
968 $patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
969 my $holds = $patron->holds;
970 is
( ref($holds), 'Koha::Holds',
971 'Koha::Patron->holds should return a Koha::Holds objects' );
972 is
( $holds->count, 0, 'There should not be holds placed by this patron yet' );
974 C4
::Reserves
::AddReserve
(
976 branchcode
=> $library->{branchcode
},
977 borrowernumber
=> $patron->borrowernumber,
978 biblionumber
=> $biblionumber_1
982 C4
::Reserves
::AddReserve
(
984 branchcode
=> $library->{branchcode
},
985 borrowernumber
=> $patron->borrowernumber,
986 biblionumber
=> $biblionumber_2,
987 expiration_date
=> dt_from_string
->add( days
=> 2 )
991 $holds = $patron->holds;
992 is
( $holds->count, 2, 'There should be 2 holds placed by this patron' );
994 my $old_holds = $patron->old_holds;
995 is
( ref($old_holds), 'Koha::Old::Holds',
996 'Koha::Patron->old_holds should return a Koha::Old::Holds objects' );
997 is
( $old_holds->count, 0, 'There should not be any old holds yet');
999 my $hold = $holds->next;
1002 $old_holds = $patron->old_holds;
1003 is
( $old_holds->count, 1, 'There should be 1 old (cancelled) hold');
1010 subtest
'notice_email_address' => sub {
1013 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1015 t
::lib
::Mocks
::mock_preference
( 'AutoEmailPrimaryAddress', 'OFF' );
1016 is
($patron->notice_email_address, $patron->email, "Koha::Patron->notice_email_address returns correct value when AutoEmailPrimaryAddress is off");
1018 t
::lib
::Mocks
::mock_preference
( 'AutoEmailPrimaryAddress', 'emailpro' );
1019 is
($patron->notice_email_address, $patron->emailpro, "Koha::Patron->notice_email_address returns correct value when AutoEmailPrimaryAddress is emailpro");
1024 subtest
'search_patrons_to_anonymise & anonymise_issue_history' => sub {
1027 # TODO create a subroutine in t::lib::Mocks
1028 my $branch = $builder->build({ source
=> 'Branch' });
1029 my $userenv_patron = $builder->build_object({
1030 class => 'Koha::Patrons',
1031 value
=> { branchcode
=> $branch->{branchcode
}, flags
=> 0 },
1033 t
::lib
::Mocks
::mock_userenv
({ patron
=> $userenv_patron });
1035 my $anonymous = $builder->build( { source
=> 'Borrower', }, );
1037 t
::lib
::Mocks
::mock_preference
( 'AnonymousPatron', $anonymous->{borrowernumber
} );
1039 subtest
'patron privacy is 1 (default)' => sub {
1042 t
::lib
::Mocks
::mock_preference
('IndependentBranches', 0);
1043 my $patron = $builder->build(
1044 { source
=> 'Borrower',
1045 value
=> { privacy
=> 1, }
1048 my $item_1 = $builder->build_sample_item;
1049 my $issue_1 = $builder->build(
1050 { source
=> 'Issue',
1052 borrowernumber
=> $patron->{borrowernumber
},
1053 itemnumber
=> $item_1->itemnumber,
1057 my $item_2 = $builder->build_sample_item;
1058 my $issue_2 = $builder->build(
1059 { source
=> 'Issue',
1061 borrowernumber
=> $patron->{borrowernumber
},
1062 itemnumber
=> $item_2->itemnumber,
1067 my ( $returned_1, undef, undef ) = C4
::Circulation
::AddReturn
( $item_1->barcode, undef, undef, dt_from_string
('2010-10-10') );
1068 my ( $returned_2, undef, undef ) = C4
::Circulation
::AddReturn
( $item_2->barcode, undef, undef, dt_from_string
('2011-11-11') );
1069 is
( $returned_1 && $returned_2, 1, 'The items should have been returned' );
1071 my $patrons_to_anonymise = Koha
::Patrons
->search_patrons_to_anonymise( { before
=> '2010-10-11' } )->search( { 'me.borrowernumber' => $patron->{borrowernumber
} } );
1072 is
( ref($patrons_to_anonymise), 'Koha::Patrons', 'search_patrons_to_anonymise should return Koha::Patrons' );
1074 my $rows_affected = Koha
::Patrons
->search_patrons_to_anonymise( { before
=> '2011-11-12' } )->anonymise_issue_history( { before
=> '2010-10-11' } );
1075 ok
( $rows_affected > 0, 'AnonymiseIssueHistory should affect at least 1 row' );
1077 $patrons_to_anonymise = Koha
::Patrons
->search_patrons_to_anonymise( { before
=> '2010-10-11' } );
1078 is
( $patrons_to_anonymise->count, 0, 'search_patrons_to_anonymise should return 0 after anonymisation is done' );
1080 my $dbh = C4
::Context
->dbh;
1081 my $sth = $dbh->prepare(q
|SELECT borrowernumber FROM old_issues where itemnumber
= ?
|);
1082 $sth->execute($item_1->itemnumber);
1083 my ($borrowernumber_used_to_anonymised) = $sth->fetchrow_array;
1084 is
( $borrowernumber_used_to_anonymised, $anonymous->{borrowernumber
}, 'With privacy=1, the issue should have been anonymised' );
1085 $sth->execute($item_2->itemnumber);
1086 ($borrowernumber_used_to_anonymised) = $sth->fetchrow_array;
1087 is
( $borrowernumber_used_to_anonymised, $patron->{borrowernumber
}, 'The issue should not have been anonymised, the returned date is later' );
1089 $rows_affected = Koha
::Patrons
->search_patrons_to_anonymise( { before
=> '2011-11-12' } )->anonymise_issue_history;
1090 $sth->execute($item_2->itemnumber);
1091 ($borrowernumber_used_to_anonymised) = $sth->fetchrow_array;
1092 is
( $borrowernumber_used_to_anonymised, $anonymous->{borrowernumber
}, 'The issue should have been anonymised, the returned date is before' );
1094 my $sth_reset = $dbh->prepare(q
|UPDATE old_issues SET borrowernumber
= ? WHERE itemnumber
= ?
|);
1095 $sth_reset->execute( $patron->{borrowernumber
}, $item_1->itemnumber );
1096 $sth_reset->execute( $patron->{borrowernumber
}, $item_2->itemnumber );
1097 $rows_affected = Koha
::Patrons
->search_patrons_to_anonymise->anonymise_issue_history;
1098 $sth->execute($item_1->itemnumber);
1099 ($borrowernumber_used_to_anonymised) = $sth->fetchrow_array;
1100 is
( $borrowernumber_used_to_anonymised, $anonymous->{borrowernumber
}, 'The issue 1 should have been anonymised, before parameter was not passed' );
1101 $sth->execute($item_2->itemnumber);
1102 ($borrowernumber_used_to_anonymised) = $sth->fetchrow_array;
1103 is
( $borrowernumber_used_to_anonymised, $anonymous->{borrowernumber
}, 'The issue 2 should have been anonymised, before parameter was not passed' );
1105 Koha
::Patrons
->find( $patron->{borrowernumber
})->delete;
1108 subtest
'patron privacy is 0 (forever)' => sub {
1111 t
::lib
::Mocks
::mock_preference
('IndependentBranches', 0);
1112 my $patron = $builder->build(
1113 { source
=> 'Borrower',
1114 value
=> { privacy
=> 0, }
1117 my $item = $builder->build_sample_item;
1118 my $issue = $builder->build(
1119 { source
=> 'Issue',
1121 borrowernumber
=> $patron->{borrowernumber
},
1122 itemnumber
=> $item->itemnumber,
1127 my ( $returned, undef, undef ) = C4
::Circulation
::AddReturn
( $item->barcode, undef, undef, dt_from_string
('2010-10-10') );
1128 is
( $returned, 1, 'The item should have been returned' );
1130 my $dbh = C4
::Context
->dbh;
1131 my ($borrowernumber_used_to_anonymised) = $dbh->selectrow_array(q
|
1132 SELECT borrowernumber FROM old_issues where itemnumber
= ?
1133 |, undef, $item->itemnumber);
1134 is
( $borrowernumber_used_to_anonymised, $patron->{borrowernumber
}, 'With privacy=0, the issue should not be anonymised' );
1135 Koha
::Patrons
->find( $patron->{borrowernumber
})->delete;
1138 t
::lib
::Mocks
::mock_preference
( 'AnonymousPatron', '' );
1140 subtest
'AnonymousPatron is not defined' => sub {
1143 t
::lib
::Mocks
::mock_preference
('IndependentBranches', 0);
1144 my $patron = $builder->build(
1145 { source
=> 'Borrower',
1146 value
=> { privacy
=> 1, }
1149 my $item = $builder->build_sample_item;
1150 my $issue = $builder->build(
1151 { source
=> 'Issue',
1153 borrowernumber
=> $patron->{borrowernumber
},
1154 itemnumber
=> $item->itemnumber,
1159 my ( $returned, undef, undef ) = C4
::Circulation
::AddReturn
( $item->barcode, undef, undef, dt_from_string
('2010-10-10') );
1160 is
( $returned, 1, 'The item should have been returned' );
1161 my $rows_affected = Koha
::Patrons
->search_patrons_to_anonymise( { before
=> '2010-10-11' } )->anonymise_issue_history( { before
=> '2010-10-11' } );
1162 ok
( $rows_affected > 0, 'AnonymiseIssueHistory should affect at least 1 row' );
1164 my $dbh = C4
::Context
->dbh;
1165 my ($borrowernumber_used_to_anonymised) = $dbh->selectrow_array(q
|
1166 SELECT borrowernumber FROM old_issues where itemnumber
= ?
1167 |, undef, $item->itemnumber);
1168 is
( $borrowernumber_used_to_anonymised, undef, 'With AnonymousPatron is not defined, the issue should have been anonymised anyway' );
1169 Koha
::Patrons
->find( $patron->{borrowernumber
})->delete;
1172 subtest
'Logged in librarian is not superlibrarian & IndependentBranches' => sub {
1174 t
::lib
::Mocks
::mock_preference
( 'IndependentBranches', 1 );
1175 my $patron = $builder->build(
1176 { source
=> 'Borrower',
1177 value
=> { privacy
=> 1 } # Another branchcode than the logged in librarian
1180 my $item = $builder->build_sample_item;
1181 my $issue = $builder->build(
1182 { source
=> 'Issue',
1184 borrowernumber
=> $patron->{borrowernumber
},
1185 itemnumber
=> $item->itemnumber,
1190 my ( $returned, undef, undef ) = C4
::Circulation
::AddReturn
( $item->barcode, undef, undef, dt_from_string
('2010-10-10') );
1191 is
( Koha
::Patrons
->search_patrons_to_anonymise( { before
=> '2010-10-11' } )->count, 0 );
1192 Koha
::Patrons
->find( $patron->{borrowernumber
})->delete;
1195 Koha
::Patrons
->find( $anonymous->{borrowernumber
})->delete;
1196 $userenv_patron->delete;
1198 # Reset IndependentBranches for further tests
1199 t
::lib
::Mocks
::mock_preference
('IndependentBranches', 0);
1202 subtest
'libraries_where_can_see_patrons + can_see_patron_infos + search_limited' => sub {
1210 $nb_of_patrons = Koha
::Patrons
->search->count;
1211 my $group_1 = Koha
::Library
::Group
->new( { title
=> 'TEST Group 1', ft_hide_patron_info
=> 1 } )->store;
1212 my $group_2 = Koha
::Library
::Group
->new( { title
=> 'TEST Group 2', ft_hide_patron_info
=> 1 } )->store;
1213 my $library_11 = $builder->build( { source
=> 'Branch' } );
1214 my $library_12 = $builder->build( { source
=> 'Branch' } );
1215 my $library_21 = $builder->build( { source
=> 'Branch' } );
1216 $library_11 = Koha
::Libraries
->find( $library_11->{branchcode
} );
1217 $library_12 = Koha
::Libraries
->find( $library_12->{branchcode
} );
1218 $library_21 = Koha
::Libraries
->find( $library_21->{branchcode
} );
1219 Koha
::Library
::Group
->new(
1220 { branchcode
=> $library_11->branchcode, parent_id
=> $group_1->id } )->store;
1221 Koha
::Library
::Group
->new(
1222 { branchcode
=> $library_12->branchcode, parent_id
=> $group_1->id } )->store;
1223 Koha
::Library
::Group
->new(
1224 { branchcode
=> $library_21->branchcode, parent_id
=> $group_2->id } )->store;
1226 my $sth = C4
::Context
->dbh->prepare(q
|INSERT INTO user_permissions
( borrowernumber
, module_bit
, code
) VALUES
(?
, 4, ?
)|); # 4 for borrowers
1227 # 2 patrons from library_11 (group1)
1228 # patron_11_1 see patron's infos from outside its group
1229 # Setting flags => undef to not be considered as superlibrarian
1230 my $patron_11_1 = $builder->build({ source
=> 'Borrower', value
=> { branchcode
=> $library_11->branchcode, flags
=> undef, }});
1231 $patron_11_1 = Koha
::Patrons
->find( $patron_11_1->{borrowernumber
} );
1232 $sth->execute( $patron_11_1->borrowernumber, 'edit_borrowers' );
1233 $sth->execute( $patron_11_1->borrowernumber, 'view_borrower_infos_from_any_libraries' );
1234 # patron_11_2 can only see patron's info from its group
1235 my $patron_11_2 = $builder->build({ source
=> 'Borrower', value
=> { branchcode
=> $library_11->branchcode, flags
=> undef, }});
1236 $patron_11_2 = Koha
::Patrons
->find( $patron_11_2->{borrowernumber
} );
1237 $sth->execute( $patron_11_2->borrowernumber, 'edit_borrowers' );
1238 # 1 patron from library_12 (group1)
1239 my $patron_12 = $builder->build({ source
=> 'Borrower', value
=> { branchcode
=> $library_12->branchcode, flags
=> undef, }});
1240 $patron_12 = Koha
::Patrons
->find( $patron_12->{borrowernumber
} );
1241 # 1 patron from library_21 (group2) can only see patron's info from its group
1242 my $patron_21 = $builder->build({ source
=> 'Borrower', value
=> { branchcode
=> $library_21->branchcode, flags
=> undef, }});
1243 $patron_21 = Koha
::Patrons
->find( $patron_21->{borrowernumber
} );
1244 $sth->execute( $patron_21->borrowernumber, 'edit_borrowers' );
1246 # Pfiou, we can start now!
1247 subtest
'libraries_where_can_see_patrons' => sub {
1252 t
::lib
::Mocks
::mock_userenv
({ patron
=> $patron_11_1 });
1253 @branchcodes = $patron_11_1->libraries_where_can_see_patrons;
1254 is_deeply
( \
@branchcodes, [], q
|patron_11_1 has view_borrower_infos_from_any_libraries
=> No restriction
| );
1256 t
::lib
::Mocks
::mock_userenv
({ patron
=> $patron_11_2 });
1257 @branchcodes = $patron_11_2->libraries_where_can_see_patrons;
1258 is_deeply
( \
@branchcodes, [ sort ( $library_11->branchcode, $library_12->branchcode ) ], q
|patron_11_2 has
not view_borrower_infos_from_any_libraries
=> Can only see patron
's from its group| );
1260 t::lib::Mocks::mock_userenv({ patron => $patron_21 });
1261 @branchcodes = $patron_21->libraries_where_can_see_patrons;
1262 is_deeply( \@branchcodes, [$library_21->branchcode], q|patron_21 has not view_borrower_infos_from_any_libraries => Can only see patron's from its group
| );
1264 subtest
'can_see_patron_infos' => sub {
1267 t
::lib
::Mocks
::mock_userenv
({ patron
=> $patron_11_1 });
1268 is
( $patron_11_1->can_see_patron_infos( $patron_11_2 ), 1, q
|patron_11_1 can see patron_11_2
, from its library
| );
1269 is
( $patron_11_1->can_see_patron_infos( $patron_12 ), 1, q
|patron_11_1 can see patron_12
, from its group
| );
1270 is
( $patron_11_1->can_see_patron_infos( $patron_21 ), 1, q
|patron_11_1 can see patron_11_2
, from another group
| );
1272 t
::lib
::Mocks
::mock_userenv
({ patron
=> $patron_11_2 });
1273 is
( $patron_11_2->can_see_patron_infos( $patron_11_1 ), 1, q
|patron_11_2 can see patron_11_1
, from its library
| );
1274 is
( $patron_11_2->can_see_patron_infos( $patron_12 ), 1, q
|patron_11_2 can see patron_12
, from its group
| );
1275 is
( $patron_11_2->can_see_patron_infos( $patron_21 ), 0, q
|patron_11_2 can NOT see patron_21
, from another group
| );
1277 subtest
'search_limited' => sub {
1280 t
::lib
::Mocks
::mock_userenv
({ patron
=> $patron_11_1 });
1281 my $total_number_of_patrons = $nb_of_patrons + 4; #we added four in these tests
1282 is
( Koha
::Patrons
->search->count, $total_number_of_patrons, 'Non-limited search should return all patrons' );
1283 is
( Koha
::Patrons
->search_limited->count, $total_number_of_patrons, 'patron_11_1 is allowed to see all patrons' );
1285 t
::lib
::Mocks
::mock_userenv
({ patron
=> $patron_11_2 });
1286 is
( Koha
::Patrons
->search->count, $total_number_of_patrons, 'Non-limited search should return all patrons');
1287 is
( Koha
::Patrons
->search_limited->count, 3, 'patron_12_1 is not allowed to see patrons from other groups, only patron_11_1, patron_11_2 and patron_12' );
1289 t
::lib
::Mocks
::mock_userenv
({ patron
=> $patron_21 });
1290 is
( Koha
::Patrons
->search->count, $total_number_of_patrons, 'Non-limited search should return all patrons');
1291 is
( Koha
::Patrons
->search_limited->count, 1, 'patron_21 is not allowed to see patrons from other groups, only himself' );
1293 $patron_11_1->delete;
1294 $patron_11_2->delete;
1299 subtest
'account_locked' => sub {
1301 my $patron = $builder->build({ source
=> 'Borrower', value
=> { login_attempts
=> 0 } });
1302 $patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
1303 for my $value ( undef, '', 0 ) {
1304 t
::lib
::Mocks
::mock_preference
('FailedloginAttempts', $value);
1305 $patron->login_attempts(0)->store;
1306 is
( $patron->account_locked, 0, 'Feature is disabled, patron account should not be considered locked' );
1307 $patron->login_attempts(1)->store;
1308 is
( $patron->account_locked, 0, 'Feature is disabled, patron account should not be considered locked' );
1309 $patron->login_attempts(-1)->store;
1310 is
( $patron->account_locked, 1, 'Feature is disabled but administrative lockout has been triggered' );
1313 t
::lib
::Mocks
::mock_preference
('FailedloginAttempts', 3);
1314 $patron->login_attempts(2)->store;
1315 is
( $patron->account_locked, 0, 'Patron has 2 failed attempts, account should not be considered locked yet' );
1316 $patron->login_attempts(3)->store;
1317 is
( $patron->account_locked, 1, 'Patron has 3 failed attempts, account should be considered locked yet' );
1318 $patron->login_attempts(4)->store;
1319 is
( $patron->account_locked, 1, 'Patron could not have 4 failed attempts, but account should still be considered locked' );
1320 $patron->login_attempts(-1)->store;
1321 is
( $patron->account_locked, 1, 'Administrative lockout triggered' );
1326 subtest
'is_child | is_adult' => sub {
1328 my $category = $builder->build_object(
1330 class => 'Koha::Patron::Categories',
1331 value
=> { category_type
=> 'A' }
1334 my $patron_adult = $builder->build_object(
1336 class => 'Koha::Patrons',
1337 value
=> { categorycode
=> $category->categorycode }
1340 $category = $builder->build_object(
1342 class => 'Koha::Patron::Categories',
1343 value
=> { category_type
=> 'I' }
1346 my $patron_adult_i = $builder->build_object(
1348 class => 'Koha::Patrons',
1349 value
=> { categorycode
=> $category->categorycode }
1352 $category = $builder->build_object(
1354 class => 'Koha::Patron::Categories',
1355 value
=> { category_type
=> 'C' }
1358 my $patron_child = $builder->build_object(
1360 class => 'Koha::Patrons',
1361 value
=> { categorycode
=> $category->categorycode }
1364 $category = $builder->build_object(
1366 class => 'Koha::Patron::Categories',
1367 value
=> { category_type
=> 'O' }
1370 my $patron_other = $builder->build_object(
1372 class => 'Koha::Patrons',
1373 value
=> { categorycode
=> $category->categorycode }
1376 is
( $patron_adult->is_adult, 1, 'Patron from category A should be considered adult' );
1377 is
( $patron_adult_i->is_adult, 1, 'Patron from category I should be considered adult' );
1378 is
( $patron_child->is_adult, 0, 'Patron from category C should not be considered adult' );
1379 is
( $patron_other->is_adult, 0, 'Patron from category O should not be considered adult' );
1381 is
( $patron_adult->is_child, 0, 'Patron from category A should be considered child' );
1382 is
( $patron_adult_i->is_child, 0, 'Patron from category I should be considered child' );
1383 is
( $patron_child->is_child, 1, 'Patron from category C should not be considered child' );
1384 is
( $patron_other->is_child, 0, 'Patron from category O should not be considered child' );
1387 $patron_adult->delete;
1388 $patron_adult_i->delete;
1389 $patron_child->delete;
1390 $patron_other->delete;
1393 subtest
'get_overdues' => sub {
1396 my $library = $builder->build( { source
=> 'Branch' } );
1397 my ($biblionumber_1) = AddBiblio
( MARC
::Record
->new, '' );
1398 my $item_1 = $builder->build_sample_item(
1400 library
=> $library->{branchcode
},
1401 biblionumber
=> $biblionumber_1,
1404 my $item_2 = $builder->build_sample_item(
1406 library
=> $library->{branchcode
},
1407 biblionumber
=> $biblionumber_1,
1410 my ($biblionumber_2) = AddBiblio
( MARC
::Record
->new, '' );
1411 my $item_3 = $builder->build_sample_item(
1413 library
=> $library->{branchcode
},
1414 biblionumber
=> $biblionumber_2,
1418 my $patron = $builder->build(
1420 source
=> 'Borrower',
1421 value
=> { branchcode
=> $library->{branchcode
} }
1425 t
::lib
::Mocks
::mock_preference
({ branchcode
=> $library->{branchcode
} });
1427 AddIssue
( $patron, $item_1->barcode, DateTime
->now->subtract( days
=> 1 ) );
1428 AddIssue
( $patron, $item_2->barcode, DateTime
->now->subtract( days
=> 5 ) );
1429 AddIssue
( $patron, $item_3->barcode );
1431 $patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
1432 my $overdues = $patron->get_overdues;
1433 is
( $overdues->count, 2, 'Patron should have 2 overdues');
1434 is
( $overdues->next->itemnumber, $item_1->itemnumber, 'The issue should be returned in the same order as they have been done, first is correct' );
1435 is
( $overdues->next->itemnumber, $item_2->itemnumber, 'The issue should be returned in the same order as they have been done, second is correct' );
1437 my $o = $overdues->reset->next;
1438 my $unblessed_overdue = $o->unblessed_all_relateds;
1439 is
( exists( $unblessed_overdue->{issuedate
} ), 1, 'Fields from the issues table should be filled' );
1440 is
( exists( $unblessed_overdue->{itemcallnumber
} ), 1, 'Fields from the items table should be filled' );
1441 is
( exists( $unblessed_overdue->{title
} ), 1, 'Fields from the biblio table should be filled' );
1442 is
( exists( $unblessed_overdue->{itemtype
} ), 1, 'Fields from the biblioitems table should be filled' );
1445 $patron->checkouts->delete;
1449 subtest
'userid_is_valid' => sub {
1452 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
1453 my $patron_category = $builder->build_object(
1455 class => 'Koha::Patron::Categories',
1456 value
=> { category_type
=> 'P', enrolmentfee
=> 0 }
1460 cardnumber
=> "123456789",
1461 firstname
=> "Tomasito",
1463 categorycode
=> $patron_category->categorycode,
1464 branchcode
=> $library->branchcode,
1467 my $expected_userid_patron_1 = 'tomasito.none';
1468 my $borrowernumber = Koha
::Patron
->new(\
%data)->store->borrowernumber;
1469 my $patron_1 = Koha
::Patrons
->find($borrowernumber);
1470 is
( $patron_1->has_valid_userid, 1, "Should be valid when compared against them self" );
1471 is
( $patron_1->userid, $expected_userid_patron_1, 'The userid generated should be the one we expect' );
1473 $patron_1->userid( 'tomasito.non' );
1474 is
( $patron_1->has_valid_userid, # FIXME Joubu: What is the difference with the next test?
1475 1, 'recently created userid -> unique (borrowernumber passed)' );
1477 $patron_1->userid( 'tomasitoxxx' );
1478 is
( $patron_1->has_valid_userid,
1479 1, 'non-existent userid -> unique (borrowernumber passed)' );
1480 $patron_1->discard_changes; # We compare with the original userid later
1482 my $patron_not_in_storage = Koha
::Patron
->new( { userid
=> '' } );
1483 is
( $patron_not_in_storage->has_valid_userid,
1484 0, 'userid exists for another patron, patron is not in storage yet' );
1486 $patron_not_in_storage = Koha
::Patron
->new( { userid
=> 'tomasitoxxx' } );
1487 is
( $patron_not_in_storage->has_valid_userid,
1488 1, 'non-existent userid, patron is not in storage yet' );
1490 # Regression tests for BZ12226
1491 my $db_patron = Koha
::Patron
->new( { userid
=> C4
::Context
->config('user') } );
1492 is
( $db_patron->has_valid_userid,
1493 0, 'Koha::Patron->has_valid_userid should return 0 for the DB user (Bug 12226)' );
1495 # Add a new borrower with the same userid but different cardnumber
1496 $data{cardnumber
} = "987654321";
1497 my $new_borrowernumber = Koha
::Patron
->new(\
%data)->store->borrowernumber;
1498 my $patron_2 = Koha
::Patrons
->find($new_borrowernumber);
1499 $patron_2->userid($patron_1->userid);
1500 is
( $patron_2->has_valid_userid,
1501 0, 'The userid is already in used, it cannot be used for another patron' );
1503 my $new_userid = 'a_user_id';
1504 $data{cardnumber
} = "234567890";
1505 $data{userid
} = 'a_user_id';
1506 $borrowernumber = Koha
::Patron
->new(\
%data)->store->borrowernumber;
1507 my $patron_3 = Koha
::Patrons
->find($borrowernumber);
1508 is
( $patron_3->userid, $new_userid,
1509 'Koha::Patron->store should insert the given userid' );
1517 subtest
'generate_userid' => sub {
1520 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
1521 my $patron_category = $builder->build_object(
1523 class => 'Koha::Patron::Categories',
1524 value
=> { category_type
=> 'P', enrolmentfee
=> 0 }
1528 cardnumber
=> "123456789",
1529 firstname
=> "Tômà sÃtó",
1530 surname
=> "Ñoné",
1531 categorycode
=> $patron_category->categorycode,
1532 branchcode
=> $library->branchcode,
1535 my $expected_userid_patron_1 = 'tomasito.none';
1536 my $new_patron = Koha
::Patron
->new({ firstname
=> $data{firstname
}, surname
=> $data{surname
} } );
1537 $new_patron->generate_userid;
1538 my $userid = $new_patron->userid;
1539 is
( $userid, $expected_userid_patron_1, 'generate_userid should generate the userid we expect' );
1540 my $borrowernumber = Koha
::Patron
->new(\
%data)->store->borrowernumber;
1541 my $patron_1 = Koha
::Patrons
->find($borrowernumber);
1542 is
( $patron_1->userid, $expected_userid_patron_1, 'The userid generated should be the one we expect' );
1544 $new_patron->generate_userid;
1545 $userid = $new_patron->userid;
1546 is
( $userid, $expected_userid_patron_1 . '1', 'generate_userid should generate the userid we expect' );
1547 $data{cardnumber
} = '987654321';
1548 my $new_borrowernumber = Koha
::Patron
->new(\
%data)->store->borrowernumber;
1549 my $patron_2 = Koha
::Patrons
->find($new_borrowernumber);
1550 isnt
( $patron_2->userid, 'tomasito',
1551 "Patron with duplicate userid has new userid generated" );
1552 is
( $patron_2->userid, $expected_userid_patron_1 . '1', # TODO we could make that configurable
1553 "Patron with duplicate userid has new userid generated (1 is appened" );
1555 $new_patron->generate_userid;
1556 $userid = $new_patron->userid;
1557 is
( $userid, $expected_userid_patron_1 . '2', 'generate_userid should generate the userid we expect' );
1559 $patron_1 = Koha
::Patrons
->find($borrowernumber);
1560 $patron_1->userid(undef);
1561 $patron_1->generate_userid;
1562 $userid = $patron_1->userid;
1563 is
( $userid, $expected_userid_patron_1, 'generate_userid should generate the userid we expect' );
1570 $nb_of_patrons = Koha
::Patrons
->search->count;
1571 $retrieved_patron_1->delete;
1572 is
( Koha
::Patrons
->search->count, $nb_of_patrons - 1, 'Delete should have deleted the patron' );
1574 subtest
'BorrowersLog tests' => sub {
1577 t
::lib
::Mocks
::mock_preference
( 'BorrowersLog', 1 );
1578 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1580 my $cardnumber = $patron->cardnumber;
1581 $patron->set( { cardnumber
=> 'TESTCARDNUMBER' });
1584 my @logs = $schema->resultset('ActionLog')->search( { module
=> 'MEMBERS', action
=> 'MODIFY', object
=> $patron->borrowernumber } );
1585 my $log_info = from_json
( $logs[0]->info );
1586 is
( $log_info->{cardnumber
}->{after
}, 'TESTCARDNUMBER', 'Got correct new cardnumber' );
1587 is
( $log_info->{cardnumber
}->{before
}, $cardnumber, 'Got correct old cardnumber' );
1588 is
( scalar @logs, 1, 'With BorrowerLogs, one detailed MODIFY action should be logged for the modification.' );
1590 t
::lib
::Mocks
::mock_preference
( 'TrackLastPatronActivity', 1 );
1591 $patron->track_login();
1592 @logs = $schema->resultset('ActionLog')->search( { module
=> 'MEMBERS', action
=> 'MODIFY', object
=> $patron->borrowernumber } );
1593 is
( scalar @logs, 1, 'With BorrowerLogs and TrackLastPatronActivity we should not spam the logs');
1596 $schema->storage->txn_rollback;
1598 subtest
'Test Koha::Patrons::merge' => sub {
1601 my $schema = Koha
::Database
->new()->schema();
1603 my $resultsets = $Koha::Patron
::RESULTSET_PATRON_ID_MAPPING
;
1605 $schema->storage->txn_begin;
1607 my $keeper = $builder->build_object({ class => 'Koha::Patrons' });
1608 my $loser_1 = $builder->build({ source
=> 'Borrower' })->{borrowernumber
};
1609 my $loser_2 = $builder->build({ source
=> 'Borrower' })->{borrowernumber
};
1611 while (my ($r, $field) = each(%$resultsets)) {
1612 $builder->build({ source
=> $r, value
=> { $field => $keeper->id } });
1613 $builder->build({ source
=> $r, value
=> { $field => $loser_1 } });
1614 $builder->build({ source
=> $r, value
=> { $field => $loser_2 } });
1617 $schema->resultset($r)->search( { $field => $keeper->id } );
1618 is
( $keeper_rs->count(), 1, "Found 1 $r rows for keeper" );
1621 $schema->resultset($r)->search( { $field => $loser_1 } );
1622 is
( $loser_1_rs->count(), 1, "Found 1 $r rows for loser_1" );
1625 $schema->resultset($r)->search( { $field => $loser_2 } );
1626 is
( $loser_2_rs->count(), 1, "Found 1 $r rows for loser_2" );
1629 my $results = $keeper->merge_with([ $loser_1, $loser_2 ]);
1631 while (my ($r, $field) = each(%$resultsets)) {
1633 $schema->resultset($r)->search( {$field => $keeper->id } );
1634 is
( $keeper_rs->count(), 3, "Found 2 $r rows for keeper" );
1637 is
( Koha
::Patrons
->find($loser_1), undef, 'Loser 1 has been deleted' );
1638 is
( Koha
::Patrons
->find($loser_2), undef, 'Loser 2 has been deleted' );
1640 $schema->storage->txn_rollback;
1643 subtest
'->store' => sub {
1645 my $schema = Koha
::Database
->new->schema;
1646 $schema->storage->txn_begin;
1648 my $print_error = $schema->storage->dbh->{PrintError
};
1649 $schema->storage->dbh->{PrintError
} = 0; ; # FIXME This does not longer work - because of the transaction in Koha::Patron->store?
1651 my $patron_1 = $builder->build_object({class=> 'Koha::Patrons'});
1652 my $patron_2 = $builder->build_object({class=> 'Koha::Patrons'});
1656 open STDERR
, '>', '/dev/null';
1657 throws_ok
{ $patron_2->userid( $patron_1->userid )->store; }
1658 'Koha::Exceptions::Object::DuplicateID',
1659 'Koha::Patron->store raises an exception on duplicate ID';
1664 t
::lib
::Mocks
::mock_preference
( 'RequireStrongPassword', 0 );
1665 my $password = 'password';
1666 $patron_1->set_password({ password
=> $password });
1667 like
( $patron_1->password, qr
|^\
$2|, 'Password should be hashed using bcrypt (start with $2)' );
1668 my $digest = $patron_1->password;
1669 $patron_1->surname('xxx')->store;
1670 is
( $patron_1->password, $digest, 'Password should not have changed on ->store');
1672 # Test uppercasesurnames
1673 t
::lib
::Mocks
::mock_preference
( 'uppercasesurnames', 1 );
1674 my $surname = lc $patron_1->surname;
1675 $patron_1->surname($surname)->store;
1676 isnt
( $patron_1->surname, $surname,
1677 'Surname converts to uppercase on store.');
1678 t
::lib
::Mocks
::mock_preference
( 'uppercasesurnames', 0 );
1679 $patron_1->surname($surname)->store;
1680 is
( $patron_1->surname, $surname,
1681 'Surname remains unchanged on store.');
1684 $patron_1->relationship("")->store;
1685 is
( $patron_1->relationship, undef, );
1687 $schema->storage->dbh->{PrintError
} = $print_error;
1688 $schema->storage->txn_rollback;
1690 subtest
'skip updated_on for BorrowersLog' => sub {
1692 $schema->storage->txn_begin;
1693 t
::lib
::Mocks
::mock_preference
('BorrowersLog', 1);
1694 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1695 $patron->updated_on(dt_from_string
($patron->updated_on)->add( seconds
=> 1 ))->store;
1696 my $logs = Koha
::ActionLogs
->search({ module
=>'MEMBERS', action
=> 'MODIFY', object
=> $patron->borrowernumber });
1697 is
($logs->count, 0, '->store should not have generated a log for updated_on') or diag
'Log generated:'.Dumper
($logs->unblessed);
1698 $schema->storage->txn_rollback;
1702 subtest
'->set_password' => sub {
1706 $schema->storage->txn_begin;
1708 my $patron = $builder->build_object( { class => 'Koha::Patrons', value
=> { login_attempts
=> 3 } } );
1710 # Disable logging password changes for this tests
1711 t
::lib
::Mocks
::mock_preference
( 'BorrowersLog', 0 );
1713 # Password-length tests
1714 t
::lib
::Mocks
::mock_preference
( 'minPasswordLength', undef );
1715 throws_ok
{ $patron->set_password({ password
=> 'ab' }); }
1716 'Koha::Exceptions::Password::TooShort',
1717 'minPasswordLength is undef, fall back to 3, fail test';
1719 'Password length (2) is shorter than required (3)',
1720 'Exception parameters passed correctly'
1723 t
::lib
::Mocks
::mock_preference
( 'minPasswordLength', 2 );
1724 throws_ok
{ $patron->set_password({ password
=> 'ab' }); }
1725 'Koha::Exceptions::Password::TooShort',
1726 'minPasswordLength is 2, fall back to 3, fail test';
1728 t
::lib
::Mocks
::mock_preference
( 'minPasswordLength', 5 );
1729 throws_ok
{ $patron->set_password({ password
=> 'abcb' }); }
1730 'Koha::Exceptions::Password::TooShort',
1731 'minPasswordLength is 5, fail test';
1733 # Trailing spaces tests
1734 throws_ok
{ $patron->set_password({ password
=> 'abcD12d ' }); }
1735 'Koha::Exceptions::Password::WhitespaceCharacters',
1736 'Password contains trailing spaces, exception is thrown';
1738 # Require strong password tests
1739 t
::lib
::Mocks
::mock_preference
( 'RequireStrongPassword', 1 );
1740 throws_ok
{ $patron->set_password({ password
=> 'abcd a' }); }
1741 'Koha::Exceptions::Password::TooWeak',
1742 'Password is too weak, exception is thrown';
1744 # Refresh patron from DB, just to make sure
1745 $patron->discard_changes;
1746 is
( $patron->login_attempts, 3, 'Previous tests kept login attemps count' );
1748 $patron->set_password({ password
=> 'abcD12 34' });
1749 $patron->discard_changes;
1751 is
( $patron->login_attempts, 0, 'Changing the password resets the login attempts count' );
1753 lives_ok
{ $patron->set_password({ password
=> 'abcd a', skip_validation
=> 1 }) }
1754 'Password is weak, but skip_validation was passed, so no exception thrown';
1757 t
::lib
::Mocks
::mock_preference
( 'RequireStrongPassword', 0 );
1758 $patron->login_attempts(3)->store;
1759 my $old_digest = $patron->password;
1760 $patron->set_password({ password
=> 'abcd a' });
1761 $patron->discard_changes;
1763 isnt
( $patron->password, $old_digest, 'Password has been updated' );
1764 ok
( checkpw_hash
('abcd a', $patron->password), 'Password hash is correct' );
1765 is
( $patron->login_attempts, 0, 'Login attemps have been reset' );
1767 my $number_of_logs = $schema->resultset('ActionLog')->search( { module
=> 'MEMBERS', action
=> 'CHANGE PASS', object
=> $patron->borrowernumber } )->count;
1768 is
( $number_of_logs, 0, 'Without BorrowerLogs, Koha::Patron->set_password doesn\'t log password changes' );
1770 # Enable logging password changes
1771 t
::lib
::Mocks
::mock_preference
( 'BorrowersLog', 1 );
1772 $patron->set_password({ password
=> 'abcd b' });
1774 $number_of_logs = $schema->resultset('ActionLog')->search( { module
=> 'MEMBERS', action
=> 'CHANGE PASS', object
=> $patron->borrowernumber } )->count;
1775 is
( $number_of_logs, 1, 'With BorrowerLogs, Koha::Patron->set_password does log password changes' );
1777 $schema->storage->txn_rollback;
1780 $schema->storage->txn_begin;
1781 subtest
'search_unsubscribed' => sub {
1784 t
::lib
::Mocks
::mock_preference
( 'FailedLoginAttempts', 3 );
1785 t
::lib
::Mocks
::mock_preference
( 'UnsubscribeReflectionDelay', '' );
1786 is
( Koha
::Patrons
->search_unsubscribed->count, 0, 'Empty delay should return empty set' );
1788 my $patron1 = $builder->build_object({ class => 'Koha::Patrons' });
1789 my $patron2 = $builder->build_object({ class => 'Koha::Patrons' });
1791 t
::lib
::Mocks
::mock_preference
( 'UnsubscribeReflectionDelay', 0 );
1792 Koha
::Patron
::Consents
->delete; # for correct counts
1793 Koha
::Patron
::Consent
->new({ borrowernumber
=> $patron1->borrowernumber, type
=> 'GDPR_PROCESSING', refused_on
=> dt_from_string
})->store;
1794 is
( Koha
::Patrons
->search_unsubscribed->count, 1, 'Find patron1' );
1796 # Add another refusal but shift the period
1797 t
::lib
::Mocks
::mock_preference
( 'UnsubscribeReflectionDelay', 2 );
1798 Koha
::Patron
::Consent
->new({ borrowernumber
=> $patron2->borrowernumber, type
=> 'GDPR_PROCESSING', refused_on
=> dt_from_string
->subtract(days
=>2) })->store;
1799 is
( Koha
::Patrons
->search_unsubscribed->count, 1, 'Find patron2 only' );
1801 # Try another (special) attempts setting
1802 t
::lib
::Mocks
::mock_preference
( 'FailedLoginAttempts', 0 );
1803 # Lockout is now disabled
1804 # Patron2 still matches: refused earlier, not locked
1805 is
( Koha
::Patrons
->search_unsubscribed->count, 1, 'Lockout disabled' );
1808 subtest
'search_anonymize_candidates' => sub {
1810 my $patron1 = $builder->build_object({ class => 'Koha::Patrons' });
1811 my $patron2 = $builder->build_object({ class => 'Koha::Patrons' });
1812 $patron1->anonymized(0);
1813 $patron1->dateexpiry( dt_from_string
->add(days
=> 1) )->store;
1814 $patron2->anonymized(0);
1815 $patron2->dateexpiry( dt_from_string
->add(days
=> 1) )->store;
1817 t
::lib
::Mocks
::mock_preference
( 'PatronAnonymizeDelay', q{} );
1818 is
( Koha
::Patrons
->search_anonymize_candidates->count, 0, 'Empty set' );
1820 t
::lib
::Mocks
::mock_preference
( 'PatronAnonymizeDelay', 0 );
1821 my $cnt = Koha
::Patrons
->search_anonymize_candidates->count;
1822 $patron1->dateexpiry( dt_from_string
->subtract(days
=> 1) )->store;
1823 $patron2->dateexpiry( dt_from_string
->subtract(days
=> 3) )->store;
1824 is
( Koha
::Patrons
->search_anonymize_candidates->count, $cnt+2, 'Delay 0' );
1826 t
::lib
::Mocks
::mock_preference
( 'PatronAnonymizeDelay', 2 );
1827 $patron1->dateexpiry( dt_from_string
->add(days
=> 1) )->store;
1828 $patron2->dateexpiry( dt_from_string
->add(days
=> 1) )->store;
1829 $cnt = Koha
::Patrons
->search_anonymize_candidates->count;
1830 $patron1->dateexpiry( dt_from_string
->subtract(days
=> 1) )->store;
1831 $patron2->dateexpiry( dt_from_string
->subtract(days
=> 3) )->store;
1832 is
( Koha
::Patrons
->search_anonymize_candidates->count, $cnt+1, 'Delay 2' );
1834 t
::lib
::Mocks
::mock_preference
( 'PatronAnonymizeDelay', 4 );
1835 $patron1->dateexpiry( dt_from_string
->add(days
=> 1) )->store;
1836 $patron2->dateexpiry( dt_from_string
->add(days
=> 1) )->store;
1837 $cnt = Koha
::Patrons
->search_anonymize_candidates->count;
1838 $patron1->dateexpiry( dt_from_string
->subtract(days
=> 1) )->store;
1839 $patron2->dateexpiry( dt_from_string
->subtract(days
=> 3) )->store;
1840 is
( Koha
::Patrons
->search_anonymize_candidates->count, $cnt, 'Delay 4' );
1842 t
::lib
::Mocks
::mock_preference
( 'FailedLoginAttempts', 3 );
1843 $patron1->dateexpiry( dt_from_string
->subtract(days
=> 5) )->store;
1844 $patron1->login_attempts(0)->store;
1845 $patron2->dateexpiry( dt_from_string
->subtract(days
=> 5) )->store;
1846 $patron2->login_attempts(0)->store;
1847 $cnt = Koha
::Patrons
->search_anonymize_candidates({locked
=> 1})->count;
1848 $patron1->login_attempts(3)->store;
1849 is
( Koha
::Patrons
->search_anonymize_candidates({locked
=> 1})->count,
1850 $cnt+1, 'Locked flag' );
1852 t
::lib
::Mocks
::mock_preference
( 'FailedLoginAttempts', q{} );
1853 # Patron 1 still on 3 == locked
1854 is
( Koha
::Patrons
->search_anonymize_candidates({locked
=> 1})->count,
1855 $cnt+1, 'Still expect same number for FailedLoginAttempts empty' );
1856 $patron1->login_attempts(0)->store;
1858 is
( Koha
::Patrons
->search_anonymize_candidates({locked
=> 1})->count,
1859 $cnt, 'Patron 1 unlocked' );
1862 subtest
'search_anonymized' => sub {
1864 my $patron1 = $builder->build_object( { class => 'Koha::Patrons' } );
1866 t
::lib
::Mocks
::mock_preference
( 'PatronRemovalDelay', q{} );
1867 is
( Koha
::Patrons
->search_anonymized->count, 0, 'Empty set' );
1869 t
::lib
::Mocks
::mock_preference
( 'PatronRemovalDelay', 1 );
1870 $patron1->dateexpiry( dt_from_string
);
1871 $patron1->anonymized(0)->store;
1872 my $cnt = Koha
::Patrons
->search_anonymized->count;
1873 $patron1->anonymized(1)->store;
1874 is
( Koha
::Patrons
->search_anonymized->count, $cnt, 'Number unchanged' );
1875 $patron1->dateexpiry( dt_from_string
->subtract(days
=> 1) )->store;
1876 is
( Koha
::Patrons
->search_anonymized->count, $cnt+1, 'Found patron1' );
1879 subtest
'lock' => sub {
1882 my $patron1 = $builder->build_object( { class => 'Koha::Patrons' } );
1883 my $patron2 = $builder->build_object( { class => 'Koha::Patrons' } );
1884 my $hold = $builder->build_object({
1885 class => 'Koha::Holds',
1886 value
=> { borrowernumber
=> $patron1->borrowernumber },
1889 t
::lib
::Mocks
::mock_preference
( 'FailedLoginAttempts', 3 );
1890 my $expiry = dt_from_string
->add(days
=> 1);
1891 $patron1->dateexpiry( $expiry );
1893 is
( $patron1->login_attempts, Koha
::Patron
::ADMINISTRATIVE_LOCKOUT
, 'Check login_attempts' );
1894 is
( $patron1->dateexpiry, $expiry, 'Not expired yet' );
1895 is
( $patron1->holds->count, 1, 'No holds removed' );
1897 $patron1->lock({ expire
=> 1, remove
=> 1});
1898 isnt
( $patron1->dateexpiry, $expiry, 'Expiry date adjusted' );
1899 is
( $patron1->holds->count, 0, 'Holds removed' );
1901 # Disable lockout feature
1902 t
::lib
::Mocks
::mock_preference
( 'FailedLoginAttempts', q{} );
1903 $patron1->login_attempts(0);
1904 $patron1->dateexpiry( $expiry );
1907 is
( $patron1->login_attempts, Koha
::Patron
::ADMINISTRATIVE_LOCKOUT
, 'Check login_attempts' );
1909 # Trivial wrapper test (Koha::Patrons->lock)
1910 $patron1->login_attempts(0)->store;
1911 Koha
::Patrons
->search({ borrowernumber
=> [ $patron1->borrowernumber, $patron2->borrowernumber ] })->lock;
1912 $patron1->discard_changes; # refresh
1913 $patron2->discard_changes;
1914 is
( $patron1->login_attempts, Koha
::Patron
::ADMINISTRATIVE_LOCKOUT
, 'Check login_attempts patron 1' );
1915 is
( $patron2->login_attempts, Koha
::Patron
::ADMINISTRATIVE_LOCKOUT
, 'Check login_attempts patron 2' );
1918 subtest
'anonymize' => sub {
1921 my $patron1 = $builder->build_object( { class => 'Koha::Patrons' } );
1922 my $patron2 = $builder->build_object( { class => 'Koha::Patrons' } );
1924 # First try patron with issues
1925 my $issue = $builder->build_object({ class => 'Koha::Checkouts', value
=> { borrowernumber
=> $patron2->borrowernumber } });
1926 warning_like
{ $patron2->anonymize } qr/still has issues/, 'Skip patron with issues';
1929 t
::lib
::Mocks
::mock_preference
( 'BorrowerMandatoryField', 'surname|email|cardnumber' );
1930 my $surname = $patron1->surname; # expect change, no clear
1931 my $branchcode = $patron1->branchcode; # expect skip
1932 $patron1->anonymize;
1933 is
($patron1->anonymized, 1, 'Check flag' );
1935 is
( $patron1->dateofbirth, undef, 'Birth date cleared' );
1936 is
( $patron1->firstname, undef, 'First name cleared' );
1937 isnt
( $patron1->surname, $surname, 'Surname changed' );
1938 ok
( $patron1->surname =~ /^\w{10}$/, 'Mandatory surname randomized' );
1939 is
( $patron1->branchcode, $branchcode, 'Branch code skipped' );
1940 is
( $patron1->email, undef, 'Email was mandatory, must be cleared' );
1942 # Test wrapper in Koha::Patrons
1943 $patron1->surname($surname)->store; # restore
1944 my $rs = Koha
::Patrons
->search({ borrowernumber
=> [ $patron1->borrowernumber, $patron2->borrowernumber ] })->anonymize;
1945 $patron1->discard_changes; # refresh
1946 isnt
( $patron1->surname, $surname, 'Surname patron1 changed again' );
1947 $patron2->discard_changes; # refresh
1948 is
( $patron2->firstname, undef, 'First name patron2 cleared' );
1950 $schema->storage->txn_rollback;
1952 subtest
'extended_attributes' => sub {
1954 my $schema = Koha
::Database
->new->schema;
1955 $schema->storage->txn_begin;
1957 my $patron_1 = $builder->build_object({class=> 'Koha::Patrons'});
1958 my $patron_2 = $builder->build_object({class=> 'Koha::Patrons'});
1960 t
::lib
::Mocks
::mock_userenv
({ patron
=> $patron_1 });
1962 my $attribute_type1 = Koha
::Patron
::Attribute
::Type
->new(
1965 description
=> 'my description1',
1969 my $attribute_type2 = Koha
::Patron
::Attribute
::Type
->new(
1972 description
=> 'my description2',
1974 staff_searchable
=> 1
1978 my $attribute_type3 = $builder->build_object({ class => 'Koha::Patron::Attribute::Types' });
1980 my $deleted_attribute_type = $builder->build_object({ class => 'Koha::Patron::Attribute::Types' });
1981 my $deleted_attribute_type_code = $deleted_attribute_type->code;
1982 $deleted_attribute_type->delete;
1984 my $new_library = $builder->build( { source
=> 'Branch' } );
1985 my $attribute_type_limited = Koha
::Patron
::Attribute
::Type
->new(
1986 { code
=> 'my code3', description
=> 'my description3' } )->store;
1987 $attribute_type_limited->library_limits( [ $new_library->{branchcode
} ] );
1989 my $attributes_for_1 = [
1991 attribute
=> 'my attribute1',
1992 code
=> $attribute_type1->code(),
1995 attribute
=> 'my attribute2',
1996 code
=> $attribute_type2->code(),
1999 attribute
=> 'my attribute limited',
2000 code
=> $attribute_type_limited->code(),
2004 my $attributes_for_2 = [
2006 attribute
=> 'my attribute12',
2007 code
=> $attribute_type1->code(),
2010 attribute
=> 'my attribute limited 2',
2011 code
=> $attribute_type_limited->code(),
2014 attribute
=> 'my nonexistent attribute 2',
2015 code
=> $deleted_attribute_type_code,
2019 my $extended_attributes = $patron_1->extended_attributes;
2020 is
( ref($extended_attributes), 'Koha::Patron::Attributes', 'Koha::Patron->extended_attributes must return a Koha::Patron::Attribute set' );
2021 is
( $extended_attributes->count, 0, 'There should not be attribute yet');
2023 $patron_1->extended_attributes->filter_by_branch_limitations->delete;
2024 $patron_2->extended_attributes->filter_by_branch_limitations->delete;
2025 $patron_1->extended_attributes($attributes_for_1);
2028 $patron_2->extended_attributes($attributes_for_2);
2029 } [ qr/a foreign key constraint fails/ ], 'nonexistent attribute should have not exploded but print a warning';
2031 my $extended_attributes_for_1 = $patron_1->extended_attributes;
2032 is
( $extended_attributes_for_1->count, 3, 'There should be 3 attributes now for patron 1');
2034 my $extended_attributes_for_2 = $patron_2->extended_attributes;
2035 is
( $extended_attributes_for_2->count, 2, 'There should be 2 attributes now for patron 2');
2037 my $attribute_12 = $extended_attributes_for_2->search({ code
=> $attribute_type1->code });
2038 is
( $attribute_12->next->attribute, 'my attribute12', 'search by code should return the correct attribute' );
2040 $attribute_12 = $patron_2->get_extended_attribute( $attribute_type1->code );
2041 is
( $attribute_12->attribute, 'my attribute12', 'Koha::Patron->get_extended_attribute should return the correct attribute value' );
2044 $extended_attributes_for_2 = $patron_2->extended_attributes->merge_with(
2047 attribute
=> 'my attribute12 XXX',
2048 code
=> $attribute_type1->code(),
2051 attribute
=> 'my nonexistent attribute 2',
2052 code
=> $deleted_attribute_type_code,
2055 attribute
=> 'my attribute 3', # Adding a new attribute using merge_with
2056 code
=> $attribute_type3->code,
2061 "Cannot merge element: unrecognized code = '$deleted_attribute_type_code'",
2062 "Trying to merge_with using a nonexistent attribute code should display a warning";
2064 is
( @
$extended_attributes_for_2, 3, 'There should be 3 attributes now for patron 3');
2065 my $expected_attributes_for_2 = [
2067 code
=> $attribute_type1->code(),
2068 attribute
=> 'my attribute12 XXX',
2071 code
=> $attribute_type_limited->code(),
2072 attribute
=> 'my attribute limited 2',
2075 attribute
=> 'my attribute 3',
2076 code
=> $attribute_type3->code,
2079 # Sorting them by code
2080 $expected_attributes_for_2 = [ sort { $a->{code
} cmp $b->{code
} } @
$expected_attributes_for_2 ];
2085 code
=> $extended_attributes_for_2->[0]->{code
},
2086 attribute
=> $extended_attributes_for_2->[0]->{attribute
}
2089 code
=> $extended_attributes_for_2->[1]->{code
},
2090 attribute
=> $extended_attributes_for_2->[1]->{attribute
}
2093 code
=> $extended_attributes_for_2->[2]->{code
},
2094 attribute
=> $extended_attributes_for_2->[2]->{attribute
}
2097 $expected_attributes_for_2
2100 # TODO - What about multiple? POD explains the problem
2101 my $non_existent = $patron_2->get_extended_attribute( 'not_exist' );
2102 is
( $non_existent, undef, 'Koha::Patron->get_extended_attribute must return undef if the attribute does not exist' );
2104 # Test branch limitations
2105 t
::lib
::Mocks
::mock_userenv
({ patron
=> $patron_2 });
2107 $extended_attributes_for_1 = $patron_1->extended_attributes;
2108 is
( $extended_attributes_for_1->count, 3, 'There should be 2 attributes for patron 1, the limited one should be returned');
2111 $extended_attributes_for_1 = $patron_1->extended_attributes->filter_by_branch_limitations;
2112 is
( $extended_attributes_for_1->count, 2, 'There should be 2 attributes for patron 1, the limited one should be returned');
2115 my $limited_value = $patron_1->get_extended_attribute( $attribute_type_limited->code );
2116 is
( $limited_value->attribute, 'my attribute limited', );
2118 ## Do we need a filtered?
2119 #$limited_value = $patron_1->get_extended_attribute( $attribute_type_limited->code );
2120 #is( $limited_value, undef, );
2122 $schema->storage->txn_rollback;