summaryrefslogtreecommitdiff
path: root/tests/test_core.py
diff options
context:
space:
mode:
authorJohan Fleury <jfleury@arcaik.net>2018-10-08 12:58:12 -0400
committerJohan Fleury <jfleury@arcaik.net>2018-10-08 12:58:12 -0400
commit735c78d3ecb695dd16cb37879880f522c4b29c72 (patch)
tree8bfb2ecbd0f03730efa1540cc65d8490b67e8ddd /tests/test_core.py
Import upstream version 1.7.0
Diffstat (limited to 'tests/test_core.py')
-rw-r--r--tests/test_core.py572
1 files changed, 572 insertions, 0 deletions
diff --git a/tests/test_core.py b/tests/test_core.py
new file mode 100644
index 0000000..6fcd81e
--- /dev/null
+++ b/tests/test_core.py
@@ -0,0 +1,572 @@
+# -*- coding: utf-8 -*-
+
+""" Unit test for pyKwalify - Core """
+
+# python std lib
+import os
+
+# pykwalify imports
+import pykwalify
+from pykwalify.core import Core
+from pykwalify.errors import SchemaError, CoreError
+
+# 3rd party imports
+import pytest
+from pykwalify.compat import yaml
+from testfixtures import compare
+
+
+class TestCore(object):
+
+ def setUp(self):
+ pykwalify.partial_schemas = {}
+
+ def f(self, *args):
+ return os.path.join(os.path.dirname(os.path.realpath(__file__)), "files", *args)
+
+ def test_create_empty_core_object(self, tmpdir):
+ """
+ If createing a core object without any source or schema file an exception should be raised.
+ """
+ with pytest.raises(CoreError) as ex:
+ Core()
+ assert "No source file/data was loaded" in str(ex.value)
+
+ # To trigger schema exception we must pass in a source file
+ source_f = tmpdir.join("bar.json")
+ source_f.write("3.14159")
+
+ with pytest.raises(CoreError) as ex:
+ Core(source_file=str(source_f))
+ assert "No schema file/data was loaded" in str(ex.value)
+
+ def test_load_non_existing_file(self):
+ file_to_load = "/tmp/foo/bar/barfoo"
+ assert not os.path.exists(file_to_load), "Following file cannot exists on your system while running these tests : {0}".format(file_to_load)
+ with pytest.raises(CoreError) as ex:
+ Core(source_file=file_to_load)
+ assert "Provided source_file do not exists on disk" in str(ex.value)
+
+ def test_load_non_existsing_schema_file(self):
+ """
+ Exception should be raised if the specefied schema file do not exists on disk.
+ """
+ file_to_load = "/tmp/foo/bar/barfoo"
+ assert not os.path.exists(file_to_load), "Following file cannot exists on your system while running these tests : {0}".format(file_to_load)
+ with pytest.raises(CoreError) as ex:
+ Core(schema_files=[file_to_load])
+ assert "Provided source_file do not exists on disk" in str(ex.value)
+
+ def test_load_wrong_schema_files_type(self):
+ """
+ It should only be possible to send in a list type as 'schema_files' object
+ """
+ with pytest.raises(CoreError) as ex:
+ Core(source_file=None, schema_files={})
+ assert "schema_files must be of list type" in str(ex.value)
+
+ def test_load_json_file(self, tmpdir):
+ """
+ Load source & schema files that has json file ending.
+ """
+ source_f = tmpdir.join("bar.json")
+ source_f.write("3.14159")
+
+ schema_f = tmpdir.join("foo.json")
+ schema_f.write('{"type": "float"}')
+
+ Core(source_file=str(source_f), schema_files=[str(schema_f)])
+
+ # TODO: Try to load a non existing json file
+
+ def test_load_yaml_files(self, tmpdir):
+ """
+ Load source & schema files that has yaml file ending.
+ """
+ source_f = tmpdir.join("foo.yaml")
+ source_f.write("3.14159")
+
+ schema_f = tmpdir.join("bar.yaml")
+ schema_f.write("type: float")
+
+ Core(source_file=str(source_f), schema_files=[str(schema_f)])
+
+ def test_load_unsupported_format(self, tmpdir):
+ """
+ Try to load some fileending that is not supported.
+ Currently XML is not supported.
+ """
+ source_f = tmpdir.join("foo.xml")
+ source_f.write("<foo>bar</foo>")
+
+ schema_f = tmpdir.join("bar.xml")
+ schema_f.write("<foo>bar</foo>")
+
+ with pytest.raises(CoreError) as ex:
+ Core(source_file=str(source_f))
+ assert "Unable to load source_file. Unknown file format of specified file path" in str(ex.value)
+
+ with pytest.raises(CoreError) as ex:
+ Core(schema_files=[str(schema_f)])
+ assert "Unknown file format. Supported file endings is" in str(ex.value)
+
+ def test_load_empty_json_file(self, tmpdir):
+ """
+ Loading an empty json files should raise an exception
+ """
+ # Load empty source file
+ source_f = tmpdir.join("foo.json")
+ source_f.write("")
+
+ schema_f = tmpdir.join("bar.json")
+ schema_f.write("")
+
+ with pytest.raises(ValueError) as ex:
+ Core(source_file=str(source_f), schema_files=[str(schema_f)])
+ # Python 2.7 and Python 3.5 JSON parsers return different exception
+ # strings for the same data file, so check for both errors strings.
+ assert ("No JSON object could be decoded" in str(ex.value) or
+ "Expecting value:" in str(ex.value))
+
+ # Load empty schema files
+ source_f = tmpdir.join("foo.json")
+ source_f.write("3.14159")
+
+ schema_f = tmpdir.join("bar.json")
+ schema_f.write("")
+
+ with pytest.raises(ValueError) as ex:
+ Core(source_file=str(source_f), schema_files=[str(schema_f)])
+ assert ("No JSON object could be decoded" in str(ex.value) or
+ "Expecting value:" in str(ex.value))
+
+ def test_load_empty_yaml_file(self, tmpdir):
+ """
+ Loading empty yaml files should raise an exception
+ """
+ # Load empty source file
+ source_f = tmpdir.join("foo.yaml")
+ source_f.write("")
+
+ schema_f = tmpdir.join("bar.yaml")
+ schema_f.write("")
+
+ # TODO: This is abit buggy because wrong exception is raised...
+ with pytest.raises(CoreError) as ex:
+ Core(source_file=str(source_f), schema_files=[str(schema_f)])
+ # assert "Unable to load any data from source yaml file" in str(ex.value)
+
+ # Load empty schema files
+ source_f = tmpdir.join("foo.yaml")
+ source_f.write("3.14159")
+
+ schema_f = tmpdir.join("bar.yaml")
+ schema_f.write("")
+
+ with pytest.raises(CoreError) as ex:
+ Core(source_file=str(source_f), schema_files=[str(schema_f)])
+ assert "No data loaded from file" in str(ex.value)
+
+ def test_validation_error_but_not_raise_exception(self):
+ """
+ Test that if 'raise_exception=False' when validating that no exception is raised.
+
+ Currently file 2a.yaml & 2b.yaml is designed to cause exception.
+ """
+ c = Core(source_file=self.f("cli", "2a.yaml"), schema_files=[self.f("cli", "2b.yaml")])
+ c.validate(raise_exception=False)
+
+ assert c.validation_errors == [
+ "Value '1' is not of type 'str'. Path: '/0'", "Value '2' is not of type 'str'. Path: '/1'", "Value '3' is not of type 'str'. Path: '/2'"
+ ]
+
+ # TODO: Fix this issue...
+ # assert ('pykwalify.core', 'ERROR', 'Errors found but will not raise exception...') in l.actual()
+
+ def test_core_data_mode(self):
+ Core(source_data=3.14159, schema_data={"type": "number"}).validate()
+ Core(source_data="1e-06", schema_data={"type": "float"}).validate()
+ Core(source_data=3.14159, schema_data={"type": "float"}).validate()
+ Core(source_data=3, schema_data={"type": "float"}).validate()
+ Core(source_data=3, schema_data={"type": "int"}).validate()
+ Core(source_data=True, schema_data={"type": "bool"}).validate()
+ Core(source_data="foobar", schema_data={"type": "str"}).validate()
+ Core(source_data="foobar", schema_data={"type": "text"}).validate()
+ Core(source_data="foobar", schema_data={"type": "any"}).validate()
+
+ # Test that 'any' allows types that is not even implemented
+ def foo():
+ pass
+ Core(source_data=foo, schema_data={"type": "any"}).validate()
+ Core(source_data=lambda x: x, schema_data={"type": "any"}).validate()
+
+ with pytest.raises(SchemaError):
+ Core(source_data="1z-06", schema_data={"type": "float"}).validate()
+
+ with pytest.raises(SchemaError):
+ Core(source_data="abc", schema_data={"type": "number"}).validate()
+
+ with pytest.raises(SchemaError):
+ Core(source_data=3.14159, schema_data={"type": "int"}).validate()
+
+ with pytest.raises(SchemaError):
+ Core(source_data=1337, schema_data={"type": "bool"}).validate()
+
+ with pytest.raises(SchemaError):
+ Core(source_data=1, schema_data={"type": "str"}).validate()
+
+ with pytest.raises(SchemaError):
+ Core(source_data=True, schema_data={"type": "text"}).validate()
+
+ def test_multi_file_support(self):
+ """
+ This should test that multiple files is supported correctly
+ """
+ pass_tests = [
+ # Test that include directive can be used at top level of the schema
+ (
+ [
+ self.f("partial_schemas", "1s-schema.yaml"),
+ self.f("partial_schemas", "1s-partials.yaml"),
+ ],
+ self.f("partial_schemas", "1s-data.yaml"),
+ {
+ 'sequence': [{'include': 'fooone'}],
+ 'type': 'seq',
+ }
+ ),
+ # # This test that include directive works inside sequence
+ # ([self.f("33a.yaml"), self.f("33b.yaml")], self.f("33c.yaml"), {'sequence': [{'include': 'fooone'}], 'type': 'seq'}),
+ # This test recursive schemas
+ (
+ [
+ self.f("partial_schemas", "2s-schema.yaml"),
+ self.f("partial_schemas", "2s-partials.yaml"),
+ ],
+ self.f("partial_schemas", "2s-data.yaml"),
+ {
+ 'sequence': [{'include': 'fooone'}],
+ 'type': 'seq',
+ }
+ ),
+ # This tests that you can include a partial schema alongside other rules in a map
+ (
+ [
+ self.f("partial_schemas", "7s-schema.yaml"),
+ ],
+ self.f("partial_schemas", "7s-data.yaml"),
+ {
+ 'type': 'map',
+ 'mapping': {
+ 'foo': {
+ 'type': 'str',
+ 'required': True
+ },
+ 'bar': {
+ 'include': 'bar'
+ }
+ }
+ }
+ )
+ ]
+
+ failing_tests = [
+ # Test include inside partial schema
+ (
+ [
+ self.f("partial_schemas", "1f-schema.yaml"),
+ self.f("partial_schemas", "1f-partials.yaml")
+ ],
+ self.f("partial_schemas", "1f-data.yaml"),
+ SchemaError,
+ ["Cannot find partial schema with name 'fooonez'. Existing partial schemas: 'bar, fooone, foothree, footwo'. Path: '/0'"]
+ ),
+ (
+ [
+ self.f('partial_schemas', '2f-schema.yaml')
+ ],
+ self.f('partial_schemas', '2f-data.yaml'),
+ SchemaError,
+ ["Value 'True' is not of type 'str'. Path: '/0'"]
+ ),
+ (
+ [
+ self.f('partial_schemas', '3f-schema.yaml')
+ ],
+ self.f('partial_schemas', '3f-data.yaml'),
+ SchemaError,
+ ["Value 'True' is not of type 'str'. Path: ''"]
+ ),
+ (
+ [
+ self.f('partial_schemas', '4f-schema.yaml')
+ ],
+ self.f('partial_schemas', '4f-data.yaml'),
+ SchemaError,
+ ["Value 'True' is not of type 'str'. Path: '/0/foo/0/bar'"]
+ ),
+ (
+ [
+ self.f('partial_schemas', '5f-schema.yaml')
+ ],
+ self.f('partial_schemas', '5f-data.yaml'),
+ SchemaError,
+ ["Value 'True' is not of type 'str'. Path: '/0/0/0/0'"]
+ ),
+ (
+ [
+ self.f('partial_schemas', '6f-schema.yaml')
+ ],
+ self.f('partial_schemas', '6f-data.yaml'),
+ SchemaError,
+ ["Value 'True' is not of type 'str'. Path: '/foo/bar/qwe/ewq'"]
+ )
+ ]
+
+ for passing_test in pass_tests:
+ try:
+ c = Core(source_file=passing_test[1], schema_files=passing_test[0])
+ c.validate()
+ compare(c.validation_errors, [], prefix="No validation errors should exist...")
+ except Exception as e:
+ print("ERROR RUNNING FILE: {0} : {1}".format(passing_test[0], passing_test[1]))
+ raise e
+
+ # This serve as an extra schema validation that tests more complex structures then testrule.py do
+ compare(c.root_rule.schema_str, passing_test[2], prefix="Parsed rules is not correct, something have changed...")
+
+ for failing_test in failing_tests:
+ with pytest.raises(failing_test[2], message="Test files: {0} : {1}".format(", ".join(failing_test[0]), failing_test[1])):
+ c = Core(schema_files=failing_test[0], source_file=failing_test[1])
+ c.validate()
+
+ if not c.validation_errors:
+ raise AssertionError("No validation_errors was raised...")
+
+ compare(
+ sorted(c.validation_errors),
+ sorted(failing_test[3]),
+ prefix="Wrong validation errors when parsing files : {0} : {1}".format(
+ failing_test[0],
+ failing_test[1],
+ ),
+ )
+
+ def test_core_files(self):
+ # These tests should pass with no exception raised
+ pass_tests = [
+ # All tests for keyword assert
+ "test_assert.yaml",
+ # All tests for keyword default
+ "test_default.yaml",
+ # All tests for keyword desc
+ "test_desc.yaml",
+ # All tests for keyword enum
+ "test_enum.yaml",
+ # All tests for keyword example
+ "test_example.yaml",
+ # All tests for keyword extensions
+ "test_extensions.yaml",
+ # All tests for keyword func
+ "test_func.yaml",
+ # All tests for keyword ident
+ "test_ident.yaml",
+ # All tests for keyword include
+ "test_include.yaml",
+ # All tests for keyword length
+ "test_length.yaml",
+ # All tests for keyword mapping
+ "test_mapping.yaml",
+ # All tests for keyword matching
+ "test_matching.yaml",
+ # All tests for keyword name
+ "test_name.yaml",
+ # All tests for keyword nullable
+ "test_nullable.yaml",
+ # All tests for keyword pattern
+ "test_pattern.yaml",
+ # All tests for keyword range
+ "test_range.yaml",
+ # All tests for keyword required
+ "test_required.yaml",
+ # All tests for keyword schema
+ "test_schema.yaml",
+ # All tests for keyword sequence
+ "test_sequence.yaml",
+ # All tests for keyword unique
+ "test_unique.yaml",
+ # All tests for keyword version
+ "test_version.yaml",
+
+ # All test cases for Multiple sequence checks
+ "test_sequence_multi.yaml",
+ # All test cases for merging
+ "test_merge.yaml",
+ # All test cases for yaml anchors
+ "test_anchor.yaml",
+
+ # All tests for TYPE: any
+ "test_type_any.yaml",
+ # All tests for TYPE: bool
+ "test_type_bool.yaml",
+ # All tests for TYPE: date
+ "test_type_date.yaml",
+ # All tests for TYPE: enum
+ "test_type_enum.yaml",
+ # All tests for TYPE: float
+ "test_type_float.yaml",
+ # All tests for TYPE: int
+ "test_type_int.yaml",
+ # All tests for TYPE: map
+ "test_type_map.yaml",
+ # All tests for TYPE: none
+ "test_type_none.yaml",
+ # All tests for TYPE: number
+ "test_type_number.yaml",
+ # All tests for TYPE: scalar
+ "test_type_scalar.yaml",
+ # All tests for TYPE: seq
+ "test_type_seq.yaml",
+ # All tests for TYPE: str
+ "test_type_str.yaml",
+ # All tests for TYPE: symbol
+ "test_type_symbol.yaml",
+ # All tests for TYPE: text
+ "test_type_text.yaml",
+ # All tests for TYPE: timestamp
+ "test_type_timestamp.yaml",
+ ]
+
+ _fail_tests = [
+ # All tests for keyword assert
+ ("test_assert.yaml", SchemaError),
+ # All tests for keyword default
+ ("test_default.yaml", SchemaError),
+ # All tests for keyword desc
+ ("test_desc.yaml", SchemaError),
+ # All tests for keyword enum
+ ("test_enum.yaml", SchemaError),
+ # All tests for keyword example
+ ("test_example.yaml", SchemaError),
+ # All tests for keyword extensions
+ ("test_extensions.yaml", SchemaError),
+ # All tests for keyword func
+ ("test_func.yaml", SchemaError),
+ # All tests for keyword ident
+ ("test_ident.yaml", SchemaError),
+ # All tests for keyword include
+ ("test_include.yaml", SchemaError),
+ # All tests for keyword length
+ ("test_length.yaml", SchemaError),
+ # All tests for keyword mapping
+ ("test_mapping.yaml", SchemaError),
+ # All tests for keyword matching
+ ("test_matching.yaml", SchemaError),
+ # All tests for keyword name
+ ("test_name.yaml", SchemaError),
+ # All tests for keyword nullable
+ ("test_nullable.yaml", SchemaError),
+ # All tests for keyword pattern
+ ("test_pattern.yaml", SchemaError),
+ # All tests for keyword range
+ ("test_range.yaml", SchemaError),
+ # All tests for keyword required
+ ("test_required.yaml", SchemaError),
+ # All tests for keyword schema
+ ("test_schema.yaml", SchemaError),
+ # All tests for keyword sequence
+ ("test_sequence.yaml", SchemaError),
+ # All tests for keyword unique
+ ("test_unique.yaml", SchemaError),
+ # All tests for keyword version
+ ("test_version.yaml", SchemaError),
+
+ # All test cases for Multiple sequence checks
+ ("test_sequence_multi.yaml", SchemaError),
+ # All test cases for merging
+ ("test_merge.yaml", SchemaError),
+ # All test cases for yaml anchors
+ ("test_anchor.yaml", SchemaError),
+
+ # All tests for TYPE: any
+ ("test_type_any.yaml", SchemaError),
+ # All tests for TYPE: bool
+ ("test_type_bool.yaml", SchemaError),
+ # All tests for TYPE: date
+ ("test_type_date.yaml", SchemaError),
+ # All tests for TYPE: float
+ ("test_type_float.yaml", SchemaError),
+ # All tests for TYPE: int
+ ("test_type_int.yaml", SchemaError),
+ # All tests for TYPE: map
+ ("test_type_map.yaml", SchemaError),
+ # All tests for TYPE: none
+ ("test_type_none.yaml", SchemaError),
+ # All tests for TYPE: number
+ ("test_type_number.yaml", SchemaError),
+ # All tests for TYPE: scalar
+ ("test_type_scalar.yaml", SchemaError),
+ # All tests for TYPE: seq
+ ("test_type_seq.yaml", SchemaError),
+ # All tests for TYPE: str
+ ("test_type_str.yaml", SchemaError),
+ # All tests for TYPE: symbol
+ ("test_type_symbol.yaml", SchemaError),
+ # All tests for TYPE: text
+ ("test_type_text.yaml", SchemaError),
+ # All tests for TYPE: timestamp
+ ("test_type_timestamp.yaml", SchemaError),
+ ]
+
+ # Add override magic to make it easier to test a specific file
+ if "S" in os.environ:
+ pass_tests = [os.environ["S"]]
+ _fail_tests = []
+ elif "F" in os.environ:
+ pass_tests = []
+ _fail_tests = [(os.environ["F"], SchemaError)]
+
+ for passing_test_file in pass_tests:
+ f = self.f(os.path.join("success", passing_test_file))
+ with open(f, "r") as stream:
+ yaml_data = yaml.safe_load_all(stream)
+
+ for document_index, document in enumerate(yaml_data):
+ data = document["data"]
+ schema = document["schema"]
+
+ try:
+ print("Running test files: {0}".format(f))
+ c = Core(source_data=data, schema_data=schema, strict_rule_validation=True, allow_assertions=True)
+ c.validate()
+ compare(c.validation_errors, [], prefix="No validation errors should exist...")
+ except Exception as e:
+ print("ERROR RUNNING FILES: {0} : {1}:{2}".format(f, document_index, document.get('name', 'UNKNOWN')))
+ raise e
+
+ # This serve as an extra schema validation that tests more complex structures then testrule.py do
+ compare(c.root_rule.schema_str, schema, prefix="Parsed rules is not correct, something have changed... files : {0} : {1}".format(f, document_index))
+
+ for failing_test, exception_type in _fail_tests:
+ f = self.f(os.path.join("fail", failing_test))
+ with open(f, "r") as stream:
+ yaml_data = yaml.safe_load_all(stream)
+
+ for document_index, document in enumerate(yaml_data):
+ data = document["data"]
+ schema = document["schema"]
+ errors = document.get("errors", [])
+
+ try:
+ print("Running test files: {0}".format(f))
+ c = Core(source_data=data, schema_data=schema, strict_rule_validation=True, allow_assertions=True)
+ c.validate()
+ except exception_type as e:
+ pass
+ else:
+ print("ERROR RUNNING FILES: {0} : {1}:{2}".format(f, document_index, document.get('name', 'UNKNOWN')))
+ raise AssertionError("Exception {0} not raised as expected... FILES: {1} : {2} : {3}:{4}".format(
+ exception_type, exception_type, failing_test, document_index, document.get('name', 'UNKNOWN')))
+
+ compare(sorted(c.validation_errors), sorted(errors), prefix="Wrong validation errors when parsing files : {0} : {1} : {2}".format(
+ f, document_index, document.get('name', 'UNKNOWN')))