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

(-)a/Koha/REST/V1/Cities.pm (-7 / +9 lines)
Lines 29-38 sub list { Link Here
29
    my ( $c, $args, $cb ) = @_;
29
    my ( $c, $args, $cb ) = @_;
30
30
31
    my $cities;
31
    my $cities;
32
    my $filter;
33
34
    for my $filter_param ( keys $args ) {
35
        $filter->{$filter_param} = { LIKE => $args->{$filter_param} . "%" };
36
    }
32
37
33
    return try {
38
    return try {
34
        $cities =
39
        $cities = Koha::Cities->search($filter)->unblessed;
35
          Koha::Cities->search( $c->req->query_params->to_hash )->unblessed;
36
        return $c->$cb( $cities, 200 );
40
        return $c->$cb( $cities, 200 );
37
    }
41
    }
38
    catch {
42
    catch {
Lines 60-66 sub get { Link Here
60
sub add {
64
sub add {
61
    my ( $c, $args, $cb ) = @_;
65
    my ( $c, $args, $cb ) = @_;
62
66
63
    my $city = Koha::City->new( $c->req->json );
67
    my $city = Koha::City->new( $args->{body} );
64
68
65
    return try {
69
    return try {
66
        $city->store;
70
        $city->store;
Lines 84-93 sub update { Link Here
84
88
85
    return try {
89
    return try {
86
        $city = Koha::Cities->find( $args->{cityid} );
90
        $city = Koha::Cities->find( $args->{cityid} );
87
        while ( my ( $k, $v ) = each %{ $c->req->json } ) {
91
        $city->set( $args->{body} );
88
            $city->$k($v);
92
        $city->store();
89
        }
90
        $city->store;
91
        return $c->$cb( $city->unblessed, 200 );
93
        return $c->$cb( $city->unblessed, 200 );
92
    }
94
    }
93
    catch {
95
    catch {
(-)a/api/v1/swagger/definitions/city.json (-20 / +22 lines)
Lines 1-24 Link Here
1
{
1
{
2
  "type": "object",
2
  "type": "object",
3
  "properties": {
3
  "properties": {
4
      "cityid": {
4
    "cityid": {
5
        "$ref": "../x-primitives.json#/cityid"
5
      "$ref": "../x-primitives.json#/cityid"
6
      },
6
    },
7
      "city_name": {
7
    "city_name": {
8
        "description": "city name",
8
      "description": "city name",
9
        "type": "string"
9
      "type": "string"
10
      },
10
    },
11
      "city_state": {
11
    "city_state": {
12
        "description": "city state",
12
      "description": "city state",
13
        "type": ["string", "null"]
13
      "type": ["string", "null"]
14
      },
14
    },
15
      "city_zipcode": {
15
    "city_zipcode": {
16
        "description": "city zipcode",
16
      "description": "city zipcode",
17
        "type": ["string", "null"]
17
      "type": ["string", "null"]
18
      },
18
    },
19
      "city_country": {
19
    "city_country": {
20
        "description": "city country",
20
      "description": "city country",
21
        "type": ["string", "null"]
21
      "type": ["string", "null"]
22
      }
22
    }
23
  }
23
  },
24
  "additionalProperties": false,
25
  "required": ["city_name", "city_state", "city_zipcode", "city_country"]
24
}
26
}
(-)a/api/v1/swagger/paths/cities.json (-53 / +69 lines)
Lines 7-12 Link Here
7
      "produces": [
7
      "produces": [
8
        "application/json"
8
        "application/json"
9
      ],
9
      ],
10
      "parameters": [{
11
        "name": "city_name",
12
        "in": "query",
13
        "description": "Case insensative 'starts-with' search on city name",
14
        "required": false,
15
        "type": "string"
16
      }, {
17
        "name": "city_state",
18
        "in": "query",
19
        "description": "Case insensative 'starts-with' search on city state",
20
        "required": false,
21
        "type": "string"
22
      }, {
23
        "name": "city_country",
24
        "in": "query",
25
        "description": "Case insensative 'starts_with' search on city country",
26
        "required": false,
27
        "type": "string"
28
      }, {
29
        "name": "city_zipcode",
30
        "in": "query",
31
        "description": "Case Insensative 'starts-with' search on zipcode",
32
        "required": false,
33
        "type": "string"
34
      }],
10
      "responses": {
35
      "responses": {
11
        "200": {
36
        "200": {
12
          "description": "A list of cities",
37
          "description": "A list of cities",
Lines 24-33 Link Here
24
          }
49
          }
25
        },
50
        },
26
        "500": {
51
        "500": {
27
            "description": "Internal error",
52
          "description": "Internal error",
28
            "schema": {
53
          "schema": {
29
                "$ref": "../definitions.json#/error"
54
            "$ref": "../definitions.json#/error"
30
            }
55
          }
31
        }
56
        }
32
      }
57
      }
33
    },
58
    },
Lines 36-50 Link Here
36
      "operationId": "add",
61
      "operationId": "add",
37
      "tags": ["cities"],
62
      "tags": ["cities"],
38
      "parameters": [{
63
      "parameters": [{
39
          "name": "body",
64
        "name": "body",
40
          "in": "body",
65
        "in": "body",
41
          "description": "A JSON object containing informations about the new hold",
66
        "description": "A JSON object containing informations about the new hold",
42
          "required": true,
67
        "required": true,
43
          "schema": {
68
        "schema": {
44
            "$ref": "../definitions.json#/city"
69
          "$ref": "../definitions.json#/city"
45
          }
46
        }
70
        }
47
      ],
71
      }],
48
      "produces": [
72
      "produces": [
49
        "application/json"
73
        "application/json"
50
      ],
74
      ],
Lines 62-71 Link Here
62
          }
86
          }
63
        },
87
        },
64
        "500": {
88
        "500": {
65
            "description": "Internal error",
89
          "description": "Internal error",
66
            "schema": {
90
          "schema": {
67
                "$ref": "../definitions.json#/error"
91
            "$ref": "../definitions.json#/error"
68
            }
92
          }
69
        }
93
        }
70
      },
94
      },
71
      "x-koha-authorization": {
95
      "x-koha-authorization": {
Lines 80-90 Link Here
80
      "x-mojo-controller": "Koha::REST::V1::Cities",
104
      "x-mojo-controller": "Koha::REST::V1::Cities",
81
      "operationId": "get",
105
      "operationId": "get",
82
      "tags": ["cities"],
106
      "tags": ["cities"],
83
      "parameters": [
107
      "parameters": [{
84
        {
108
        "$ref": "../parameters.json#/cityidPathParam"
85
          "$ref": "../parameters.json#/cityidPathParam"
109
      }],
86
        }
87
      ],
88
      "produces": [
110
      "produces": [
89
        "application/json"
111
        "application/json"
90
      ],
112
      ],
Lines 106-118 Link Here
106
          "schema": {
128
          "schema": {
107
            "$ref": "../definitions.json#/error"
129
            "$ref": "../definitions.json#/error"
108
          }
130
          }
109
        }
131
        },
110
        ,
111
        "500": {
132
        "500": {
112
            "description": "Internal error",
133
          "description": "Internal error",
113
            "schema": {
134
          "schema": {
114
                "$ref": "../definitions.json#/error"
135
            "$ref": "../definitions.json#/error"
115
            }
136
          }
116
        }
137
        }
117
      }
138
      }
118
    },
139
    },
Lines 120-139 Link Here
120
      "x-mojo-controller": "Koha::REST::V1::Cities",
141
      "x-mojo-controller": "Koha::REST::V1::Cities",
121
      "operationId": "update",
142
      "operationId": "update",
122
      "tags": ["cities"],
143
      "tags": ["cities"],
123
      "parameters": [
144
      "parameters": [{
124
        {
145
        "$ref": "../parameters.json#/cityidPathParam"
125
          "$ref": "../parameters.json#/cityidPathParam"
146
      }, {
126
        },
147
        "name": "body",
127
        {
148
        "in": "body",
128
          "name": "body",
149
        "description": "A JSON object containing informations about the new hold",
129
          "in": "body",
150
        "required": true,
130
          "description": "A JSON object containing informations about the new hold",
151
        "schema": {
131
          "required": true,
152
          "$ref": "../definitions.json#/city"
132
          "schema": {
133
            "$ref": "../definitions.json#/city"
134
          }
135
        }
153
        }
136
      ],
154
      }],
137
      "produces": [
155
      "produces": [
138
        "application/json"
156
        "application/json"
139
      ],
157
      ],
Lines 157-166 Link Here
157
          }
175
          }
158
        },
176
        },
159
        "500": {
177
        "500": {
160
            "description": "Internal error",
178
          "description": "Internal error",
161
            "schema": {
179
          "schema": {
162
                "$ref": "../definitions.json#/error"
180
            "$ref": "../definitions.json#/error"
163
            }
181
          }
164
        }
182
        }
165
      },
183
      },
166
      "x-koha-authorization": {
184
      "x-koha-authorization": {
Lines 173-183 Link Here
173
      "x-mojo-controller": "Koha::REST::V1::Cities",
191
      "x-mojo-controller": "Koha::REST::V1::Cities",
174
      "operationId": "delete",
192
      "operationId": "delete",
175
      "tags": ["cities"],
193
      "tags": ["cities"],
176
      "parameters": [
194
      "parameters": [{
177
        {
195
        "$ref": "../parameters.json#/cityidPathParam"
178
          "$ref": "../parameters.json#/cityidPathParam"
196
      }],
179
        }
180
      ],
181
      "produces": [
197
      "produces": [
182
        "application/json"
198
        "application/json"
183
      ],
199
      ],
Lines 201-210 Link Here
201
          }
217
          }
202
        },
218
        },
203
        "500": {
219
        "500": {
204
            "description": "Internal error",
220
          "description": "Internal error",
205
            "schema": {
221
          "schema": {
206
                "$ref": "../definitions.json#/error"
222
            "$ref": "../definitions.json#/error"
207
            }
223
          }
208
        }
224
        }
209
      },
225
      },
210
      "x-koha-authorization": {
226
      "x-koha-authorization": {
(-)a/api/v1/swagger/x-primitives.json (-1 / +2 lines)
Lines 17-23 Link Here
17
  },
17
  },
18
  "cityid": {
18
  "cityid": {
19
    "type": "string",
19
    "type": "string",
20
    "description": "internally assigned city identifier"
20
    "description": "internally assigned city identifier",
21
    "readOnly": true
21
  },
22
  },
22
  "email": {
23
  "email": {
23
    "type": ["string", "null"],
24
    "type": ["string", "null"],
(-)a/t/db_dependent/api/v1/cities.t (-33 / +110 lines)
Lines 24-37 use Test::Warn; Link Here
24
use t::lib::TestBuilder;
24
use t::lib::TestBuilder;
25
use t::lib::Mocks;
25
use t::lib::Mocks;
26
26
27
use Data::Printer colored => 1;
28
29
use C4::Auth;
27
use C4::Auth;
30
use Koha::Cities;
28
use Koha::Cities;
31
use Koha::Database;
29
use Koha::Database;
32
30
33
my $schema  = Koha::Database->new->schema;
31
my $schema  = Koha::Database->new->schema;
34
my $builder = t::lib::TestBuilder->new;
32
my $builder = t::lib::TestBuilder->new;
33
35
# FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling
34
# FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling
36
# this affects the other REST api tests
35
# this affects the other REST api tests
37
t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
36
t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
Lines 41-47 my $t = Test::Mojo->new('Koha::REST::V1'); Link Here
41
40
42
subtest 'list() tests' => sub {
41
subtest 'list() tests' => sub {
43
42
44
    plan tests => 19;
43
    plan tests => 18;
45
44
46
    $schema->storage->txn_begin;
45
    $schema->storage->txn_begin;
47
46
Lines 82-88 subtest 'list() tests' => sub { Link Here
82
      $t->ua->build_tx( GET => "/api/v1/cities?city_country=" . $city_country );
81
      $t->ua->build_tx( GET => "/api/v1/cities?city_country=" . $city_country );
83
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
82
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
84
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
83
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
85
    $t->request_ok($tx)->status_is(200)->json_is( [ $city, $another_city ] );
84
    my $result =
85
      $t->request_ok($tx)->status_is(200)->json_is( [ $city, $another_city ] );
86
86
87
    $tx = $t->ua->build_tx(
87
    $tx = $t->ua->build_tx(
88
        GET => "/api/v1/cities?city_name=" . $city->{city_name} );
88
        GET => "/api/v1/cities?city_name=" . $city->{city_name} );
Lines 90-104 subtest 'list() tests' => sub { Link Here
90
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
90
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
91
    $t->request_ok($tx)->status_is(200)->json_is( [$city] );
91
    $t->request_ok($tx)->status_is(200)->json_is( [$city] );
92
92
93
    # Warn on unsupported query parameter
93
    $tx = $t->ua->build_tx( GET => '/api/v1/cities?city_blah=blah' );
94
    $tx = $t->ua->build_tx( GET => '/api/v1/cities?city_blah=blah' );
94
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
95
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
95
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
96
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
96
97
    $t->request_ok($tx)->status_is(400)
97
    warning_like {
98
      ->json_is( [{ path => '/query/city_blah', message => 'Malformed query string'}] );
98
        $t->request_ok($tx)->status_is(500)
99
          ->json_like( '/error' => qr/Unknown column/ );
100
    }
101
    qr/Unknown column/, 'Wrong parameters raise warnings';
102
99
103
    $schema->storage->txn_rollback;
100
    $schema->storage->txn_rollback;
104
};
101
};
Lines 130-136 subtest 'get() tests' => sub { Link Here
130
127
131
subtest 'add() tests' => sub {
128
subtest 'add() tests' => sub {
132
129
133
    plan tests => 10;
130
    plan tests => 17;
134
131
135
    $schema->storage->txn_begin;
132
    $schema->storage->txn_begin;
136
133
Lines 152-189 subtest 'add() tests' => sub { Link Here
152
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
149
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
153
    $t->request_ok($tx)->status_is(403);
150
    $t->request_ok($tx)->status_is(403);
154
151
152
    # Authorized attempt to write invalid data
153
    my $city_with_invalid_field = {
154
        city_blah    => "City Blah",
155
        city_state   => "City State",
156
        city_zipcode => "City Zipcode",
157
        city_country => "City Country"
158
    };
159
160
    $tx = $t->ua->build_tx(
161
        POST => "/api/v1/cities/" => json => $city_with_invalid_field );
162
    $tx->req->cookies(
163
        { name => 'CGISESSID', value => $authorized_session_id } );
164
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
165
    $t->request_ok($tx)->status_is(400)->json_is(
166
        "/errors" => [
167
            {
168
                message => "Properties not allowed: city_blah.",
169
                path    => "/body"
170
            }
171
        ]
172
    );
173
155
    # Authorized attempt to write
174
    # Authorized attempt to write
156
    $tx = $t->ua->build_tx( POST => "/api/v1/cities/" => json => $city );
175
    $tx = $t->ua->build_tx( POST => "/api/v1/cities/" => json => $city );
157
    $tx->req->cookies(
176
    $tx->req->cookies(
158
        { name => 'CGISESSID', value => $authorized_session_id } );
177
        { name => 'CGISESSID', value => $authorized_session_id } );
159
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
178
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
160
    $t->request_ok($tx)->status_is(200)
179
    my $cityid = $t->request_ok($tx)->status_is(200)
161
      ->json_is( '/city_name'    => $city->{city_name} )
180
      ->json_is( '/city_name'    => $city->{city_name} )
162
      ->json_is( '/city_state'   => $city->{city_state} )
181
      ->json_is( '/city_state'   => $city->{city_state} )
163
      ->json_is( '/city_zipcode' => $city->{city_zipcode} )
182
      ->json_is( '/city_zipcode' => $city->{city_zipcode} )
164
      ->json_is( '/city_country' => $city->{city_country} );
183
      ->json_is( '/city_country' => $city->{city_country} )
184
      ->tx->res->json->{cityid};
165
185
166
    my $city_with_invalid_field = {
186
    # Authorized attempt to create with null id
167
        city_blah    => "City Blah",
187
    $city->{cityid} = undef;
168
        city_state   => "City State",
188
    $tx = $t->ua->build_tx(
169
        city_zipcode => "City Zipcode",
189
        POST => "/api/v1/cities/" => json => $city );
170
        city_country => "City Country"
190
    $tx->req->cookies(
171
    };
191
        { name => 'CGISESSID', value => $authorized_session_id } );
192
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
193
    $t->request_ok($tx)->status_is(400)->json_has('/errors');
172
194
173
    # Authorized attempt to write invalid data
195
    # Authorized attempt to create with existing id
196
    $city->{cityid} = $cityid;
174
    $tx = $t->ua->build_tx(
197
    $tx = $t->ua->build_tx(
175
        POST => "/api/v1/cities/" => json => $city_with_invalid_field );
198
        POST => "/api/v1/cities/" => json => $city );
176
    $tx->req->cookies(
199
    $tx->req->cookies(
177
        { name => 'CGISESSID', value => $authorized_session_id } );
200
        { name => 'CGISESSID', value => $authorized_session_id } );
178
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
201
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
179
    $t->request_ok($tx)->status_is(500);
202
    $t->request_ok($tx)->status_is(400)->json_is(
203
        "/errors" => [
204
            {
205
                message => "Read-only.",
206
                path    => "/body/cityid"
207
            }
208
        ]
209
    );
180
210
181
    $schema->storage->txn_rollback;
211
    $schema->storage->txn_rollback;
182
};
212
};
183
213
184
subtest 'update() tests' => sub {
214
subtest 'update() tests' => sub {
185
215
186
    plan tests => 10;
216
    plan tests => 15;
187
217
188
    $schema->storage->txn_begin;
218
    $schema->storage->txn_begin;
189
219
Lines 202-233 subtest 'update() tests' => sub { Link Here
202
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
232
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
203
    $t->request_ok($tx)->status_is(403);
233
    $t->request_ok($tx)->status_is(403);
204
234
235
    # Attempt partial update on a PUT
236
    my $city_with_missing_field = {
237
        city_name    => 'New name',
238
        city_state   => 'New state',
239
        city_country => 'New country'
240
    };
241
205
    $tx = $t->ua->build_tx(
242
    $tx = $t->ua->build_tx(
206
        PUT => "/api/v1/cities/$city_id" => json => { city_name => 'New name' }
243
        PUT => "/api/v1/cities/$city_id" => json => $city_with_missing_field );
207
    );
208
    $tx->req->cookies(
244
    $tx->req->cookies(
209
        { name => 'CGISESSID', value => $authorized_session_id } );
245
        { name => 'CGISESSID', value => $authorized_session_id } );
210
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
246
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
211
    $t->request_ok($tx)->status_is(200)->json_is( '/city_name' => 'New name' );
247
    $t->request_ok($tx)->status_is(400)
248
      ->json_is( "/errors" =>
249
          [ { message => "Missing property.", path => "/body/city_zipcode" } ]
250
      );
251
252
    # Full object update on PUT
253
    my $city_with_updated_field = {
254
        city_name    => "London",
255
        city_state   => "City State",
256
        city_zipcode => "City Zipcode",
257
        city_country => "City Country"
258
    };
212
259
213
    $tx = $t->ua->build_tx(
260
    $tx = $t->ua->build_tx(
214
        PUT => "/api/v1/cities/$city_id" => json => { city_blah => 'New blah' }
261
        PUT => "/api/v1/cities/$city_id" => json => $city_with_updated_field );
215
    );
216
    $tx->req->cookies(
262
    $tx->req->cookies(
217
        { name => 'CGISESSID', value => $authorized_session_id } );
263
        { name => 'CGISESSID', value => $authorized_session_id } );
218
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
264
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
219
    $t->request_ok($tx)->status_is(500)
265
    $t->request_ok($tx)->status_is(200)->json_is( '/city_name' => 'London' );
220
      ->json_is( '/error' => "No method city_blah for Koha::City" );
266
267
    # Authorized attempt to write invalid data
268
    my $city_with_invalid_field = {
269
        city_blah    => "City Blah",
270
        city_state   => "City State",
271
        city_zipcode => "City Zipcode",
272
        city_country => "City Country"
273
    };
274
275
    $tx = $t->ua->build_tx(
276
        PUT => "/api/v1/cities/$city_id" => json => $city_with_invalid_field );
277
    $tx->req->cookies(
278
        { name => 'CGISESSID', value => $authorized_session_id } );
279
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
280
    $t->request_ok($tx)->status_is(400)->json_is(
281
        "/errors" => [
282
            {
283
                message => "Properties not allowed: city_blah.",
284
                path    => "/body"
285
            }
286
        ]
287
    );
221
288
222
    my $non_existent_id = $city_id + 1;
289
    my $non_existent_id = $city_id + 1;
223
    $tx = $t->ua->build_tx( PUT => "/api/v1/cities/$non_existent_id" => json =>
290
    $tx =
224
          { city_name => 'New name' } );
291
      $t->ua->build_tx( PUT => "/api/v1/cities/$non_existent_id" => json =>
292
          $city_with_updated_field );
225
    $tx->req->cookies(
293
    $tx->req->cookies(
226
        { name => 'CGISESSID', value => $authorized_session_id } );
294
        { name => 'CGISESSID', value => $authorized_session_id } );
227
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
295
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
228
    $t->request_ok($tx)->status_is(404);
296
    $t->request_ok($tx)->status_is(404);
229
297
230
    $schema->storage->txn_rollback;
298
    $schema->storage->txn_rollback;
299
300
    # Wrong mathod (POST)
301
    $city_with_updated_field->{cityid} = 2;
302
303
    $tx = $t->ua->build_tx(
304
        POST => "/api/v1/cities/$city_id" => json => $city_with_updated_field );
305
    $tx->req->cookies(
306
        { name => 'CGISESSID', value => $authorized_session_id } );
307
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
308
    $t->request_ok($tx)->status_is(404);
231
};
309
};
232
310
233
subtest 'delete() tests' => sub {
311
subtest 'delete() tests' => sub {
234
- 

Return to bug 17428