blob: e6cf04728f1b30d977c84b0d9fdecb1707913179 [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
Matthew Treinishffad78a2016-04-16 14:39:52 -040025from tempest.tests import base
Matthew Treinish9e26ca82016-02-23 11:43:20 -050026from 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 = {
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500299 "resp": resp,
300 "resp_body": json.dumps(resp_body)
301 }
302 if r_body is not None:
303 data.update({"resp_body": r_body})
304 return data
305
306 def setUp(self):
307 super(TestRestClientErrorCheckerJSON, self).setUp()
308 self.rest_client = rest_client.RestClient(
309 fake_auth_provider.FakeAuthProvider(), None, None)
310
311 def test_response_less_than_400(self):
312 self.rest_client._error_checker(**self.set_data("399"))
313
314 def _test_error_checker(self, exception_type, data):
315 e = self.assertRaises(exception_type,
316 self.rest_client._error_checker,
317 **data)
318 self.assertEqual(e.resp, data['resp'])
319 self.assertTrue(hasattr(e, 'resp_body'))
320 return e
321
322 def test_response_400(self):
323 self._test_error_checker(exceptions.BadRequest, self.set_data("400"))
324
325 def test_response_401(self):
326 self._test_error_checker(exceptions.Unauthorized, self.set_data("401"))
327
328 def test_response_403(self):
329 self._test_error_checker(exceptions.Forbidden, self.set_data("403"))
330
331 def test_response_404(self):
332 self._test_error_checker(exceptions.NotFound, self.set_data("404"))
333
334 def test_response_409(self):
335 self._test_error_checker(exceptions.Conflict, self.set_data("409"))
336
337 def test_response_410(self):
338 self._test_error_checker(exceptions.Gone, self.set_data("410"))
339
340 def test_response_413(self):
341 self._test_error_checker(exceptions.OverLimit, self.set_data("413"))
342
343 def test_response_413_without_absolute_limit(self):
344 self._test_error_checker(exceptions.RateLimitExceeded,
345 self.set_data("413", absolute_limit=False))
346
347 def test_response_415(self):
348 self._test_error_checker(exceptions.InvalidContentType,
349 self.set_data("415"))
350
351 def test_response_422(self):
352 self._test_error_checker(exceptions.UnprocessableEntity,
353 self.set_data("422"))
354
355 def test_response_500_with_text(self):
356 # _parse_resp is expected to return 'str'
357 self._test_error_checker(exceptions.ServerFault, self.set_data("500"))
358
359 def test_response_501_with_text(self):
360 self._test_error_checker(exceptions.NotImplemented,
361 self.set_data("501"))
362
363 def test_response_400_with_dict(self):
364 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
365 e = self._test_error_checker(exceptions.BadRequest,
366 self.set_data("400", r_body=r_body))
367
368 if self.c_type == 'application/json':
369 expected = {"err": "fake_resp_body"}
370 else:
371 expected = r_body
372 self.assertEqual(expected, e.resp_body)
373
374 def test_response_401_with_dict(self):
375 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
376 e = self._test_error_checker(exceptions.Unauthorized,
377 self.set_data("401", r_body=r_body))
378
379 if self.c_type == 'application/json':
380 expected = {"err": "fake_resp_body"}
381 else:
382 expected = r_body
383 self.assertEqual(expected, e.resp_body)
384
385 def test_response_403_with_dict(self):
386 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
387 e = self._test_error_checker(exceptions.Forbidden,
388 self.set_data("403", r_body=r_body))
389
390 if self.c_type == 'application/json':
391 expected = {"err": "fake_resp_body"}
392 else:
393 expected = r_body
394 self.assertEqual(expected, e.resp_body)
395
396 def test_response_404_with_dict(self):
397 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
398 e = self._test_error_checker(exceptions.NotFound,
399 self.set_data("404", r_body=r_body))
400
401 if self.c_type == 'application/json':
402 expected = {"err": "fake_resp_body"}
403 else:
404 expected = r_body
405 self.assertEqual(expected, e.resp_body)
406
407 def test_response_404_with_invalid_dict(self):
408 r_body = '{"foo": "bar"]'
409 e = self._test_error_checker(exceptions.NotFound,
410 self.set_data("404", r_body=r_body))
411
412 expected = r_body
413 self.assertEqual(expected, e.resp_body)
414
415 def test_response_410_with_dict(self):
416 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
417 e = self._test_error_checker(exceptions.Gone,
418 self.set_data("410", r_body=r_body))
419
420 if self.c_type == 'application/json':
421 expected = {"err": "fake_resp_body"}
422 else:
423 expected = r_body
424 self.assertEqual(expected, e.resp_body)
425
426 def test_response_410_with_invalid_dict(self):
427 r_body = '{"foo": "bar"]'
428 e = self._test_error_checker(exceptions.Gone,
429 self.set_data("410", r_body=r_body))
430
431 expected = r_body
432 self.assertEqual(expected, e.resp_body)
433
434 def test_response_409_with_dict(self):
435 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
436 e = self._test_error_checker(exceptions.Conflict,
437 self.set_data("409", r_body=r_body))
438
439 if self.c_type == 'application/json':
440 expected = {"err": "fake_resp_body"}
441 else:
442 expected = r_body
443 self.assertEqual(expected, e.resp_body)
444
445 def test_response_500_with_dict(self):
446 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
447 e = self._test_error_checker(exceptions.ServerFault,
448 self.set_data("500", r_body=r_body))
449
450 if self.c_type == 'application/json':
451 expected = {"err": "fake_resp_body"}
452 else:
453 expected = r_body
454 self.assertEqual(expected, e.resp_body)
455
456 def test_response_501_with_dict(self):
457 r_body = '{"resp_body": {"err": "fake_resp_body"}}'
458 self._test_error_checker(exceptions.NotImplemented,
459 self.set_data("501", r_body=r_body))
460
461 def test_response_bigger_than_400(self):
462 # Any response code, that bigger than 400, and not in
463 # (401, 403, 404, 409, 413, 422, 500, 501)
464 self._test_error_checker(exceptions.UnexpectedResponseCode,
465 self.set_data("402"))
466
467
468class TestRestClientErrorCheckerTEXT(TestRestClientErrorCheckerJSON):
469 c_type = "text/plain"
470
471 def test_fake_content_type(self):
472 # This test is required only in one exemplar
473 # Any response code, that bigger than 400, and not in
474 # (401, 403, 404, 409, 413, 422, 500, 501)
475 self._test_error_checker(exceptions.UnexpectedContentType,
476 self.set_data("405", enc="fake_enc"))
477
478 def test_response_413_without_absolute_limit(self):
479 # Skip this test because rest_client cannot get overLimit message
480 # from text body.
481 pass
482
483
484class TestRestClientUtils(BaseRestClientTestClass):
485
486 def _is_resource_deleted(self, resource_id):
487 if not isinstance(self.retry_pass, int):
488 return False
489 if self.retry_count >= self.retry_pass:
490 return True
491 self.retry_count = self.retry_count + 1
492 return False
493
494 def setUp(self):
495 self.fake_http = fake_http.fake_httplib2()
496 super(TestRestClientUtils, self).setUp()
497 self.retry_count = 0
498 self.retry_pass = None
499 self.original_deleted_method = self.rest_client.is_resource_deleted
500 self.rest_client.is_resource_deleted = self._is_resource_deleted
501
502 def test_wait_for_resource_deletion(self):
503 self.retry_pass = 2
504 # Ensure timeout long enough for loop execution to hit retry count
505 self.rest_client.build_timeout = 500
506 sleep_mock = self.patch('time.sleep')
507 self.rest_client.wait_for_resource_deletion('1234')
508 self.assertEqual(len(sleep_mock.mock_calls), 2)
509
510 def test_wait_for_resource_deletion_not_deleted(self):
511 self.patch('time.sleep')
512 # Set timeout to be very quick to force exception faster
Jordan Pittier0e53b612016-03-03 14:23:17 +0100513 timeout = 1
514 self.rest_client.build_timeout = timeout
515
516 time_mock = self.patch('time.time')
517 time_mock.side_effect = utils.generate_timeout_series(timeout)
518
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500519 self.assertRaises(exceptions.TimeoutException,
520 self.rest_client.wait_for_resource_deletion,
521 '1234')
522
Jordan Pittier0e53b612016-03-03 14:23:17 +0100523 # time.time() should be called twice, first to start the timer
524 # and then to compute the timedelta
525 self.assertEqual(2, time_mock.call_count)
526
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500527 def test_wait_for_deletion_with_unimplemented_deleted_method(self):
528 self.rest_client.is_resource_deleted = self.original_deleted_method
529 self.assertRaises(NotImplementedError,
530 self.rest_client.wait_for_resource_deletion,
531 '1234')
532
533 def test_get_versions(self):
534 self.rest_client._parse_resp = lambda x: [{'id': 'v1'}, {'id': 'v2'}]
535 actual_resp, actual_versions = self.rest_client.get_versions()
536 self.assertEqual(['v1', 'v2'], list(actual_versions))
537
538 def test__str__(self):
539 def get_token():
540 return "deadbeef"
541
542 self.fake_auth_provider.get_token = get_token
543 self.assertIsNotNone(str(self.rest_client))
544
545
Paul Glass119565a2016-04-06 11:41:42 -0500546class TestRateLimiting(BaseRestClientTestClass):
547
548 def setUp(self):
549 self.fake_http = fake_http.fake_httplib2()
550 super(TestRateLimiting, self).setUp()
551
552 def test__get_retry_after_delay_with_integer(self):
553 resp = {'retry-after': '123'}
554 self.assertEqual(123, self.rest_client._get_retry_after_delay(resp))
555
556 def test__get_retry_after_delay_with_http_date(self):
557 resp = {
558 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
559 'retry-after': 'Mon, 4 Apr 2016 21:58:26 GMT',
560 }
561 self.assertEqual(123, self.rest_client._get_retry_after_delay(resp))
562
563 def test__get_retry_after_delay_of_zero_with_integer(self):
564 resp = {'retry-after': '0'}
565 self.assertEqual(1, self.rest_client._get_retry_after_delay(resp))
566
567 def test__get_retry_after_delay_of_zero_with_http_date(self):
568 resp = {
569 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
570 'retry-after': 'Mon, 4 Apr 2016 21:56:23 GMT',
571 }
572 self.assertEqual(1, self.rest_client._get_retry_after_delay(resp))
573
574 def test__get_retry_after_delay_with_missing_date_header(self):
575 resp = {
576 'retry-after': 'Mon, 4 Apr 2016 21:58:26 GMT',
577 }
578 self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
579 resp)
580
581 def test__get_retry_after_delay_with_invalid_http_date(self):
582 resp = {
583 'retry-after': 'Mon, 4 AAA 2016 21:58:26 GMT',
584 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
585 }
586 self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
587 resp)
588
589 def test__get_retry_after_delay_with_missing_retry_after_header(self):
590 self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
591 {})
592
593 def test_is_absolute_limit_gives_false_with_retry_after(self):
594 resp = {'retry-after': 123}
595
596 # is_absolute_limit() requires the overLimit body to be unwrapped
597 resp_body = self.rest_client._parse_resp("""{
598 "overLimit": {
599 "message": ""
600 }
601 }""")
602 self.assertFalse(self.rest_client.is_absolute_limit(resp, resp_body))
603
604
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500605class TestProperties(BaseRestClientTestClass):
606
607 def setUp(self):
608 self.fake_http = fake_http.fake_httplib2()
609 super(TestProperties, self).setUp()
610 creds_dict = {
611 'username': 'test-user',
612 'user_id': 'test-user_id',
613 'tenant_name': 'test-tenant_name',
614 'tenant_id': 'test-tenant_id',
615 'password': 'test-password'
616 }
617 self.rest_client = rest_client.RestClient(
618 fake_auth_provider.FakeAuthProvider(creds_dict=creds_dict),
619 None, None)
620
621 def test_properties(self):
622 self.assertEqual('test-user', self.rest_client.user)
623 self.assertEqual('test-user_id', self.rest_client.user_id)
624 self.assertEqual('test-tenant_name', self.rest_client.tenant_name)
625 self.assertEqual('test-tenant_id', self.rest_client.tenant_id)
626 self.assertEqual('test-password', self.rest_client.password)
627
628 self.rest_client.api_version = 'v1'
629 expected = {'api_version': 'v1',
630 'endpoint_type': 'publicURL',
631 'region': None,
Eric Wehrmeister54c7bd42016-02-24 11:11:07 -0600632 'name': None,
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500633 'service': None,
634 'skip_path': True}
635 self.rest_client.skip_path()
636 self.assertEqual(expected, self.rest_client.filters)
637
638 self.rest_client.reset_path()
639 self.rest_client.api_version = 'v1'
640 expected = {'api_version': 'v1',
641 'endpoint_type': 'publicURL',
642 'region': None,
Eric Wehrmeister54c7bd42016-02-24 11:11:07 -0600643 'name': None,
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500644 'service': None}
645 self.assertEqual(expected, self.rest_client.filters)
646
647
648class TestExpectedSuccess(BaseRestClientTestClass):
649
650 def setUp(self):
651 self.fake_http = fake_http.fake_httplib2()
652 super(TestExpectedSuccess, self).setUp()
653
654 def test_expected_succes_int_match(self):
655 expected_code = 202
656 read_code = 202
657 resp = self.rest_client.expected_success(expected_code, read_code)
658 # Assert None resp on success
659 self.assertFalse(resp)
660
661 def test_expected_succes_int_no_match(self):
662 expected_code = 204
663 read_code = 202
664 self.assertRaises(exceptions.InvalidHttpSuccessCode,
665 self.rest_client.expected_success,
666 expected_code, read_code)
667
668 def test_expected_succes_list_match(self):
669 expected_code = [202, 204]
670 read_code = 202
671 resp = self.rest_client.expected_success(expected_code, read_code)
672 # Assert None resp on success
673 self.assertFalse(resp)
674
675 def test_expected_succes_list_no_match(self):
676 expected_code = [202, 204]
677 read_code = 200
678 self.assertRaises(exceptions.InvalidHttpSuccessCode,
679 self.rest_client.expected_success,
680 expected_code, read_code)
681
682 def test_non_success_expected_int(self):
683 expected_code = 404
684 read_code = 202
685 self.assertRaises(AssertionError, self.rest_client.expected_success,
686 expected_code, read_code)
687
688 def test_non_success_expected_list(self):
689 expected_code = [404, 202]
690 read_code = 202
691 self.assertRaises(AssertionError, self.rest_client.expected_success,
692 expected_code, read_code)
693
ghanshyamc3074202016-04-18 15:20:45 +0900694 def test_non_success_read_code_as_string(self):
695 expected_code = 202
696 read_code = '202'
697 self.assertRaises(TypeError, self.rest_client.expected_success,
698 expected_code, read_code)
699
700 def test_non_success_read_code_as_list(self):
701 expected_code = 202
702 read_code = [202]
703 self.assertRaises(TypeError, self.rest_client.expected_success,
704 expected_code, read_code)
705
706 def test_non_success_expected_code_as_non_int(self):
707 expected_code = ['201', 202]
708 read_code = 202
709 self.assertRaises(AssertionError, self.rest_client.expected_success,
710 expected_code, read_code)
711
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500712
713class TestResponseBody(base.TestCase):
714
715 def test_str(self):
716 response = {'status': 200}
717 body = {'key1': 'value1'}
718 actual = rest_client.ResponseBody(response, body)
719 self.assertEqual("response: %s\nBody: %s" % (response, body),
720 str(actual))
721
722
723class TestResponseBodyData(base.TestCase):
724
725 def test_str(self):
726 response = {'status': 200}
727 data = 'data1'
728 actual = rest_client.ResponseBodyData(response, data)
729 self.assertEqual("response: %s\nBody: %s" % (response, data),
730 str(actual))
731
732
733class TestResponseBodyList(base.TestCase):
734
735 def test_str(self):
736 response = {'status': 200}
737 body = ['value1', 'value2', 'value3']
738 actual = rest_client.ResponseBodyList(response, body)
739 self.assertEqual("response: %s\nBody: %s" % (response, body),
740 str(actual))
741
742
743class TestJSONSchemaValidationBase(base.TestCase):
744
745 class Response(dict):
746
747 def __getattr__(self, attr):
748 return self[attr]
749
750 def __setattr__(self, attr, value):
751 self[attr] = value
752
753 def setUp(self):
754 super(TestJSONSchemaValidationBase, self).setUp()
755 self.fake_auth_provider = fake_auth_provider.FakeAuthProvider()
756 self.rest_client = rest_client.RestClient(
757 self.fake_auth_provider, None, None)
758
759 def _test_validate_pass(self, schema, resp_body, status=200):
760 resp = self.Response()
761 resp.status = status
762 self.rest_client.validate_response(schema, resp, resp_body)
763
764 def _test_validate_fail(self, schema, resp_body, status=200,
765 error_msg="HTTP response body is invalid"):
766 resp = self.Response()
767 resp.status = status
768 ex = self.assertRaises(exceptions.InvalidHTTPResponseBody,
769 self.rest_client.validate_response,
770 schema, resp, resp_body)
771 self.assertIn(error_msg, ex._error_string)
772
773
774class TestRestClientJSONSchemaValidation(TestJSONSchemaValidationBase):
775
776 schema = {
777 'status_code': [200],
778 'response_body': {
779 'type': 'object',
780 'properties': {
781 'foo': {
782 'type': 'integer',
783 },
784 },
785 'required': ['foo']
786 }
787 }
788
789 def test_validate_pass_with_http_success_code(self):
790 body = {'foo': 12}
791 self._test_validate_pass(self.schema, body, status=200)
792
793 def test_validate_pass_with_http_redirect_code(self):
794 body = {'foo': 12}
795 schema = copy.deepcopy(self.schema)
796 schema['status_code'] = 300
797 self._test_validate_pass(schema, body, status=300)
798
799 def test_validate_not_http_success_code(self):
800 schema = {
801 'status_code': [200]
802 }
803 body = {}
804 self._test_validate_pass(schema, body, status=400)
805
806 def test_validate_multiple_allowed_type(self):
807 schema = {
808 'status_code': [200],
809 'response_body': {
810 'type': 'object',
811 'properties': {
812 'foo': {
813 'type': ['integer', 'string'],
814 },
815 },
816 'required': ['foo']
817 }
818 }
819 body = {'foo': 12}
820 self._test_validate_pass(schema, body)
821 body = {'foo': '12'}
822 self._test_validate_pass(schema, body)
823
824 def test_validate_enable_additional_property_pass(self):
825 schema = {
826 'status_code': [200],
827 'response_body': {
828 'type': 'object',
829 'properties': {
830 'foo': {'type': 'integer'}
831 },
832 'additionalProperties': True,
833 'required': ['foo']
834 }
835 }
836 body = {'foo': 12, 'foo2': 'foo2value'}
837 self._test_validate_pass(schema, body)
838
839 def test_validate_disable_additional_property_pass(self):
840 schema = {
841 'status_code': [200],
842 'response_body': {
843 'type': 'object',
844 'properties': {
845 'foo': {'type': 'integer'}
846 },
847 'additionalProperties': False,
848 'required': ['foo']
849 }
850 }
851 body = {'foo': 12}
852 self._test_validate_pass(schema, body)
853
854 def test_validate_disable_additional_property_fail(self):
855 schema = {
856 'status_code': [200],
857 'response_body': {
858 'type': 'object',
859 'properties': {
860 'foo': {'type': 'integer'}
861 },
862 'additionalProperties': False,
863 'required': ['foo']
864 }
865 }
866 body = {'foo': 12, 'foo2': 'foo2value'}
867 self._test_validate_fail(schema, body)
868
869 def test_validate_wrong_status_code(self):
870 schema = {
871 'status_code': [202]
872 }
873 body = {}
874 resp = self.Response()
875 resp.status = 200
876 ex = self.assertRaises(exceptions.InvalidHttpSuccessCode,
877 self.rest_client.validate_response,
878 schema, resp, body)
879 self.assertIn("Unexpected http success status code", ex._error_string)
880
881 def test_validate_wrong_attribute_type(self):
882 body = {'foo': 1.2}
883 self._test_validate_fail(self.schema, body)
884
885 def test_validate_unexpected_response_body(self):
886 schema = {
887 'status_code': [200],
888 }
889 body = {'foo': 12}
890 self._test_validate_fail(
891 schema, body,
892 error_msg="HTTP response body should not exist")
893
894 def test_validate_missing_response_body(self):
895 body = {}
896 self._test_validate_fail(self.schema, body)
897
898 def test_validate_missing_required_attribute(self):
899 body = {'notfoo': 12}
900 self._test_validate_fail(self.schema, body)
901
902 def test_validate_response_body_not_list(self):
903 schema = {
904 'status_code': [200],
905 'response_body': {
906 'type': 'object',
907 'properties': {
908 'list_items': {
909 'type': 'array',
910 'items': {'foo': {'type': 'integer'}}
911 }
912 },
913 'required': ['list_items'],
914 }
915 }
916 body = {'foo': 12}
917 self._test_validate_fail(schema, body)
918
919 def test_validate_response_body_list_pass(self):
920 schema = {
921 'status_code': [200],
922 'response_body': {
923 'type': 'object',
924 'properties': {
925 'list_items': {
926 'type': 'array',
927 'items': {'foo': {'type': 'integer'}}
928 }
929 },
930 'required': ['list_items'],
931 }
932 }
933 body = {'list_items': [{'foo': 12}, {'foo': 10}]}
934 self._test_validate_pass(schema, body)
935
936
937class TestRestClientJSONHeaderSchemaValidation(TestJSONSchemaValidationBase):
938
939 schema = {
940 'status_code': [200],
941 'response_header': {
942 'type': 'object',
943 'properties': {
944 'foo': {'type': 'integer'}
945 },
946 'required': ['foo']
947 }
948 }
949
950 def test_validate_header_schema_pass(self):
951 resp_body = {}
952 resp = self.Response()
953 resp.status = 200
954 resp.foo = 12
955 self.rest_client.validate_response(self.schema, resp, resp_body)
956
957 def test_validate_header_schema_fail(self):
958 resp_body = {}
959 resp = self.Response()
960 resp.status = 200
961 resp.foo = 1.2
962 ex = self.assertRaises(exceptions.InvalidHTTPResponseHeader,
963 self.rest_client.validate_response,
964 self.schema, resp, resp_body)
965 self.assertIn("HTTP response header is invalid", ex._error_string)
966
967
968class TestRestClientJSONSchemaFormatValidation(TestJSONSchemaValidationBase):
969
970 schema = {
971 'status_code': [200],
972 'response_body': {
973 'type': 'object',
974 'properties': {
975 'foo': {
976 'type': 'string',
977 'format': 'email'
978 }
979 },
980 'required': ['foo']
981 }
982 }
983
984 def test_validate_format_pass(self):
985 body = {'foo': 'example@example.com'}
986 self._test_validate_pass(self.schema, body)
987
988 def test_validate_format_fail(self):
989 body = {'foo': 'wrong_email'}
990 self._test_validate_fail(self.schema, body)
991
992 def test_validate_formats_in_oneOf_pass(self):
993 schema = {
994 'status_code': [200],
995 'response_body': {
996 'type': 'object',
997 'properties': {
998 'foo': {
999 'type': 'string',
1000 'oneOf': [
1001 {'format': 'ipv4'},
1002 {'format': 'ipv6'}
1003 ]
1004 }
1005 },
1006 'required': ['foo']
1007 }
1008 }
1009 body = {'foo': '10.0.0.0'}
1010 self._test_validate_pass(schema, body)
1011 body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
1012 self._test_validate_pass(schema, body)
1013
1014 def test_validate_formats_in_oneOf_fail_both_match(self):
1015 schema = {
1016 'status_code': [200],
1017 'response_body': {
1018 'type': 'object',
1019 'properties': {
1020 'foo': {
1021 'type': 'string',
1022 'oneOf': [
1023 {'format': 'ipv4'},
1024 {'format': 'ipv4'}
1025 ]
1026 }
1027 },
1028 'required': ['foo']
1029 }
1030 }
1031 body = {'foo': '10.0.0.0'}
1032 self._test_validate_fail(schema, body)
1033
1034 def test_validate_formats_in_oneOf_fail_no_match(self):
1035 schema = {
1036 'status_code': [200],
1037 'response_body': {
1038 'type': 'object',
1039 'properties': {
1040 'foo': {
1041 'type': 'string',
1042 'oneOf': [
1043 {'format': 'ipv4'},
1044 {'format': 'ipv6'}
1045 ]
1046 }
1047 },
1048 'required': ['foo']
1049 }
1050 }
1051 body = {'foo': 'wrong_ip_format'}
1052 self._test_validate_fail(schema, body)
1053
1054 def test_validate_formats_in_anyOf_pass(self):
1055 schema = {
1056 'status_code': [200],
1057 'response_body': {
1058 'type': 'object',
1059 'properties': {
1060 'foo': {
1061 'type': 'string',
1062 'anyOf': [
1063 {'format': 'ipv4'},
1064 {'format': 'ipv6'}
1065 ]
1066 }
1067 },
1068 'required': ['foo']
1069 }
1070 }
1071 body = {'foo': '10.0.0.0'}
1072 self._test_validate_pass(schema, body)
1073 body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
1074 self._test_validate_pass(schema, body)
1075
1076 def test_validate_formats_in_anyOf_pass_both_match(self):
1077 schema = {
1078 'status_code': [200],
1079 'response_body': {
1080 'type': 'object',
1081 'properties': {
1082 'foo': {
1083 'type': 'string',
1084 'anyOf': [
1085 {'format': 'ipv4'},
1086 {'format': 'ipv4'}
1087 ]
1088 }
1089 },
1090 'required': ['foo']
1091 }
1092 }
1093 body = {'foo': '10.0.0.0'}
1094 self._test_validate_pass(schema, body)
1095
1096 def test_validate_formats_in_anyOf_fail_no_match(self):
1097 schema = {
1098 'status_code': [200],
1099 'response_body': {
1100 'type': 'object',
1101 'properties': {
1102 'foo': {
1103 'type': 'string',
1104 'anyOf': [
1105 {'format': 'ipv4'},
1106 {'format': 'ipv6'}
1107 ]
1108 }
1109 },
1110 'required': ['foo']
1111 }
1112 }
1113 body = {'foo': 'wrong_ip_format'}
1114 self._test_validate_fail(schema, body)
1115
1116 def test_validate_formats_pass_for_unknow_format(self):
1117 schema = {
1118 'status_code': [200],
1119 'response_body': {
1120 'type': 'object',
1121 'properties': {
1122 'foo': {
1123 'type': 'string',
1124 'format': 'UNKNOWN'
1125 }
1126 },
1127 'required': ['foo']
1128 }
1129 }
1130 body = {'foo': 'example@example.com'}
1131 self._test_validate_pass(schema, body)
1132
1133
1134class TestRestClientJSONSchemaValidatorVersion(TestJSONSchemaValidationBase):
1135
1136 schema = {
1137 'status_code': [200],
1138 'response_body': {
1139 'type': 'object',
1140 'properties': {
1141 'foo': {'type': 'string'}
1142 }
1143 }
1144 }
1145
1146 def test_current_json_schema_validator_version(self):
1147 with mockpatch.PatchObject(jsonschema.Draft4Validator,
1148 "check_schema") as chk_schema:
1149 body = {'foo': 'test'}
1150 self._test_validate_pass(self.schema, body)
1151 chk_schema.mock.assert_called_once_with(
1152 self.schema['response_body'])