From f46b6c99e05c2a4dba23f071edc2b1dad23e42dc Mon Sep 17 00:00:00 2001 From: ghanshyam Date: Tue, 9 Jun 2015 17:20:34 +0900 Subject: [PATCH] Fix 'format' checking for json schema validation In json schema, "format" keyword is defined to allow inter-operable semantic validation for a fixed subset of values. For example - ip address which comes as 'string' can be further checked with ipv4 or ipv6 format. ip_address = { 'type': 'string', 'oneOf': [ {'format': 'ipv4'}, {'format': 'ipv6'} ] } Json schema does not validate 'format' correctly if FormatChecker is not passed during validation. Without FormatChecker, it validate all format as true. To validate 'format' correctly FormatChecker has to be passed while doing schema validation. This commit fix this issue and adds unit tests for that. Closes-Bug:1460975 Change-Id: Iad6ec9644eb1ec976b4a47f7dec127bfaecfd1fa --- tempest_lib/common/rest_client.py | 9 +- tempest_lib/tests/test_rest_client.py | 166 ++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 2 deletions(-) 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)