def test_enum_in_array_items_pass(): """Test that having enum in array's items passes.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "$schema": { "type": "string" }, "resource_type": { "title": "Resource Type", "description": "The type of the resource.", "type": "array", "items": { "type": "string", "enum": [ "Text", "Image", "Video", "Audio", "Time-Series", "Other" ] }, "uniqueItems": True } }, "required": ["community", "title", "open_access"] } obj = JSONSchemaValidator() obj.validate(v1, 'first')
def test_conflict_in_dependencies(): """Test that having conflict between elements in dependencies fails.""" v1 = { "type": "object", "properties": { "credit_card": { "type": "number" }, "billing_address": { "type": "number" } }, "dependencies": { "credit_card": { "properties": { "billing_address": { "type": "string" } } } } } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_conflict_in_collection2(): """Test that having a conflict between properties of an allOf collection fails.""" v1 = { "type": "object", "allOf": [ { "type": "object", "properties": { "my_field": { "type": "string" } } }, { "type": "object", "dependencies": { "my_field": { "type": "object", "properties": { "my_field": { "type": "integer" } } } } } ] } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_conflict_in_collection2(): """Test that having a conflict between properties of an allOf collection fails.""" v1 = { "type": "object", "allOf": [{ "type": "object", "properties": { "my_field": { "type": "string" } } }, { "type": "object", "dependencies": { "my_field": { "type": "object", "properties": { "my_field": { "type": "integer" } } } } }] } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_not_ignoring_indices_pass(): """Test that with option 'ignore index' set to False and different types in array passes.""" v1 = { "type": "object", "properties": { "experiment_info": { "type": "array", "items": [ { "type": "object", "properties": { "field_A": {"type": "string"} } }, { "type": "object", "properties": { "field_A": {"type": "number"} } } ] } } } obj = JSONSchemaValidator(False) obj.validate(v1, 'first')
def test_enum_in_array_items_pass(): """Test that having enum in array's items passes.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "$schema": {"type": "string"}, "resource_type": { "title": "Resource Type", "description": "The type of the resource.", "type": "array", "items": { "type": "string", "enum": [ "Text", "Image", "Video", "Audio", "Time-Series", "Other" ] }, "uniqueItems": True } }, "required": ["community", "title", "open_access"] } obj = JSONSchemaValidator() obj.validate(v1, 'first')
def test_ref_no_conflict_inside_schema_pass(): """Test that having no conflict in schema with references passes.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "definitions": { "address": { "type": "object", "properties": { "street_address": { "type": "string" } } } }, "type": "object", "properties": { "billing_address": { "$ref": "#/definitions/address" }, "shipping_address": { "allOf": [{ "$ref": "#/definitions/address" }, { "properties": { "type": { "enum": ["residential", "business"] } } }] } } } obj = JSONSchemaValidator() obj.validate(v1, 'first')
def test_ignoring_indices(): """Test that with option 'ignore index' set to True and different types in array fails.""" v1 = { "type": "object", "properties": { "experiment_info": { "type": "array", "items": [ { "type": "object", "properties": { "field_A": {"type": "string"} } }, { "type": "object", "properties": { "field_A": {"type": "number"} } } ] } } } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_ref_conflict_inside_schema(): """Test that having an invalid reference in the schema raises RefResolutionError.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "billing_address": { "$ref": "#/definitions/differentaddress" }, "shipping_address": { "allOf": [{ "$ref": "#/definitions/address" }, { "properties": { "type": { "enum": ["residential", "business"] } } }] } } } obj = JSONSchemaValidator() with pytest.raises(jsonschema.exceptions.RefResolutionError): obj.validate(v1, 'first')
def test_type_conflict_with_dependencies_implied(): """Test that having type given in schema and type 'object' implied by dependencies fails.""" v1 = {"type": "string", "dependencies": {"field_a": {"type": "string"}}} obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_not_ignoring_indices_pass(): """Test that with option 'ignore index' set to False and different types in array passes.""" v1 = { "type": "object", "properties": { "experiment_info": { "type": "array", "items": [{ "type": "object", "properties": { "field_A": { "type": "string" } } }, { "type": "object", "properties": { "field_A": { "type": "number" } } }] } } } obj = JSONSchemaValidator(False) obj.validate(v1, 'first')
def test_ignoring_indices(): """Test that with option 'ignore index' set to True and different types in array fails.""" v1 = { "type": "object", "properties": { "experiment_info": { "type": "array", "items": [{ "type": "object", "properties": { "field_A": { "type": "string" } } }, { "type": "object", "properties": { "field_A": { "type": "number" } } }] } } } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_ref_conflict_inside_schema(): """Test that having an invalid reference in the schema raises RefResolutionError.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "billing_address": {"$ref": "#/definitions/differentaddress"}, "shipping_address": { "allOf": [ {"$ref": "#/definitions/address"}, { "properties": { "type": {"enum": ["residential", "business"]} } } ] } } } obj = JSONSchemaValidator() with pytest.raises(jsonschema.exceptions.RefResolutionError): obj.validate(v1, 'first')
def test_ref_no_conflict_inside_schema_pass(): """Test that having no conflict in schema with references passes.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "definitions": { "address": { "type": "object", "properties": { "street_address": {"type": "string"} } } }, "type": "object", "properties": { "billing_address": {"$ref": "#/definitions/address"}, "shipping_address": { "allOf": [ {"$ref": "#/definitions/address"}, { "properties": { "type": {"enum": ["residential", "business"]} } } ] } } } obj = JSONSchemaValidator() obj.validate(v1, 'first')
def test_root_not_object(): """Test that not having type 'object' of the root fails.""" v1 = { "type": "string" } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_root_object_implicit_pass(): """Test that having implicit type 'object' of the root passes.""" v1 = { "properties": { "type": "string" } } obj = JSONSchemaValidator() obj.validate(v1, 'first')
def test_type_conflict_with_dependencies_implied(): """Test that having type given in schema and type 'object' implied by dependencies fails.""" v1 = { "type": "string", "dependencies": { "field_a": {"type": "string"} } } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_null_type_not_implemented(): """Test that having a field with null type fails.""" v1 = { "type": "object", "properties": { "abc": { "type": "null" } } } obj = JSONSchemaValidator() with pytest.raises(NotImplementedError): obj.validate(v1, 'first')
def test_array_no_items_pass(): """Test that having an array without items passes.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "$schema": {"type": "string"}, "_files": { "type": "array" } } } obj = JSONSchemaValidator() obj.validate(v1, 'first')
def test_different_types_given_and_guessed_enum(): """Tets that having type given in schema and different type guessed from enum fails.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "resource_type": { "type": "integer", "enum": ["Text", "Image"] } } } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_list_type_inside_enum(): "Test that having a list inside enum fails." v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "resource_type": { "type": "array", "items": { "enum": [['abc'], ['bdc']] } } } } obj = JSONSchemaValidator() with pytest.raises(NotImplementedError): obj.validate(v1, 'first')
def test_different_types_inside_enum(): """Test that having different types inside enum fails.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "resource_type": { "type": "array", "items": { "enum": ["Text", 42] } } } } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_dependencies_not_dict(): """Test the exception raised when dependencies are neither of type list nor a dict.""" v1 = { "type": "object", "properties": { "name": {"type": "string"}, "credit_card": {"type": "number"}, }, "dependencies": { "name": "credit_card" } } obj = JSONSchemaValidator() with pytest.raises(ValueError): obj.validate(v1, 'first')
def test_no_conflict_between_schemas_pass(): """Test that having two correct schemas and no conflict between them passes.""" v1 = { "type": "object", "properties": { "field_A": {"type": "string"} } } v2 = { "type": "object", "properties": { "field_B": {"type": "string"} } } obj = JSONSchemaValidator() obj.validate(v1, 'first') obj.validate(v2, 'second')
def test_conflict_between_schemas(): """Test that having correct schemas and conflict between them fails.""" v1 = { "type": "object", "properties": { "field_A": {"type": "string"} } } v2 = { "type": "object", "properties": { "field_A": {"type": "number"} } } obj = JSONSchemaValidator() obj.validate(v1, 'first') with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v2, 'second')
def test_new_field_in_dependencies_passes(): """Test that having a new field in dependencies fails.""" v1 = { "type": "object", "properties": { "billing_address": {"type": "number"} }, "dependencies": { "credit_card": { "properties": { "billing_address": {"type": "number"} } } } } obj = JSONSchemaValidator() obj.validate(v1, 'first')
def test_different_types_given_and_guessed_enum(): """Tets that having type given in schema and different type guessed from enum fails.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "resource_type": { "type": "integer", "enum": [ "Text", "Image" ] } } } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_no_conflict_in_collection_pass(): """Test that having no conflict between elements of a collection passes.""" v1 = { "type": "object", "oneOf": [ { "type": "object", "properties": { "field_A": {"type": "string"} } }, { "type": "object", "properties": { "field_B": {"type": "integer"} } } ] } obj = JSONSchemaValidator() obj.validate(v1, 'first')
def test_different_types_inside_enum(): """Test that having different types inside enum fails.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "resource_type": { "type": "array", "items": { "enum": [ "Text", 42 ] } } } } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_no_conflicting_fields(): """Test that having no conflict between fields inside schema passes.""" v1 = { "type": "object", "properties": { "root_field": { "type": "array", "items": { "type": "object", "properties": { "sub_field": { "type": "string" } } } } } } obj = JSONSchemaValidator() obj.validate(v1, 'first')
def test_none_types_pass(): """Test that having a field with no type passes. FIXME: Later we will want to add an optional failure for unknown types. """ v1 = { "type": "object", "properties": { "credit_card": { "type": "number" } }, "dependencies": { # 'billing_address' is only referenced here without defining type # thus we save it as 'None' "credit_card": ["billing_address"] } } obj = JSONSchemaValidator() obj.validate(v1, 'first')
def test_none_types_pass(): """Test that having a field with no type passes. FIXME: Later we will want to add an optional failure for unknown types. """ v1 = { "type": "object", "properties": { "credit_card": {"type": "number"} }, "dependencies": { # 'billing_address' is only referenced here without defining type # thus we save it as 'None' "credit_card": ["billing_address"] } } obj = JSONSchemaValidator() obj.validate(v1, 'first')
def test_conflict_in_dependencies(): """Test that having conflict between elements in dependencies fails.""" v1 = { "type": "object", "properties": { "credit_card": {"type": "number"}, "billing_address": {"type": "number"} }, "dependencies": { "credit_card": { "properties": { "billing_address": {"type": "string"} } } } } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_new_field_in_dependencies_passes(): """Test that having a new field in dependencies fails.""" v1 = { "type": "object", "properties": { "billing_address": { "type": "number" } }, "dependencies": { "credit_card": { "properties": { "billing_address": { "type": "number" } } } } } obj = JSONSchemaValidator() obj.validate(v1, 'first')
def validate_json_schema(new_json_schema, prev_schemas): """Check that a JSON Schema is valid.abs A JSON Schema is valid if it matches its "$schema" and it is backward compatible with its previous versions. Args: new_json_schema: json_schema to be created. prev_schemas: list of previous versions of a schema. """ if '$schema' not in new_json_schema: raise InvalidJSONSchemaError('Missing "$schema" field in JSON Schema') if new_json_schema['$schema'] != 'http://json-schema.org/draft-04/schema#': # FIXME: later we should accept other json-schema versions too # but we have to make sure that the root-schema, block-schema and # community-schema are compatible raise InvalidJSONSchemaError( '"$schema" field can only be ' '"http://json-schema.org/draft-04/schema#"') schema_validator = JSONSchemaValidator( resolver_factory=lambda *args, **kwargs: current_app.extensions[ 'invenio-records'].ref_resolver_cls.from_schema(new_json_schema)) for prev_schema in prev_schemas: schema_validator.validate(json.loads(prev_schema), 'prevs') schema_validator.validate(new_json_schema, 'current schema') try: super_schema = resolve_json(new_json_schema['$schema']) except URLError as e: raise InvalidJSONSchemaError('Invalid "$schema" URL.') from e jsonschema.validate(new_json_schema, super_schema)
def test_conflicting_fields(): """Test that having a conflict between fields inside schema fails.""" v1 = { "type": "object", "properties": { "properties": { "type": "string" }, "type": { "type": "string" }, "items": { "type": "array", "items": { "type": "string" } }, "dependencies": { "properties": { "dependencies": { "type": "integer" } }, "dependencies": { "dependencies": { "type": "object", "properties": { "dependencies": { "type": "string" } } } } } } } obj = JSONSchemaValidator() with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v1, 'first')
def test_null_type_repeated_fields(): """Test that having already used field using it with type null fails.""" v1 = { "type": "object", "properties": { "abc": { "type": "string" } } } v2 = { "type": "object", "properties": { "abc": { "type": "null" } } } obj = JSONSchemaValidator() obj.validate(v1, 'first') with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v2, 'second')
def test_no_conflict_between_schemas_pass(): """Test that having two correct schemas and no conflict between them passes.""" v1 = {"type": "object", "properties": {"field_A": {"type": "string"}}} v2 = {"type": "object", "properties": {"field_B": {"type": "string"}}} obj = JSONSchemaValidator() obj.validate(v1, 'first') obj.validate(v2, 'second')
def test_conflict_between_schemas(): """Test that having correct schemas and conflict between them fails.""" v1 = {"type": "object", "properties": {"field_A": {"type": "string"}}} v2 = {"type": "object", "properties": {"field_A": {"type": "number"}}} obj = JSONSchemaValidator() obj.validate(v1, 'first') with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v2, 'second')
def test_null_type_repeated_fields(): """Test that having already used field using it with type null fails.""" v1 = {"type": "object", "properties": {"abc": {"type": "string"}}} v2 = {"type": "object", "properties": {"abc": {"type": "null"}}} obj = JSONSchemaValidator() obj.validate(v1, 'first') with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v2, 'second')
def validate_json_schema(new_json_schema, prev_schemas): """Check that a JSON Schema is valid.abs A JSON Schema is valid if it matches its "$schema" and it is backward compatible with its previous versions. Args: new_json_schema: json_schema to be created. prev_schemas: list of previous versions of a schema. """ if '$schema' not in new_json_schema: raise InvalidJSONSchemaError('Missing "$schema" field in JSON Schema') if new_json_schema['$schema'] != 'http://json-schema.org/draft-04/schema#': # FIXME: later we should accept other json-schema versions too # but we have to make sure that the root-schema, block-schema and # community-schema are compatible raise InvalidJSONSchemaError( '"$schema" field can only be ' '"http://json-schema.org/draft-04/schema#"') schema_validator = JSONSchemaValidator( resolver_factory=lambda *args, **kwargs: current_app.extensions[ 'invenio-records' ].ref_resolver_cls.from_schema(new_json_schema) ) for prev_schema in prev_schemas: schema_validator.validate(json.loads(prev_schema), 'prevs') schema_validator.validate(new_json_schema, 'current schema') try: super_schema = resolve_json(new_json_schema['$schema']) except URLError as e: raise InvalidJSONSchemaError('Invalid "$schema" URL.') from e jsonschema.validate(new_json_schema, super_schema)
def validate_json_schema(new_json_schema, prev_schemas): """Check that a JSON Schema is valid.abs A JSON Schema is valid if it matches its "$schema" and it is backward compatible with its previous versions. Args: new_json_schema: json_schema to be created. prev_schemas: list of previous versions of a schema. """ def verify_required_fields(json_schema): """Verify that required fields exist in a schema definition Recursively check existence of all required fields per (sub)field of type 'object' Prerequisites: - If current structure has a field 'type' valued 'object': and has a field 'properties' on the same level and has a field 'required' on the same level Args: json_schema: the (partial) json_schema to be verified. """ if json_schema.get("type", "") != "object" or json_schema.get( "required", None) is None: pass else: # check for missing fields in 'properties' that are given in 'required' field missing = set(json_schema["required"]) - set( json_schema["properties"].keys()) if len(missing) > 0: raise MissingRequiredFieldSchemaError( "Missing required fields in schema properties definition.") for k, v in json_schema["properties"].items(): # any objects if v.get("type", None) == "object": js = v # arrays of objects elif v.get("type", None) == "array" and v.get("items", {}).get( "type", None) == "object": js = v["items"] else: continue verify_required_fields(js) def verify_presentation_fields(json_schema): """Verify that presentation fields exist in a schema definition Check existence of all presentation fields per (sub)field of type 'object' Prerequisites: - A 'b2share' with 'presentation' field must be present in JSON schema definition Args: json_schema: the json_schema to be verified. """ for section, fields in json_schema.get("b2share", {}).get("presentation", {}).items(): missing = set(fields) - set(json_schema["properties"]) if len(missing) > 0: raise MissingPresentationFieldSchemaError( "Missing fields in schema presentation definition.") if '$schema' not in new_json_schema: raise InvalidJSONSchemaError('Missing "$schema" field in JSON Schema') if new_json_schema['$schema'] != 'http://json-schema.org/draft-04/schema#': # FIXME: later we should accept other json-schema versions too # but we have to make sure that the root-schema, block-schema and # community-schema are compatible raise InvalidJSONSchemaError( '"$schema" field can only be ' '"http://json-schema.org/draft-04/schema#"') schema_validator = JSONSchemaValidator( resolver_factory=lambda *args, **kwargs: current_app.extensions[ 'invenio-records'].ref_resolver_cls.from_schema(new_json_schema)) for prev_schema in prev_schemas: schema_validator.validate(json.loads(prev_schema), 'prevs') schema_validator.validate(new_json_schema, 'current schema') try: super_schema = resolve_json(new_json_schema['$schema']) except URLError as e: raise InvalidJSONSchemaError('Invalid "$schema" URL.') from e jsonschema.validate(new_json_schema, super_schema) # verify that required fields are defined in schema properties verify_required_fields(new_json_schema) # verify that presentation fields are defined in schema properties verify_presentation_fields(new_json_schema)
def test_ref_conflict_between_schemas(): """Test that having a conflict between schemas with references passes.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "definitions": { "address": { "type": "object", "properties": { "street_address": {"type": "string"} } } }, "type": "object", "properties": { "billing_address": {"$ref": "#/definitions/address"}, "shipping_address": { "allOf": [ {"$ref": "#/definitions/address"}, { "properties": { "type": {"enum": ["residential", "business"]} } } ] } } } v2 = { "$schema": "http://json-schema.org/draft-04/schema#", "definitions": { "address": { "type": "object", "properties": { "street_address": {"type": "differenttype"} } } }, "type": "object", "properties": { "billing_address": {"$ref": "#/definitions/address"}, "shipping_address": { "allOf": [ {"$ref": "#/definitions/address"}, { "properties": { "type": {"enum": ["residential", "business"]} } } ] } } } obj = JSONSchemaValidator() obj.validate(v1, 'first') with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v2, 'second')
def test_ref_conflict_between_schemas(): """Test that having a conflict between schemas with references passes.""" v1 = { "$schema": "http://json-schema.org/draft-04/schema#", "definitions": { "address": { "type": "object", "properties": { "street_address": { "type": "string" } } } }, "type": "object", "properties": { "billing_address": { "$ref": "#/definitions/address" }, "shipping_address": { "allOf": [{ "$ref": "#/definitions/address" }, { "properties": { "type": { "enum": ["residential", "business"] } } }] } } } v2 = { "$schema": "http://json-schema.org/draft-04/schema#", "definitions": { "address": { "type": "object", "properties": { "street_address": { "type": "differenttype" } } } }, "type": "object", "properties": { "billing_address": { "$ref": "#/definitions/address" }, "shipping_address": { "allOf": [{ "$ref": "#/definitions/address" }, { "properties": { "type": { "enum": ["residential", "business"] } } }] } } } obj = JSONSchemaValidator() obj.validate(v1, 'first') with pytest.raises(JSONSchemaCompatibilityError): obj.validate(v2, 'second')