Koha/t/db_dependent/api/v1/acquisitions_orders.t
Tomas Cohen Arazi 61d2095a84 Bug 20212: Controller changes regression tests
This patchset did a copy and paste of the objects->search helper inside
the controller, to handle a very special case that wasn't worth
generalizing: the fact that the biblio object (that this patchset makes
embeddable) is actually built from two tables, and we want to filter by
fields in both tables.

This requires us to translate:
- Any filter reference to fields in biblioitems we allow to search on, through
  an allow list.
- For filtering to work, we need to tweak the stashed 'embed' object so
  the prefetch is done correctly

This patchset also adds a new query parameter: only_active. It is used
to only request active orders (see the spec for more details).

This patch adds tests for:
- Filtering by a column in bilbioitems (this implies correctly
  prefetching the bilbio => biblioitems relation, and translating the
  filtering parameter.
- Using the only_active query parameter to retrieve only active orders

To test:
1. Apply this patchset
2. Run:
   $ kshell
  k$ prove t/db_dependent/api/v1/acquisitions_orders.t
=> SUCCESS: Tests pass!
3. Verify the tests check for the explained behaviours.
=> SUCCESS: They do!
4. Sign off :-D

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Andrew Fuerste-Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2021-01-28 16:44:12 +01:00

398 lines
14 KiB
Perl
Executable file

#!/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 => 5;
use Test::Mojo;
use Test::Warn;
use t::lib::TestBuilder;
use t::lib::Mocks;
use JSON qw( encode_json );
use Koha::Acquisition::Orders;
use Koha::Database;
my $schema = Koha::Database->new->schema;
my $builder = t::lib::TestBuilder->new;
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
my $t = Test::Mojo->new('Koha::REST::V1');
subtest 'list() tests' => sub {
plan tests => 18;
$schema->storage->txn_begin;
my $patron = $builder->build_object({
class => 'Koha::Patrons',
value => { flags => 1 }
});
my $password = 'thePassword123';
$patron->set_password({ password => $password, skip_validation => 1 });
my $userid = $patron->userid;
my $basket = $builder->build_object({ class => 'Koha::Acquisition::Baskets' });
my $biblio = $builder->build_sample_biblio;
my $biblioitem = $biblio->biblioitem;
$biblioitem->set({ isbn => 'SOMETHING' })->store;
# Create test context
my $order = $builder->build_object(
{
class => 'Koha::Acquisition::Orders',
value => {
basketno => $basket->basketno,
orderstatus => 'new',
biblionumber => $biblio->biblionumber
}
}
);
my $another_order = $order->unblessed; # create a copy of $order but make
delete $another_order->{ordernumber}; # sure ordernumber will be regenerated
$another_order = $builder->build_object({ class => 'Koha::Acquisition::Orders', value => $another_order });
## Authorized user tests
my $count_of_orders = Koha::Acquisition::Orders->search->count;
# Make sure we are returned with the correct amount of orders
$t->get_ok( "//$userid:$password@/api/v1/acquisitions/orders" )
->status_is( 200, 'SWAGGER3.2.2' )
->json_has('/'.($count_of_orders-1).'/order_id')
->json_hasnt('/'.($count_of_orders).'/order_id');
subtest 'query parameters' => sub {
my $fields = {
biblio_id => 'biblionumber',
basket_id => 'basketno',
fund_id => 'budget_id',
};
my $size = keys %{$fields};
plan tests => $size * (2 + 2 * $size);
foreach my $field ( keys %{$fields} ) {
my $model_field = $fields->{ $field };
my $result = $t->get_ok("//$userid:$password@/api/v1/acquisitions/orders?$field=" . $order->$model_field)
->status_is(200);
foreach my $key ( keys %{$fields} ) {
my $key_field = $fields->{ $key };
# Check the result order first since it's not predefined.
if ($result->tx->res->json->[0]->{$key} eq $order->$key_field) {
$result->json_is( "/0/$key", $order->$key_field );
$result->json_is( "/1/$key", $another_order->$key_field );
} else {
$result->json_is( "/0/$key", $another_order->$key_field );
$result->json_is( "/1/$key", $order->$key_field );
}
}
}
};
my $query = { "biblio.isbn" => { "-like" => "\%SOMETHING\%" } };
$t->get_ok( "//$userid:$password@/api/v1/acquisitions/orders?q=" . encode_json($query) => {'x-koha-embed' => 'biblio'} )
->status_is( 200, "Embeddig biblio.isbn doesn't make it explode" )
->json_has( "/0/biblio", "biblio object correctly embedded" )
->json_is( "/0/biblio/isbn", 'SOMETHING', 'Filtering by a biblioitems column works!' );
my $result = $t->get_ok( "//$userid:$password@/api/v1/acquisitions/orders?biblio_id=" . $biblio->biblionumber )
->status_is( 200 );
is( scalar @{ $result->tx->res->json}, 2, 'Two orders fetched' );
# Mark $another_order as cancelled
$another_order->set({ orderstatus => 'cancelled' })->store;
$result = $t->get_ok( "//$userid:$password@/api/v1/acquisitions/orders?only_active=1&biblio_id=" . $biblio->biblionumber )
->status_is( 200, "only_active parameter accepted" );
is( scalar @{ $result->tx->res->json}, 1, 'Only one order is active' );
# Warn on unsupported query parameter
$t->get_ok( "//$userid:$password@/api/v1/acquisitions/orders?order_blah=blah" )
->status_is(400)
->json_is( [{ path => '/query/order_blah', message => 'Malformed query string'}] );
$schema->storage->txn_rollback;
};
subtest 'get() tests' => sub {
plan tests => 6;
$schema->storage->txn_begin;
my $order = $builder->build_object(
{
class => 'Koha::Acquisition::Orders',
value => { orderstatus => 'new' }
}
);
my $patron = $builder->build_object({
class => 'Koha::Patrons',
value => { flags => 2048 }
});
my $password = 'thePassword123';
$patron->set_password({ password => $password, skip_validation => 1 });
my $userid = $patron->userid;
$t->get_ok( "//$userid:$password@/api/v1/acquisitions/orders/" . $order->ordernumber )
->status_is( 200, 'SWAGGER3.2.2' )
->json_is( '' => $order->to_api, 'SWAGGER3.3.2' );
my $non_existent_order_id = $order->ordernumber;
$order->delete;
$t->get_ok( "//$userid:$password@/api/v1/acquisitions/orders/" . $non_existent_order_id )
->status_is(404)
->json_is( '/error' => 'Order not found' );
# FIXME This does not work on all the OS we support
## Regression tests for bug 25513
## Pick a high value that could be transformed into exponential
## representation and not considered a number by buggy DBD::mysql versions
#$order = $builder->build_object(
# {
# class => 'Koha::Acquisition::Orders',
# value => {
# orderstatus => 'new',
# ecost_tax_excluded => 9963405519357589504,
# unitprice => 10177559957753600000
# }
# }
#);
#
#$t->get_ok( "//$userid:$password@/api/v1/acquisitions/orders/" . $order->ordernumber )
# ->json_is( '' => $order->to_api, 'Number representation should be consistent' );
$schema->storage->txn_rollback;
};
subtest 'add() tests' => sub {
plan tests => 17;
$schema->storage->txn_begin;
my $authorized_patron = $builder->build_object({
class => 'Koha::Patrons',
value => { flags => 1 }
});
my $password = 'thePassword123';
$authorized_patron->set_password({ password => $password, skip_validation => 1 });
my $auth_userid = $authorized_patron->userid;
my $unauthorized_patron = $builder->build_object({
class => 'Koha::Patrons',
value => { flags => 4 }
});
$unauthorized_patron->set_password({ password => $password, skip_validation => 1 });
my $unauth_userid = $unauthorized_patron->userid;
my $order_obj = $builder->build_object(
{
class => 'Koha::Acquisition::Orders',
value => {
orderstatus => 'new',
unitprice => 10,
replacementprice => 10,
quantity => 2,
quantityreceived => 0,
datecancellationprinted => undef,
order_internalnote => 'This is a dummy note for testing'
}
}
);
my $order = $order_obj->to_api;
$order_obj->delete;
delete $order->{ordernumber};
$order->{uncertain_price} = Mojo::JSON->false;
# Unauthorized attempt to write
$t->post_ok( "//$unauth_userid:$password@/api/v1/acquisitions/orders" => json => $order )
->status_is(403);
# Authorized attempt to write invalid data
my $order_with_invalid_field = { %$order };
$order_with_invalid_field->{'orderinvalid'} = 'Order invalid';
$t->post_ok( "//$auth_userid:$password@/api/v1/acquisitions/orders" => json => $order_with_invalid_field )
->status_is(400)
->json_is(
"/errors" => [
{
message => "Properties not allowed: orderinvalid.",
path => "/body"
}
]
);
# Authorized attempt to write
$t->post_ok( "//$auth_userid:$password@/api/v1/acquisitions/orders" => json => $order )
->status_is( 201, 'SWAGGER3.2.1' )
->json_is( '/internal_note' => $order->{internal_note}, 'SWAGGER3.3.1' )
->header_like( Location => qr/\/api\/v1\/acquisitions\/orders\/\d*/, 'SWAGGER3.4.1' );
# save the order_id
my $order_id = $order->{order_id};
# Authorized attempt to create with null id
$order->{order_id} = undef;
$t->post_ok( "//$auth_userid:$password@/api/v1/acquisitions/orders" => json => $order )
->status_is(400)
->json_has('/errors');
# Authorized attempt to create with existing id
$order->{order_id} = $order_id;
warning_like {
$t->post_ok( "//$auth_userid:$password@/api/v1/acquisitions/orders" => json => $order )
->status_is(409)
->json_has( '/error' => "Fails when trying to add an existing order_id")
->json_like( '/conflict' => qr/(aqorders\.)?PRIMARY/ ); }
qr/DBD::mysql::st execute failed: Duplicate entry '(.*)' for key '(aqorders\.)?PRIMARY'/;
$schema->storage->txn_rollback;
};
subtest 'update() tests' => sub {
plan tests => 13;
$schema->storage->txn_begin;
my $authorized_patron = $builder->build_object({
class => 'Koha::Patrons',
value => { flags => 1 }
});
my $password = 'thePassword123';
$authorized_patron->set_password({ password => $password, skip_validation => 1 });
my $auth_userid = $authorized_patron->userid;
my $unauthorized_patron = $builder->build_object({
class => 'Koha::Patrons',
value => { flags => 4 }
});
$unauthorized_patron->set_password({ password => $password, skip_validation => 1 });
my $unauth_userid = $unauthorized_patron->userid;
my $library = $builder->build_object({ class => 'Koha::Libraries' });
my $library_id = $library->branchcode;
# Unauthorized attempt to update
$t->put_ok( "//$unauth_userid:$password@/api/v1/libraries/$library_id"
=> json => { name => 'New unauthorized name change' } )
->status_is(403);
# Attempt partial update on a PUT
my $library_with_missing_field = {
address1 => "New library address",
};
my $result = $t->put_ok( "//$auth_userid:$password@/api/v1/libraries/$library_id" => json => $library_with_missing_field )
->status_is(400);
# Check the result order first since it's not predefined.
if ($result->tx->res->json->{errors}->[0]->{path} eq '/body/name') {
$result->json_is(
"/errors",
[
{message => "Missing property.", path => "/body/name"},
{message => "Missing property.", path => "/body/library_id"}
]
);
} else {
$result->json_is(
"/errors",
[
{message => "Missing property.", path => "/body/library_id"},
{message => "Missing property.", path => "/body/name"}
]
);
}
my $deleted_library = $builder->build_object( { class => 'Koha::Libraries' } );
my $library_with_updated_field = $deleted_library->to_api;
$library_with_updated_field->{library_id} = $library_id;
$deleted_library->delete;
$t->put_ok( "//$auth_userid:$password@/api/v1/libraries/$library_id" => json => $library_with_updated_field )
->status_is(200, 'SWAGGER3.2.1')
->json_is( '' => $library_with_updated_field, 'SWAGGER3.3.3' );
# Authorized attempt to write invalid data
my $library_with_invalid_field = { %$library_with_updated_field };
$library_with_invalid_field->{'branchinvalid'} = 'Library invalid';
$t->put_ok( "//$auth_userid:$password@/api/v1/libraries/$library_id" => json => $library_with_invalid_field )
->status_is(400)
->json_is(
"/errors" => [
{
message => "Properties not allowed: branchinvalid.",
path => "/body"
}
]
);
my $non_existent_code = 'nope'.int(rand(10000));
$t->put_ok("//$auth_userid:$password@/api/v1/libraries/$non_existent_code" => json => $library_with_updated_field)
->status_is(404);
$schema->storage->txn_rollback;
};
subtest 'delete() tests' => sub {
plan tests => 7;
$schema->storage->txn_begin;
my $authorized_patron = $builder->build_object({
class => 'Koha::Patrons',
value => { flags => 1 }
});
my $password = 'thePassword123';
$authorized_patron->set_password({ password => $password, skip_validation => 1 });
my $auth_userid = $authorized_patron->userid;
my $unauthorized_patron = $builder->build_object({
class => 'Koha::Patrons',
value => { flags => 4 }
});
$unauthorized_patron->set_password({ password => $password, skip_validation => 1 });
my $unauth_userid = $unauthorized_patron->userid;
my $order = $builder->build_object( { class => 'Koha::Acquisition::Orders' } );
# Unauthorized attempt to delete
$t->delete_ok( "//$unauth_userid:$password@/api/v1/acquisitions/orders/" . $order->ordernumber )
->status_is(403);
$t->delete_ok( "//$auth_userid:$password@/api/v1/acquisitions/orders/" . $order->ordernumber )
->status_is(204, 'SWAGGER3.2.4')
->content_is('', 'SWAGGER3.3.4');
$t->delete_ok( "//$auth_userid:$password@/api/v1/acquisitions/orders/" . $order->ordernumber )
->status_is(404);
$schema->storage->txn_rollback;
};