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::DateUtils qw(dt_from_string output_pref);
32 use Koha::Exceptions::Patron;
33 use Koha::Exceptions::Patron::Attribute;
34 use Koha::Old::Patrons;
35 use Koha::Patron::Attributes;
36 use Koha::Patron::Debarments qw( AddDebarment );
38 use JSON qw(encode_json);
40 my $schema = Koha::Database->new->schema;
41 my $builder = t::lib::TestBuilder->new;
43 my $t = Test::Mojo->new('Koha::REST::V1');
44 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
46 subtest 'list() tests' => sub {
49 $schema->storage->txn_begin;
50 unauthorized_access_tests('GET', undef, undef);
51 $schema->storage->txn_rollback;
53 subtest 'librarian access tests' => sub {
56 $schema->storage->txn_begin;
58 my $librarian = $builder->build_object(
60 class => 'Koha::Patrons',
61 value => { flags => 2**4 } # borrowers flag = 4
64 my $password = 'thePassword123';
65 $librarian->set_password( { password => $password, skip_validation => 1 } );
66 my $userid = $librarian->userid;
68 $t->get_ok("//$userid:$password@/api/v1/patrons")
71 $t->get_ok("//$userid:$password@/api/v1/patrons?cardnumber=" . $librarian->cardnumber)
73 ->json_is('/0/cardnumber' => $librarian->cardnumber);
75 $t->get_ok("//$userid:$password@/api/v1/patrons?q={\"cardnumber\":\"" . $librarian->cardnumber ."\"}")
77 ->json_is('/0/cardnumber' => $librarian->cardnumber);
79 $t->get_ok("//$userid:$password@/api/v1/patrons?address2=" . $librarian->address2)
81 ->json_is('/0/address2' => $librarian->address2);
83 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
84 AddDebarment({ borrowernumber => $patron->borrowernumber });
86 $t->get_ok("//$userid:$password@/api/v1/patrons?restricted=" . Mojo::JSON->true . "&cardnumber=" . $patron->cardnumber )
88 ->json_has('/0/restricted')
89 ->json_is( '/0/restricted' => Mojo::JSON->true )
92 subtest 'searching date and date-time fields' => sub {
96 my $date_of_birth = '1980-06-18';
97 my $last_seen = '2021-06-25 14:05:35';
99 my $patron = $builder->build_object(
101 class => 'Koha::Patrons',
103 dateofbirth => $date_of_birth,
104 lastseen => $last_seen,
109 my $last_seen_rfc3339 = $last_seen . "z";
111 $t->get_ok("//$userid:$password@/api/v1/patrons?date_of_birth=" . $date_of_birth . "&cardnumber=" . $patron->cardnumber)
113 ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date works' );
115 $t->get_ok("//$userid:$password@/api/v1/patrons?last_seen=" . $last_seen_rfc3339 . "&cardnumber=" . $patron->cardnumber)
117 ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date-time works' );
121 date_of_birth => $date_of_birth,
122 cardnumber => $patron->cardnumber,
126 $t->get_ok("//$userid:$password@/api/v1/patrons?q=$q")
128 ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date works' );
132 last_seen => $last_seen_rfc3339,
133 cardnumber => $patron->cardnumber,
137 $t->get_ok("//$userid:$password@/api/v1/patrons?q=$q")
139 ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date-time works' );
142 $schema->storage->txn_rollback;
146 subtest 'get() tests' => sub {
149 $schema->storage->txn_begin;
150 unauthorized_access_tests('GET', -1, undef);
151 $schema->storage->txn_rollback;
153 subtest 'librarian access tests' => sub {
156 $schema->storage->txn_begin;
158 my $librarian = $builder->build_object(
160 class => 'Koha::Patrons',
161 value => { flags => 2**4 } # borrowers flag = 4
164 my $password = 'thePassword123';
165 $librarian->set_password( { password => $password, skip_validation => 1 } );
166 my $userid = $librarian->userid;
168 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
170 $t->get_ok("//$userid:$password@/api/v1/patrons/" . $patron->id)
172 ->json_is('/patron_id' => $patron->id)
173 ->json_is('/category_id' => $patron->categorycode )
174 ->json_is('/surname' => $patron->surname)
175 ->json_is('/patron_card_lost' => Mojo::JSON->false );
177 $schema->storage->txn_rollback;
181 subtest 'add() tests' => sub {
184 $schema->storage->txn_begin;
186 my $patron = $builder->build_object( { class => 'Koha::Patrons' } )->to_api;
188 unauthorized_access_tests('POST', undef, $patron);
190 $schema->storage->txn_rollback;
192 subtest 'librarian access tests' => sub {
195 $schema->storage->txn_begin;
197 my $extended_attrs_exception;
200 my $attr = "Let's go";
202 # Mock early, so existing mandatory attributes don't break all the tests
203 my $mocked_patron = Test::MockModule->new('Koha::Patron');
204 $mocked_patron->mock(
205 'extended_attributes',
208 if ($extended_attrs_exception) {
209 if ( $extended_attrs_exception eq 'Koha::Exceptions::Patron::Attribute::NonRepeatable'
210 or $extended_attrs_exception eq 'Koha::Exceptions::Patron::Attribute::UniqueIDConstraint'
213 $extended_attrs_exception->throw(
214 attribute => Koha::Patron::Attribute->new(
215 { code => $code, attribute => $attr }
220 $extended_attrs_exception->throw( type => $type );
227 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
228 my $newpatron = $patron->to_api;
229 # delete RO attributes
230 delete $newpatron->{patron_id};
231 delete $newpatron->{restricted};
232 delete $newpatron->{anonymized};
234 # Create a library just to make sure its ID doesn't exist on the DB
235 my $library_to_delete = $builder->build_object({ class => 'Koha::Libraries' });
236 my $deleted_library_id = $library_to_delete->id;
238 $library_to_delete->delete;
240 my $librarian = $builder->build_object(
242 class => 'Koha::Patrons',
243 value => { flags => 2**4 } # borrowers flag = 4
246 my $password = 'thePassword123';
247 $librarian->set_password( { password => $password, skip_validation => 1 } );
248 my $userid = $librarian->userid;
250 $newpatron->{library_id} = $deleted_library_id;
253 $t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
255 ->json_is('/error' => "Duplicate ID"); }
256 qr/DBD::mysql::st execute failed: Duplicate entry/;
258 $newpatron->{library_id} = $patron->branchcode;
260 # Create a library just to make sure its ID doesn't exist on the DB
261 my $category_to_delete = $builder->build_object({ class => 'Koha::Patron::Categories' });
262 my $deleted_category_id = $category_to_delete->id;
264 $category_to_delete->delete;
266 $newpatron->{category_id} = $deleted_category_id; # Test invalid patron category
268 $t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
270 ->json_is('/error' => "Given category_id does not exist");
271 $newpatron->{category_id} = $patron->categorycode;
273 $newpatron->{falseproperty} = "Non existent property";
275 $t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
278 delete $newpatron->{falseproperty};
280 my $patron_to_delete = $builder->build_object({ class => 'Koha::Patrons' });
281 $newpatron = $patron_to_delete->to_api;
282 # delete RO attributes
283 delete $newpatron->{patron_id};
284 delete $newpatron->{restricted};
285 delete $newpatron->{anonymized};
286 $patron_to_delete->delete;
289 $newpatron->{date_of_birth} = '1980-06-18';
290 # Set a date-time field
291 $newpatron->{last_seen} = output_pref({ dt => dt_from_string->add( days => -1 ), dateformat => 'rfc3339' });
293 $t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
294 ->status_is(201, 'Patron created successfully')
296 Location => qr|^\/api\/v1\/patrons/\d*|,
299 ->json_has('/patron_id', 'got a patron_id')
300 ->json_is( '/cardnumber' => $newpatron->{ cardnumber })
301 ->json_is( '/surname' => $newpatron->{ surname })
302 ->json_is( '/firstname' => $newpatron->{ firstname })
303 ->json_is( '/date_of_birth' => $newpatron->{ date_of_birth }, 'Date field set (Bug 28585)' )
304 ->json_is( '/last_seen' => $newpatron->{ last_seen }, 'Date-time field set (Bug 28585)' );
307 $t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
309 ->json_has( '/error', 'Fails when trying to POST duplicate cardnumber' )
310 ->json_like( '/conflict' => qr/(borrowers\.)?cardnumber/ ); }
311 qr/DBD::mysql::st execute failed: Duplicate entry '(.*?)' for key '(borrowers\.)?cardnumber'/;
313 subtest 'extended_attributes handling tests' => sub {
317 my $patrons_count = Koha::Patrons->search->count;
319 $extended_attrs_exception = 'Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute';
321 "//$userid:$password@/api/v1/patrons" => json => {
322 "firstname" => "Katrina",
323 "surname" => "Fischer",
324 "address" => "Somewhere",
325 "category_id" => "ST",
326 "city" => "Konstanz",
327 "library_id" => "MPL"
330 ->json_is( '/error' =>
331 "Missing mandatory extended attribute (type=$type)" );
333 is( Koha::Patrons->search->count, $patrons_count, 'No patron added' );
335 $extended_attrs_exception = 'Koha::Exceptions::Patron::Attribute::InvalidType';
337 "//$userid:$password@/api/v1/patrons" => json => {
338 "firstname" => "Katrina",
339 "surname" => "Fischer",
340 "address" => "Somewhere",
341 "category_id" => "ST",
342 "city" => "Konstanz",
343 "library_id" => "MPL"
346 ->json_is( '/error' =>
347 "Tried to use an invalid attribute type. type=$type" );
349 is( Koha::Patrons->search->count, $patrons_count, 'No patron added' );
351 $extended_attrs_exception = 'Koha::Exceptions::Patron::Attribute::NonRepeatable';
353 "//$userid:$password@/api/v1/patrons" => json => {
354 "firstname" => "Katrina",
355 "surname" => "Fischer",
356 "address" => "Somewhere",
357 "category_id" => "ST",
358 "city" => "Konstanz",
359 "library_id" => "MPL"
362 ->json_is( '/error' =>
363 "Tried to add more than one non-repeatable attributes. type=$code value=$attr" );
365 is( Koha::Patrons->search->count, $patrons_count, 'No patron added' );
367 $extended_attrs_exception = 'Koha::Exceptions::Patron::Attribute::UniqueIDConstraint';
369 "//$userid:$password@/api/v1/patrons" => json => {
370 "firstname" => "Katrina",
371 "surname" => "Fischer",
372 "address" => "Somewhere",
373 "category_id" => "ST",
374 "city" => "Konstanz",
375 "library_id" => "MPL"
378 ->json_is( '/error' =>
379 "Your action breaks a unique constraint on the attribute. type=$code value=$attr" );
381 is( Koha::Patrons->search->count, $patrons_count, 'No patron added' );
383 $mocked_patron->unmock('extended_attributes');
384 # Temporarily get rid of mandatory attribute types
385 Koha::Patron::Attribute::Types->search({ mandatory => 1 })->delete;
386 # Create a couple attribute attribute types
387 my $repeatable_1 = $builder->build_object(
389 class => 'Koha::Patron::Attribute::Types',
394 category_code => 'ST'
398 my $repeatable_2 = $builder->build_object(
400 class => 'Koha::Patron::Attribute::Types',
405 category_code => 'ST'
410 my $patron_id = $t->post_ok(
411 "//$userid:$password@/api/v1/patrons" => json => {
412 "firstname" => "Katrina",
413 "surname" => "Fischer",
414 "address" => "Somewhere",
415 "category_id" => "ST",
416 "city" => "Konstanz",
417 "library_id" => "MPL",
418 "extended_attributes" => [
419 { type => $repeatable_1->code, value => 'a' },
420 { type => $repeatable_1->code, value => 'b' },
421 { type => $repeatable_1->code, value => 'c' },
422 { type => $repeatable_2->code, value => 'd' },
423 { type => $repeatable_2->code, value => 'e' }
426 )->status_is(201, 'Patron added')->tx->res->json->{patron_id};
427 my $extended_attributes = join( ' ', sort map {$_->attribute} Koha::Patrons->find($patron_id)->extended_attributes->as_list);
428 is( $extended_attributes, 'a b c d e', 'Extended attributes are stored correctly');
431 $schema->storage->txn_rollback;
435 subtest 'update() tests' => sub {
438 $schema->storage->txn_begin;
439 unauthorized_access_tests('PUT', 123, {email => 'nobody@example.com'});
440 $schema->storage->txn_rollback;
442 subtest 'librarian access tests' => sub {
445 $schema->storage->txn_begin;
447 my $authorized_patron = $builder->build_object(
449 class => 'Koha::Patrons',
450 value => { flags => 1 }
453 my $password = 'thePassword123';
454 $authorized_patron->set_password(
455 { password => $password, skip_validation => 1 } );
456 my $userid = $authorized_patron->userid;
458 my $unauthorized_patron = $builder->build_object(
460 class => 'Koha::Patrons',
461 value => { flags => 0 }
464 $unauthorized_patron->set_password( { password => $password, skip_validation => 1 } );
465 my $unauth_userid = $unauthorized_patron->userid;
467 my $patron_1 = $authorized_patron;
468 my $patron_2 = $unauthorized_patron;
469 my $newpatron = $unauthorized_patron->to_api;
470 # delete RO attributes
471 delete $newpatron->{patron_id};
472 delete $newpatron->{restricted};
473 delete $newpatron->{anonymized};
475 $t->put_ok("//$userid:$password@/api/v1/patrons/-1" => json => $newpatron)
477 ->json_has('/error', 'Fails when trying to PUT nonexistent patron');
479 # Create a library just to make sure its ID doesn't exist on the DB
480 my $category_to_delete = $builder->build_object({ class => 'Koha::Patron::Categories' });
481 my $deleted_category_id = $category_to_delete->id;
483 $category_to_delete->delete;
485 # Use an invalid category
486 $newpatron->{category_id} = $deleted_category_id;
488 $t->put_ok("//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron)
490 ->json_is('/error' => "Given category_id does not exist");
492 # Restore the valid category
493 $newpatron->{category_id} = $patron_2->categorycode;
495 # Create a library just to make sure its ID doesn't exist on the DB
496 my $library_to_delete = $builder->build_object({ class => 'Koha::Libraries' });
497 my $deleted_library_id = $library_to_delete->id;
499 $library_to_delete->delete;
501 # Use an invalid library_id
502 $newpatron->{library_id} = $deleted_library_id;
505 $t->put_ok("//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron)
507 ->json_is('/error' => "Given library_id does not exist"); }
508 qr/DBD::mysql::st execute failed: Cannot add or update a child row: a foreign key constraint fails/;
510 # Restore the valid library_id
511 $newpatron->{library_id} = $patron_2->branchcode;
513 # Use an invalid attribute
514 $newpatron->{falseproperty} = "Non existent property";
516 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
518 ->json_is('/errors/0/message' =>
519 'Properties not allowed: falseproperty.');
521 # Get rid of the invalid attribute
522 delete $newpatron->{falseproperty};
524 # Set both cardnumber and userid to already existing values
525 $newpatron->{cardnumber} = $patron_1->cardnumber;
526 $newpatron->{userid} = $patron_1->userid;
529 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
531 ->json_has( '/error', "Fails when trying to update to an existing cardnumber or userid")
532 ->json_like( '/conflict' => qr/(borrowers\.)?cardnumber/ ); }
533 qr/DBD::mysql::st execute failed: Duplicate entry '(.*?)' for key '(borrowers\.)?cardnumber'/;
535 $newpatron->{ cardnumber } = $patron_1->id . $patron_2->id;
536 $newpatron->{ userid } = "user" . $patron_1->id.$patron_2->id;
537 $newpatron->{ surname } = "user" . $patron_1->id.$patron_2->id;
539 ## Trying to set to null on specially handled cases
540 # Special case: a date
541 $newpatron->{ date_of_birth } = undef;
542 # Special case: a date-time
543 $newpatron->{ last_seen } = undef;
545 my $result = $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
546 ->status_is(200, 'Patron updated successfully');
548 # Put back the RO attributes
549 $newpatron->{patron_id} = $unauthorized_patron->to_api->{patron_id};
550 $newpatron->{restricted} = $unauthorized_patron->to_api->{restricted};
551 $newpatron->{anonymized} = $unauthorized_patron->to_api->{anonymized};
553 my $got = $result->tx->res->json;
554 my $updated_on_got = delete $got->{updated_on};
555 my $updated_on_expected = delete $newpatron->{updated_on};
556 is_deeply($got, $newpatron, 'Returned patron from update matches expected');
557 t::lib::Dates::compare( $updated_on_got, $updated_on_expected, 'updated_on values matched' );
559 is(Koha::Patrons->find( $patron_2->id )->cardnumber,
560 $newpatron->{ cardnumber }, 'Patron is really updated!');
562 my $superlibrarian = $builder->build_object(
564 class => 'Koha::Patrons',
565 value => { flags => 1 }
569 $newpatron->{cardnumber} = $superlibrarian->cardnumber;
570 $newpatron->{userid} = $superlibrarian->userid;
571 $newpatron->{email} = 'nosense@no.no';
572 # delete RO attributes
573 delete $newpatron->{patron_id};
574 delete $newpatron->{restricted};
575 delete $newpatron->{anonymized};
578 $authorized_patron->flags( 2**4 )->store; # borrowers flag = 4
579 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
580 ->status_is(403, "Non-superlibrarian user change of superlibrarian email forbidden")
581 ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
584 $newpatron->{email} = undef;
585 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
586 ->status_is(403, "Non-superlibrarian user change of superlibrarian email to undefined forbidden")
587 ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
589 $newpatron->{email} = $superlibrarian->email;
590 $newpatron->{secondary_email} = 'nonsense@no.no';
593 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
594 ->status_is(403, "Non-superlibrarian user change of superlibrarian secondary_email forbidden")
595 ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
598 $newpatron->{secondary_email} = undef;
599 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
600 ->status_is(403, "Non-superlibrarian user change of superlibrarian secondary_email to undefined forbidden")
601 ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
603 $newpatron->{secondary_email} = $superlibrarian->emailpro;
604 $newpatron->{altaddress_email} = 'nonsense@no.no';
607 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
608 ->status_is(403, "Non-superlibrarian user change of superlibrarian altaddress_email forbidden")
609 ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
612 $newpatron->{altaddress_email} = undef;
613 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
614 ->status_is(403, "Non-superlibrarian user change of superlibrarian altaddress_email to undefined forbidden")
615 ->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
617 # update patron without sending email
618 delete $newpatron->{email};
619 delete $newpatron->{secondary_email};
620 delete $newpatron->{altaddress_email};
623 $newpatron->{date_of_birth} = '1980-06-18';
624 # Set a date-time field
625 $newpatron->{last_seen} = output_pref({ dt => dt_from_string->add( days => -1 ), dateformat => 'rfc3339' });
627 $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $superlibrarian->borrowernumber => json => $newpatron )
628 ->status_is(200, "Non-superlibrarian user can edit superlibrarian successfully if not changing email")
629 ->json_is( '/date_of_birth' => $newpatron->{ date_of_birth }, 'Date field set (Bug 28585)' )
630 ->json_is( '/last_seen' => $newpatron->{ last_seen }, 'Date-time field set (Bug 28585)' );
632 $schema->storage->txn_rollback;
636 subtest 'delete() tests' => sub {
639 $schema->storage->txn_begin;
640 unauthorized_access_tests('DELETE', 123, undef);
641 $schema->storage->txn_rollback;
643 subtest 'librarian access test' => sub {
646 $schema->storage->txn_begin;
648 my $authorized_patron = $builder->build_object(
650 class => 'Koha::Patrons',
651 value => { flags => 2**4 } # borrowers flag = 4
654 my $password = 'thePassword123';
655 $authorized_patron->set_password(
656 { password => $password, skip_validation => 1 } );
657 my $userid = $authorized_patron->userid;
659 $t->delete_ok("//$userid:$password@/api/v1/patrons/-1")
660 ->status_is(404, 'Patron not found');
662 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
664 t::lib::Mocks::mock_preference('AnonymousPatron', $patron->borrowernumber);
665 $t->delete_ok("//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber)
666 ->status_is(403, 'Anonymous patron cannot be deleted')
667 ->json_is( { error => 'Anonymous patron cannot be deleted' } );
668 t::lib::Mocks::mock_preference('AnonymousPatron', 0); # back to default
670 t::lib::Mocks::mock_preference( 'borrowerRelationship', 'parent' );
672 my $checkout = $builder->build_object(
674 class => 'Koha::Checkouts',
675 value => { borrowernumber => $patron->borrowernumber }
678 my $debit = $patron->account->add_debit({ amount => 10, interface => 'intranet', type => 'MANUAL' });
679 my $guarantee = $builder->build_object({ class => 'Koha::Patrons' });
681 $guarantee->add_guarantor({ guarantor_id => $patron->id, relationship => 'parent' });
683 $t->delete_ok("//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber)
684 ->status_is(409, 'Patron with checkouts cannot be deleted')
685 ->json_is( { error => 'Pending checkouts prevent deletion' } );
687 # Make sure it has no pending checkouts
690 $t->delete_ok("//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber)
691 ->status_is(409, 'Patron with debt cannot be deleted')
692 ->json_is( { error => 'Pending debts prevent deletion' } );
694 # Make sure it has no debt
695 $patron->account->pay({ amount => 10, debits => [ $debit ] });
697 $t->delete_ok("//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber)
698 ->status_is(409, 'Patron with guarantees cannot be deleted')
699 ->json_is( { error => 'Patron is a guarantor and it prevents deletion' } );
702 $patron->guarantee_relationships->delete;
704 $t->delete_ok("//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber)
705 ->status_is(204, 'SWAGGER3.2.4')
706 ->content_is('', 'SWAGGER3.3.4');
708 my $deleted_patrons = Koha::Old::Patrons->search({ borrowernumber => $patron->borrowernumber });
709 is( $deleted_patrons->count, 1, 'The patron has been moved to the vault' );
711 $schema->storage->txn_rollback;
715 subtest 'guarantors_can_see_charges() tests' => sub {
719 t::lib::Mocks::mock_preference( 'RESTPublicAPI', 1 );
720 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
722 $schema->storage->txn_begin;
724 my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { privacy_guarantor_fines => 0 } });
725 my $password = 'thePassword123';
726 $patron->set_password({ password => $password, skip_validation => 1 });
727 my $userid = $patron->userid;
728 my $patron_id = $patron->borrowernumber;
730 t::lib::Mocks::mock_preference( 'AllowPatronToSetFinesVisibilityForGuarantor', 0 );
732 $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_charges" => json => { allowed => Mojo::JSON->true } )
734 ->json_is( '/error', 'The current configuration doesn\'t allow the requested action.' );
736 t::lib::Mocks::mock_preference( 'AllowPatronToSetFinesVisibilityForGuarantor', 1 );
738 $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_charges" => json => { allowed => Mojo::JSON->true } )
742 ok( $patron->discard_changes->privacy_guarantor_fines, 'privacy_guarantor_fines has been set correctly' );
744 $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_charges" => json => { allowed => Mojo::JSON->false } )
748 ok( !$patron->discard_changes->privacy_guarantor_fines, 'privacy_guarantor_fines has been set correctly' );
750 $schema->storage->txn_rollback;
753 subtest 'guarantors_can_see_checkouts() tests' => sub {
757 t::lib::Mocks::mock_preference( 'RESTPublicAPI', 1 );
758 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
760 $schema->storage->txn_begin;
762 my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { privacy_guarantor_checkouts => 0 } });
763 my $password = 'thePassword123';
764 $patron->set_password({ password => $password, skip_validation => 1 });
765 my $userid = $patron->userid;
766 my $patron_id = $patron->borrowernumber;
768 t::lib::Mocks::mock_preference( 'AllowPatronToSetCheckoutsVisibilityForGuarantor', 0 );
770 $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_checkouts" => json => { allowed => Mojo::JSON->true } )
772 ->json_is( '/error', 'The current configuration doesn\'t allow the requested action.' );
774 t::lib::Mocks::mock_preference( 'AllowPatronToSetCheckoutsVisibilityForGuarantor', 1 );
776 $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_checkouts" => json => { allowed => Mojo::JSON->true } )
780 ok( $patron->discard_changes->privacy_guarantor_checkouts, 'privacy_guarantor_checkouts has been set correctly' );
782 $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_checkouts" => json => { allowed => Mojo::JSON->false } )
786 ok( !$patron->discard_changes->privacy_guarantor_checkouts, 'privacy_guarantor_checkouts has been set correctly' );
788 $schema->storage->txn_rollback;
791 # Centralized tests for 401s and 403s assuming the endpoint requires
792 # borrowers flag for access
793 sub unauthorized_access_tests {
794 my ($verb, $patron_id, $json) = @_;
796 my $endpoint = '/api/v1/patrons';
797 $endpoint .= ($patron_id) ? "/$patron_id" : '';
799 subtest 'unauthorized access tests' => sub {
802 my $verb_ok = lc($verb) . '_ok';
804 $t->$verb_ok($endpoint => json => $json)
807 my $unauthorized_patron = $builder->build_object(
809 class => 'Koha::Patrons',
810 value => { flags => 0 }
813 my $password = "thePassword123!";
814 $unauthorized_patron->set_password(
815 { password => $password, skip_validation => 1 } );
816 my $unauth_userid = $unauthorized_patron->userid;
818 $t->$verb_ok( "//$unauth_userid:$password\@$endpoint" => json => $json )
820 ->json_has('/required_permissions');