| # Copyright 2014 Red Hat, Inc. & Deutsche Telekom AG |
| # All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| # not use this file except in compliance with the License. You may obtain |
| # a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations |
| # under the License. |
| |
| import copy |
| import jsonschema |
| |
| from tempest.openstack.common import log as logging |
| |
| LOG = logging.getLogger(__name__) |
| |
| |
| def generate_valid(schema): |
| """ |
| Create a valid dictionary based on the types in a json schema. |
| """ |
| LOG.debug("generate_valid: %s" % schema) |
| schema_type = schema["type"] |
| if isinstance(schema_type, list): |
| # Just choose the first one since all are valid. |
| schema_type = schema_type[0] |
| return type_map_valid[schema_type](schema) |
| |
| |
| def generate_valid_string(schema): |
| size = schema.get("minLength", 0) |
| # TODO(dkr mko): handle format and pattern |
| return "x" * size |
| |
| |
| def generate_valid_integer(schema): |
| # TODO(dkr mko): handle multipleOf |
| if "minimum" in schema: |
| minimum = schema["minimum"] |
| if "exclusiveMinimum" not in schema: |
| return minimum |
| else: |
| return minimum + 1 |
| if "maximum" in schema: |
| maximum = schema["maximum"] |
| if "exclusiveMaximum" not in schema: |
| return maximum |
| else: |
| return maximum - 1 |
| return 0 |
| |
| |
| def generate_valid_object(schema): |
| obj = {} |
| for k, v in schema["properties"].iteritems(): |
| obj[k] = generate_valid(v) |
| return obj |
| |
| |
| def generate_invalid(schema): |
| """ |
| Generate an invalid json dictionary based on a schema. |
| Only one value is mis-generated for each dictionary created. |
| |
| Any generator must return a list of tuples or a single tuple. |
| The values of this tuple are: |
| result[0]: Name of the test |
| result[1]: json schema for the test |
| result[2]: expected result of the test (can be None) |
| """ |
| LOG.debug("generate_invalid: %s" % schema) |
| schema_type = schema["type"] |
| if isinstance(schema_type, list): |
| if "integer" in schema_type: |
| schema_type = "integer" |
| else: |
| raise Exception("non-integer list types not supported") |
| result = [] |
| for generator in type_map_invalid[schema_type]: |
| ret = generator(schema) |
| if ret is not None: |
| if isinstance(ret, list): |
| result.extend(ret) |
| elif isinstance(ret, tuple): |
| result.append(ret) |
| else: |
| raise Exception("generator (%s) returns invalid result" |
| % generator) |
| LOG.debug("result: %s" % result) |
| return result |
| |
| |
| def _check_for_expected_result(name, schema): |
| expected_result = None |
| if "results" in schema: |
| if name in schema["results"]: |
| expected_result = schema["results"][name] |
| return expected_result |
| |
| |
| def generator(fn): |
| """ |
| Decorator for simple generators that simply return one value |
| """ |
| def wrapped(schema): |
| result = fn(schema) |
| if result is not None: |
| expected_result = _check_for_expected_result(fn.__name__, schema) |
| return (fn.__name__, result, expected_result) |
| return |
| return wrapped |
| |
| |
| @generator |
| def gen_int(_): |
| return 4 |
| |
| |
| @generator |
| def gen_string(_): |
| return "XXXXXX" |
| |
| |
| def gen_none(schema): |
| # Note(mkoderer): it's not using the decorator otherwise it'd be filtered |
| expected_result = _check_for_expected_result('gen_none', schema) |
| return ('gen_none', None, expected_result) |
| |
| |
| @generator |
| def gen_str_min_length(schema): |
| min_length = schema.get("minLength", 0) |
| if min_length > 0: |
| return "x" * (min_length - 1) |
| |
| |
| @generator |
| def gen_str_max_length(schema): |
| max_length = schema.get("maxLength", -1) |
| if max_length > -1: |
| return "x" * (max_length + 1) |
| |
| |
| @generator |
| def gen_int_min(schema): |
| if "minimum" in schema: |
| minimum = schema["minimum"] |
| if "exclusiveMinimum" not in schema: |
| minimum -= 1 |
| return minimum |
| |
| |
| @generator |
| def gen_int_max(schema): |
| if "maximum" in schema: |
| maximum = schema["maximum"] |
| if "exclusiveMaximum" not in schema: |
| maximum += 1 |
| return maximum |
| |
| |
| def gen_obj_remove_attr(schema): |
| invalids = [] |
| valid = generate_valid(schema) |
| required = schema.get("required", []) |
| for r in required: |
| new_valid = copy.deepcopy(valid) |
| del new_valid[r] |
| invalids.append(("gen_obj_remove_attr", new_valid, None)) |
| return invalids |
| |
| |
| @generator |
| def gen_obj_add_attr(schema): |
| valid = generate_valid(schema) |
| if not schema.get("additionalProperties", True): |
| new_valid = copy.deepcopy(valid) |
| new_valid["$$$$$$$$$$"] = "xxx" |
| return new_valid |
| |
| |
| def gen_inv_prop_obj(schema): |
| LOG.debug("generate_invalid_object: %s" % schema) |
| valid = generate_valid(schema) |
| invalids = [] |
| properties = schema["properties"] |
| |
| for k, v in properties.iteritems(): |
| for invalid in generate_invalid(v): |
| LOG.debug(v) |
| new_valid = copy.deepcopy(valid) |
| new_valid[k] = invalid[1] |
| name = "prop_%s_%s" % (k, invalid[0]) |
| invalids.append((name, new_valid, invalid[2])) |
| |
| LOG.debug("generate_invalid_object return: %s" % invalids) |
| return invalids |
| |
| |
| type_map_valid = { |
| "string": generate_valid_string, |
| "integer": generate_valid_integer, |
| "object": generate_valid_object |
| } |
| |
| type_map_invalid = { |
| "string": [ |
| gen_int, |
| gen_none, |
| gen_str_min_length, |
| gen_str_max_length], |
| "integer": [ |
| gen_string, |
| gen_none, |
| gen_int_min, |
| gen_int_max], |
| "object": [ |
| gen_obj_remove_attr, |
| gen_obj_add_attr, |
| gen_inv_prop_obj] |
| } |
| |
| schema = { |
| "type": "object", |
| "properties": { |
| "name": {"type": "string"}, |
| "http-method": { |
| "enum": ["GET", "PUT", "HEAD", |
| "POST", "PATCH", "DELETE", 'COPY'] |
| }, |
| "url": {"type": "string"}, |
| "json-schema": jsonschema._utils.load_schema("draft4"), |
| "resources": { |
| "type": "array", |
| "items": { |
| "oneOf": [ |
| {"type": "string"}, |
| { |
| "type": "object", |
| "properties": { |
| "name": {"type": "string"}, |
| "expected_result": {"type": "integer"} |
| } |
| } |
| ] |
| } |
| }, |
| "results": { |
| "type": "object", |
| "properties": {} |
| } |
| }, |
| "required": ["name", "http-method", "url"], |
| "additionalProperties": False, |
| } |
| |
| |
| def validate_negative_test_schema(nts): |
| jsonschema.validate(nts, schema) |