Bug 25774: Handle utf8 chars in REST API queries
[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::TestBuilder;
84 use Koha::Database;
85
86 my $t = Test::Mojo->new;
87
88 my $schema  = Koha::Database->new()->schema();
89 my $builder = t::lib::TestBuilder->new;
90
91 subtest 'objects.search helper' => sub {
92
93     plan tests => 38;
94
95     $schema->storage->txn_begin;
96
97     # Remove existing cities to have more control on the search results
98     Koha::Cities->delete;
99
100     # Create three sample cities that match the query. This makes sure we
101     # always have a "next" link regardless of Mojolicious::Plugin::OpenAPI version.
102     $builder->build_object({
103         class => 'Koha::Cities',
104         value => {
105             city_name => 'Manuel'
106         }
107     });
108     $builder->build_object({
109         class => 'Koha::Cities',
110         value => {
111             city_name => 'Manuela'
112         }
113     });
114     $builder->build_object({
115         class => 'Koha::Cities',
116         value => {
117             city_name => 'Manuelab'
118         }
119     });
120
121     $t->get_ok('/cities?name=manuel&_per_page=1&_page=1')
122         ->status_is(200)
123         ->header_like( 'Link' => qr/<http:\/\/.*[\?&]_page=2.*>; rel="next",/ )
124         ->json_has('/0')
125         ->json_hasnt('/1')
126         ->json_is('/0/name' => 'Manuel');
127
128     $builder->build_object({
129         class => 'Koha::Cities',
130         value => {
131             city_name => 'Emanuel'
132         }
133     });
134
135     # _match=starts_with
136     $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=starts_with')
137         ->status_is(200)
138         ->json_has('/0')
139         ->json_has('/1')
140         ->json_has('/2')
141         ->json_hasnt('/3')
142         ->json_is('/0/name' => 'Manuel')
143         ->json_is('/1/name' => 'Manuela')
144         ->json_is('/2/name' => 'Manuelab');
145
146     # _match=ends_with
147     $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=ends_with')
148         ->status_is(200)
149         ->json_has('/0')
150         ->json_has('/1')
151         ->json_hasnt('/2')
152         ->json_is('/0/name' => 'Manuel')
153         ->json_is('/1/name' => 'Emanuel');
154
155     # _match=exact
156     $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=exact')
157         ->status_is(200)
158         ->json_has('/0')
159         ->json_hasnt('/1')
160         ->json_is('/0/name' => 'Manuel');
161
162     # _match=contains
163     $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=contains')
164         ->status_is(200)
165         ->json_has('/0')
166         ->json_has('/1')
167         ->json_has('/2')
168         ->json_has('/3')
169         ->json_hasnt('/4')
170         ->json_is('/0/name' => 'Manuel')
171         ->json_is('/1/name' => 'Manuela')
172         ->json_is('/2/name' => 'Manuelab')
173         ->json_is('/3/name' => 'Emanuel');
174
175     $schema->storage->txn_rollback;
176 };
177
178 subtest 'objects.search helper, sorting on mapped column' => sub {
179
180     plan tests => 14;
181
182     $schema->storage->txn_begin;
183
184     # Have complete control over the existing cities to ease testing
185     Koha::Cities->delete;
186
187     $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'A', city_country => 'Argentina' } });
188     $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'B', city_country => 'Argentina' } });
189
190     $t->get_ok('/cities?_order_by=%2Bname&_order_by=+country')
191       ->status_is(200)
192       ->json_has('/0')
193       ->json_has('/1')
194       ->json_hasnt('/2')
195       ->json_is('/0/name' => 'A')
196       ->json_is('/1/name' => 'B');
197
198     $t->get_ok('/cities?_order_by=-name')
199       ->status_is(200)
200       ->json_has('/0')
201       ->json_has('/1')
202       ->json_hasnt('/2')
203       ->json_is('/0/name' => 'B')
204       ->json_is('/1/name' => 'A');
205
206     $schema->storage->txn_rollback;
207 };
208
209 subtest 'objects.search helper, encoding' => sub {
210
211     plan tests => 5;
212
213     $schema->storage->txn_begin;
214
215     Koha::Cities->delete;
216
217     $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'A', city_country => 'Argentina' } });
218     $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'B', city_country => '❤Argentina❤' } });
219
220     $t->get_ok('/cities?q={"country": "❤Argentina❤"}')
221       ->status_is(200)
222       ->json_has('/0')
223       ->json_hasnt('/1')
224       ->json_is('/0/name' => 'B');
225
226     $schema->storage->txn_rollback;
227 };
228
229 subtest 'objects.search helper, embed' => sub {
230
231     plan tests => 2;
232
233     $schema->storage->txn_begin;
234
235     my $order = $builder->build_object({ class => 'Koha::Acquisition::Orders' });
236
237     $t->get_ok('/orders?order_id=' . $order->ordernumber)
238       ->json_is('/0',$order->to_api({ embed => ( { fund => {} } ) }));
239
240     $schema->storage->txn_rollback;
241 };
242
243 subtest 'objects.search helper, with path parameters and _match' => sub {
244     plan tests => 8;
245
246     $schema->storage->txn_begin;
247
248     Koha::Holds->search()->delete;
249
250     my $patron = Koha::Patrons->find(10);
251     $patron->delete if $patron;
252     $patron = $builder->build_object( { class => "Koha::Patrons" } );
253     $patron->borrowernumber(10)->store;
254     $builder->build_object(
255         {
256             class => "Koha::Holds",
257             value => { borrowernumber => $patron->borrowernumber }
258         }
259     );
260
261     $t->get_ok('/patrons/1/holds?_match=exact')
262       ->json_is('/count' => 0, 'there should be no holds for borrower 1 with _match=exact');
263
264     $t->get_ok('/patrons/1/holds?_match=contains')
265       ->json_is('/count' => 0, 'there should be no holds for borrower 1 with _match=contains');
266
267     $t->get_ok('/patrons/10/holds?_match=exact')
268       ->json_is('/count' => 1, 'there should be 1 hold for borrower 10 with _match=exact');
269
270     $t->get_ok('/patrons/10/holds?_match=contains')
271       ->json_is('/count' => 1, 'there should be 1 hold for borrower 10 with _match=contains');
272
273     $schema->storage->txn_rollback;
274 };
275
276 subtest 'object.search helper with query parameter' => sub {
277     plan tests => 4;
278
279     $schema->storage->txn_begin;
280
281     my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
282     my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
283     my $biblio1 = $builder->build_sample_biblio;
284     my $biblio2 = $builder->build_sample_biblio;
285     my $biblio3 = $builder->build_sample_biblio;
286     my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
287     my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
288     my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
289
290     $t->get_ok('/biblios' => json => {"suggestions.suggester.patron_id" => $patron1->borrowernumber })
291       ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
292
293     $t->get_ok('/biblios' => json => {"suggestions.suggester.patron_id" => $patron2->borrowernumber })
294       ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
295
296     $schema->storage->txn_rollback;
297 };
298
299 subtest 'object.search helper with q parameter' => sub {
300     plan tests => 4;
301
302     $schema->storage->txn_begin;
303
304     my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
305     my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
306     my $biblio1 = $builder->build_sample_biblio;
307     my $biblio2 = $builder->build_sample_biblio;
308     my $biblio3 = $builder->build_sample_biblio;
309     my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
310     my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
311     my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
312
313     $t->get_ok('/biblios?q={"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}')
314       ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
315
316     $t->get_ok('/biblios?q={"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}')
317       ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
318
319     $schema->storage->txn_rollback;
320 };
321
322 subtest 'object.search helper with x-koha-query header' => sub {
323     plan tests => 4;
324
325     $schema->storage->txn_begin;
326
327     my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
328     my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
329     my $biblio1 = $builder->build_sample_biblio;
330     my $biblio2 = $builder->build_sample_biblio;
331     my $biblio3 = $builder->build_sample_biblio;
332     my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
333     my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
334     my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
335
336     $t->get_ok('/biblios' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}'})
337       ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
338
339     $t->get_ok('/biblios' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}'})
340       ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
341
342     $schema->storage->txn_rollback;
343 };
344
345 subtest 'object.search helper with all query methods' => sub {
346     plan tests => 6;
347
348     $schema->storage->txn_begin;
349
350     my $patron1 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron1'} } );
351     my $patron2 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron2'} } );
352     my $biblio1 = $builder->build_sample_biblio;
353     my $biblio2 = $builder->build_sample_biblio;
354     my $biblio3 = $builder->build_sample_biblio;
355     my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
356     my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
357     my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
358
359     $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})
360       ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
361
362     $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})
363       ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
364
365     $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})
366       ->json_is('/count' => 0, 'there shouldn\'t be biblios where suggester has patron1 fistname and patron2 id');
367
368     $schema->storage->txn_rollback;
369 };
370
371 subtest 'object.search helper order by embedded columns' => sub {
372     plan tests => 3;
373
374     my $patron1 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron1'} } );
375     my $patron2 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron2'} } );
376     my $biblio1 = $builder->build_sample_biblio;
377     my $biblio2 = $builder->build_sample_biblio;
378     my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
379     my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
380
381     $t->get_ok('/biblios?_order_by=-suggestions.suggester.firstname' => json => [{"me.biblio_id" => $biblio1->biblionumber}, {"me.biblio_id" => $biblio2->biblionumber}])
382       ->json_is('/biblios/0/biblio_id' => $biblio2->biblionumber, 'Biblio 2 should be first')
383       ->json_is('/biblios/1/biblio_id' => $biblio1->biblionumber, 'Biblio 1 should be second');
384
385     $schema->storage->txn_begin;
386 }