Tomas Cohen Arazi
b33d69684f
This patch adds regression tests for the response bodies and statuses on DELETE actions against existing API routes. This is just enforcing the existing (voted) Coding guidelines for the API (tm). To test: 1. Apply this patch 2. Run: $ kshell k$ prove t/db_dependent/api/v1/*.t => FAIL: Several routes have problems Signed-off-by: David Nind <david@davidnind.com> Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com> Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
480 lines
18 KiB
Perl
480 lines
18 KiB
Perl
#!/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 <http://www.gnu.org/licenses>.
|
|
|
|
use Modern::Perl;
|
|
|
|
use Test::More tests => 7;
|
|
use Test::Mojo;
|
|
use Test::Warn;
|
|
|
|
use t::lib::TestBuilder;
|
|
use t::lib::Mocks;
|
|
|
|
use C4::Auth;
|
|
use Koha::Database;
|
|
use Koha::Patron::Debarments qw/AddDebarment/;
|
|
|
|
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 'list() tests' => sub {
|
|
plan tests => 2;
|
|
|
|
$schema->storage->txn_begin;
|
|
unauthorized_access_tests('GET', undef, undef);
|
|
$schema->storage->txn_rollback;
|
|
|
|
subtest 'librarian access tests' => sub {
|
|
plan tests => 13;
|
|
|
|
$schema->storage->txn_begin;
|
|
|
|
my $librarian = $builder->build_object(
|
|
{
|
|
class => 'Koha::Patrons',
|
|
value => { flags => 2**4 } # borrowers flag = 4
|
|
}
|
|
);
|
|
my $password = 'thePassword123';
|
|
$librarian->set_password( { password => $password, skip_validation => 1 } );
|
|
my $userid = $librarian->userid;
|
|
|
|
$t->get_ok("//$userid:$password@/api/v1/patrons")
|
|
->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?address2=" . $librarian->address2)
|
|
->status_is(200)
|
|
->json_is('/0/address2' => $librarian->address2);
|
|
|
|
my $patron = $builder->build_object({ class => 'Koha::Patrons' });
|
|
AddDebarment({ borrowernumber => $patron->borrowernumber });
|
|
|
|
$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_hasnt('/1');
|
|
|
|
$schema->storage->txn_rollback;
|
|
};
|
|
};
|
|
|
|
subtest 'get() tests' => sub {
|
|
plan tests => 2;
|
|
|
|
$schema->storage->txn_begin;
|
|
unauthorized_access_tests('GET', -1, undef);
|
|
$schema->storage->txn_rollback;
|
|
|
|
subtest 'librarian access tests' => sub {
|
|
plan tests => 6;
|
|
|
|
$schema->storage->txn_begin;
|
|
|
|
my $librarian = $builder->build_object(
|
|
{
|
|
class => 'Koha::Patrons',
|
|
value => { flags => 2**4 } # borrowers flag = 4
|
|
}
|
|
);
|
|
my $password = 'thePassword123';
|
|
$librarian->set_password( { password => $password, skip_validation => 1 } );
|
|
my $userid = $librarian->userid;
|
|
|
|
my $patron = $builder->build_object({ class => 'Koha::Patrons' });
|
|
|
|
$t->get_ok("//$userid:$password@/api/v1/patrons/" . $patron->id)
|
|
->status_is(200)
|
|
->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;
|
|
};
|
|
};
|
|
|
|
subtest 'add() tests' => sub {
|
|
plan tests => 2;
|
|
|
|
$schema->storage->txn_begin;
|
|
|
|
my $patron = $builder->build_object( { class => 'Koha::Patrons' } )->to_api;
|
|
|
|
unauthorized_access_tests('POST', undef, $patron);
|
|
|
|
$schema->storage->txn_rollback;
|
|
|
|
subtest 'librarian access tests' => sub {
|
|
plan tests => 21;
|
|
|
|
$schema->storage->txn_begin;
|
|
|
|
my $patron = $builder->build_object({ class => 'Koha::Patrons' });
|
|
my $newpatron = $patron->to_api;
|
|
# delete RO attributes
|
|
delete $newpatron->{patron_id};
|
|
delete $newpatron->{restricted};
|
|
delete $newpatron->{anonymized};
|
|
|
|
# Create a library just to make sure its ID doesn't exist on the DB
|
|
my $library_to_delete = $builder->build_object({ class => 'Koha::Libraries' });
|
|
my $deleted_library_id = $library_to_delete->id;
|
|
# Delete library
|
|
$library_to_delete->delete;
|
|
|
|
my $librarian = $builder->build_object(
|
|
{
|
|
class => 'Koha::Patrons',
|
|
value => { flags => 2**4 } # borrowers flag = 4
|
|
}
|
|
);
|
|
my $password = 'thePassword123';
|
|
$librarian->set_password( { password => $password, skip_validation => 1 } );
|
|
my $userid = $librarian->userid;
|
|
|
|
$newpatron->{library_id} = $deleted_library_id;
|
|
|
|
warning_like {
|
|
$t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
|
|
->status_is(409)
|
|
->json_is('/error' => "Duplicate ID"); }
|
|
qr/DBD::mysql::st execute failed: Duplicate entry/;
|
|
|
|
$newpatron->{library_id} = $patron->branchcode;
|
|
|
|
# Create a library just to make sure its ID doesn't exist on the DB
|
|
my $category_to_delete = $builder->build_object({ class => 'Koha::Patron::Categories' });
|
|
my $deleted_category_id = $category_to_delete->id;
|
|
# Delete library
|
|
$category_to_delete->delete;
|
|
|
|
$newpatron->{category_id} = $deleted_category_id; # Test invalid patron category
|
|
|
|
$t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
|
|
->status_is(400)
|
|
->json_is('/error' => "Given category_id does not exist");
|
|
$newpatron->{category_id} = $patron->categorycode;
|
|
|
|
$newpatron->{falseproperty} = "Non existent property";
|
|
|
|
$t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
|
|
->status_is(400);
|
|
|
|
delete $newpatron->{falseproperty};
|
|
|
|
my $patron_to_delete = $builder->build_object({ class => 'Koha::Patrons' });
|
|
$newpatron = $patron_to_delete->to_api;
|
|
# delete RO attributes
|
|
delete $newpatron->{patron_id};
|
|
delete $newpatron->{restricted};
|
|
delete $newpatron->{anonymized};
|
|
$patron_to_delete->delete;
|
|
|
|
$t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
|
|
->status_is(201, 'Patron created successfully')
|
|
->header_like(
|
|
Location => qr|^\/api\/v1\/patrons/\d*|,
|
|
'SWAGGER3.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 });
|
|
|
|
warning_like {
|
|
$t->post_ok("//$userid:$password@/api/v1/patrons" => json => $newpatron)
|
|
->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'/;
|
|
|
|
$schema->storage->txn_rollback;
|
|
};
|
|
};
|
|
|
|
subtest 'update() tests' => sub {
|
|
plan tests => 2;
|
|
|
|
$schema->storage->txn_begin;
|
|
unauthorized_access_tests('PUT', 123, {email => 'nobody@example.com'});
|
|
$schema->storage->txn_rollback;
|
|
|
|
subtest 'librarian access tests' => sub {
|
|
plan tests => 22;
|
|
|
|
$schema->storage->txn_begin;
|
|
|
|
my $authorized_patron = $builder->build_object(
|
|
{
|
|
class => 'Koha::Patrons',
|
|
value => { flags => 2**4 } # borrowers flag = 4
|
|
}
|
|
);
|
|
my $password = 'thePassword123';
|
|
$authorized_patron->set_password(
|
|
{ password => $password, skip_validation => 1 } );
|
|
my $userid = $authorized_patron->userid;
|
|
|
|
my $unauthorized_patron = $builder->build_object(
|
|
{
|
|
class => 'Koha::Patrons',
|
|
value => { flags => 0 }
|
|
}
|
|
);
|
|
$unauthorized_patron->set_password( { password => $password, skip_validation => 1 } );
|
|
my $unauth_userid = $unauthorized_patron->userid;
|
|
|
|
my $patron_1 = $authorized_patron;
|
|
my $patron_2 = $unauthorized_patron;
|
|
my $newpatron = $unauthorized_patron->to_api;
|
|
# delete RO attributes
|
|
delete $newpatron->{patron_id};
|
|
delete $newpatron->{restricted};
|
|
delete $newpatron->{anonymized};
|
|
|
|
$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
|
|
my $category_to_delete = $builder->build_object({ class => 'Koha::Patron::Categories' });
|
|
my $deleted_category_id = $category_to_delete->id;
|
|
# Delete library
|
|
$category_to_delete->delete;
|
|
|
|
# Use an invalid category
|
|
$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");
|
|
|
|
# Restore the valid category
|
|
$newpatron->{category_id} = $patron_2->categorycode;
|
|
|
|
# Create a library just to make sure its ID doesn't exist on the DB
|
|
my $library_to_delete = $builder->build_object({ class => 'Koha::Libraries' });
|
|
my $deleted_library_id = $library_to_delete->id;
|
|
# Delete library
|
|
$library_to_delete->delete;
|
|
|
|
# Use an invalid library_id
|
|
$newpatron->{library_id} = $deleted_library_id;
|
|
|
|
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"); }
|
|
qr/DBD::mysql::st execute failed: Cannot add or update a child row: a foreign key constraint fails/;
|
|
|
|
# Restore the valid library_id
|
|
$newpatron->{library_id} = $patron_2->branchcode;
|
|
|
|
# Use an invalid attribute
|
|
$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.');
|
|
|
|
# Get rid of the invalid attribute
|
|
delete $newpatron->{falseproperty};
|
|
|
|
# Set both cardnumber and userid to already existing values
|
|
$newpatron->{cardnumber} = $patron_1->cardnumber;
|
|
$newpatron->{userid} = $patron_1->userid;
|
|
|
|
warning_like {
|
|
$t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
|
|
->status_is(409)
|
|
->json_has( '/error', "Fails when trying to update to an existing cardnumber or userid")
|
|
->json_like( '/conflict' => qr/(borrowers\.)?cardnumber/ ); }
|
|
qr/DBD::mysql::st execute failed: Duplicate entry '(.*?)' for key '(borrowers\.)?cardnumber'/;
|
|
|
|
$newpatron->{ cardnumber } = $patron_1->id . $patron_2->id;
|
|
$newpatron->{ userid } = "user" . $patron_1->id.$patron_2->id;
|
|
$newpatron->{ surname } = "user" . $patron_1->id.$patron_2->id;
|
|
|
|
my $result = $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
|
|
->status_is(200, 'Patron updated successfully');
|
|
|
|
# Put back the RO attributes
|
|
$newpatron->{patron_id} = $unauthorized_patron->to_api->{patron_id};
|
|
$newpatron->{restricted} = $unauthorized_patron->to_api->{restricted};
|
|
$newpatron->{anonymized} = $unauthorized_patron->to_api->{anonymized};
|
|
is_deeply($result->tx->res->json, $newpatron, 'Returned patron from update matches expected');
|
|
|
|
is(Koha::Patrons->find( $patron_2->id )->cardnumber,
|
|
$newpatron->{ cardnumber }, 'Patron is really updated!');
|
|
|
|
$schema->storage->txn_rollback;
|
|
};
|
|
};
|
|
|
|
subtest 'delete() tests' => sub {
|
|
plan tests => 2;
|
|
|
|
$schema->storage->txn_begin;
|
|
unauthorized_access_tests('DELETE', 123, undef);
|
|
$schema->storage->txn_rollback;
|
|
|
|
subtest 'librarian access test' => sub {
|
|
plan tests => 5;
|
|
|
|
$schema->storage->txn_begin;
|
|
|
|
my $authorized_patron = $builder->build_object(
|
|
{
|
|
class => 'Koha::Patrons',
|
|
value => { flags => 2**4 } # borrowers flag = 4
|
|
}
|
|
);
|
|
my $password = 'thePassword123';
|
|
$authorized_patron->set_password(
|
|
{ password => $password, skip_validation => 1 } );
|
|
my $userid = $authorized_patron->userid;
|
|
|
|
$t->delete_ok("//$userid:$password@/api/v1/patrons/-1")
|
|
->status_is(404, 'Patron not found');
|
|
|
|
my $patron = $builder->build_object({ class => 'Koha::Patrons' });
|
|
|
|
$t->delete_ok("//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber)
|
|
->status_is(204, 'SWAGGER3.2.4')
|
|
->content_is('', 'SWAGGER3.3.4');
|
|
|
|
$schema->storage->txn_rollback;
|
|
};
|
|
};
|
|
|
|
subtest 'guarantors_can_see_charges() tests' => sub {
|
|
|
|
plan tests => 11;
|
|
|
|
t::lib::Mocks::mock_preference( 'RESTPublicAPI', 1 );
|
|
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
|
|
|
|
$schema->storage->txn_begin;
|
|
|
|
my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { privacy_guarantor_fines => 0 } });
|
|
my $password = 'thePassword123';
|
|
$patron->set_password({ password => $password, skip_validation => 1 });
|
|
my $userid = $patron->userid;
|
|
my $patron_id = $patron->borrowernumber;
|
|
|
|
t::lib::Mocks::mock_preference( 'AllowPatronToSetFinesVisibilityForGuarantor', 0 );
|
|
|
|
$t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_charges" => json => { 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 );
|
|
|
|
$t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_charges" => json => { allowed => Mojo::JSON->true } )
|
|
->status_is( 200 )
|
|
->json_is( {} );
|
|
|
|
ok( $patron->discard_changes->privacy_guarantor_fines, 'privacy_guarantor_fines has been set correctly' );
|
|
|
|
$t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_charges" => json => { allowed => Mojo::JSON->false } )
|
|
->status_is( 200 )
|
|
->json_is( {} );
|
|
|
|
ok( !$patron->discard_changes->privacy_guarantor_fines, 'privacy_guarantor_fines has been set correctly' );
|
|
|
|
$schema->storage->txn_rollback;
|
|
};
|
|
|
|
subtest 'guarantors_can_see_checkouts() tests' => sub {
|
|
|
|
plan tests => 11;
|
|
|
|
t::lib::Mocks::mock_preference( 'RESTPublicAPI', 1 );
|
|
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
|
|
|
|
$schema->storage->txn_begin;
|
|
|
|
my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { privacy_guarantor_checkouts => 0 } });
|
|
my $password = 'thePassword123';
|
|
$patron->set_password({ password => $password, skip_validation => 1 });
|
|
my $userid = $patron->userid;
|
|
my $patron_id = $patron->borrowernumber;
|
|
|
|
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 )
|
|
->json_is( '/error', 'The current configuration doesn\'t allow the requested action.' );
|
|
|
|
t::lib::Mocks::mock_preference( 'AllowPatronToSetCheckoutsVisibilityForGuarantor', 1 );
|
|
|
|
$t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_checkouts" => json => { allowed => Mojo::JSON->true } )
|
|
->status_is( 200 )
|
|
->json_is( {} );
|
|
|
|
ok( $patron->discard_changes->privacy_guarantor_checkouts, 'privacy_guarantor_checkouts has been set correctly' );
|
|
|
|
$t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_checkouts" => json => { allowed => Mojo::JSON->false } )
|
|
->status_is( 200 )
|
|
->json_is( {} );
|
|
|
|
ok( !$patron->discard_changes->privacy_guarantor_checkouts, 'privacy_guarantor_checkouts has been set correctly' );
|
|
|
|
$schema->storage->txn_rollback;
|
|
};
|
|
|
|
# Centralized tests for 401s and 403s assuming the endpoint requires
|
|
# borrowers flag for access
|
|
sub unauthorized_access_tests {
|
|
my ($verb, $patron_id, $json) = @_;
|
|
|
|
my $endpoint = '/api/v1/patrons';
|
|
$endpoint .= ($patron_id) ? "/$patron_id" : '';
|
|
|
|
subtest 'unauthorized access tests' => sub {
|
|
plan tests => 5;
|
|
|
|
my $verb_ok = lc($verb) . '_ok';
|
|
|
|
$t->$verb_ok($endpoint => json => $json)
|
|
->status_is(401);
|
|
|
|
my $unauthorized_patron = $builder->build_object(
|
|
{
|
|
class => 'Koha::Patrons',
|
|
value => { flags => 0 }
|
|
}
|
|
);
|
|
my $password = "thePassword123!";
|
|
$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)
|
|
->json_has('/required_permissions');
|
|
};
|
|
}
|