From 23add9ce30802c85963e4f05ab3b02f49cea9bc7 Mon Sep 17 00:00:00 2001 From: Matt Blenkinsop Date: Wed, 21 Jan 2026 09:46:39 +0000 Subject: [PATCH] Bug 26355: Add unit and API tests --- t/db_dependent/Koha/Patron.t | 142 +++++++++++- t/db_dependent/api/v1/patrons_self_renewal.t | 214 +++++++++++++++++++ 2 files changed, 355 insertions(+), 1 deletion(-) create mode 100755 t/db_dependent/api/v1/patrons_self_renewal.t diff --git a/t/db_dependent/Koha/Patron.t b/t/db_dependent/Koha/Patron.t index 44802ce4ba1..02c70d27e9a 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; @@ -3562,3 +3562,143 @@ 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/patrons_self_renewal.t b/t/db_dependent/api/v1/patrons_self_renewal.t new file mode 100755 index 00000000000..7d1431df84b --- /dev/null +++ b/t/db_dependent/api/v1/patrons_self_renewal.t @@ -0,0 +1,214 @@ +#!/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 . + +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; + + Koha::Patron::Attribute::Types->search()->delete; + + 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; + + $t->get_ok("//$userid:$password@/api/v1/public/patrons/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/self_renewal")->status_is( 200, 'REST3.2.2' )->json_is( + { + verification_checks => [], + self_renewal_settings => { + self_renewal_failure_message => $category->self_renewal_failure_message, + opac_patron_details => 1 + } + } + ); + + my $attribute_type = $builder->build_object( + { + class => 'Koha::Patron::Attribute::Types', + value => { unique_id => 1, repeatable => 0, category_code => undef, self_renewal_verification_check => 1 } + } + ); + my $non_renewal_attribute_type = $builder->build_object( + { + class => 'Koha::Patron::Attribute::Types', + value => { unique_id => 1, repeatable => 0, category_code => undef, self_renewal_verification_check => 0 } + } + ); + 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' + } + } + ); + my $wrong_category_attribute_type = $builder->build_object( + { + class => 'Koha::Patron::Attribute::Types', + value => { + unique_id => 1, repeatable => 0, category_code => $new_category->categorycode, + self_renewal_verification_check => 0 + } + } + ); + $t->get_ok("//$userid:$password@/api/v1/public/patrons/self_renewal")->status_is( 200, 'REST3.2.2' )->json_is( + { + verification_checks => [ $attribute_type->unblessed ], + self_renewal_settings => { + self_renewal_failure_message => $category->self_renewal_failure_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; + + $t->post_ok( "//$userid:$password@/api/v1/public/patrons/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/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 = { firstname => 'Newname' }; + $patron->dateexpiry( dt_from_string() )->store(); + + $t->post_ok( "//$userid:$password@/api/v1/public/patrons/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.39.5