3 # This file is part of Koha.
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20 use Test::More tests => 12;
23 use t::lib::TestBuilder;
36 use Koha::Biblioitems;
38 use Koha::CirculationRules;
40 my $schema = Koha::Database->new->schema;
41 my $builder = t::lib::TestBuilder->new();
43 $schema->storage->txn_begin;
45 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
47 my $t = Test::Mojo->new('Koha::REST::V1');
49 my $categorycode = $builder->build({ source => 'Category' })->{categorycode};
50 my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
51 my $branchcode2 = $builder->build({ source => 'Branch' })->{branchcode};
52 my $itemtype = $builder->build({ source => 'Itemtype' })->{itemtype};
54 # Generic password for everyone
55 my $password = 'thePassword123';
57 # User without any permissions
58 my $nopermission = $builder->build_object({
59 class => 'Koha::Patrons',
61 branchcode => $branchcode,
62 categorycode => $categorycode,
66 $nopermission->set_password( { password => $password, skip_validation => 1 } );
67 my $nopermission_userid = $nopermission->userid;
69 my $patron_1 = $builder->build_object(
71 class => 'Koha::Patrons',
73 categorycode => $categorycode,
74 branchcode => $branchcode,
75 surname => 'Test Surname',
76 flags => 80, #borrowers and reserveforothers flags
80 $patron_1->set_password( { password => $password, skip_validation => 1 } );
81 my $userid_1 = $patron_1->userid;
83 my $patron_2 = $builder->build_object(
85 class => 'Koha::Patrons',
87 categorycode => $categorycode,
88 branchcode => $branchcode,
89 surname => 'Test Surname 2',
90 flags => 16, # borrowers flag
94 $patron_2->set_password( { password => $password, skip_validation => 1 } );
95 my $userid_2 = $patron_2->userid;
97 my $patron_3 = $builder->build_object(
99 class => 'Koha::Patrons',
101 categorycode => $categorycode,
102 branchcode => $branchcode,
103 surname => 'Test Surname 3',
104 flags => 64, # reserveforothers flag
108 $patron_3->set_password( { password => $password, skip_validation => 1 } );
109 my $userid_3 = $patron_3->userid;
111 my $biblio_1 = $builder->build_sample_biblio;
112 my $item_1 = $builder->build_sample_item({ biblionumber => $biblio_1->biblionumber, itype => $itemtype });
114 my $biblio_2 = $builder->build_sample_biblio;
115 my $item_2 = $builder->build_sample_item({ biblionumber => $biblio_2->biblionumber, itype => $itemtype });
117 my $dbh = C4::Context->dbh;
118 $dbh->do('DELETE FROM reserves');
119 Koha::CirculationRules->search()->delete();
120 Koha::CirculationRules->set_rules(
122 categorycode => undef,
126 reservesallowed => 1,
127 holds_per_record => 99
132 my $reserve_id = C4::Reserves::AddReserve(
134 branchcode => $branchcode,
135 borrowernumber => $patron_1->borrowernumber,
136 biblionumber => $biblio_1->biblionumber,
138 itemnumber => $item_1->itemnumber,
142 # Add another reserve to be able to change first reserve's rank
143 my $reserve_id2 = C4::Reserves::AddReserve(
145 branchcode => $branchcode,
146 borrowernumber => $patron_2->borrowernumber,
147 biblionumber => $biblio_1->biblionumber,
149 itemnumber => $item_1->itemnumber,
153 my $suspended_until = DateTime->now->add(days => 10)->truncate( to => 'day' );
154 my $expiration_date = DateTime->now->add(days => 10)->truncate( to => 'day' );
157 patron_id => int($patron_1->borrowernumber),
158 biblio_id => int($biblio_1->biblionumber),
159 item_id => int($item_1->itemnumber),
160 pickup_library_id => $branchcode,
161 expiration_date => output_pref({ dt => $expiration_date, dateformat => 'rfc3339', dateonly => 1 }),
166 suspended_until => output_pref({ dt => $suspended_until, dateformat => 'rfc3339' }),
169 subtest "Test endpoints without authentication" => sub {
171 $t->get_ok('/api/v1/holds')
173 $t->post_ok('/api/v1/holds')
175 $t->put_ok('/api/v1/holds/0')
177 $t->delete_ok('/api/v1/holds/0')
181 subtest "Test endpoints without permission" => sub {
185 $t->get_ok( "//$nopermission_userid:$password@/api/v1/holds?patron_id=" . $patron_1->borrowernumber ) # no permission
188 $t->get_ok( "//$userid_3:$password@/api/v1/holds?patron_id=" . $patron_1->borrowernumber ) # no permission
191 $t->post_ok( "//$nopermission_userid:$password@/api/v1/holds" => json => $post_data )
194 $t->put_ok( "//$nopermission_userid:$password@/api/v1/holds/0" => json => $put_data )
197 $t->delete_ok( "//$nopermission_userid:$password@/api/v1/holds/0" )
201 subtest "Test endpoints with permission" => sub {
205 $t->get_ok( "//$userid_1:$password@/api/v1/holds" )
211 $t->get_ok( "//$userid_1:$password@/api/v1/holds?priority=2" )
213 ->json_is('/0/patron_id', $patron_2->borrowernumber)
216 $t->delete_ok( "//$userid_3:$password@/api/v1/holds/$reserve_id" )
217 ->status_is(204, 'SWAGGER3.2.4')
218 ->content_is('', 'SWAGGER3.3.4');
220 $t->put_ok( "//$userid_3:$password@/api/v1/holds/$reserve_id" => json => $put_data )
222 ->json_has('/error');
224 $t->delete_ok( "//$userid_3:$password@/api/v1/holds/$reserve_id" )
226 ->json_has('/error');
228 $t->get_ok( "//$userid_2:$password@/api/v1/holds?patron_id=" . $patron_1->borrowernumber )
232 my $inexisting_borrowernumber = $patron_2->borrowernumber * 2;
233 $t->get_ok( "//$userid_1:$password@/api/v1/holds?patron_id=$inexisting_borrowernumber")
237 $t->delete_ok( "//$userid_3:$password@/api/v1/holds/$reserve_id2" )
238 ->status_is(204, 'SWAGGER3.2.4')
239 ->content_is('', 'SWAGGER3.3.4');
241 # Make sure pickup location checks doesn't get in the middle
242 my $mock_biblio = Test::MockModule->new('Koha::Biblio');
243 $mock_biblio->mock( 'pickup_locations', sub { return Koha::Libraries->search; });
244 my $mock_item = Test::MockModule->new('Koha::Item');
245 $mock_item->mock( 'pickup_locations', sub { return Koha::Libraries->search });
247 $t->post_ok( "//$userid_3:$password@/api/v1/holds" => json => $post_data )
249 ->json_has('/hold_id');
251 # Get id from response
252 $reserve_id = $t->tx->res->json->{hold_id};
254 $t->get_ok( "//$userid_1:$password@/api/v1/holds?patron_id=" . $patron_1->borrowernumber )
256 ->json_is('/0/hold_id', $reserve_id)
257 ->json_is('/0/expiration_date', output_pref({ dt => $expiration_date, dateformat => 'rfc3339', dateonly => 1 }))
258 ->json_is('/0/pickup_library_id', $branchcode);
260 $t->post_ok( "//$userid_3:$password@/api/v1/holds" => json => $post_data )
262 ->json_like('/error', qr/itemAlreadyOnHold/);
264 $post_data->{biblionumber} = int($biblio_2->biblionumber);
265 $post_data->{itemnumber} = int($item_2->itemnumber);
267 $t->post_ok( "//$userid_3:$password@/api/v1/holds" => json => $post_data )
269 ->json_like('/error', qr/itemAlreadyOnHold/);
271 my $to_delete_patron = $builder->build_object({ class => 'Koha::Patrons' });
272 my $deleted_patron_id = $to_delete_patron->borrowernumber;
273 $to_delete_patron->delete;
275 my $tmp_patron_id = $post_data->{patron_id};
276 $post_data->{patron_id} = $deleted_patron_id;
277 $t->post_ok( "//$userid_3:$password@/api/v1/holds" => json => $post_data )
279 ->json_is( { error => 'patron_id not found' } );
281 # Restore the original patron_id as it is expected by the next subtest
282 # FIXME: this tests need to be rewritten from scratch
283 $post_data->{patron_id} = $tmp_patron_id;
286 subtest 'Reserves with itemtype' => sub {
290 patron_id => int($patron_1->borrowernumber),
291 biblio_id => int($biblio_1->biblionumber),
292 pickup_library_id => $branchcode,
293 item_type => $itemtype,
296 $t->delete_ok( "//$userid_3:$password@/api/v1/holds/$reserve_id" )
297 ->status_is(204, 'SWAGGER3.2.4')
298 ->content_is('', 'SWAGGER3.3.4');
300 # Make sure pickup location checks doesn't get in the middle
301 my $mock_biblio = Test::MockModule->new('Koha::Biblio');
302 $mock_biblio->mock( 'pickup_locations', sub { return Koha::Libraries->search; });
303 my $mock_item = Test::MockModule->new('Koha::Item');
304 $mock_item->mock( 'pickup_locations', sub { return Koha::Libraries->search });
306 $t->post_ok( "//$userid_3:$password@/api/v1/holds" => json => $post_data )
308 ->json_has('/hold_id');
310 $reserve_id = $t->tx->res->json->{hold_id};
312 $t->get_ok( "//$userid_1:$password@/api/v1/holds?patron_id=" . $patron_1->borrowernumber )
314 ->json_is('/0/hold_id', $reserve_id)
315 ->json_is('/0/item_type', $itemtype);
319 subtest 'test AllowHoldDateInFuture' => sub {
323 $dbh->do('DELETE FROM reserves');
325 my $future_hold_date = DateTime->now->add(days => 10)->truncate( to => 'day' );
328 patron_id => int($patron_1->borrowernumber),
329 biblio_id => int($biblio_1->biblionumber),
330 item_id => int($item_1->itemnumber),
331 pickup_library_id => $branchcode,
332 expiration_date => output_pref({ dt => $expiration_date, dateformat => 'rfc3339', dateonly => 1 }),
333 hold_date => output_pref({ dt => $future_hold_date, dateformat => 'rfc3339', dateonly => 1 }),
337 t::lib::Mocks::mock_preference( 'AllowHoldDateInFuture', 0 );
339 $t->post_ok( "//$userid_3:$password@/api/v1/holds" => json => $post_data )
341 ->json_has('/error');
343 t::lib::Mocks::mock_preference( 'AllowHoldDateInFuture', 1 );
345 # Make sure pickup location checks doesn't get in the middle
346 my $mock_biblio = Test::MockModule->new('Koha::Biblio');
347 $mock_biblio->mock( 'pickup_locations', sub { return Koha::Libraries->search; });
348 my $mock_item = Test::MockModule->new('Koha::Item');
349 $mock_item->mock( 'pickup_locations', sub { return Koha::Libraries->search });
351 $t->post_ok( "//$userid_3:$password@/api/v1/holds" => json => $post_data )
353 ->json_is('/hold_date', output_pref({ dt => $future_hold_date, dateformat => 'rfc3339', dateonly => 1 }));
356 subtest 'test AllowHoldPolicyOverride' => sub {
360 $dbh->do('DELETE FROM reserves');
362 Koha::CirculationRules->set_rules(
372 t::lib::Mocks::mock_preference( 'AllowHoldPolicyOverride', 0 );
374 # Make sure pickup location checks doesn't get in the middle
375 my $mock_biblio = Test::MockModule->new('Koha::Biblio');
376 $mock_biblio->mock( 'pickup_locations', sub { return Koha::Libraries->search; });
377 my $mock_item = Test::MockModule->new('Koha::Item');
378 $mock_item->mock( 'pickup_locations', sub { return Koha::Libraries->search });
380 $t->post_ok( "//$userid_3:$password@/api/v1/holds" => json => $post_data )
382 ->json_has('/error');
384 t::lib::Mocks::mock_preference( 'AllowHoldPolicyOverride', 1 );
386 $t->post_ok( "//$userid_3:$password@/api/v1/holds" => json => $post_data )
390 $schema->storage->txn_rollback;
392 subtest 'suspend and resume tests' => sub {
396 $schema->storage->txn_begin;
398 my $password = 'AbcdEFG123';
400 my $patron = $builder->build_object(
401 { class => 'Koha::Patrons', value => { userid => 'tomasito', flags => 1 } } );
402 $patron->set_password({ password => $password, skip_validation => 1 });
403 my $userid = $patron->userid;
406 t::lib::Mocks::mock_preference( 'HoldsLog', 0 );
407 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
409 my $hold = $builder->build_object(
410 { class => 'Koha::Holds',
411 value => { suspend => 0, suspend_until => undef, waitingdate => undef, found => undef }
415 ok( !$hold->is_suspended, 'Hold is not suspended' );
416 $t->post_ok( "//$userid:$password@/api/v1/holds/" . $hold->id . "/suspension" )
417 ->status_is( 201, 'Hold suspension created' );
419 $hold->discard_changes; # refresh object
421 ok( $hold->is_suspended, 'Hold is suspended' );
422 $t->json_is('/end_date', undef, 'Hold suspension has no end date');
424 my $end_date = output_pref({
425 dt => dt_from_string( undef ),
426 dateformat => 'rfc3339',
430 $t->post_ok( "//$userid:$password@/api/v1/holds/" . $hold->id . "/suspension" => json => { end_date => $end_date } );
432 $hold->discard_changes; # refresh object
434 ok( $hold->is_suspended, 'Hold is suspended' );
438 dt => dt_from_string( $hold->suspend_until ),
439 dateformat => 'rfc3339',
442 'Hold suspension has correct end date'
445 $t->delete_ok( "//$userid:$password@/api/v1/holds/" . $hold->id . "/suspension" )
446 ->status_is(204, 'SWAGGER3.2.4')
447 ->content_is('', 'SWAGGER3.3.4');
449 # Pass a an expiration date for the suspension
450 my $date = dt_from_string()->add( days => 5 );
452 "//$userid:$password@/api/v1/holds/"
454 . "/suspension" => json => {
456 output_pref( { dt => $date, dateformat => 'rfc3339', dateonly => 1 } )
458 )->status_is( 201, 'Hold suspension created' )
459 ->json_is( '/end_date',
460 output_pref( { dt => $date, dateformat => 'rfc3339', dateonly => 1 } ) )
461 ->header_is( Location => "/api/v1/holds/" . $hold->id . "/suspension", 'The Location header is set' );
463 $t->delete_ok( "//$userid:$password@/api/v1/holds/" . $hold->id . "/suspension" )
464 ->status_is(204, 'SWAGGER3.2.4')
465 ->content_is('', 'SWAGGER3.3.4');
467 $hold->set_waiting->discard_changes;
469 $t->post_ok( "//$userid:$password@/api/v1/holds/" . $hold->id . "/suspension" )
470 ->status_is( 400, 'Cannot suspend waiting hold' )
471 ->json_is( '/error', 'Found hold cannot be suspended. Status=W' );
473 $hold->set_transfer->discard_changes;
475 $t->post_ok( "//$userid:$password@/api/v1/holds/" . $hold->id . "/suspension" )
476 ->status_is( 400, 'Cannot suspend hold on transfer' )
477 ->json_is( '/error', 'Found hold cannot be suspended. Status=T' );
479 $schema->storage->txn_rollback;
482 subtest 'PUT /holds/{hold_id}/priority tests' => sub {
486 $schema->storage->txn_begin;
488 my $password = 'AbcdEFG123';
490 my $library = $builder->build_object({ class => 'Koha::Libraries' });
491 my $patron_np = $builder->build_object(
492 { class => 'Koha::Patrons', value => { flags => 0 } } );
493 $patron_np->set_password( { password => $password, skip_validation => 1 } );
494 my $userid_np = $patron_np->userid;
496 my $patron = $builder->build_object(
497 { class => 'Koha::Patrons', value => { flags => 0 } } );
498 $patron->set_password( { password => $password, skip_validation => 1 } );
499 my $userid = $patron->userid;
502 source => 'UserPermission',
504 borrowernumber => $patron->borrowernumber,
506 code => 'modify_holds_priority',
512 t::lib::Mocks::mock_preference( 'HoldsLog', 0 );
513 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
515 my $biblio = $builder->build_sample_biblio;
516 my $patron_1 = $builder->build_object(
518 class => 'Koha::Patrons',
519 value => { branchcode => $library->branchcode }
522 my $patron_2 = $builder->build_object(
524 class => 'Koha::Patrons',
525 value => { branchcode => $library->branchcode }
528 my $patron_3 = $builder->build_object(
530 class => 'Koha::Patrons',
531 value => { branchcode => $library->branchcode }
535 my $hold_1 = Koha::Holds->find(
538 branchcode => $library->branchcode,
539 borrowernumber => $patron_1->borrowernumber,
540 biblionumber => $biblio->biblionumber,
545 my $hold_2 = Koha::Holds->find(
548 branchcode => $library->branchcode,
549 borrowernumber => $patron_2->borrowernumber,
550 biblionumber => $biblio->biblionumber,
555 my $hold_3 = Koha::Holds->find(
558 branchcode => $library->branchcode,
559 borrowernumber => $patron_3->borrowernumber,
560 biblionumber => $biblio->biblionumber,
566 $t->put_ok( "//$userid_np:$password@/api/v1/holds/"
568 . "/priority" => json => 1 )->status_is(403);
570 $t->put_ok( "//$userid:$password@/api/v1/holds/"
572 . "/priority" => json => 1 )->status_is(200)->json_is(1);
574 is( $hold_1->discard_changes->priority, 2, 'Priority adjusted correctly' );
575 is( $hold_2->discard_changes->priority, 3, 'Priority adjusted correctly' );
576 is( $hold_3->discard_changes->priority, 1, 'Priority adjusted correctly' );
578 $t->put_ok( "//$userid:$password@/api/v1/holds/"
580 . "/priority" => json => 3 )->status_is(200)->json_is(3);
582 is( $hold_1->discard_changes->priority, 1, 'Priority adjusted correctly' );
583 is( $hold_2->discard_changes->priority, 2, 'Priority adjusted correctly' );
584 is( $hold_3->discard_changes->priority, 3, 'Priority adjusted correctly' );
586 $schema->storage->txn_rollback;
589 subtest 'add() tests (maxreserves behaviour)' => sub {
593 $schema->storage->txn_begin;
595 $dbh->do('DELETE FROM reserves');
597 Koha::CirculationRules->new->delete;
599 my $password = 'AbcdEFG123';
601 my $patron = $builder->build_object(
602 { class => 'Koha::Patrons', value => { userid => 'tomasito', flags => 1 } } );
603 $patron->set_password({ password => $password, skip_validation => 1 });
604 my $userid = $patron->userid;
606 Koha::CirculationRules->set_rules(
610 categorycode => undef,
617 Koha::CirculationRules->set_rules(
620 categorycode => $patron->categorycode,
627 my $biblio_1 = $builder->build_sample_biblio;
628 my $item_1 = $builder->build_sample_item({ biblionumber => $biblio_1->biblionumber });
629 my $biblio_2 = $builder->build_sample_biblio;
630 my $item_2 = $builder->build_sample_item({ biblionumber => $biblio_2->biblionumber });
631 my $biblio_3 = $builder->build_sample_biblio;
632 my $item_3 = $builder->build_sample_item({ biblionumber => $biblio_3->biblionumber });
634 # Make sure pickup location checks doesn't get in the middle
635 my $mock_biblio = Test::MockModule->new('Koha::Biblio');
636 $mock_biblio->mock( 'pickup_locations', sub { return Koha::Libraries->search; });
637 my $mock_item = Test::MockModule->new('Koha::Item');
638 $mock_item->mock( 'pickup_locations', sub { return Koha::Libraries->search });
641 t::lib::Mocks::mock_preference( 'HoldsLog', 0 );
642 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
643 t::lib::Mocks::mock_preference( 'maxreserves', 2 );
644 t::lib::Mocks::mock_preference( 'AllowHoldPolicyOverride', 0 );
647 patron_id => $patron->borrowernumber,
648 biblio_id => $biblio_1->biblionumber,
649 pickup_library_id => $item_1->home_branch->branchcode,
650 item_type => $item_1->itype,
653 $t->post_ok( "//$userid:$password@/api/v1/holds" => json => $post_data )
657 patron_id => $patron->borrowernumber,
658 biblio_id => $biblio_2->biblionumber,
659 pickup_library_id => $item_2->home_branch->branchcode,
660 item_id => $item_2->itemnumber
663 $t->post_ok( "//$userid:$password@/api/v1/holds" => json => $post_data )
667 patron_id => $patron->borrowernumber,
668 biblio_id => $biblio_3->biblionumber,
669 pickup_library_id => $item_1->home_branch->branchcode,
670 item_id => $item_3->itemnumber
673 $t->post_ok( "//$userid:$password@/api/v1/holds" => json => $post_data )
675 ->json_is( { error => 'Hold cannot be placed. Reason: tooManyReserves' } );
677 $schema->storage->txn_rollback;
680 subtest 'pickup_locations() tests' => sub {
684 $schema->storage->txn_begin;
686 my $library_1 = $builder->build_object({ class => 'Koha::Libraries', value => { marcorgcode => 'A' } });
687 my $library_2 = $builder->build_object({ class => 'Koha::Libraries', value => { marcorgcode => 'B' } });
688 my $library_3 = $builder->build_object({ class => 'Koha::Libraries', value => { marcorgcode => 'C' } });
690 my $patron = $builder->build_object(
692 class => 'Koha::Patrons',
693 value => { userid => 'tomasito', flags => 0 }
696 $patron->set_password( { password => $password, skip_validation => 1 } );
697 my $userid = $patron->userid;
700 source => 'UserPermission',
702 borrowernumber => $patron->borrowernumber,
704 code => 'place_holds',
709 my $item_class = Test::MockModule->new('Koha::Item');
713 my ( $self, $params ) = @_;
714 my $mock_patron = $params->{patron};
715 is( $mock_patron->borrowernumber,
716 $patron->borrowernumber, 'Patron passed correctly' );
717 return Koha::Libraries->search(
721 $library_1->branchcode,
722 $library_2->branchcode
726 { # we make sure no surprises in the order of the result
727 order_by => { '-asc' => 'marcorgcode' }
733 my $biblio_class = Test::MockModule->new('Koha::Biblio');
737 my ( $self, $params ) = @_;
738 my $mock_patron = $params->{patron};
739 is( $mock_patron->borrowernumber,
740 $patron->borrowernumber, 'Patron passed correctly' );
741 return Koha::Libraries->search(
745 $library_2->branchcode,
746 $library_3->branchcode
750 { # we make sure no surprises in the order of the result
751 order_by => { '-asc' => 'marcorgcode' }
757 my $item = $builder->build_sample_item;
760 my $hold_1 = $builder->build_object(
762 class => 'Koha::Holds',
765 biblionumber => $item->biblionumber,
766 borrowernumber => $patron->borrowernumber
771 my $hold_2 = $builder->build_object(
773 class => 'Koha::Holds',
775 itemnumber => $item->itemnumber,
776 biblionumber => $item->biblionumber,
777 borrowernumber => $patron->borrowernumber
782 $t->get_ok( "//$userid:$password@/api/v1/holds/"
784 . "/pickup_locations" )
785 ->json_is( [ $library_2->to_api, $library_3->to_api ] );
787 $t->get_ok( "//$userid:$password@/api/v1/holds/"
789 . "/pickup_locations" )
790 ->json_is( [ $library_1->to_api, $library_2->to_api ] );
793 $t->get_ok( "//$userid:$password@/api/v1/holds/"
795 . '/pickup_locations?q={"marc_org_code": { "-like": "A%" }}' )
796 ->json_is( [ $library_1->to_api ] );
799 $schema->storage->txn_rollback;
802 subtest 'edit() tests' => sub {
806 $schema->storage->txn_begin;
808 my $password = 'AbcdEFG123';
810 my $library = $builder->build_object({ class => 'Koha::Libraries' });
811 my $patron = $builder->build_object(
812 { class => 'Koha::Patrons', value => { flags => 1 } } );
813 $patron->set_password( { password => $password, skip_validation => 1 } );
814 my $userid = $patron->userid;
817 source => 'UserPermission',
819 borrowernumber => $patron->borrowernumber,
821 code => 'modify_holds_priority',
827 t::lib::Mocks::mock_preference( 'HoldsLog', 0 );
828 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
830 my $mock_biblio = Test::MockModule->new('Koha::Biblio');
831 my $mock_item = Test::MockModule->new('Koha::Item');
833 my $library_1 = $builder->build_object({ class => 'Koha::Libraries' });
834 my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
835 my $library_3 = $builder->build_object({ class => 'Koha::Libraries' });
837 # let's control what Koha::Biblio->pickup_locations returns, for testing
838 $mock_biblio->mock( 'pickup_locations', sub {
839 return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
841 # let's mock what Koha::Item->pickup_locations returns, for testing
842 $mock_item->mock( 'pickup_locations', sub {
843 return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
846 my $biblio = $builder->build_sample_biblio;
847 my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
849 # Test biblio-level holds
850 my $biblio_hold = $builder->build_object(
852 class => "Koha::Holds",
854 biblionumber => $biblio->biblionumber,
855 branchcode => $library_3->branchcode,
862 my $biblio_hold_data = $biblio_hold->to_api;
863 $biblio_hold_data->{pickup_library_id} = $library_1->branchcode;
865 $t->put_ok( "//$userid:$password@/api/v1/holds/"
867 => json => $biblio_hold_data )
869 ->json_is({ error => 'The supplied pickup location is not valid' });
871 $biblio_hold->discard_changes;
872 is( $biblio_hold->branchcode, $library_3->branchcode, 'branchcode remains untouched' );
874 $biblio_hold_data->{pickup_library_id} = $library_2->branchcode;
875 $t->put_ok( "//$userid:$password@/api/v1/holds/"
877 => json => $biblio_hold_data )
880 $biblio_hold->discard_changes;
881 is( $biblio_hold->branchcode, $library_2->id, 'Pickup location changed correctly' );
883 # Test item-level holds
884 my $item_hold = $builder->build_object(
886 class => "Koha::Holds",
888 biblionumber => $biblio->biblionumber,
889 branchcode => $library_3->branchcode,
890 itemnumber => $item->itemnumber,
896 my $item_hold_data = $item_hold->to_api;
897 $item_hold_data->{pickup_library_id} = $library_1->branchcode;
899 $t->put_ok( "//$userid:$password@/api/v1/holds/"
901 => json => $item_hold_data )
903 ->json_is({ error => 'The supplied pickup location is not valid' });
905 $item_hold->discard_changes;
906 is( $item_hold->branchcode, $library_3->branchcode, 'branchcode remains untouched' );
908 $item_hold_data->{pickup_library_id} = $library_2->branchcode;
909 $t->put_ok( "//$userid:$password@/api/v1/holds/"
911 => json => $item_hold_data )
914 $item_hold->discard_changes;
915 is( $item_hold->branchcode, $library_2->id, 'Pickup location changed correctly' );
917 $schema->storage->txn_rollback;
920 subtest 'add() tests' => sub {
924 $schema->storage->txn_begin;
926 my $password = 'AbcdEFG123';
928 my $library = $builder->build_object({ class => 'Koha::Libraries' });
929 my $patron = $builder->build_object(
930 { class => 'Koha::Patrons', value => { flags => 1 } } );
931 $patron->set_password( { password => $password, skip_validation => 1 } );
932 my $userid = $patron->userid;
935 source => 'UserPermission',
937 borrowernumber => $patron->borrowernumber,
939 code => 'modify_holds_priority',
945 t::lib::Mocks::mock_preference( 'HoldsLog', 0 );
946 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
948 my $mock_biblio = Test::MockModule->new('Koha::Biblio');
949 my $mock_item = Test::MockModule->new('Koha::Item');
951 my $library_1 = $builder->build_object({ class => 'Koha::Libraries' });
952 my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
953 my $library_3 = $builder->build_object({ class => 'Koha::Libraries' });
955 # let's control what Koha::Biblio->pickup_locations returns, for testing
956 $mock_biblio->mock( 'pickup_locations', sub {
957 return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
959 # let's mock what Koha::Item->pickup_locations returns, for testing
960 $mock_item->mock( 'pickup_locations', sub {
961 return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
964 my $biblio = $builder->build_sample_biblio;
965 my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
967 # Test biblio-level holds
968 my $biblio_hold = $builder->build_object(
970 class => "Koha::Holds",
972 biblionumber => $biblio->biblionumber,
973 branchcode => $library_3->branchcode,
980 my $biblio_hold_data = $biblio_hold->to_api;
981 $biblio_hold->delete;
982 $biblio_hold_data->{pickup_library_id} = $library_1->branchcode;
983 delete $biblio_hold_data->{hold_id};
985 $t->post_ok( "//$userid:$password@/api/v1/holds" => json => $biblio_hold_data )
987 ->json_is({ error => 'The supplied pickup location is not valid' });
989 $biblio_hold_data->{pickup_library_id} = $library_2->branchcode;
990 $t->post_ok( "//$userid:$password@/api/v1/holds" => json => $biblio_hold_data )
993 # Test item-level holds
994 my $item_hold = $builder->build_object(
996 class => "Koha::Holds",
998 biblionumber => $biblio->biblionumber,
999 branchcode => $library_3->branchcode,
1000 itemnumber => $item->itemnumber,
1006 my $item_hold_data = $item_hold->to_api;
1008 $item_hold_data->{pickup_library_id} = $library_1->branchcode;
1009 delete $item_hold->{hold_id};
1011 $t->post_ok( "//$userid:$password@/api/v1/holds" => json => $item_hold_data )
1013 ->json_is({ error => 'The supplied pickup location is not valid' });
1015 $item_hold_data->{pickup_library_id} = $library_2->branchcode;
1016 $t->post_ok( "//$userid:$password@/api/v1/holds" => json => $item_hold_data )
1019 $schema->storage->txn_rollback;