3 # This file is part of Koha.
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.
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.
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>.
20 use Test::More tests => 101;
24 use t::lib::TestBuilder;
29 use C4::Circulation qw( AddIssue AddReturn CanBookBeIssued );
32 use Koha::DateUtils qw( dt_from_string output_pref );
35 my $schema = Koha::Database->schema;
36 my $builder = t::lib::TestBuilder->new;
38 t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
39 my $t = Test::Mojo->new('Koha::REST::V1');
41 $schema->storage->txn_begin;
43 my $dbh = C4::Context->dbh;
45 my $librarian = $builder->build_object({
46 class => 'Koha::Patrons',
47 value => { flags => 2 }
49 my $password = 'thePassword123';
50 $librarian->set_password({ password => $password, skip_validation => 1 });
51 my $userid = $librarian->userid;
53 my $patron = $builder->build_object({
54 class => 'Koha::Patrons',
55 value => { flags => 0 }
57 my $unauth_password = 'thePassword000';
58 $patron->set_password({ password => $unauth_password, skip_validattion => 1 });
59 my $unauth_userid = $patron->userid;
60 my $patron_id = $patron->borrowernumber;
62 my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
64 $t->get_ok( "//$userid:$password@/api/v1/checkouts?patron_id=$patron_id" )
68 my $notexisting_patron_id = $patron_id + 1;
69 $t->get_ok( "//$userid:$password@/api/v1/checkouts?patron_id=$notexisting_patron_id" )
73 Koha::CirculationRules->set_rules(
75 categorycode => undef,
86 my $item1 = $builder->build_sample_item;
87 my $item2 = $builder->build_sample_item;
88 my $item3 = $builder->build_sample_item;
89 my $item4 = $builder->build_sample_item;
91 my $date_due = DateTime->now->add(weeks => 2);
92 my $issue1 = C4::Circulation::AddIssue($patron->unblessed, $item1->barcode, $date_due);
93 my $date_due1 = Koha::DateUtils::dt_from_string( $issue1->date_due );
94 my $issue2 = C4::Circulation::AddIssue($patron->unblessed, $item2->barcode, $date_due);
95 my $date_due2 = Koha::DateUtils::dt_from_string( $issue2->date_due );
96 my $issue3 = C4::Circulation::AddIssue($librarian->unblessed, $item3->barcode, $date_due);
97 my $date_due3 = Koha::DateUtils::dt_from_string( $issue3->date_due );
98 my $issue4 = C4::Circulation::AddIssue($patron->unblessed, $item4->barcode);
99 C4::Circulation::AddReturn($item4->barcode, $branchcode);
101 $t->get_ok( "//$userid:$password@/api/v1/checkouts?patron_id=$patron_id" )
103 ->json_is('/0/patron_id' => $patron_id)
104 ->json_is('/0/item_id' => $item1->itemnumber)
105 ->json_is('/0/due_date' => output_pref({ dateformat => "rfc3339", dt => $date_due1 }) )
106 ->json_is('/1/patron_id' => $patron_id)
107 ->json_is('/1/item_id' => $item2->itemnumber)
108 ->json_is('/1/due_date' => output_pref({ dateformat => "rfc3339", dt => $date_due2 }) )
111 # Test checked_in parameter, zero means, the response is same as without it
112 $t->get_ok( "//$userid:$password@/api/v1/checkouts?patron_id=$patron_id&checked_in=0" )
114 ->json_is('/0/patron_id' => $patron_id)
115 ->json_is('/0/item_id' => $item1->itemnumber)
116 ->json_is('/0/due_date' => output_pref({ dateformat => "rfc3339", dt => $date_due1 }) )
117 ->json_is('/1/patron_id' => $patron_id)
118 ->json_is('/1/item_id' => $item2->itemnumber)
119 ->json_is('/1/due_date' => output_pref({ dateformat => "rfc3339", dt => $date_due2 }) )
122 # Test checked_in parameter, one measn, the checked in checkout is in the response too
123 $t->get_ok( "//$userid:$password@/api/v1/checkouts?patron_id=$patron_id&checked_in=1" )
125 ->json_is('/0/patron_id' => $patron_id)
126 ->json_is('/0/item_id' => $item4->itemnumber)
129 $t->get_ok( "//$unauth_userid:$unauth_password@/api/v1/checkouts/" . $issue3->issue_id )
131 ->json_is({ error => "Authorization failure. Missing required permission(s).",
132 required_permissions => { circulate => "circulate_remaining_permissions" }
135 $t->get_ok( "//$userid:$password@/api/v1/checkouts?patron_id=$patron_id")
137 ->json_is('/0/patron_id' => $patron_id)
138 ->json_is('/0/item_id' => $item1->itemnumber)
139 ->json_is('/0/due_date' => output_pref({ dateformat => "rfc3339", dt => $date_due1 }) )
140 ->json_is('/1/patron_id' => $patron_id)
141 ->json_is('/1/item_id' => $item2->itemnumber)
142 ->json_is('/1/due_date' => output_pref({ dateformat => "rfc3339", dt => $date_due2 }) )
145 $t->get_ok( "//$userid:$password@/api/v1/checkouts?patron_id=$patron_id&_per_page=1&_page=1")
147 ->header_is('X-Total-Count', '2')
148 ->header_like('Link', qr|rel="next"|)
149 ->header_like('Link', qr|rel="first"|)
150 ->header_like('Link', qr|rel="last"|)
151 ->json_is('/0/patron_id' => $patron_id)
152 ->json_is('/0/item_id' => $item1->itemnumber)
153 ->json_is('/0/due_date' => output_pref({ dateformat => "rfc3339", dt => $date_due1 }) )
156 $t->get_ok( "//$userid:$password@/api/v1/checkouts?patron_id=$patron_id&_per_page=1&_page=2")
158 ->header_is('X-Total-Count', '2')
159 ->header_like('Link', qr|rel="prev"|)
160 ->header_like('Link', qr|rel="first"|)
161 ->header_like('Link', qr|rel="last"|)
162 ->json_is('/0/patron_id' => $patron_id)
163 ->json_is('/0/item_id' => $item2->itemnumber)
164 ->json_is('/0/due_date' => output_pref({ dateformat => "rfc3339", dt => $date_due2 }) )
167 $t->get_ok( "//$userid:$password@/api/v1/checkouts/" . $issue1->issue_id)
169 ->json_is('/patron_id' => $patron_id)
170 ->json_is('/item_id' => $item1->itemnumber)
171 ->json_is('/due_date' => output_pref({ dateformat => "rfc3339", dt => $date_due1 }) )
174 $t->get_ok( "//$userid:$password@/api/v1/checkouts/" . $issue1->issue_id)
176 ->json_is('/due_date' => output_pref({ dateformat => "rfc3339", dt => $date_due1 }) );
178 $t->get_ok( "//$userid:$password@/api/v1/checkouts/" . $issue2->issue_id)
180 ->json_is('/due_date' => output_pref( { dateformat => "rfc3339", dt => $date_due2 }) );
182 my $expected_datedue = $date_due
183 ->set_time_zone('local')
185 ->set(hour => 23, minute => 59, second => 0);
187 $t->post_ok ( "//$userid:$password@/api/v1/checkouts/" . $issue1->issue_id . "/renewal" )
189 ->json_is('/due_date' => output_pref( { dateformat => "rfc3339", dt => $expected_datedue }) )
190 ->header_is(Location => "/api/v1/checkouts/" . $issue1->issue_id . "/renewal");
192 my $renewal = $issue1->renewals->last;
193 is( $renewal->renewal_type, 'Manual', 'Manual renewal recorded' );
195 $t->get_ok ( "//$userid:$password@/api/v1/checkouts/" . $issue1->issue_id . "/renewals" )
197 ->json_is('/0/checkout_id' => $issue1->issue_id)
198 ->json_is('/0/interface' => 'api')
199 ->json_is('/0/renewer_id' => $librarian->borrowernumber );
201 $t->post_ok( "//$unauth_userid:$unauth_password@/api/v1/checkouts/" . $issue3->issue_id . "/renewal" )
203 ->json_is({ error => "Authorization failure. Missing required permission(s).",
204 required_permissions => { circulate => "circulate_remaining_permissions" }
207 $t->get_ok( "//$userid:$password@/api/v1/checkouts/" . $issue2->issue_id . "/allows_renewal")
210 allows_renewal => Mojo::JSON->true,
212 unseen_renewals => 0,
213 current_renewals => 0,
217 $t->post_ok( "//$userid:$password@/api/v1/checkouts/" . $issue2->issue_id . "/renewal" )
219 ->json_is('/due_date' => output_pref({ dateformat => "rfc3339", dt => $expected_datedue}) )
220 ->header_is(Location => "/api/v1/checkouts/" . $issue2->issue_id . "/renewal");
223 $t->post_ok( "//$userid:$password@/api/v1/checkouts/" . $issue1->issue_id . "/renewal" )
225 ->json_is({ error => 'Renewal not authorized (too_many)' });
227 $t->get_ok( "//$userid:$password@/api/v1/checkouts/" . $issue2->issue_id . "/allows_renewal")
230 allows_renewal => Mojo::JSON->false,
232 unseen_renewals => 0,
233 current_renewals => 1,
237 $schema->storage->txn_rollback;
239 subtest 'get_availability' => sub {
243 $schema->storage->txn_begin;
244 my $librarian = $builder->build_object(
246 class => 'Koha::Patrons',
247 value => { flags => 2 }
250 my $password = 'thePassword123';
251 $librarian->set_password( { password => $password, skip_validation => 1 } );
252 my $userid = $librarian->userid;
254 my $patron = $builder->build_object(
256 class => 'Koha::Patrons',
257 value => { flags => 0 }
260 my $unauth_password = 'thePassword000';
261 $patron->set_password(
262 { password => $unauth_password, skip_validattion => 1 } );
263 my $unauth_userid = $patron->userid;
264 my $patron_id = $patron->borrowernumber;
266 my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
268 my $item1 = $builder->build_sample_item;
269 my $item1_id = $item1->id;
271 my %issuingimpossible = ();
272 my %needsconfirmation = ();
275 my $mocked_circ = Test::MockModule->new('C4::Circulation');
279 return ( \%issuingimpossible, \%needsconfirmation, \%alerts, \%messages );
284 "//$unauth_userid:$unauth_password@/api/v1/checkouts/availability?item_id=$item1_id&patron_id=$patron_id"
285 )->status_is(403)->json_is(
287 error => "Authorization failure. Missing required permission(s).",
288 required_permissions =>
289 { circulate => "circulate_remaining_permissions" }
295 "//$userid:$password@/api/v1/checkouts/availability?item_id=$item1_id&patron_id=$patron_id"
296 )->status_is(200)->json_is( '/blockers' => {} )
297 ->json_is( '/confirms' => {} )->json_is( '/warnings' => {} )
298 ->json_has( '/confirmation_token');
301 %issuingimpossible = ( GNA => 1 );
303 "//$userid:$password@/api/v1/checkouts/availability?item_id=$item1_id&patron_id=$patron_id"
304 )->status_is(200)->json_is( '/blockers' => { GNA => 1 } )
305 ->json_is( '/confirms' => {} )->json_is( '/warnings' => {} )
306 ->json_has( '/confirmation_token');
307 %issuingimpossible = ();
310 %alerts = ( alert1 => "this is an alert" );
311 %messages = ( message1 => "this is a message" );
313 "//$userid:$password@/api/v1/checkouts/availability?item_id=$item1_id&patron_id=$patron_id"
314 )->status_is(200)->json_is( '/blockers' => {} )
315 ->json_is( '/confirms' => {} )
316 ->json_is( '/warnings' =>
317 { alert1 => "this is an alert", message1 => "this is a message" } )
318 ->json_has( '/confirmation_token');
323 %needsconfirmation = ( confirm1 => 1, confirm2 => 'please' );
324 my $token = Koha::Token->new->generate_jwt( { id => $librarian->id . ":" . $item1_id . ":confirm1:confirm2:please" });
326 "//$userid:$password@/api/v1/checkouts/availability?item_id=$item1_id&patron_id=$patron_id"
327 )->status_is(200)->json_is( '/blockers' => {} )
328 ->json_is( '/confirms' => { confirm1 => 1, confirm2 => 'please' } )
329 ->json_is( '/warnings' => {} )
330 ->json_has( '/confirmation_token');
331 my $confirmation_token = $t->tx->res->json('/confirmation_token');
333 Koha::Token->new->check_jwt(
335 id => $librarian->id . ":"
337 . ":confirm1:confirm2:please",
338 token => $confirmation_token
344 $schema->storage->txn_rollback;
347 subtest 'add checkout' => sub {
351 $schema->storage->txn_begin;
352 my $librarian = $builder->build_object(
354 class => 'Koha::Patrons',
355 value => { flags => 2 }
358 my $password = 'thePassword123';
359 $librarian->set_password( { password => $password, skip_validation => 1 } );
360 my $userid = $librarian->userid;
362 my $patron = $builder->build_object(
364 class => 'Koha::Patrons',
365 value => { flags => 0 }
368 my $unauth_password = 'thePassword000';
369 $patron->set_password(
370 { password => $unauth_password, skip_validattion => 1 } );
371 my $unauth_userid = $patron->userid;
372 my $patron_id = $patron->borrowernumber;
374 my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
376 my $item1 = $builder->build_sample_item;
377 my $item1_id = $item1->id;
379 my %issuingimpossible = ();
380 my %needsconfirmation = ();
383 my $mocked_circ = Test::MockModule->new('C4::Circulation');
387 return ( \%issuingimpossible, \%needsconfirmation, \%alerts, \%messages );
392 "//$unauth_userid:$unauth_password@/api/v1/checkouts" => json =>
393 { item_id => $item1_id, patron_id => $patron_id } )->status_is(403)
396 error => "Authorization failure. Missing required permission(s).",
397 required_permissions =>
398 { circulate => "circulate_remaining_permissions" }
402 $t->post_ok( "//$userid:$password@/api/v1/checkouts" => json =>
403 { item_id => $item1_id, patron_id => $patron_id } )->status_is(201);
406 %needsconfirmation = ( confirm1 => 1, confirm2 => 'please' );
408 "//$userid:$password@/api/v1/checkouts" => json => {
409 item_id => $item1_id,
410 patron_id => $patron_id,
414 my $token = Koha::Token->new->generate_jwt(
416 id => $librarian->id . ":"
418 . ":confirm1:confirm2:please"
422 "//$userid:$password@/api/v1/checkouts?confirmation=$token" => json => {
423 item_id => $item1_id,
424 patron_id => $patron_id
426 )->status_is(201)->or(sub { diag $t->tx->res->body });
427 %needsconfirmation = ();
429 $schema->storage->txn_rollback;