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 => 7;
25 use t::lib::TestBuilder;
31 use Koha::Exceptions::Patron;
32 use Koha::Exceptions::Patron::Attribute;
33 use Koha::Patron::Attributes;
34 use Koha::Patron::Debarments qw/AddDebarment/;
36 my $schema = Koha::Database->new->schema;
37 my $builder = t::lib::TestBuilder->new;
39 my $t = Test::Mojo->new('Koha::REST::V1');
40 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
42 subtest 'list() tests' => sub {
45 $schema->storage->txn_begin;
46 unauthorized_access_tests('GET', undef, undef);
47 $schema->storage->txn_rollback;
49 subtest 'librarian access tests' => sub {
52 $schema->storage->txn_begin;
54 my $librarian = $builder->build_object(
56 class => 'Koha::Patrons',
57 value => { flags => 2**4 } # borrowers flag = 4
60 my $password = 'thePassword123';
61 $librarian->set_password( { password => $password, skip_validation => 1 } );
62 my $userid = $librarian->userid;
64 $t->get_ok("//$userid:$password@/api/v1/patrons")
67 $t->get_ok("//$userid:$password@/api/v1/patrons?cardnumber=" . $librarian->cardnumber)
69 ->json_is('/0/cardnumber' => $librarian->cardnumber);
71 $t->get_ok("//$userid:$password@/api/v1/patrons?q={\"cardnumber\":\"" . $librarian->cardnumber ."\"}")
73 ->json_is('/0/cardnumber' => $librarian->cardnumber);
75 $t->get_ok("//$userid:$password@/api/v1/patrons?address2=" . $librarian->address2)
77 ->json_is('/0/address2' => $librarian->address2);
79 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
80 AddDebarment({ borrowernumber => $patron->borrowernumber });
82 $t->get_ok("//$userid:$password@/api/v1/patrons?restricted=" . Mojo::JSON->true . "&cardnumber=" . $patron->cardnumber )
84 ->json_has('/0/restricted')
85 ->json_is( '/0/restricted' => Mojo::JSON->true )
88 $schema->storage->txn_rollback;
92 subtest 'get() tests' => sub {
95 $schema->storage->txn_begin;
96 unauthorized_access_tests('GET', -1, undef);
97 $schema->storage->txn_rollback;
99 subtest 'librarian access tests' => sub {
102 $schema->storage->txn_begin;
104 my $librarian = $builder->build_object(
106 class => 'Koha::Patrons',
107 value => { flags => 2**4 } # borrowers flag = 4
110 my $password = 'thePassword123';
111 $librarian->set_password( { password => $password, skip_validation => 1 } );
112 my $userid = $librarian->userid;
114 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
116 $t->get_ok("//$userid:$password@/api/v1/patrons/" . $patron->id)
118 ->json_is('/patron_id' => $patron->id)
119 ->json_is('/category_id' => $patron->categorycode )
120 ->json_is('/surname' => $patron->surname)
121 ->json_is('/patron_card_lost' => Mojo::JSON->false );
123 $schema->storage->txn_rollback;
127 subtest 'add() tests' => sub {
130 $schema->storage->txn_begin;
132 my $patron = $builder->build_object( { class => 'Koha::Patrons' } )->to_api;
134 unauthorized_access_tests('POST', undef, $patron);
136 $schema->storage->txn_rollback;
138 subtest 'librarian access tests' => sub {
141 $schema->storage->txn_begin;
143 my $extended_attrs_exception;
146 my $attr = "Let's go";
148 # Mock early, so existing mandatory attributes don't break all the tests
149 my $mocked_patron = Test::MockModule->new('Koha::Patron');
150 $mocked_patron->mock(
151 'extended_attributes',
154 if ($extended_attrs_exception) {
155 if ( $extended_attrs_exception eq 'Koha::Exceptions::Patron::Attribute::NonRepeatable'
156 or $extended_attrs_exception eq 'Koha::Exceptions::Patron::Attribute::UniqueIDConstraint'
159 $extended_attrs_exception->throw(
160 attribute => Koha::Patron::Attribute->new(
161 { code => $code, attribute => $attr }
166 $extended_attrs_exception->throw( type => $type );
173 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
174 my $newpatron = $patron->to_api;
175 # delete RO attributes
176 delete $newpatron->{patron_id};
177 delete $newpatron->{restricted};
178 delete $newpatron->{anonymized};
180 # Create a library just to make sure its ID doesn't exist on the DB
181 my $library_to_delete = $builder->build_object({ class => 'Koha::Libraries' });
182 my $deleted_library_id = $library_to_delete->id;
184 $library_to_delete->delete;
186 my $librarian = $builder->build_object(
188 class => 'Koha::Patrons',
189 value => { flags => 2**4 } # borrowers flag = 4
192 my $password = 'thePassword123';
193 $librarian->set_password( { password => $password, skip_validation => 1 } );
194 my $userid = $librarian->userid;
196 $newpatron->{library_id} = $deleted_library_id;
199 $t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
201 ->json_is('/error' => "Duplicate ID"); }
202 qr/DBD::mysql::st execute failed: Duplicate entry/;
204 $newpatron->{library_id} = $patron->branchcode;
206 # Create a library just to make sure its ID doesn't exist on the DB
207 my $category_to_delete = $builder->build_object({ class => 'Koha::Patron::Categories' });
208 my $deleted_category_id = $category_to_delete->id;
210 $category_to_delete->delete;
212 $newpatron->{category_id} = $deleted_category_id; # Test invalid patron category
214 $t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
216 ->json_is('/error' => "Given category_id does not exist");
217 $newpatron->{category_id} = $patron->categorycode;
219 $newpatron->{falseproperty} = "Non existent property";
221 $t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
224 delete $newpatron->{falseproperty};
226 my $patron_to_delete = $builder->build_object({ class => 'Koha::Patrons' });
227 $newpatron = $patron_to_delete->to_api;
228 # delete RO attributes
229 delete $newpatron->{patron_id};
230 delete $newpatron->{restricted};
231 delete $newpatron->{anonymized};
232 $patron_to_delete->delete;
234 $t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
235 ->status_is(201, 'Patron created successfully')
237 Location => qr|^\/api\/v1\/patrons/\d*|,
240 ->json_has('/patron_id', 'got a patron_id')
241 ->json_is( '/cardnumber' => $newpatron->{ cardnumber })
242 ->json_is( '/surname' => $newpatron->{ surname })
243 ->json_is( '/firstname' => $newpatron->{ firstname });
246 $t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
248 ->json_has( '/error', 'Fails when trying to POST duplicate cardnumber' )
249 ->json_like( '/conflict' => qr/(borrowers\.)?cardnumber/ ); }
250 qr/DBD::mysql::st execute failed: Duplicate entry '(.*?)' for key '(borrowers\.)?cardnumber'/;
252 subtest 'extended_attributes handling tests' => sub {
256 my $patrons_count = Koha::Patrons->search->count;
258 $extended_attrs_exception = 'Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute';
260 "//$userid:$password@/api/v1/patrons" => json => {
261 "firstname" => "Katrina",
262 "surname" => "Fischer",
263 "address" => "Somewhere",
264 "category_id" => "ST",
265 "city" => "Konstanz",
266 "library_id" => "MPL"
269 ->json_is( '/error' =>
270 "Missing mandatory extended attribute (type=$type)" );
272 is( Koha::Patrons->search->count, $patrons_count, 'No patron added' );
274 $extended_attrs_exception = 'Koha::Exceptions::Patron::Attribute::InvalidType';
276 "//$userid:$password@/api/v1/patrons" => json => {
277 "firstname" => "Katrina",
278 "surname" => "Fischer",
279 "address" => "Somewhere",
280 "category_id" => "ST",
281 "city" => "Konstanz",
282 "library_id" => "MPL"
285 ->json_is( '/error' =>
286 "Tried to use an invalid attribute type. type=$type" );
288 is( Koha::Patrons->search->count, $patrons_count, 'No patron added' );
290 $extended_attrs_exception = 'Koha::Exceptions::Patron::Attribute::NonRepeatable';
292 "//$userid:$password@/api/v1/patrons" => json => {
293 "firstname" => "Katrina",
294 "surname" => "Fischer",
295 "address" => "Somewhere",
296 "category_id" => "ST",
297 "city" => "Konstanz",
298 "library_id" => "MPL"
301 ->json_is( '/error' =>
302 "Tried to add more than one non-repeatable attributes. type=$code value=$attr" );
304 is( Koha::Patrons->search->count, $patrons_count, 'No patron added' );
306 $extended_attrs_exception = 'Koha::Exceptions::Patron::Attribute::UniqueIDConstraint';
308 "//$userid:$password@/api/v1/patrons" => json => {
309 "firstname" => "Katrina",
310 "surname" => "Fischer",
311 "address" => "Somewhere",
312 "category_id" => "ST",
313 "city" => "Konstanz",
314 "library_id" => "MPL"
317 ->json_is( '/error' =>
318 "Your action breaks a unique constraint on the attribute. type=$code value=$attr" );
320 is( Koha::Patrons->search->count, $patrons_count, 'No patron added' );
322 $mocked_patron->unmock('extended_attributes');
323 # Temporarily get rid of mandatory attribute types
324 Koha::Patron::Attribute::Types->search({ mandatory => 1 })->delete;
325 # Create a couple attribute attribute types
326 my $repeatable_1 = $builder->build_object(
328 class => 'Koha::Patron::Attribute::Types',
333 category_code => 'ST'
337 my $repeatable_2 = $builder->build_object(
339 class => 'Koha::Patron::Attribute::Types',
344 category_code => 'ST'
349 my $patron_id = $t->post_ok(
350 "//$userid:$password@/api/v1/patrons" => json => {
351 "firstname" => "Katrina",
352 "surname" => "Fischer",
353 "address" => "Somewhere",
354 "category_id" => "ST",
355 "city" => "Konstanz",
356 "library_id" => "MPL",
357 "extended_attributes" => [
358 { type => $repeatable_1->code, value => 'a' },
359 { type => $repeatable_1->code, value => 'b' },
360 { type => $repeatable_1->code, value => 'c' },
361 { type => $repeatable_2->code, value => 'd' },
362 { type => $repeatable_2->code, value => 'e' }
365 )->status_is(201, 'Patron added')->tx->res->json->{patron_id};
366 my $extended_attributes = join( ' ', sort map {$_->attribute} Koha::Patrons->find($patron_id)->extended_attributes->as_list);
367 is( $extended_attributes, 'a b c d e', 'Extended attributes are stored correctly');
370 $schema->storage->txn_rollback;
374 subtest 'update() tests' => sub {
377 $schema->storage->txn_begin;
378 unauthorized_access_tests('PUT', 123, {email => 'nobody@example.com'});
379 $schema->storage->txn_rollback;
381 subtest 'librarian access tests' => sub {
384 $schema->storage->txn_begin;
386 my $authorized_patron = $builder->build_object(
388 class => 'Koha::Patrons',
389 value => { flags => 1 }
392 my $password = 'thePassword123';
393 $authorized_patron->set_password(
394 { password => $password, skip_validation => 1 } );
395 my $userid = $authorized_patron->userid;
397 my $unauthorized_patron = $builder->build_object(
399 class => 'Koha::Patrons',
400 value => { flags => 0 }
403 $unauthorized_patron->set_password( { password => $password, skip_validation => 1 } );
404 my $unauth_userid = $unauthorized_patron->userid;
406 my $patron_1 = $authorized_patron;
407 my $patron_2 = $unauthorized_patron;
408 my $newpatron = $unauthorized_patron->to_api;
409 # delete RO attributes
410 delete $newpatron->{patron_id};
411 delete $newpatron->{restricted};
412 delete $newpatron->{anonymized};
414 $t->put_ok("//$userid:$password@/api/v1/patrons/-1" => json => $newpatron)
416 ->json_has('/error', 'Fails when trying to PUT nonexistent patron');
418 # Create a library just to make sure its ID doesn't exist on the DB
419 my $category_to_delete = $builder->build_object({ class => 'Koha::Patron::Categories' });
420 my $deleted_category_id = $category_to_delete->id;
422 $category_to_delete->delete;
424 # Use an invalid category
425 $newpatron->{category_id} = $deleted_category_id;
427 $t->put_ok("//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron)
429 ->json_is('/error' => "Given category_id does not exist");
431 # Restore the valid category
432 $newpatron->{category_id} = $patron_2->categorycode;
434 # Create a library just to make sure its ID doesn't exist on the DB
435 my $library_to_delete = $builder->build_object({ class => 'Koha::Libraries' });
436 my $deleted_library_id = $library_to_delete->id;
438 $library_to_delete->delete;
440 # Use an invalid library_id
441 $newpatron->{library_id} = $deleted_library_id;
444 $t->put_ok("//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron)
446 ->json_is('/error' => "Given library_id does not exist"); }
447 qr/DBD::mysql::st execute failed: Cannot add or update a child row: a foreign key constraint fails/;
449 # Restore the valid library_id
450 $newpatron->{library_id} = $patron_2->branchcode;
452 # Use an invalid attribute
453 $newpatron->{falseproperty} = "Non existent property";
455 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
457 ->json_is('/errors/0/message' =>
458 'Properties not allowed: falseproperty.');
460 # Get rid of the invalid attribute
461 delete $newpatron->{falseproperty};
463 # Set both cardnumber and userid to already existing values
464 $newpatron->{cardnumber} = $patron_1->cardnumber;
465 $newpatron->{userid} = $patron_1->userid;
468 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
470 ->json_has( '/error', "Fails when trying to update to an existing cardnumber or userid")
471 ->json_like( '/conflict' => qr/(borrowers\.)?cardnumber/ ); }
472 qr/DBD::mysql::st execute failed: Duplicate entry '(.*?)' for key '(borrowers\.)?cardnumber'/;
474 $newpatron->{ cardnumber } = $patron_1->id . $patron_2->id;
475 $newpatron->{ userid } = "user" . $patron_1->id.$patron_2->id;
476 $newpatron->{ surname } = "user" . $patron_1->id.$patron_2->id;
478 my $result = $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
479 ->status_is(200, 'Patron updated successfully');
481 # Put back the RO attributes
482 $newpatron->{patron_id} = $unauthorized_patron->to_api->{patron_id};
483 $newpatron->{restricted} = $unauthorized_patron->to_api->{restricted};
484 $newpatron->{anonymized} = $unauthorized_patron->to_api->{anonymized};
486 my $got = $result->tx->res->json;
487 my $updated_on_got = delete $got->{updated_on};
488 my $updated_on_expected = delete $newpatron->{updated_on};
489 is_deeply($got, $newpatron, 'Returned patron from update matches expected');
490 t::lib::Dates::compare( $updated_on_got, $updated_on_expected, 'updated_on values matched' );
493 is(Koha::Patrons->find( $patron_2->id )->cardnumber,
494 $newpatron->{ cardnumber }, 'Patron is really updated!');
496 my $superlibrarian = $builder->build_object(
498 class => 'Koha::Patrons',
499 value => { flags => 1 }
503 $newpatron->{cardnumber} = $superlibrarian->cardnumber;
504 $newpatron->{userid} = $superlibrarian->userid;
505 $newpatron->{email} = 'nosense@no.no';
506 # delete RO attributes
507 delete $newpatron->{patron_id};
508 delete $newpatron->{restricted};
509 delete $newpatron->{anonymized};
512 $authorized_patron->flags( 2**4 )->store; # borrowers flag = 4
513 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
514 ->status_is(403, "Non-superlibrarian user change of superlibrarian email forbidden")
515 ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
518 $newpatron->{email} = undef;
519 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
520 ->status_is(403, "Non-superlibrarian user change of superlibrarian email to undefined forbidden")
521 ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
523 $newpatron->{email} = $superlibrarian->email;
524 $newpatron->{secondary_email} = 'nonsense@no.no';
527 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
528 ->status_is(403, "Non-superlibrarian user change of superlibrarian secondary_email forbidden")
529 ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
532 $newpatron->{secondary_email} = undef;
533 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
534 ->status_is(403, "Non-superlibrarian user change of superlibrarian secondary_email to undefined forbidden")
535 ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
537 $newpatron->{secondary_email} = $superlibrarian->emailpro;
538 $newpatron->{altaddress_email} = 'nonsense@no.no';
541 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
542 ->status_is(403, "Non-superlibrarian user change of superlibrarian altaddress_email forbidden")
543 ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
546 $newpatron->{altaddress_email} = undef;
547 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
548 ->status_is(403, "Non-superlibrarian user change of superlibrarian altaddress_email to undefined forbidden")
549 ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
551 # update patron without sending email
552 delete $newpatron->{email};
553 delete $newpatron->{secondary_email};
554 delete $newpatron->{altaddress_email};
555 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
556 ->status_is(200, "Non-superlibrarian user can edit superlibrarian successfully if not changing email");
557 # ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
559 $schema->storage->txn_rollback;
563 subtest 'delete() tests' => sub {
566 $schema->storage->txn_begin;
567 unauthorized_access_tests('DELETE', 123, undef);
568 $schema->storage->txn_rollback;
570 subtest 'librarian access test' => sub {
573 $schema->storage->txn_begin;
575 my $authorized_patron = $builder->build_object(
577 class => 'Koha::Patrons',
578 value => { flags => 2**4 } # borrowers flag = 4
581 my $password = 'thePassword123';
582 $authorized_patron->set_password(
583 { password => $password, skip_validation => 1 } );
584 my $userid = $authorized_patron->userid;
586 $t->delete_ok("//$userid:$password@/api/v1/patrons/-1")
587 ->status_is(404, 'Patron not found');
589 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
591 t::lib::Mocks::mock_preference('AnonymousPatron', $patron->borrowernumber);
592 $t->delete_ok("//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber)
593 ->status_is(403, 'Anonymous patron cannot be deleted')
594 ->json_is( { error => 'Anonymous patron cannot be deleted' } );
596 t::lib::Mocks::mock_preference('AnonymousPatron', 0); # back to default
597 $t->delete_ok("//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber)
598 ->status_is(204, 'SWAGGER3.2.4')
599 ->content_is('', 'SWAGGER3.3.4');
601 $schema->storage->txn_rollback;
605 subtest 'guarantors_can_see_charges() tests' => sub {
609 t::lib::Mocks::mock_preference( 'RESTPublicAPI', 1 );
610 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
612 $schema->storage->txn_begin;
614 my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { privacy_guarantor_fines => 0 } });
615 my $password = 'thePassword123';
616 $patron->set_password({ password => $password, skip_validation => 1 });
617 my $userid = $patron->userid;
618 my $patron_id = $patron->borrowernumber;
620 t::lib::Mocks::mock_preference( 'AllowPatronToSetFinesVisibilityForGuarantor', 0 );
622 $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_charges" => json => { allowed => Mojo::JSON->true } )
624 ->json_is( '/error', 'The current configuration doesn\'t allow the requested action.' );
626 t::lib::Mocks::mock_preference( 'AllowPatronToSetFinesVisibilityForGuarantor', 1 );
628 $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_charges" => json => { allowed => Mojo::JSON->true } )
632 ok( $patron->discard_changes->privacy_guarantor_fines, 'privacy_guarantor_fines has been set correctly' );
634 $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_charges" => json => { allowed => Mojo::JSON->false } )
638 ok( !$patron->discard_changes->privacy_guarantor_fines, 'privacy_guarantor_fines has been set correctly' );
640 $schema->storage->txn_rollback;
643 subtest 'guarantors_can_see_checkouts() tests' => sub {
647 t::lib::Mocks::mock_preference( 'RESTPublicAPI', 1 );
648 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
650 $schema->storage->txn_begin;
652 my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { privacy_guarantor_checkouts => 0 } });
653 my $password = 'thePassword123';
654 $patron->set_password({ password => $password, skip_validation => 1 });
655 my $userid = $patron->userid;
656 my $patron_id = $patron->borrowernumber;
658 t::lib::Mocks::mock_preference( 'AllowPatronToSetCheckoutsVisibilityForGuarantor', 0 );
660 $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_checkouts" => json => { allowed => Mojo::JSON->true } )
662 ->json_is( '/error', 'The current configuration doesn\'t allow the requested action.' );
664 t::lib::Mocks::mock_preference( 'AllowPatronToSetCheckoutsVisibilityForGuarantor', 1 );
666 $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_checkouts" => json => { allowed => Mojo::JSON->true } )
670 ok( $patron->discard_changes->privacy_guarantor_checkouts, 'privacy_guarantor_checkouts has been set correctly' );
672 $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_checkouts" => json => { allowed => Mojo::JSON->false } )
676 ok( !$patron->discard_changes->privacy_guarantor_checkouts, 'privacy_guarantor_checkouts has been set correctly' );
678 $schema->storage->txn_rollback;
681 # Centralized tests for 401s and 403s assuming the endpoint requires
682 # borrowers flag for access
683 sub unauthorized_access_tests {
684 my ($verb, $patron_id, $json) = @_;
686 my $endpoint = '/api/v1/patrons';
687 $endpoint .= ($patron_id) ? "/$patron_id" : '';
689 subtest 'unauthorized access tests' => sub {
692 my $verb_ok = lc($verb) . '_ok';
694 $t->$verb_ok($endpoint => json => $json)
697 my $unauthorized_patron = $builder->build_object(
699 class => 'Koha::Patrons',
700 value => { flags => 0 }
703 my $password = "thePassword123!";
704 $unauthorized_patron->set_password(
705 { password => $password, skip_validation => 1 } );
706 my $unauth_userid = $unauthorized_patron->userid;
708 $t->$verb_ok( "//$unauth_userid:$password\@$endpoint" => json => $json )
710 ->json_has('/required_permissions');