blob: 106a1e545cc9f5ba2c9177624f7b01a6cf237d5e [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 = {
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
Paul Glass119565a2016-04-06 11:41:42 -0500550class TestRateLimiting(BaseRestClientTestClass):
551
552 def setUp(self):
553 self.fake_http = fake_http.fake_httplib2()
554 super(TestRateLimiting, self).setUp()
555
556 def test__get_retry_after_delay_with_integer(self):
557 resp = {'retry-after': '123'}
558 self.assertEqual(123, self.rest_client._get_retry_after_delay(resp))
559
560 def test__get_retry_after_delay_with_http_date(self):
561 resp = {
562 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
563 'retry-after': 'Mon, 4 Apr 2016 21:58:26 GMT',
564 }
565 self.assertEqual(123, self.rest_client._get_retry_after_delay(resp))
566
567 def test__get_retry_after_delay_of_zero_with_integer(self):
568 resp = {'retry-after': '0'}
569 self.assertEqual(1, self.rest_client._get_retry_after_delay(resp))
570
571 def test__get_retry_after_delay_of_zero_with_http_date(self):
572 resp = {
573 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
574 'retry-after': 'Mon, 4 Apr 2016 21:56:23 GMT',
575 }
576 self.assertEqual(1, self.rest_client._get_retry_after_delay(resp))
577
578 def test__get_retry_after_delay_with_missing_date_header(self):
579 resp = {
580 'retry-after': 'Mon, 4 Apr 2016 21:58:26 GMT',
581 }
582 self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
583 resp)
584
585 def test__get_retry_after_delay_with_invalid_http_date(self):
586 resp = {
587 'retry-after': 'Mon, 4 AAA 2016 21:58:26 GMT',
588 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
589 }
590 self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
591 resp)
592
593 def test__get_retry_after_delay_with_missing_retry_after_header(self):
594 self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
595 {})
596
597 def test_is_absolute_limit_gives_false_with_retry_after(self):
598 resp = {'retry-after': 123}
599
600 # is_absolute_limit() requires the overLimit body to be unwrapped
601 resp_body = self.rest_client._parse_resp("""{
602 "overLimit": {
603 "message": ""
604 }
605 }""")
606 self.assertFalse(self.rest_client.is_absolute_limit(resp, resp_body))
607
608
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500609class TestProperties(BaseRestClientTestClass):
610
611 def setUp(self):
612 self.fake_http = fake_http.fake_httplib2()
613 super(TestProperties, self).setUp()
614 creds_dict = {
615 'username': 'test-user',
616 'user_id': 'test-user_id',
617 'tenant_name': 'test-tenant_name',
618 'tenant_id': 'test-tenant_id',
619 'password': 'test-password'
620 }
621 self.rest_client = rest_client.RestClient(
622 fake_auth_provider.FakeAuthProvider(creds_dict=creds_dict),
623 None, None)
624
625 def test_properties(self):
626 self.assertEqual('test-user', self.rest_client.user)
627 self.assertEqual('test-user_id', self.rest_client.user_id)
628 self.assertEqual('test-tenant_name', self.rest_client.tenant_name)
629 self.assertEqual('test-tenant_id', self.rest_client.tenant_id)
630 self.assertEqual('test-password', self.rest_client.password)
631
632 self.rest_client.api_version = 'v1'
633 expected = {'api_version': 'v1',
634 'endpoint_type': 'publicURL',
635 'region': None,
636 'service': None,
637 'skip_path': True}
638 self.rest_client.skip_path()
639 self.assertEqual(expected, self.rest_client.filters)
640
641 self.rest_client.reset_path()
642 self.rest_client.api_version = 'v1'
643 expected = {'api_version': 'v1',
644 'endpoint_type': 'publicURL',
645 'region': None,
646 'service': None}
647 self.assertEqual(expected, self.rest_client.filters)
648
649
650class TestExpectedSuccess(BaseRestClientTestClass):
651
652 def setUp(self):
653 self.fake_http = fake_http.fake_httplib2()
654 super(TestExpectedSuccess, self).setUp()
655
656 def test_expected_succes_int_match(self):
657 expected_code = 202
658 read_code = 202
659 resp = self.rest_client.expected_success(expected_code, read_code)
660 # Assert None resp on success
661 self.assertFalse(resp)
662
663 def test_expected_succes_int_no_match(self):
664 expected_code = 204
665 read_code = 202
666 self.assertRaises(exceptions.InvalidHttpSuccessCode,
667 self.rest_client.expected_success,
668 expected_code, read_code)
669
670 def test_expected_succes_list_match(self):
671 expected_code = [202, 204]
672 read_code = 202
673 resp = self.rest_client.expected_success(expected_code, read_code)
674 # Assert None resp on success
675 self.assertFalse(resp)
676
677 def test_expected_succes_list_no_match(self):
678 expected_code = [202, 204]
679 read_code = 200
680 self.assertRaises(exceptions.InvalidHttpSuccessCode,
681 self.rest_client.expected_success,
682 expected_code, read_code)
683
684 def test_non_success_expected_int(self):
685 expected_code = 404
686 read_code = 202
687 self.assertRaises(AssertionError, self.rest_client.expected_success,
688 expected_code, read_code)
689
690 def test_non_success_expected_list(self):
691 expected_code = [404, 202]
692 read_code = 202
693 self.assertRaises(AssertionError, self.rest_client.expected_success,
694 expected_code, read_code)
695
ghanshyamc3074202016-04-18 15:20:45 +0900696 def test_non_success_read_code_as_string(self):
697 expected_code = 202
698 read_code = '202'
699 self.assertRaises(TypeError, self.rest_client.expected_success,
700 expected_code, read_code)
701
702 def test_non_success_read_code_as_list(self):
703 expected_code = 202
704 read_code = [202]
705 self.assertRaises(TypeError, self.rest_client.expected_success,
706 expected_code, read_code)
707
708 def test_non_success_expected_code_as_non_int(self):
709 expected_code = ['201', 202]
710 read_code = 202
711 self.assertRaises(AssertionError, self.rest_client.expected_success,
712 expected_code, read_code)
713
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500714
715class TestResponseBody(base.TestCase):
716
717 def test_str(self):
718 response = {'status': 200}
719 body = {'key1': 'value1'}
720 actual = rest_client.ResponseBody(response, body)
721 self.assertEqual("response: %s\nBody: %s" % (response, body),
722 str(actual))
723
724
725class TestResponseBodyData(base.TestCase):
726
727 def test_str(self):
728 response = {'status': 200}
729 data = 'data1'
730 actual = rest_client.ResponseBodyData(response, data)
731 self.assertEqual("response: %s\nBody: %s" % (response, data),
732 str(actual))
733
734
735class TestResponseBodyList(base.TestCase):
736
737 def test_str(self):
738 response = {'status': 200}
739 body = ['value1', 'value2', 'value3']
740 actual = rest_client.ResponseBodyList(response, body)
741 self.assertEqual("response: %s\nBody: %s" % (response, body),
742 str(actual))
743
744
745class TestJSONSchemaValidationBase(base.TestCase):
746
747 class Response(dict):
748
749 def __getattr__(self, attr):
750 return self[attr]
751
752 def __setattr__(self, attr, value):
753 self[attr] = value
754
755 def setUp(self):
756 super(TestJSONSchemaValidationBase, self).setUp()
757 self.fake_auth_provider = fake_auth_provider.FakeAuthProvider()
758 self.rest_client = rest_client.RestClient(
759 self.fake_auth_provider, None, None)
760
761 def _test_validate_pass(self, schema, resp_body, status=200):
762 resp = self.Response()
763 resp.status = status
764 self.rest_client.validate_response(schema, resp, resp_body)
765
766 def _test_validate_fail(self, schema, resp_body, status=200,
767 error_msg="HTTP response body is invalid"):
768 resp = self.Response()
769 resp.status = status
770 ex = self.assertRaises(exceptions.InvalidHTTPResponseBody,
771 self.rest_client.validate_response,
772 schema, resp, resp_body)
773 self.assertIn(error_msg, ex._error_string)
774
775
776class TestRestClientJSONSchemaValidation(TestJSONSchemaValidationBase):
777
778 schema = {
779 'status_code': [200],
780 'response_body': {
781 'type': 'object',
782 'properties': {
783 'foo': {
784 'type': 'integer',
785 },
786 },
787 'required': ['foo']
788 }
789 }
790
791 def test_validate_pass_with_http_success_code(self):
792 body = {'foo': 12}
793 self._test_validate_pass(self.schema, body, status=200)
794
795 def test_validate_pass_with_http_redirect_code(self):
796 body = {'foo': 12}
797 schema = copy.deepcopy(self.schema)
798 schema['status_code'] = 300
799 self._test_validate_pass(schema, body, status=300)
800
801 def test_validate_not_http_success_code(self):
802 schema = {
803 'status_code': [200]
804 }
805 body = {}
806 self._test_validate_pass(schema, body, status=400)
807
808 def test_validate_multiple_allowed_type(self):
809 schema = {
810 'status_code': [200],
811 'response_body': {
812 'type': 'object',
813 'properties': {
814 'foo': {
815 'type': ['integer', 'string'],
816 },
817 },
818 'required': ['foo']
819 }
820 }
821 body = {'foo': 12}
822 self._test_validate_pass(schema, body)
823 body = {'foo': '12'}
824 self._test_validate_pass(schema, body)
825
826 def test_validate_enable_additional_property_pass(self):
827 schema = {
828 'status_code': [200],
829 'response_body': {
830 'type': 'object',
831 'properties': {
832 'foo': {'type': 'integer'}
833 },
834 'additionalProperties': True,
835 'required': ['foo']
836 }
837 }
838 body = {'foo': 12, 'foo2': 'foo2value'}
839 self._test_validate_pass(schema, body)
840
841 def test_validate_disable_additional_property_pass(self):
842 schema = {
843 'status_code': [200],
844 'response_body': {
845 'type': 'object',
846 'properties': {
847 'foo': {'type': 'integer'}
848 },
849 'additionalProperties': False,
850 'required': ['foo']
851 }
852 }
853 body = {'foo': 12}
854 self._test_validate_pass(schema, body)
855
856 def test_validate_disable_additional_property_fail(self):
857 schema = {
858 'status_code': [200],
859 'response_body': {
860 'type': 'object',
861 'properties': {
862 'foo': {'type': 'integer'}
863 },
864 'additionalProperties': False,
865 'required': ['foo']
866 }
867 }
868 body = {'foo': 12, 'foo2': 'foo2value'}
869 self._test_validate_fail(schema, body)
870
871 def test_validate_wrong_status_code(self):
872 schema = {
873 'status_code': [202]
874 }
875 body = {}
876 resp = self.Response()
877 resp.status = 200
878 ex = self.assertRaises(exceptions.InvalidHttpSuccessCode,
879 self.rest_client.validate_response,
880 schema, resp, body)
881 self.assertIn("Unexpected http success status code", ex._error_string)
882
883 def test_validate_wrong_attribute_type(self):
884 body = {'foo': 1.2}
885 self._test_validate_fail(self.schema, body)
886
887 def test_validate_unexpected_response_body(self):
888 schema = {
889 'status_code': [200],
890 }
891 body = {'foo': 12}
892 self._test_validate_fail(
893 schema, body,
894 error_msg="HTTP response body should not exist")
895
896 def test_validate_missing_response_body(self):
897 body = {}
898 self._test_validate_fail(self.schema, body)
899
900 def test_validate_missing_required_attribute(self):
901 body = {'notfoo': 12}
902 self._test_validate_fail(self.schema, body)
903
904 def test_validate_response_body_not_list(self):
905 schema = {
906 'status_code': [200],
907 'response_body': {
908 'type': 'object',
909 'properties': {
910 'list_items': {
911 'type': 'array',
912 'items': {'foo': {'type': 'integer'}}
913 }
914 },
915 'required': ['list_items'],
916 }
917 }
918 body = {'foo': 12}
919 self._test_validate_fail(schema, body)
920
921 def test_validate_response_body_list_pass(self):
922 schema = {
923 'status_code': [200],
924 'response_body': {
925 'type': 'object',
926 'properties': {
927 'list_items': {
928 'type': 'array',
929 'items': {'foo': {'type': 'integer'}}
930 }
931 },
932 'required': ['list_items'],
933 }
934 }
935 body = {'list_items': [{'foo': 12}, {'foo': 10}]}
936 self._test_validate_pass(schema, body)
937
938
939class TestRestClientJSONHeaderSchemaValidation(TestJSONSchemaValidationBase):
940
941 schema = {
942 'status_code': [200],
943 'response_header': {
944 'type': 'object',
945 'properties': {
946 'foo': {'type': 'integer'}
947 },
948 'required': ['foo']
949 }
950 }
951
952 def test_validate_header_schema_pass(self):
953 resp_body = {}
954 resp = self.Response()
955 resp.status = 200
956 resp.foo = 12
957 self.rest_client.validate_response(self.schema, resp, resp_body)
958
959 def test_validate_header_schema_fail(self):
960 resp_body = {}
961 resp = self.Response()
962 resp.status = 200
963 resp.foo = 1.2
964 ex = self.assertRaises(exceptions.InvalidHTTPResponseHeader,
965 self.rest_client.validate_response,
966 self.schema, resp, resp_body)
967 self.assertIn("HTTP response header is invalid", ex._error_string)
968
969
970class TestRestClientJSONSchemaFormatValidation(TestJSONSchemaValidationBase):
971
972 schema = {
973 'status_code': [200],
974 'response_body': {
975 'type': 'object',
976 'properties': {
977 'foo': {
978 'type': 'string',
979 'format': 'email'
980 }
981 },
982 'required': ['foo']
983 }
984 }
985
986 def test_validate_format_pass(self):
987 body = {'foo': 'example@example.com'}
988 self._test_validate_pass(self.schema, body)
989
990 def test_validate_format_fail(self):
991 body = {'foo': 'wrong_email'}
992 self._test_validate_fail(self.schema, body)
993
994 def test_validate_formats_in_oneOf_pass(self):
995 schema = {
996 'status_code': [200],
997 'response_body': {
998 'type': 'object',
999 'properties': {
1000 'foo': {
1001 'type': 'string',
1002 'oneOf': [
1003 {'format': 'ipv4'},
1004 {'format': 'ipv6'}
1005 ]
1006 }
1007 },
1008 'required': ['foo']
1009 }
1010 }
1011 body = {'foo': '10.0.0.0'}
1012 self._test_validate_pass(schema, body)
1013 body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
1014 self._test_validate_pass(schema, body)
1015
1016 def test_validate_formats_in_oneOf_fail_both_match(self):
1017 schema = {
1018 'status_code': [200],
1019 'response_body': {
1020 'type': 'object',
1021 'properties': {
1022 'foo': {
1023 'type': 'string',
1024 'oneOf': [
1025 {'format': 'ipv4'},
1026 {'format': 'ipv4'}
1027 ]
1028 }
1029 },
1030 'required': ['foo']
1031 }
1032 }
1033 body = {'foo': '10.0.0.0'}
1034 self._test_validate_fail(schema, body)
1035
1036 def test_validate_formats_in_oneOf_fail_no_match(self):
1037 schema = {
1038 'status_code': [200],
1039 'response_body': {
1040 'type': 'object',
1041 'properties': {
1042 'foo': {
1043 'type': 'string',
1044 'oneOf': [
1045 {'format': 'ipv4'},
1046 {'format': 'ipv6'}
1047 ]
1048 }
1049 },
1050 'required': ['foo']
1051 }
1052 }
1053 body = {'foo': 'wrong_ip_format'}
1054 self._test_validate_fail(schema, body)
1055
1056 def test_validate_formats_in_anyOf_pass(self):
1057 schema = {
1058 'status_code': [200],
1059 'response_body': {
1060 'type': 'object',
1061 'properties': {
1062 'foo': {
1063 'type': 'string',
1064 'anyOf': [
1065 {'format': 'ipv4'},
1066 {'format': 'ipv6'}
1067 ]
1068 }
1069 },
1070 'required': ['foo']
1071 }
1072 }
1073 body = {'foo': '10.0.0.0'}
1074 self._test_validate_pass(schema, body)
1075 body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
1076 self._test_validate_pass(schema, body)
1077
1078 def test_validate_formats_in_anyOf_pass_both_match(self):
1079 schema = {
1080 'status_code': [200],
1081 'response_body': {
1082 'type': 'object',
1083 'properties': {
1084 'foo': {
1085 'type': 'string',
1086 'anyOf': [
1087 {'format': 'ipv4'},
1088 {'format': 'ipv4'}
1089 ]
1090 }
1091 },
1092 'required': ['foo']
1093 }
1094 }
1095 body = {'foo': '10.0.0.0'}
1096 self._test_validate_pass(schema, body)
1097
1098 def test_validate_formats_in_anyOf_fail_no_match(self):
1099 schema = {
1100 'status_code': [200],
1101 'response_body': {
1102 'type': 'object',
1103 'properties': {
1104 'foo': {
1105 'type': 'string',
1106 'anyOf': [
1107 {'format': 'ipv4'},
1108 {'format': 'ipv6'}
1109 ]
1110 }
1111 },
1112 'required': ['foo']
1113 }
1114 }
1115 body = {'foo': 'wrong_ip_format'}
1116 self._test_validate_fail(schema, body)
1117
1118 def test_validate_formats_pass_for_unknow_format(self):
1119 schema = {
1120 'status_code': [200],
1121 'response_body': {
1122 'type': 'object',
1123 'properties': {
1124 'foo': {
1125 'type': 'string',
1126 'format': 'UNKNOWN'
1127 }
1128 },
1129 'required': ['foo']
1130 }
1131 }
1132 body = {'foo': 'example@example.com'}
1133 self._test_validate_pass(schema, body)
1134
1135
1136class TestRestClientJSONSchemaValidatorVersion(TestJSONSchemaValidationBase):
1137
1138 schema = {
1139 'status_code': [200],
1140 'response_body': {
1141 'type': 'object',
1142 'properties': {
1143 'foo': {'type': 'string'}
1144 }
1145 }
1146 }
1147
1148 def test_current_json_schema_validator_version(self):
1149 with mockpatch.PatchObject(jsonschema.Draft4Validator,
1150 "check_schema") as chk_schema:
1151 body = {'foo': 'test'}
1152 self._test_validate_pass(self.schema, body)
1153 chk_schema.mock.assert_called_once_with(
1154 self.schema['response_body'])