Bug 16497: (follow-up) GET operations require staff access
[koha.git] / t / db_dependent / api / v1 / libraries.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::Libraries;
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     plan tests => 8;
43
44     $schema->storage->txn_begin;
45
46     # Create test context
47     my $library = $builder->build_object({ class => 'Koha::Libraries' });
48     my $another_library = $library->unblessed; # create a copy of $library but make
49     delete $another_library->{branchcode};     # sure branchcode will be regenerated
50     $another_library = $builder->build_object({ class => 'Koha::Libraries', value => $another_library });
51     my ( $borrowernumber, $session_id ) = create_user_and_session( { authorized => 1 } );
52
53     ## Authorized user tests
54     my $count_of_libraries = Koha::Libraries->search->count;
55     # Make sure we are returned with the correct amount of libraries
56     my $tx = $t->ua->build_tx( GET => '/api/v1/libraries' );
57     $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
58     $tx->req->env( { REMOTE_ADDR => $remote_address } );
59     $t->request_ok($tx)
60       ->status_is( 200, 'SWAGGER3.2.2' )
61       ->json_has('/'.($count_of_libraries-1).'/library_id')
62       ->json_hasnt('/'.($count_of_libraries).'/library_id');
63
64     subtest 'query parameters' => sub {
65
66         my $fields = {
67             name              => 'branchname',
68             address1          => 'branchaddress1',
69             address2          => 'branchaddress2',
70             address3          => 'branchaddress3',
71             postal_code       => 'branchzip',
72             city              => 'branchcity',
73             state             => 'branchstate',
74             country           => 'branchcountry',
75             phone             => 'branchphone',
76             fax               => 'branchfax',
77             email             => 'branchemail',
78             reply_to_email    => 'branchreplyto',
79             return_path_email => 'branchreturnpath',
80             url               => 'branchurl',
81             ip                => 'branchip',
82             notes             => 'branchnotes',
83             opac_info         => 'opac_info',
84         };
85
86         my $size = keys %{$fields};
87
88         plan tests => $size * 3;
89
90         foreach my $field ( keys %{$fields} ) {
91             my $model_field = $fields->{ $field };
92             $tx = $t->ua->build_tx( GET =>
93                          "/api/v1/libraries?$field=" . $library->$model_field );
94             $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
95             $tx->req->env( { REMOTE_ADDR => $remote_address } );
96             my $result =
97             $t->request_ok($tx)
98               ->status_is(200)
99               ->json_has( [ $library, $another_library ] );
100         }
101     };
102
103     # Warn on unsupported query parameter
104     $tx = $t->ua->build_tx( GET => '/api/v1/libraries?library_blah=blah' );
105     $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
106     $tx->req->env( { REMOTE_ADDR => $remote_address } );
107     $t->request_ok($tx)
108       ->status_is(400)
109       ->json_is( [{ path => '/query/library_blah', message => 'Malformed query string'}] );
110
111     $schema->storage->txn_rollback;
112 };
113
114 subtest 'get() tests' => sub {
115
116     plan tests => 6;
117
118     $schema->storage->txn_begin;
119
120     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
121     my ( $borrowernumber, $session_id ) =
122       create_user_and_session( { authorized => 1 } );
123
124     my $tx = $t->ua->build_tx( GET => "/api/v1/libraries/" . $library->branchcode );
125     $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
126     $tx->req->env( { REMOTE_ADDR => $remote_address } );
127     $t->request_ok($tx)
128       ->status_is( 200, 'SWAGGER3.2.2' )
129       ->json_is( '' => Koha::REST::V1::Library::_to_api( $library->TO_JSON ), 'SWAGGER3.3.2' );
130
131     my $non_existent_code = $library->branchcode;
132     $library->delete;
133
134     $tx = $t->ua->build_tx( GET => "/api/v1/libraries/" . $non_existent_code );
135     $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
136     $tx->req->env( { REMOTE_ADDR => $remote_address } );
137     $t->request_ok($tx)
138       ->status_is(404)
139       ->json_is( '/error' => 'Library not found' );
140
141     $schema->storage->txn_rollback;
142 };
143
144 subtest 'add() tests' => sub {
145
146     plan tests => 17;
147
148     $schema->storage->txn_begin;
149
150     my ( $unauthorized_borrowernumber, $unauthorized_session_id ) =
151       create_user_and_session( { authorized => 0 } );
152     my ( $authorized_borrowernumber, $authorized_session_id ) =
153       create_user_and_session( { authorized => 1 } );
154
155     my $library_obj = $builder->build_object({ class => 'Koha::Libraries' });
156     my $library     = Koha::REST::V1::Library::_to_api( $library_obj->TO_JSON );
157     $library_obj->delete;
158
159     # Unauthorized attempt to write
160     my $tx = $t->ua->build_tx( POST => "/api/v1/libraries" => json => $library );
161     $tx->req->cookies(
162         { name => 'CGISESSID', value => $unauthorized_session_id } );
163     $tx->req->env( { REMOTE_ADDR => $remote_address } );
164     $t->request_ok($tx)
165       ->status_is(403);
166
167     # Authorized attempt to write invalid data
168     my $library_with_invalid_field = { %$library };
169     $library_with_invalid_field->{'branchinvalid'} = 'Library invalid';
170
171     $tx = $t->ua->build_tx(
172         POST => "/api/v1/libraries" => json => $library_with_invalid_field );
173     $tx->req->cookies(
174         { name => 'CGISESSID', value => $authorized_session_id } );
175     $tx->req->env( { REMOTE_ADDR => $remote_address } );
176     $t->request_ok($tx)
177       ->status_is(400)
178       ->json_is(
179         "/errors" => [
180             {
181                 message => "Properties not allowed: branchinvalid.",
182                 path    => "/body"
183             }
184         ]
185     );
186
187     # Authorized attempt to write
188     $tx = $t->ua->build_tx( POST => "/api/v1/libraries" => json => $library );
189     $tx->req->cookies(
190         { name => 'CGISESSID', value => $authorized_session_id } );
191     $tx->req->env( { REMOTE_ADDR => $remote_address } );
192     $t->request_ok($tx)
193       ->status_is( 201, 'SWAGGER3.2.1' )
194       ->json_is( '' => $library, 'SWAGGER3.3.1' )
195       ->header_is( Location => '/api/v1/libraries/' . $library->{library_id}, 'SWAGGER3.4.1' );
196
197     # save the library_id
198     my $library_id = $library->{library_id};
199     # Authorized attempt to create with null id
200     $library->{library_id} = undef;
201     $tx = $t->ua->build_tx(
202         POST => "/api/v1/libraries" => json => $library );
203     $tx->req->cookies(
204         { 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     $library->{library_id} = $library_id;
212     $tx = $t->ua->build_tx(
213         POST => "/api/v1/libraries" => json => $library );
214     $tx->req->cookies(
215         { name => 'CGISESSID', value => $authorized_session_id } );
216     $tx->req->env( { REMOTE_ADDR => $remote_address } );
217
218     warning_like {
219         $t->request_ok($tx)
220           ->status_is(409)
221           ->json_has( '/error' => "Fails when trying to add an existing library_id")
222           ->json_is(  '/conflict', 'PRIMARY' ); } # WTF
223         qr/^DBD::mysql::st execute failed: Duplicate entry '(.*)' for key 'PRIMARY'/;
224
225     $schema->storage->txn_rollback;
226 };
227
228 subtest 'update() tests' => sub {
229     plan tests => 13;
230
231     $schema->storage->txn_begin;
232
233     my ( $unauthorized_borrowernumber, $unauthorized_session_id ) =
234       create_user_and_session( { authorized => 0 } );
235     my ( $authorized_borrowernumber, $authorized_session_id ) =
236       create_user_and_session( { authorized => 1 } );
237
238     my $library    = $builder->build_object({ class => 'Koha::Libraries' });
239     my $library_id = $library->branchcode;
240
241     # Unauthorized attempt to update
242     my $tx = $t->ua->build_tx( PUT => "/api/v1/libraries/$library_id"
243         => json => { branchname => 'New unauthorized name change' } );
244     $tx->req->cookies(
245         { 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 $library_with_missing_field = {
252         address1 => "New library address",
253     };
254
255     $tx = $t->ua->build_tx( PUT => "/api/v1/libraries/$library_id" =>
256                             json => $library_with_missing_field );
257     $tx->req->cookies(
258         { name => 'CGISESSID', value => $authorized_session_id } );
259     $tx->req->env( { REMOTE_ADDR => $remote_address } );
260     $t->request_ok($tx)
261       ->status_is(400)
262       ->json_has( "/errors" =>
263           [ { message => "Missing property.", path => "/body/address2" } ]
264       );
265
266     my $deleted_library = $builder->build_object( { class => 'Koha::Libraries' } );
267     my $library_with_updated_field = Koha::REST::V1::Library::_to_api( $deleted_library->TO_JSON );
268     $library_with_updated_field->{library_id} = $library_id;
269     $deleted_library->delete;
270
271     $tx = $t->ua->build_tx( PUT => "/api/v1/libraries/$library_id" => json => $library_with_updated_field );
272     $tx->req->cookies( { name => 'CGISESSID', value => $authorized_session_id } );
273     $tx->req->env( { REMOTE_ADDR => $remote_address } );
274     $t->request_ok($tx)
275       ->status_is(200, 'SWAGGER3.2.1')
276       ->json_is( '' => $library_with_updated_field, 'SWAGGER3.3.3' );
277
278     # Authorized attempt to write invalid data
279     my $library_with_invalid_field = { %$library_with_updated_field };
280     $library_with_invalid_field->{'branchinvalid'} = 'Library invalid';
281
282     $tx = $t->ua->build_tx(
283         PUT => "/api/v1/libraries/$library_id" => json => $library_with_invalid_field );
284     $tx->req->cookies(
285         { name => 'CGISESSID', value => $authorized_session_id } );
286     $tx->req->env( { REMOTE_ADDR => $remote_address } );
287     $t->request_ok($tx)
288       ->status_is(400)
289       ->json_is(
290         "/errors" => [
291             {
292                 message => "Properties not allowed: branchinvalid.",
293                 path    => "/body"
294             }
295         ]
296     );
297
298     my $non_existent_code = 'nope'.int(rand(10000));
299     $tx =
300       $t->ua->build_tx( PUT => "/api/v1/libraries/$non_existent_code" => json =>
301           $library_with_updated_field );
302     $tx->req->cookies(
303         { name => 'CGISESSID', value => $authorized_session_id } );
304     $tx->req->env( { REMOTE_ADDR => $remote_address } );
305     $t->request_ok($tx)
306       ->status_is(404);
307
308     $schema->storage->txn_rollback;
309 };
310
311 subtest 'delete() tests' => sub {
312     plan tests => 7;
313
314     $schema->storage->txn_begin;
315
316     my ( $unauthorized_borrowernumber, $unauthorized_session_id ) =
317       create_user_and_session( { authorized => 0 } );
318     my ( $authorized_borrowernumber, $authorized_session_id ) =
319       create_user_and_session( { authorized => 1 } );
320
321     my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
322
323     # Unauthorized attempt to delete
324     my $tx = $t->ua->build_tx( DELETE => "/api/v1/libraries/$branchcode" );
325     $tx->req->cookies(
326         { name => 'CGISESSID', value => $unauthorized_session_id } );
327     $tx->req->env( { REMOTE_ADDR => $remote_address } );
328     $t->request_ok($tx)
329       ->status_is(403);
330
331     $tx = $t->ua->build_tx( DELETE => "/api/v1/libraries/$branchcode" );
332     $tx->req->cookies(
333         { name => 'CGISESSID', value => $authorized_session_id } );
334     $tx->req->env( { REMOTE_ADDR => $remote_address } );
335     $t->request_ok($tx)
336       ->status_is(204, 'SWAGGER3.2.4')
337       ->content_is('', 'SWAGGER3.3.4');
338
339     $tx = $t->ua->build_tx( DELETE => "/api/v1/libraries/$branchcode" );
340     $tx->req->cookies(
341         { name => 'CGISESSID', value => $authorized_session_id } );
342     $tx->req->env( { REMOTE_ADDR => $remote_address } );
343     $t->request_ok($tx)
344       ->status_is(404);
345
346     $schema->storage->txn_rollback;
347 };
348
349 sub create_user_and_session {
350
351     my $args  = shift;
352     my $flags = ( $args->{authorized} ) ? $args->{authorized} : 0;
353     my $dbh   = C4::Context->dbh;
354
355     my $user = $builder->build(
356         {
357             source => 'Borrower',
358             value  => {
359                 flags => $flags
360             }
361         }
362     );
363
364     # Create a session for the authorized user
365     my $session = C4::Auth::get_session('');
366     $session->param( 'number',   $user->{borrowernumber} );
367     $session->param( 'id',       $user->{userid} );
368     $session->param( 'ip',       '127.0.0.1' );
369     $session->param( 'lasttime', time() );
370     $session->flush;
371
372     if ( $args->{authorized} ) {
373         $dbh->do( "
374             INSERT INTO user_permissions (borrowernumber,module_bit,code)
375             VALUES (?,3,'parameters_remaining_permissions')", undef,
376             $user->{borrowernumber} );
377     }
378
379     return ( $user->{borrowernumber}, $session->id );
380 }
381
382 1;