Bug 19661: REST API - Funds Endpoint
[koha.git] / t / db_dependent / api / v1 / cities.t
1 #!/usr/bin/env perl
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation; either version 3 of the License, or (at your option) any later
8 # version.
9 #
10 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with Koha; if not, write to the Free Software Foundation, Inc.,
16 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18 use Modern::Perl;
19
20 use Test::More tests => 5;
21 use Test::Mojo;
22 use Test::Warn;
23
24 use t::lib::TestBuilder;
25 use t::lib::Mocks;
26
27 use C4::Auth;
28 use Koha::Cities;
29 use Koha::Database;
30
31 my $schema  = Koha::Database->new->schema;
32 my $builder = t::lib::TestBuilder->new;
33
34 # FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling
35 # this affects the other REST api tests
36 t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
37
38 my $remote_address = '127.0.0.1';
39 my $t              = Test::Mojo->new('Koha::REST::V1');
40
41 subtest 'list() tests' => sub {
42
43     plan tests => 18;
44
45     $schema->storage->txn_begin;
46
47     Koha::Cities->search->delete;
48     my ( $borrowernumber, $session_id ) =
49       create_user_and_session( { authorized => 0 } );
50
51     ## Authorized user tests
52     # No cities, so empty array should be returned
53     my $tx = $t->ua->build_tx( GET => '/api/v1/cities' );
54     $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
55     $tx->req->env( { REMOTE_ADDR => $remote_address } );
56     $t->request_ok($tx)
57       ->status_is(200)
58       ->json_is( [] );
59
60     my $city = $builder->build_object({ class => 'Koha::Cities' });
61
62     # One city created, should get returned
63     $tx = $t->ua->build_tx( GET => '/api/v1/cities' );
64     $tx->req->cookies({ name => 'CGISESSID', value => $session_id });
65     $tx->req->env({ REMOTE_ADDR => $remote_address });
66     $t->request_ok($tx)
67       ->status_is(200)
68       ->json_is( [Koha::REST::V1::Cities::_to_api( $city->TO_JSON )] );
69
70     my $another_city = $builder->build_object(
71         { class => 'Koha::Cities', value => { city_country => $city->city_country } } );
72     my $city_with_another_country = $builder->build_object({ class => 'Koha::Cities' });
73
74     # Two cities created, they should both be returned
75     $tx = $t->ua->build_tx( GET => '/api/v1/cities' );
76     $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
77     $tx->req->env( { REMOTE_ADDR => $remote_address } );
78     $t->request_ok($tx)->status_is(200)
79       ->json_is([Koha::REST::V1::Cities::_to_api($city->TO_JSON),
80                  Koha::REST::V1::Cities::_to_api($another_city->TO_JSON),
81                  Koha::REST::V1::Cities::_to_api($city_with_another_country->TO_JSON)
82                  ] );
83
84     # Filtering works, two cities sharing city_country
85     $tx = $t->ua->build_tx( GET => "/api/v1/cities?country=" . $city->city_country );
86     $tx->req->cookies({ name => 'CGISESSID', value => $session_id });
87     $tx->req->env({ REMOTE_ADDR => $remote_address });
88     $t->request_ok($tx)
89       ->status_is(200)
90       ->json_is([ Koha::REST::V1::Cities::_to_api($city->TO_JSON),
91                   Koha::REST::V1::Cities::_to_api($another_city->TO_JSON)
92                   ]);
93
94     $tx = $t->ua->build_tx( GET => "/api/v1/cities?name=" . $city->city_name );
95     $tx->req->cookies({ name => 'CGISESSID', value => $session_id });
96     $tx->req->env({ REMOTE_ADDR => $remote_address });
97     $t->request_ok($tx)
98       ->status_is(200)
99       ->json_is( [Koha::REST::V1::Cities::_to_api($city->TO_JSON)] );
100
101     # Warn on unsupported query parameter
102     $tx = $t->ua->build_tx( GET => '/api/v1/cities?city_blah=blah' );
103     $tx->req->cookies({ name => 'CGISESSID', value => $session_id });
104     $tx->req->env({ REMOTE_ADDR => $remote_address });
105     $t->request_ok($tx)
106       ->status_is(400)
107       ->json_is( [{ path => '/query/city_blah', message => 'Malformed query string'}] );
108
109     $schema->storage->txn_rollback;
110 };
111
112 subtest 'get() tests' => sub {
113
114     plan tests => 6;
115
116     $schema->storage->txn_begin;
117
118     my $city = $builder->build_object({ class => 'Koha::Cities' });
119     my ( $borrowernumber, $session_id ) = create_user_and_session({ authorized => 0 });
120
121     my $tx = $t->ua->build_tx( GET => "/api/v1/cities/" . $city->id );
122     $tx->req->cookies({ name => 'CGISESSID', value => $session_id });
123     $tx->req->env({ REMOTE_ADDR => $remote_address });
124     $t->request_ok($tx)
125       ->status_is(200)
126       ->json_is(Koha::REST::V1::Cities::_to_api($city->TO_JSON));
127
128     my $city_to_delete = $builder->build_object({ class => 'Koha::Cities' });
129     my $non_existent_id = $city_to_delete->id;
130     $city_to_delete->delete;
131
132     $tx = $t->ua->build_tx( GET => "/api/v1/cities/" . $non_existent_id );
133     $tx->req->cookies({ name => 'CGISESSID', value => $session_id });
134     $tx->req->env({ REMOTE_ADDR => $remote_address });
135     $t->request_ok($tx)
136       ->status_is(404)
137       ->json_is( '/error' => 'City not found' );
138
139     $schema->storage->txn_rollback;
140 };
141
142 subtest 'add() tests' => sub {
143
144     plan tests => 17;
145
146     $schema->storage->txn_begin;
147
148     my ( $unauthorized_borrowernumber, $unauthorized_session_id ) =
149       create_user_and_session( { authorized => 0 } );
150     my ( $authorized_borrowernumber, $authorized_session_id ) =
151       create_user_and_session( { authorized => 1 } );
152     my $city = {
153         name        => "City Name",
154         state       => "City State",
155         postal_code => "City Zipcode",
156         country     => "City Country"
157     };
158
159     # Unauthorized attempt to write
160     my $tx = $t->ua->build_tx( POST => "/api/v1/cities/" => json => $city );
161     $tx->req->cookies({ name => 'CGISESSID', value => $unauthorized_session_id });
162     $tx->req->env({ REMOTE_ADDR => $remote_address });
163     $t->request_ok($tx)
164       ->status_is(403);
165
166     # Authorized attempt to write invalid data
167     my $city_with_invalid_field = {
168         blah        => "City Blah",
169         state       => "City State",
170         postal_code => "City Zipcode",
171         country     => "City Country"
172     };
173
174     $tx = $t->ua->build_tx( POST => "/api/v1/cities/" => json => $city_with_invalid_field );
175     $tx->req->cookies({ name => 'CGISESSID', value => $authorized_session_id });
176     $tx->req->env( { REMOTE_ADDR => $remote_address } );
177     $t->request_ok($tx)
178       ->status_is(400)
179       ->json_is(
180         "/errors" => [
181             {
182                 message => "Properties not allowed: blah.",
183                 path    => "/body"
184             }
185         ]
186       );
187
188     # Authorized attempt to write
189     $tx = $t->ua->build_tx( POST => "/api/v1/cities/" => json => $city );
190     $tx->req->cookies({ name => 'CGISESSID', value => $authorized_session_id });
191     $tx->req->env({ REMOTE_ADDR => $remote_address });
192     my $city_id =
193       $t->request_ok($tx)
194         ->status_is(200)
195         ->json_is( '/name'        => $city->{name} )
196         ->json_is( '/state'       => $city->{state} )
197         ->json_is( '/postal_code' => $city->{postal_code} )
198         ->json_is( '/country'     => $city->{country} )
199         ->tx->res->json->{city_id};
200
201     # Authorized attempt to create with null id
202     $city->{city_id} = undef;
203     $tx = $t->ua->build_tx( POST => "/api/v1/cities/" => json => $city );
204     $tx->req->cookies({ name => 'CGISESSID', value => $authorized_session_id });
205     $tx->req->env({ REMOTE_ADDR => $remote_address });
206     $t->request_ok($tx)
207       ->status_is(400)
208       ->json_has('/errors');
209
210     # Authorized attempt to create with existing id
211     $city->{city_id} = $city_id;
212     $tx = $t->ua->build_tx( POST => "/api/v1/cities/" => json => $city );
213     $tx->req->cookies({ name => 'CGISESSID', value => $authorized_session_id });
214     $tx->req->env({ REMOTE_ADDR => $remote_address });
215     $t->request_ok($tx)
216       ->status_is(400)
217       ->json_is(
218         "/errors" => [
219             {
220                 message => "Read-only.",
221                 path    => "/body/city_id"
222             }
223         ]
224     );
225
226     $schema->storage->txn_rollback;
227 };
228
229 subtest 'update() tests' => sub {
230
231     plan tests => 15;
232
233     $schema->storage->txn_begin;
234
235     my ( $unauthorized_borrowernumber, $unauthorized_session_id ) =
236       create_user_and_session( { authorized => 0 } );
237     my ( $authorized_borrowernumber, $authorized_session_id ) =
238       create_user_and_session( { authorized => 1 } );
239
240     my $city_id = $builder->build_object({ class => 'Koha::Cities' } )->id;
241
242     # Unauthorized attempt to update
243     my $tx = $t->ua->build_tx( PUT => "/api/v1/cities/$city_id" => json =>
244           { name => 'New unauthorized name change' } );
245     $tx->req->cookies({ name => 'CGISESSID', value => $unauthorized_session_id });
246     $tx->req->env({ REMOTE_ADDR => $remote_address });
247     $t->request_ok($tx)
248       ->status_is(403);
249
250     # Attempt partial update on a PUT
251     my $city_with_missing_field = {
252         name    => 'New name',
253         state   => 'New state',
254         country => 'New country'
255     };
256
257     $tx = $t->ua->build_tx(
258         PUT => "/api/v1/cities/$city_id" => json => $city_with_missing_field );
259     $tx->req->cookies({ name => 'CGISESSID', value => $authorized_session_id });
260     $tx->req->env({ REMOTE_ADDR => $remote_address });
261     $t->request_ok($tx)->status_is(400)
262       ->json_is( "/errors" =>
263           [ { message => "Missing property.", path => "/body/postal_code" } ]
264       );
265
266     # Full object update on PUT
267     my $city_with_updated_field = {
268         name        => "London",
269         state       => "City State",
270         postal_code => "City Zipcode",
271         country     => "City Country"
272     };
273
274     $tx = $t->ua->build_tx( PUT => "/api/v1/cities/$city_id" => json => $city_with_updated_field );
275     $tx->req->cookies({ name => 'CGISESSID', value => $authorized_session_id });
276     $tx->req->env({ REMOTE_ADDR => $remote_address });
277     $t->request_ok($tx)
278       ->status_is(200)
279       ->json_is( '/name' => 'London' );
280
281     # Authorized attempt to write invalid data
282     my $city_with_invalid_field = {
283         blah        => "City Blah",
284         state       => "City State",
285         postal_code => "City Zipcode",
286         country     => "City Country"
287     };
288
289     $tx = $t->ua->build_tx( PUT => "/api/v1/cities/$city_id" => json => $city_with_invalid_field );
290     $tx->req->cookies({ name => 'CGISESSID', value => $authorized_session_id });
291     $tx->req->env({ REMOTE_ADDR => $remote_address });
292     $t->request_ok($tx)
293       ->status_is(400)
294       ->json_is(
295         "/errors" => [
296             {
297                 message => "Properties not allowed: blah.",
298                 path    => "/body"
299             }
300         ]
301     );
302
303     my $city_to_delete = $builder->build_object({ class => 'Koha::Cities' });
304     my $non_existent_id = $city_to_delete->id;
305     $city_to_delete->delete;
306
307     $tx = $t->ua->build_tx( PUT => "/api/v1/cities/$non_existent_id" => json =>
308           $city_with_updated_field );
309     $tx->req->cookies({ name => 'CGISESSID', value => $authorized_session_id });
310     $tx->req->env({ REMOTE_ADDR => $remote_address });
311     $t->request_ok($tx)
312       ->status_is(404);
313
314     $schema->storage->txn_rollback;
315
316     # Wrong method (POST)
317     $city_with_updated_field->{city_id} = 2;
318
319     $tx = $t->ua->build_tx(
320         POST => "/api/v1/cities/$city_id" => json => $city_with_updated_field );
321     $tx->req->cookies({ name => 'CGISESSID', value => $authorized_session_id });
322     $tx->req->env({ REMOTE_ADDR => $remote_address });
323     $t->request_ok($tx)
324       ->status_is(404);
325 };
326
327 subtest 'delete() tests' => sub {
328
329     plan tests => 7;
330
331     $schema->storage->txn_begin;
332
333     my ( $unauthorized_borrowernumber, $unauthorized_session_id ) =
334       create_user_and_session( { authorized => 0 } );
335     my ( $authorized_borrowernumber, $authorized_session_id ) =
336       create_user_and_session( { authorized => 1 } );
337
338     my $city_id = $builder->build_object({ class => 'Koha::Cities' })->id;
339
340     # Unauthorized attempt to delete
341     my $tx = $t->ua->build_tx( DELETE => "/api/v1/cities/$city_id" );
342     $tx->req->cookies({ name => 'CGISESSID', value => $unauthorized_session_id });
343     $tx->req->env({ REMOTE_ADDR => $remote_address });
344     $t->request_ok($tx)
345       ->status_is(403);
346
347     $tx = $t->ua->build_tx( DELETE => "/api/v1/cities/$city_id" );
348     $tx->req->cookies({ name => 'CGISESSID', value => $authorized_session_id });
349     $tx->req->env({ REMOTE_ADDR => $remote_address });
350     $t->request_ok($tx)
351       ->status_is(200)
352       ->content_is('""');
353
354     $tx = $t->ua->build_tx( DELETE => "/api/v1/cities/$city_id" );
355     $tx->req->cookies({ name => 'CGISESSID', value => $authorized_session_id });
356     $tx->req->env({ REMOTE_ADDR => $remote_address });
357     $t->request_ok($tx)
358       ->status_is(404);
359
360     $schema->storage->txn_rollback;
361 };
362
363 sub create_user_and_session {
364
365     my $args  = shift;
366     my $flags = ( $args->{authorized} ) ? $args->{authorized} : 0;
367     my $dbh   = C4::Context->dbh;
368
369     my $user = $builder->build(
370         {
371             source => 'Borrower',
372             value  => {
373                 flags => $flags
374             }
375         }
376     );
377
378     # Create a session for the authorized user
379     my $session = C4::Auth::get_session('');
380     $session->param( 'number',   $user->{borrowernumber} );
381     $session->param( 'id',       $user->{userid} );
382     $session->param( 'ip',       '127.0.0.1' );
383     $session->param( 'lasttime', time() );
384     $session->flush;
385
386     if ( $args->{authorized} ) {
387         $dbh->do( "
388             INSERT INTO user_permissions (borrowernumber,module_bit,code)
389             VALUES (?,3,'parameters_remaining_permissions')", undef,
390             $user->{borrowernumber} );
391     }
392
393     return ( $user->{borrowernumber}, $session->id );
394 }
395