Bug 26636: Add tests
[koha-ffzg.git] / t / db_dependent / Koha / REST / Plugin / Objects.t
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
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.
9 #
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.
14 #
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>.
17
18 use Modern::Perl;
19
20 use Koha::Acquisition::Orders;
21 use Koha::Cities;
22 use Koha::Biblios;
23
24 # Dummy app for testing the plugin
25 use Mojolicious::Lite;
26
27 app->log->level('error');
28
29 plugin 'Koha::REST::Plugin::Objects';
30 plugin 'Koha::REST::Plugin::Query';
31 plugin 'Koha::REST::Plugin::Pagination';
32
33 get '/cities' => sub {
34     my $c = shift;
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 );
38 };
39
40 get '/cities/:city_id' => sub {
41     my $c = shift;
42     my $id = $c->stash("city_id");
43     my $city = $c->objects->find(Koha::Cities->new, $id);
44     $c->render( status => 200, json => $city );
45 };
46
47 get '/orders' => sub {
48     my $c = shift;
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 );
53 };
54
55 get '/orders/:order_id' => sub {
56     my $c = shift;
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 );
61 };
62
63 get '/biblios' => sub {
64     my $c = shift;
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", {
72         "suggestions" => {
73             children => {
74                 "suggester" => {}
75             }
76         }
77     });
78     my $biblios = $c->objects->search($biblios_set);
79     $c->render( status => 200, json => {count => scalar(@$biblios), biblios => $biblios} );
80 };
81
82 # The tests
83 use Test::More tests => 12;
84 use Test::Mojo;
85
86 use t::lib::Mocks;
87 use t::lib::TestBuilder;
88 use Koha::Database;
89
90 my $schema  = Koha::Database->new()->schema();
91 my $builder = t::lib::TestBuilder->new;
92
93 subtest 'objects.search helper' => sub {
94
95     plan tests => 50;
96
97     $schema->storage->txn_begin;
98
99     # Remove existing cities to have more control on the search results
100     Koha::Cities->delete;
101
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',
106         value => {
107             city_name => 'Manuel'
108         }
109     });
110     $builder->build_object({
111         class => 'Koha::Cities',
112         value => {
113             city_name => 'Manuela'
114         }
115     });
116     $builder->build_object({
117         class => 'Koha::Cities',
118         value => {
119             city_name => 'Manuelab'
120         }
121     });
122
123     my $t = Test::Mojo->new;
124     $t->get_ok('/cities?name=manuel&_per_page=1&_page=1')
125         ->status_is(200)
126         ->header_like( 'Link' => qr/<http:\/\/.*[\?&]_page=2.*>; rel="next",/ )
127         ->json_has('/0')
128         ->json_hasnt('/1')
129         ->json_is('/0/name' => 'Manuel');
130
131     $builder->build_object({
132         class => 'Koha::Cities',
133         value => {
134             city_name => 'Emanuel'
135         }
136     });
137
138     # _match=starts_with
139     $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=starts_with')
140         ->status_is(200)
141         ->json_has('/0')
142         ->json_has('/1')
143         ->json_has('/2')
144         ->json_hasnt('/3')
145         ->json_is('/0/name' => 'Manuel')
146         ->json_is('/1/name' => 'Manuela')
147         ->json_is('/2/name' => 'Manuelab');
148
149     # _match=ends_with
150     $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=ends_with')
151         ->status_is(200)
152         ->json_has('/0')
153         ->json_has('/1')
154         ->json_hasnt('/2')
155         ->json_is('/0/name' => 'Manuel')
156         ->json_is('/1/name' => 'Emanuel');
157
158     # _match=exact
159     $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=exact')
160         ->status_is(200)
161         ->json_has('/0')
162         ->json_hasnt('/1')
163         ->json_is('/0/name' => 'Manuel');
164
165     # _match=contains
166     $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=contains')
167         ->status_is(200)
168         ->json_has('/0')
169         ->json_has('/1')
170         ->json_has('/2')
171         ->json_has('/3')
172         ->json_hasnt('/4')
173         ->json_is('/0/name' => 'Manuel')
174         ->json_is('/1/name' => 'Manuela')
175         ->json_is('/2/name' => 'Manuelab')
176         ->json_is('/3/name' => 'Emanuel');
177
178     # Add 20 more cities
179     for ( 1..20 ) {
180         $builder->build_object({ class => 'Koha::Cities' });
181     }
182
183     t::lib::Mocks::mock_preference('RESTdefaultPageSize', 20 );
184     $t->get_ok('/cities')
185       ->status_is(200);
186
187     my $response_count = scalar @{ $t->tx->res->json };
188     is( $response_count, 20, 'RESTdefaultPageSize is honoured by default (20)' );
189
190     t::lib::Mocks::mock_preference('RESTdefaultPageSize', 5 );
191     $t->get_ok('/cities')
192       ->status_is(200);
193
194     $response_count = scalar @{ $t->tx->res->json };
195     is( $response_count, 5, 'RESTdefaultPageSize is honoured by default (5)' );
196
197     $t->get_ok('/cities?_page=1&_per_page=-1')
198       ->status_is(200);
199
200     $response_count = scalar @{ $t->tx->res->json };
201     is( $response_count, 24, '_per_page=-1 means all resources' );
202
203     $t->get_ok('/cities?_page=100&_per_page=-1')
204       ->status_is(200);
205
206     $response_count = scalar @{ $t->tx->res->json };
207     is( $response_count, 24, 'When _per_page=-1 the page param is not considered' );
208
209     $schema->storage->txn_rollback;
210 };
211
212 subtest 'objects.search helper, sorting on mapped column' => sub {
213
214     plan tests => 42;
215
216     $schema->storage->txn_begin;
217
218     # Have complete control over the existing cities to ease testing
219     Koha::Cities->delete;
220
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' } });
225
226     my $t = Test::Mojo->new;
227     # CSV-param
228     $t->get_ok('/cities?_order_by=%2Bname,-country')
229       ->status_is(200)
230       ->json_has('/0')
231       ->json_has('/1')
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')
238       ->json_hasnt('/4');
239
240     # Multi-param: traditional
241     $t->get_ok('/cities?_order_by=%2Bname&_order_by=-country')
242       ->status_is(200)
243       ->json_has('/0')
244       ->json_has('/1')
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')
251       ->json_hasnt('/4');
252
253     # Multi-param: PHP Style, Passes validation as above, subsequntly explodes
254     $t->get_ok('/cities?_order_by[]=%2Bname&_order_by[]=-country')
255       ->status_is(200)
256       ->json_has('/0')
257       ->json_has('/1')
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')
264       ->json_hasnt('/4');
265
266     # Single-param
267     $t->get_ok('/cities?_order_by=-name')
268       ->status_is(200)
269       ->json_has('/0')
270       ->json_has('/1')
271       ->json_is('/0/name' => 'C')
272       ->json_is('/1/name' => 'C')
273       ->json_is('/2/name' => 'B')
274       ->json_is('/3/name' => 'A')
275       ->json_hasnt('/4');
276
277     $schema->storage->txn_rollback;
278 };
279
280 subtest 'objects.search helper, encoding' => sub {
281
282     plan tests => 5;
283
284     $schema->storage->txn_begin;
285
286     Koha::Cities->delete;
287
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❤' } });
290
291     my $t = Test::Mojo->new;
292     $t->get_ok('/cities?q={"country": "❤Argentina❤"}')
293       ->status_is(200)
294       ->json_has('/0')
295       ->json_hasnt('/1')
296       ->json_is('/0/name' => 'B');
297
298     $schema->storage->txn_rollback;
299 };
300
301 subtest 'objects.search helper, X-Total-Count and X-Base-Total-Count' => sub {
302
303     plan tests => 8;
304
305     $schema->storage->txn_begin;
306
307     Koha::Cities->delete;
308
309     my $long_city_name = 'Llanfairpwllgwyngyll';
310     for my $i ( 1 .. length($long_city_name) ) {
311         $builder->build_object(
312             {
313                 class => 'Koha::Cities',
314                 value => {
315                     city_name    => substr( $long_city_name, 0, $i ),
316                     city_country => 'Wales'
317                 }
318             }
319         );
320     }
321
322     my $t = Test::Mojo->new;
323     $t->get_ok('/cities?name=L&_per_page=10&_page=1&_match=starts_with')
324       ->status_is(200)
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' );
327
328     $t->get_ok('/cities?name=Llan&_per_page=10&_page=1&_match=starts_with')
329       ->status_is(200)
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' );
332
333     $schema->storage->txn_rollback;
334 };
335
336 subtest 'objects.search helper, embed' => sub {
337
338     plan tests => 2;
339
340     $schema->storage->txn_begin;
341
342     my $order = $builder->build_object({ class => 'Koha::Acquisition::Orders' });
343
344     my $t = Test::Mojo->new;
345     $t->get_ok('/orders?order_id=' . $order->ordernumber)
346       ->json_is('/0',$order->to_api({ embed => ( { fund => {} } ) }));
347
348     $schema->storage->txn_rollback;
349 };
350
351 subtest 'object.search helper with query parameter' => sub {
352     plan tests => 4;
353
354     $schema->storage->txn_begin;
355
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} } );
364
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');
368
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');
371
372     $schema->storage->txn_rollback;
373 };
374
375 subtest 'object.search helper with q parameter' => sub {
376     plan tests => 4;
377
378     $schema->storage->txn_begin;
379
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} } );
388
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');
392
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');
395
396     $schema->storage->txn_rollback;
397 };
398
399 subtest 'object.search helper with x-koha-query header' => sub {
400     plan tests => 4;
401
402     $schema->storage->txn_begin;
403
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} } );
412
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');
416
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');
419
420     $schema->storage->txn_rollback;
421 };
422
423 subtest 'object.search helper with all query methods' => sub {
424     plan tests => 6;
425
426     $schema->storage->txn_begin;
427
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} } );
436
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');
440
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');
443
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');
446
447     $schema->storage->txn_rollback;
448 };
449
450 subtest 'object.search helper order by embedded columns' => sub {
451
452     plan tests => 3;
453
454     $schema->storage->txn_begin;
455
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} } );
462
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');
467
468     $schema->storage->txn_rollback;
469 };
470
471 subtest 'objects.find helper' => sub {
472
473     plan tests => 6;
474
475     my $t = Test::Mojo->new;
476
477     $schema->storage->txn_begin;
478
479     my $city_1 = $builder->build_object( { class => 'Koha::Cities' } );
480     my $city_2 = $builder->build_object( { class => 'Koha::Cities' } );
481
482     $t->get_ok( '/cities/' . $city_1->id )
483       ->status_is(200)
484       ->json_is( $city_1->to_api );
485
486     $t->get_ok( '/cities/' . $city_2->id )
487       ->status_is(200)
488       ->json_is( $city_2->to_api );
489
490     $schema->storage->txn_rollback;
491 };
492
493 subtest 'objects.find helper, embed' => sub {
494
495     plan tests => 2;
496
497     my $t = Test::Mojo->new;
498
499     $schema->storage->txn_begin;
500
501     my $order = $builder->build_object({ class => 'Koha::Acquisition::Orders' });
502
503     $t->get_ok( '/orders/' . $order->ordernumber )
504       ->json_is( $order->to_api( { embed => ( { fund => {} } ) } ) );
505
506     $schema->storage->txn_rollback;
507 };