Bug 26384: Fix executable flags
[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::Holds;
23 use Koha::Biblios;
24
25 # Dummy app for testing the plugin
26 use Mojolicious::Lite;
27
28 app->log->level('error');
29
30 plugin 'Koha::REST::Plugin::Objects';
31 plugin 'Koha::REST::Plugin::Query';
32 plugin 'Koha::REST::Plugin::Pagination';
33
34 get '/cities' => sub {
35     my $c = shift;
36     $c->validation->output($c->req->params->to_hash);
37     my $cities = $c->objects->search(Koha::Cities->new);
38     $c->render( status => 200, json => $cities );
39 };
40
41 get '/orders' => sub {
42     my $c = shift;
43     $c->stash('koha.embed', ( { fund => {} } ) );
44     $c->validation->output($c->req->params->to_hash);
45     my $orders = $c->objects->search(Koha::Acquisition::Orders->new);
46     $c->render( status => 200, json => $orders );
47 };
48
49 get '/patrons/:patron_id/holds' => sub {
50     my $c = shift;
51     my $params = $c->req->params->to_hash;
52     $params->{patron_id} = $c->stash("patron_id");
53     $c->validation->output($params);
54     my $holds_set = Koha::Holds->new;
55     my $holds     = $c->objects->search( $holds_set );
56     $c->render( status => 200, json => {count => scalar(@$holds)} );
57 };
58
59 get '/biblios' => sub {
60     my $c = shift;
61     my $output = $c->req->params->to_hash;
62     $output->{query} = $c->req->json if defined $c->req->json;
63     my $headers = $c->req->headers->to_hash;
64     $output->{'x-koha-query'} = $headers->{'x-koha-query'} if defined $headers->{'x-koha-query'};
65     $c->validation->output($output);
66     my $biblios_set = Koha::Biblios->new;
67     $c->stash("koha.embed", {
68         "suggestions" => {
69             children => {
70                 "suggester" => {}
71             }
72         }
73     });
74     my $biblios = $c->objects->search($biblios_set);
75     $c->render( status => 200, json => {count => scalar(@$biblios), biblios => $biblios} );
76 };
77
78
79 # The tests
80 use Test::More tests => 10;
81 use Test::Mojo;
82
83 use t::lib::Mocks;
84 use t::lib::TestBuilder;
85 use Koha::Database;
86
87 my $t = Test::Mojo->new;
88
89 my $schema  = Koha::Database->new()->schema();
90 my $builder = t::lib::TestBuilder->new;
91
92 subtest 'objects.search helper' => sub {
93
94     plan tests => 50;
95
96     $schema->storage->txn_begin;
97
98     # Remove existing cities to have more control on the search results
99     Koha::Cities->delete;
100
101     # Create three sample cities that match the query. This makes sure we
102     # always have a "next" link regardless of Mojolicious::Plugin::OpenAPI version.
103     $builder->build_object({
104         class => 'Koha::Cities',
105         value => {
106             city_name => 'Manuel'
107         }
108     });
109     $builder->build_object({
110         class => 'Koha::Cities',
111         value => {
112             city_name => 'Manuela'
113         }
114     });
115     $builder->build_object({
116         class => 'Koha::Cities',
117         value => {
118             city_name => 'Manuelab'
119         }
120     });
121
122     $t->get_ok('/cities?name=manuel&_per_page=1&_page=1')
123         ->status_is(200)
124         ->header_like( 'Link' => qr/<http:\/\/.*[\?&]_page=2.*>; rel="next",/ )
125         ->json_has('/0')
126         ->json_hasnt('/1')
127         ->json_is('/0/name' => 'Manuel');
128
129     $builder->build_object({
130         class => 'Koha::Cities',
131         value => {
132             city_name => 'Emanuel'
133         }
134     });
135
136     # _match=starts_with
137     $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=starts_with')
138         ->status_is(200)
139         ->json_has('/0')
140         ->json_has('/1')
141         ->json_has('/2')
142         ->json_hasnt('/3')
143         ->json_is('/0/name' => 'Manuel')
144         ->json_is('/1/name' => 'Manuela')
145         ->json_is('/2/name' => 'Manuelab');
146
147     # _match=ends_with
148     $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=ends_with')
149         ->status_is(200)
150         ->json_has('/0')
151         ->json_has('/1')
152         ->json_hasnt('/2')
153         ->json_is('/0/name' => 'Manuel')
154         ->json_is('/1/name' => 'Emanuel');
155
156     # _match=exact
157     $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=exact')
158         ->status_is(200)
159         ->json_has('/0')
160         ->json_hasnt('/1')
161         ->json_is('/0/name' => 'Manuel');
162
163     # _match=contains
164     $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=contains')
165         ->status_is(200)
166         ->json_has('/0')
167         ->json_has('/1')
168         ->json_has('/2')
169         ->json_has('/3')
170         ->json_hasnt('/4')
171         ->json_is('/0/name' => 'Manuel')
172         ->json_is('/1/name' => 'Manuela')
173         ->json_is('/2/name' => 'Manuelab')
174         ->json_is('/3/name' => 'Emanuel');
175
176     # Add 20 more cities
177     for ( 1..20 ) {
178         $builder->build_object({ class => 'Koha::Cities' });
179     }
180
181     t::lib::Mocks::mock_preference('RESTdefaultPageSize', 20 );
182     $t->get_ok('/cities')
183       ->status_is(200);
184
185     my $response_count = scalar @{ $t->tx->res->json };
186     is( $response_count, 20, 'RESTdefaultPageSize is honoured by default (20)' );
187
188     t::lib::Mocks::mock_preference('RESTdefaultPageSize', 5 );
189     $t->get_ok('/cities')
190       ->status_is(200);
191
192     $response_count = scalar @{ $t->tx->res->json };
193     is( $response_count, 5, 'RESTdefaultPageSize is honoured by default (5)' );
194
195     $t->get_ok('/cities?_page=1&_per_page=-1')
196       ->status_is(200);
197
198     $response_count = scalar @{ $t->tx->res->json };
199     is( $response_count, 24, '_per_page=-1 means all resources' );
200
201     $t->get_ok('/cities?_page=100&_per_page=-1')
202       ->status_is(200);
203
204     $response_count = scalar @{ $t->tx->res->json };
205     is( $response_count, 24, 'When _per_page=-1 the page param is not considered' );
206
207     $schema->storage->txn_rollback;
208 };
209
210 subtest 'objects.search helper, sorting on mapped column' => sub {
211
212     plan tests => 14;
213
214     $schema->storage->txn_begin;
215
216     # Have complete control over the existing cities to ease testing
217     Koha::Cities->delete;
218
219     $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'A', city_country => 'Argentina' } });
220     $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'B', city_country => 'Argentina' } });
221
222     $t->get_ok('/cities?_order_by=%2Bname&_order_by=+country')
223       ->status_is(200)
224       ->json_has('/0')
225       ->json_has('/1')
226       ->json_hasnt('/2')
227       ->json_is('/0/name' => 'A')
228       ->json_is('/1/name' => 'B');
229
230     $t->get_ok('/cities?_order_by=-name')
231       ->status_is(200)
232       ->json_has('/0')
233       ->json_has('/1')
234       ->json_hasnt('/2')
235       ->json_is('/0/name' => 'B')
236       ->json_is('/1/name' => 'A');
237
238     $schema->storage->txn_rollback;
239 };
240
241 subtest 'objects.search helper, encoding' => sub {
242
243     plan tests => 5;
244
245     $schema->storage->txn_begin;
246
247     Koha::Cities->delete;
248
249     $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'A', city_country => 'Argentina' } });
250     $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'B', city_country => '❤Argentina❤' } });
251
252     $t->get_ok('/cities?q={"country": "❤Argentina❤"}')
253       ->status_is(200)
254       ->json_has('/0')
255       ->json_hasnt('/1')
256       ->json_is('/0/name' => 'B');
257
258     $schema->storage->txn_rollback;
259 };
260
261 subtest 'objects.search helper, embed' => sub {
262
263     plan tests => 2;
264
265     $schema->storage->txn_begin;
266
267     my $order = $builder->build_object({ class => 'Koha::Acquisition::Orders' });
268
269     $t->get_ok('/orders?order_id=' . $order->ordernumber)
270       ->json_is('/0',$order->to_api({ embed => ( { fund => {} } ) }));
271
272     $schema->storage->txn_rollback;
273 };
274
275 subtest 'objects.search helper, with path parameters and _match' => sub {
276     plan tests => 8;
277
278     $schema->storage->txn_begin;
279
280     Koha::Holds->search()->delete;
281
282     my $patron = Koha::Patrons->find(10);
283     $patron->delete if $patron;
284     $patron = $builder->build_object( { class => "Koha::Patrons" } );
285     $patron->borrowernumber(10)->store;
286     $builder->build_object(
287         {
288             class => "Koha::Holds",
289             value => { borrowernumber => $patron->borrowernumber }
290         }
291     );
292
293     $t->get_ok('/patrons/1/holds?_match=exact')
294       ->json_is('/count' => 0, 'there should be no holds for borrower 1 with _match=exact');
295
296     $t->get_ok('/patrons/1/holds?_match=contains')
297       ->json_is('/count' => 0, 'there should be no holds for borrower 1 with _match=contains');
298
299     $t->get_ok('/patrons/10/holds?_match=exact')
300       ->json_is('/count' => 1, 'there should be 1 hold for borrower 10 with _match=exact');
301
302     $t->get_ok('/patrons/10/holds?_match=contains')
303       ->json_is('/count' => 1, 'there should be 1 hold for borrower 10 with _match=contains');
304
305     $schema->storage->txn_rollback;
306 };
307
308 subtest 'object.search helper with query parameter' => sub {
309     plan tests => 4;
310
311     $schema->storage->txn_begin;
312
313     my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
314     my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
315     my $biblio1 = $builder->build_sample_biblio;
316     my $biblio2 = $builder->build_sample_biblio;
317     my $biblio3 = $builder->build_sample_biblio;
318     my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
319     my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
320     my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
321
322     $t->get_ok('/biblios' => json => {"suggestions.suggester.patron_id" => $patron1->borrowernumber })
323       ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
324
325     $t->get_ok('/biblios' => json => {"suggestions.suggester.patron_id" => $patron2->borrowernumber })
326       ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
327
328     $schema->storage->txn_rollback;
329 };
330
331 subtest 'object.search helper with q parameter' => sub {
332     plan tests => 4;
333
334     $schema->storage->txn_begin;
335
336     my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
337     my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
338     my $biblio1 = $builder->build_sample_biblio;
339     my $biblio2 = $builder->build_sample_biblio;
340     my $biblio3 = $builder->build_sample_biblio;
341     my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
342     my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
343     my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
344
345     $t->get_ok('/biblios?q={"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}')
346       ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
347
348     $t->get_ok('/biblios?q={"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}')
349       ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
350
351     $schema->storage->txn_rollback;
352 };
353
354 subtest 'object.search helper with x-koha-query header' => sub {
355     plan tests => 4;
356
357     $schema->storage->txn_begin;
358
359     my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
360     my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
361     my $biblio1 = $builder->build_sample_biblio;
362     my $biblio2 = $builder->build_sample_biblio;
363     my $biblio3 = $builder->build_sample_biblio;
364     my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
365     my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
366     my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
367
368     $t->get_ok('/biblios' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}'})
369       ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
370
371     $t->get_ok('/biblios' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}'})
372       ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
373
374     $schema->storage->txn_rollback;
375 };
376
377 subtest 'object.search helper with all query methods' => sub {
378     plan tests => 6;
379
380     $schema->storage->txn_begin;
381
382     my $patron1 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron1'} } );
383     my $patron2 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron2'} } );
384     my $biblio1 = $builder->build_sample_biblio;
385     my $biblio2 = $builder->build_sample_biblio;
386     my $biblio3 = $builder->build_sample_biblio;
387     my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
388     my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
389     my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
390
391     $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})
392       ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
393
394     $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})
395       ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
396
397     $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})
398       ->json_is('/count' => 0, 'there shouldn\'t be biblios where suggester has patron1 fistname and patron2 id');
399
400     $schema->storage->txn_rollback;
401 };
402
403 subtest 'object.search helper order by embedded columns' => sub {
404     plan tests => 3;
405
406     my $patron1 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron1'} } );
407     my $patron2 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron2'} } );
408     my $biblio1 = $builder->build_sample_biblio;
409     my $biblio2 = $builder->build_sample_biblio;
410     my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
411     my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
412
413     $t->get_ok('/biblios?_order_by=-suggestions.suggester.firstname' => json => [{"me.biblio_id" => $biblio1->biblionumber}, {"me.biblio_id" => $biblio2->biblionumber}])
414       ->json_is('/biblios/0/biblio_id' => $biblio2->biblionumber, 'Biblio 2 should be first')
415       ->json_is('/biblios/1/biblio_id' => $biblio1->biblionumber, 'Biblio 1 should be second');
416
417     $schema->storage->txn_begin;
418 }