diff --git a/tempest_lib/common/rest_client.py b/tempest_lib/common/rest_client.py index 1102a85..2206ca1 100644 --- a/tempest_lib/common/rest_client.py +++ b/tempest_lib/common/rest_client.py @@ -34,6 +34,9 @@ MAX_RECURSION_DEPTH = 2 # All the successful HTTP status codes from RFC 7231 & 4918 HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206, 207) +# JSON Schema format checker used for JSON Schema validation +FORMAT_CHECKER = jsonschema.draft4_format_checker + class RestClient(object): """Unified OpenStack RestClient class @@ -805,7 +808,8 @@ class RestClient(object): body_schema = schema.get('response_body') if body_schema: try: - jsonschema.validate(body, body_schema) + jsonschema.validate(body, body_schema, + format_checker=FORMAT_CHECKER) except jsonschema.ValidationError as ex: msg = ("HTTP response body is invalid (%s)") % ex raise exceptions.InvalidHTTPResponseBody(msg) @@ -818,7 +822,8 @@ class RestClient(object): header_schema = schema.get('response_header') if header_schema: try: - jsonschema.validate(resp, header_schema) + jsonschema.validate(resp, header_schema, + format_checker=FORMAT_CHECKER) except jsonschema.ValidationError as ex: msg = ("HTTP response header is invalid (%s)") % ex raise exceptions.InvalidHTTPResponseHeader(msg) diff --git a/tempest_lib/tests/test_rest_client.py b/tempest_lib/tests/test_rest_client.py index 04829cc..21b0eda 100644 --- a/tempest_lib/tests/test_rest_client.py +++ b/tempest_lib/tests/test_rest_client.py @@ -861,3 +861,169 @@ class TestRestClientJSONHeaderSchemaValidation(TestJSONSchemaValidationBase): self.rest_client.validate_response, self.schema, resp, resp_body) self.assertIn("HTTP response header is invalid", ex._error_string) + + +class TestRestClientJSONSchemaFormatValidation(TestJSONSchemaValidationBase): + + schema = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'foo': { + 'type': 'string', + 'format': 'email' + } + }, + 'required': ['foo'] + } + } + + def test_validate_format_pass(self): + body = {'foo': 'example@example.com'} + self._test_validate_pass(self.schema, body) + + def test_validate_format_fail(self): + body = {'foo': 'wrong_email'} + self._test_validate_fail(self.schema, body) + + def test_validate_formats_in_oneOf_pass(self): + schema = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'foo': { + 'type': 'string', + 'oneOf': [ + {'format': 'ipv4'}, + {'format': 'ipv6'} + ] + } + }, + 'required': ['foo'] + } + } + body = {'foo': '10.0.0.0'} + self._test_validate_pass(schema, body) + body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'} + self._test_validate_pass(schema, body) + + def test_validate_formats_in_oneOf_fail_both_match(self): + schema = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'foo': { + 'type': 'string', + 'oneOf': [ + {'format': 'ipv4'}, + {'format': 'ipv4'} + ] + } + }, + 'required': ['foo'] + } + } + body = {'foo': '10.0.0.0'} + self._test_validate_fail(schema, body) + + def test_validate_formats_in_oneOf_fail_no_match(self): + schema = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'foo': { + 'type': 'string', + 'oneOf': [ + {'format': 'ipv4'}, + {'format': 'ipv6'} + ] + } + }, + 'required': ['foo'] + } + } + body = {'foo': 'wrong_ip_format'} + self._test_validate_fail(schema, body) + + def test_validate_formats_in_anyOf_pass(self): + schema = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'foo': { + 'type': 'string', + 'anyOf': [ + {'format': 'ipv4'}, + {'format': 'ipv6'} + ] + } + }, + 'required': ['foo'] + } + } + body = {'foo': '10.0.0.0'} + self._test_validate_pass(schema, body) + body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'} + self._test_validate_pass(schema, body) + + def test_validate_formats_in_anyOf_pass_both_match(self): + schema = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'foo': { + 'type': 'string', + 'anyOf': [ + {'format': 'ipv4'}, + {'format': 'ipv4'} + ] + } + }, + 'required': ['foo'] + } + } + body = {'foo': '10.0.0.0'} + self._test_validate_pass(schema, body) + + def test_validate_formats_in_anyOf_fail_no_match(self): + schema = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'foo': { + 'type': 'string', + 'anyOf': [ + {'format': 'ipv4'}, + {'format': 'ipv6'} + ] + } + }, + 'required': ['foo'] + } + } + body = {'foo': 'wrong_ip_format'} + self._test_validate_fail(schema, body) + + def test_validate_formats_pass_for_unknow_format(self): + schema = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'foo': { + 'type': 'string', + 'format': 'UNKNOWN' + } + }, + 'required': ['foo'] + } + } + body = {'foo': 'example@example.com'} + self._test_validate_pass(schema, body)