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