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>.
23 use Test::More tests => 8;
29 use t::lib::TestBuilder;
32 use C4::Circulation qw( AddIssue AddReturn );
37 use Koha::Old::Checkouts;
39 my $schema = Koha::Database->new->schema;
40 my $builder = t::lib::TestBuilder->new;
42 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
44 my $t = Test::Mojo->new('Koha::REST::V1');
46 subtest 'get() tests' => sub {
50 $schema->storage->txn_begin;
52 my $patron = $builder->build_object(
54 class => 'Koha::Patrons',
55 value => { flags => 0 }
58 my $password = 'thePassword123';
59 $patron->set_password( { password => $password, skip_validation => 1 } );
60 $patron->discard_changes;
61 my $userid = $patron->userid;
63 my $biblio = $builder->build_sample_biblio({
64 title => 'The unbearable lightness of being',
65 author => 'Milan Kundera'
67 $t->get_ok("//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber)
70 $patron->flags(4)->store;
72 $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber
73 => { Accept => 'application/weird+format' } )
76 $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber
77 => { Accept => 'application/json' } )
79 ->json_is( '/title', 'The unbearable lightness of being' )
80 ->json_is( '/author', 'Milan Kundera' );
82 $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber
83 => { Accept => 'application/marcxml+xml' } )
86 $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber
87 => { Accept => 'application/marc-in-json' } )
90 $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber
91 => { Accept => 'application/marc' } )
94 $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber
95 => { Accept => 'text/plain' } )
97 ->content_is($biblio->metadata->record->as_formatted);
100 $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber
101 => { Accept => 'application/marc' } )
103 ->json_is( '/error', 'Object not found.' );
105 subtest 'marc-in-json encoding tests' => sub {
109 my $title_with_diacritics = "L'insoutenable légèreté de l'être";
111 my $biblio = $builder->build_sample_biblio(
113 title => $title_with_diacritics,
114 author => "Milan Kundera"
118 my $result = $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber
119 => { Accept => 'application/marc-in-json' } )
120 ->status_is(200)->tx->res->body;
122 my $encoded_title = Encode::encode( "UTF-8", $title_with_diacritics );
124 like( $result, qr/\Q$encoded_title/, "The title is not double encoded" );
127 $schema->storage->txn_rollback;
130 subtest 'get_items() tests' => sub {
134 $schema->storage->txn_begin;
136 my $patron = $builder->build_object(
138 class => 'Koha::Patrons',
139 value => { flags => 0 }
142 my $password = 'thePassword123';
143 $patron->set_password( { password => $password, skip_validation => 1 } );
144 $patron->discard_changes;
145 my $userid = $patron->userid;
147 my $biblio = $builder->build_sample_biblio();
148 $t->get_ok("//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber . "/items")
151 $patron->flags(4)->store;
153 $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber . "/items")
155 ->json_is( '' => [], 'No items on the biblio' );
157 my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
158 my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
160 $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber . "/items")
162 ->json_is( '' => [ $item_1->to_api, $item_2->to_api ], 'The items are returned' );
164 $schema->storage->txn_rollback;
167 subtest 'delete() tests' => sub {
171 $schema->storage->txn_begin;
173 my $patron = $builder->build_object(
175 class => 'Koha::Patrons',
176 value => { flags => 0 } # no permissions
179 my $password = 'thePassword123';
180 $patron->set_password( { password => $password, skip_validation => 1 } );
181 my $userid = $patron->userid;
183 my $item = $builder->build_sample_item();
184 my $biblio_id = $item->biblionumber;
186 $t->delete_ok("//$userid:$password@/api/v1/biblios/$biblio_id")
187 ->status_is(403, 'Not enough permissions makes it return the right code');
192 source => 'UserPermission',
194 borrowernumber => $patron->borrowernumber,
196 code => 'edit_catalogue'
202 # Bibs with items cannot be deleted
203 $t->delete_ok("//$userid:$password@/api/v1/biblios/$biblio_id")
208 # Bibs with no items can be deleted
209 $t->delete_ok("//$userid:$password@/api/v1/biblios/$biblio_id")
210 ->status_is(204, 'SWAGGER3.2.4')
211 ->content_is('', 'SWAGGER3.3.4');
213 $t->delete_ok("//$userid:$password@/api/v1/biblios/$biblio_id")
216 $schema->storage->txn_rollback;
219 subtest 'get_public() tests' => sub {
223 $schema->storage->txn_begin;
225 my $category = $builder->build_object({ class => 'Koha::Patron::Categories' });
226 my $patron = $builder->build_object(
228 class => 'Koha::Patrons',
230 flags => undef, # opac user
231 categorycode => $category->categorycode
235 my $password = 'thePassword123';
236 $patron->set_password( { password => $password, skip_validation => 1 } );
237 $patron->discard_changes;
238 my $userid = $patron->userid;
240 my $biblio = $builder->build_sample_biblio({
241 title => 'The unbearable lightness of being',
242 author => 'Milan Kundera'
245 # Make sure author in shown in the OPAC
246 my $subfields = Koha::MarcSubfieldStructures->search({ tagfield => '100' });
247 while ( my $subfield = $subfields->next ) {
248 $subfield->set({ hidden => -1 })->store;
250 Koha::Caches->get_instance()->flush_all;
252 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/" . $biblio->biblionumber
253 => { Accept => 'application/weird+format' } )
256 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/" . $biblio->biblionumber
257 => { Accept => 'text/plain' } )
259 ->content_like( qr{100\s+_aMilan Kundera} )
260 ->content_like( qr{245\s+_aThe unbearable lightness of being} );
262 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/" . $biblio->biblionumber
263 => { Accept => 'application/marcxml+xml' } )
266 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/" . $biblio->biblionumber
267 => { Accept => 'application/marc-in-json' } )
270 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/" . $biblio->biblionumber
271 => { Accept => 'application/marc' } )
274 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/" . $biblio->biblionumber
275 => { Accept => 'text/plain' } )
277 ->content_is($biblio->metadata->record->as_formatted);
279 subtest 'anonymous access' => sub {
282 $t->get_ok( "/api/v1/public/biblios/" . $biblio->biblionumber
283 => { Accept => 'application/marcxml+xml' } )
286 $t->get_ok( "/api/v1/public/biblios/" . $biblio->biblionumber
287 => { Accept => 'application/marc-in-json' } )
290 $t->get_ok( "/api/v1/public/biblios/" . $biblio->biblionumber
291 => { Accept => 'application/marc' } )
294 $t->get_ok( "/api/v1/public/biblios/" . $biblio->biblionumber
295 => { Accept => 'text/plain' } )
297 ->content_is($biblio->metadata->record->as_formatted);
300 subtest 'marc-in-json encoding tests' => sub {
304 my $title_with_diacritics = "L'insoutenable légèreté de l'être";
306 my $biblio = $builder->build_sample_biblio(
308 title => $title_with_diacritics,
309 author => "Milan Kundera"
313 my $result = $t->get_ok( "/api/v1/public/biblios/" . $biblio->biblionumber
314 => { Accept => 'application/marc-in-json' } )
315 ->status_is(200)->tx->res->body;
317 my $encoded_title = Encode::encode( "UTF-8", $title_with_diacritics );
319 like( $result, qr/\Q$encoded_title/, "The title is not double encoded" );
322 # Hide author in OPAC
323 $subfields = Koha::MarcSubfieldStructures->search({ tagfield => '100' });
324 while ( my $subfield = $subfields->next ) {
325 $subfield->set({ hidden => 1 })->store;
328 Koha::Caches->get_instance()->flush_all;
330 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/" . $biblio->biblionumber
331 => { Accept => 'text/plain' } )
333 ->content_unlike( qr{100\s+_aMilan Kundera} )
334 ->content_like( qr{245\s+_aThe unbearable lightness of being} );
336 subtest 'hidden_in_opac tests' => sub {
340 my $biblio_hidden_in_opac = 1;
342 my $biblio_class = Test::MockModule->new('Koha::Biblio');
343 # force biblio hidden in OPAC
344 $biblio_class->mock( 'hidden_in_opac', sub { return $biblio_hidden_in_opac; } );
346 $t->get_ok( "/api/v1/public/biblios/" . $biblio->biblionumber
347 => { Accept => 'text/plain' } )
348 ->status_is(404, 'hidden_in_opac + anonymous => hidden');
350 my $category_override_hidden_items = 0;
351 my $category_class = Test::MockModule->new('Koha::Patron::Category');
352 $category_class->mock( 'override_hidden_items', sub { return $category_override_hidden_items; } );
353 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/" . $biblio->biblionumber
354 => { Accept => 'text/plain' } )
355 ->status_is(404, "hidden_in_opac + patron whose category doesn't override => hidden");
357 # Make the category override
358 $category_override_hidden_items = 1;
359 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/" . $biblio->biblionumber
360 => { Accept => 'text/plain' } )
361 ->status_is(200, "hidden_in_opac + patron whose category that overrides => displayed");
363 t::lib::Mocks::mock_preference('OpacHiddenItems');
367 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/" . $biblio->biblionumber
368 => { Accept => 'application/marc' } )
370 ->json_is( '/error', 'Object not found.' );
372 $schema->storage->txn_rollback;
375 subtest 'pickup_locations() tests' => sub {
379 $schema->storage->txn_begin;
381 t::lib::Mocks::mock_preference( 'AllowHoldPolicyOverride', 0 );
383 # Small trick to ease testing
384 Koha::Libraries->search->update({ pickup_location => 0 });
386 my $library_1 = $builder->build_object({ class => 'Koha::Libraries', value => { marcorgcode => 'A', pickup_location => 1 } });
387 my $library_2 = $builder->build_object({ class => 'Koha::Libraries', value => { marcorgcode => 'B', pickup_location => 1 } });
388 my $library_3 = $builder->build_object({ class => 'Koha::Libraries', value => { marcorgcode => 'C', pickup_location => 1 } });
390 my $library_1_api = $library_1->to_api();
391 my $library_2_api = $library_2->to_api();
392 my $library_3_api = $library_3->to_api();
394 $library_1_api->{needs_override} = Mojo::JSON->false;
395 $library_2_api->{needs_override} = Mojo::JSON->false;
396 $library_3_api->{needs_override} = Mojo::JSON->true;
398 my $patron = $builder->build_object(
400 class => 'Koha::Patrons',
401 value => { userid => 'tomasito', flags => 0 }
404 my $password = 'thePassword123';
405 $patron->set_password( { password => $password, skip_validation => 1 } );
406 my $userid = $patron->userid;
409 source => 'UserPermission',
411 borrowernumber => $patron->borrowernumber,
413 code => 'place_holds',
418 my $biblio_class = Test::MockModule->new('Koha::Biblio');
422 my ( $self, $params ) = @_;
423 my $mock_patron = $params->{patron};
424 is( $mock_patron->borrowernumber,
425 $patron->borrowernumber, 'Patron passed correctly' );
426 return Koha::Libraries->search(
430 $library_1->branchcode,
431 $library_2->branchcode
435 { # we make sure no surprises in the order of the result
436 order_by => { '-asc' => 'marcorgcode' }
442 my $biblio = $builder->build_sample_biblio;
444 $t->get_ok( "//$userid:$password@/api/v1/biblios/"
446 . "/pickup_locations?patron_id=" . $patron->id )
447 ->json_is( [ $library_1_api, $library_2_api ] );
450 $t->get_ok( "//$userid:$password@/api/v1/biblios/"
452 . '/pickup_locations?'
453 . 'patron_id=' . $patron->id . '&q={"marc_org_code": { "-like": "A%" }}' )
454 ->json_is( [ $library_1_api ] );
456 t::lib::Mocks::mock_preference( 'AllowHoldPolicyOverride', 1 );
458 my $library_4 = $builder->build_object({ class => 'Koha::Libraries', value => { pickup_location => 0, marcorgcode => 'X' } });
459 my $library_5 = $builder->build_object({ class => 'Koha::Libraries', value => { pickup_location => 1, marcorgcode => 'Y' } });
461 my $library_5_api = $library_5->to_api();
462 $library_5_api->{needs_override} = Mojo::JSON->true;
464 $t->get_ok( "//$userid:$password@/api/v1/biblios/"
466 . "/pickup_locations?"
467 . "patron_id=" . $patron->id . "&_order_by=marc_org_code" )
468 ->json_is( [ $library_1_api, $library_2_api, $library_3_api, $library_5_api ] );
470 subtest 'Pagination and AllowHoldPolicyOverride tests' => sub {
474 t::lib::Mocks::mock_preference( 'AllowHoldPolicyOverride', 1 );
476 $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->id . "/pickup_locations?" . "patron_id=" . $patron->id . "&_order_by=marc_org_code" . "&_per_page=1" )
477 ->json_is( [$library_1_api] )
478 ->header_is( 'X-Total-Count', '4', '4 is the count for libraries with pickup_location=1' )
479 ->header_is( 'X-Base-Total-Count', '4', '4 is the count for libraries with pickup_location=1' )
480 ->header_unlike( 'Link', qr|rel="prev"| )
481 ->header_like( 'Link', qr#(_per_page=1.*\&_page=2.*|_page=2.*\&_per_page=1.*)>\; rel="next"# )
482 ->header_like( 'Link', qr#(_per_page=1.*\&_page=1.*|_page=1.*\&_per_page=1).*>\; rel="first"# )
483 ->header_like( 'Link', qr#(_per_page=1.*\&_page=4.*|_page=4.*\&_per_page=1).*>\; rel="last"# );
485 $t->get_ok( "//$userid:$password@/api/v1/biblios/"
487 . "/pickup_locations?"
490 . "&_order_by=marc_org_code"
491 . "&_per_page=1&_page=3" ) # force the needs_override=1 check
492 ->json_is( [$library_3_api] )
493 ->header_is( 'X-Total-Count', '4', '4 is the count for libraries with pickup_location=1' )
494 ->header_is( 'X-Base-Total-Count', '4', '4 is the count for libraries with pickup_location=1' )
495 ->header_like( 'Link', qr#(_per_page=1.*\&_page=2.*|_page=2.*\&_per_page=1.*)>\; rel="prev"# )
496 ->header_like( 'Link', qr#(_per_page=1.*\&_page=4.*|_page=4.*\&_per_page=1.*)>\; rel="next"# )
497 ->header_like( 'Link', qr#(_per_page=1.*\&_page=1.*|_page=1.*\&_per_page=1).*>\; rel="first"# )
498 ->header_like( 'Link', qr#(_per_page=1.*\&_page=4.*|_page=4.*\&_per_page=1).*>\; rel="last"# );
500 t::lib::Mocks::mock_preference( 'AllowHoldPolicyOverride', 0 );
502 $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->id . "/pickup_locations?" . "patron_id=" . $patron->id . "&_order_by=marc_org_code" . "&_per_page=1" )
503 ->json_is( [$library_1_api] )
504 ->header_is( 'X-Total-Count', '2' )
505 ->header_is( 'X-Base-Total-Count', '2' )
506 ->header_unlike( 'Link', qr|rel="prev"| )
507 ->header_like( 'Link', qr#(_per_page=1.*\&_page=2.*|_page=2.*\&_per_page=1.*)>\; rel="next"# )
508 ->header_like( 'Link', qr#(_per_page=1.*\&_page=1.*|_page=1.*\&_per_page=1).*>\; rel="first"# )
509 ->header_like( 'Link', qr#(_per_page=1.*\&_page=2.*|_page=2.*\&_per_page=1).*>\; rel="last"# );
512 my $deleted_patron = $builder->build_object({ class => 'Koha::Patrons' });
513 my $deleted_patron_id = $deleted_patron->id;
514 $deleted_patron->delete;
516 $t->get_ok( "//$userid:$password@/api/v1/biblios/"
518 . "/pickup_locations?"
519 . "patron_id=" . $deleted_patron_id )
521 ->json_is( '/error' => 'Patron not found' );
525 $t->get_ok( "//$userid:$password@/api/v1/biblios/"
527 . "/pickup_locations?"
528 . "patron_id=" . $patron->id )
530 ->json_is( '/error' => 'Biblio not found' );
532 $schema->storage->txn_rollback;
535 subtest 'get_items_public() tests' => sub {
539 $schema->storage->txn_begin;
541 my $override_hidden_items = 0;
543 my $mocked_category = Test::MockModule->new('Koha::Patron::Category');
544 $mocked_category->mock(
545 'override_hidden_items',
547 return $override_hidden_items;
553 my $mocked_context = Test::MockModule->new('C4::Context');
554 $mocked_context->mock(
561 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
562 my $password = 'thePassword123';
563 $patron->set_password( { password => $password, skip_validation => 1 } );
564 $patron->discard_changes;
565 my $userid = $patron->userid;
567 my $biblio = $builder->build_sample_biblio();
570 "//$userid:$password@/api/v1/public/biblios/" . $biblio->id . "/items" )
571 ->status_is(200)->json_is( '' => [], 'No items on the biblio' );
573 my $item_1 = $builder->build_sample_item( { biblionumber => $biblio->id } );
574 my $item_2 = $builder->build_sample_item(
575 { biblionumber => $biblio->id, withdrawn => 1 } );
577 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/"
578 . $biblio->biblionumber
579 . "/items" )->status_is(200)->json_is(
581 $item_1->to_api( { public => 1 } ),
582 $item_2->to_api( { public => 1 } )
584 'The items are returned'
587 $rules = { withdrawn => ['1'] };
589 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/"
590 . $biblio->biblionumber
591 . "/items" )->status_is(200)->json_is(
592 '' => [ $item_1->to_api( { public => 1 } ) ],
593 'The items are returned, hidden one is not returned'
596 $t->get_ok( "/api/v1/public/biblios/"
597 . $biblio->biblionumber
598 . "/items" )->status_is(200)->json_is(
599 '' => [ $item_1->to_api( { public => 1 } ) ],
600 'Anonymous user, items are returned, hidden one is not returned'
604 $override_hidden_items = 1;
606 $t->get_ok( "//$userid:$password@/api/v1/public/biblios/"
607 . $biblio->biblionumber
608 . "/items" )->status_is(200)->json_is(
610 $item_1->to_api( { public => 1 } ),
611 $item_2->to_api( { public => 1 } )
613 'The items are returned, the patron category has an override'
616 $schema->storage->txn_rollback;
619 subtest 'get_checkouts() tests' => sub {
623 $schema->storage->txn_begin;
625 my $patron = $builder->build_object(
627 class => 'Koha::Patrons',
628 value => { flags => 0 }
631 my $password = 'thePassword123';
632 $patron->set_password( { password => $password, skip_validation => 1 } );
633 $patron->discard_changes;
634 my $userid = $patron->userid;
636 my $biblio = $builder->build_sample_biblio();
637 $t->get_ok("//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber . "/checkouts")
640 $patron->flags(1)->store; # circulate permissions
642 $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber . "/checkouts")
644 ->json_is( '' => [], 'No checkouts on the biblio' );
646 my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
647 my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
649 AddIssue( $patron->unblessed, $item_1->barcode );
650 AddIssue( $patron->unblessed, $item_2->barcode );
652 my $ret = $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber . "/checkouts")
656 my $checkout_1 = Koha::Checkouts->find({ itemnumber => $item_1->id });
657 my $checkout_2 = Koha::Checkouts->find({ itemnumber => $item_2->id });
659 is_deeply( $ret, [ $checkout_1->to_api, $checkout_2->to_api ] );
661 AddReturn( $item_1->barcode );
663 $ret = $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber . "/checkouts")
667 is_deeply( $ret, [ $checkout_2->to_api ] );
669 $ret = $t->get_ok( "//$userid:$password@/api/v1/biblios/" . $biblio->biblionumber . "/checkouts?checked_in=1")
673 my $old_checkout_1 = Koha::Old::Checkouts->find( $checkout_1->id );
675 is_deeply( $ret, [ $old_checkout_1->to_api ] );
677 $schema->storage->txn_rollback;
680 subtest 'set_rating() tests' => sub {
684 $schema->storage->txn_begin;
686 my $patron = $builder->build_object(
688 class => 'Koha::Patrons',
689 value => { flags => 0 }
692 my $password = 'thePassword123';
693 $patron->set_password( { password => $password, skip_validation => 1 } );
694 $patron->discard_changes;
695 my $userid = $patron->userid;
697 my $biblio = $builder->build_sample_biblio();
698 $t->post_ok("/api/v1/public/biblios/" . $biblio->biblionumber . "/ratings" => json => { rating => 3 })
701 $t->post_ok("//$userid:$password@/api/v1/public/biblios/" . $biblio->biblionumber . "/ratings" => json => { rating => 3 })
703 ->json_is( '/rating', '3' )
704 ->json_is( '/average', '3' )
705 ->json_is( '/count', '1' );
707 $t->post_ok("//$userid:$password@/api/v1/public/biblios/" . $biblio->biblionumber . "/ratings" => json => { rating => undef })
709 ->json_is( '/rating', undef )
710 ->json_is( '/average', '0' )
711 ->json_is( '/count', '0' );
713 $schema->storage->txn_rollback;