blob: 81a76e02c25f9d78aa89659b03155c289d71800c [file] [log] [blame]
Matthew Treinish9e26ca82016-02-23 11:43:20 -05001# Copyright 2013 IBM Corp.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import copy
Matthew Treinish9e26ca82016-02-23 11:43:20 -050016
Ngo Quoc Cuong33710b32017-05-11 14:17:17 +070017import fixtures
Matthew Treinish9e26ca82016-02-23 11:43:20 -050018import jsonschema
zhulingjie92b87a52019-02-21 01:05:54 +080019from oslo_serialization import jsonutils as json
Matthew Treinish9e26ca82016-02-23 11:43:20 -050020
Jordan Pittier00f25962016-03-18 17:10:07 +010021from tempest.lib.common import http
Matthew Treinish9e26ca82016-02-23 11:43:20 -050022from tempest.lib.common import rest_client
23from tempest.lib import exceptions
Matthew Treinishffad78a2016-04-16 14:39:52 -040024from tempest.tests import base
Matthew Treinish9e26ca82016-02-23 11:43:20 -050025from tempest.tests.lib import fake_auth_provider
26from tempest.tests.lib import fake_http
Jordan Pittier0e53b612016-03-03 14:23:17 +010027import tempest.tests.utils as utils
Matthew Treinish9e26ca82016-02-23 11:43:20 -050028
29
30class BaseRestClientTestClass(base.TestCase):
31
32 url = 'fake_endpoint'
33
34 def setUp(self):
35 super(BaseRestClientTestClass, self).setUp()
36 self.fake_auth_provider = fake_auth_provider.FakeAuthProvider()
37 self.rest_client = rest_client.RestClient(
38 self.fake_auth_provider, None, None)
Jordan Pittier0021c292016-03-29 21:33:34 +020039 self.patchobject(http.ClosingHttp, 'request', self.fake_http.request)
Ngo Quoc Cuong33710b32017-05-11 14:17:17 +070040 self.useFixture(fixtures.MockPatchObject(self.rest_client,
41 '_log_request'))
Matthew Treinish9e26ca82016-02-23 11:43:20 -050042
43
44class TestRestClientHTTPMethods(BaseRestClientTestClass):
45 def setUp(self):
46 self.fake_http = fake_http.fake_httplib2()
47 super(TestRestClientHTTPMethods, self).setUp()
Ngo Quoc Cuong33710b32017-05-11 14:17:17 +070048 self.useFixture(fixtures.MockPatchObject(self.rest_client,
49 '_error_checker'))
Matthew Treinish9e26ca82016-02-23 11:43:20 -050050
51 def test_post(self):
52 __, return_dict = self.rest_client.post(self.url, {}, {})
53 self.assertEqual('POST', return_dict['method'])
54
55 def test_get(self):
56 __, return_dict = self.rest_client.get(self.url)
57 self.assertEqual('GET', return_dict['method'])
Dan Smith2c192f42023-01-18 11:22:34 -080058 self.assertTrue(return_dict['preload_content'])
Matthew Treinish9e26ca82016-02-23 11:43:20 -050059
60 def test_delete(self):
61 __, return_dict = self.rest_client.delete(self.url)
62 self.assertEqual('DELETE', return_dict['method'])
63
64 def test_patch(self):
65 __, return_dict = self.rest_client.patch(self.url, {}, {})
66 self.assertEqual('PATCH', return_dict['method'])
67
68 def test_put(self):
69 __, return_dict = self.rest_client.put(self.url, {}, {})
70 self.assertEqual('PUT', return_dict['method'])
71
72 def test_head(self):
Ngo Quoc Cuong33710b32017-05-11 14:17:17 +070073 self.useFixture(fixtures.MockPatchObject(self.rest_client,
74 'response_checker'))
Matthew Treinish9e26ca82016-02-23 11:43:20 -050075 __, return_dict = self.rest_client.head(self.url)
76 self.assertEqual('HEAD', return_dict['method'])
77
78 def test_copy(self):
79 __, return_dict = self.rest_client.copy(self.url)
80 self.assertEqual('COPY', return_dict['method'])
81
Dan Smith2c192f42023-01-18 11:22:34 -080082 def test_get_chunked(self):
83 self.useFixture(fixtures.MockPatchObject(self.rest_client,
84 '_log_request'))
85 __, return_dict = self.rest_client.get(self.url, chunked=True)
86 # Default is preload_content=True, make sure we passed False
87 self.assertFalse(return_dict['preload_content'])
88 # Make sure we did not pass chunked=True to urllib3 for GET
89 self.assertFalse(return_dict['chunked'])
90 # Make sure we did not call _log_request() on the raw response
91 self.rest_client._log_request.assert_not_called()
92
Matthew Treinish9e26ca82016-02-23 11:43:20 -050093
94class TestRestClientNotFoundHandling(BaseRestClientTestClass):
95 def setUp(self):
96 self.fake_http = fake_http.fake_httplib2(404)
97 super(TestRestClientNotFoundHandling, self).setUp()
98
99 def test_post(self):
100 self.assertRaises(exceptions.NotFound, self.rest_client.post,
101 self.url, {}, {})
102
103
104class TestRestClientHeadersJSON(TestRestClientHTTPMethods):
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500105
106 def _verify_headers(self, resp):
songwenpinge6623072021-02-22 14:47:34 +0800107 resp = dict((k.lower(), v) for k, v in resp.items())
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500108 self.assertEqual(self.header_value, resp['accept'])
109 self.assertEqual(self.header_value, resp['content-type'])
110
111 def setUp(self):
112 super(TestRestClientHeadersJSON, self).setUp()
Masayuki Igawa189b92f2017-04-24 18:57:17 +0900113 self.header_value = 'application/json'
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500114
115 def test_post(self):
116 resp, __ = self.rest_client.post(self.url, {})
117 self._verify_headers(resp)
118
119 def test_get(self):
120 resp, __ = self.rest_client.get(self.url)
121 self._verify_headers(resp)
122
123 def test_delete(self):
124 resp, __ = self.rest_client.delete(self.url)
125 self._verify_headers(resp)
126
127 def test_patch(self):
128 resp, __ = self.rest_client.patch(self.url, {})
129 self._verify_headers(resp)
130
131 def test_put(self):
132 resp, __ = self.rest_client.put(self.url, {})
133 self._verify_headers(resp)
134
135 def test_head(self):
Ngo Quoc Cuong33710b32017-05-11 14:17:17 +0700136 self.useFixture(fixtures.MockPatchObject(self.rest_client,
137 'response_checker'))
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500138 resp, __ = self.rest_client.head(self.url)
139 self._verify_headers(resp)
140
141 def test_copy(self):
142 resp, __ = self.rest_client.copy(self.url)
143 self._verify_headers(resp)
144
145
146class TestRestClientUpdateHeaders(BaseRestClientTestClass):
147 def setUp(self):
148 self.fake_http = fake_http.fake_httplib2()
149 super(TestRestClientUpdateHeaders, self).setUp()
Ngo Quoc Cuong33710b32017-05-11 14:17:17 +0700150 self.useFixture(fixtures.MockPatchObject(self.rest_client,
151 '_error_checker'))
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500152 self.headers = {'X-Configuration-Session': 'session_id'}
153
154 def test_post_update_headers(self):
155 __, return_dict = self.rest_client.post(self.url, {},
156 extra_headers=True,
157 headers=self.headers)
158
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900159 self.assertLessEqual(
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500160 {'X-Configuration-Session': 'session_id',
161 'Content-Type': 'application/json',
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900162 'Accept': 'application/json'}.items(),
163 return_dict['headers'].items()
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500164 )
165
166 def test_get_update_headers(self):
167 __, return_dict = self.rest_client.get(self.url,
168 extra_headers=True,
169 headers=self.headers)
170
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900171 self.assertLessEqual(
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500172 {'X-Configuration-Session': 'session_id',
173 'Content-Type': 'application/json',
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900174 'Accept': 'application/json'}.items(),
175 return_dict['headers'].items()
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500176 )
177
178 def test_delete_update_headers(self):
179 __, return_dict = self.rest_client.delete(self.url,
180 extra_headers=True,
181 headers=self.headers)
182
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900183 self.assertLessEqual(
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500184 {'X-Configuration-Session': 'session_id',
185 'Content-Type': 'application/json',
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900186 'Accept': 'application/json'}.items(),
187 return_dict['headers'].items()
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500188 )
189
190 def test_patch_update_headers(self):
191 __, return_dict = self.rest_client.patch(self.url, {},
192 extra_headers=True,
193 headers=self.headers)
194
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900195 self.assertLessEqual(
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500196 {'X-Configuration-Session': 'session_id',
197 'Content-Type': 'application/json',
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900198 'Accept': 'application/json'}.items(),
199 return_dict['headers'].items()
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500200 )
201
202 def test_put_update_headers(self):
203 __, return_dict = self.rest_client.put(self.url, {},
204 extra_headers=True,
205 headers=self.headers)
206
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900207 self.assertLessEqual(
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500208 {'X-Configuration-Session': 'session_id',
209 'Content-Type': 'application/json',
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900210 'Accept': 'application/json'}.items(),
211 return_dict['headers'].items()
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500212 )
213
214 def test_head_update_headers(self):
Ngo Quoc Cuong33710b32017-05-11 14:17:17 +0700215 self.useFixture(fixtures.MockPatchObject(self.rest_client,
216 'response_checker'))
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500217
218 __, return_dict = self.rest_client.head(self.url,
219 extra_headers=True,
220 headers=self.headers)
221
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900222 self.assertLessEqual(
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500223 {'X-Configuration-Session': 'session_id',
224 'Content-Type': 'application/json',
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900225 'Accept': 'application/json'}.items(),
226 return_dict['headers'].items()
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500227 )
228
229 def test_copy_update_headers(self):
230 __, return_dict = self.rest_client.copy(self.url,
231 extra_headers=True,
232 headers=self.headers)
233
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900234 self.assertLessEqual(
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500235 {'X-Configuration-Session': 'session_id',
236 'Content-Type': 'application/json',
Takashi Kajinamibbe4f8c2021-11-30 13:29:18 +0900237 'Accept': 'application/json'}.items(),
238 return_dict['headers'].items()
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500239 )
240
241
242class TestRestClientParseRespJSON(BaseRestClientTestClass):
243 TYPE = "json"
244
245 keys = ["fake_key1", "fake_key2"]
246 values = ["fake_value1", "fake_value2"]
247 item_expected = dict((key, value) for (key, value) in zip(keys, values))
248 list_expected = {"body_list": [
249 {keys[0]: values[0]},
250 {keys[1]: values[1]},
251 ]}
252 dict_expected = {"body_dict": {
253 keys[0]: values[0],
254 keys[1]: values[1],
255 }}
256 null_dict = {}
257
258 def setUp(self):
259 self.fake_http = fake_http.fake_httplib2()
260 super(TestRestClientParseRespJSON, self).setUp()
261 self.rest_client.TYPE = self.TYPE
262
263 def test_parse_resp_body_item(self):
264 body = self.rest_client._parse_resp(json.dumps(self.item_expected))
265 self.assertEqual(self.item_expected, body)
266
267 def test_parse_resp_body_list(self):
268 body = self.rest_client._parse_resp(json.dumps(self.list_expected))
269 self.assertEqual(self.list_expected["body_list"], body)
270
271 def test_parse_resp_body_dict(self):
272 body = self.rest_client._parse_resp(json.dumps(self.dict_expected))
273 self.assertEqual(self.dict_expected["body_dict"], body)
274
275 def test_parse_resp_two_top_keys(self):
276 dict_two_keys = self.dict_expected.copy()
277 dict_two_keys.update({"second_key": ""})
278 body = self.rest_client._parse_resp(json.dumps(dict_two_keys))
279 self.assertEqual(dict_two_keys, body)
280
281 def test_parse_resp_one_top_key_without_list_or_dict(self):
282 data = {"one_top_key": "not_list_or_dict_value"}
283 body = self.rest_client._parse_resp(json.dumps(data))
284 self.assertEqual(data, body)
285
286 def test_parse_nullable_dict(self):
287 body = self.rest_client._parse_resp(json.dumps(self.null_dict))
288 self.assertEqual(self.null_dict, body)
289
Ken'ichi Ohmichi69a8edc2017-04-28 11:41:20 -0700290 def test_parse_empty_list(self):
291 empty_list = []
292 body = self.rest_client._parse_resp(json.dumps(empty_list))
293 self.assertEqual(empty_list, body)
294
Goutham Pacha Ravic0a15ba2022-04-06 23:41:57 +0530295 def test_parse_top_key_match(self):
296 body = self.rest_client._parse_resp(json.dumps(self.dict_expected),
297 top_key_to_verify="body_dict")
298 self.assertEqual(self.dict_expected["body_dict"], body)
299
300
301class TestRestClientParseErrorRespJSON(BaseRestClientTestClass):
302
303 dict_expected = {"body_dict": {"fake_key": "fake_value"}}
304
305 def setUp(self):
306 self.fake_http = fake_http.fake_httplib2()
307 super(TestRestClientParseErrorRespJSON, self).setUp()
308
309 def test_parse_top_key_no_match(self):
310 self.assertRaises(AssertionError,
311 self.rest_client._parse_resp,
312 json.dumps(self.dict_expected),
313 top_key_to_verify="body_key")
314
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500315
316class TestRestClientErrorCheckerJSON(base.TestCase):
317 c_type = "application/json"
318
319 def set_data(self, r_code, enc=None, r_body=None, absolute_limit=True):
320 if enc is None:
321 enc = self.c_type
322 resp_dict = {'status': r_code, 'content-type': enc}
323 resp_body = {'resp_body': 'fake_resp_body'}
324
325 if absolute_limit is False:
326 resp_dict.update({'retry-after': 120})
327 resp_body.update({'overLimit': {'message': 'fake_message'}})
Jordan Pittier00f25962016-03-18 17:10:07 +0100328 resp = fake_http.fake_http_response(headers=resp_dict,
329 status=int(r_code),
330 body=json.dumps(resp_body))
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500331 data = {
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500332 "resp": resp,
333 "resp_body": json.dumps(resp_body)
334 }
335 if r_body is not None:
336 data.update({"resp_body": r_body})
337 return data
338
339 def setUp(self):
340 super(TestRestClientErrorCheckerJSON, self).setUp()
341 self.rest_client = rest_client.RestClient(
342 fake_auth_provider.FakeAuthProvider(), None, None)
343
344 def test_response_less_than_400(self):
345 self.rest_client._error_checker(**self.set_data("399"))
346
347 def _test_error_checker(self, exception_type, data):
348 e = self.assertRaises(exception_type,
349 self.rest_client._error_checker,
350 **data)
351 self.assertEqual(e.resp, data['resp'])
352 self.assertTrue(hasattr(e, 'resp_body'))
353 return e
354
355 def test_response_400(self):
356 self._test_error_checker(exceptions.BadRequest, self.set_data("400"))
357
358 def test_response_401(self):
359 self._test_error_checker(exceptions.Unauthorized, self.set_data("401"))
360
361 def test_response_403(self):
362 self._test_error_checker(exceptions.Forbidden, self.set_data("403"))
363
364 def test_response_404(self):
365 self._test_error_checker(exceptions.NotFound, self.set_data("404"))
366
367 def test_response_409(self):
368 self._test_error_checker(exceptions.Conflict, self.set_data("409"))
369
370 def test_response_410(self):
371 self._test_error_checker(exceptions.Gone, self.set_data("410"))
372
Kevin Bentona82bc862017-02-13 01:16:13 -0800373 def test_response_412(self):
374 self._test_error_checker(exceptions.PreconditionFailed,
375 self.set_data("412"))
376
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500377 def test_response_413(self):
378 self._test_error_checker(exceptions.OverLimit, self.set_data("413"))
379
380 def test_response_413_without_absolute_limit(self):
381 self._test_error_checker(exceptions.RateLimitExceeded,
382 self.set_data("413", absolute_limit=False))
383
384 def test_response_415(self):
385 self._test_error_checker(exceptions.InvalidContentType,
386 self.set_data("415"))
387
388 def test_response_422(self):
389 self._test_error_checker(exceptions.UnprocessableEntity,
390 self.set_data("422"))
391
392 def test_response_500_with_text(self):
393 # _parse_resp is expected to return 'str'
394 self._test_error_checker(exceptions.ServerFault, self.set_data("500"))
395
396 def test_response_501_with_text(self):
397 self._test_error_checker(exceptions.NotImplemented,
398 self.set_data("501"))
399
400 def test_response_400_with_dict(self):
401 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
402 e = self._test_error_checker(exceptions.BadRequest,
403 self.set_data("400", r_body=r_body))
404
405 if self.c_type == 'application/json':
406 expected = {"err": "fake_resp_body"}
407 else:
408 expected = r_body
409 self.assertEqual(expected, e.resp_body)
410
411 def test_response_401_with_dict(self):
412 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
413 e = self._test_error_checker(exceptions.Unauthorized,
414 self.set_data("401", r_body=r_body))
415
416 if self.c_type == 'application/json':
417 expected = {"err": "fake_resp_body"}
418 else:
419 expected = r_body
420 self.assertEqual(expected, e.resp_body)
421
422 def test_response_403_with_dict(self):
423 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
424 e = self._test_error_checker(exceptions.Forbidden,
425 self.set_data("403", r_body=r_body))
426
427 if self.c_type == 'application/json':
428 expected = {"err": "fake_resp_body"}
429 else:
430 expected = r_body
431 self.assertEqual(expected, e.resp_body)
432
433 def test_response_404_with_dict(self):
434 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
435 e = self._test_error_checker(exceptions.NotFound,
436 self.set_data("404", r_body=r_body))
437
438 if self.c_type == 'application/json':
439 expected = {"err": "fake_resp_body"}
440 else:
441 expected = r_body
442 self.assertEqual(expected, e.resp_body)
443
444 def test_response_404_with_invalid_dict(self):
445 r_body = '{"foo": "bar"]'
446 e = self._test_error_checker(exceptions.NotFound,
447 self.set_data("404", r_body=r_body))
448
449 expected = r_body
450 self.assertEqual(expected, e.resp_body)
451
452 def test_response_410_with_dict(self):
453 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
454 e = self._test_error_checker(exceptions.Gone,
455 self.set_data("410", r_body=r_body))
456
457 if self.c_type == 'application/json':
458 expected = {"err": "fake_resp_body"}
459 else:
460 expected = r_body
461 self.assertEqual(expected, e.resp_body)
462
463 def test_response_410_with_invalid_dict(self):
464 r_body = '{"foo": "bar"]'
465 e = self._test_error_checker(exceptions.Gone,
466 self.set_data("410", r_body=r_body))
467
468 expected = r_body
469 self.assertEqual(expected, e.resp_body)
470
471 def test_response_409_with_dict(self):
472 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
473 e = self._test_error_checker(exceptions.Conflict,
474 self.set_data("409", r_body=r_body))
475
476 if self.c_type == 'application/json':
477 expected = {"err": "fake_resp_body"}
478 else:
479 expected = r_body
480 self.assertEqual(expected, e.resp_body)
481
482 def test_response_500_with_dict(self):
483 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
484 e = self._test_error_checker(exceptions.ServerFault,
485 self.set_data("500", r_body=r_body))
486
487 if self.c_type == 'application/json':
488 expected = {"err": "fake_resp_body"}
489 else:
490 expected = r_body
491 self.assertEqual(expected, e.resp_body)
492
493 def test_response_501_with_dict(self):
494 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
495 self._test_error_checker(exceptions.NotImplemented,
496 self.set_data("501", r_body=r_body))
497
498 def test_response_bigger_than_400(self):
499 # Any response code, that bigger than 400, and not in
Kevin Bentona82bc862017-02-13 01:16:13 -0800500 # (401, 403, 404, 409, 412, 413, 422, 500, 501)
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500501 self._test_error_checker(exceptions.UnexpectedResponseCode,
502 self.set_data("402"))
503
504
505class TestRestClientErrorCheckerTEXT(TestRestClientErrorCheckerJSON):
506 c_type = "text/plain"
507
508 def test_fake_content_type(self):
509 # This test is required only in one exemplar
510 # Any response code, that bigger than 400, and not in
511 # (401, 403, 404, 409, 413, 422, 500, 501)
512 self._test_error_checker(exceptions.UnexpectedContentType,
513 self.set_data("405", enc="fake_enc"))
514
515 def test_response_413_without_absolute_limit(self):
516 # Skip this test because rest_client cannot get overLimit message
517 # from text body.
518 pass
519
520
521class TestRestClientUtils(BaseRestClientTestClass):
522
523 def _is_resource_deleted(self, resource_id):
524 if not isinstance(self.retry_pass, int):
525 return False
526 if self.retry_count >= self.retry_pass:
527 return True
528 self.retry_count = self.retry_count + 1
529 return False
530
531 def setUp(self):
532 self.fake_http = fake_http.fake_httplib2()
533 super(TestRestClientUtils, self).setUp()
534 self.retry_count = 0
535 self.retry_pass = None
536 self.original_deleted_method = self.rest_client.is_resource_deleted
537 self.rest_client.is_resource_deleted = self._is_resource_deleted
538
539 def test_wait_for_resource_deletion(self):
540 self.retry_pass = 2
541 # Ensure timeout long enough for loop execution to hit retry count
542 self.rest_client.build_timeout = 500
543 sleep_mock = self.patch('time.sleep')
544 self.rest_client.wait_for_resource_deletion('1234')
545 self.assertEqual(len(sleep_mock.mock_calls), 2)
546
547 def test_wait_for_resource_deletion_not_deleted(self):
548 self.patch('time.sleep')
549 # Set timeout to be very quick to force exception faster
Jordan Pittier0e53b612016-03-03 14:23:17 +0100550 timeout = 1
551 self.rest_client.build_timeout = timeout
552
553 time_mock = self.patch('time.time')
554 time_mock.side_effect = utils.generate_timeout_series(timeout)
555
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500556 self.assertRaises(exceptions.TimeoutException,
557 self.rest_client.wait_for_resource_deletion,
558 '1234')
559
Sampat Ponnagantief552162021-03-17 18:07:36 +0000560 # time.time() should be called 4 times,
561 # 1. Start timer
562 # 2. End timer
563 # 3 & 4. To generate timeout exception message
564 self.assertEqual(4, time_mock.call_count)
Jordan Pittier0e53b612016-03-03 14:23:17 +0100565
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500566 def test_wait_for_deletion_with_unimplemented_deleted_method(self):
567 self.rest_client.is_resource_deleted = self.original_deleted_method
568 self.assertRaises(NotImplementedError,
569 self.rest_client.wait_for_resource_deletion,
570 '1234')
571
572 def test_get_versions(self):
573 self.rest_client._parse_resp = lambda x: [{'id': 'v1'}, {'id': 'v2'}]
574 actual_resp, actual_versions = self.rest_client.get_versions()
575 self.assertEqual(['v1', 'v2'], list(actual_versions))
576
577 def test__str__(self):
578 def get_token():
579 return "deadbeef"
580
581 self.fake_auth_provider.get_token = get_token
582 self.assertIsNotNone(str(self.rest_client))
583
584
Paul Glass119565a2016-04-06 11:41:42 -0500585class TestRateLimiting(BaseRestClientTestClass):
586
587 def setUp(self):
588 self.fake_http = fake_http.fake_httplib2()
589 super(TestRateLimiting, self).setUp()
590
591 def test__get_retry_after_delay_with_integer(self):
592 resp = {'retry-after': '123'}
593 self.assertEqual(123, self.rest_client._get_retry_after_delay(resp))
594
595 def test__get_retry_after_delay_with_http_date(self):
596 resp = {
597 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
598 'retry-after': 'Mon, 4 Apr 2016 21:58:26 GMT',
599 }
600 self.assertEqual(123, self.rest_client._get_retry_after_delay(resp))
601
602 def test__get_retry_after_delay_of_zero_with_integer(self):
603 resp = {'retry-after': '0'}
604 self.assertEqual(1, self.rest_client._get_retry_after_delay(resp))
605
606 def test__get_retry_after_delay_of_zero_with_http_date(self):
607 resp = {
608 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
609 'retry-after': 'Mon, 4 Apr 2016 21:56:23 GMT',
610 }
611 self.assertEqual(1, self.rest_client._get_retry_after_delay(resp))
612
613 def test__get_retry_after_delay_with_missing_date_header(self):
614 resp = {
615 'retry-after': 'Mon, 4 Apr 2016 21:58:26 GMT',
616 }
617 self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
618 resp)
619
620 def test__get_retry_after_delay_with_invalid_http_date(self):
621 resp = {
622 'retry-after': 'Mon, 4 AAA 2016 21:58:26 GMT',
623 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
624 }
625 self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
626 resp)
627
628 def test__get_retry_after_delay_with_missing_retry_after_header(self):
629 self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
630 {})
631
632 def test_is_absolute_limit_gives_false_with_retry_after(self):
633 resp = {'retry-after': 123}
634
635 # is_absolute_limit() requires the overLimit body to be unwrapped
636 resp_body = self.rest_client._parse_resp("""{
637 "overLimit": {
638 "message": ""
639 }
640 }""")
641 self.assertFalse(self.rest_client.is_absolute_limit(resp, resp_body))
642
643
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500644class TestProperties(BaseRestClientTestClass):
645
646 def setUp(self):
647 self.fake_http = fake_http.fake_httplib2()
648 super(TestProperties, self).setUp()
649 creds_dict = {
650 'username': 'test-user',
651 'user_id': 'test-user_id',
652 'tenant_name': 'test-tenant_name',
653 'tenant_id': 'test-tenant_id',
654 'password': 'test-password'
655 }
656 self.rest_client = rest_client.RestClient(
657 fake_auth_provider.FakeAuthProvider(creds_dict=creds_dict),
658 None, None)
659
660 def test_properties(self):
661 self.assertEqual('test-user', self.rest_client.user)
662 self.assertEqual('test-user_id', self.rest_client.user_id)
663 self.assertEqual('test-tenant_name', self.rest_client.tenant_name)
664 self.assertEqual('test-tenant_id', self.rest_client.tenant_id)
665 self.assertEqual('test-password', self.rest_client.password)
666
667 self.rest_client.api_version = 'v1'
668 expected = {'api_version': 'v1',
669 'endpoint_type': 'publicURL',
670 'region': None,
Eric Wehrmeister54c7bd42016-02-24 11:11:07 -0600671 'name': None,
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500672 'service': None,
673 'skip_path': True}
674 self.rest_client.skip_path()
675 self.assertEqual(expected, self.rest_client.filters)
676
677 self.rest_client.reset_path()
678 self.rest_client.api_version = 'v1'
679 expected = {'api_version': 'v1',
680 'endpoint_type': 'publicURL',
681 'region': None,
Eric Wehrmeister54c7bd42016-02-24 11:11:07 -0600682 'name': None,
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500683 'service': None}
684 self.assertEqual(expected, self.rest_client.filters)
685
686
687class TestExpectedSuccess(BaseRestClientTestClass):
688
689 def setUp(self):
690 self.fake_http = fake_http.fake_httplib2()
691 super(TestExpectedSuccess, self).setUp()
692
693 def test_expected_succes_int_match(self):
694 expected_code = 202
695 read_code = 202
696 resp = self.rest_client.expected_success(expected_code, read_code)
697 # Assert None resp on success
698 self.assertFalse(resp)
699
700 def test_expected_succes_int_no_match(self):
701 expected_code = 204
702 read_code = 202
703 self.assertRaises(exceptions.InvalidHttpSuccessCode,
704 self.rest_client.expected_success,
705 expected_code, read_code)
706
707 def test_expected_succes_list_match(self):
708 expected_code = [202, 204]
709 read_code = 202
710 resp = self.rest_client.expected_success(expected_code, read_code)
711 # Assert None resp on success
712 self.assertFalse(resp)
713
714 def test_expected_succes_list_no_match(self):
715 expected_code = [202, 204]
716 read_code = 200
717 self.assertRaises(exceptions.InvalidHttpSuccessCode,
718 self.rest_client.expected_success,
719 expected_code, read_code)
720
721 def test_non_success_expected_int(self):
722 expected_code = 404
723 read_code = 202
724 self.assertRaises(AssertionError, self.rest_client.expected_success,
725 expected_code, read_code)
726
727 def test_non_success_expected_list(self):
728 expected_code = [404, 202]
729 read_code = 202
730 self.assertRaises(AssertionError, self.rest_client.expected_success,
731 expected_code, read_code)
732
ghanshyamc3074202016-04-18 15:20:45 +0900733 def test_non_success_read_code_as_string(self):
734 expected_code = 202
735 read_code = '202'
736 self.assertRaises(TypeError, self.rest_client.expected_success,
737 expected_code, read_code)
738
739 def test_non_success_read_code_as_list(self):
740 expected_code = 202
741 read_code = [202]
742 self.assertRaises(TypeError, self.rest_client.expected_success,
743 expected_code, read_code)
744
745 def test_non_success_expected_code_as_non_int(self):
746 expected_code = ['201', 202]
747 read_code = 202
748 self.assertRaises(AssertionError, self.rest_client.expected_success,
749 expected_code, read_code)
750
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500751
752class TestResponseBody(base.TestCase):
753
754 def test_str(self):
755 response = {'status': 200}
756 body = {'key1': 'value1'}
757 actual = rest_client.ResponseBody(response, body)
758 self.assertEqual("response: %s\nBody: %s" % (response, body),
759 str(actual))
760
761
762class TestResponseBodyData(base.TestCase):
763
764 def test_str(self):
765 response = {'status': 200}
766 data = 'data1'
767 actual = rest_client.ResponseBodyData(response, data)
768 self.assertEqual("response: %s\nBody: %s" % (response, data),
769 str(actual))
770
771
772class TestResponseBodyList(base.TestCase):
773
774 def test_str(self):
775 response = {'status': 200}
776 body = ['value1', 'value2', 'value3']
777 actual = rest_client.ResponseBodyList(response, body)
778 self.assertEqual("response: %s\nBody: %s" % (response, body),
779 str(actual))
780
781
782class TestJSONSchemaValidationBase(base.TestCase):
783
784 class Response(dict):
785
786 def __getattr__(self, attr):
787 return self[attr]
788
789 def __setattr__(self, attr, value):
790 self[attr] = value
791
792 def setUp(self):
793 super(TestJSONSchemaValidationBase, self).setUp()
794 self.fake_auth_provider = fake_auth_provider.FakeAuthProvider()
795 self.rest_client = rest_client.RestClient(
796 self.fake_auth_provider, None, None)
797
798 def _test_validate_pass(self, schema, resp_body, status=200):
799 resp = self.Response()
800 resp.status = status
801 self.rest_client.validate_response(schema, resp, resp_body)
802
803 def _test_validate_fail(self, schema, resp_body, status=200,
804 error_msg="HTTP response body is invalid"):
805 resp = self.Response()
806 resp.status = status
807 ex = self.assertRaises(exceptions.InvalidHTTPResponseBody,
808 self.rest_client.validate_response,
809 schema, resp, resp_body)
810 self.assertIn(error_msg, ex._error_string)
811
812
813class TestRestClientJSONSchemaValidation(TestJSONSchemaValidationBase):
814
815 schema = {
816 'status_code': [200],
817 'response_body': {
818 'type': 'object',
819 'properties': {
820 'foo': {
821 'type': 'integer',
822 },
823 },
824 'required': ['foo']
825 }
826 }
827
828 def test_validate_pass_with_http_success_code(self):
829 body = {'foo': 12}
830 self._test_validate_pass(self.schema, body, status=200)
831
832 def test_validate_pass_with_http_redirect_code(self):
833 body = {'foo': 12}
834 schema = copy.deepcopy(self.schema)
835 schema['status_code'] = 300
836 self._test_validate_pass(schema, body, status=300)
837
838 def test_validate_not_http_success_code(self):
839 schema = {
840 'status_code': [200]
841 }
842 body = {}
843 self._test_validate_pass(schema, body, status=400)
844
845 def test_validate_multiple_allowed_type(self):
846 schema = {
847 'status_code': [200],
848 'response_body': {
849 'type': 'object',
850 'properties': {
851 'foo': {
852 'type': ['integer', 'string'],
853 },
854 },
855 'required': ['foo']
856 }
857 }
858 body = {'foo': 12}
859 self._test_validate_pass(schema, body)
860 body = {'foo': '12'}
861 self._test_validate_pass(schema, body)
862
863 def test_validate_enable_additional_property_pass(self):
864 schema = {
865 'status_code': [200],
866 'response_body': {
867 'type': 'object',
868 'properties': {
869 'foo': {'type': 'integer'}
870 },
871 'additionalProperties': True,
872 'required': ['foo']
873 }
874 }
875 body = {'foo': 12, 'foo2': 'foo2value'}
876 self._test_validate_pass(schema, body)
877
878 def test_validate_disable_additional_property_pass(self):
879 schema = {
880 'status_code': [200],
881 'response_body': {
882 'type': 'object',
883 'properties': {
884 'foo': {'type': 'integer'}
885 },
886 'additionalProperties': False,
887 'required': ['foo']
888 }
889 }
890 body = {'foo': 12}
891 self._test_validate_pass(schema, body)
892
893 def test_validate_disable_additional_property_fail(self):
894 schema = {
895 'status_code': [200],
896 'response_body': {
897 'type': 'object',
898 'properties': {
899 'foo': {'type': 'integer'}
900 },
901 'additionalProperties': False,
902 'required': ['foo']
903 }
904 }
905 body = {'foo': 12, 'foo2': 'foo2value'}
906 self._test_validate_fail(schema, body)
907
908 def test_validate_wrong_status_code(self):
909 schema = {
910 'status_code': [202]
911 }
912 body = {}
913 resp = self.Response()
914 resp.status = 200
915 ex = self.assertRaises(exceptions.InvalidHttpSuccessCode,
916 self.rest_client.validate_response,
917 schema, resp, body)
918 self.assertIn("Unexpected http success status code", ex._error_string)
919
920 def test_validate_wrong_attribute_type(self):
921 body = {'foo': 1.2}
922 self._test_validate_fail(self.schema, body)
923
924 def test_validate_unexpected_response_body(self):
925 schema = {
926 'status_code': [200],
927 }
928 body = {'foo': 12}
929 self._test_validate_fail(
930 schema, body,
931 error_msg="HTTP response body should not exist")
932
933 def test_validate_missing_response_body(self):
934 body = {}
935 self._test_validate_fail(self.schema, body)
936
937 def test_validate_missing_required_attribute(self):
938 body = {'notfoo': 12}
939 self._test_validate_fail(self.schema, body)
940
941 def test_validate_response_body_not_list(self):
942 schema = {
943 'status_code': [200],
944 'response_body': {
945 'type': 'object',
946 'properties': {
947 'list_items': {
948 'type': 'array',
949 'items': {'foo': {'type': 'integer'}}
950 }
951 },
952 'required': ['list_items'],
953 }
954 }
955 body = {'foo': 12}
956 self._test_validate_fail(schema, body)
957
958 def test_validate_response_body_list_pass(self):
959 schema = {
960 'status_code': [200],
961 'response_body': {
962 'type': 'object',
963 'properties': {
964 'list_items': {
965 'type': 'array',
966 'items': {'foo': {'type': 'integer'}}
967 }
968 },
969 'required': ['list_items'],
970 }
971 }
972 body = {'list_items': [{'foo': 12}, {'foo': 10}]}
973 self._test_validate_pass(schema, body)
974
975
976class TestRestClientJSONHeaderSchemaValidation(TestJSONSchemaValidationBase):
977
978 schema = {
979 'status_code': [200],
980 'response_header': {
981 'type': 'object',
982 'properties': {
983 'foo': {'type': 'integer'}
984 },
985 'required': ['foo']
986 }
987 }
988
989 def test_validate_header_schema_pass(self):
990 resp_body = {}
991 resp = self.Response()
992 resp.status = 200
993 resp.foo = 12
994 self.rest_client.validate_response(self.schema, resp, resp_body)
995
996 def test_validate_header_schema_fail(self):
997 resp_body = {}
998 resp = self.Response()
999 resp.status = 200
1000 resp.foo = 1.2
1001 ex = self.assertRaises(exceptions.InvalidHTTPResponseHeader,
1002 self.rest_client.validate_response,
1003 self.schema, resp, resp_body)
1004 self.assertIn("HTTP response header is invalid", ex._error_string)
1005
1006
1007class TestRestClientJSONSchemaFormatValidation(TestJSONSchemaValidationBase):
1008
1009 schema = {
1010 'status_code': [200],
1011 'response_body': {
1012 'type': 'object',
1013 'properties': {
1014 'foo': {
1015 'type': 'string',
1016 'format': 'email'
1017 }
1018 },
1019 'required': ['foo']
1020 }
1021 }
1022
1023 def test_validate_format_pass(self):
1024 body = {'foo': 'example@example.com'}
1025 self._test_validate_pass(self.schema, body)
1026
1027 def test_validate_format_fail(self):
1028 body = {'foo': 'wrong_email'}
1029 self._test_validate_fail(self.schema, body)
1030
1031 def test_validate_formats_in_oneOf_pass(self):
1032 schema = {
1033 'status_code': [200],
1034 'response_body': {
1035 'type': 'object',
1036 'properties': {
1037 'foo': {
1038 'type': 'string',
1039 'oneOf': [
1040 {'format': 'ipv4'},
1041 {'format': 'ipv6'}
1042 ]
1043 }
1044 },
1045 'required': ['foo']
1046 }
1047 }
1048 body = {'foo': '10.0.0.0'}
1049 self._test_validate_pass(schema, body)
1050 body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
1051 self._test_validate_pass(schema, body)
1052
1053 def test_validate_formats_in_oneOf_fail_both_match(self):
1054 schema = {
1055 'status_code': [200],
1056 'response_body': {
1057 'type': 'object',
1058 'properties': {
1059 'foo': {
1060 'type': 'string',
1061 'oneOf': [
1062 {'format': 'ipv4'},
1063 {'format': 'ipv4'}
1064 ]
1065 }
1066 },
1067 'required': ['foo']
1068 }
1069 }
1070 body = {'foo': '10.0.0.0'}
1071 self._test_validate_fail(schema, body)
1072
1073 def test_validate_formats_in_oneOf_fail_no_match(self):
1074 schema = {
1075 'status_code': [200],
1076 'response_body': {
1077 'type': 'object',
1078 'properties': {
1079 'foo': {
1080 'type': 'string',
1081 'oneOf': [
1082 {'format': 'ipv4'},
1083 {'format': 'ipv6'}
1084 ]
1085 }
1086 },
1087 'required': ['foo']
1088 }
1089 }
1090 body = {'foo': 'wrong_ip_format'}
1091 self._test_validate_fail(schema, body)
1092
1093 def test_validate_formats_in_anyOf_pass(self):
1094 schema = {
1095 'status_code': [200],
1096 'response_body': {
1097 'type': 'object',
1098 'properties': {
1099 'foo': {
1100 'type': 'string',
1101 'anyOf': [
1102 {'format': 'ipv4'},
1103 {'format': 'ipv6'}
1104 ]
1105 }
1106 },
1107 'required': ['foo']
1108 }
1109 }
1110 body = {'foo': '10.0.0.0'}
1111 self._test_validate_pass(schema, body)
1112 body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
1113 self._test_validate_pass(schema, body)
1114
1115 def test_validate_formats_in_anyOf_pass_both_match(self):
1116 schema = {
1117 'status_code': [200],
1118 'response_body': {
1119 'type': 'object',
1120 'properties': {
1121 'foo': {
1122 'type': 'string',
1123 'anyOf': [
1124 {'format': 'ipv4'},
1125 {'format': 'ipv4'}
1126 ]
1127 }
1128 },
1129 'required': ['foo']
1130 }
1131 }
1132 body = {'foo': '10.0.0.0'}
1133 self._test_validate_pass(schema, body)
1134
1135 def test_validate_formats_in_anyOf_fail_no_match(self):
1136 schema = {
1137 'status_code': [200],
1138 'response_body': {
1139 'type': 'object',
1140 'properties': {
1141 'foo': {
1142 'type': 'string',
1143 'anyOf': [
1144 {'format': 'ipv4'},
1145 {'format': 'ipv6'}
1146 ]
1147 }
1148 },
1149 'required': ['foo']
1150 }
1151 }
1152 body = {'foo': 'wrong_ip_format'}
1153 self._test_validate_fail(schema, body)
1154
1155 def test_validate_formats_pass_for_unknow_format(self):
1156 schema = {
1157 'status_code': [200],
1158 'response_body': {
1159 'type': 'object',
1160 'properties': {
1161 'foo': {
1162 'type': 'string',
1163 'format': 'UNKNOWN'
1164 }
1165 },
1166 'required': ['foo']
1167 }
1168 }
1169 body = {'foo': 'example@example.com'}
1170 self._test_validate_pass(schema, body)
1171
1172
1173class TestRestClientJSONSchemaValidatorVersion(TestJSONSchemaValidationBase):
1174
1175 schema = {
1176 'status_code': [200],
1177 'response_body': {
1178 'type': 'object',
1179 'properties': {
1180 'foo': {'type': 'string'}
1181 }
1182 }
1183 }
1184
1185 def test_current_json_schema_validator_version(self):
Ngo Quoc Cuong33710b32017-05-11 14:17:17 +07001186 with fixtures.MockPatchObject(jsonschema.Draft4Validator,
1187 "check_schema") as chk_schema:
Matthew Treinish9e26ca82016-02-23 11:43:20 -05001188 body = {'foo': 'test'}
1189 self._test_validate_pass(self.schema, body)
1190 chk_schema.mock.assert_called_once_with(
1191 self.schema['response_body'])