3 # Copyright 2020 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 => 6;
28 use t::lib::TestBuilder;
36 my $schema = Koha::Database->new->schema;
37 my $builder = t::lib::TestBuilder->new;
39 subtest 'fill() tests' => sub {
43 $schema->storage->txn_begin;
47 my $category = $builder->build_object(
49 class => 'Koha::Patron::Categories',
50 value => { reservefee => $fee }
53 my $patron = $builder->build_object(
55 class => 'Koha::Patrons',
56 value => { categorycode => $category->id }
59 my $manager = $builder->build_object( { class => 'Koha::Patrons' } );
61 my $title = 'Do what you want';
62 my $biblio = $builder->build_sample_biblio( { title => $title } );
63 my $item = $builder->build_sample_item( { biblionumber => $biblio->id } );
64 my $hold = $builder->build_object(
66 class => 'Koha::Holds',
68 biblionumber => $biblio->id,
69 borrowernumber => $patron->id,
70 itemnumber => $item->id,
76 t::lib::Mocks::mock_preference( 'HoldFeeMode', 'any_time_is_collected' );
77 t::lib::Mocks::mock_preference( 'HoldsLog', 1 );
78 t::lib::Mocks::mock_userenv(
79 { patron => $manager, branchcode => $manager->branchcode } );
81 my $interface = 'api';
82 C4::Context->interface($interface);
84 my $ret = $hold->fill;
86 is( ref($ret), 'Koha::Hold', '->fill returns the object type' );
87 is( $ret->id, $hold->id, '->fill returns the object' );
89 is( Koha::Holds->find($hold->id), undef, 'Hold no longer current' );
90 my $old_hold = Koha::Old::Holds->find( $hold->id );
92 is( $old_hold->id, $hold->id, 'reserve_id retained' );
93 is( $old_hold->priority, 0, 'priority set to 0' );
94 is( $old_hold->found, 'F', 'found set to F' );
96 subtest 'fee applied tests' => sub {
100 my $account = $patron->account;
101 is( $account->balance, $fee, 'Charge applied correctly' );
103 my $debits = $account->outstanding_debits;
104 is( $debits->count, 1, 'Only one fee charged' );
106 my $fee_debit = $debits->next;
107 is( $fee_debit->amount * 1, $fee, 'Fee amount stored correctly' );
108 is( $fee_debit->description, $title,
109 'Fee description stored correctly' );
110 is( $fee_debit->manager_id, $manager->id,
111 'Fee manager_id stored correctly' );
112 is( $fee_debit->branchcode, $manager->branchcode,
113 'Fee branchcode stored correctly' );
114 is( $fee_debit->interface, $interface,
115 'Fee interface stored correctly' );
116 is( $fee_debit->debit_type_code,
117 'RESERVE', 'Fee debit_type_code stored correctly' );
118 is( $fee_debit->itemnumber, $item->id,
119 'Fee itemnumber stored correctly' );
122 my $logs = Koha::ActionLogs->search(
130 is( $logs->count, 1, '1 log line added' );
132 # Set HoldFeeMode to something other than any_time_is_collected
133 t::lib::Mocks::mock_preference( 'HoldFeeMode', 'not_always' );
135 t::lib::Mocks::mock_preference( 'HoldsLog', 0 );
137 $hold = $builder->build_object(
139 class => 'Koha::Holds',
141 biblionumber => $biblio->id,
142 borrowernumber => $patron->id,
143 itemnumber => $item->id,
151 my $account = $patron->account;
152 is( $account->balance, $fee, 'No new charge applied' );
154 my $debits = $account->outstanding_debits;
155 is( $debits->count, 1, 'Only one fee charged, because of HoldFeeMode' );
157 $logs = Koha::ActionLogs->search(
165 is( $logs->count, 0, 'HoldsLog disabled, no logs added' );
167 subtest 'anonymization behavior tests' => sub {
171 # reduce the tests noise
172 t::lib::Mocks::mock_preference( 'HoldsLog', 0 );
173 t::lib::Mocks::mock_preference( 'HoldFeeMode', 'not_always' );
174 # unset AnonymousPatron
175 t::lib::Mocks::mock_preference( 'AnonymousPatron', undef );
178 $patron->privacy(0)->store;
179 my $hold = $builder->build_object(
181 class => 'Koha::Holds',
182 value => { borrowernumber => $patron->id, found => undef }
186 is( Koha::Old::Holds->find( $hold->id )->borrowernumber,
187 $patron->borrowernumber, 'Patron link is kept' );
189 # 1 == "default", meaning it is not protected from removal
190 $patron->privacy(1)->store;
191 $hold = $builder->build_object(
193 class => 'Koha::Holds',
194 value => { borrowernumber => $patron->id, found => undef }
198 is( Koha::Old::Holds->find( $hold->id )->borrowernumber,
199 $patron->borrowernumber, 'Patron link is kept' );
201 # 2 == delete immediately
202 $patron->privacy(2)->store;
203 $hold = $builder->build_object(
205 class => 'Koha::Holds',
206 value => { borrowernumber => $patron->id, found => undef }
213 'AnonymousPatron not set, exception thrown';
215 $hold->discard_changes; # refresh from DB
217 ok( !$hold->is_found, 'Hold is not filled' );
219 my $anonymous_patron = $builder->build_object({ class => 'Koha::Patrons' });
220 t::lib::Mocks::mock_preference( 'AnonymousPatron', $anonymous_patron->id );
222 $hold = $builder->build_object(
224 class => 'Koha::Holds',
225 value => { borrowernumber => $patron->id, found => undef }
230 Koha::Old::Holds->find( $hold->id )->borrowernumber,
231 $anonymous_patron->id,
232 'Patron link is set to the configured anonymous patron immediately'
236 subtest 'holds_queue update tests' => sub {
240 my $biblio = $builder->build_sample_biblio;
242 my $mock = Test::MockModule->new('Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue');
243 $mock->mock( 'enqueue', sub {
244 my ( $self, $args ) = @_;
248 '->fill triggers a holds queue update for the related biblio'
252 t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 1 );
254 $builder->build_object(
256 class => 'Koha::Holds',
258 biblionumber => $biblio->id,
263 t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 0 );
264 # this call shouldn't add a new test
265 $builder->build_object(
267 class => 'Koha::Holds',
269 biblionumber => $biblio->id,
275 $schema->storage->txn_rollback;
278 subtest 'patron() tests' => sub {
282 $schema->storage->txn_begin;
284 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
285 my $hold = $builder->build_object(
287 class => 'Koha::Holds',
289 borrowernumber => $patron->borrowernumber
294 my $hold_patron = $hold->patron;
295 is( ref($hold_patron), 'Koha::Patron', 'Right type' );
296 is( $hold_patron->id, $patron->id, 'Right object' );
298 $schema->storage->txn_rollback;
301 subtest 'set_pickup_location() tests' => sub {
305 $schema->storage->txn_begin;
307 my $mock_biblio = Test::MockModule->new('Koha::Biblio');
308 my $mock_item = Test::MockModule->new('Koha::Item');
310 my $library_1 = $builder->build_object({ class => 'Koha::Libraries' });
311 my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
312 my $library_3 = $builder->build_object({ class => 'Koha::Libraries' });
314 # let's control what Koha::Biblio->pickup_locations returns, for testing
315 $mock_biblio->mock( 'pickup_locations', sub {
316 return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
318 # let's mock what Koha::Item->pickup_locations returns, for testing
319 $mock_item->mock( 'pickup_locations', sub {
320 return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
323 my $biblio = $builder->build_sample_biblio;
324 my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
326 # Test biblio-level holds
327 my $biblio_hold = $builder->build_object(
329 class => "Koha::Holds",
331 biblionumber => $biblio->biblionumber,
332 branchcode => $library_3->branchcode,
339 { $biblio_hold->set_pickup_location({ library_id => $library_1->branchcode }); }
340 'Koha::Exceptions::Hold::InvalidPickupLocation',
341 'Exception thrown on invalid pickup location';
343 $biblio_hold->discard_changes;
344 is( $biblio_hold->branchcode, $library_3->branchcode, 'branchcode remains untouched' );
346 my $ret = $biblio_hold->set_pickup_location({ library_id => $library_2->id });
347 is( ref($ret), 'Koha::Hold', 'self is returned' );
349 $biblio_hold->discard_changes;
350 is( $biblio_hold->branchcode, $library_2->id, 'Pickup location changed correctly' );
352 # Test item-level holds
353 my $item_hold = $builder->build_object(
355 class => "Koha::Holds",
357 biblionumber => $biblio->biblionumber,
358 branchcode => $library_3->branchcode,
359 itemnumber => $item->itemnumber,
365 { $item_hold->set_pickup_location({ library_id => $library_1->branchcode }); }
366 'Koha::Exceptions::Hold::InvalidPickupLocation',
367 'Exception thrown on invalid pickup location';
369 $item_hold->discard_changes;
370 is( $item_hold->branchcode, $library_3->branchcode, 'branchcode remains untouched' );
372 $item_hold->set_pickup_location({ library_id => $library_1->branchcode, force => 1 });
373 $item_hold->discard_changes;
374 is( $item_hold->branchcode, $library_1->branchcode, 'branchcode changed because of \'force\'' );
376 $ret = $item_hold->set_pickup_location({ library_id => $library_2->id });
377 is( ref($ret), 'Koha::Hold', 'self is returned' );
379 $item_hold->discard_changes;
380 is( $item_hold->branchcode, $library_2->id, 'Pickup location changed correctly' );
383 { $item_hold->set_pickup_location({ library_id => undef }); }
384 'Koha::Exceptions::MissingParameter',
385 'Exception thrown if missing parameter';
387 like( "$@", qr/The library_id parameter is mandatory/, 'Exception message is clear' );
389 $schema->storage->txn_rollback;
392 subtest 'is_pickup_location_valid() tests' => sub {
396 $schema->storage->txn_begin;
398 my $mock_biblio = Test::MockModule->new('Koha::Biblio');
399 my $mock_item = Test::MockModule->new('Koha::Item');
401 my $library_1 = $builder->build_object({ class => 'Koha::Libraries' });
402 my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
403 my $library_3 = $builder->build_object({ class => 'Koha::Libraries' });
405 # let's control what Koha::Biblio->pickup_locations returns, for testing
406 $mock_biblio->mock( 'pickup_locations', sub {
407 return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
409 # let's mock what Koha::Item->pickup_locations returns, for testing
410 $mock_item->mock( 'pickup_locations', sub {
411 return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
414 my $biblio = $builder->build_sample_biblio;
415 my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
417 # Test biblio-level holds
418 my $biblio_hold = $builder->build_object(
420 class => "Koha::Holds",
422 biblionumber => $biblio->biblionumber,
423 branchcode => $library_3->branchcode,
429 ok( !$biblio_hold->is_pickup_location_valid({ library_id => $library_1->branchcode }), 'Pickup location invalid');
430 ok( $biblio_hold->is_pickup_location_valid({ library_id => $library_2->id }), 'Pickup location valid');
432 # Test item-level holds
433 my $item_hold = $builder->build_object(
435 class => "Koha::Holds",
437 biblionumber => $biblio->biblionumber,
438 branchcode => $library_3->branchcode,
439 itemnumber => $item->itemnumber,
444 ok( !$item_hold->is_pickup_location_valid({ library_id => $library_1->branchcode }), 'Pickup location invalid');
445 ok( $item_hold->is_pickup_location_valid({ library_id => $library_2->id }), 'Pickup location valid' );
447 subtest 'pickup_locations() returning ->empty' => sub {
451 $schema->storage->txn_begin;
453 my $library = $builder->build_object({ class => 'Koha::Libraries' });
455 my $mock_item = Test::MockModule->new('Koha::Item');
456 $mock_item->mock( 'pickup_locations', sub { return Koha::Libraries->new->empty; } );
458 my $mock_biblio = Test::MockModule->new('Koha::Biblio');
459 $mock_biblio->mock( 'pickup_locations', sub { return Koha::Libraries->new->empty; } );
461 my $item = $builder->build_sample_item();
462 my $biblio = $item->biblio;
464 # Test biblio-level holds
465 my $biblio_hold = $builder->build_object(
467 class => "Koha::Holds",
469 biblionumber => $biblio->biblionumber,
475 ok( !$biblio_hold->is_pickup_location_valid({ library_id => $library->branchcode }), 'Pickup location invalid');
477 # Test item-level holds
478 my $item_hold = $builder->build_object(
480 class => "Koha::Holds",
482 biblionumber => $biblio->biblionumber,
483 itemnumber => $item->itemnumber,
488 ok( !$item_hold->is_pickup_location_valid({ library_id => $library->branchcode }), 'Pickup location invalid');
490 $schema->storage->txn_rollback;
493 $schema->storage->txn_rollback;
496 subtest 'cancel() tests' => sub {
500 $schema->storage->txn_begin;
502 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
504 # reduce the tests noise
505 t::lib::Mocks::mock_preference( 'HoldsLog', 0 );
506 t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelayCharge',
509 t::lib::Mocks::mock_preference( 'AnonymousPatron', undef );
512 $patron->privacy(0)->store;
513 my $hold = $builder->build_object(
515 class => 'Koha::Holds',
516 value => { borrowernumber => $patron->id, found => undef }
520 is( Koha::Old::Holds->find( $hold->id )->borrowernumber,
521 $patron->borrowernumber, 'Patron link is kept' );
523 # 1 == "default", meaning it is not protected from removal
524 $patron->privacy(1)->store;
525 $hold = $builder->build_object(
527 class => 'Koha::Holds',
528 value => { borrowernumber => $patron->id, found => undef }
532 is( Koha::Old::Holds->find( $hold->id )->borrowernumber,
533 $patron->borrowernumber, 'Patron link is kept' );
535 # 2 == delete immediately
536 $patron->privacy(2)->store;
537 $hold = $builder->build_object(
539 class => 'Koha::Holds',
540 value => { borrowernumber => $patron->id, found => undef }
546 'AnonymousPatron not set, exception thrown';
548 $hold->discard_changes;
550 ok( !$hold->is_found, 'Hold is not cancelled' );
552 my $anonymous_patron = $builder->build_object({ class => 'Koha::Patrons' });
553 t::lib::Mocks::mock_preference( 'AnonymousPatron', $anonymous_patron->id );
555 $hold = $builder->build_object(
557 class => 'Koha::Holds',
558 value => { borrowernumber => $patron->id, found => undef }
563 Koha::Old::Holds->find( $hold->id )->borrowernumber,
564 $anonymous_patron->id,
565 'Patron link is set to the configured anonymous patron immediately'
568 subtest 'holds_queue update tests' => sub {
572 my $biblio = $builder->build_sample_biblio;
574 t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 1 );
576 my $mock = Test::MockModule->new('Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue');
577 $mock->mock( 'enqueue', sub {
578 my ( $self, $args ) = @_;
582 '->cancel triggers a holds queue update for the related biblio'
586 $builder->build_object(
588 class => 'Koha::Holds',
590 biblionumber => $biblio->id,
595 # If the skip_holds_queue param is not honoured, then test count will fail.
596 $builder->build_object(
598 class => 'Koha::Holds',
600 biblionumber => $biblio->id,
603 )->cancel({ skip_holds_queue => 1 });
605 t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 0 );
607 $builder->build_object(
609 class => 'Koha::Holds',
611 biblionumber => $biblio->id,
614 )->cancel({ skip_holds_queue => 0 });
617 $schema->storage->txn_rollback;
620 subtest 'suspend_hold() and resume() tests' => sub {
624 $schema->storage->txn_begin;
626 my $biblio = $builder->build_sample_biblio;
629 t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 1 );
631 my $mock = Test::MockModule->new('Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue');
632 $mock->mock( 'enqueue', sub {
633 my ( $self, $args ) = @_;
637 "->$action triggers a holds queue update for the related biblio"
641 my $hold = $builder->build_object(
643 class => 'Koha::Holds',
645 biblionumber => $biblio->id,
651 $action = 'suspend_hold';
657 $schema->storage->txn_rollback;