Bugzilla – Attachment 193764 Details for
Bug 26355
Allow patrons to self-renew through the OPAC
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 26355: Add unit tests
Bug-26355-Add-unit-tests.patch (text/plain), 45.59 KB, created by
Matt Blenkinsop
on 2026-02-24 13:43:46 UTC
(
hide
)
Description:
Bug 26355: Add unit tests
Filename:
MIME Type:
Creator:
Matt Blenkinsop
Created:
2026-02-24 13:43:46 UTC
Size:
45.59 KB
patch
obsolete
>From 330957aedebc63cb5640c5160f49b2e97da1ad47 Mon Sep 17 00:00:00 2001 >From: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk> >Date: Thu, 19 Feb 2026 13:34:12 +0000 >Subject: [PATCH] Bug 26355: Add unit tests > >--- > Koha/REST/V1/Patrons/SelfRenewal.pm | 1 + > t/db_dependent/Koha/Patron.t | 146 +++++++++++++- > t/db_dependent/api/v1/patron_categories.t | 82 ++++++-- > t/db_dependent/api/v1/patrons.t | 180 ++++++----------- > t/db_dependent/api/v1/patrons_self_renewal.t | 198 +++++++++++++++++++ > 5 files changed, 477 insertions(+), 130 deletions(-) > create mode 100755 t/db_dependent/api/v1/patrons_self_renewal.t > >diff --git a/Koha/REST/V1/Patrons/SelfRenewal.pm b/Koha/REST/V1/Patrons/SelfRenewal.pm >index 00f3a66cf48..646f0be9ce8 100644 >--- a/Koha/REST/V1/Patrons/SelfRenewal.pm >+++ b/Koha/REST/V1/Patrons/SelfRenewal.pm >@@ -121,6 +121,7 @@ sub submit { > $response->{confirmation_sent} = 1 if $result->{sent}; > } > >+ $c->res->headers->location( $c->req->url->to_string ); > return $c->render( > status => 201, > openapi => $response >diff --git a/t/db_dependent/Koha/Patron.t b/t/db_dependent/Koha/Patron.t >index 44802ce4ba1..7b7774ac016 100755 >--- a/t/db_dependent/Koha/Patron.t >+++ b/t/db_dependent/Koha/Patron.t >@@ -19,7 +19,7 @@ > > use Modern::Perl; > >-use Test::More tests => 46; >+use Test::More tests => 49; > use Test::NoWarnings; > use Test::Exception; > use Test::Warn; >@@ -721,7 +721,7 @@ subtest 'messaging_preferences() tests' => sub { > > subtest 'to_api() tests' => sub { > >- plan tests => 6; >+ plan tests => 7; > > $schema->storage->txn_begin; > >@@ -759,6 +759,9 @@ subtest 'to_api() tests' => sub { > ok( exists $patron_json->{algo} ); > is( $patron_json->{algo}, 'algo' ); > >+ my $patron_eligible = $patron->to_api( { user => $consumer } ); >+ ok( exists $patron_eligible->{self_renewal_available} ); >+ > $schema->storage->txn_rollback; > }; > >@@ -3562,3 +3565,142 @@ subtest "create_hold_group, hold_groups, visual_hold_group_id tests" => sub { > > $schema->storage->txn_rollback; > }; >+ >+subtest "is_eligible_for_self_renewal" => sub { >+ plan tests => 8; >+ >+ $schema->storage->txn_begin; >+ >+ my $category = $builder->build_object( >+ { >+ class => 'Koha::Patron::Categories', >+ value => { >+ self_renewal_enabled => 0, self_renewal_availability_start => 10, self_renewal_if_expired => 10, >+ self_renewal_fines_block => 10, noissuescharge => 10 >+ } >+ } >+ ); >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { categorycode => $category->categorycode, dateexpiry => dt_from_string(), debarred => undef } >+ } >+ ); >+ >+ is( $patron->is_eligible_for_self_renewal, 0, "Category not enabled" ); >+ >+ $category->self_renewal_enabled(1)->store(); >+ $patron->delete()->store()->discard_changes(); >+ is( $patron->is_eligible_for_self_renewal, 1, "Category now enabled" ); >+ >+ $patron->debarred('2026-01-01')->store; >+ is( $patron->is_eligible_for_self_renewal, 0, "Patron debarred" ); >+ $patron->debarred(undef)->store; >+ $patron->delete()->store()->discard_changes(); >+ >+ # Move expiry date outside the window >+ $patron->dateexpiry( dt_from_string()->add( days => 11 ) )->store; >+ is( $patron->is_eligible_for_self_renewal, 0, "Patron not yet within the expiry window" ); >+ >+ # Shift the expiry window to cover new expiry date >+ $category->self_renewal_availability_start(12)->store(); >+ $patron->delete()->store()->discard_changes(); >+ is( $patron->is_eligible_for_self_renewal, 1, "Patron is back within the expiry window" ); >+ >+ # Shift the date to now already be expired >+ $patron->dateexpiry( dt_from_string()->subtract( days => 11 ) )->store; >+ is( $patron->is_eligible_for_self_renewal, 0, "Patron can only self renew within 10 days of expiry" ); >+ >+ # Expand the expiry window >+ $category->self_renewal_if_expired(12)->store(); >+ $patron->delete()->store()->discard_changes(); >+ is( $patron->is_eligible_for_self_renewal, 1, "Patron is back within the expiry window" ); >+ >+ t::lib::Mocks::mock_preference( 'noissuescharge', 10 ); >+ >+ my $fee1 = $builder->build_object( >+ { >+ class => 'Koha::Account::Lines', >+ value => { >+ borrowernumber => $patron->borrowernumber, >+ amountoutstanding => 11, >+ debit_type_code => 'OVERDUE', >+ } >+ } >+ )->store; >+ is( $patron->is_eligible_for_self_renewal, 0, "Patron is outside the charge limits" ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest "request_modification" => sub { >+ plan tests => 5; >+ >+ $schema->storage->txn_begin; >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ } >+ ); >+ >+ my $modification_data = { firstname => 'Newname', changed_fields => 'firstname' }; >+ t::lib::Mocks::mock_preference( 'OPACPatronDetails', 1 ); >+ t::lib::Mocks::mock_preference( 'AutoApprovePatronProfileSettings', 0 ); >+ >+ $patron->request_modification($modification_data); >+ >+ my @modifications = Koha::Patron::Modifications->search( { borrowernumber => $patron->borrowernumber } )->as_list; >+ is( scalar(@modifications), 1, "New modification has replaced any existing mods" ); >+ >+ my $modification = $modifications[0]; >+ is( $modification->changed_fields, 'firstname', 'Fields correctly set' ); >+ is( $modification->firstname, $modification_data->{firstname}, 'Fields correctly set' ); >+ >+ t::lib::Mocks::mock_preference( 'AutoApprovePatronProfileSettings', 1 ); >+ $patron->request_modification($modification_data); >+ >+ @modifications = Koha::Patron::Modifications->search( { borrowernumber => $patron->borrowernumber } )->as_list; >+ is( scalar(@modifications), 0, "New modification has been approved and deleted" ); >+ is( $patron->discard_changes()->firstname, $modification_data->{firstname}, 'Name updated' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest "create_expiry_notice_parameters" => sub { >+ plan tests => 1; >+ >+ $schema->storage->txn_begin; >+ >+ my $library = $builder->build_object( { class => 'Koha::Libraries' } ); >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { branchcode => $library->branchcode } >+ } >+ ); >+ >+ my $expected_return = { >+ 'letter_params' => { >+ 'borrowernumber' => $patron->borrowernumber, >+ 'branchcode' => $library->branchcode, >+ 'tables' => { >+ 'borrowers' => $patron->borrowernumber, >+ 'branches' => $library->branchcode >+ }, >+ 'module' => 'members', >+ 'letter_code' => 'MEMBERSHIP_RENEWED', >+ 'lang' => $patron->lang >+ }, >+ 'message_name' => 'Patron_Expiry', >+ 'forceprint' => 0 >+ }; >+ >+ my $letter_params = $patron->create_expiry_notice_parameters( >+ { letter_code => 'MEMBERSHIP_RENEWED', forceprint => 0, is_notice_mandatory => 0 } ); >+ >+ is_deeply( $letter_params, $expected_return, 'Letter params generated correctly' ); >+ $schema->storage->txn_rollback; >+}; >diff --git a/t/db_dependent/api/v1/patron_categories.t b/t/db_dependent/api/v1/patron_categories.t >index d383be3a47f..36213024db9 100755 >--- a/t/db_dependent/api/v1/patron_categories.t >+++ b/t/db_dependent/api/v1/patron_categories.t >@@ -18,7 +18,7 @@ > use Modern::Perl; > > use Test::NoWarnings; >-use Test::More tests => 2; >+use Test::More tests => 3; > use Test::Mojo; > > use t::lib::TestBuilder; >@@ -66,11 +66,8 @@ subtest 'list() tests' => sub { > > $t->get_ok("//$userid:$password@/api/v1/patron_categories")->status_is(200); > >- $t->get_ok("//$userid:$password@/api/v1/patron_categories?q={\"me.categorycode\":\"TEST\"}") >- ->status_is(200) >- ->json_has('/0/name') >- ->json_is( '/0/name' => 'Test' ) >- ->json_hasnt('/1'); >+ $t->get_ok("//$userid:$password@/api/v1/patron_categories?q={\"me.categorycode\":\"TEST\"}")->status_is(200) >+ ->json_has('/0/name')->json_is( '/0/name' => 'Test' )->json_hasnt('/1'); > > # Off limits search > >@@ -95,19 +92,80 @@ subtest 'list() tests' => sub { > my $off_limits_userid = $off_limits_librarian->userid; > > $t->get_ok("//$off_limits_userid:$off_limits_password@/api/v1/patron_categories?q={\"me.categorycode\":\"TEST\"}") >- ->status_is(200) >- ->json_hasnt('/0'); >+ ->status_is(200)->json_hasnt('/0'); > > # Off limits librarian category has changed to one within limits > > $off_limits_librarian->branchcode( $library->branchcode )->store; > > $t->get_ok("//$off_limits_userid:$off_limits_password@/api/v1/patron_categories?q={\"me.categorycode\":\"TEST\"}") >- ->status_is(200) >- ->json_has('/0/name') >- ->json_is( [ $category->to_api ] ) >- ->json_hasnt('/1'); >+ ->status_is(200)->json_has('/0/name')->json_is( [ $category->to_api ] )->json_hasnt('/1'); > > $schema->storage->txn_rollback; > > }; >+ >+subtest 'add() tests' => sub { >+ >+ plan tests => 9; >+ >+ $schema->storage->txn_begin; >+ >+ my $library = $builder->build_object( { class => 'Koha::Libraries' } ); >+ >+ my $librarian = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 2**3, categorycode => 'TEST', branchcode => $library->branchcode } # parameters flag = 3 >+ } >+ ); >+ >+ my $password = 'thePassword123'; >+ $librarian->set_password( { password => $password, skip_validation => 1 } ); >+ my $userid = $librarian->userid; >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 0 } >+ } >+ ); >+ >+ $patron->set_password( { password => $password, skip_validation => 1 } ); >+ my $unauth_userid = $patron->userid; >+ >+ my $category = { >+ patron_category_id => 'new', >+ name => "A new category", >+ }; >+ >+ # Unauthorized attempt to write >+ $t->post_ok( "//$unauth_userid:$password@/api/v1/patron_categories" => json => $category )->status_is(403); >+ >+ # Authorized attempt to write invalid data >+ my $category_with_invalid_field = { >+ blah => "Category Blah", >+ name => "Won't work" >+ }; >+ >+ $t->post_ok( "//$userid:$password@/api/v1/patron_categories" => json => $category_with_invalid_field ) >+ ->status_is(400)->json_is( >+ "/errors" => [ >+ { >+ message => "Properties not allowed: blah.", >+ path => "/body" >+ } >+ ] >+ ); >+ >+ # Authorized attempt to write >+ my $patron_category_id = >+ $t->post_ok( "//$userid:$password@/api/v1/patron_categories" => json => $category ) >+ ->status_is( 201, 'REST3.2.1' )->header_like( >+ Location => qr|^\/api\/v1\/patron_categories/\d*|, >+ 'REST3.4.1' >+ )->json_is( '/name' => $category->{name} )->tx->res->json->{patron_category_id}; >+ >+ $schema->storage->txn_rollback; >+}; >+ >diff --git a/t/db_dependent/api/v1/patrons.t b/t/db_dependent/api/v1/patrons.t >index 1ec56c94595..516faa66dc8 100755 >--- a/t/db_dependent/api/v1/patrons.t >+++ b/t/db_dependent/api/v1/patrons.t >@@ -125,16 +125,13 @@ subtest 'list() tests' => sub { > > $t->get_ok("//$userid:$password@/api/v1/patrons")->status_is( 200, 'list_borrowers makes /patrons accessible' ); > >- $t->get_ok( "//$userid:$password@/api/v1/patrons?cardnumber=" . $librarian->cardnumber ) >- ->status_is(200) >+ $t->get_ok( "//$userid:$password@/api/v1/patrons?cardnumber=" . $librarian->cardnumber )->status_is(200) > ->json_is( '/0/cardnumber' => $librarian->cardnumber ); > > $t->get_ok( "//$userid:$password@/api/v1/patrons?q={\"cardnumber\":\"" . $librarian->cardnumber . "\"}" ) >- ->status_is(200) >- ->json_is( '/0/cardnumber' => $librarian->cardnumber ); >+ ->status_is(200)->json_is( '/0/cardnumber' => $librarian->cardnumber ); > >- $t->get_ok( "//$userid:$password@/api/v1/patrons?address2=" . $librarian->address2 ) >- ->status_is(200) >+ $t->get_ok( "//$userid:$password@/api/v1/patrons?address2=" . $librarian->address2 )->status_is(200) > ->json_is( '/0/address2' => $librarian->address2 ); > > subtest 'restricted & expired' => sub { >@@ -148,19 +145,13 @@ subtest 'list() tests' => sub { > $t->get_ok( "//$userid:$password@/api/v1/patrons?restricted=" > . Mojo::JSON->true > . "&cardnumber=" >- . $patron->cardnumber ) >- ->status_is(200) >- ->json_has('/0/restricted') >- ->json_is( '/0/restricted' => Mojo::JSON->true ) >- ->json_has('/0/expired') >- ->json_is( '/0/expired' => Mojo::JSON->false ) >- ->json_hasnt('/1'); >+ . $patron->cardnumber )->status_is(200)->json_has('/0/restricted') >+ ->json_is( '/0/restricted' => Mojo::JSON->true )->json_has('/0/expired') >+ ->json_is( '/0/expired' => Mojo::JSON->false )->json_hasnt('/1'); > > $patron->dateexpiry( dt_from_string->subtract( days => 2 ) )->store; >- $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber ) >- ->status_is(200) >- ->json_has('/expired') >- ->json_is( '/expired' => Mojo::JSON->true ); >+ $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )->status_is(200) >+ ->json_has('/expired')->json_is( '/expired' => Mojo::JSON->true ); > > $t->get_ok( "//$userid:$password@/api/v1/patrons?" > . 'q={"extended_attributes.type":"CODE"}' => { 'x-koha-embed' => 'extended_attributes' } ) >@@ -189,15 +180,13 @@ subtest 'list() tests' => sub { > $t->get_ok( "//$userid:$password@/api/v1/patrons?date_of_birth=" > . $date_of_birth > . "&cardnumber=" >- . $patron->cardnumber ) >- ->status_is(200) >+ . $patron->cardnumber )->status_is(200) > ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date works' ); > > $t->get_ok( "//$userid:$password@/api/v1/patrons?last_seen=" > . $last_seen_rfc3339 > . "&cardnumber=" >- . $patron->cardnumber ) >- ->status_is(200) >+ . $patron->cardnumber )->status_is(200) > ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date-time works' ); > > my $q = encode_json( >@@ -207,8 +196,7 @@ subtest 'list() tests' => sub { > } > ); > >- $t->get_ok("//$userid:$password@/api/v1/patrons?q=$q") >- ->status_is(200) >+ $t->get_ok("//$userid:$password@/api/v1/patrons?q=$q")->status_is(200) > ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date works' ); > > $q = encode_json( >@@ -218,8 +206,7 @@ subtest 'list() tests' => sub { > } > ); > >- $t->get_ok("//$userid:$password@/api/v1/patrons?q=$q") >- ->status_is(200) >+ $t->get_ok("//$userid:$password@/api/v1/patrons?q=$q")->status_is(200) > ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date-time works' ); > }; > >@@ -263,20 +250,16 @@ subtest 'list() tests' => sub { > my $userid = $librarian->userid; > > $t->get_ok( "//$userid:$password@/api/v1/patrons?_order_by=patron_id&q=" >- . encode_json( { library_id => [ $library_1->id, $library_2->id ] } ) ) >- ->status_is(200) >- ->json_is( '/0/patron_id' => $patron_1->id ) >- ->json_is( '/1/patron_id' => $patron_2->id ) >+ . encode_json( { library_id => [ $library_1->id, $library_2->id ] } ) )->status_is(200) >+ ->json_is( '/0/patron_id' => $patron_1->id )->json_is( '/1/patron_id' => $patron_2->id ) > ->json_is( '/2/patron_id' => $patron_3->id ); > > @libraries_where_can_see_patrons = ( $library_2->id ); > > my $res = > $t->get_ok( "//$userid:$password@/api/v1/patrons?_order_by=patron_id&q=" >- . encode_json( { library_id => [ $library_1->id, $library_2->id ] } ) ) >- ->status_is(200) >- ->json_is( '/0/patron_id' => $patron_3->id, 'Returns the only allowed patron' ) >- ->tx->res->json; >+ . encode_json( { library_id => [ $library_1->id, $library_2->id ] } ) )->status_is(200) >+ ->json_is( '/0/patron_id' => $patron_3->id, 'Returns the only allowed patron' )->tx->res->json; > > is( scalar @{$res}, 1, 'Only one patron returned' ); > >@@ -339,10 +322,8 @@ subtest 'get() tests' => sub { > > $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->id ) > ->status_is( 200, 'list_borrowers permission makes patron visible' ) >- ->json_is( '/patron_id' => $patron->id ) >- ->json_is( '/category_id' => $patron->categorycode ) >- ->json_is( '/surname' => $patron->surname ) >- ->json_is( '/patron_card_lost' => Mojo::JSON->false ); >+ ->json_is( '/patron_id' => $patron->id )->json_is( '/category_id' => $patron->categorycode ) >+ ->json_is( '/surname' => $patron->surname )->json_is( '/patron_card_lost' => Mojo::JSON->false ); > > $schema->storage->txn_rollback; > }; >@@ -384,22 +365,18 @@ subtest 'get() tests' => sub { > $librarian->set_password( { password => $password, skip_validation => 1 } ); > my $userid = $librarian->userid; > >- $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_1->id ) >- ->status_is(200) >+ $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_1->id )->status_is(200) > ->json_is( '/patron_id' => $patron_1->id ); > >- $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->id ) >- ->status_is(200) >+ $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->id )->status_is(200) > ->json_is( '/patron_id' => $patron_2->id ); > > @libraries_where_can_see_patrons = ( $library_1->id ); > >- $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_1->id ) >- ->status_is(200) >+ $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_1->id )->status_is(200) > ->json_is( '/patron_id' => $patron_1->id ); > >- $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->id ) >- ->status_is(404) >+ $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->id )->status_is(404) > ->json_is( '/error' => 'Patron not found' ); > > $schema->storage->txn_rollback; >@@ -481,14 +458,14 @@ subtest 'add() tests' => sub { > delete $newpatron->{restricted}; > delete $newpatron->{expired}; > delete $newpatron->{anonymized}; >+ delete $newpatron->{self_renewal_available}; > > my $password = 'thePassword123'; > $librarian->set_password( { password => $password, skip_validation => 1 } ); > my $userid = $librarian->userid; > t::lib::Mocks::mock_preference( 'PatronDuplicateMatchingAddFields', 'firstname|surname|dateofbirth' ); > >- $t->post_ok( "//$userid:$password@/api/v1/patrons" => json => $newpatron ) >- ->status_is(409) >+ $t->post_ok( "//$userid:$password@/api/v1/patrons" => json => $newpatron )->status_is(409) > ->json_is( '/error' => "A patron record matching these details already exists" ); > > # Create a library just to make sure its ID doesn't exist on the DB >@@ -502,8 +479,7 @@ subtest 'add() tests' => sub { > > # Test duplicate userid constraint > $t->post_ok( "//$userid:$password@/api/v1/patrons" => { 'x-confirm-not-duplicate' => 1 } => json => $newpatron ) >- ->status_is(400) >- ->json_is( '/error' => "Problem with " . $newpatron->{userid} ); >+ ->status_is(400)->json_is( '/error' => "Problem with " . $newpatron->{userid} ); > > $newpatron->{library_id} = $patron->branchcode; > >@@ -512,8 +488,7 @@ subtest 'add() tests' => sub { > warning_like { > $t->post_ok( > "//$userid:$password@/api/v1/patrons" => { 'x-confirm-not-duplicate' => 1 } => json => $newpatron ) >- ->status_is(409) >- ->json_has( '/error', 'Fails when trying to POST duplicate cardnumber' ) >+ ->status_is(409)->json_has( '/error', 'Fails when trying to POST duplicate cardnumber' ) > ->json_like( '/conflict' => qr/(borrowers\.)?cardnumber/ ); > } > qr/DBD::mysql::st execute failed: Duplicate entry '(.*?)' for key '(borrowers\.)?cardnumber'/; >@@ -528,8 +503,7 @@ subtest 'add() tests' => sub { > $newpatron->{category_id} = $deleted_category_id; # Test invalid patron category > > $t->post_ok( "//$userid:$password@/api/v1/patrons" => { 'x-confirm-not-duplicate' => 1 } => json => $newpatron ) >- ->status_is(400) >- ->json_is( '/error' => "Given category_id does not exist" ); >+ ->status_is(400)->json_is( '/error' => "Given category_id does not exist" ); > $newpatron->{category_id} = $patron->categorycode; > > $newpatron->{falseproperty} = "Non existent property"; >@@ -546,6 +520,7 @@ subtest 'add() tests' => sub { > delete $newpatron->{restricted}; > delete $newpatron->{expired}; > delete $newpatron->{anonymized}; >+ delete $newpatron->{self_renewal_available}; > $patron_to_delete->delete; > > # Set a date field >@@ -559,15 +534,11 @@ subtest 'add() tests' => sub { > $letter_enqueued = 0; > $t->post_ok( > "//$userid:$password@/api/v1/patrons" => { 'x-koha-override' => 'welcome_yes' } => json => $newpatron ) >- ->status_is( 201, 'Patron created successfully' ) >- ->header_like( >+ ->status_is( 201, 'Patron created successfully' )->header_like( > Location => qr|^\/api\/v1\/patrons/\d*|, > 'REST3.4.1' >- ) >- ->json_has( '/patron_id', 'got a patron_id' ) >- ->json_is( '/cardnumber' => $newpatron->{cardnumber} ) >- ->json_is( '/surname' => $newpatron->{surname} ) >- ->json_is( '/firstname' => $newpatron->{firstname} ) >+ )->json_has( '/patron_id', 'got a patron_id' )->json_is( '/cardnumber' => $newpatron->{cardnumber} ) >+ ->json_is( '/surname' => $newpatron->{surname} )->json_is( '/firstname' => $newpatron->{firstname} ) > ->json_is( '/date_of_birth' => $newpatron->{date_of_birth}, 'Date field set (Bug 28585)' ) > ->json_is( '/last_seen' => $newpatron->{last_seen}, 'Date-time field set (Bug 28585)' ); > >@@ -694,8 +665,7 @@ subtest 'add() tests' => sub { > "city" => "Konstanz", > "library_id" => "MPL" > } >- ) >- ->status_is(400) >+ )->status_is(400) > ->json_is( '/error' => "Tried to add more than one non-repeatable attributes. type=$code value=$attr" ); > > is( Koha::Patrons->search->count, $patrons_count, 'No patron added' ); >@@ -710,8 +680,7 @@ subtest 'add() tests' => sub { > "city" => "Konstanz", > "library_id" => "MPL" > } >- ) >- ->status_is(400) >+ )->status_is(400) > ->json_is( > '/error' => "Your action breaks a unique constraint on the attribute. type=$code value=$attr" ); > >@@ -872,8 +841,7 @@ subtest 'add() tests' => sub { > "city" => "Bigtown", > "library_id" => "MPL", > } >- ) >- ->status_is( 201, 'Patron added with EnhancedMessagingPreferences disabled' ) >+ )->status_is( 201, 'Patron added with EnhancedMessagingPreferences disabled' ) > ->tx->res->json->{patron_id}; > > # No messaging preferences should be set >@@ -931,9 +899,9 @@ subtest 'update() tests' => sub { > delete $newpatron->{restricted}; > delete $newpatron->{expired}; > delete $newpatron->{anonymized}; >+ delete $newpatron->{self_renewal_available}; > >- $t->put_ok( "//$userid:$password@/api/v1/patrons/-1" => json => $newpatron ) >- ->status_is(404) >+ $t->put_ok( "//$userid:$password@/api/v1/patrons/-1" => json => $newpatron )->status_is(404) > ->json_has( '/error', 'Fails when trying to PUT nonexistent patron' ); > > # Create a library just to make sure its ID doesn't exist on the DB >@@ -947,8 +915,7 @@ subtest 'update() tests' => sub { > $newpatron->{category_id} = $deleted_category_id; > > $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron ) >- ->status_is(400) >- ->json_is( '/error' => "Given category_id does not exist" ); >+ ->status_is(400)->json_is( '/error' => "Given category_id does not exist" ); > > # Restore the valid category > $newpatron->{category_id} = $patron_2->categorycode; >@@ -965,8 +932,7 @@ subtest 'update() tests' => sub { > > warning_like { > $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron ) >- ->status_is(400) >- ->json_is( '/error' => "Given library_id does not exist" ); >+ ->status_is(400)->json_is( '/error' => "Given library_id does not exist" ); > } > qr/DBD::mysql::st execute failed: Cannot add or update a child row: a foreign key constraint fails/; > >@@ -977,8 +943,7 @@ subtest 'update() tests' => sub { > $newpatron->{falseproperty} = "Non existent property"; > > $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron ) >- ->status_is(400) >- ->json_is( '/errors/0/message' => 'Properties not allowed: falseproperty.' ); >+ ->status_is(400)->json_is( '/errors/0/message' => 'Properties not allowed: falseproperty.' ); > > # Get rid of the invalid attribute > delete $newpatron->{falseproperty}; >@@ -988,8 +953,7 @@ subtest 'update() tests' => sub { > $newpatron->{userid} = $patron_1->userid; > > $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron ) >- ->status_is(400) >- ->json_has( '/error', "Problem with userid " . $patron_1->userid ); >+ ->status_is(400)->json_has( '/error', "Problem with userid " . $patron_1->userid ); > > $newpatron->{cardnumber} = $patron_1->id . $patron_2->id; > $newpatron->{userid} = "user" . $patron_1->id . $patron_2->id; >@@ -1011,6 +975,8 @@ subtest 'update() tests' => sub { > $newpatron->{restricted} = $unauthorized_patron->to_api( { user => $authorized_patron } )->{restricted}; > $newpatron->{expired} = $unauthorized_patron->to_api( { user => $authorized_patron } )->{expired}; > $newpatron->{anonymized} = $unauthorized_patron->to_api( { user => $authorized_patron } )->{anonymized}; >+ $newpatron->{self_renewal_available} = >+ $unauthorized_patron->to_api( { user => $authorized_patron } )->{self_renewal_available}; > > my $got = $result->tx->res->json; > my $updated_on_got = delete $got->{updated_on}; >@@ -1046,6 +1012,7 @@ subtest 'update() tests' => sub { > delete $newpatron->{restricted}; > delete $newpatron->{expired}; > delete $newpatron->{anonymized}; >+ delete $newpatron->{self_renewal_available}; > > # attempt to update > $authorized_patron->flags( 2**4 )->store; # borrowers flag = 4 >@@ -1073,7 +1040,7 @@ subtest 'update() tests' => sub { > ->status_is( > 403, > "Non-superlibrarian user change of superlibrarian secondary_email to undefined forbidden" >- )->json_is( { error => "Not enough privileges to change a superlibrarian's email" } ); >+ )->json_is( { error => "Not enough privileges to change a superlibrarian's email" } ); > > $newpatron->{secondary_email} = $superlibrarian->emailpro; > $newpatron->{altaddress_email} = 'nonsense@no.no'; >@@ -1089,7 +1056,7 @@ subtest 'update() tests' => sub { > ->status_is( > 403, > "Non-superlibrarian user change of superlibrarian altaddress_email to undefined forbidden" >- )->json_is( { error => "Not enough privileges to change a superlibrarian's email" } ); >+ )->json_is( { error => "Not enough privileges to change a superlibrarian's email" } ); > > # update patron without sending email > delete $newpatron->{email}; >@@ -1160,15 +1127,13 @@ subtest 'update() tests' => sub { > "city" => "Konstanz", > "library_id" => "MPL" > } >- ) >- ->status_is(400) >+ )->status_is(400) > ->json_is( > '/error' => "Missing mandatory extended attribute (type=" . $attr_type_mandatory->code . ")" ); > > $t->put_ok( "//$userid:$password@/api/v1/patrons/" > . $superlibrarian->borrowernumber => { 'x-koha-embed' => 'extended_attributes' } => json => >- $newpatron ) >- ->status_is(400) >+ $newpatron )->status_is(400) > ->json_is( '/error' => 'Tried to use an invalid attribute type. type=' . $deleted_attr_code ) > ->json_is( '/error_code' => 'invalid_attribute_type' ); > >@@ -1185,13 +1150,11 @@ subtest 'update() tests' => sub { > > $t->put_ok( "//$userid:$password@/api/v1/patrons/" > . $superlibrarian->borrowernumber => { 'x-koha-embed' => 'extended_attributes' } => json => >- $newpatron ) >- ->status_is(400) >+ $newpatron )->status_is(400) > ->json_is( '/error' => 'Your action breaks a unique constraint on the attribute. type=' > . $attr_type_unique->code > . ' value=' >- . $unique_attr->attribute ) >- ->json_is( '/error_code' => 'attribute_not_unique' ); >+ . $unique_attr->attribute )->json_is( '/error_code' => 'attribute_not_unique' ); > > $newpatron->{extended_attributes} = [ > { type => $attr_type_repeatable->code, value => 'a' }, >@@ -1202,13 +1165,11 @@ subtest 'update() tests' => sub { > > $t->put_ok( "//$userid:$password@/api/v1/patrons/" > . $superlibrarian->borrowernumber => { 'x-koha-embed' => 'extended_attributes' } => json => >- $newpatron ) >- ->status_is(400) >+ $newpatron )->status_is(400) > ->json_is( '/error' => 'Your action breaks a unique constraint on the attribute. type=' > . $attr_type_unique->code > . ' value=' >- . $unique_attr->attribute ) >- ->json_is( '/error_code' => 'attribute_not_unique' ); >+ . $unique_attr->attribute )->json_is( '/error_code' => 'attribute_not_unique' ); > > $newpatron->{extended_attributes} = [ > { type => $attr_type_repeatable->code, value => 'a' }, >@@ -1218,12 +1179,10 @@ subtest 'update() tests' => sub { > > $t->put_ok( "//$userid:$password@/api/v1/patrons/" > . $superlibrarian->borrowernumber => { 'x-koha-embed' => 'extended_attributes' } => json => >- $newpatron ) >- ->status_is(400) >+ $newpatron )->status_is(400) > ->json_is( '/error' => 'Tried to add more than one non-repeatable attributes. type=' > . $attr_type_mandatory->code >- . ' value=pong' ) >- ->json_is( '/error_code' => 'non_repeatable_attribute' ); >+ . ' value=pong' )->json_is( '/error_code' => 'non_repeatable_attribute' ); > > my $unique_value = $unique_attr->attribute; > $unique_attr->delete; >@@ -1282,8 +1241,7 @@ subtest 'delete() tests' => sub { > > t::lib::Mocks::mock_preference( 'AnonymousPatron', $patron->borrowernumber ); > $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber ) >- ->status_is( 409, 'Anonymous patron cannot be deleted' ) >- ->json_is( >+ ->status_is( 409, 'Anonymous patron cannot be deleted' )->json_is( > { > error => 'Anonymous patron cannot be deleted', > error_code => 'is_anonymous_patron' >@@ -1305,8 +1263,7 @@ subtest 'delete() tests' => sub { > $guarantee->add_guarantor( { guarantor_id => $patron->id, relationship => 'parent' } ); > > $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber ) >- ->status_is( 409, 'Patron with checkouts cannot be deleted' ) >- ->json_is( >+ ->status_is( 409, 'Patron with checkouts cannot be deleted' )->json_is( > { > error => 'Pending checkouts prevent deletion', > error_code => 'has_checkouts' >@@ -1317,8 +1274,7 @@ subtest 'delete() tests' => sub { > $checkout->delete; > > $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber ) >- ->status_is( 409, 'Patron with debt cannot be deleted' ) >- ->json_is( >+ ->status_is( 409, 'Patron with debt cannot be deleted' )->json_is( > { > error => 'Pending debts prevent deletion', > error_code => 'has_debt' >@@ -1329,8 +1285,7 @@ subtest 'delete() tests' => sub { > $patron->account->pay( { amount => 10, debits => [$debit] } ); > > $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber ) >- ->status_is( 409, 'Patron with guarantees cannot be deleted' ) >- ->json_is( >+ ->status_is( 409, 'Patron with guarantees cannot be deleted' )->json_is( > { > error => 'Patron is a guarantor and it prevents deletion', > error_code => 'has_guarantees' >@@ -1343,8 +1298,7 @@ subtest 'delete() tests' => sub { > $patron->protected(1)->store(); > > $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber ) >- ->status_is( 409, 'Protected patron cannot be deleted' ) >- ->json_is( >+ ->status_is( 409, 'Protected patron cannot be deleted' )->json_is( > { > error => 'Protected patrons cannot be deleted', > error_code => 'is_protected' >@@ -1354,8 +1308,7 @@ subtest 'delete() tests' => sub { > $patron->protected(0)->store(); > > $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber ) >- ->status_is( 204, 'REST3.2.4' ) >- ->content_is( '', 'REST3.3.4' ); >+ ->status_is( 204, 'REST3.2.4' )->content_is( '', 'REST3.3.4' ); > > my $deleted_patrons = Koha::Old::Patrons->search( { borrowernumber => $patron->borrowernumber } ); > is( $deleted_patrons->count, 1, 'The patron has been moved to the vault' ); >@@ -1387,13 +1340,11 @@ subtest 'guarantors_can_see_charges() tests' => sub { > )->status_is(401)->json_is( { error => "Authentication failure." } ); > > $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$other_patron_id/guarantors/can_see_charges" => json => >- { allowed => Mojo::JSON->true } ) >- ->status_is(403) >+ { allowed => Mojo::JSON->true } )->status_is(403) > ->json_is( { error => "Unprivileged user cannot access another user's resources" } ); > > $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_charges" => json => >- { allowed => Mojo::JSON->true } ) >- ->status_is(403) >+ { allowed => Mojo::JSON->true } )->status_is(403) > ->json_is( '/error', 'The current configuration doesn\'t allow the requested action.' ); > > t::lib::Mocks::mock_preference( 'AllowPatronToSetFinesVisibilityForGuarantor', 1 ); >@@ -1431,15 +1382,13 @@ subtest 'guarantors_can_see_checkouts() tests' => sub { > { allowed => Mojo::JSON->true } )->status_is(401)->json_is( { error => "Authentication failure." } ); > > $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$other_patron_id/guarantors/can_see_checkouts" => json => >- { allowed => Mojo::JSON->true } ) >- ->status_is(403) >+ { allowed => Mojo::JSON->true } )->status_is(403) > ->json_is( { error => "Unprivileged user cannot access another user's resources" } ); > > t::lib::Mocks::mock_preference( 'AllowPatronToSetCheckoutsVisibilityForGuarantor', 0 ); > > $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_checkouts" => json => >- { allowed => Mojo::JSON->true } ) >- ->status_is(403) >+ { allowed => Mojo::JSON->true } )->status_is(403) > ->json_is( '/error', 'The current configuration doesn\'t allow the requested action.' ); > > t::lib::Mocks::mock_preference( 'AllowPatronToSetCheckoutsVisibilityForGuarantor', 1 ); >@@ -1482,8 +1431,7 @@ sub unauthorized_access_tests { > $unauthorized_patron->set_password( { password => $password, skip_validation => 1 } ); > my $unauth_userid = $unauthorized_patron->userid; > >- $t->$verb_ok( "//$unauth_userid:$password\@$endpoint" => json => $json ) >- ->status_is(403) >+ $t->$verb_ok( "//$unauth_userid:$password\@$endpoint" => json => $json )->status_is(403) > ->json_has('/required_permissions'); > }; > } >diff --git a/t/db_dependent/api/v1/patrons_self_renewal.t b/t/db_dependent/api/v1/patrons_self_renewal.t >new file mode 100755 >index 00000000000..d9f9f8da4cc >--- /dev/null >+++ b/t/db_dependent/api/v1/patrons_self_renewal.t >@@ -0,0 +1,198 @@ >+#!/usr/bin/env perl >+ >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <https://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Test::NoWarnings; >+use Test::More tests => 3; >+use Test::Mojo; >+ >+use t::lib::TestBuilder; >+use t::lib::Mocks; >+ >+use Koha::Database; >+use Koha::DateUtils qw( dt_from_string ); >+ >+my $schema = Koha::Database->new->schema; >+my $builder = t::lib::TestBuilder->new(); >+ >+my $t = Test::Mojo->new('Koha::REST::V1'); >+ >+t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); >+ >+subtest 'start()' => sub { >+ plan tests => 9; >+ >+ $schema->storage->txn_begin; >+ >+ my $category = $builder->build_object( >+ { >+ class => 'Koha::Patron::Categories', >+ value => { >+ self_renewal_enabled => 0, self_renewal_availability_start => 10, self_renewal_if_expired => 10, >+ self_renewal_fines_block => 10, noissuescharge => 10, >+ self_renewal_failure_message => 'This is a failure message' >+ } >+ } >+ ); >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { categorycode => $category->categorycode, dateexpiry => dt_from_string(), debarred => undef } >+ } >+ ); >+ >+ my $password = 'thePassword123'; >+ $patron->set_password( { password => $password, skip_validation => 1 } ); >+ my $userid = $patron->userid; >+ >+ my $borrowernumber = $patron->borrowernumber; >+ >+ $t->get_ok("//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal") >+ ->status_is( 403, 'REST3.2.2' )->json_is( { error => "You are not eligible for self-renewal" } ); >+ >+ $category->self_renewal_enabled(1)->store(); >+ >+ t::lib::Mocks::mock_preference( 'OPACPatronDetails', 1 ); >+ >+ $t->get_ok("//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal") >+ ->status_is( 200, 'REST3.2.2' )->json_is( >+ { >+ self_renewal_settings => { >+ self_renewal_failure_message => $category->self_renewal_failure_message, >+ self_renewal_information_message => $category->self_renewal_information_message, >+ opac_patron_details => 1 >+ } >+ } >+ ); >+ >+ my $new_category = $builder->build_object( >+ { >+ class => 'Koha::Patron::Categories', >+ value => { >+ self_renewal_enabled => 1, self_renewal_availability_start => 10, self_renewal_if_expired => 10, >+ self_renewal_fines_block => 10, noissuescharge => 10, >+ self_renewal_failure_message => 'This is a failure message' >+ } >+ } >+ ); >+ $t->get_ok("//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal") >+ ->status_is( 200, 'REST3.2.2' )->json_is( >+ { >+ self_renewal_settings => { >+ self_renewal_failure_message => $category->self_renewal_failure_message, >+ self_renewal_information_message => $category->self_renewal_information_message, >+ opac_patron_details => 1 >+ } >+ } >+ ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'submit()' => sub { >+ plan tests => 11; >+ >+ $schema->storage->txn_begin; >+ >+ my $category = $builder->build_object( >+ { >+ class => 'Koha::Patron::Categories', >+ value => { >+ self_renewal_enabled => 0, self_renewal_availability_start => 10, self_renewal_if_expired => 10, >+ self_renewal_fines_block => 10, noissuescharge => 10, >+ self_renewal_failure_message => 'This is a failure message' >+ } >+ } >+ ); >+ my $branch = $builder->build_object( { class => 'Koha::Libraries' } ); >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { >+ branchcode => $branch->branchcode, categorycode => $category->categorycode, >+ dateexpiry => dt_from_string(), debarred => undef, lang => 'default' >+ } >+ } >+ ); >+ >+ my $password = 'thePassword123'; >+ $patron->set_password( { password => $password, skip_validation => 1 } ); >+ my $userid = $patron->userid; >+ >+ my $borrowernumber = $patron->borrowernumber; >+ >+ $t->post_ok( "//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal" => json => {} ) >+ ->status_is( 403, 'REST3.2.2' )->json_is( { error => "You are not eligible for self-renewal" } ); >+ >+ t::lib::Mocks::mock_preference( 'OPACPatronDetails', 1 ); >+ t::lib::Mocks::mock_preference( 'AutoApprovePatronProfileSettings', 0 ); >+ $category->self_renewal_enabled(1)->store(); >+ >+ my $date; >+ if ( C4::Context->preference('BorrowerRenewalPeriodBase') eq 'combination' ) { >+ $date = >+ ( dt_from_string gt dt_from_string( $patron->dateexpiry ) ) >+ ? dt_from_string >+ : dt_from_string( $patron->dateexpiry ); >+ } else { >+ $date = >+ C4::Context->preference('BorrowerRenewalPeriodBase') eq 'dateexpiry' >+ ? dt_from_string( $patron->dateexpiry ) >+ : dt_from_string; >+ } >+ my $expiry_date = $patron->category->get_expiry_date($date); >+ >+ my $renewal_notice = $builder->build_object( >+ { >+ class => 'Koha::Notice::Templates', >+ value => { >+ module => 'members', >+ code => 'MEMBERSHIP_RENEWED', >+ branchcode => $branch->branchcode, >+ message_transport_type => 'print', >+ lang => 'default' >+ } >+ } >+ ); >+ >+ my $counter = Koha::Notice::Messages->search( { borrowernumber => $patron->borrowernumber } )->count; >+ $t->post_ok( "//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal" => json => {} ) >+ ->status_is( 201, 'REST3.2.2' ) >+ ->json_is( { expiry_date => $expiry_date->truncate( to => 'day' ), confirmation_sent => 1 } ); >+ is( >+ Koha::Notice::Messages->search( { borrowernumber => $patron->borrowernumber } )->count, $counter + 1, >+ "Notice queued" >+ ); >+ >+ # Test that modifications are created correctly >+ t::lib::Mocks::mock_preference( 'OPACPatronDetails', 1 ); >+ my $modification_data = { patron => { firstname => 'Newname' } }; >+ $patron->dateexpiry( dt_from_string() )->store(); >+ >+ $t->post_ok( >+ "//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal" => json => $modification_data ) >+ ->status_is( 201, 'REST3.2.2' ) >+ ->json_is( { expiry_date => $expiry_date->truncate( to => 'day' ), confirmation_sent => 1 } ); >+ >+ my @modifications = Koha::Patron::Modifications->search( { borrowernumber => $patron->borrowernumber } )->as_list; >+ is( scalar(@modifications), 1, "New modification has replaced any existing mods" ); >+ >+ $schema->storage->txn_rollback; >+}; >-- >2.50.1
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 26355
:
191781
|
191782
|
191783
|
191784
|
191785
|
191786
|
191787
|
191788
|
191789
|
192440
|
192441
|
192442
|
192443
|
192444
|
192445
|
192446
|
192447
|
192448
|
193065
|
193066
|
193067
|
193068
|
193069
|
193070
|
193071
|
193072
|
193073
|
193074
|
193249
|
193250
|
193251
|
193252
|
193253
|
193521
|
193522
|
193523
|
193524
|
193525
|
193526
|
193527
|
193528
|
193529
|
193581
|
193635
|
193654
|
193699
|
193756
|
193757
|
193758
|
193759
|
193760
|
193761
|
193762
|
193763
|
193764
|
193765
|
193921
|
194435
|
194464
|
194465
|
194466
|
194467
|
194468
|
194469
|
194470
|
194471
|
194472
|
194473
|
194474