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 Koha::Acquisition::Orders;
24 # Dummy app for testing the plugin
25 use Mojolicious::Lite;
27 app->log->level('error');
29 plugin 'Koha::REST::Plugin::Objects';
30 plugin 'Koha::REST::Plugin::Query';
31 plugin 'Koha::REST::Plugin::Pagination';
33 get '/cities' => sub {
35 $c->validation->output($c->req->params->to_hash);
36 my $cities = $c->objects->search(Koha::Cities->new);
37 $c->render( status => 200, json => $cities );
40 get '/cities/:city_id' => sub {
42 my $id = $c->stash("city_id");
43 my $city = $c->objects->find(Koha::Cities->new, $id);
44 $c->render( status => 200, json => $city );
47 get '/orders' => sub {
49 $c->stash('koha.embed', ( { fund => {} } ) );
50 $c->validation->output($c->req->params->to_hash);
51 my $orders = $c->objects->search(Koha::Acquisition::Orders->new);
52 $c->render( status => 200, json => $orders );
55 get '/orders/:order_id' => sub {
57 $c->stash('koha.embed', ( { fund => {} } ) );
58 my $id = $c->stash("order_id");
59 my $order = $c->objects->find(Koha::Acquisition::Orders->new, $id);
60 $c->render( status => 200, json => $order );
63 get '/biblios' => sub {
65 my $output = $c->req->params->to_hash;
66 $output->{query} = $c->req->json if defined $c->req->json;
67 my $headers = $c->req->headers->to_hash;
68 $output->{'x-koha-query'} = $headers->{'x-koha-query'} if defined $headers->{'x-koha-query'};
69 $c->validation->output($output);
70 my $biblios_set = Koha::Biblios->new;
71 $c->stash("koha.embed", {
78 my $biblios = $c->objects->search($biblios_set);
79 $c->render( status => 200, json => {count => scalar(@$biblios), biblios => $biblios} );
83 use Test::More tests => 12;
87 use t::lib::TestBuilder;
90 my $schema = Koha::Database->new()->schema();
91 my $builder = t::lib::TestBuilder->new;
93 subtest 'objects.search helper' => sub {
97 $schema->storage->txn_begin;
99 # Remove existing cities to have more control on the search results
100 Koha::Cities->delete;
102 # Create three sample cities that match the query. This makes sure we
103 # always have a "next" link regardless of Mojolicious::Plugin::OpenAPI version.
104 $builder->build_object({
105 class => 'Koha::Cities',
107 city_name => 'Manuel'
110 $builder->build_object({
111 class => 'Koha::Cities',
113 city_name => 'Manuela'
116 $builder->build_object({
117 class => 'Koha::Cities',
119 city_name => 'Manuelab'
123 my $t = Test::Mojo->new;
124 $t->get_ok('/cities?name=manuel&_per_page=1&_page=1')
126 ->header_like( 'Link' => qr/<http:\/\/.*[\?&]_page=2.*>; rel="next",/ )
129 ->json_is('/0/name' => 'Manuel');
131 $builder->build_object({
132 class => 'Koha::Cities',
134 city_name => 'Emanuel'
139 $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=starts_with')
145 ->json_is('/0/name' => 'Manuel')
146 ->json_is('/1/name' => 'Manuela')
147 ->json_is('/2/name' => 'Manuelab');
150 $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=ends_with')
155 ->json_is('/0/name' => 'Manuel')
156 ->json_is('/1/name' => 'Emanuel');
159 $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=exact')
163 ->json_is('/0/name' => 'Manuel');
166 $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=contains')
173 ->json_is('/0/name' => 'Manuel')
174 ->json_is('/1/name' => 'Manuela')
175 ->json_is('/2/name' => 'Manuelab')
176 ->json_is('/3/name' => 'Emanuel');
180 $builder->build_object({ class => 'Koha::Cities' });
183 t::lib::Mocks::mock_preference('RESTdefaultPageSize', 20 );
184 $t->get_ok('/cities')
187 my $response_count = scalar @{ $t->tx->res->json };
188 is( $response_count, 20, 'RESTdefaultPageSize is honoured by default (20)' );
190 t::lib::Mocks::mock_preference('RESTdefaultPageSize', 5 );
191 $t->get_ok('/cities')
194 $response_count = scalar @{ $t->tx->res->json };
195 is( $response_count, 5, 'RESTdefaultPageSize is honoured by default (5)' );
197 $t->get_ok('/cities?_page=1&_per_page=-1')
200 $response_count = scalar @{ $t->tx->res->json };
201 is( $response_count, 24, '_per_page=-1 means all resources' );
203 $t->get_ok('/cities?_page=100&_per_page=-1')
206 $response_count = scalar @{ $t->tx->res->json };
207 is( $response_count, 24, 'When _per_page=-1 the page param is not considered' );
209 $schema->storage->txn_rollback;
212 subtest 'objects.search helper, sorting on mapped column' => sub {
216 $schema->storage->txn_begin;
218 # Have complete control over the existing cities to ease testing
219 Koha::Cities->delete;
221 $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'A', city_country => 'Argentina' } });
222 $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'B', city_country => 'Argentina' } });
223 $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'C', city_country => 'Argentina' } });
224 $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'C', city_country => 'Belarus' } });
226 my $t = Test::Mojo->new;
228 $t->get_ok('/cities?_order_by=%2Bname,-country')
232 ->json_is('/0/name' => 'A')
233 ->json_is('/1/name' => 'B')
234 ->json_is('/2/name' => 'C')
235 ->json_is('/2/country' => 'Belarus')
236 ->json_is('/3/name' => 'C')
237 ->json_is('/3/country' => 'Argentina')
240 # Multi-param: traditional
241 $t->get_ok('/cities?_order_by=%2Bname&_order_by=-country')
245 ->json_is('/0/name' => 'A')
246 ->json_is('/1/name' => 'B')
247 ->json_is('/2/name' => 'C')
248 ->json_is('/2/country' => 'Belarus')
249 ->json_is('/3/name' => 'C')
250 ->json_is('/3/country' => 'Argentina')
253 # Multi-param: PHP Style, Passes validation as above, subsequntly explodes
254 $t->get_ok('/cities?_order_by[]=%2Bname&_order_by[]=-country')
258 ->json_is('/0/name' => 'A')
259 ->json_is('/1/name' => 'B')
260 ->json_is('/2/name' => 'C')
261 ->json_is('/2/country' => 'Belarus')
262 ->json_is('/3/name' => 'C')
263 ->json_is('/3/country' => 'Argentina')
267 $t->get_ok('/cities?_order_by=-name')
271 ->json_is('/0/name' => 'C')
272 ->json_is('/1/name' => 'C')
273 ->json_is('/2/name' => 'B')
274 ->json_is('/3/name' => 'A')
277 $schema->storage->txn_rollback;
280 subtest 'objects.search helper, encoding' => sub {
284 $schema->storage->txn_begin;
286 Koha::Cities->delete;
288 $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'A', city_country => 'Argentina' } });
289 $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'B', city_country => '❤Argentina❤' } });
291 my $t = Test::Mojo->new;
292 $t->get_ok('/cities?q={"country": "❤Argentina❤"}')
296 ->json_is('/0/name' => 'B');
298 $schema->storage->txn_rollback;
301 subtest 'objects.search helper, X-Total-Count and X-Base-Total-Count' => sub {
305 $schema->storage->txn_begin;
307 Koha::Cities->delete;
309 my $long_city_name = 'Llanfairpwllgwyngyll';
310 for my $i ( 1 .. length($long_city_name) ) {
311 $builder->build_object(
313 class => 'Koha::Cities',
315 city_name => substr( $long_city_name, 0, $i ),
316 city_country => 'Wales'
322 my $t = Test::Mojo->new;
323 $t->get_ok('/cities?name=L&_per_page=10&_page=1&_match=starts_with')
325 ->header_is( 'X-Total-Count' => 20, 'X-Total-Count header present' )
326 ->header_is( 'X-Base-Total-Count' => 20, 'X-Base-Total-Count header present' );
328 $t->get_ok('/cities?name=Llan&_per_page=10&_page=1&_match=starts_with')
330 ->header_is( 'X-Total-Count' => 17, 'X-Total-Count header present' )
331 ->header_is('X-Base-Total-Count' => 20, 'X-Base-Total-Count header present' );
333 $schema->storage->txn_rollback;
336 subtest 'objects.search helper, embed' => sub {
340 $schema->storage->txn_begin;
342 my $order = $builder->build_object({ class => 'Koha::Acquisition::Orders' });
344 my $t = Test::Mojo->new;
345 $t->get_ok('/orders?order_id=' . $order->ordernumber)
346 ->json_is('/0',$order->to_api({ embed => ( { fund => {} } ) }));
348 $schema->storage->txn_rollback;
351 subtest 'object.search helper with query parameter' => sub {
354 $schema->storage->txn_begin;
356 my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
357 my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
358 my $biblio1 = $builder->build_sample_biblio;
359 my $biblio2 = $builder->build_sample_biblio;
360 my $biblio3 = $builder->build_sample_biblio;
361 my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
362 my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
363 my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
365 my $t = Test::Mojo->new;
366 $t->get_ok('/biblios' => json => {"suggestions.suggester.patron_id" => $patron1->borrowernumber })
367 ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
369 $t->get_ok('/biblios' => json => {"suggestions.suggester.patron_id" => $patron2->borrowernumber })
370 ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
372 $schema->storage->txn_rollback;
375 subtest 'object.search helper with q parameter' => sub {
378 $schema->storage->txn_begin;
380 my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
381 my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
382 my $biblio1 = $builder->build_sample_biblio;
383 my $biblio2 = $builder->build_sample_biblio;
384 my $biblio3 = $builder->build_sample_biblio;
385 my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
386 my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
387 my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
389 my $t = Test::Mojo->new;
390 $t->get_ok('/biblios?q={"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}')
391 ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
393 $t->get_ok('/biblios?q={"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}')
394 ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
396 $schema->storage->txn_rollback;
399 subtest 'object.search helper with x-koha-query header' => sub {
402 $schema->storage->txn_begin;
404 my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
405 my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
406 my $biblio1 = $builder->build_sample_biblio;
407 my $biblio2 = $builder->build_sample_biblio;
408 my $biblio3 = $builder->build_sample_biblio;
409 my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
410 my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
411 my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
413 my $t = Test::Mojo->new;
414 $t->get_ok('/biblios' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}'})
415 ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
417 $t->get_ok('/biblios' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}'})
418 ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
420 $schema->storage->txn_rollback;
423 subtest 'object.search helper with all query methods' => sub {
426 $schema->storage->txn_begin;
428 my $patron1 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron1'} } );
429 my $patron2 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron2'} } );
430 my $biblio1 = $builder->build_sample_biblio;
431 my $biblio2 = $builder->build_sample_biblio;
432 my $biblio3 = $builder->build_sample_biblio;
433 my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
434 my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
435 my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
437 my $t = Test::Mojo->new;
438 $t->get_ok('/biblios?q={"suggestions.suggester.firstname": "'.$patron1->firstname.'"}' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}'} => json => {"suggestions.suggester.cardnumber" => $patron1->cardnumber})
439 ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
441 $t->get_ok('/biblios?q={"suggestions.suggester.firstname": "'.$patron2->firstname.'"}' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}'} => json => {"suggestions.suggester.cardnumber" => $patron2->cardnumber})
442 ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
444 $t->get_ok('/biblios?q={"suggestions.suggester.firstname": "'.$patron1->firstname.'"}' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}'} => json => {"suggestions.suggester.cardnumber" => $patron2->cardnumber})
445 ->json_is('/count' => 0, 'there shouldn\'t be biblios where suggester has patron1 fistname and patron2 id');
447 $schema->storage->txn_rollback;
450 subtest 'object.search helper order by embedded columns' => sub {
454 $schema->storage->txn_begin;
456 my $patron1 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron1'} } );
457 my $patron2 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron2'} } );
458 my $biblio1 = $builder->build_sample_biblio;
459 my $biblio2 = $builder->build_sample_biblio;
460 my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
461 my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
463 my $t = Test::Mojo->new;
464 $t->get_ok('/biblios?_order_by=-suggestions.suggester.firstname' => json => [{"me.biblio_id" => $biblio1->biblionumber}, {"me.biblio_id" => $biblio2->biblionumber}])
465 ->json_is('/biblios/0/biblio_id' => $biblio2->biblionumber, 'Biblio 2 should be first')
466 ->json_is('/biblios/1/biblio_id' => $biblio1->biblionumber, 'Biblio 1 should be second');
468 $schema->storage->txn_rollback;
471 subtest 'objects.find helper' => sub {
475 my $t = Test::Mojo->new;
477 $schema->storage->txn_begin;
479 my $city_1 = $builder->build_object( { class => 'Koha::Cities' } );
480 my $city_2 = $builder->build_object( { class => 'Koha::Cities' } );
482 $t->get_ok( '/cities/' . $city_1->id )
484 ->json_is( $city_1->to_api );
486 $t->get_ok( '/cities/' . $city_2->id )
488 ->json_is( $city_2->to_api );
490 $schema->storage->txn_rollback;
493 subtest 'objects.find helper, embed' => sub {
497 my $t = Test::Mojo->new;
499 $schema->storage->txn_begin;
501 my $order = $builder->build_object({ class => 'Koha::Acquisition::Orders' });
503 $t->get_ok( '/orders/' . $order->ordernumber )
504 ->json_is( $order->to_api( { embed => ( { fund => {} } ) } ) );
506 $schema->storage->txn_rollback;