blob: 572b4462ce00f1d8d144d4e1dbc7e964a264e55f [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
16import json
17
Matthew Treinish9e26ca82016-02-23 11:43:20 -050018import jsonschema
19from oslotest import mockpatch
20import six
21
Jordan Pittier00f25962016-03-18 17:10:07 +010022from tempest.lib.common import http
Matthew Treinish9e26ca82016-02-23 11:43:20 -050023from tempest.lib.common import rest_client
24from tempest.lib import exceptions
25from tempest.tests.lib import base
26from tempest.tests.lib import fake_auth_provider
27from tempest.tests.lib import fake_http
Jordan Pittier0e53b612016-03-03 14:23:17 +010028import tempest.tests.utils as utils
Matthew Treinish9e26ca82016-02-23 11:43:20 -050029
30
31class BaseRestClientTestClass(base.TestCase):
32
33 url = 'fake_endpoint'
34
35 def setUp(self):
36 super(BaseRestClientTestClass, self).setUp()
37 self.fake_auth_provider = fake_auth_provider.FakeAuthProvider()
38 self.rest_client = rest_client.RestClient(
39 self.fake_auth_provider, None, None)
Jordan Pittier0021c292016-03-29 21:33:34 +020040 self.patchobject(http.ClosingHttp, 'request', self.fake_http.request)
Matthew Treinish9e26ca82016-02-23 11:43:20 -050041 self.useFixture(mockpatch.PatchObject(self.rest_client,
42 '_log_request'))
43
44
45class TestRestClientHTTPMethods(BaseRestClientTestClass):
46 def setUp(self):
47 self.fake_http = fake_http.fake_httplib2()
48 super(TestRestClientHTTPMethods, self).setUp()
49 self.useFixture(mockpatch.PatchObject(self.rest_client,
50 '_error_checker'))
51
52 def test_post(self):
53 __, return_dict = self.rest_client.post(self.url, {}, {})
54 self.assertEqual('POST', return_dict['method'])
55
56 def test_get(self):
57 __, return_dict = self.rest_client.get(self.url)
58 self.assertEqual('GET', return_dict['method'])
59
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):
73 self.useFixture(mockpatch.PatchObject(self.rest_client,
74 'response_checker'))
75 __, 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
82
83class TestRestClientNotFoundHandling(BaseRestClientTestClass):
84 def setUp(self):
85 self.fake_http = fake_http.fake_httplib2(404)
86 super(TestRestClientNotFoundHandling, self).setUp()
87
88 def test_post(self):
89 self.assertRaises(exceptions.NotFound, self.rest_client.post,
90 self.url, {}, {})
91
92
93class TestRestClientHeadersJSON(TestRestClientHTTPMethods):
94 TYPE = "json"
95
96 def _verify_headers(self, resp):
97 self.assertEqual(self.rest_client._get_type(), self.TYPE)
98 resp = dict((k.lower(), v) for k, v in six.iteritems(resp))
99 self.assertEqual(self.header_value, resp['accept'])
100 self.assertEqual(self.header_value, resp['content-type'])
101
102 def setUp(self):
103 super(TestRestClientHeadersJSON, self).setUp()
104 self.rest_client.TYPE = self.TYPE
105 self.header_value = 'application/%s' % self.rest_client._get_type()
106
107 def test_post(self):
108 resp, __ = self.rest_client.post(self.url, {})
109 self._verify_headers(resp)
110
111 def test_get(self):
112 resp, __ = self.rest_client.get(self.url)
113 self._verify_headers(resp)
114
115 def test_delete(self):
116 resp, __ = self.rest_client.delete(self.url)
117 self._verify_headers(resp)
118
119 def test_patch(self):
120 resp, __ = self.rest_client.patch(self.url, {})
121 self._verify_headers(resp)
122
123 def test_put(self):
124 resp, __ = self.rest_client.put(self.url, {})
125 self._verify_headers(resp)
126
127 def test_head(self):
128 self.useFixture(mockpatch.PatchObject(self.rest_client,
129 'response_checker'))
130 resp, __ = self.rest_client.head(self.url)
131 self._verify_headers(resp)
132
133 def test_copy(self):
134 resp, __ = self.rest_client.copy(self.url)
135 self._verify_headers(resp)
136
137
138class TestRestClientUpdateHeaders(BaseRestClientTestClass):
139 def setUp(self):
140 self.fake_http = fake_http.fake_httplib2()
141 super(TestRestClientUpdateHeaders, self).setUp()
142 self.useFixture(mockpatch.PatchObject(self.rest_client,
143 '_error_checker'))
144 self.headers = {'X-Configuration-Session': 'session_id'}
145
146 def test_post_update_headers(self):
147 __, return_dict = self.rest_client.post(self.url, {},
148 extra_headers=True,
149 headers=self.headers)
150
151 self.assertDictContainsSubset(
152 {'X-Configuration-Session': 'session_id',
153 'Content-Type': 'application/json',
154 'Accept': 'application/json'},
155 return_dict['headers']
156 )
157
158 def test_get_update_headers(self):
159 __, return_dict = self.rest_client.get(self.url,
160 extra_headers=True,
161 headers=self.headers)
162
163 self.assertDictContainsSubset(
164 {'X-Configuration-Session': 'session_id',
165 'Content-Type': 'application/json',
166 'Accept': 'application/json'},
167 return_dict['headers']
168 )
169
170 def test_delete_update_headers(self):
171 __, return_dict = self.rest_client.delete(self.url,
172 extra_headers=True,
173 headers=self.headers)
174
175 self.assertDictContainsSubset(
176 {'X-Configuration-Session': 'session_id',
177 'Content-Type': 'application/json',
178 'Accept': 'application/json'},
179 return_dict['headers']
180 )
181
182 def test_patch_update_headers(self):
183 __, return_dict = self.rest_client.patch(self.url, {},
184 extra_headers=True,
185 headers=self.headers)
186
187 self.assertDictContainsSubset(
188 {'X-Configuration-Session': 'session_id',
189 'Content-Type': 'application/json',
190 'Accept': 'application/json'},
191 return_dict['headers']
192 )
193
194 def test_put_update_headers(self):
195 __, return_dict = self.rest_client.put(self.url, {},
196 extra_headers=True,
197 headers=self.headers)
198
199 self.assertDictContainsSubset(
200 {'X-Configuration-Session': 'session_id',
201 'Content-Type': 'application/json',
202 'Accept': 'application/json'},
203 return_dict['headers']
204 )
205
206 def test_head_update_headers(self):
207 self.useFixture(mockpatch.PatchObject(self.rest_client,
208 'response_checker'))
209
210 __, return_dict = self.rest_client.head(self.url,
211 extra_headers=True,
212 headers=self.headers)
213
214 self.assertDictContainsSubset(
215 {'X-Configuration-Session': 'session_id',
216 'Content-Type': 'application/json',
217 'Accept': 'application/json'},
218 return_dict['headers']
219 )
220
221 def test_copy_update_headers(self):
222 __, return_dict = self.rest_client.copy(self.url,
223 extra_headers=True,
224 headers=self.headers)
225
226 self.assertDictContainsSubset(
227 {'X-Configuration-Session': 'session_id',
228 'Content-Type': 'application/json',
229 'Accept': 'application/json'},
230 return_dict['headers']
231 )
232
233
234class TestRestClientParseRespJSON(BaseRestClientTestClass):
235 TYPE = "json"
236
237 keys = ["fake_key1", "fake_key2"]
238 values = ["fake_value1", "fake_value2"]
239 item_expected = dict((key, value) for (key, value) in zip(keys, values))
240 list_expected = {"body_list": [
241 {keys[0]: values[0]},
242 {keys[1]: values[1]},
243 ]}
244 dict_expected = {"body_dict": {
245 keys[0]: values[0],
246 keys[1]: values[1],
247 }}
248 null_dict = {}
249
250 def setUp(self):
251 self.fake_http = fake_http.fake_httplib2()
252 super(TestRestClientParseRespJSON, self).setUp()
253 self.rest_client.TYPE = self.TYPE
254
255 def test_parse_resp_body_item(self):
256 body = self.rest_client._parse_resp(json.dumps(self.item_expected))
257 self.assertEqual(self.item_expected, body)
258
259 def test_parse_resp_body_list(self):
260 body = self.rest_client._parse_resp(json.dumps(self.list_expected))
261 self.assertEqual(self.list_expected["body_list"], body)
262
263 def test_parse_resp_body_dict(self):
264 body = self.rest_client._parse_resp(json.dumps(self.dict_expected))
265 self.assertEqual(self.dict_expected["body_dict"], body)
266
267 def test_parse_resp_two_top_keys(self):
268 dict_two_keys = self.dict_expected.copy()
269 dict_two_keys.update({"second_key": ""})
270 body = self.rest_client._parse_resp(json.dumps(dict_two_keys))
271 self.assertEqual(dict_two_keys, body)
272
273 def test_parse_resp_one_top_key_without_list_or_dict(self):
274 data = {"one_top_key": "not_list_or_dict_value"}
275 body = self.rest_client._parse_resp(json.dumps(data))
276 self.assertEqual(data, body)
277
278 def test_parse_nullable_dict(self):
279 body = self.rest_client._parse_resp(json.dumps(self.null_dict))
280 self.assertEqual(self.null_dict, body)
281
282
283class TestRestClientErrorCheckerJSON(base.TestCase):
284 c_type = "application/json"
285
286 def set_data(self, r_code, enc=None, r_body=None, absolute_limit=True):
287 if enc is None:
288 enc = self.c_type
289 resp_dict = {'status': r_code, 'content-type': enc}
290 resp_body = {'resp_body': 'fake_resp_body'}
291
292 if absolute_limit is False:
293 resp_dict.update({'retry-after': 120})
294 resp_body.update({'overLimit': {'message': 'fake_message'}})
Jordan Pittier00f25962016-03-18 17:10:07 +0100295 resp = fake_http.fake_http_response(headers=resp_dict,
296 status=int(r_code),
297 body=json.dumps(resp_body))
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500298 data = {
299 "method": "fake_method",
300 "url": "fake_url",
301 "headers": "fake_headers",
302 "body": "fake_body",
303 "resp": resp,
304 "resp_body": json.dumps(resp_body)
305 }
306 if r_body is not None:
307 data.update({"resp_body": r_body})
308 return data
309
310 def setUp(self):
311 super(TestRestClientErrorCheckerJSON, self).setUp()
312 self.rest_client = rest_client.RestClient(
313 fake_auth_provider.FakeAuthProvider(), None, None)
314
315 def test_response_less_than_400(self):
316 self.rest_client._error_checker(**self.set_data("399"))
317
318 def _test_error_checker(self, exception_type, data):
319 e = self.assertRaises(exception_type,
320 self.rest_client._error_checker,
321 **data)
322 self.assertEqual(e.resp, data['resp'])
323 self.assertTrue(hasattr(e, 'resp_body'))
324 return e
325
326 def test_response_400(self):
327 self._test_error_checker(exceptions.BadRequest, self.set_data("400"))
328
329 def test_response_401(self):
330 self._test_error_checker(exceptions.Unauthorized, self.set_data("401"))
331
332 def test_response_403(self):
333 self._test_error_checker(exceptions.Forbidden, self.set_data("403"))
334
335 def test_response_404(self):
336 self._test_error_checker(exceptions.NotFound, self.set_data("404"))
337
338 def test_response_409(self):
339 self._test_error_checker(exceptions.Conflict, self.set_data("409"))
340
341 def test_response_410(self):
342 self._test_error_checker(exceptions.Gone, self.set_data("410"))
343
344 def test_response_413(self):
345 self._test_error_checker(exceptions.OverLimit, self.set_data("413"))
346
347 def test_response_413_without_absolute_limit(self):
348 self._test_error_checker(exceptions.RateLimitExceeded,
349 self.set_data("413", absolute_limit=False))
350
351 def test_response_415(self):
352 self._test_error_checker(exceptions.InvalidContentType,
353 self.set_data("415"))
354
355 def test_response_422(self):
356 self._test_error_checker(exceptions.UnprocessableEntity,
357 self.set_data("422"))
358
359 def test_response_500_with_text(self):
360 # _parse_resp is expected to return 'str'
361 self._test_error_checker(exceptions.ServerFault, self.set_data("500"))
362
363 def test_response_501_with_text(self):
364 self._test_error_checker(exceptions.NotImplemented,
365 self.set_data("501"))
366
367 def test_response_400_with_dict(self):
368 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
369 e = self._test_error_checker(exceptions.BadRequest,
370 self.set_data("400", r_body=r_body))
371
372 if self.c_type == 'application/json':
373 expected = {"err": "fake_resp_body"}
374 else:
375 expected = r_body
376 self.assertEqual(expected, e.resp_body)
377
378 def test_response_401_with_dict(self):
379 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
380 e = self._test_error_checker(exceptions.Unauthorized,
381 self.set_data("401", r_body=r_body))
382
383 if self.c_type == 'application/json':
384 expected = {"err": "fake_resp_body"}
385 else:
386 expected = r_body
387 self.assertEqual(expected, e.resp_body)
388
389 def test_response_403_with_dict(self):
390 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
391 e = self._test_error_checker(exceptions.Forbidden,
392 self.set_data("403", r_body=r_body))
393
394 if self.c_type == 'application/json':
395 expected = {"err": "fake_resp_body"}
396 else:
397 expected = r_body
398 self.assertEqual(expected, e.resp_body)
399
400 def test_response_404_with_dict(self):
401 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
402 e = self._test_error_checker(exceptions.NotFound,
403 self.set_data("404", 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_404_with_invalid_dict(self):
412 r_body = '{"foo": "bar"]'
413 e = self._test_error_checker(exceptions.NotFound,
414 self.set_data("404", r_body=r_body))
415
416 expected = r_body
417 self.assertEqual(expected, e.resp_body)
418
419 def test_response_410_with_dict(self):
420 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
421 e = self._test_error_checker(exceptions.Gone,
422 self.set_data("410", r_body=r_body))
423
424 if self.c_type == 'application/json':
425 expected = {"err": "fake_resp_body"}
426 else:
427 expected = r_body
428 self.assertEqual(expected, e.resp_body)
429
430 def test_response_410_with_invalid_dict(self):
431 r_body = '{"foo": "bar"]'
432 e = self._test_error_checker(exceptions.Gone,
433 self.set_data("410", r_body=r_body))
434
435 expected = r_body
436 self.assertEqual(expected, e.resp_body)
437
438 def test_response_409_with_dict(self):
439 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
440 e = self._test_error_checker(exceptions.Conflict,
441 self.set_data("409", r_body=r_body))
442
443 if self.c_type == 'application/json':
444 expected = {"err": "fake_resp_body"}
445 else:
446 expected = r_body
447 self.assertEqual(expected, e.resp_body)
448
449 def test_response_500_with_dict(self):
450 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
451 e = self._test_error_checker(exceptions.ServerFault,
452 self.set_data("500", r_body=r_body))
453
454 if self.c_type == 'application/json':
455 expected = {"err": "fake_resp_body"}
456 else:
457 expected = r_body
458 self.assertEqual(expected, e.resp_body)
459
460 def test_response_501_with_dict(self):
461 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
462 self._test_error_checker(exceptions.NotImplemented,
463 self.set_data("501", r_body=r_body))
464
465 def test_response_bigger_than_400(self):
466 # Any response code, that bigger than 400, and not in
467 # (401, 403, 404, 409, 413, 422, 500, 501)
468 self._test_error_checker(exceptions.UnexpectedResponseCode,
469 self.set_data("402"))
470
471
472class TestRestClientErrorCheckerTEXT(TestRestClientErrorCheckerJSON):
473 c_type = "text/plain"
474
475 def test_fake_content_type(self):
476 # This test is required only in one exemplar
477 # Any response code, that bigger than 400, and not in
478 # (401, 403, 404, 409, 413, 422, 500, 501)
479 self._test_error_checker(exceptions.UnexpectedContentType,
480 self.set_data("405", enc="fake_enc"))
481
482 def test_response_413_without_absolute_limit(self):
483 # Skip this test because rest_client cannot get overLimit message
484 # from text body.
485 pass
486
487
488class TestRestClientUtils(BaseRestClientTestClass):
489
490 def _is_resource_deleted(self, resource_id):
491 if not isinstance(self.retry_pass, int):
492 return False
493 if self.retry_count >= self.retry_pass:
494 return True
495 self.retry_count = self.retry_count + 1
496 return False
497
498 def setUp(self):
499 self.fake_http = fake_http.fake_httplib2()
500 super(TestRestClientUtils, self).setUp()
501 self.retry_count = 0
502 self.retry_pass = None
503 self.original_deleted_method = self.rest_client.is_resource_deleted
504 self.rest_client.is_resource_deleted = self._is_resource_deleted
505
506 def test_wait_for_resource_deletion(self):
507 self.retry_pass = 2
508 # Ensure timeout long enough for loop execution to hit retry count
509 self.rest_client.build_timeout = 500
510 sleep_mock = self.patch('time.sleep')
511 self.rest_client.wait_for_resource_deletion('1234')
512 self.assertEqual(len(sleep_mock.mock_calls), 2)
513
514 def test_wait_for_resource_deletion_not_deleted(self):
515 self.patch('time.sleep')
516 # Set timeout to be very quick to force exception faster
Jordan Pittier0e53b612016-03-03 14:23:17 +0100517 timeout = 1
518 self.rest_client.build_timeout = timeout
519
520 time_mock = self.patch('time.time')
521 time_mock.side_effect = utils.generate_timeout_series(timeout)
522
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500523 self.assertRaises(exceptions.TimeoutException,
524 self.rest_client.wait_for_resource_deletion,
525 '1234')
526
Jordan Pittier0e53b612016-03-03 14:23:17 +0100527 # time.time() should be called twice, first to start the timer
528 # and then to compute the timedelta
529 self.assertEqual(2, time_mock.call_count)
530
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500531 def test_wait_for_deletion_with_unimplemented_deleted_method(self):
532 self.rest_client.is_resource_deleted = self.original_deleted_method
533 self.assertRaises(NotImplementedError,
534 self.rest_client.wait_for_resource_deletion,
535 '1234')
536
537 def test_get_versions(self):
538 self.rest_client._parse_resp = lambda x: [{'id': 'v1'}, {'id': 'v2'}]
539 actual_resp, actual_versions = self.rest_client.get_versions()
540 self.assertEqual(['v1', 'v2'], list(actual_versions))
541
542 def test__str__(self):
543 def get_token():
544 return "deadbeef"
545
546 self.fake_auth_provider.get_token = get_token
547 self.assertIsNotNone(str(self.rest_client))
548
549
550class TestProperties(BaseRestClientTestClass):
551
552 def setUp(self):
553 self.fake_http = fake_http.fake_httplib2()
554 super(TestProperties, self).setUp()
555 creds_dict = {
556 'username': 'test-user',
557 'user_id': 'test-user_id',
558 'tenant_name': 'test-tenant_name',
559 'tenant_id': 'test-tenant_id',
560 'password': 'test-password'
561 }
562 self.rest_client = rest_client.RestClient(
563 fake_auth_provider.FakeAuthProvider(creds_dict=creds_dict),
564 None, None)
565
566 def test_properties(self):
567 self.assertEqual('test-user', self.rest_client.user)
568 self.assertEqual('test-user_id', self.rest_client.user_id)
569 self.assertEqual('test-tenant_name', self.rest_client.tenant_name)
570 self.assertEqual('test-tenant_id', self.rest_client.tenant_id)
571 self.assertEqual('test-password', self.rest_client.password)
572
573 self.rest_client.api_version = 'v1'
574 expected = {'api_version': 'v1',
575 'endpoint_type': 'publicURL',
576 'region': None,
577 'service': None,
578 'skip_path': True}
579 self.rest_client.skip_path()
580 self.assertEqual(expected, self.rest_client.filters)
581
582 self.rest_client.reset_path()
583 self.rest_client.api_version = 'v1'
584 expected = {'api_version': 'v1',
585 'endpoint_type': 'publicURL',
586 'region': None,
587 'service': None}
588 self.assertEqual(expected, self.rest_client.filters)
589
590
591class TestExpectedSuccess(BaseRestClientTestClass):
592
593 def setUp(self):
594 self.fake_http = fake_http.fake_httplib2()
595 super(TestExpectedSuccess, self).setUp()
596
597 def test_expected_succes_int_match(self):
598 expected_code = 202
599 read_code = 202
600 resp = self.rest_client.expected_success(expected_code, read_code)
601 # Assert None resp on success
602 self.assertFalse(resp)
603
604 def test_expected_succes_int_no_match(self):
605 expected_code = 204
606 read_code = 202
607 self.assertRaises(exceptions.InvalidHttpSuccessCode,
608 self.rest_client.expected_success,
609 expected_code, read_code)
610
611 def test_expected_succes_list_match(self):
612 expected_code = [202, 204]
613 read_code = 202
614 resp = self.rest_client.expected_success(expected_code, read_code)
615 # Assert None resp on success
616 self.assertFalse(resp)
617
618 def test_expected_succes_list_no_match(self):
619 expected_code = [202, 204]
620 read_code = 200
621 self.assertRaises(exceptions.InvalidHttpSuccessCode,
622 self.rest_client.expected_success,
623 expected_code, read_code)
624
625 def test_non_success_expected_int(self):
626 expected_code = 404
627 read_code = 202
628 self.assertRaises(AssertionError, self.rest_client.expected_success,
629 expected_code, read_code)
630
631 def test_non_success_expected_list(self):
632 expected_code = [404, 202]
633 read_code = 202
634 self.assertRaises(AssertionError, self.rest_client.expected_success,
635 expected_code, read_code)
636
637
638class TestResponseBody(base.TestCase):
639
640 def test_str(self):
641 response = {'status': 200}
642 body = {'key1': 'value1'}
643 actual = rest_client.ResponseBody(response, body)
644 self.assertEqual("response: %s\nBody: %s" % (response, body),
645 str(actual))
646
647
648class TestResponseBodyData(base.TestCase):
649
650 def test_str(self):
651 response = {'status': 200}
652 data = 'data1'
653 actual = rest_client.ResponseBodyData(response, data)
654 self.assertEqual("response: %s\nBody: %s" % (response, data),
655 str(actual))
656
657
658class TestResponseBodyList(base.TestCase):
659
660 def test_str(self):
661 response = {'status': 200}
662 body = ['value1', 'value2', 'value3']
663 actual = rest_client.ResponseBodyList(response, body)
664 self.assertEqual("response: %s\nBody: %s" % (response, body),
665 str(actual))
666
667
668class TestJSONSchemaValidationBase(base.TestCase):
669
670 class Response(dict):
671
672 def __getattr__(self, attr):
673 return self[attr]
674
675 def __setattr__(self, attr, value):
676 self[attr] = value
677
678 def setUp(self):
679 super(TestJSONSchemaValidationBase, self).setUp()
680 self.fake_auth_provider = fake_auth_provider.FakeAuthProvider()
681 self.rest_client = rest_client.RestClient(
682 self.fake_auth_provider, None, None)
683
684 def _test_validate_pass(self, schema, resp_body, status=200):
685 resp = self.Response()
686 resp.status = status
687 self.rest_client.validate_response(schema, resp, resp_body)
688
689 def _test_validate_fail(self, schema, resp_body, status=200,
690 error_msg="HTTP response body is invalid"):
691 resp = self.Response()
692 resp.status = status
693 ex = self.assertRaises(exceptions.InvalidHTTPResponseBody,
694 self.rest_client.validate_response,
695 schema, resp, resp_body)
696 self.assertIn(error_msg, ex._error_string)
697
698
699class TestRestClientJSONSchemaValidation(TestJSONSchemaValidationBase):
700
701 schema = {
702 'status_code': [200],
703 'response_body': {
704 'type': 'object',
705 'properties': {
706 'foo': {
707 'type': 'integer',
708 },
709 },
710 'required': ['foo']
711 }
712 }
713
714 def test_validate_pass_with_http_success_code(self):
715 body = {'foo': 12}
716 self._test_validate_pass(self.schema, body, status=200)
717
718 def test_validate_pass_with_http_redirect_code(self):
719 body = {'foo': 12}
720 schema = copy.deepcopy(self.schema)
721 schema['status_code'] = 300
722 self._test_validate_pass(schema, body, status=300)
723
724 def test_validate_not_http_success_code(self):
725 schema = {
726 'status_code': [200]
727 }
728 body = {}
729 self._test_validate_pass(schema, body, status=400)
730
731 def test_validate_multiple_allowed_type(self):
732 schema = {
733 'status_code': [200],
734 'response_body': {
735 'type': 'object',
736 'properties': {
737 'foo': {
738 'type': ['integer', 'string'],
739 },
740 },
741 'required': ['foo']
742 }
743 }
744 body = {'foo': 12}
745 self._test_validate_pass(schema, body)
746 body = {'foo': '12'}
747 self._test_validate_pass(schema, body)
748
749 def test_validate_enable_additional_property_pass(self):
750 schema = {
751 'status_code': [200],
752 'response_body': {
753 'type': 'object',
754 'properties': {
755 'foo': {'type': 'integer'}
756 },
757 'additionalProperties': True,
758 'required': ['foo']
759 }
760 }
761 body = {'foo': 12, 'foo2': 'foo2value'}
762 self._test_validate_pass(schema, body)
763
764 def test_validate_disable_additional_property_pass(self):
765 schema = {
766 'status_code': [200],
767 'response_body': {
768 'type': 'object',
769 'properties': {
770 'foo': {'type': 'integer'}
771 },
772 'additionalProperties': False,
773 'required': ['foo']
774 }
775 }
776 body = {'foo': 12}
777 self._test_validate_pass(schema, body)
778
779 def test_validate_disable_additional_property_fail(self):
780 schema = {
781 'status_code': [200],
782 'response_body': {
783 'type': 'object',
784 'properties': {
785 'foo': {'type': 'integer'}
786 },
787 'additionalProperties': False,
788 'required': ['foo']
789 }
790 }
791 body = {'foo': 12, 'foo2': 'foo2value'}
792 self._test_validate_fail(schema, body)
793
794 def test_validate_wrong_status_code(self):
795 schema = {
796 'status_code': [202]
797 }
798 body = {}
799 resp = self.Response()
800 resp.status = 200
801 ex = self.assertRaises(exceptions.InvalidHttpSuccessCode,
802 self.rest_client.validate_response,
803 schema, resp, body)
804 self.assertIn("Unexpected http success status code", ex._error_string)
805
806 def test_validate_wrong_attribute_type(self):
807 body = {'foo': 1.2}
808 self._test_validate_fail(self.schema, body)
809
810 def test_validate_unexpected_response_body(self):
811 schema = {
812 'status_code': [200],
813 }
814 body = {'foo': 12}
815 self._test_validate_fail(
816 schema, body,
817 error_msg="HTTP response body should not exist")
818
819 def test_validate_missing_response_body(self):
820 body = {}
821 self._test_validate_fail(self.schema, body)
822
823 def test_validate_missing_required_attribute(self):
824 body = {'notfoo': 12}
825 self._test_validate_fail(self.schema, body)
826
827 def test_validate_response_body_not_list(self):
828 schema = {
829 'status_code': [200],
830 'response_body': {
831 'type': 'object',
832 'properties': {
833 'list_items': {
834 'type': 'array',
835 'items': {'foo': {'type': 'integer'}}
836 }
837 },
838 'required': ['list_items'],
839 }
840 }
841 body = {'foo': 12}
842 self._test_validate_fail(schema, body)
843
844 def test_validate_response_body_list_pass(self):
845 schema = {
846 'status_code': [200],
847 'response_body': {
848 'type': 'object',
849 'properties': {
850 'list_items': {
851 'type': 'array',
852 'items': {'foo': {'type': 'integer'}}
853 }
854 },
855 'required': ['list_items'],
856 }
857 }
858 body = {'list_items': [{'foo': 12}, {'foo': 10}]}
859 self._test_validate_pass(schema, body)
860
861
862class TestRestClientJSONHeaderSchemaValidation(TestJSONSchemaValidationBase):
863
864 schema = {
865 'status_code': [200],
866 'response_header': {
867 'type': 'object',
868 'properties': {
869 'foo': {'type': 'integer'}
870 },
871 'required': ['foo']
872 }
873 }
874
875 def test_validate_header_schema_pass(self):
876 resp_body = {}
877 resp = self.Response()
878 resp.status = 200
879 resp.foo = 12
880 self.rest_client.validate_response(self.schema, resp, resp_body)
881
882 def test_validate_header_schema_fail(self):
883 resp_body = {}
884 resp = self.Response()
885 resp.status = 200
886 resp.foo = 1.2
887 ex = self.assertRaises(exceptions.InvalidHTTPResponseHeader,
888 self.rest_client.validate_response,
889 self.schema, resp, resp_body)
890 self.assertIn("HTTP response header is invalid", ex._error_string)
891
892
893class TestRestClientJSONSchemaFormatValidation(TestJSONSchemaValidationBase):
894
895 schema = {
896 'status_code': [200],
897 'response_body': {
898 'type': 'object',
899 'properties': {
900 'foo': {
901 'type': 'string',
902 'format': 'email'
903 }
904 },
905 'required': ['foo']
906 }
907 }
908
909 def test_validate_format_pass(self):
910 body = {'foo': 'example@example.com'}
911 self._test_validate_pass(self.schema, body)
912
913 def test_validate_format_fail(self):
914 body = {'foo': 'wrong_email'}
915 self._test_validate_fail(self.schema, body)
916
917 def test_validate_formats_in_oneOf_pass(self):
918 schema = {
919 'status_code': [200],
920 'response_body': {
921 'type': 'object',
922 'properties': {
923 'foo': {
924 'type': 'string',
925 'oneOf': [
926 {'format': 'ipv4'},
927 {'format': 'ipv6'}
928 ]
929 }
930 },
931 'required': ['foo']
932 }
933 }
934 body = {'foo': '10.0.0.0'}
935 self._test_validate_pass(schema, body)
936 body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
937 self._test_validate_pass(schema, body)
938
939 def test_validate_formats_in_oneOf_fail_both_match(self):
940 schema = {
941 'status_code': [200],
942 'response_body': {
943 'type': 'object',
944 'properties': {
945 'foo': {
946 'type': 'string',
947 'oneOf': [
948 {'format': 'ipv4'},
949 {'format': 'ipv4'}
950 ]
951 }
952 },
953 'required': ['foo']
954 }
955 }
956 body = {'foo': '10.0.0.0'}
957 self._test_validate_fail(schema, body)
958
959 def test_validate_formats_in_oneOf_fail_no_match(self):
960 schema = {
961 'status_code': [200],
962 'response_body': {
963 'type': 'object',
964 'properties': {
965 'foo': {
966 'type': 'string',
967 'oneOf': [
968 {'format': 'ipv4'},
969 {'format': 'ipv6'}
970 ]
971 }
972 },
973 'required': ['foo']
974 }
975 }
976 body = {'foo': 'wrong_ip_format'}
977 self._test_validate_fail(schema, body)
978
979 def test_validate_formats_in_anyOf_pass(self):
980 schema = {
981 'status_code': [200],
982 'response_body': {
983 'type': 'object',
984 'properties': {
985 'foo': {
986 'type': 'string',
987 'anyOf': [
988 {'format': 'ipv4'},
989 {'format': 'ipv6'}
990 ]
991 }
992 },
993 'required': ['foo']
994 }
995 }
996 body = {'foo': '10.0.0.0'}
997 self._test_validate_pass(schema, body)
998 body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
999 self._test_validate_pass(schema, body)
1000
1001 def test_validate_formats_in_anyOf_pass_both_match(self):
1002 schema = {
1003 'status_code': [200],
1004 'response_body': {
1005 'type': 'object',
1006 'properties': {
1007 'foo': {
1008 'type': 'string',
1009 'anyOf': [
1010 {'format': 'ipv4'},
1011 {'format': 'ipv4'}
1012 ]
1013 }
1014 },
1015 'required': ['foo']
1016 }
1017 }
1018 body = {'foo': '10.0.0.0'}
1019 self._test_validate_pass(schema, body)
1020
1021 def test_validate_formats_in_anyOf_fail_no_match(self):
1022 schema = {
1023 'status_code': [200],
1024 'response_body': {
1025 'type': 'object',
1026 'properties': {
1027 'foo': {
1028 'type': 'string',
1029 'anyOf': [
1030 {'format': 'ipv4'},
1031 {'format': 'ipv6'}
1032 ]
1033 }
1034 },
1035 'required': ['foo']
1036 }
1037 }
1038 body = {'foo': 'wrong_ip_format'}
1039 self._test_validate_fail(schema, body)
1040
1041 def test_validate_formats_pass_for_unknow_format(self):
1042 schema = {
1043 'status_code': [200],
1044 'response_body': {
1045 'type': 'object',
1046 'properties': {
1047 'foo': {
1048 'type': 'string',
1049 'format': 'UNKNOWN'
1050 }
1051 },
1052 'required': ['foo']
1053 }
1054 }
1055 body = {'foo': 'example@example.com'}
1056 self._test_validate_pass(schema, body)
1057
1058
1059class TestRestClientJSONSchemaValidatorVersion(TestJSONSchemaValidationBase):
1060
1061 schema = {
1062 'status_code': [200],
1063 'response_body': {
1064 'type': 'object',
1065 'properties': {
1066 'foo': {'type': 'string'}
1067 }
1068 }
1069 }
1070
1071 def test_current_json_schema_validator_version(self):
1072 with mockpatch.PatchObject(jsonschema.Draft4Validator,
1073 "check_schema") as chk_schema:
1074 body = {'foo': 'test'}
1075 self._test_validate_pass(self.schema, body)
1076 chk_schema.mock.assert_called_once_with(
1077 self.schema['response_body'])