View | Details | Raw Unified | Return to bug 16826
Collapse All | Expand All

(-)a/Koha/REST/V1/BiblioAvailability.pm (+145 lines)
Line 0 Link Here
1
package Koha::REST::V1::BiblioAvailability;
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 Mojo::Base 'Mojolicious::Controller';
21
22
use C4::Auth qw( haspermission );
23
24
use Koha::Exceptions;
25
26
use Koha::Availability::Hold;
27
use Koha::Availability::Search;
28
29
use Try::Tiny;
30
31
sub search {
32
    my ($c, $args, $cb) = @_;
33
34
    my @availabilities;
35
36
    return try {
37
        my $biblios = $args->{'biblionumber'};
38
        foreach my $biblionumber (@$biblios) {
39
            if (my $biblio = Koha::Biblios->find($biblionumber)) {
40
                push @availabilities, Koha::Availability::Search->biblio({
41
                    biblio => $biblio
42
                })->in_opac->swaggerize;
43
            }
44
        }
45
        return $c->$cb(\@availabilities, 200);
46
    }
47
    catch {
48
        if ( $_->isa('DBIx::Class::Exception') ) {
49
            return $c->$cb( { error => $_->{msg} }, 500 );
50
        }
51
        else {
52
            return $c->$cb(
53
                { error => "Something went wrong, check the logs." }, 500 );
54
        }
55
    };
56
}
57
58
sub hold {
59
    my ($c, $args, $cb) = @_;
60
61
    my @availabilities;
62
    my $user = $c->stash('koha.user');
63
    my $borrowernumber = $args->{'borrowernumber'};
64
    my $to_branch = $args->{'branchcode'};
65
    my $limit_items = $args->{'limit_items'};
66
    my $patron;
67
    my $librarian;
68
69
    return try {
70
        ($patron, $librarian) = _get_patron($c, $user, $borrowernumber);
71
72
        my $biblios = $args->{'biblionumber'};
73
        my $params = {
74
            patron => $patron,
75
        };
76
77
        $params->{'to_branch'} = $to_branch if $to_branch;
78
        $params->{'limit'} = $limit_items if $limit_items;
79
80
        foreach my $biblionumber (@$biblios) {
81
            if (my $biblio = Koha::Biblios->find($biblionumber)) {
82
                $params->{'biblio'} = $biblio;
83
                my $availability = Koha::Availability::Hold->biblio($params);
84
                unless ($librarian) {
85
                    push @availabilities, $availability->in_opac->swaggerize;
86
                } else {
87
                    push @availabilities, $availability->in_intranet->swaggerize;
88
                }
89
            }
90
        }
91
92
        return $c->$cb(\@availabilities, 200);
93
    }
94
    catch {
95
        if ( $_->isa('DBIx::Class::Exception') ) {
96
            return $c->$cb( { error => $_->{msg} }, 500 );
97
        }
98
        elsif ($_->isa('Koha::Exceptions::AuthenticationRequired')) {
99
            return $c->$cb(
100
                { error => "Authentication required." }, 401 );
101
        }
102
        elsif ($_->isa('Koha::Exceptions::NoPermission')) {
103
            return $c->$cb({
104
                error => "Authorization failure. Missing required permission(s).",
105
                required_permissions => $_->required_permissions}, 403 );
106
        }
107
        else {
108
            return $c->$cb(
109
                { error => "Something went wrong, check the logs. $_" }, 500 );
110
        }
111
    };
112
}
113
114
sub _get_patron {
115
    my ($c, $user, $borrowernumber) = @_;
116
117
    my $patron;
118
    my $librarian = 0;
119
120
    unless ($user) {
121
        Koha::Exceptions::AuthenticationRequired->throw;
122
    }
123
    if (haspermission($user->userid, { borrowers => 1 })) {
124
        $librarian = 1;
125
    }
126
    if ($borrowernumber) {
127
        if ($borrowernumber == $user->borrowernumber) {
128
            $patron = $user;
129
        } else {
130
            if ($librarian) {
131
                $patron = Koha::Patrons->find($borrowernumber);
132
            } else {
133
                Koha::Exceptions::NoPermission->throw(
134
                    required_permissions => "borrowers"
135
                );
136
            }
137
        }
138
    } else {
139
        $patron = $user;
140
    }
141
142
    return ($patron, $librarian);
143
}
144
145
1;
(-)a/Koha/REST/V1/ItemAvailability.pm (+187 lines)
Line 0 Link Here
1
package Koha::REST::V1::ItemAvailability;
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 Mojo::Base 'Mojolicious::Controller';
21
22
use C4::Auth qw( haspermission );
23
24
use Koha::Exceptions;
25
26
use Koha::Availability::Checkout;
27
use Koha::Availability::Hold;
28
use Koha::Availability::Search;
29
30
use Try::Tiny;
31
32
sub checkout {
33
    my ($c, $args, $cb) = @_;
34
35
    my @availabilities;
36
    my $user = $c->stash('koha.user');
37
    my $borrowernumber = $args->{'borrowernumber'};
38
    my $patron;
39
    my $librarian;
40
41
    return try {
42
        ($patron, $librarian) = _get_patron($c, $user, $borrowernumber);
43
44
        my $items = $args->{'itemnumber'};
45
        foreach my $itemnumber (@$items) {
46
            if (my $item = Koha::Items->find($itemnumber)) {
47
                push @availabilities, Koha::Availability::Checkout->item({
48
                    patron => $patron,
49
                    item => $item
50
                })->in_intranet->swaggerize;
51
            }
52
        }
53
        return $c->$cb(\@availabilities, 200);
54
    }
55
    catch {
56
        if ( $_->isa('DBIx::Class::Exception') ) {
57
            return $c->$cb( { error => $_->{msg} }, 500 );
58
        }
59
        elsif ($_->isa('Koha::Exceptions::AuthenticationRequired')) {
60
            return $c->$cb(
61
                { error => "Authentication required." }, 401 );
62
        }
63
        elsif ($_->isa('Koha::Exceptions::NoPermission')) {
64
            return $c->$cb({
65
                error => "Authorization failure. Missing required permission(s).",
66
                required_permissions => $_->required_permissions}, 403 );
67
        }
68
        else {
69
            return $c->$cb(
70
                { error => "Something went wrong, check the logs. $_" }, 500 );
71
        }
72
    };
73
}
74
75
sub hold {
76
    my ($c, $args, $cb) = @_;
77
78
    my @availabilities;
79
    my $user = $c->stash('koha.user');
80
    my $borrowernumber = $args->{'borrowernumber'};
81
    my $to_branch = $args->{'branchcode'};
82
    my $patron;
83
    my $librarian;
84
85
    return try {
86
        ($patron, $librarian) = _get_patron($c, $user, $borrowernumber);
87
88
        my $items = $args->{'itemnumber'};
89
        my $params = {
90
            patron => $patron,
91
        };
92
        if ($to_branch) {
93
            $params->{'to_branch'} = $to_branch;
94
        }
95
        foreach my $itemnumber (@$items) {
96
            if (my $item = Koha::Items->find($itemnumber)) {
97
                $params->{'item'} = $item;
98
                my $availability = Koha::Availability::Hold->item($params);
99
                unless ($librarian) {
100
                    push @availabilities, $availability->in_opac->swaggerize;
101
                } else {
102
                    push @availabilities, $availability->in_intranet->swaggerize;
103
                }
104
            }
105
        }
106
107
        return $c->$cb(\@availabilities, 200);
108
    }
109
    catch {
110
        if ( $_->isa('DBIx::Class::Exception') ) {
111
            return $c->$cb( { error => $_->{msg} }, 500 );
112
        }
113
        elsif ($_->isa('Koha::Exceptions::AuthenticationRequired')) {
114
            return $c->$cb(
115
                { error => "Authentication required." }, 401 );
116
        }
117
        elsif ($_->isa('Koha::Exceptions::NoPermission')) {
118
            return $c->$cb({
119
                error => "Authorization failure. Missing required permission(s).",
120
                required_permissions => $_->required_permissions}, 403 );
121
        }
122
        else {
123
            return $c->$cb(
124
                { error => "Something went wrong, check the logs. $_" }, 500 );
125
        }
126
    };
127
}
128
129
sub search {
130
    my ($c, $args, $cb) = @_;
131
132
    my @availabilities;
133
134
    return try {
135
        my $items = $args->{'itemnumber'};
136
        foreach my $itemnumber (@$items) {
137
            if (my $item = Koha::Items->find($itemnumber)) {
138
                push @availabilities, Koha::Availability::Search->item({
139
                    item => $item
140
                })->in_opac->swaggerize;
141
            }
142
        }
143
        return $c->$cb(\@availabilities, 200);
144
    }
145
    catch {
146
        if ( $_->isa('DBIx::Class::Exception') ) {
147
            return $c->$cb( { error => $_->{msg} }, 500 );
148
        }
149
        else {
150
            return $c->$cb(
151
                { error => "Something went wrong, check the logs. $_" }, 500 );
152
        }
153
    };
154
}
155
156
sub _get_patron {
157
    my ($c, $user, $borrowernumber) = @_;
158
159
    my $patron;
160
    my $librarian = 0;
161
162
    unless ($user) {
163
        Koha::Exceptions::AuthenticationRequired->throw;
164
    }
165
    if (haspermission($user->userid, { borrowers => 1 })) {
166
        $librarian = 1;
167
    }
168
    if ($borrowernumber) {
169
        if ($borrowernumber == $user->borrowernumber) {
170
            $patron = $user;
171
        } else {
172
            if ($librarian) {
173
                $patron = Koha::Patrons->find($borrowernumber);
174
            } else {
175
                Koha::Exceptions::NoPermission->throw(
176
                    required_permissions => "borrowers"
177
                );
178
            }
179
        }
180
    } else {
181
        $patron = $user;
182
    }
183
184
    return ($patron, $librarian);
185
}
186
187
1;
(-)a/t/db_dependent/api/v1/availability.t (-1 / +685 lines)
Line 0 Link Here
0
- 
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 => 2;
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::Biblio::Availability;
29
use Koha::Item::Availability;
30
use Koha::Database;
31
32
require t::db_dependent::Koha::Availability::Helpers;
33
34
my $schema  = Koha::Database->new->schema;
35
my $builder = t::lib::TestBuilder->new;
36
37
# FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling
38
# this affects the other REST api tests
39
t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
40
41
my $remote_address = '127.0.0.1';
42
my $t              = Test::Mojo->new('Koha::REST::V1');
43
44
subtest '/availability/biblio' => sub {
45
    plan tests => 2;
46
47
    subtest '/hold' => sub {
48
        plan tests => 30;
49
50
        $schema->storage->txn_begin;
51
52
        set_default_circulation_rules();
53
        set_default_system_preferences();
54
55
        my $item = build_a_test_item();
56
        my $item2 = build_a_test_item(
57
            Koha::Biblios->find($item->biblionumber),
58
            Koha::Biblioitems->find($item->biblioitemnumber)
59
        );
60
        my ($patron, $session_id) = create_user_and_session();
61
        $patron = Koha::Patrons->find($patron);
62
        my $route = '/api/v1/availability/biblio/hold';
63
        my $tx = $t->ua->build_tx( GET => $route . '?biblionumber='.$item->biblionumber );
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_has('/0/availability')
69
          ->json_is('/0/availability/available' => Mojo::JSON->true)
70
          ->json_is('/0/item_availabilities/0/availability/available' => Mojo::JSON->true)
71
          ->json_is('/0/item_availabilities/1/availability/available' => Mojo::JSON->true);
72
73
        $tx = $t->ua->build_tx( GET => $route . '?biblionumber='.$item->biblionumber.'&limit_items=1' );
74
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
75
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
76
        $t->request_ok($tx)
77
          ->status_is(200)
78
          ->json_has('/0/availability')
79
          ->json_has('/0/item_availabilities/0')
80
          ->json_hasnt('/0/item_availabilities/1');
81
82
        $patron->gonenoaddress('1')->store;
83
        $item2->notforloan('1')->store;
84
        $tx = $t->ua->build_tx( GET => $route . '?biblionumber='.$item->biblionumber );
85
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
86
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
87
        $t->request_ok($tx)
88
          ->status_is(200)
89
          ->json_has('/0/availability')
90
          ->json_is('/0/availability/available' => Mojo::JSON->false)
91
          ->json_is('/0/availability/unavailabilities/Patron::GoneNoAddress' => {})
92
          ->json_is('/0/item_availabilities/0/itemnumber' => $item->itemnumber)
93
          ->json_is('/0/item_availabilities/0/availability/available' => Mojo::JSON->true)
94
          ->json_is('/0/item_availabilities/1/itemnumber' => $item2->itemnumber)
95
          ->json_is('/0/item_availabilities/1/availability/available' => Mojo::JSON->false)
96
          ->json_is('/0/item_availabilities/1/availability/unavailabilities/Item::NotForLoan' => {
97
            code => "Not For Loan",
98
            status => 1,
99
            });
100
        $patron->gonenoaddress('0')->store;
101
        $item2->notforloan('0')->store;
102
103
        my $patron2 = build_a_test_patron();
104
        $tx = $t->ua->build_tx( GET => $route . '?biblionumber='.$item->biblionumber.'&borrowernumber='.$patron2->borrowernumber );
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(403);
109
110
        my $branch = Koha::Libraries->find(
111
        $builder->build({ source => 'Branch' })->{'branchcode'});
112
        t::lib::Mocks::mock_preference('UseBranchTransferLimits', 1);
113
        t::lib::Mocks::mock_preference('BranchTransferLimitsType', 'itemtype');
114
        C4::Circulation::CreateBranchTransferLimit(
115
            $branch->branchcode,
116
            $item->holdingbranch,
117
            $item->effective_itemtype
118
        );
119
120
        $tx = $t->ua->build_tx( GET => $route . '?biblionumber='.$item->biblionumber.'&branchcode='.$branch->branchcode );
121
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
122
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
123
        $t->request_ok($tx)
124
          ->status_is(200)
125
          ->json_has('/0/availability')
126
          ->json_is('/0/availability/available' => Mojo::JSON->true)
127
          ->json_is('/0/item_availabilities/0/availability/available' => Mojo::JSON->true)
128
          ->json_is('/0/item_availabilities/1/availability/available' => Mojo::JSON->false)
129
          ->json_is('/0/item_availabilities/1/availability/unavailabilities/Item::CannotBeTransferred' => {
130
                from_library => $item->holdingbranch,
131
                to_library => $branch->branchcode,});
132
133
        $schema->storage->txn_rollback;
134
    };
135
136
    subtest '/search' => sub {
137
        plan tests => 15;
138
139
        $schema->storage->txn_begin;
140
141
        set_default_circulation_rules();
142
        set_default_system_preferences();
143
144
        my $item = build_a_test_item();
145
        my $item2 = build_a_test_item(
146
            Koha::Biblios->find($item->biblionumber),
147
            Koha::Biblioitems->find($item->biblioitemnumber)
148
        );
149
        my $route = '/api/v1/availability/biblio/search';
150
        my $tx = $t->ua->build_tx( GET => $route . '?biblionumber='.$item->biblionumber );
151
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
152
        $t->request_ok($tx)
153
          ->status_is(200)
154
          ->json_has('/0/availability')
155
          ->json_is('/0/availability/available' => Mojo::JSON->true)
156
          ->json_is('/0/item_availabilities/0/availability/available' => Mojo::JSON->true)
157
          ->json_is('/0/item_availabilities/1/availability/available' => Mojo::JSON->true);
158
159
        $item2->notforloan('1')->store;
160
        $tx = $t->ua->build_tx( GET => $route . '?biblionumber='.$item->biblionumber );
161
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
162
        $t->request_ok($tx)
163
          ->status_is(200)
164
          ->json_has('/0/availability')
165
          ->json_is('/0/availability/available' => Mojo::JSON->true)
166
          ->json_is('/0/item_availabilities/0/itemnumber' => $item->itemnumber)
167
          ->json_is('/0/item_availabilities/0/availability/available' => Mojo::JSON->true)
168
          ->json_is('/0/item_availabilities/1/itemnumber' => $item2->itemnumber)
169
          ->json_is('/0/item_availabilities/1/availability/available' => Mojo::JSON->false)
170
          ->json_is('/0/item_availabilities/1/availability/unavailabilities/Item::NotForLoan' => {
171
            code => "Not For Loan",
172
            status => 1,
173
            });
174
175
        $schema->storage->txn_rollback;
176
    };
177
};
178
179
subtest '/availability/item' => sub {
180
    plan tests => 3;
181
182
    subtest '/hold' => sub {
183
        plan tests => 17;
184
185
        $schema->storage->txn_begin;
186
187
        set_default_circulation_rules();
188
        set_default_system_preferences();
189
190
        my $item = build_a_test_item();
191
        my ($patron, $session_id) = create_user_and_session();
192
        $patron = Koha::Patrons->find($patron);
193
        my $route = '/api/v1/availability/item/hold';
194
        my $tx = $t->ua->build_tx( GET => $route . '?itemnumber='.$item->itemnumber );
195
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
196
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
197
        $t->request_ok($tx)
198
          ->status_is(200)
199
          ->json_has('/0/availability')
200
          ->json_is('/0/availability/available' => Mojo::JSON->true);
201
202
        $patron->gonenoaddress('1')->store;
203
        $item->notforloan('1')->store;
204
        $tx = $t->ua->build_tx( GET => $route . '?itemnumber='.$item->itemnumber );
205
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
206
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
207
        $t->request_ok($tx)
208
          ->status_is(200)
209
          ->json_has('/0/availability')
210
          ->json_is('/0/availability/available' => Mojo::JSON->false)
211
          ->json_is('/0/itemnumber' => $item->itemnumber)
212
          ->json_is('/0/availability/unavailabilities/Patron::GoneNoAddress' => {})
213
          ->json_is('/0/availability/unavailabilities/Item::NotForLoan' => {
214
            code => "Not For Loan",
215
            status => 1,
216
            });
217
        $patron->gonenoaddress('0')->store;
218
        $item->notforloan('0')->store;
219
220
        my $patron2 = build_a_test_patron();
221
        $tx = $t->ua->build_tx( GET => $route . '?itemnumber='.$item->itemnumber.'&borrowernumber='.$patron2->borrowernumber );
222
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
223
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
224
        $t->request_ok($tx)
225
          ->status_is(403);
226
227
        my $branch = Koha::Libraries->find(
228
        $builder->build({ source => 'Branch' })->{'branchcode'});
229
        t::lib::Mocks::mock_preference('UseBranchTransferLimits', 1);
230
        t::lib::Mocks::mock_preference('BranchTransferLimitsType', 'itemtype');
231
        C4::Circulation::CreateBranchTransferLimit(
232
            $branch->branchcode,
233
            $item->holdingbranch,
234
            $item->effective_itemtype
235
        );
236
237
        $tx = $t->ua->build_tx( GET => $route . '?itemnumber='.$item->itemnumber.'&branchcode='.$branch->branchcode );
238
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
239
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
240
        $t->request_ok($tx)
241
          ->status_is(200)
242
          ->json_is('/0/availability/available' => Mojo::JSON->false)
243
          ->json_is('/0/availability/unavailabilities/Item::CannotBeTransferred' => {
244
                from_library => $item->holdingbranch,
245
                to_library => $branch->branchcode,});
246
247
        $schema->storage->txn_rollback;
248
    };
249
250
    subtest '/checkout' => sub {
251
        plan tests => 12;
252
253
        $schema->storage->txn_begin;
254
255
        set_default_circulation_rules();
256
        set_default_system_preferences();
257
258
        my $item = build_a_test_item();
259
        my ($patron, $session_id) = create_user_and_session();
260
        $patron = Koha::Patrons->find($patron);
261
        my $route = '/api/v1/availability/item/checkout';
262
        my $tx = $t->ua->build_tx( GET => $route . '?itemnumber='.$item->itemnumber );
263
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
264
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
265
        $t->request_ok($tx)
266
          ->status_is(200)
267
          ->json_has('/0/availability')
268
          ->json_is('/0/availability/available' => Mojo::JSON->true);
269
270
        $item->notforloan('1')->store;
271
        $tx = $t->ua->build_tx( GET => $route . '?itemnumber='.$item->itemnumber );
272
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
273
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
274
        $t->request_ok($tx)
275
          ->status_is(200)
276
          ->json_has('/0/availability')
277
          ->json_is('/0/availability/available' => Mojo::JSON->false)
278
          ->json_is('/0/itemnumber' => $item->itemnumber)
279
          ->json_is('/0/availability/unavailabilities/Item::NotForLoan' => {
280
            code => "Not For Loan",
281
            status => 1,
282
            });
283
        $item->notforloan('0')->store;
284
285
        my $patron2 = build_a_test_patron();
286
        $tx = $t->ua->build_tx( GET => $route . '?itemnumber='.$item->itemnumber.'&borrowernumber='.$patron2->borrowernumber );
287
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
288
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
289
        $t->request_ok($tx)
290
          ->status_is(403);
291
292
        $schema->storage->txn_rollback;
293
    };
294
295
    subtest '/search' => sub {
296
        plan tests => 15;
297
298
        $schema->storage->txn_begin;
299
300
        set_default_circulation_rules();
301
        set_default_system_preferences();
302
303
        my $item = build_a_test_item();
304
        my $item2 = build_a_test_item(
305
            Koha::Biblios->find($item->biblionumber),
306
            Koha::Biblioitems->find($item->biblioitemnumber)
307
        );
308
        my $route = '/api/v1/availability/biblio/search';
309
        my $tx = $t->ua->build_tx( GET => $route . '?biblionumber='.$item->biblionumber );
310
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
311
        $t->request_ok($tx)
312
          ->status_is(200)
313
          ->json_has('/0/availability')
314
          ->json_is('/0/availability/available' => Mojo::JSON->true)
315
          ->json_is('/0/item_availabilities/0/availability/available' => Mojo::JSON->true)
316
          ->json_is('/0/item_availabilities/1/availability/available' => Mojo::JSON->true);
317
318
        $item2->notforloan('1')->store;
319
        $tx = $t->ua->build_tx( GET => $route . '?biblionumber='.$item->biblionumber );
320
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
321
        $t->request_ok($tx)
322
          ->status_is(200)
323
          ->json_has('/0/availability')
324
          ->json_is('/0/availability/available' => Mojo::JSON->true)
325
          ->json_is('/0/item_availabilities/0/itemnumber' => $item->itemnumber)
326
          ->json_is('/0/item_availabilities/0/availability/available' => Mojo::JSON->true)
327
          ->json_is('/0/item_availabilities/1/itemnumber' => $item2->itemnumber)
328
          ->json_is('/0/item_availabilities/1/availability/available' => Mojo::JSON->false)
329
          ->json_is('/0/item_availabilities/1/availability/unavailabilities/Item::NotForLoan' => {
330
            code => "Not For Loan",
331
            status => 1,
332
            });
333
334
        $schema->storage->txn_rollback;
335
    };
336
};
337
338
=head3
339
subtest '' => sub {
340
341
    plan tests => 18;
342
343
    $schema->storage->txn_begin;
344
345
    Koha::Cities->search->delete;
346
    my ( $borrowernumber, $session_id ) =
347
      create_user_and_session( { authorized => 0 } );
348
349
    ## Authorized user tests
350
    # No cities, so empty array should be returned
351
    my $tx = $t->ua->build_tx( GET => '/api/v1/cities' );
352
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
353
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
354
    $t->request_ok($tx)->status_is(200)->json_is( [] );
355
356
    my $city_country = 'France';
357
    my $city         = $builder->build(
358
        { source => 'City', value => { city_country => $city_country } } );
359
360
    # One city created, should get returned
361
    $tx = $t->ua->build_tx( GET => '/api/v1/cities' );
362
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
363
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
364
    $t->request_ok($tx)->status_is(200)->json_is( [$city] );
365
366
    my $another_city = $builder->build(
367
        { source => 'City', value => { city_country => $city_country } } );
368
    my $city_with_another_country = $builder->build( { source => 'City' } );
369
370
    # Two cities created, they should both be returned
371
    $tx = $t->ua->build_tx( GET => '/api/v1/cities' );
372
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
373
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
374
    $t->request_ok($tx)->status_is(200)
375
      ->json_is( [ $city, $another_city, $city_with_another_country ] );
376
377
    # Filtering works, two cities sharing city_country
378
    $tx =
379
      $t->ua->build_tx( GET => "/api/v1/cities?city_country=" . $city_country );
380
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
381
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
382
    my $result =
383
      $t->request_ok($tx)->status_is(200)->json_is( [ $city, $another_city ] );
384
385
    $tx = $t->ua->build_tx(
386
        GET => "/api/v1/cities?city_name=" . $city->{city_name} );
387
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
388
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
389
    $t->request_ok($tx)->status_is(200)->json_is( [$city] );
390
391
    # Warn on unsupported query parameter
392
    $tx = $t->ua->build_tx( GET => '/api/v1/cities?city_blah=blah' );
393
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
394
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
395
    $t->request_ok($tx)->status_is(400)
396
      ->json_is( [{ path => '/query/city_blah', message => 'Malformed query string'}] );
397
398
    $schema->storage->txn_rollback;
399
};
400
401
subtest 'get() tests' => sub {
402
403
    plan tests => 6;
404
405
    $schema->storage->txn_begin;
406
407
    my $city = $builder->build( { source => 'City' } );
408
    my ( $borrowernumber, $session_id ) =
409
      create_user_and_session( { authorized => 0 } );
410
411
    my $tx = $t->ua->build_tx( GET => "/api/v1/cities/" . $city->{cityid} );
412
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
413
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
414
    $t->request_ok($tx)->status_is(200)->json_is($city);
415
416
    my $non_existent_id = $city->{cityid} + 1;
417
    $tx = $t->ua->build_tx( GET => "/api/v1/cities/" . $non_existent_id );
418
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
419
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
420
    $t->request_ok($tx)->status_is(404)
421
      ->json_is( '/error' => 'City not found' );
422
423
    $schema->storage->txn_rollback;
424
};
425
426
subtest 'add() tests' => sub {
427
428
    plan tests => 17;
429
430
    $schema->storage->txn_begin;
431
432
    my ( $unauthorized_borrowernumber, $unauthorized_session_id ) =
433
      create_user_and_session( { authorized => 0 } );
434
    my ( $authorized_borrowernumber, $authorized_session_id ) =
435
      create_user_and_session( { authorized => 1 } );
436
    my $city = {
437
        city_name    => "City Name",
438
        city_state   => "City State",
439
        city_zipcode => "City Zipcode",
440
        city_country => "City Country"
441
    };
442
443
    # Unauthorized attempt to write
444
    my $tx = $t->ua->build_tx( POST => "/api/v1/cities/" => json => $city );
445
    $tx->req->cookies(
446
        { name => 'CGISESSID', value => $unauthorized_session_id } );
447
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
448
    $t->request_ok($tx)->status_is(403);
449
450
    # Authorized attempt to write invalid data
451
    my $city_with_invalid_field = {
452
        city_blah    => "City Blah",
453
        city_state   => "City State",
454
        city_zipcode => "City Zipcode",
455
        city_country => "City Country"
456
    };
457
458
    $tx = $t->ua->build_tx(
459
        POST => "/api/v1/cities/" => json => $city_with_invalid_field );
460
    $tx->req->cookies(
461
        { name => 'CGISESSID', value => $authorized_session_id } );
462
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
463
    $t->request_ok($tx)->status_is(400)->json_is(
464
        "/errors" => [
465
            {
466
                message => "Properties not allowed: city_blah.",
467
                path    => "/body"
468
            }
469
        ]
470
    );
471
472
    # Authorized attempt to write
473
    $tx = $t->ua->build_tx( POST => "/api/v1/cities/" => json => $city );
474
    $tx->req->cookies(
475
        { name => 'CGISESSID', value => $authorized_session_id } );
476
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
477
    my $cityid = $t->request_ok($tx)->status_is(200)
478
      ->json_is( '/city_name'    => $city->{city_name} )
479
      ->json_is( '/city_state'   => $city->{city_state} )
480
      ->json_is( '/city_zipcode' => $city->{city_zipcode} )
481
      ->json_is( '/city_country' => $city->{city_country} )
482
      ->tx->res->json->{cityid};
483
484
    # Authorized attempt to create with null id
485
    $city->{cityid} = undef;
486
    $tx = $t->ua->build_tx(
487
        POST => "/api/v1/cities/" => json => $city );
488
    $tx->req->cookies(
489
        { name => 'CGISESSID', value => $authorized_session_id } );
490
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
491
    $t->request_ok($tx)->status_is(400)->json_has('/errors');
492
493
    # Authorized attempt to create with existing id
494
    $city->{cityid} = $cityid;
495
    $tx = $t->ua->build_tx(
496
        POST => "/api/v1/cities/" => json => $city );
497
    $tx->req->cookies(
498
        { name => 'CGISESSID', value => $authorized_session_id } );
499
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
500
    $t->request_ok($tx)->status_is(400)->json_is(
501
        "/errors" => [
502
            {
503
                message => "Read-only.",
504
                path    => "/body/cityid"
505
            }
506
        ]
507
    );
508
509
    $schema->storage->txn_rollback;
510
};
511
512
subtest 'update() tests' => sub {
513
514
    plan tests => 15;
515
516
    $schema->storage->txn_begin;
517
518
    my ( $unauthorized_borrowernumber, $unauthorized_session_id ) =
519
      create_user_and_session( { authorized => 0 } );
520
    my ( $authorized_borrowernumber, $authorized_session_id ) =
521
      create_user_and_session( { authorized => 1 } );
522
523
    my $city_id = $builder->build( { source => 'City' } )->{cityid};
524
525
    # Unauthorized attempt to update
526
    my $tx = $t->ua->build_tx( PUT => "/api/v1/cities/$city_id" => json =>
527
          { city_name => 'New unauthorized name change' } );
528
    $tx->req->cookies(
529
        { name => 'CGISESSID', value => $unauthorized_session_id } );
530
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
531
    $t->request_ok($tx)->status_is(403);
532
533
    # Attempt partial update on a PUT
534
    my $city_with_missing_field = {
535
        city_name    => 'New name',
536
        city_state   => 'New state',
537
        city_country => 'New country'
538
    };
539
540
    $tx = $t->ua->build_tx(
541
        PUT => "/api/v1/cities/$city_id" => json => $city_with_missing_field );
542
    $tx->req->cookies(
543
        { name => 'CGISESSID', value => $authorized_session_id } );
544
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
545
    $t->request_ok($tx)->status_is(400)
546
      ->json_is( "/errors" =>
547
          [ { message => "Missing property.", path => "/body/city_zipcode" } ]
548
      );
549
550
    # Full object update on PUT
551
    my $city_with_updated_field = {
552
        city_name    => "London",
553
        city_state   => "City State",
554
        city_zipcode => "City Zipcode",
555
        city_country => "City Country"
556
    };
557
558
    $tx = $t->ua->build_tx(
559
        PUT => "/api/v1/cities/$city_id" => json => $city_with_updated_field );
560
    $tx->req->cookies(
561
        { name => 'CGISESSID', value => $authorized_session_id } );
562
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
563
    $t->request_ok($tx)->status_is(200)->json_is( '/city_name' => 'London' );
564
565
    # Authorized attempt to write invalid data
566
    my $city_with_invalid_field = {
567
        city_blah    => "City Blah",
568
        city_state   => "City State",
569
        city_zipcode => "City Zipcode",
570
        city_country => "City Country"
571
    };
572
573
    $tx = $t->ua->build_tx(
574
        PUT => "/api/v1/cities/$city_id" => json => $city_with_invalid_field );
575
    $tx->req->cookies(
576
        { name => 'CGISESSID', value => $authorized_session_id } );
577
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
578
    $t->request_ok($tx)->status_is(400)->json_is(
579
        "/errors" => [
580
            {
581
                message => "Properties not allowed: city_blah.",
582
                path    => "/body"
583
            }
584
        ]
585
    );
586
587
    my $non_existent_id = $city_id + 1;
588
    $tx =
589
      $t->ua->build_tx( PUT => "/api/v1/cities/$non_existent_id" => json =>
590
          $city_with_updated_field );
591
    $tx->req->cookies(
592
        { name => 'CGISESSID', value => $authorized_session_id } );
593
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
594
    $t->request_ok($tx)->status_is(404);
595
596
    $schema->storage->txn_rollback;
597
598
    # Wrong mathod (POST)
599
    $city_with_updated_field->{cityid} = 2;
600
601
    $tx = $t->ua->build_tx(
602
        POST => "/api/v1/cities/$city_id" => json => $city_with_updated_field );
603
    $tx->req->cookies(
604
        { name => 'CGISESSID', value => $authorized_session_id } );
605
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
606
    $t->request_ok($tx)->status_is(404);
607
};
608
609
subtest 'delete() tests' => sub {
610
611
    plan tests => 7;
612
613
    $schema->storage->txn_begin;
614
615
    my ( $unauthorized_borrowernumber, $unauthorized_session_id ) =
616
      create_user_and_session( { authorized => 0 } );
617
    my ( $authorized_borrowernumber, $authorized_session_id ) =
618
      create_user_and_session( { authorized => 1 } );
619
620
    my $city_id = $builder->build( { source => 'City' } )->{cityid};
621
622
    # Unauthorized attempt to update
623
    my $tx = $t->ua->build_tx( DELETE => "/api/v1/cities/$city_id" );
624
    $tx->req->cookies(
625
        { name => 'CGISESSID', value => $unauthorized_session_id } );
626
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
627
    $t->request_ok($tx)->status_is(403);
628
629
    $tx = $t->ua->build_tx( DELETE => "/api/v1/cities/$city_id" );
630
    $tx->req->cookies(
631
        { name => 'CGISESSID', value => $authorized_session_id } );
632
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
633
    $t->request_ok($tx)->status_is(200)->content_is('');
634
635
    $tx = $t->ua->build_tx( DELETE => "/api/v1/cities/$city_id" );
636
    $tx->req->cookies(
637
        { name => 'CGISESSID', value => $authorized_session_id } );
638
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
639
    $t->request_ok($tx)->status_is(404);
640
641
    $schema->storage->txn_rollback;
642
};
643
=cut
644
sub create_user_and_session {
645
646
    my $args  = shift;
647
    my $flags = ( $args->{authorized} ) ? $args->{authorized} : 0;
648
    my $dbh   = C4::Context->dbh;
649
650
    my $user = $builder->build(
651
        {
652
            source => 'Borrower',
653
            value  => {
654
                flags => $flags,
655
                debarred => undef,
656
                debarredcomment => undef,
657
                lost => undef,
658
                gonenoaddress => undef,
659
                dateexpiry => output_pref({ dt => dt_from_string()->add_duration( # expires in 100 days
660
                              DateTime::Duration->new(days => 100)), dateformat => 'iso', dateonly => 1 }),
661
                dateofbirth => '1950-10-10',
662
            }
663
        }
664
    );
665
666
    # Create a session for the authorized user
667
    my $session = C4::Auth::get_session('');
668
    $session->param( 'number',   $user->{borrowernumber} );
669
    $session->param( 'id',       $user->{userid} );
670
    $session->param( 'ip',       '127.0.0.1' );
671
    $session->param( 'lasttime', time() );
672
    $session->flush;
673
674
    if ( $args->{authorized} ) {
675
        $dbh->do( "
676
            INSERT INTO user_permissions (borrowernumber,module_bit,code)
677
            VALUES (?,3,'parameters_remaining_permissions')", undef,
678
            $user->{borrowernumber} );
679
    }
680
681
    return ( $user->{borrowernumber}, $session->id );
682
}
683
684
685
1;

Return to bug 16826