From 735c78d3ecb695dd16cb37879880f522c4b29c72 Mon Sep 17 00:00:00 2001 From: Johan Fleury Date: Mon, 8 Oct 2018 12:58:12 -0400 Subject: Import upstream version 1.7.0 --- tests/__init__.py | 3 + tests/files/README.md | 34 ++ tests/files/cli/1a.yaml | 3 + tests/files/cli/1b.yaml | 3 + tests/files/cli/2a.yaml | 3 + tests/files/cli/2b.yaml | 3 + tests/files/fail/test_anchor.yaml | 95 +++++ tests/files/fail/test_assert.yaml | 34 ++ tests/files/fail/test_default.yaml | 21 + tests/files/fail/test_desc.yaml | 1 + tests/files/fail/test_enum.yaml | 16 + tests/files/fail/test_example.yaml | 1 + tests/files/fail/test_extensions.yaml | 0 tests/files/fail/test_func.yaml | 0 tests/files/fail/test_ident.yaml | 23 ++ tests/files/fail/test_include.yaml | 0 tests/files/fail/test_length.yaml | 113 ++++++ tests/files/fail/test_mapping.yaml | 186 +++++++++ tests/files/fail/test_matching.yaml | 0 tests/files/fail/test_merge.yaml | 37 ++ tests/files/fail/test_name.yaml | 0 tests/files/fail/test_nullable.yaml | 19 + tests/files/fail/test_pattern.yaml | 26 ++ tests/files/fail/test_range.yaml | 219 ++++++++++ tests/files/fail/test_required.yaml | 19 + tests/files/fail/test_schema.yaml | 0 tests/files/fail/test_sequence.yaml | 71 ++++ tests/files/fail/test_sequence_multi.yaml | 31 ++ tests/files/fail/test_type_any.yaml | 1 + tests/files/fail/test_type_bool.yaml | 52 +++ tests/files/fail/test_type_date.yaml | 83 ++++ tests/files/fail/test_type_float.yaml | 56 +++ tests/files/fail/test_type_int.yaml | 45 +++ tests/files/fail/test_type_map.yaml | 46 +++ tests/files/fail/test_type_none.yaml | 54 +++ tests/files/fail/test_type_number.yaml | 83 ++++ tests/files/fail/test_type_scalar.yaml | 0 tests/files/fail/test_type_seq.yaml | 47 +++ tests/files/fail/test_type_str.yaml | 73 ++++ tests/files/fail/test_type_symbol.yaml | 0 tests/files/fail/test_type_text.yaml | 70 ++++ tests/files/fail/test_type_timestamp.yaml | 15 + tests/files/fail/test_unique.yaml | 109 +++++ tests/files/fail/test_version.yaml | 0 tests/files/partial_schemas/1f-data.yaml | 1 + tests/files/partial_schemas/1f-partials.yaml | 11 + tests/files/partial_schemas/1f-schema.yaml | 3 + tests/files/partial_schemas/1s-data.yaml | 1 + tests/files/partial_schemas/1s-partials.yaml | 11 + tests/files/partial_schemas/1s-schema.yaml | 3 + tests/files/partial_schemas/2f-data.yaml | 1 + tests/files/partial_schemas/2f-schema.yaml | 5 + tests/files/partial_schemas/2s-data.yaml | 3 + tests/files/partial_schemas/2s-partials.yaml | 16 + tests/files/partial_schemas/2s-schema.yaml | 3 + tests/files/partial_schemas/3f-data.yaml | 1 + tests/files/partial_schemas/3f-schema.yaml | 3 + tests/files/partial_schemas/4f-data.yaml | 2 + tests/files/partial_schemas/4f-schema.yaml | 20 + tests/files/partial_schemas/5f-data.yaml | 1 + tests/files/partial_schemas/5f-schema.yaml | 18 + tests/files/partial_schemas/6f-data.yaml | 4 + tests/files/partial_schemas/6f-schema.yaml | 22 ++ tests/files/partial_schemas/7s-data.yaml | 5 + tests/files/partial_schemas/7s-schema.yaml | 12 + tests/files/success/test_anchor.yaml | 92 +++++ tests/files/success/test_assert.yaml | 28 ++ tests/files/success/test_default.yaml | 0 tests/files/success/test_desc.yaml | 7 + tests/files/success/test_enum.yaml | 12 + tests/files/success/test_example.yaml | 5 + tests/files/success/test_extensions.yaml | 0 tests/files/success/test_func.yaml | 0 tests/files/success/test_ident.yaml | 22 ++ tests/files/success/test_include.yaml | 0 tests/files/success/test_length.yaml | 98 +++++ tests/files/success/test_mapping.yaml | 308 +++++++++++++++ tests/files/success/test_matching.yaml | 0 tests/files/success/test_merge.yaml | 36 ++ tests/files/success/test_name.yaml | 0 tests/files/success/test_nullable.yaml | 11 + tests/files/success/test_pattern.yaml | 18 + tests/files/success/test_range.yaml | 166 ++++++++ tests/files/success/test_required.yaml | 15 + tests/files/success/test_schema.yaml | 0 tests/files/success/test_sequence.yaml | 44 +++ tests/files/success/test_sequence_multi.yaml | 64 +++ tests/files/success/test_type_any.yaml | 27 ++ tests/files/success/test_type_bool.yaml | 39 ++ tests/files/success/test_type_date.yaml | 40 ++ tests/files/success/test_type_enum.yaml | 43 ++ tests/files/success/test_type_float.yaml | 43 ++ tests/files/success/test_type_int.yaml | 38 ++ tests/files/success/test_type_map.yaml | 39 ++ tests/files/success/test_type_none.yaml | 47 +++ tests/files/success/test_type_number.yaml | 76 ++++ tests/files/success/test_type_scalar.yaml | 76 ++++ tests/files/success/test_type_seq.yaml | 36 ++ tests/files/success/test_type_str.yaml | 56 +++ tests/files/success/test_type_symbol.yaml | 0 tests/files/success/test_type_text.yaml | 75 ++++ tests/files/success/test_type_timestamp.yaml | 37 ++ tests/files/success/test_unique.yaml | 138 +++++++ tests/files/success/test_version.yaml | 5 + tests/files/unicode/1f.yaml | 13 + tests/files/unicode/1s.yaml | 10 + tests/files/unicode/3f.yaml | 10 + tests/files/unicode/3s.yaml | 7 + tests/test_cli.py | 62 +++ tests/test_core.py | 572 +++++++++++++++++++++++++++ tests/test_core_methods.py | 308 +++++++++++++++ tests/test_exceptions.py | 29 ++ tests/test_helper.py | 33 ++ tests/test_rule.py | 399 +++++++++++++++++++ tests/test_types.py | 75 ++++ tests/test_unicode.py | 137 +++++++ 116 files changed, 5237 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/files/README.md create mode 100644 tests/files/cli/1a.yaml create mode 100644 tests/files/cli/1b.yaml create mode 100644 tests/files/cli/2a.yaml create mode 100644 tests/files/cli/2b.yaml create mode 100644 tests/files/fail/test_anchor.yaml create mode 100644 tests/files/fail/test_assert.yaml create mode 100644 tests/files/fail/test_default.yaml create mode 100644 tests/files/fail/test_desc.yaml create mode 100644 tests/files/fail/test_enum.yaml create mode 100644 tests/files/fail/test_example.yaml create mode 100644 tests/files/fail/test_extensions.yaml create mode 100644 tests/files/fail/test_func.yaml create mode 100644 tests/files/fail/test_ident.yaml create mode 100644 tests/files/fail/test_include.yaml create mode 100644 tests/files/fail/test_length.yaml create mode 100644 tests/files/fail/test_mapping.yaml create mode 100644 tests/files/fail/test_matching.yaml create mode 100644 tests/files/fail/test_merge.yaml create mode 100644 tests/files/fail/test_name.yaml create mode 100644 tests/files/fail/test_nullable.yaml create mode 100644 tests/files/fail/test_pattern.yaml create mode 100644 tests/files/fail/test_range.yaml create mode 100644 tests/files/fail/test_required.yaml create mode 100644 tests/files/fail/test_schema.yaml create mode 100644 tests/files/fail/test_sequence.yaml create mode 100644 tests/files/fail/test_sequence_multi.yaml create mode 100644 tests/files/fail/test_type_any.yaml create mode 100644 tests/files/fail/test_type_bool.yaml create mode 100644 tests/files/fail/test_type_date.yaml create mode 100644 tests/files/fail/test_type_float.yaml create mode 100644 tests/files/fail/test_type_int.yaml create mode 100644 tests/files/fail/test_type_map.yaml create mode 100644 tests/files/fail/test_type_none.yaml create mode 100644 tests/files/fail/test_type_number.yaml create mode 100644 tests/files/fail/test_type_scalar.yaml create mode 100644 tests/files/fail/test_type_seq.yaml create mode 100644 tests/files/fail/test_type_str.yaml create mode 100644 tests/files/fail/test_type_symbol.yaml create mode 100644 tests/files/fail/test_type_text.yaml create mode 100644 tests/files/fail/test_type_timestamp.yaml create mode 100644 tests/files/fail/test_unique.yaml create mode 100644 tests/files/fail/test_version.yaml create mode 100644 tests/files/partial_schemas/1f-data.yaml create mode 100644 tests/files/partial_schemas/1f-partials.yaml create mode 100644 tests/files/partial_schemas/1f-schema.yaml create mode 100644 tests/files/partial_schemas/1s-data.yaml create mode 100644 tests/files/partial_schemas/1s-partials.yaml create mode 100644 tests/files/partial_schemas/1s-schema.yaml create mode 100644 tests/files/partial_schemas/2f-data.yaml create mode 100644 tests/files/partial_schemas/2f-schema.yaml create mode 100644 tests/files/partial_schemas/2s-data.yaml create mode 100644 tests/files/partial_schemas/2s-partials.yaml create mode 100644 tests/files/partial_schemas/2s-schema.yaml create mode 100644 tests/files/partial_schemas/3f-data.yaml create mode 100644 tests/files/partial_schemas/3f-schema.yaml create mode 100644 tests/files/partial_schemas/4f-data.yaml create mode 100644 tests/files/partial_schemas/4f-schema.yaml create mode 100644 tests/files/partial_schemas/5f-data.yaml create mode 100644 tests/files/partial_schemas/5f-schema.yaml create mode 100644 tests/files/partial_schemas/6f-data.yaml create mode 100644 tests/files/partial_schemas/6f-schema.yaml create mode 100644 tests/files/partial_schemas/7s-data.yaml create mode 100644 tests/files/partial_schemas/7s-schema.yaml create mode 100644 tests/files/success/test_anchor.yaml create mode 100644 tests/files/success/test_assert.yaml create mode 100644 tests/files/success/test_default.yaml create mode 100644 tests/files/success/test_desc.yaml create mode 100644 tests/files/success/test_enum.yaml create mode 100644 tests/files/success/test_example.yaml create mode 100644 tests/files/success/test_extensions.yaml create mode 100644 tests/files/success/test_func.yaml create mode 100644 tests/files/success/test_ident.yaml create mode 100644 tests/files/success/test_include.yaml create mode 100644 tests/files/success/test_length.yaml create mode 100644 tests/files/success/test_mapping.yaml create mode 100644 tests/files/success/test_matching.yaml create mode 100644 tests/files/success/test_merge.yaml create mode 100644 tests/files/success/test_name.yaml create mode 100644 tests/files/success/test_nullable.yaml create mode 100644 tests/files/success/test_pattern.yaml create mode 100644 tests/files/success/test_range.yaml create mode 100644 tests/files/success/test_required.yaml create mode 100644 tests/files/success/test_schema.yaml create mode 100644 tests/files/success/test_sequence.yaml create mode 100644 tests/files/success/test_sequence_multi.yaml create mode 100644 tests/files/success/test_type_any.yaml create mode 100644 tests/files/success/test_type_bool.yaml create mode 100644 tests/files/success/test_type_date.yaml create mode 100644 tests/files/success/test_type_enum.yaml create mode 100644 tests/files/success/test_type_float.yaml create mode 100644 tests/files/success/test_type_int.yaml create mode 100644 tests/files/success/test_type_map.yaml create mode 100644 tests/files/success/test_type_none.yaml create mode 100644 tests/files/success/test_type_number.yaml create mode 100644 tests/files/success/test_type_scalar.yaml create mode 100644 tests/files/success/test_type_seq.yaml create mode 100644 tests/files/success/test_type_str.yaml create mode 100644 tests/files/success/test_type_symbol.yaml create mode 100644 tests/files/success/test_type_text.yaml create mode 100644 tests/files/success/test_type_timestamp.yaml create mode 100644 tests/files/success/test_unique.yaml create mode 100644 tests/files/success/test_version.yaml create mode 100644 tests/files/unicode/1f.yaml create mode 100644 tests/files/unicode/1s.yaml create mode 100644 tests/files/unicode/3f.yaml create mode 100644 tests/files/unicode/3s.yaml create mode 100644 tests/test_cli.py create mode 100644 tests/test_core.py create mode 100644 tests/test_core_methods.py create mode 100644 tests/test_exceptions.py create mode 100644 tests/test_helper.py create mode 100644 tests/test_rule.py create mode 100644 tests/test_types.py create mode 100644 tests/test_unicode.py (limited to 'tests') diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..3dbf9fa --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +""" pyKwalify validation framework """ diff --git a/tests/files/README.md b/tests/files/README.md new file mode 100644 index 0000000..3faa375 --- /dev/null +++ b/tests/files/README.md @@ -0,0 +1,34 @@ +# Test files + +Test files are divided up into 2 types of tests. They follow a naming schema that follows `(Number)(Type).yaml` Where number is just a ever increasing integer and Type is different depending on the test type. Each type of test should be counted seperatly. + +- Successfull tests. Type: 's' +- Failing tests. Type: 'f' + + + +# Successfull tests + +Files in `success` folder. + +Each file should contain a top level dict with the keys `data` and `schema` where the test data should exists. + + + +# Failing tests + +Files in `fail` folder. + +Each file should contain a top level dict with the keys `data`, `schema` and `errors` where the test data should exists. + + + +# cli tests + +Simple schema and data files that is used to test input of files via cli. + + + +# partial schemas + +Files used to test partial schema support. diff --git a/tests/files/cli/1a.yaml b/tests/files/cli/1a.yaml new file mode 100644 index 0000000..c34e2ee --- /dev/null +++ b/tests/files/cli/1a.yaml @@ -0,0 +1,3 @@ +- foo +- bar +- baz diff --git a/tests/files/cli/1b.yaml b/tests/files/cli/1b.yaml new file mode 100644 index 0000000..41f00aa --- /dev/null +++ b/tests/files/cli/1b.yaml @@ -0,0 +1,3 @@ +type: seq +sequence: + - type: str diff --git a/tests/files/cli/2a.yaml b/tests/files/cli/2a.yaml new file mode 100644 index 0000000..a0cd6f3 --- /dev/null +++ b/tests/files/cli/2a.yaml @@ -0,0 +1,3 @@ +- 1 +- 2 +- 3 diff --git a/tests/files/cli/2b.yaml b/tests/files/cli/2b.yaml new file mode 100644 index 0000000..41f00aa --- /dev/null +++ b/tests/files/cli/2b.yaml @@ -0,0 +1,3 @@ +type: seq +sequence: + - type: str diff --git a/tests/files/fail/test_anchor.yaml b/tests/files/fail/test_anchor.yaml new file mode 100644 index 0000000..dca97f5 --- /dev/null +++ b/tests/files/fail/test_anchor.yaml @@ -0,0 +1,95 @@ +--- +name: fail-anchor-1 +desc: schema with anchor +schema: + type: seq + required: true + sequence: + - type: map + required: true + mapping: + first-name: &name + type: str + required: true + family-name: *name +data: + - first-name: foo + last-name: Foo + - first-name: bar + family-name: 100 +errors: + - "Cannot find required key 'family-name'. Path: '/0'" + - "Key 'last-name' was not defined. Path: '/0'" + - "Value '100' is not of type 'str'. Path: '/1/family-name'" + ## Kwalify errors + # :required_nokey : 1:3:[/0] key 'family-name:' is required. + # :key_undefined : 2:3:[/0/last-name] key 'last-name:' is undefined. + # :type_unmatch : 4:3:[/1/family-name] '100': not a string. +--- +name: fail-anchor-2 +desc: schema with anchor 2 +schema: + type: map + required: true + mapping: + title: &name + type: str + required: true + address-book: + type: seq + required: true + sequence: + - type: map + mapping: + name: *name + email: + type: str + required: true +data: + title: my friends + address-book: + - name: 100 + email: foo@mail.com + - first-name: bar + email: bar@mail.com +errors: + - "Cannot find required key 'name'. Path: '/address-book/1'" + - "Key 'first-name' was not defined. Path: '/address-book/1'" + - "Value '100' is not of type 'str'. Path: '/address-book/0/name'" + ## Kwalify errors + # :type_unmatch : 3:5:[/address-book/0/name] '100': not a string. + # :required_nokey : 5:5:[/address-book/1] key 'name:' is required. + # :key_undefined : 5:5:[/address-book/1/first-name] key 'first-name:' is undefined. +# TODO: THIS TEST IS BROKEN BECUASE IT CAUSE INFINITE RECURSION IN PYTHON +# --- +# name: fail-anchor-3 +# desc: document with anchor +# schema: +# type: seq +# sequence: +# - &employee +# type: map +# mapping: +# name: +# type: str +# post: +# type: str +# enum: +# - exective +# - manager +# - clerk +# supervisor: *employee +# data: +# - &foo +# name: 100 +# post: exective +# supervisor: *foo +# - &bar +# name: foo +# post: worker +# supervisor: *foo +# errors: +# - '' +# ## Kwalify errors +# # :type_unmatch : 2:3:[/0/name] '100': not a string. +# # :enum_notexist : 7:3:[/1/post] 'worker': invalid post value. diff --git a/tests/files/fail/test_assert.yaml b/tests/files/fail/test_assert.yaml new file mode 100644 index 0000000..9073cdf --- /dev/null +++ b/tests/files/fail/test_assert.yaml @@ -0,0 +1,34 @@ +--- +name: fail-assert-1 +desc: assert test +schema: + type: seq + sequence: + - type: map + mapping: + "less-than": + type: number + assert: val < 8 + "more-than": + type: number + assert: 3 < val + "between": + type: number + assert: 3 < val and val < 8 + "except": + type: number + assert: val < 3 or 8 < val +data: + - less-than: 8 + - more-than: 3 + - between: 2.9 + - except: 3.1 +errors: + - "Value: '2.9' assertion expression failed (3 < val and val < 8)" + - "Value: '3' assertion expression failed (3 < val)" + - "Value: '3.1' assertion expression failed (val < 3 or 8 < val)" + - "Value: '8' assertion expression failed (val < 8)" + # :assert_failed : 1:3:[/0/less-than] '8': assertion expression failed (val < 8). + # :assert_failed : 2:3:[/1/more-than] '3': assertion expression failed (3 < val). + # :assert_failed : 3:3:[/2/between] '2.9': assertion expression failed (3 < val and val < 8). + # :assert_failed : 4:3:[/3/except] '3.1': assertion expression failed (val < 3 or 8 < val). diff --git a/tests/files/fail/test_default.yaml b/tests/files/fail/test_default.yaml new file mode 100644 index 0000000..ab0a0a5 --- /dev/null +++ b/tests/files/fail/test_default.yaml @@ -0,0 +1,21 @@ +--- +name: fail-default-1 +desc: default value of map +schema: + type: map + mapping: + =: + type: number + range: + min: -10 + max: 10 +data: + value1: 0 + value2: 20 + value3: -20 +errors: + - "Type 'scalar' has size of '-20', less than min limit '-10'. Path: '/value3'" + - "Type 'scalar' has size of '20', greater than max limit '10'. Path: '/value2'" + ## Kwalify errors + # :range_toolarge : 2:1:[/value2] '20': too large (> max 10). + # :range_toosmall : 3:1:[/value3] '-20': too small (< min -10). diff --git a/tests/files/fail/test_desc.yaml b/tests/files/fail/test_desc.yaml new file mode 100644 index 0000000..401dde3 --- /dev/null +++ b/tests/files/fail/test_desc.yaml @@ -0,0 +1 @@ +# Becuase desc has no validation done on the value there is no failure case for this keyword diff --git a/tests/files/fail/test_enum.yaml b/tests/files/fail/test_enum.yaml new file mode 100644 index 0000000..9f619dc --- /dev/null +++ b/tests/files/fail/test_enum.yaml @@ -0,0 +1,16 @@ +--- +name: fail-enum-1 +desc: Test simple enum +data: + - A + - B + - O +schema: + type: seq + sequence: + - type: str + enum: [E, F, G, H] +errors: + - "Enum 'A' does not exist. Path: '/0'" + - "Enum 'B' does not exist. Path: '/1'" + - "Enum 'O' does not exist. Path: '/2'" diff --git a/tests/files/fail/test_example.yaml b/tests/files/fail/test_example.yaml new file mode 100644 index 0000000..baa8caf --- /dev/null +++ b/tests/files/fail/test_example.yaml @@ -0,0 +1 @@ +# Becuase example has no validation done on the value there is no failure case for this keyword \ No newline at end of file diff --git a/tests/files/fail/test_extensions.yaml b/tests/files/fail/test_extensions.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/fail/test_func.yaml b/tests/files/fail/test_func.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/fail/test_ident.yaml b/tests/files/fail/test_ident.yaml new file mode 100644 index 0000000..2531dfe --- /dev/null +++ b/tests/files/fail/test_ident.yaml @@ -0,0 +1,23 @@ +--- +name: fail-ident-1 +desc: ident constraint test +schema: + type: seq + sequence: + - type: map + mapping: + "name": + ident: true + "age": + type: int +data: + - name: foo + age: 10 + - name: bar + age: 10 + - name: bar + age: 10 +errors: + - "Value 'bar' is not unique. Previous path: '/1/name'. Path: '/2/name'" + ## Kwalify errors + # :value_notunique : 5:3:[/2/name] 'bar': is already used at '/1/name'. diff --git a/tests/files/fail/test_include.yaml b/tests/files/fail/test_include.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/fail/test_length.yaml b/tests/files/fail/test_length.yaml new file mode 100644 index 0000000..8264987 --- /dev/null +++ b/tests/files/fail/test_length.yaml @@ -0,0 +1,113 @@ +--- +name: fail-length-1 +desc: length test +schema: + type: map + mapping: + "max-only": + type: seq + sequence: + - type: str + length: {max: 8} + "min-only": + type: seq + sequence: + - type: str + length: {min: 4} + "max-and-min": + type: seq + sequence: + - type: str + length: {max: 8, min: 4} +data: + max-only: + - hogehoge! + min-only: + - foo + - + max-and-min: + - foobarbaz + - foo +errors: + - "Value: 'foo' has length of '3', greater than min limit '4'. Path: '/max-and-min/1'" + - "Value: 'foo' has length of '3', greater than min limit '4'. Path: '/min-only/0'" + - "Value: 'foobarbaz' has length of '9', greater than max limit '8'. Path: '/max-and-min/0'" + - "Value: 'hogehoge!' has length of '9', greater than max limit '8'. Path: '/max-only/0'" + ## Kwalify errors + # :length_toolong : 2:3:[/max-only/0] 'hogehoge!': too long (length 9 > max 8). + # :length_tooshort : 4:3:[/min-only/0] 'foo': too short (length 3 < min 4). + # :length_toolong : 7:3:[/max-and-min/0] 'foobarbaz': too long (length 9 > max 8). + # :length_tooshort : 8:3:[/max-and-min/1] 'foo': too short (length 3 < min 4). +--- +name: fail-length-2 +desc: length test (with max-ex and min-ex) +schema: + type: map + mapping: + "max-ex-only": + type: seq + sequence: + - type: str + length: {max-ex: 8} + "min-ex-only": + type: seq + sequence: + - type: str + length: {min-ex: 4} + "max-ex-and-min-ex": + type: seq + sequence: + - type: str + length: {max-ex: 8, min-ex: 4} +data: + max-ex-only: + - hogehoge + min-ex-only: + - foo! + - + max-ex-and-min-ex: + - foobarba + - foo! +errors: + - "Value: 'foo!' has length of '4', greater than min_ex limit '4'. Path: '/max-ex-and-min-ex/1'" + - "Value: 'foo!' has length of '4', greater than min_ex limit '4'. Path: '/min-ex-only/0'" + - "Value: 'foobarba' has length of '8', greater than max_ex limit '8'. Path: '/max-ex-and-min-ex/0'" + - "Value: 'hogehoge' has length of '8', greater than max_ex limit '8'. Path: '/max-ex-only/0'" + ## Kwalify errors + # :length_toolongex : 2:3:[/max-ex-only/0] 'hogehoge': too long (length 8 >= max 8). + # :length_tooshortex : 4:3:[/min-ex-only/0] 'foo!': too short (length 4 <= min 4). + # :length_toolongex : 7:3:[/max-ex-and-min-ex/0] 'foobarba': too long (length 8 >= max 8). + # :length_tooshortex : 8:3:[/max-ex-and-min-ex/1] 'foo!': too short (length 4 <= min 4). +--- +name: fail-length-3 +desc: length test (with min, max, max-ex and min-ex) +schema: + type: map + mapping: + "A": + type: seq + sequence: + - type: str + length: {max: 8, min-ex: 4} + "B": + type: seq + sequence: + - type: str + length: {max-ex: 8, min: 4} +data: + A: + - hogehoge! + - hoge + B: + - hogehoge + - hog +errors: + - "Value: 'hog' has length of '3', greater than min limit '4'. Path: '/B/1'" + - "Value: 'hoge' has length of '4', greater than min_ex limit '4'. Path: '/A/1'" + - "Value: 'hogehoge!' has length of '9', greater than max limit '8'. Path: '/A/0'" + - "Value: 'hogehoge' has length of '8', greater than max_ex limit '8'. Path: '/B/0'" + ## Kwalify errors + # :length_toolong : 2:3:[/A/0] 'hogehoge!': too long (length 9 > max 8). + # :length_tooshortex : 3:3:[/A/1] 'hoge': too short (length 4 <= min 4). + # :length_toolongex : 5:3:[/B/0] 'hogehoge': too long (length 8 >= max 8). + # :length_tooshort : 6:3:[/B/1] 'hog': too short (length 3 < min 4). diff --git a/tests/files/fail/test_mapping.yaml b/tests/files/fail/test_mapping.yaml new file mode 100644 index 0000000..0561ea4 --- /dev/null +++ b/tests/files/fail/test_mapping.yaml @@ -0,0 +1,186 @@ +--- +name: fail-mapping-1 +desc: This test that typechecking works when value in map is None +data: + streams: + - name: ~ + sampleRateMultiple: 1 + - name: media + sampleRateMultiple: 2 +schema: + type: map + mapping: + streams: + type: seq + required: True + sequence: + - type: map + mapping: + name: + type: str + range: + min: 1 + required: True + sampleRateMultiple: + type: int + required: True +errors: + - "required.novalue : '/streams/0/name'" +--- +name: fail-mapping-2 +desc: Test keyword regex using default matching-rule 'any' +data: + foobar1: 1 + foobar2: 2 + foobar3: 3 +schema: + type: map + mapping: + regex;(^foobar[1-2]$): + type: int +errors: + - "Key 'foobar3' does not match any regex '^foobar[1-2]$'. Path: ''" +--- +name: fail-mapping-3 +desc: Test keyword regex using declared matching-rule 'any' +data: + foobar1: 1 + foobar2: 2 + bar3: 3 +schema: + type: map + matching-rule: 'any' + mapping: + regex;(^foobar): + type: int + regex;([1-2]$): + type: int +errors: + - "Key 'bar3' does not match any regex '[1-2]$' or '^foobar'. Path: ''" +--- +name: fail-mapping-4 +desc: Test keyword regex using declared matching-rule 'all' +data: + foobar1: 1 + foobar2: 2 + foobar3: 3 +schema: + type: map + matching-rule: 'all' + mapping: + regex;(^foobar.*$): + type: int + regex;(^.*[1-2]$): + type: int +errors: + - "Key 'foobar3' does not match all regex '^.*[1-2]$' and '^foobar.*$'. Path: ''" +--- +name: fail-mapping-5 +desc: Test that sequence of mappings check the correct type and raises correct error when value is not a dict +data: + - foo: whatever + - "sgdf" + - 2 + - ~ +schema: + type: seq + required: True + matching: all + seq: + - type: map + required: True + map: + foo: + type: str +errors: + - "Value '2' is not a dict. Value path: '/2'" + - "Value 'sgdf' is not a dict. Value path: '/1'" + - "required.novalue : '/3'" +--- +name: fail-mapping-6 +desc: Test that type checking of mapping is done even if the mapping keyword is not specefied in the schema +data: + - not + - a + - map +schema: + type: map + allowempty: True +errors: + - "Value '['not', 'a', 'map']' is not a dict. Value path: ''" +--- +name: fail-mapping-7 +desc: Test that default mode fails out in a similar way to regular mode and that a key that is not defined when default is set uses the default impl +data: + OWNERSHIP: abc + WHT: def +schema: + type: map + mapping: + WHT: + type: int + =: + type: int +errors: + - "Value 'abc' is not of type 'int'. Path: '/OWNERSHIP'" + - "Value 'def' is not of type 'int'. Path: '/WHT'" +--- +name: fail-mapping-8 +desc: mapping test +schema: + type: map + required: true + mapping: + name: + type: str + required: true + email: + type: str + # This pattern value was modified from /@/ to .+@.+ to make it copmatible with python + pattern: .+@.+ + required: True + age: + type: int + blood: + type: str + enum: + - A + - B + - O + - AB + birth: + type: date +data: + nam: foo + email: foo(at)mail.com + age: twenty + blood: ab + birth: Jul 01, 1985 +errors: + - "Cannot find required key 'name'. Path: ''" + - "Enum 'ab' does not exist. Path: '/blood'" + - "Key 'nam' was not defined. Path: ''" + - "Value 'foo(at)mail.com' does not match pattern '.+@.+'. Path: '/email'" + - "Value 'twenty' is not of type 'int'. Path: '/age'" + ## Kwalify errors + # :required_nokey : 1:1:[/] key 'name:' is required. + # :key_undefined : 1:1:[/nam] key 'nam:' is undefined. + # :pattern_unmatch : 2:1:[/email] 'foo(at)mail.com': not matched to pattern /@/. + # :type_unmatch : 3:1:[/age] 'twenty': not a integer. + # :enum_notexist : 4:1:[/blood] 'ab': invalid blood value. + # :type_unmatch : 5:1:[/birth] 'Jul 01, 1985': not a date. +--- +name: fail-mapping-9 +desc: Test that regexes can be 'required' +data: + hello: Hi + person: Fred +schema: + type: map + mapping: + regex;(person[1-9]): + required: True +errors: + - "Cannot find required key 'regex;(person[1-9])'. Path: ''" + - "Key 'hello' does not match any regex 'person[1-9]'. Path: ''" + - "Key 'person' does not match any regex 'person[1-9]'. Path: ''" \ No newline at end of file diff --git a/tests/files/fail/test_matching.yaml b/tests/files/fail/test_matching.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/fail/test_merge.yaml b/tests/files/fail/test_merge.yaml new file mode 100644 index 0000000..3b7eac8 --- /dev/null +++ b/tests/files/fail/test_merge.yaml @@ -0,0 +1,37 @@ +--- +name: fail-merge-1 +desc: merge maps +schema: + type: map + mapping: + "group": + type: map + mapping: + "name": &name + type: str + required: true + "email": &email + type: str + pattern: .+@.+ + required: False + "user": + type: map + mapping: + "name": + <<: *name # merge + length: {max: 16} # add + "email": + <<: *email # merge + required: true # override +data: + group: + name: foo + email: foo@mail.com + user: + name: toooooo-looooong-naaaame +errors: + - "Cannot find required key 'email'. Path: '/user'" + - "Value: 'toooooo-looooong-naaaame' has length of '24', greater than max limit '16'. Path: '/user/name'" + ## Kwalify errors + # :required_nokey : 5:3:[/user] key 'email:' is required. + # :length_toolong : 5:3:[/user/name] 'toooooo-looooong-naaaame': too long (length 24 > max 16). diff --git a/tests/files/fail/test_name.yaml b/tests/files/fail/test_name.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/fail/test_nullable.yaml b/tests/files/fail/test_nullable.yaml new file mode 100644 index 0000000..8ce8259 --- /dev/null +++ b/tests/files/fail/test_nullable.yaml @@ -0,0 +1,19 @@ +--- +name: fail-nullable-1 +desc: +data: + - name: + email: foo@mail.com + - email: bar@mail.net +schema: + type: seq + sequence: + - type: map + mapping: + name: + type: str + nullable: False + email: + type: str +errors: + - "nullable.novalue : '/0/name'" diff --git a/tests/files/fail/test_pattern.yaml b/tests/files/fail/test_pattern.yaml new file mode 100644 index 0000000..4531d55 --- /dev/null +++ b/tests/files/fail/test_pattern.yaml @@ -0,0 +1,26 @@ +--- +name: fail-pattern-1 +desc: +data: + email: foo(at)mail.com +schema: + type: map + mapping: + email: + type: str + pattern: .+@.+ +errors: + - "Value 'foo(at)mail.com' does not match pattern '.+@.+'. Path: '/email'" +--- +name: fail-pattern-2 +desc: +data: + d: 'a' +schema: + type: map + mapping: + d: + type: str + pattern: '[0-9]+' +errors: + - "Value 'a' does not match pattern '[0-9]+'. Path: '/d'" diff --git a/tests/files/fail/test_range.yaml b/tests/files/fail/test_range.yaml new file mode 100644 index 0000000..22bd801 --- /dev/null +++ b/tests/files/fail/test_range.yaml @@ -0,0 +1,219 @@ +--- +name: fail-range-1 +desc: +data: + - foo + - bar + - foobar +schema: + type: seq + sequence: + - type: str + range: + max: 5 + min: 1 +errors: + - "Type 'scalar' has size of '6', greater than max limit '5'. Path: '/2'" +--- +name: fail-range-2 +desc: Test that range validates on 'map' raise correct error +data: + streams: + sampleRateMultiple: 1 +schema: + type: map + mapping: + streams: + type: map + range: + min: 2 + max: 3 + mapping: + sampleRateMultiple: + type: int + required: True +errors: + - "Type 'map' has size of '1', less than min limit '2'. Path: '/streams'" +--- +name: fail-range-3 +desc: Test that range validates on 'seq' raise correct error +data: + - foobar + - barfoo + - opa +schema: + type: seq + range: + min: 1 + max: 2 + sequence: + - type: str +errors: + - "Type 'seq' has size of '3', greater than max limit '2'. Path: ''" +--- +name: fail-range-4 +desc: Test float range value out of range +data: + the_float: 1.2 + the_float_ex: 2.1 +schema: + type: map + mapping: + the_float: + type: float + required: True + range: + min: 2.1 + max: 3.2 + the_float_ex: + type: float + required: True + range: + min-ex: 2.1 + max-ex: 3.2 +errors: + - "Type 'scalar' has size of '1.2', less than min limit '2.1'. Path: '/the_float'" + - "Type 'scalar' has size of '2.1', less than or equals to min limit(exclusive) '2.1'. Path: '/the_float_ex'" +--- +name: fail-range-1 +desc: range test && bug#????? +schema: + type: map + mapping: + "max-only": + type: seq + sequence: + - type: number + required: true + range: {max: 100} + "min-only": + type: seq + sequence: + - type: number + required: true + range: {min: 10.0} + "max-and-min": + type: seq + sequence: + - type: number + required: true + range: {max: 100.0, min: 10.0} +data: + max-only: + - 101 + - 100.1 + min-only: + - 9 + - 9.99 + max-and-min: + - 101 + - 100.1 + - 9 + - 9.99 +errors: + - "Type 'scalar' has size of '100.1', greater than max limit '100'. Path: '/max-only/1'" + - "Type 'scalar' has size of '100.1', greater than max limit '100.0'. Path: '/max-and-min/1'" + - "Type 'scalar' has size of '101', greater than max limit '100'. Path: '/max-only/0'" + - "Type 'scalar' has size of '101', greater than max limit '100.0'. Path: '/max-and-min/0'" + - "Type 'scalar' has size of '9', less than min limit '10.0'. Path: '/max-and-min/2'" + - "Type 'scalar' has size of '9', less than min limit '10.0'. Path: '/min-only/0'" + - "Type 'scalar' has size of '9.99', less than min limit '10.0'. Path: '/max-and-min/3'" + - "Type 'scalar' has size of '9.99', less than min limit '10.0'. Path: '/min-only/1'" + ## Kwalify errors + # :range_toolarge : 2:3:[/max-only/0] '101': too large (> max 100). + # :range_toolarge : 3:3:[/max-only/1] '100.1': too large (> max 100). + # :range_toosmall : 5:3:[/min-only/0] '9': too small (< min 10.0). + # :range_toosmall : 6:3:[/min-only/1] '9.99': too small (< min 10.0). + # :range_toolarge : 8:3:[/max-and-min/0] '101': too large (> max 100.0). + # :range_toolarge : 9:3:[/max-and-min/1] '100.1': too large (> max 100.0). + # :range_toosmall : 10:3:[/max-and-min/2] '9': too small (< min 10.0). + # :range_toosmall : 11:3:[/max-and-min/3] '9.99': too small (< min 10.0). +--- +name: fail-range-2 +desc: range test (with max-ex and min-ex) +schema: + type: map + mapping: + "max-ex-only": + type: seq + sequence: + - type: number + required: true + range: {max-ex: 100} + "min-ex-only": + type: seq + sequence: + - type: number + required: true + range: {min-ex: 10.0} + "max-ex-and-min-ex": + type: seq + sequence: + - type: number + required: true + range: {max-ex: 100.0, min-ex: 10.0} +data: + max-ex-only: + - 100 + - 100.0 + min-ex-only: + - 10 + - 10.0 + max-ex-and-min-ex: + - 100 + - 100.0 + - 10 + - 10.0 +errors: + - "Type 'scalar' has size of '10', less than or equals to min limit(exclusive) '10.0'. Path: '/max-ex-and-min-ex/2'" + - "Type 'scalar' has size of '10', less than or equals to min limit(exclusive) '10.0'. Path: '/min-ex-only/0'" + - "Type 'scalar' has size of '10.0', less than or equals to min limit(exclusive) '10.0'. Path: '/max-ex-and-min-ex/3'" + - "Type 'scalar' has size of '10.0', less than or equals to min limit(exclusive) '10.0'. Path: '/min-ex-only/1'" + - "Type 'scalar' has size of '100', greater than or equals to max limit(exclusive) '100'. Path: '/max-ex-only/0'" + - "Type 'scalar' has size of '100', greater than or equals to max limit(exclusive) '100.0'. Path: '/max-ex-and-min-ex/0'" + - "Type 'scalar' has size of '100.0', greater than or equals to max limit(exclusive) '100'. Path: '/max-ex-only/1'" + - "Type 'scalar' has size of '100.0', greater than or equals to max limit(exclusive) '100.0'. Path: '/max-ex-and-min-ex/1'" + ## Kwalify errors + # :range_toolargeex : 2:3:[/max-ex-only/0] '100': too large (>= max 100). + # :range_toolargeex : 3:3:[/max-ex-only/1] '100.0': too large (>= max 100). + # :range_toosmallex : 5:3:[/min-ex-only/0] '10': too small (<= min 10.0). + # :range_toosmallex : 6:3:[/min-ex-only/1] '10.0': too small (<= min 10.0). + # :range_toolargeex : 8:3:[/max-ex-and-min-ex/0] '100': too large (>= max 100.0). + # :range_toolargeex : 9:3:[/max-ex-and-min-ex/1] '100.0': too large (>= max 100.0). + # :range_toosmallex : 10:3:[/max-ex-and-min-ex/2] '10': too small (<= min 10.0). + # :range_toosmallex : 11:3:[/max-ex-and-min-ex/3] '10.0': too small (<= min 10.0). +--- +name: fail-range-3 +desc: range test (with max, min, max-ex and min-ex) +schema: + type: map + mapping: + "A": + type: seq + sequence: + - type: number + required: true + range: {max: 100, min-ex: 10.0} + "B": + type: seq + sequence: + - type: number + required: true + range: {min: 10, max-ex: 100.0} +data: + A: + - 100.00001 + - 10.0 + B: + - 9.99999 + - 100.0 +errors: + - "Type 'scalar' has size of '10.0', less than or equals to min limit(exclusive) '10.0'. Path: '/A/1'" + - "Type 'scalar' has size of '100.0', greater than or equals to max limit(exclusive) '100.0'. Path: '/B/1'" + - "Type 'scalar' has size of '100.00001', greater than max limit '100'. Path: '/A/0'" + - "Type 'scalar' has size of '9.99999', less than min limit '10'. Path: '/B/0'" + ## Kwalify errors + # :range_toolarge : 2:3:[/A/0] '100.00001': too large (> max 100)" + # :range_toosmallex : 3:3:[/A/1] '10.0': too small (<= min 10.0). + # :range_toosmall : 5:3:[/B/0] '9.99999': too small (< min 10). + # :range_toolargeex : 6:3:[/B/1] '100.0': too large (>= max 100.0). diff --git a/tests/files/fail/test_required.yaml b/tests/files/fail/test_required.yaml new file mode 100644 index 0000000..6038544 --- /dev/null +++ b/tests/files/fail/test_required.yaml @@ -0,0 +1,19 @@ +--- +name: fail-required-1 +desc: +data: + - name: foo + email: foo@mail.com + - email: bar@mail.net +schema: + type: seq + sequence: + - type: map + mapping: + name: + type: str + required: True + email: + type: str +errors: + - "Cannot find required key 'name'. Path: '/1'" diff --git a/tests/files/fail/test_schema.yaml b/tests/files/fail/test_schema.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/fail/test_sequence.yaml b/tests/files/fail/test_sequence.yaml new file mode 100644 index 0000000..e26c4a2 --- /dev/null +++ b/tests/files/fail/test_sequence.yaml @@ -0,0 +1,71 @@ +--- +name: fail-sequence-1 +desc: +data: + - 1 + - 2 + - 3 + - True + - False +schema: + type: seq + sequence: + - type: str +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'" + - "Value 'True' is not of type 'str'. Path: '/3'" + - "Value 'False' is not of type 'str'. Path: '/4'" +--- +name: fail-sequence-2 +desc: +data: + - True + - False + - 1 +schema: + type: seq + sequence: + - type: bool +errors: + - "Value '1' is not of type 'bool'. Path: '/2'" +--- +name: fail-sequence-3 +desc: sequence test +schema: + type: seq + required: true + sequence: + - type: str + required: true +data: + - foo + - bar + - + - baz + - 100 +errors: + - "Value '100' is not of type 'str'. Path: '/4'" + - "required.novalue : '/2'" + ## Kwalify errors + # - "Value 'None' is not of type 'str'. Path: '/2'" + # :required_novalue : (line 3)[/2] value required but none. + # :type_unmatch : (line 5)[/4] '100': not a string. +--- +name: fail-sequence-4 +desc: Test that very deep nested sequences fail when schema expected sequence but value was something else +schema: + type: seq + sequence: + - type: seq + sequence: + - type: seq + sequence: + - type: seq + sequence: + - type: str +data: + - - - 1 +errors: + - "Value '1' is not a list. Value path: '/0/0/0'" diff --git a/tests/files/fail/test_sequence_multi.yaml b/tests/files/fail/test_sequence_multi.yaml new file mode 100644 index 0000000..a5007a0 --- /dev/null +++ b/tests/files/fail/test_sequence_multi.yaml @@ -0,0 +1,31 @@ +--- +name: fail-sequence-multi-1 +desc: Test multiple sequence values with wrong sub type and 'all' matching rule +data: + - "foo" +schema: + type: seq + matching: "all" + seq: + - type: str + - type: int +errors: + - "Value 'foo' is not of type 'int'. Path: '/0'" +--- +name: fail-sequence-multi-2 +desc: Test multiple nested sequence values with error in level 2 with 'any' matching rule +data: + - - 123 + - "foobar" +schema: + type: seq + matching: "any" + seq: + - type: str + - type: seq + matching: "any" + sequence: + - type: str +errors: + - "Value '123' is not of type 'str'. Path: '/0/0'" + - "Value '[123]' is not of type 'str'. Path: '/0'" diff --git a/tests/files/fail/test_type_any.yaml b/tests/files/fail/test_type_any.yaml new file mode 100644 index 0000000..5c8e7fa --- /dev/null +++ b/tests/files/fail/test_type_any.yaml @@ -0,0 +1 @@ +# Becuase type 'any' validates for any kind of data there is no failure case for this type. diff --git a/tests/files/fail/test_type_bool.yaml b/tests/files/fail/test_type_bool.yaml new file mode 100644 index 0000000..1007348 --- /dev/null +++ b/tests/files/fail/test_type_bool.yaml @@ -0,0 +1,52 @@ +--- +name: fail-type-bool-1 +desc: Test wrong type as value in list +data: + - "foo" +schema: + type: seq + matching: "any" + seq: + - type: bool +errors: + - "Value 'foo' is not of type 'bool'. Path: '/0'" +--- +name: fail-type-bool-2 +desc: Test bool value inside list +data: + - 'abc' + - 123 +schema: + type: seq + sequence: + - type: bool +errors: + - "Value '123' is not of type 'bool'. Path: '/1'" + - "Value 'abc' is not of type 'bool'. Path: '/0'" +--- +name: fail-type-bool-3 +desc: Test bool value in mapping +data: + foo: 'abc' +schema: + type: map + mapping: + foo: + type: bool +errors: + - "Value 'abc' is not of type 'bool'. Path: '/foo'" +--- +name: fail-type-bool-4 +desc: Test bool inside nested map & seq +data: + foo: + - 'abc' +schema: + type: map + mapping: + foo: + type: seq + sequence: + - type: bool +errors: + - "Value 'abc' is not of type 'bool'. Path: '/foo/0'" diff --git a/tests/files/fail/test_type_date.yaml b/tests/files/fail/test_type_date.yaml new file mode 100644 index 0000000..5a1b39a --- /dev/null +++ b/tests/files/fail/test_type_date.yaml @@ -0,0 +1,83 @@ +--- +name: fail-type-date-1 +desc: basic test for date type with default formats +data: "abc" +schema: + type: date +errors: + - "Not a valid date: abc Path: ''" +--- +name: fail-type-date-2 +desc: Basic test for date type with defined date-formats +data: "31-01-2017" +schema: + type: date + format: "%Y-%m-%d" +errors: + - "Not a valid date: 31-01-2017 format: %Y-%m-%d. Path: ''" +--- +name: fail-type-date-3 +desc: Basic test for date type with defined date-formats +data: + - "2017" + - "31" +schema: + type: seq + sequence: + - type: date + format: + - "%d-%m-%Y" + - "%Y-%m-%d" +errors: + - "Not a valid date: 2017 format: %Y-%m-%d. Path: '/0'" + - "Not a valid date: 31 format: %Y-%m-%d. Path: '/1'" +--- +name: fail-type-data-4 +desc: Test date type as values in a list +data: + - 'abc-1997' + - 'abc-1997-07' + - 'abc-1997-07-16' + - 'abc-1997-07-16T19:20+01:00' + - 'abc-1997-07-16T19:20:30+01:00' + - 'abc-1997-07-16T19:20:30.45+01:00' +schema: + type: seq + sequence: + - type: date +errors: + - "Not a valid date: abc-1997 Path: '/0'" + - "Not a valid date: abc-1997-07 Path: '/1'" + - "Not a valid date: abc-1997-07-16 Path: '/2'" + - "Not a valid date: abc-1997-07-16T19:20+01:00 Path: '/3'" + - "Not a valid date: abc-1997-07-16T19:20:30+01:00 Path: '/4'" + - "Not a valid date: abc-1997-07-16T19:20:30.45+01:00 Path: '/5'" +--- +name: fail-type-date-5 +desc: Test that wrong value types do not validate +data: + - 123 + - True +schema: + type: seq + sequence: + - type: date +errors: + - "Value '123' is not of type 'date'. Path: '/0'" + - "Value 'True' is not of type 'date'. Path: '/1'" +--- +name: fail-type-date-6 +desc: Test that wrong value types in map do not validate +data: + foo: 123 + bar: True +schema: + type: map + mapping: + foo: + type: date + bar: + type: date +errors: + - "Value '123' is not of type 'date'. Path: '/foo'" + - "Value 'True' is not of type 'date'. Path: '/bar'" diff --git a/tests/files/fail/test_type_float.yaml b/tests/files/fail/test_type_float.yaml new file mode 100644 index 0000000..bef14b8 --- /dev/null +++ b/tests/files/fail/test_type_float.yaml @@ -0,0 +1,56 @@ +--- +name: fail-type-float-1 +desc: Test simples float value +data: "abc" +schema: + type: float +errors: + - "Value 'abc' is not of type 'float'. Path: ''" +--- +name: fail-type-float-2 +desc: Test wrong type as value in list +data: + - "foo" + - True +schema: + type: seq + seq: + - type: float +errors: + - "Value 'foo' is not of type 'float'. Path: '/0'" +errors: + - "Value 'True' is not of type 'float'. Path: '/1'" + - "Value 'foo' is not of type 'float'. Path: '/0'" +--- +name: fail-type-float-3 +desc: Test float value in mapping +data: + foo: "abc" + bar: True +schema: + type: map + mapping: + foo: + type: float + bar: + type: float +errors: + - "Value 'True' is not of type 'float'. Path: '/bar'" + - "Value 'abc' is not of type 'float'. Path: '/foo'" +--- +name: fail-type-float-4 +desc: Test float inside nested map & seq +data: + foo: + - True + - "abc" +schema: + type: map + mapping: + foo: + type: seq + sequence: + - type: float +errors: + - "Value 'True' is not of type 'float'. Path: '/foo/0'" + - "Value 'abc' is not of type 'float'. Path: '/foo/1'" diff --git a/tests/files/fail/test_type_int.yaml b/tests/files/fail/test_type_int.yaml new file mode 100644 index 0000000..62d8fe1 --- /dev/null +++ b/tests/files/fail/test_type_int.yaml @@ -0,0 +1,45 @@ +--- +name: fail-type-int-1 +desc: +data: + A101 +schema: + type: int +errors: + - "Value 'A101' is not of type 'int'. Path: ''" +--- +name: fail-type-int-2 +desc: Test wrong type as value in list +data: + - "foo" +schema: + type: seq + matching: "any" + seq: + - type: int +errors: + - "Value 'foo' is not of type 'int'. Path: '/0'" +--- +name: fail-type-int-3 +desc: Test that True/False is not valid integers +data: + - 1 + - True + - False +schema: + type: seq + sequence: + - type: int +errors: + - "Value 'False' is not of type 'int'. Path: '/2'" + - "Value 'True' is not of type 'int'. Path: '/1'" +--- +name: fail-type-int-4 +desc: Test that hexadecimal characters fails with pattern +data: + 0x12345678 +schema: + type: text + pattern: ^0x[0-9A-F]{1,8}$ +errors: + - "Value '305419896' does not match pattern '^0x[0-9A-F]{1,8}$'. Path: ''" diff --git a/tests/files/fail/test_type_map.yaml b/tests/files/fail/test_type_map.yaml new file mode 100644 index 0000000..a46086b --- /dev/null +++ b/tests/files/fail/test_type_map.yaml @@ -0,0 +1,46 @@ +--- +name: fail-type-map-1 +desc: Test the most basic case for map +data: + - 'foo' +schema: + type: map + mapping: + foo: + type: str +errors: + - "Value '['foo']' is not a dict. Value path: ''" +--- +name: type-map-2 +desc: +data: + - - 'foo' + - - 'foo' +schema: + type: seq + sequence: + - type: map + mapping: + foo: + type: str +errors: + - "Value '['foo']' is not a dict. Value path: '/0'" + - "Value '['foo']' is not a dict. Value path: '/1'" +--- +name: type-map-3 +desc: Test bool inside nested map & seq +data: + foo: + - - 'foo' +schema: + type: map + mapping: + foo: + type: seq + sequence: + - type: map + mapping: + bar: + type: str +errors: + - "Value '['foo']' is not a dict. Value path: '/foo/0'" diff --git a/tests/files/fail/test_type_none.yaml b/tests/files/fail/test_type_none.yaml new file mode 100644 index 0000000..cc7c7df --- /dev/null +++ b/tests/files/fail/test_type_none.yaml @@ -0,0 +1,54 @@ +# +# NOTE: This case is not allowed becuase Core class do NOT allow +# there is no data to validate. This happens if None is at top level +# of the data structure. +# +# --- +# name: type-none-1 +# desc: Most basic test for type None +# data: ~ +# schema: +# type: none +--- +name: fail-type-none-2 +desc: Test that none type works with none type as value in map +data: + name: 'abc' +schema: + type: map + mapping: + name: + type: none +errors: + - "Value 'abc' is not of type 'none'. Path: '/name'" +--- +name: fail-type-none-3 +desc: Test that none type works as value in sequence +data: + - 'abc' + - 123 +schema: + type: seq + sequence: + - type: none +errors: + - "Value '123' is not of type 'none'. Path: '/1'" + - "Value 'abc' is not of type 'none'. Path: '/0'" +--- +name: fail-type-none-4 +desc: Test that none type works inside nested map, seq, map +data: + foo: + - bar: 'abc' +schema: + type: map + mapping: + foo: + type: seq + sequence: + - type: map + mapping: + bar: + type: none +errors: + - "Value 'abc' is not of type 'none'. Path: '/foo/0/bar'" diff --git a/tests/files/fail/test_type_number.yaml b/tests/files/fail/test_type_number.yaml new file mode 100644 index 0000000..003b0a6 --- /dev/null +++ b/tests/files/fail/test_type_number.yaml @@ -0,0 +1,83 @@ +--- +name: fail-type-number-1 +desc: This tests number validation rule with wrong data types +data: True +schema: + type: number +errors: + - "Value 'True' is not of type 'number'. Path: ''" +--- +name: fail-type-number-2 +desc: Test that number type works with as value in map +data: + foo: True + bar: 'abc' + qwe: [] + rty: {} +schema: + type: map + mapping: + foo: + type: number + bar: + type: number + qwe: + type: number + rty: + type: number +errors: + - "Value 'True' is not of type 'number'. Path: '/foo'" + - "Value '[]' is not of type 'number'. Path: '/qwe'" + - "Value 'abc' is not of type 'number'. Path: '/bar'" + - "Value '{}' is not of type 'number'. Path: '/rty'" +--- +name: fail-type-number-3 +desc: Test that different number values works as values in seq +data: + - True + - 'abc' + - {} + - [] +schema: + type: seq + sequence: + - type: number +errors: + - "Value 'True' is not of type 'number'. Path: '/0'" + - "Value '[]' is not of type 'number'. Path: '/3'" + - "Value 'abc' is not of type 'number'. Path: '/1'" + - "Value '{}' is not of type 'number'. Path: '/2'" +--- +name: fail-type-number-4 +desc: Test that number type works inside nested map, seq, map +data: + foobar: + - foo: True + bar: 'abc' + qwe: {} + rty: [] +schema: + type: map + mapping: + foobar: + type: seq + sequence: + - type: map + mapping: + foo: + type: number + bar: + type: number + qwe: + type: number + rty: + type: number + ewq: + type: number + dsa: + type: number +errors: + - "Value 'True' is not of type 'number'. Path: '/foobar/0/foo'" + - "Value '[]' is not of type 'number'. Path: '/foobar/0/rty'" + - "Value 'abc' is not of type 'number'. Path: '/foobar/0/bar'" + - "Value '{}' is not of type 'number'. Path: '/foobar/0/qwe'" diff --git a/tests/files/fail/test_type_scalar.yaml b/tests/files/fail/test_type_scalar.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/fail/test_type_seq.yaml b/tests/files/fail/test_type_seq.yaml new file mode 100644 index 0000000..a5baacd --- /dev/null +++ b/tests/files/fail/test_type_seq.yaml @@ -0,0 +1,47 @@ +# +# TODO: All of these failure tests currently raises a strange error that might not be the correct one and might require some redesign of the implementation. +# pykwalify.errors.NotSequenceError: +# + +# --- +# name: fail-type-seq-1 +# desc: Test the most basic case for seq +# data: +# {} +# schema: +# type: seq +# sequence: +# - type: str +# errors: +# - '' +# --- +# name: fail-type-seq-2 +# desc: Test that seq in seq works +# data: +# - {} +# - {} +# schema: +# type: seq +# sequence: +# - type: seq +# sequence: +# - type: bool +# errors: +# - '' +# --- +# name: fail-type-seq-3 +# desc: Test bool inside nested map & seq +# data: +# - foo: +# {} +# schema: +# type: seq +# sequence: +# - type: map +# mapping: +# foo: +# type: seq +# sequence: +# - type: bool +# errors: +# - '' diff --git a/tests/files/fail/test_type_str.yaml b/tests/files/fail/test_type_str.yaml new file mode 100644 index 0000000..13cdbb6 --- /dev/null +++ b/tests/files/fail/test_type_str.yaml @@ -0,0 +1,73 @@ +--- +name: fail-type-str-1 +desc: Test simples str value +data: 1 +schema: + type: str +errors: + - "Value '1' is not of type 'str'. Path: ''" +--- +name: fail-type-str-2 +desc: Test str value inside list +data: + - 1 + - True +schema: + type: seq + sequence: + - type: str +errors: + - "Value '1' is not of type 'str'. Path: '/0'" + - "Value 'True' is not of type 'str'. Path: '/1'" +--- +name: fail-type-str-3 +desc: Test str value in mapping +data: + foo: 1 +schema: + type: map + mapping: + foo: + type: str +errors: + - "Value '1' is not of type 'str'. Path: '/foo'" +--- +name: fail-type-str-4 +desc: Test str inside nested map & seq +data: + foo: + - 1 +schema: + type: map + mapping: + foo: + type: seq + sequence: + - type: str +errors: + - "Value '1' is not of type 'str'. Path: '/foo/0'" +--- +name: fail-deftype-1 +desc: default type test +schema: + type: seq + sequence: + - type: map + mapping: + "name": + "email": +data: + - name: 123 + email: true + - name: 3.14 + - email: 2004-01-01 +errors: + - "Value '123' is not of type 'str'. Path: '/0/name'" + - "Value '2004-01-01' is not of type 'str'. Path: '/2/email'" + - "Value '3.14' is not of type 'str'. Path: '/1/name'" + - "Value 'True' is not of type 'str'. Path: '/0/email'" + ## Kwalify errors + # :type_unmatch : 1:3:[/0/name] '123': not a string. + # :type_unmatch : 2:3:[/0/email] 'true': not a string. + # :type_unmatch : 3:3:[/1/name] '3.14': not a string. + # :type_unmatch : 4:3:[/2/email] '2004-01-01': not a string. diff --git a/tests/files/fail/test_type_symbol.yaml b/tests/files/fail/test_type_symbol.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/fail/test_type_text.yaml b/tests/files/fail/test_type_text.yaml new file mode 100644 index 0000000..f4e6d2e --- /dev/null +++ b/tests/files/fail/test_type_text.yaml @@ -0,0 +1,70 @@ +--- +name: fail-type-text-1 +desc: Test simples text type +data: True +schema: + type: text +errors: + - "Value 'True' is not of type 'text'. Path: ''" +--- +name: fail-type-text-2 +desc: Test possible values as values in seq +data: + - abc + - 123 + - 3.14159 + - True +schema: + type: seq + sequence: + - type: text +errors: + - "Value 'True' is not of type 'text'. Path: '/3'" +--- +name: fail-type-text-3 +desc: Test possible values as values in map +data: + foo: abc + bar: 123 + qwe: 3.14159 + rty: True +schema: + type: map + mapping: + foo: + type: text + bar: + type: text + qwe: + type: text + rty: + type: text +errors: + - "Value 'True' is not of type 'text'. Path: '/rty'" +--- +name: fail-type-text-4 +desc: Test that text type works inside nested map, seq, map +data: + foobar: + - foo: abc + bar: 123 + qwe: 3.14159 + rty: True +schema: + type: map + mapping: + foobar: + type: seq + sequence: + - type: map + mapping: + foo: + type: text + bar: + type: text + qwe: + type: text + rty: + type: text +errors: + - "Value 'True' is not of type 'text'. Path: '/foobar/0/rty'" diff --git a/tests/files/fail/test_type_timestamp.yaml b/tests/files/fail/test_type_timestamp.yaml new file mode 100644 index 0000000..1a5e120 --- /dev/null +++ b/tests/files/fail/test_type_timestamp.yaml @@ -0,0 +1,15 @@ +--- +name: fail-type-timestamp-1 +desc: Test timestamps that should throw errors +data: + d1: "" + d2: "1427650980" +schema: + type: map + mapping: + d1: + type: timestamp + d2: + type: timestamp +errors: + - "Timestamp value is empty. Path: '/d1'" diff --git a/tests/files/fail/test_unique.yaml b/tests/files/fail/test_unique.yaml new file mode 100644 index 0000000..0af5d7c --- /dev/null +++ b/tests/files/fail/test_unique.yaml @@ -0,0 +1,109 @@ +--- +name: fail-unique-1 +desc: "NOTE: The reverse unique do not currently work proper # This will test the unique constraint but should fail" +data: + - name: foo + email: admin@mail.com + groups: + - foo + - users + - admin + - foo + - name: bar + email: admin@mail.com + groups: + - admin + - users + - name: bar + email: baz@mail.com + groups: + - users +schema: + type: seq + sequence: + - type: map + required: True + mapping: + name: + type: str + required: True + unique: True + email: + type: str + groups: + type: seq + sequence: + - type: str + unique: True +errors: + - "Value 'bar' is not unique. Previous path: '/1/name'. Path: '/2/name'" + - "Value 'foo' is not unique. Previous path: '/0/groups/0'. Path: '/0/groups/3'" +--- +name: fail-unique-2 +desc: unique constraint test with map +schema: + type: seq + sequence: + - type: map + mapping: + "name": + unique: true + "age": + type: int +data: + - name: foo + age: 10 + - name: bar + age: 10 + - age: 10 + name: bar +errors: + - "Value 'bar' is not unique. Previous path: '/1/name'. Path: '/2/name'" + ## Kwalify errors + # :value_notunique : 6:3:[/2/name] 'bar': is already used at '/1/name'. +--- +name: fail-unique-3 +desc: unique constraint test with seq +schema: + type: seq + sequence: + - type: str + unique: true +data: + - foo + - ~ + - bar + - ~ + - bar +errors: + - "Value 'bar' is not unique. Previous path: '/2'. Path: '/4'" + ## Kwalify errors + # :value_notunique : 5:1:[/4] 'bar': is already used at '/2'. +--- +name: fail-unique-4 +desc: unique constraint and '<<' (merge) +schema: + type: seq + sequence: + - type: map + mapping: + "name": + type: str + required: true + unique: true + "value": + type: any + required: true +data: + - &a1 + name: x1 + value: 10 + - <<: *a1 + - <<: *a1 # wrong validation error + name: x3 +errors: + - "Value 'x1' is not unique. Previous path: '/0/name'. Path: '/1/name'" + # TODO: Possibly missing one error here... + ## Kwalify errors + # :value_notunique : 4:3:[/1/name] 'x1': is already used at '/0/name'. + # :value_notunique : 5:3:[/2/name] 'x1': is already used at '/0/name'. diff --git a/tests/files/fail/test_version.yaml b/tests/files/fail/test_version.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/partial_schemas/1f-data.yaml b/tests/files/partial_schemas/1f-data.yaml new file mode 100644 index 0000000..4444bad --- /dev/null +++ b/tests/files/partial_schemas/1f-data.yaml @@ -0,0 +1 @@ +- foo: "opa" \ No newline at end of file diff --git a/tests/files/partial_schemas/1f-partials.yaml b/tests/files/partial_schemas/1f-partials.yaml new file mode 100644 index 0000000..b93127a --- /dev/null +++ b/tests/files/partial_schemas/1f-partials.yaml @@ -0,0 +1,11 @@ +schema;fooone: + type: map + mapping: + foo: + include: footwo + +schema;footwo: + type: map + mapping: + foo: + type: bool diff --git a/tests/files/partial_schemas/1f-schema.yaml b/tests/files/partial_schemas/1f-schema.yaml new file mode 100644 index 0000000..462d391 --- /dev/null +++ b/tests/files/partial_schemas/1f-schema.yaml @@ -0,0 +1,3 @@ +type: seq +sequence: + - include: fooonez diff --git a/tests/files/partial_schemas/1s-data.yaml b/tests/files/partial_schemas/1s-data.yaml new file mode 100644 index 0000000..4444bad --- /dev/null +++ b/tests/files/partial_schemas/1s-data.yaml @@ -0,0 +1 @@ +- foo: "opa" \ No newline at end of file diff --git a/tests/files/partial_schemas/1s-partials.yaml b/tests/files/partial_schemas/1s-partials.yaml new file mode 100644 index 0000000..ef84657 --- /dev/null +++ b/tests/files/partial_schemas/1s-partials.yaml @@ -0,0 +1,11 @@ +schema;fooone: + type: map + mapping: + foo: + type: str + +schema;footwo: + type: map + mapping: + foo: + type: bool \ No newline at end of file diff --git a/tests/files/partial_schemas/1s-schema.yaml b/tests/files/partial_schemas/1s-schema.yaml new file mode 100644 index 0000000..ea433b7 --- /dev/null +++ b/tests/files/partial_schemas/1s-schema.yaml @@ -0,0 +1,3 @@ +type: seq +sequence: + - include: fooone diff --git a/tests/files/partial_schemas/2f-data.yaml b/tests/files/partial_schemas/2f-data.yaml new file mode 100644 index 0000000..b1d5359 --- /dev/null +++ b/tests/files/partial_schemas/2f-data.yaml @@ -0,0 +1 @@ +- True diff --git a/tests/files/partial_schemas/2f-schema.yaml b/tests/files/partial_schemas/2f-schema.yaml new file mode 100644 index 0000000..d5bd186 --- /dev/null +++ b/tests/files/partial_schemas/2f-schema.yaml @@ -0,0 +1,5 @@ +type: seq +sequence: + - include: foo +schema;foo: + type: str diff --git a/tests/files/partial_schemas/2s-data.yaml b/tests/files/partial_schemas/2s-data.yaml new file mode 100644 index 0000000..c67a590 --- /dev/null +++ b/tests/files/partial_schemas/2s-data.yaml @@ -0,0 +1,3 @@ +- foo: + bar: + - true diff --git a/tests/files/partial_schemas/2s-partials.yaml b/tests/files/partial_schemas/2s-partials.yaml new file mode 100644 index 0000000..238abb9 --- /dev/null +++ b/tests/files/partial_schemas/2s-partials.yaml @@ -0,0 +1,16 @@ +schema;footwo: + type: map + mapping: + bar: + include: foothree + +schema;fooone: + type: map + mapping: + foo: + include: footwo + +schema;foothree: + type: seq + sequence: + - type: bool diff --git a/tests/files/partial_schemas/2s-schema.yaml b/tests/files/partial_schemas/2s-schema.yaml new file mode 100644 index 0000000..ea433b7 --- /dev/null +++ b/tests/files/partial_schemas/2s-schema.yaml @@ -0,0 +1,3 @@ +type: seq +sequence: + - include: fooone diff --git a/tests/files/partial_schemas/3f-data.yaml b/tests/files/partial_schemas/3f-data.yaml new file mode 100644 index 0000000..0ca9514 --- /dev/null +++ b/tests/files/partial_schemas/3f-data.yaml @@ -0,0 +1 @@ +True diff --git a/tests/files/partial_schemas/3f-schema.yaml b/tests/files/partial_schemas/3f-schema.yaml new file mode 100644 index 0000000..d83c47c --- /dev/null +++ b/tests/files/partial_schemas/3f-schema.yaml @@ -0,0 +1,3 @@ +include: foo +schema;foo: + type: str diff --git a/tests/files/partial_schemas/4f-data.yaml b/tests/files/partial_schemas/4f-data.yaml new file mode 100644 index 0000000..54c108e --- /dev/null +++ b/tests/files/partial_schemas/4f-data.yaml @@ -0,0 +1,2 @@ +- foo: + - bar: True diff --git a/tests/files/partial_schemas/4f-schema.yaml b/tests/files/partial_schemas/4f-schema.yaml new file mode 100644 index 0000000..d109e40 --- /dev/null +++ b/tests/files/partial_schemas/4f-schema.yaml @@ -0,0 +1,20 @@ +type: seq +sequence: + - include: fooone + +schema;fooone: + type: map + mapping: + foo: + include: footwo + +schema;footwo: + type: seq + sequence: + - include: foothree + +schema;foothree: + type: map + mapping: + bar: + type: str diff --git a/tests/files/partial_schemas/5f-data.yaml b/tests/files/partial_schemas/5f-data.yaml new file mode 100644 index 0000000..78cc18b --- /dev/null +++ b/tests/files/partial_schemas/5f-data.yaml @@ -0,0 +1 @@ +- - - - True diff --git a/tests/files/partial_schemas/5f-schema.yaml b/tests/files/partial_schemas/5f-schema.yaml new file mode 100644 index 0000000..788d906 --- /dev/null +++ b/tests/files/partial_schemas/5f-schema.yaml @@ -0,0 +1,18 @@ +type: seq +sequence: + - include: fooone + +schema;fooone: + type: seq + sequence: + - include: footwo + +schema;footwo: + type: seq + sequence: + - include: foothree + +schema;foothree: + type: seq + sequence: + - type: str diff --git a/tests/files/partial_schemas/6f-data.yaml b/tests/files/partial_schemas/6f-data.yaml new file mode 100644 index 0000000..49b7ed7 --- /dev/null +++ b/tests/files/partial_schemas/6f-data.yaml @@ -0,0 +1,4 @@ +foo: + bar: + qwe: + ewq: True diff --git a/tests/files/partial_schemas/6f-schema.yaml b/tests/files/partial_schemas/6f-schema.yaml new file mode 100644 index 0000000..00c0a91 --- /dev/null +++ b/tests/files/partial_schemas/6f-schema.yaml @@ -0,0 +1,22 @@ +type: map +mapping: + foo: + include: fooone + +schema;fooone: + type: map + mapping: + bar: + include: footwo + +schema;footwo: + type: map + mapping: + qwe: + include: foothree + +schema;foothree: + type: map + mapping: + ewq: + type: str diff --git a/tests/files/partial_schemas/7s-data.yaml b/tests/files/partial_schemas/7s-data.yaml new file mode 100644 index 0000000..d48b5a2 --- /dev/null +++ b/tests/files/partial_schemas/7s-data.yaml @@ -0,0 +1,5 @@ +foo: blah +bar: + - blah + - blah + - blah diff --git a/tests/files/partial_schemas/7s-schema.yaml b/tests/files/partial_schemas/7s-schema.yaml new file mode 100644 index 0000000..eaf0613 --- /dev/null +++ b/tests/files/partial_schemas/7s-schema.yaml @@ -0,0 +1,12 @@ +type: map +mapping: + foo: + type: str + required: True + bar: + include: bar +schema;bar: + type: seq + required: True + sequence: + - type: str diff --git a/tests/files/success/test_anchor.yaml b/tests/files/success/test_anchor.yaml new file mode 100644 index 0000000..fa53412 --- /dev/null +++ b/tests/files/success/test_anchor.yaml @@ -0,0 +1,92 @@ +## +--- +name: anchor1 +desc: schema with anchor +# +schema: + type: seq + required: true + sequence: + - type: map + required: true + mapping: + first-name: &name + type: str + required: True + family-name: *name +# +data: + - first-name: foo + family-name: Foo + - first-name: bar + family-name: Bar +## +--- +name: anchor2 +desc: schema with anchor 2 +# +schema: + type: map + required: true + mapping: + title: &name + type: str + required: true + address-book: + type: seq + required: true + sequence: + - type: map + mapping: + name: *name + email: + type: str + required: True +# +data: + title: my friends + address-book: + - name: foo + email: foo@mail.com + - name: bar + email: bar@mail.com +# +# TODO: THIS TEST IS BROKEN BECUASE IT CAUSE INFINITE RECURSION IN PYTHON +# +# ## +# --- +# name: anchor3 +# desc: document with anchor +# # +# schema: +# type: seq +# sequence: +# - &employee +# type: map +# mapping: +# name: +# type: str +# post: +# type: str +# enum: +# - exective +# - manager +# - clerk +# supervisor: *employee +# # +# data: +# - &foo +# name: foo +# post: exective +# - &bar +# name: bar +# post: manager +# supervisor: *foo +# - &baz +# name: baz +# post: clerk +# supervisor: *bar +# - &zak +# name: zak +# post: clerk +# supervisor: *bar diff --git a/tests/files/success/test_assert.yaml b/tests/files/success/test_assert.yaml new file mode 100644 index 0000000..46b3ef3 --- /dev/null +++ b/tests/files/success/test_assert.yaml @@ -0,0 +1,28 @@ +## +--- +name: assert1 +desc: assert test +# +schema: + type: seq + sequence: + - type: map + mapping: + "less-than": + type: number + assert: val < 8 + "more-than": + type: number + assert: 3 < val + "between": + type: number + assert: 3 < val and val < 8 + "except": + type: number + assert: val < 3 or 8 < val +# +data: + - less-than: 5 + - more-than: 5 + - between: 5 + - except: 0 diff --git a/tests/files/success/test_default.yaml b/tests/files/success/test_default.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/success/test_desc.yaml b/tests/files/success/test_desc.yaml new file mode 100644 index 0000000..347a729 --- /dev/null +++ b/tests/files/success/test_desc.yaml @@ -0,0 +1,7 @@ +--- +name: desc-1 +desc: Test basic desc +data: 'foobar' +schema: + desc: This is a description... + type: str diff --git a/tests/files/success/test_enum.yaml b/tests/files/success/test_enum.yaml new file mode 100644 index 0000000..c583d42 --- /dev/null +++ b/tests/files/success/test_enum.yaml @@ -0,0 +1,12 @@ +--- +name: enum-1 +desc: Test simple enum +data: + - A + - B + - O +schema: + type: seq + sequence: + - type: str + enum: [A, B, O, AB] diff --git a/tests/files/success/test_example.yaml b/tests/files/success/test_example.yaml new file mode 100644 index 0000000..ee6965f --- /dev/null +++ b/tests/files/success/test_example.yaml @@ -0,0 +1,5 @@ +--- +data: foo +schema: + example: Foobar + type: str diff --git a/tests/files/success/test_extensions.yaml b/tests/files/success/test_extensions.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/success/test_func.yaml b/tests/files/success/test_func.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/success/test_ident.yaml b/tests/files/success/test_ident.yaml new file mode 100644 index 0000000..0f3c95c --- /dev/null +++ b/tests/files/success/test_ident.yaml @@ -0,0 +1,22 @@ +## +--- +name: ident1 +desc: ident constraint test +# +schema: + type: seq + sequence: + - type: map + mapping: + "name": + ident: true + "age": + type: int +# +data: + - name: foo + age: 10 + - name: bar + age: 10 + - name: baz + age: 10 diff --git a/tests/files/success/test_include.yaml b/tests/files/success/test_include.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/success/test_length.yaml b/tests/files/success/test_length.yaml new file mode 100644 index 0000000..1b8d7d8 --- /dev/null +++ b/tests/files/success/test_length.yaml @@ -0,0 +1,98 @@ +--- +name: length1 +desc: length test +schema: + type: map + mapping: + "max-only": + type: seq + sequence: + - type: str + length: + max: 8 + "min-only": + type: seq + sequence: + - type: str + length: + min: 4 + "max-and-min": + type: seq + sequence: + - type: str + length: + max: 8 + min: 4 +data: + max-only: + - hogehoge + - a + - + min-only: + - hoge + - hogehogehogehogehoge + max-and-min: + - hogehoge + - hoge +--- +name: length2 +desc: length test (with max-ex and min-ex) +schema: + type: map + mapping: + "max-ex-only": + type: seq + sequence: + - type: str + length: + max-ex: 8 + "min-ex-only": + type: seq + sequence: + - type: str + length: + min-ex: 4 + "max-ex-and-min-ex": + type: seq + sequence: + - type: str + length: + max-ex: 8 + min-ex: 4 +data: + max-ex-only: + - hogehog + - a + - + min-ex-only: + - hoge! + max-ex-and-min-ex: + - hogehog + - hoge! +--- +name: length3 +desc: length test (with min, max, max-ex and min-ex) +schema: + type: map + mapping: + "A": + type: seq + sequence: + - type: str + length: + max: 8 + min-ex: 4 + "B": + type: seq + sequence: + - type: str + length: + max-ex: 8 + min: 4 +data: + A: + - hogehoge + - hogeh + B: + - hogehog + - hoge diff --git a/tests/files/success/test_mapping.yaml b/tests/files/success/test_mapping.yaml new file mode 100644 index 0000000..fb82fd6 --- /dev/null +++ b/tests/files/success/test_mapping.yaml @@ -0,0 +1,308 @@ +--- +name: mapping1 +desc: Most basic mapping validation +data: + foo: bar +schema: + type: map + mapping: + foo: + type: str +--- +name: mapping2 +desc: Complex mapping that test several subtypes for each key +# +schema: + type: map + required: true + mapping: + name: + type: str + required: true + email: + type: str + # This pattern value was modified from /@/ to .+@.+ to make it copmatible with python + pattern: .+@.+ + required: True + age: + type: int + blood: + type: str + enum: + - A + - B + - O + - AB + birth: + type: date +data: + name: foo + email: foo@mail.com + age: 20 + blood: AB + birth: 1985-01-01 +--- +name: mapping3 +desc: Test that mapping works inside a sequence +data: + - foo: True +schema: + type: seq + sequence: + - type: map + mapping: + foo: + type: bool +--- +name: mapping4 +desc: Test that map inside seq inside map works +data: + company: Kuwata lab. + email: webmaster@kuwata-lab.com + employees: + - code: 101 + name: foo + email: foo@kuwata-lab.com + - code: 102 + name: bar + email: bar@kuwata-lab.com +schema: + type: map + mapping: + company: + type: str + required: True + email: + type: str + employees: + type: seq + sequence: + - type: map + mapping: + code: + type: int + required: True + name: + type: str + required: True + email: + type: str +--- +name: mapping5 +desc: test allowempty option to mapping +data: + datasources: + test1: test1.py + test2: test2.py +schema: + type: map + mapping: + datasources: + type: map + allowempty: True + mapping: + test1: + type: str +--- +name: mapping6 +desc: Test that regex keys works +data: + mic: + - input + - foo + mock: + - True + - False +schema: + type: map + matching-rule: "any" + mapping: + re;(mi.+): + type: seq + sequence: + - type: str + regex;(mo.+): + type: seq + sequence: + - type: bool +--- +name: mapping7 +desc: Test that mapping name works +data: + datasources: test1.py +schema: + type: map + mapping: + datasources: + type: str +--- +name: mapping8 +desc: Test that map shortcut works +data: + datasources: test1.py +schema: + type: map + map: + datasources: + type: str +--- +name: mapping9 +desc: Test that you do not have to specify type map +data: + streams: foobar +schema: + mapping: + streams: + type: str +--- +name: mapping10 +desc: Test that you do not have to specify type map when map exists in schema +data: + streams: foobar +schema: + map: + streams: + type: str +--- +name: mapping11 +desc: Test keyword regex default matching-rule any +data: + foobar1: 1 + foobar2: 2 + bar2: 3 +schema: + type: map + mapping: + regex;([1-2]$): + type: int + regex;(^foobar): + type: int +--- +name: mapping12 +desc: Test keyword regex declared matching-rule any +data: + foobar1: 1 + foobar2: 2 + bar2: 3 +schema: + type: map + matching-rule: 'any' + mapping: + regex;([1-2]$): + type: int + regex;(^foobar): + type: int +--- +name: mapping13 +desc: Test keyword regex declared matching-rule all +data: + foobar1: 1 + foobar2: 2 + foobar3: 3 +schema: + type: map + matching-rule: 'all' + mapping: + regex;([1-3]$): + type: int + regex;(^foobar): + type: int +--- +name: mapping14 +desc: Test mixed keyword regex and normal keyword +data: + standard: + FRIST-800-53 + AU-1: + family: AU + name: Audit and Accountability Policy and Procedures +schema: + type: map + mapping: + regex;/[A-Z]-/: + type: map + mapping: + name: + type: str + family: + type: str + required: True + standard: + type: str +--- +name: mapping-default-1 +desc: Test that default mapping keyword works out of the box in a good case +data: + OWNERSHIP: + - code: 1 + key: BLM-BURNS + alias: BLM-BURNS + WHT: foo +schema: + type: map + mapping: + WHT: + type: str + =: + type: seq + required: true + sequence: + - type: map + mapping: + 'code': + type: int + required: true + unique: true + 'key': + type: str + required: true + 'alias': + type: str + required: true +--- +name: mapping-default-2 +desc: default value of map with number type and no other key matching +# +schema: + type: map + mapping: + =: + type: number + range: + min: -10 + max: 10 +# +data: + value1: 0 + value2: 10 + value3: -10 +--- +name: mapping17 +desc: Test that allowempty works without specifying mapping keyword when used inside a sequence block +data: + rally: + plugins: + - netcreate-boot: rally/rally-plugins/netcreate-boot +schema: + type: map + mapping: + rally: + type: map + allowempty: True + mapping: + plugins: + type: seq + sequence: + - type: map + allowempty: True +--- +name: mapping18 +desc: Test that regexes can be 'required' +data: + person1: Jack + person2: Fred +schema: + type: map + mapping: + regex;(person[1-9]): + required: True \ No newline at end of file diff --git a/tests/files/success/test_matching.yaml b/tests/files/success/test_matching.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/success/test_merge.yaml b/tests/files/success/test_merge.yaml new file mode 100644 index 0000000..1e0a94d --- /dev/null +++ b/tests/files/success/test_merge.yaml @@ -0,0 +1,36 @@ +## +--- +name: merge1 +desc: merge maps +# +schema: + type: map + mapping: + "group": + type: map + mapping: + "name": &name + type: str + required: True + "email": &email + type: str + pattern: .+@.+ + required: False + "user": + type: map + mapping: + "name": + <<: *name # merge + length: + max: 16 # add + "email": + <<: *email # merge + required: True # override +# +data: + group: + name: foo + email: foo@mail.com + user: + name: bar + email: bar@mail.com diff --git a/tests/files/success/test_name.yaml b/tests/files/success/test_name.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/success/test_nullable.yaml b/tests/files/success/test_nullable.yaml new file mode 100644 index 0000000..df8ddf8 --- /dev/null +++ b/tests/files/success/test_nullable.yaml @@ -0,0 +1,11 @@ +--- +name: nullable1 +desc: Test that nullable works +data: + name: +schema: + type: map + mapping: + name: + type: str + nullable: True diff --git a/tests/files/success/test_pattern.yaml b/tests/files/success/test_pattern.yaml new file mode 100644 index 0000000..41a751b --- /dev/null +++ b/tests/files/success/test_pattern.yaml @@ -0,0 +1,18 @@ +--- +name: pattern1 +desc: Test simples pattern +data: foo@gmail.com +schema: + type: str + pattern: .+@.+ +--- +name: pattern2 +desc: Test simple pattern in list +data: + - foo@mail.com + - bar@mail.net +schema: + type: seq + sequence: + - type: str + pattern: .+@.+ diff --git a/tests/files/success/test_range.yaml b/tests/files/success/test_range.yaml new file mode 100644 index 0000000..ef3c18a --- /dev/null +++ b/tests/files/success/test_range.yaml @@ -0,0 +1,166 @@ +## +--- +name: range1 +desc: range test && bug#????? +# +schema: + type: map + mapping: + "max-only": + type: seq + sequence: + - type: number + required: True + range: {max: 100} + "min-only": + type: seq + sequence: + - type: number + required: True + range: {min: 10.0} + "max-and-min": + type: seq + sequence: + - type: number + required: True + range: {max: 100.0, min: 10.0} +# +data: + max-only: + - 100 + - 100.0 + min-only: + - 10 + - 10.0 + max-and-min: + - 100 + - 10 + - 100.0 + - 10.0 +## +--- +name: range2 +desc: range test (with max-ex and min-ex) +# +schema: + type: map + mapping: + "max-ex-only": + type: seq + sequence: + - type: number + required: True + range: {max-ex: 100} + "min-ex-only": + type: seq + sequence: + - type: number + required: True + range: {min-ex: 10.0} + "max-ex-and-min-ex": + type: seq + sequence: + - type: number + required: True + range: {max-ex: 100.0, min-ex: 10.0} +# +data: + max-ex-only: + - 99 + - 99.99999 + min-ex-only: + - 11 + - 10.00001 + max-ex-and-min-ex: + - 99 + - 11 + - 99.99999 + - 10.00001 +## +--- +name: range3 +desc: range test (with max, min, max-ex and min-ex) +# +schema: + type: map + mapping: + "A": + type: seq + sequence: + - type: number + required: True + range: {max: 100, min-ex: 10.0} + "B": + type: seq + sequence: + - type: number + required: True + range: {min: 10, max-ex: 100.0} +# +data: + A: + - 100 + - 10.00001 + B: + - 10 + - 99.99999 +--- +name: range4 +desc: Test range min/max works with map size +data: + foo: bar +schema: + type: map + range: + min: 1 + max: 3 + mapping: + foo: + type: str +--- +name: range5 +desc: Test range works with seq +data: + - foobar + - barfoo +schema: + type: seq + range: + min: 1 + max: 3 + sequence: + - type: str +--- +name: range6 +desc: test range on float type +data: + 2.0 +schema: + type: float + range: + min: 1 + max: 3 +--- +name: range7 +desc: Test range on float with negative boundary +data: + -0.9 +schema: + type: float + range: + min: -1 + max: 1.0 +--- +name: range8 +desc: Test range min-ex & max-ex +data: + - 20 + - 25 + - 29 +schema: + type: seq + sequence: + - type: int + range: + max-ex: 30 + min-ex: 18 diff --git a/tests/files/success/test_required.yaml b/tests/files/success/test_required.yaml new file mode 100644 index 0000000..9787843 --- /dev/null +++ b/tests/files/success/test_required.yaml @@ -0,0 +1,15 @@ +--- +name: required1 +desc: Test that req and required works +data: + name: foo + foo: bar +schema: + type: map + mapping: + name: + type: str + req: True + foo: + type: str + required: True diff --git a/tests/files/success/test_schema.yaml b/tests/files/success/test_schema.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/success/test_sequence.yaml b/tests/files/success/test_sequence.yaml new file mode 100644 index 0000000..3a4491f --- /dev/null +++ b/tests/files/success/test_sequence.yaml @@ -0,0 +1,44 @@ +## +--- +name: sequence1 +desc: sequence test +# +schema: + type: seq + sequence: + - type: str +data: + - foo + - bar + - baz +--- +name: sequence2 +desc: test sequence showrtcut +schema: + type: seq + seq: + - type: str +data: + - foo + - bar + - baz +--- +name: sequence3 +desc: Test that you do not have to specify type seq when keyword sequence is present +data: + - foo + - bar + - foobar +schema: + sequence: + - type: str +--- +name: sequence4 +desc: Test that you do not have to specify type seq when keyword seq is present +data: + - foo + - bar + - foobar +schema: + seq: + - type: str diff --git a/tests/files/success/test_sequence_multi.yaml b/tests/files/success/test_sequence_multi.yaml new file mode 100644 index 0000000..432145c --- /dev/null +++ b/tests/files/success/test_sequence_multi.yaml @@ -0,0 +1,64 @@ +--- +name: seq-multi-1 +desc: Test that multiple sequence values is supported +data: + - "foo" + - 123 +schema: + type: seq + matching: "any" + seq: + - type: str + - type: int +--- +name: seq-multi-2 +desc: Test that multiple sequence values with matching 'all' is supported +data: + - "foo" + - "123" +schema: + type: seq + matching: "all" + seq: + - type: str + - type: str +--- +name: seq-multi-3 +desc: Test that multiple sequence values with matching '*' is supported +data: + - "foo" +schema: + type: seq + matching: "*" + seq: + - type: bool + - type: int +--- +name: seq-multi-4 +desc: Test that multiple sequence values with nested data structures work +data: + - foo: 123 + - "foobar" +schema: + type: seq + matching: "any" + seq: + - type: str + - type: map + mapping: + foo: + type: int +--- +name: sq-multi-5 +desc: Test that multiple sequence vlaues with nested lists works +data: + - - 123 + - "foobar" +schema: + type: seq + matching: "any" + seq: + - type: str + - type: seq + sequence: + - type: int diff --git a/tests/files/success/test_type_any.yaml b/tests/files/success/test_type_any.yaml new file mode 100644 index 0000000..cc015e4 --- /dev/null +++ b/tests/files/success/test_type_any.yaml @@ -0,0 +1,27 @@ +--- +name: type-any-1 +desc: test simples case of any type +data: abc +schema: + type: any +--- +name: type-any-2 +desc: test any type inside sequence +data: + - abc + - 123 + - 3.14159 + - True +schema: + type: seq + sequence: + - type: any +--- +name: type-any-3 +desc: test any type validates a dict +data: + foobar: + barfoo: + opa: 1337 +schema: + type: any diff --git a/tests/files/success/test_type_bool.yaml b/tests/files/success/test_type_bool.yaml new file mode 100644 index 0000000..4857690 --- /dev/null +++ b/tests/files/success/test_type_bool.yaml @@ -0,0 +1,39 @@ +--- +name: bool1 +desc: Test simples bool value +data: True +schema: + type: bool +--- +name: bool2 +desc: Test bool value inside list +data: + - True + - False +schema: + type: seq + sequence: + - type: bool +--- +name: bool3 +desc: Test bool value in mapping +data: + foo: True +schema: + type: map + mapping: + foo: + type: bool +--- +name: bool4 +desc: Test bool inside nested map & seq +data: + foo: + - True +schema: + type: map + mapping: + foo: + type: seq + sequence: + - type: bool diff --git a/tests/files/success/test_type_date.yaml b/tests/files/success/test_type_date.yaml new file mode 100644 index 0000000..b6166e6 --- /dev/null +++ b/tests/files/success/test_type_date.yaml @@ -0,0 +1,40 @@ +--- +name: type-date-1 +desc: basic test for date type with default formats +data: "2017-01-01" +schema: + type: date +--- +name: type-date-2 +desc: Basic test for date type with defined date-formats +data: "31-01-2017" +schema: + type: date + format: "%d-%m-%Y" +--- +name: type-date-3 +desc: Basic test for date type with defined date-formats +data: + - "31-01-2017" + - "2017-01-31" +schema: + type: seq + sequence: + - type: date + format: + - "%d-%m-%Y" + - "%Y-%m-%d" +--- +name: type-data-4 +desc: Basic test for many different possible values with default formats +data: + - '1997' + - '1997-07' + - '1997-07-16' + - '1997-07-16T19:20+01:00' + - '1997-07-16T19:20:30+01:00' + - '1997-07-16T19:20:30.45+01:00' +schema: + type: seq + sequence: + - type: date diff --git a/tests/files/success/test_type_enum.yaml b/tests/files/success/test_type_enum.yaml new file mode 100644 index 0000000..bde9bd7 --- /dev/null +++ b/tests/files/success/test_type_enum.yaml @@ -0,0 +1,43 @@ +--- +name: type-enum-1 +desc: Test the most basic case for enum +data: C +schema: + type: str + enum: [A, B, C, D, E] +--- +name: type-enum-2 +desc: Test bool value inside list +data: + - B + - C +schema: + type: seq + sequence: + - type: str + enum: [A, B, C, D, E] +--- +name: type-enum-3 +desc: Test bool value in mapping +data: + foo: A +schema: + type: map + mapping: + foo: + type: str + enum: [A, B, C, D, E] +--- +name: type-enum-4 +desc: Test bool inside nested map & seq +data: + foo: + - C +schema: + type: map + mapping: + foo: + type: seq + sequence: + - type: str + enum: [A, B, C, D, E] diff --git a/tests/files/success/test_type_float.yaml b/tests/files/success/test_type_float.yaml new file mode 100644 index 0000000..ffd1bba --- /dev/null +++ b/tests/files/success/test_type_float.yaml @@ -0,0 +1,43 @@ +--- +name: float1 +desc: Test simples float value +data: 3.14159 +schema: + type: float +--- +name: float2 +desc: Test float value inside list +data: + - 1 + - 3.14159 +schema: + type: seq + sequence: + - type: float +--- +name: float3 +desc: Test float value in mapping +data: + foo: 3.14159 + bar: 1 +schema: + type: map + mapping: + foo: + type: float + bar: + type: float +--- +name: float4 +desc: Test float inside nested map & seq +data: + foo: + - 1 + - 3.14159 +schema: + type: map + mapping: + foo: + type: seq + sequence: + - type: float diff --git a/tests/files/success/test_type_int.yaml b/tests/files/success/test_type_int.yaml new file mode 100644 index 0000000..93dd805 --- /dev/null +++ b/tests/files/success/test_type_int.yaml @@ -0,0 +1,38 @@ +--- +name: int1 +desc: Test simples int value +data: 1 +schema: + type: int +--- +name: int2 +desc: Test int value inside list +data: + - 1 +schema: + type: seq + sequence: + - type: int +--- +name: int3 +desc: Test int value in mapping +data: + foo: 1 +schema: + type: map + mapping: + foo: + type: int +--- +name: int4 +desc: Test int inside nested map & seq +data: + foo: + - 1 +schema: + type: map + mapping: + foo: + type: seq + sequence: + - type: int diff --git a/tests/files/success/test_type_map.yaml b/tests/files/success/test_type_map.yaml new file mode 100644 index 0000000..010b0e9 --- /dev/null +++ b/tests/files/success/test_type_map.yaml @@ -0,0 +1,39 @@ +--- +name: type-map-1 +desc: Test the most basic case for map +data: + foo: bar +schema: + type: map + mapping: + foo: + type: str +--- +name: type-map-2 +desc: +data: + - foo: bar + - foo: bar +schema: + type: seq + sequence: + - type: map + mapping: + foo: + type: str +--- +name: type-map-3 +desc: Test bool inside nested map & seq +data: + foo: + - bar: foobar +schema: + type: map + mapping: + foo: + type: seq + sequence: + - type: map + mapping: + bar: + type: str diff --git a/tests/files/success/test_type_none.yaml b/tests/files/success/test_type_none.yaml new file mode 100644 index 0000000..db65652 --- /dev/null +++ b/tests/files/success/test_type_none.yaml @@ -0,0 +1,47 @@ +# +# NOTE: This case is not allowed becuase Core class do NOT allow +# there is no data to validate. This happens if None is at top level +# of the data structure. +# +# --- +# name: type-none-1 +# desc: Most basic test for type None +# data: ~ +# schema: +# type: none +--- +name: type-none-2 +desc: Test that none type works with none type as value in map +data: + name: ~ +schema: + type: map + mapping: + name: + type: none +--- +name: type-none-3 +desc: Test that none type works as value in sequence +data: + - ~ + - ~ +schema: + type: seq + sequence: + - type: none +--- +name: type-none-4 +desc: Test that none type works inside nested map, seq, map +data: + foo: + - bar: ~ +schema: + type: map + mapping: + foo: + type: seq + sequence: + - type: map + mapping: + bar: + type: none diff --git a/tests/files/success/test_type_number.yaml b/tests/files/success/test_type_number.yaml new file mode 100644 index 0000000..f0b5cb4 --- /dev/null +++ b/tests/files/success/test_type_number.yaml @@ -0,0 +1,76 @@ +--- +name: type-number-1 +desc: Most basic test for type number +data: '1337.0' +schema: + type: number +--- +name: type-number-2 +desc: Test that number type works with as value in map +data: + foo: 1337 + bar: 3.14159 + qwe: 0.0 + rty: '1337' + ewq: '3.14159' + dsa: '0.0' +schema: + type: map + mapping: + foo: + type: number + bar: + type: number + qwe: + type: number + rty: + type: number + ewq: + type: number + dsa: + type: number +--- +name: type-number-3 +desc: Test that different number values works as values in seq +data: + - 1337 + - 3.14159 + - 0.0 + - '1337' + - '3.14159' + - '0.0' +schema: + type: seq + sequence: + - type: number +--- +name: type-number-4 +desc: Test that number type works inside nested map, seq, map +data: + foobar: + - foo: 1337 + bar: 3.14159 + qwe: 0.0 + rty: '1337' + ewq: '3.14159' + dsa: '0.0' +schema: + type: map + mapping: + foobar: + type: seq + sequence: + - type: map + mapping: + foo: + type: number + bar: + type: number + qwe: + type: number + rty: + type: number + ewq: + type: number + dsa: + type: number diff --git a/tests/files/success/test_type_scalar.yaml b/tests/files/success/test_type_scalar.yaml new file mode 100644 index 0000000..da34401 --- /dev/null +++ b/tests/files/success/test_type_scalar.yaml @@ -0,0 +1,76 @@ +--- +name: type-scalar-1 +desc: Most basic test for type scalar +data: '1337.0' +schema: + type: scalar +--- +name: type-scalar-2 +desc: Test that scalar type works with as value in map +data: + foo: 1337 + bar: 3.14159 + qwe: True + rty: '1337' + ewq: '3.14159' + dsa: '0.0' +schema: + type: map + mapping: + foo: + type: scalar + bar: + type: scalar + qwe: + type: scalar + rty: + type: scalar + ewq: + type: scalar + dsa: + type: scalar +--- +name: type-scalar-3 +desc: Test that different scalar values works as values in seq +data: + - 1337 + - 3.14159 + - True + - '1337' + - '3.14159' + - '0.0' +schema: + type: seq + sequence: + - type: scalar +--- +name: type-scalar-4 +desc: Test that scalar type works inside nested map, seq, map +data: + foobar: + - foo: 1337 + bar: 3.14159 + qwe: True + rty: '1337' + ewq: '3.14159' + dsa: '0.0' +schema: + type: map + mapping: + foobar: + type: seq + sequence: + - type: map + mapping: + foo: + type: scalar + bar: + type: scalar + qwe: + type: scalar + rty: + type: scalar + ewq: + type: scalar + dsa: + type: scalar diff --git a/tests/files/success/test_type_seq.yaml b/tests/files/success/test_type_seq.yaml new file mode 100644 index 0000000..5eb1b16 --- /dev/null +++ b/tests/files/success/test_type_seq.yaml @@ -0,0 +1,36 @@ +--- +name: type-seq-1 +desc: Test the most basic case for seq +data: + - foo +schema: + type: seq + sequence: + - type: str +--- +name: type-seq-2 +desc: Test that seq in seq works +data: + - - True + - - False +schema: + type: seq + sequence: + - type: seq + sequence: + - type: bool +--- +name: type-seq-3 +desc: Test bool inside nested map & seq +data: + - foo: + - True +schema: + type: seq + sequence: + - type: map + mapping: + foo: + type: seq + sequence: + - type: bool diff --git a/tests/files/success/test_type_str.yaml b/tests/files/success/test_type_str.yaml new file mode 100644 index 0000000..e5717c9 --- /dev/null +++ b/tests/files/success/test_type_str.yaml @@ -0,0 +1,56 @@ +--- +name: str1 +desc: Test simples str value +data: "foobar" +schema: + type: str +--- +name: str2 +desc: Test str value inside list +data: + - 'foo' + - bar +schema: + type: seq + sequence: + - type: str +--- +name: str3 +desc: Test str value in mapping +data: + foo: 'foobar' +schema: + type: map + mapping: + foo: + type: str +--- +name: str4 +desc: Test str inside nested map & seq +data: + foo: + - 'foo' +schema: + type: map + mapping: + foo: + type: seq + sequence: + - type: str +--- +name: deftype1 +desc: default type test. Becuase str is the default type it is in this file. +# +schema: + type: seq + sequence: + - type: map + mapping: + "name": + "email": +# +data: + - name: foo + email: foo@mail.com + - name: bar + - email: baz@mail.com diff --git a/tests/files/success/test_type_symbol.yaml b/tests/files/success/test_type_symbol.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/files/success/test_type_text.yaml b/tests/files/success/test_type_text.yaml new file mode 100644 index 0000000..7f218e2 --- /dev/null +++ b/tests/files/success/test_type_text.yaml @@ -0,0 +1,75 @@ +--- +name: type-text-1 +desc: Test simples text type +data: "foobar" +schema: + type: text +--- +name: type-text-2 +desc: Test possible values as values in seq +data: + - 'abc' + - '1337' + - '3.14159' + - 1337 + - 3.14159 +schema: + type: seq + sequence: + - type: text +--- +name: type-text-3 +desc: Test possible values as values in map +data: + foo: 1337 + bar: 3.14159 + qwe: 'abc' + rty: '1337' + ewq: '3.14159' + dsa: '0.0' +schema: + type: map + mapping: + foo: + type: text + bar: + type: text + qwe: + type: text + rty: + type: text + ewq: + type: text + dsa: + type: text +--- +name: type-text-4 +desc: Test that text type works inside nested map, seq, map +data: + foobar: + - foo: 1337 + bar: 3.14159 + qwe: 'abc' + rty: '1337' + ewq: '3.14159' + dsa: '0.0' +schema: + type: map + mapping: + foobar: + type: seq + sequence: + - type: map + mapping: + foo: + type: text + bar: + type: text + qwe: + type: text + rty: + type: text + ewq: + type: text + dsa: + type: text diff --git a/tests/files/success/test_type_timestamp.yaml b/tests/files/success/test_type_timestamp.yaml new file mode 100644 index 0000000..5bcfbcc --- /dev/null +++ b/tests/files/success/test_type_timestamp.yaml @@ -0,0 +1,37 @@ +--- +name: type-timestamp-1 +desc: Most basic timestamp test +data: "2015-03-29T18:45:00+00:00" +schema: + type: timestamp +--- +name: type-timestamp-2 +desc: Test timestamps as values in seq +data: + - "2015-03-29T18:45:00+00:00" + - "2015-03-29T18:45:00" + - "2015-03-29T11:45:00 -0700" + - "2015-03-29" +schema: + type: seq + sequence: + - type: timestamp +--- +name: type-timestamp-3 +desc: Basic test of different types of timestamps +data: + d1: "2015-03-29T18:45:00+00:00" + d2: "2015-03-29T18:45:00" + d3: "2015-03-29T11:45:00 -0700" + d4: "2015-03-29" +schema: + type: map + mapping: + d1: + type: timestamp + d2: + type: timestamp + d3: + type: timestamp + d4: + type: timestamp diff --git a/tests/files/success/test_unique.yaml b/tests/files/success/test_unique.yaml new file mode 100644 index 0000000..20cb64f --- /dev/null +++ b/tests/files/success/test_unique.yaml @@ -0,0 +1,138 @@ +--- +name: unique1 +desc: unique constraint test with map +# +schema: + type: seq + sequence: + - type: map + mapping: + "name": + unique: true + "age": + type: int +# +data: + - name: foo + age: 10 + - name: bar + age: 10 + - name: baz + age: 10 +--- +name: unique2 +desc: unique constraint test with seq +# +schema: + type: seq + sequence: + - type: str + unique: true +# +data: + - foo + - ~ + - bar + - ~ + - baz +--- +name: unique3 +desc: unique constraint and '<<' (merge) +# +schema: + type: seq + sequence: + - type: map + mapping: + "name": + type: str + required: True + unique: true + "value": + type: any + required: True +# +data: + # no sense + - name: x1 + value: 10 + - name: x2 + value: 20 +--- +name: unique4 +desc: unique constrant and anchor +# +schema: + type: seq + sequence: + - type: map + mapping: + "name": + type: str + "value": + type: any +# +data: + # no sense + - name: x1 + value: 10 + - name: x2 + value: 20 +--- +name: unique5 +desc: unique constring in nested data structures +data: + - name: foo + email: admin@mail.com + groups: + - users + - foo + - admin + - name: bar + email: admin@mail.com + groups: + - users + - admin + - name: baz + email: baz@mail.com + groups: + - users +schema: + type: seq + sequence: + - type: map + required: True + mapping: + name: + type: str + required: True + unique: True + email: + type: str + groups: + type: seq + sequence: + - type: str + unique: True +--- +name: unique6 +desc: Test that unique do not fail when the key it tries to lookup is missing +data: + - xref: 'GOC:hm' + uri: 'GOC:hm' + - uri: 'http://orcid.org/0000-0002-4862-3181' +schema: + type: seq + sequence: + - type: map + mapping: + "uri": + type: str + required: true + unique: true + pattern: ^((ht|f)tp(s?)\:\/\/\w[\/\.\-\:\w]+)|(GOC\:[\w\_]+)$ + "xref": + type: str + required: false + unique: true + pattern: ^\w+:\w+$ diff --git a/tests/files/success/test_version.yaml b/tests/files/success/test_version.yaml new file mode 100644 index 0000000..32621ba --- /dev/null +++ b/tests/files/success/test_version.yaml @@ -0,0 +1,5 @@ +--- +data: foo +schema: + version: 1.0.0 + type: str diff --git a/tests/files/unicode/1f.yaml b/tests/files/unicode/1f.yaml new file mode 100644 index 0000000..97d51f5 --- /dev/null +++ b/tests/files/unicode/1f.yaml @@ -0,0 +1,13 @@ +schema: + type: map + mapping: + msg: + type: int + Alô: + type: int +data: + msg: "Alô do Brasil!!" + Alô: "Brasil!!" +errors: + - "Value 'Alô do Brasil!!' is not of type 'int'. Path: '/msg'" + - "Value 'Brasil!!' is not of type 'int'. Path: '/Alô'" diff --git a/tests/files/unicode/1s.yaml b/tests/files/unicode/1s.yaml new file mode 100644 index 0000000..ab9fff9 --- /dev/null +++ b/tests/files/unicode/1s.yaml @@ -0,0 +1,10 @@ +schema: + type: map + mapping: + msg: + type: str + Alô: + type: str +data: + msg: "Alô do Brasil!!" + Alô: "Brasil!!" diff --git a/tests/files/unicode/3f.yaml b/tests/files/unicode/3f.yaml new file mode 100644 index 0000000..a963f1b --- /dev/null +++ b/tests/files/unicode/3f.yaml @@ -0,0 +1,10 @@ +schema: + type: seq + sequence: + - type: int +data: + - foobar + - åäö +errors: + - "Value 'foobar' is not of type 'int'. Path: '/0'" + - "Value 'åäö' is not of type 'int'. Path: '/1'" diff --git a/tests/files/unicode/3s.yaml b/tests/files/unicode/3s.yaml new file mode 100644 index 0000000..3ab54de --- /dev/null +++ b/tests/files/unicode/3s.yaml @@ -0,0 +1,7 @@ +schema: + type: seq + sequence: + - type: str +data: + - foobar + - åäö diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..33da698 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +# python std lib +import os +import sys + +# pykwalify package imports +from pykwalify import cli + + +class TestCLI(object): + + def test_cli(self, tmpdir): + """ + Test that when passing in certain arguments from commandline they + are handled correctly by docopt and correct args structure is returned. + """ + input = tmpdir.join("cli/1a.yaml") + schema_file = tmpdir.join("cli/1b.yaml") + + sys.argv = [ + 'scripts/pykwalify', + '-d', str(input), + '-s', str(schema_file), + '-v' + ] + + expected = { + '--data-file': str(input), + '--schema-file': [str(schema_file)], + '--quiet': False, + '--verbose': 1, + } + + cli_args = cli.parse_cli() + + for k, v in expected.items(): + assert k in cli_args + assert cli_args[k] == expected[k] + + def f(self, *args): + """ + Returns abs path to test files inside tests/files/ + """ + return os.path.join(os.path.dirname(os.path.realpath(__file__)), "files", *args) + + def test_run_cli(self): + """ + This should test that running the cli still works as expected + """ + input = self.f("cli/1a.yaml") + schema_file = self.f("cli/1b.yaml") + + sys.argv = [ + 'scripts/pykwalify', + '-d', str(input), + '-s', str(schema_file), + ] + + cli_args = cli.parse_cli() + c = cli.run(cli_args) + assert c.validation_errors == [] 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("bar") + + schema_f = tmpdir.join("bar.xml") + schema_f.write("bar") + + 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'))) diff --git a/tests/test_core_methods.py b/tests/test_core_methods.py new file mode 100644 index 0000000..2208b79 --- /dev/null +++ b/tests/test_core_methods.py @@ -0,0 +1,308 @@ +# -*- coding: utf-8 -*- +import pytest +from datetime import datetime + +from pykwalify.compat import unicode +from pykwalify.core import Core +# from pykwalify.errors import NotSequenceError, CoreError +from pykwalify.errors import CoreError + + +class Rule(object): + def __init__(self, sequence=None, mapping=None, rule_type=None): + self.sequence = sequence or [] + self.mapping = mapping or {} + self.type = rule_type or '' + + +def _remap_errors(c): + return [unicode(error) for error in c.errors] + + +# TODO: Refactor this becuase it no longer raises NotSequenceError but it now adds an error to the +# error stack and it should look for that one instead. +# def test_validate_sequence(): +# # If the type is set to sequence but value is int, it should raise NotSequenceError +# with pytest.raises(NotSequenceError): +# c = Core(source_data={}, schema_data={}) +# c._validate_sequence(123, Rule(sequence=['']), '', []) + + +def ec(): + # Return a empty core object + return Core(source_data={}, schema_data={}) + + +def test_validate_range(): + data_matrix = [ + (10, 5, 10, 5, 7, []), + (None, None, None, None, 7, []), + + (10, 5, None, None, 13, ["Type 'prefix' has size of '13', greater than max limit '10'. Path: '/'"]), + (10, 5, None, None, 3, ["Type 'prefix' has size of '3', less than min limit '5'. Path: '/'"]), + (10, 5, None, None, 13.5, ["Type 'prefix' has size of '13.5', greater than max limit '10'. Path: '/'"]), + (10, 5, None, None, 3.5, ["Type 'prefix' has size of '3.5', less than min limit '5'. Path: '/'"]), + (10, 5, None, None, 10, []), + (10, 5, None, None, 5, []), + (10, 5, None, None, 10.0, []), + (10, 5, None, None, 5.0, []), + + (None, None, 10, 5, 13, ["Type 'prefix' has size of '13', greater than or equals to max limit(exclusive) '10'. Path: '/'"]), + (None, None, 10, 5, 3, ["Type 'prefix' has size of '3', less than or equals to min limit(exclusive) '5'. Path: '/'"]), + (None, None, 10, 5, 13.5, ["Type 'prefix' has size of '13.5', greater than or equals to max limit(exclusive) '10'. Path: '/'"]), + (None, None, 10, 5, 3.5, ["Type 'prefix' has size of '3.5', less than or equals to min limit(exclusive) '5'. Path: '/'"]), + (None, None, 10, 5, 10, ["Type 'prefix' has size of '10', greater than or equals to max limit(exclusive) '10'. Path: '/'"]), + (None, None, 10, 5, 5, ["Type 'prefix' has size of '5', less than or equals to min limit(exclusive) '5'. Path: '/'"]), + (None, None, 10, 5, 8, []), + (None, None, 10, 5, 7, []), + (None, None, 10, 5, 8.5, []), + (None, None, 10, 5, 7.5, []), + ] + + for max_, min_, max_ex, min_ex, value, errors in data_matrix: + print(u"Testing data: {0} {1} {2} {3} {4}".format(max_, min_, max_ex, min_ex, value)) + c = ec() + c._validate_range(max_, min_, max_ex, min_ex, value, '/', 'prefix') + assert _remap_errors(c) == errors + + # Test value type validation inside the method + with pytest.raises(CoreError): + c = ec() + c._validate_range(5, 1, None, None, [1, 2, 3], '/', 'prefix') + + with pytest.raises(CoreError): + c = ec() + c._validate_range(5, 1, None, None, {'a': 1, 'b': 2, 'c': 3}, '/', 'prefix') + + +def test_validate_timestamp(): + data_matrix = [ + ("", ["Timestamp value is empty. Path: ''"]), + ("1234567", []), + ("2016-01-01", []), + ("2016-01-01 15:01", []), + (123, []), + (1.5, []), + (0, ["Integer value of timestamp can't be below 0"]), + (-1, ["Integer value of timestamp can't be below 0"]), + (3147483647, ["Integer value of timestamp can't be above 2147483647"]), + ([], ["Not a valid timestamp"]), + (datetime.now(), []), + (datetime.today(), []), + ] + + for data in data_matrix: + c = ec() + c._validate_scalar_timestamp(data[0], '') + assert _remap_errors(c) == data[1] + + +def test_validate_date(): + formats = ["%Y-%m-%d"] + + data_matrix = [ + (datetime.now(), [[], []]), + (datetime.today(), [[], []]), + ("1234567", [["Not a valid date: 1234567 format: %Y-%m-%d. Path: ''"], []]), + ("2016-01-01", [[], []]), + ("2016-01-01 15:01", [["Not a valid date: 2016-01-01 15:01 format: %Y-%m-%d. Path: ''"], []]), + (-1, [["Not a valid date: -1 date must be a string or a datetime.date not a 'int'"], []]), + (0, [["Not a valid date: 0 date must be a string or a datetime.date not a 'int'"], []]), + (1.5, [["Not a valid date: 1.5 date must be a string or a datetime.date not a 'float'"], []]), + (3147483647, [["Not a valid date: 3147483647 date must be a string or a datetime.date not a 'int'"], []]), + ([], [["Not a valid date: [] date must be a string or a datetime.date not a 'list'"], []]), + ({}, [["Not a valid date: {} date must be a string or a datetime.date not a 'dict'"], []]), + ] + + for data in data_matrix: + for i, format in enumerate(formats): + print("Validating: {0} Format: {1}".format(data[0], format)) + c = ec() + c._validate_scalar_date(data[0], [format], '') + assert _remap_errors(c) == data[1][i] + + +def test_validate_scalar_type(): + # Test that when providing a scalar type that do not exists, it should raise an error + with pytest.raises(CoreError): + c = ec() + c._validate_scalar_type(True, True, '') + + data_matrix = [] + + # Tests for str + data_matrix += [ + ("", "str", []), + ("123", "str", []), + ("yes", "str", []), + ("no", "str", []), + (b"foobar", "str", []), + (u"Néron", "str", []), + (123, "str", ["Value '123' is not of type 'str'. Path: ''"]), + (None, "str", ["Value 'None' is not of type 'str'. Path: ''"]), + (3.14, "str", ["Value '3.14' is not of type 'str'. Path: ''"]), + (True, "str", ["Value 'True' is not of type 'str'. Path: ''"]), + ({'a': 'b'}, "str", ["Value '{'a': 'b'}' is not of type 'str'. Path: ''"]), + (['a', 'b'], "str", ["Value '['a', 'b']' is not of type 'str'. Path: ''"]), + ] + + # Tests for int + data_matrix += [ + (123, "int", []), + (3.14, "int", ["Value '3.14' is not of type 'int'. Path: ''"]), + ("", "int", ["Value '' is not of type 'int'. Path: ''"]), + ("123", "int", ["Value '123' is not of type 'int'. Path: ''"]), + # (b"foobar", "int", ["Value b'foobar' is not of type 'int'. Path: ''"]), + (u"Néron", "int", [u"Value 'Néron' is not of type 'int'. Path: ''"]), + (None, "int", ["Value 'None' is not of type 'int'. Path: ''"]), + (True, "int", ["Value 'True' is not of type 'int'. Path: ''"]), + ({'a': 'b'}, "int", ["Value '{'a': 'b'}' is not of type 'int'. Path: ''"]), + (['a', 'b'], "int", ["Value '['a', 'b']' is not of type 'int'. Path: ''"]), + ] + + # Tests for float type + data_matrix += [ + ("1e-06", 'float', []), + ("1z-06", 'float', ["Value '1z-06' is not of type 'float'. Path: ''"]), + (1.5, 'float', []), + ("abc", 'float', ["Value 'abc' is not of type 'float'. Path: ''"]), + # (b"abc", 'float', ["Value 'abc' is not of type 'float'. Path: ''"]), + (u"abc", 'float', ["Value 'abc' is not of type 'float'. Path: ''"]), + (True, 'float', ["Value 'True' is not of type 'float'. Path: ''"]), + ] + + # Tests for bool + data_matrix += [ + (True, "bool", []), + (False, "bool", []), + (1, "bool", ["Value '1' is not of type 'bool'. Path: ''"]), + (3.14, "bool", ["Value '3.14' is not of type 'bool'. Path: ''"]), + ("", "bool", ["Value '' is not of type 'bool'. Path: ''"]), + ("yes", "bool", ["Value 'yes' is not of type 'bool'. Path: ''"]), + ("no", "bool", ["Value 'no' is not of type 'bool'. Path: ''"]), + # (b"foobar", "bool", [b"Value 'foobar' is not of type 'bool'. Path: ''"]), + (u"Néron", "bool", [u"Value 'Néron' is not of type 'bool'. Path: ''"]), + ([], "bool", ["Value '[]' is not of type 'bool'. Path: ''"]), + ({}, "bool", ["Value '{}' is not of type 'bool'. Path: ''"]), + ] + + # Tests for number + data_matrix += [ + (1, "number", []), + (3.14, "number", []), + (True, "number", ["Value 'True' is not of type 'number'. Path: ''"]), + (False, "number", ["Value 'False' is not of type 'number'. Path: ''"]), + ("", "number", ["Value '' is not of type 'number'. Path: ''"]), + ("yes", "number", ["Value 'yes' is not of type 'number'. Path: ''"]), + ("no", "number", ["Value 'no' is not of type 'number'. Path: ''"]), + # (b"foobar", "number", [b"Value 'foobar' is not of type 'number'. Path: ''"]), + (u"Néron", "number", [u"Value 'Néron' is not of type 'number'. Path: ''"]), + ([], "number", ["Value '[]' is not of type 'number'. Path: ''"]), + ({}, "number", ["Value '{}' is not of type 'number'. Path: ''"]), + ] + + # Tests for text + data_matrix += [ + (1, "text", []), + (3.14, "text", []), + ("", "text", []), + ("yes", "text", []), + ("no", "text", []), + # (b"foobar", "text", []), + (u"Néron", "text", []), + (True, "text", ["Value 'True' is not of type 'text'. Path: ''"]), + (False, "text", ["Value 'False' is not of type 'text'. Path: ''"]), + ([], "text", ["Value '[]' is not of type 'text'. Path: ''"]), + ({}, "text", ["Value '{}' is not of type 'text'. Path: ''"]), + (datetime(2015, 10, 24, 10, 22, 18), "text", ["Value '2015-10-24 10:22:18' is not of type 'text'. Path: ''"]), + ] + + # Tests for any + data_matrix += [ + (1, "any", []), + (3.14, "any", []), + (True, "any", []), + (False, "any", []), + ("", "any", []), + ("yes", "any", []), + ("no", "any", []), + # (b"foobar", "any", []), + (u"Néron", "any", []), + ([], "any", []), + ({}, "any", []), + (datetime(2015, 10, 24, 10, 22, 18), "any", []), + ] + + # Tests for enum + data_matrix += [ + ("", "enum", []), + ("123", "enum", []), + ("yes", "enum", []), + ("no", "enum", []), + # (b"foobar", "enum", []), + (u"Néron", "enum", []), + (123, "enum", ["Value '123' is not of type 'enum'. Path: ''"]), + (None, "enum", ["Value 'None' is not of type 'enum'. Path: ''"]), + (3.14, "enum", ["Value '3.14' is not of type 'enum'. Path: ''"]), + (True, "enum", ["Value 'True' is not of type 'enum'. Path: ''"]), + ({'a': 'b'}, "enum", ["Value '{'a': 'b'}' is not of type 'enum'. Path: ''"]), + (['a', 'b'], "enum", ["Value '['a', 'b']' is not of type 'enum'. Path: ''"]), + ] + + # Tests for none + data_matrix += [ + ("", "none", ["Value '' is not of type 'none'. Path: ''"]), + ("123", "none", ["Value '123' is not of type 'none'. Path: ''"]), + ("yes", "none", ["Value 'yes' is not of type 'none'. Path: ''"]), + ("no", "none", ["Value 'no' is not of type 'none'. Path: ''"]), + ("None", "none", ["Value 'None' is not of type 'none'. Path: ''"]), + # (b"foobar", "none", [b"Value 'foobar' is not of type 'none'. Path: ''"]), + (u"Néron", "none", [u"Value 'Néron' is not of type 'none'. Path: ''"]), + (123, "none", ["Value '123' is not of type 'none'. Path: ''"]), + (None, "none", []), + (3.14, "none", ["Value '3.14' is not of type 'none'. Path: ''"]), + (True, "none", ["Value 'True' is not of type 'none'. Path: ''"]), + ({'a': 'b'}, "none", ["Value '{'a': 'b'}' is not of type 'none'. Path: ''"]), + (['a', 'b'], "none", ["Value '['a', 'b']' is not of type 'none'. Path: ''"]), + + ] + + # Tests for timestamp + data_matrix += [ + ("", 'timestamp', []), + ("1234567", 'timestamp', []), + ("2016-01-01", 'timestamp', []), + ("2016-01-01 15:01", 'timestamp', []), + # (b"foobar", "timestamp", []), + (u"Néron", "timestamp", []), + (123, 'timestamp', []), + (1.5, 'timestamp', []), + (0, 'timestamp', []), + (-1, 'timestamp', []), + (3147483647, 'timestamp', []), + ([], 'timestamp', ["Value '[]' is not of type 'timestamp'. Path: ''"]), + (datetime.now(), 'timestamp', []), + (datetime.today(), 'timestamp', []), + ] + + data_matrix += [ + (datetime(2015, 10, 24, 10, 22, 18), 'scalar', []), + ("", "scalar", []), + ("2016-01-01", 'scalar', []), + ("2016-01-01 15:01", 'scalar', []), + ("123", 'scalar', []), + ("yes", 'scalar', []), + (u"Néron", 'scalar', []), + (None, 'scalar', ["Value 'None' is not of type 'scalar'. Path: ''"]), + (123, 'scalar', []), + (3.14, 'scalar', []), + (True, 'scalar', []), + ({'a': 'b'}, 'scalar', ["Value '{'a': 'b'}' is not of type 'scalar'. Path: ''"]), + (['a', 'b'], 'scalar', ["Value '['a', 'b']' is not of type 'scalar'. Path: ''"]), + ] + + for data in data_matrix: + print(u"Testing data: '{0!s}', '{1!s}', '{2!s}'".format(*data)) + c = ec() + c._validate_scalar_type(data[0], data[1], '') + assert _remap_errors(c) == data[2] diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py new file mode 100644 index 0000000..7f02aab --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +# pykwalify imports +from pykwalify import errors + + +class TestCLI(object): + + def test_base_exception(self): + # retcode=2 should be schemaerror + e = errors.PyKwalifyException(msg="foobar", retcode=2) + assert e.__repr__() == "PyKwalifyException(msg='foobar')" + assert e.retname == "schemaerror" + + def test_create_sub_class_exceptions(self): + u_e = errors.UnknownError() + assert u_e.retcode == 1 + + s_e = errors.SchemaError() + assert s_e.retcode == 2 + + c_e = errors.CoreError() + assert c_e.retcode == 3 + + r_e = errors.RuleError() + assert r_e.retcode == 4 + + sc_e = errors.SchemaConflict() + assert sc_e.retcode == 5 diff --git a/tests/test_helper.py b/tests/test_helper.py new file mode 100644 index 0000000..8976b5f --- /dev/null +++ b/tests/test_helper.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +# python std lib +import logging +import logging.config + +log = logging.getLogger() + + +# Set the root logger to be silent so all code that uses the python logger +# will not print anything unless we want it to, then it should be specified +# in each test and reseted after that test +def _set_log_lv(level=1337, loggers=None): + """ If no level is set then level will be so high all logging is silenced + """ + if loggers is None: + # If no additional loggers is specified then only apply to root logger + log.setLevel(level) + for handler in log.handlers: + handler.level = level + else: + # If we have other logging instances specified apply to root logger and them + if log not in loggers: + loggers.append(log) + + for log_instance in loggers: + log_instance.setLevel(level) + for handler in log_instance.handlers: + handler.level = level + + +# Initially silence all logging +_set_log_lv() diff --git a/tests/test_rule.py b/tests/test_rule.py new file mode 100644 index 0000000..4b2b7c9 --- /dev/null +++ b/tests/test_rule.py @@ -0,0 +1,399 @@ +# -*- coding: utf-8 -*- + +""" Unit test for pyKwalify - Rule """ + +# python std lib +import unittest + +# 3rd party imports +import pytest + +# pyKwalify imports +import pykwalify +from pykwalify.errors import RuleError, SchemaConflict +from pykwalify.rule import Rule +from pykwalify.compat import unicode + + +class TestRule(unittest.TestCase): + + def setUp(self): + pykwalify.partial_schemas = {} + + def test_schema(self): + # Test that when using both schema; and include tag that it throw an error because schema; tags should be parsed via Core() + with pytest.raises(RuleError) as r: + Rule(schema={"schema;str": {"type": "map", "mapping": {"foo": {"type": "str"}}}, "type": "map", "mapping": {"foo": {"include": "str"}}}) + assert str(r.value) == "" + assert r.value.error_key == 'schema.not.toplevel' + + def test_unkknown_key(self): + # Test that providing an unknown key raises exception + with pytest.raises(RuleError) as r: + Rule(schema={"type": "str", "foobar": True}) + assert str(r.value) == "" + assert r.value.error_key == 'key.unknown' + + def test_matching_rule(self): + # Test that exception is raised when a invalid matching rule is used + with pytest.raises(RuleError) as r: + Rule(schema={"type": "map", "matching-rule": "foobar", "mapping": {"regex;.+": {"type": "seq", "sequence": [{"type": "str"}]}}}) + assert str(r.value) == "" + assert r.value.error_key == 'matching_rule.not_allowed' + + def test_allow_empty_map(self): + r = Rule(schema={"type": "map", "allowempty": True, "mapping": {"name": {"type": "str"}}}) + assert r.allowempty_map is True + + def test_type_value(self): + # TODO: This test is currently semi broken, partial schemas might be somewhat broken possibly + # # Test that when only having a schema; rule it should throw error + # with pytest.raises(RuleError) as r: + # Rule(schema={"schema;fooone": {"type": "map", "mapping": {"foo": {"type": "str"}}}}) + # assert str(r.value) == "" + # assert r.value.error_key == 'type.missing' + + # Test a valid rule with both "str" and "unicode" types work + r = Rule(schema={"type": str("str")}) + r = Rule(schema={"type": unicode("str")}) + + # Test that type key must be string otherwise exception is raised + with pytest.raises(RuleError) as r: + Rule(schema={"type": 1}) + assert str(r.value) == "" + assert r.value.error_key == 'type.not_string' + + # this tests that the type key must be a string + with pytest.raises(RuleError) as r: + Rule(schema={"type": 1}, parent=None) + assert str(r.value) == "" + assert r.value.error_key == 'type.not_string' + + def test_name_value(self): + with pytest.raises(RuleError) as r: + Rule(schema={'type': 'str', 'name': {}}) + assert str(r.value) == "" + + def test_nullable_value(self): + # Test that nullable value must be bool otherwise exception is raised + with pytest.raises(RuleError) as r: + Rule(schema={"type": "str", "nullable": "foobar"}) + assert str(r.value) == "" + assert r.value.error_key == 'nullable.not_bool' + + def test_desc_value(self): + with pytest.raises(RuleError) as r: + Rule(schema={'type': 'str', 'desc': []}) + assert str(r.value) == "" + + def test_example_value(self): + with pytest.raises(RuleError) as r: + Rule(schema={'type': 'str', 'example': []}) + assert str(r.value) == "" + + def test_required_value(self): + # Test that required value must be bool otherwise exception is raised + with pytest.raises(RuleError) as r: + Rule(schema={"type": "str", "required": "foobar"}) + assert str(r.value) == "" + assert r.value.error_key == 'required.not_bool' + + def test_pattern_value(self): + # this tests a invalid regexp pattern + with pytest.raises(RuleError) as r: + Rule(schema={"type": "str", "pattern": "/@/\\"}) + assert str(r.value) == "" + assert r.value.error_key == 'pattern.syntax_error' + + # Test that pattern keyword is not allowed when using a map + # with self.assertRaisesRegexp(RuleError, ".+map\.pattern.+"): + with pytest.raises(RuleError) as r: + Rule(schema={"type": "map", "pattern": "^[a-z]+$", "allowempty": True, "mapping": {"name": {"type": "str"}}}) + assert str(r.value) == "" + assert r.value.error_key == 'pattern.not_allowed_in_map' + + # Test that pattern value must be string otherwise exception is raised + with pytest.raises(RuleError) as r: + Rule(schema={"type": "str", "pattern": 1}) + assert str(r.value) == "" + assert r.value.error_key == 'pattern.not_string' + + def test_date_and_format_value(self): + r = Rule(schema={"type": "date", "format": "%y"}) + assert r.format is not None, "date var not set proper" + assert isinstance(r.format, list), "date format should be a list" + with pytest.raises(RuleError) as r: + Rule(schema={"type": "date", "format": 1}) + assert str(r.value) == "" + with pytest.raises(RuleError) as r: + Rule(schema={"type": "map", "format": "%y"}) + assert str(r.value) == "" + + def test_enum_value(self): + # this tests the various valid enum types + Rule(schema={"type": "int", "enum": [1, 2, 3]}) + Rule(schema={"type": "bool", "enum": [True, False]}) + r = Rule(schema={"type": "str", "enum": ["a", "b", "c"]}) + assert r.enum is not None, "enum var is not set proper" + assert isinstance(r.enum, list), "enum is not set to a list" + assert len(r.enum) == 3, "invalid length of enum entries" + + # this tests the missmatch between the type and the data inside a enum + with pytest.raises(RuleError) as r: + Rule(schema={"type": "str", "enum": [1, 2, 3]}) + assert str(r.value).startswith("" + assert r.value.error_key == 'enum.not_seq' + + def test_assert_value(self): + with pytest.raises(RuleError) as r: + Rule(schema={"type": "seq", "sequence": [{"type": "str", "assert": 1}]}) + assert str(r.value) == "" + assert r.value.error_key == 'assert.not_str' + + # Test that invalid characters is not present + with pytest.raises(RuleError) as r: + Rule(schema={"type": "seq", "sequence": [{"type": "str", "assert": "__import__"}]}) + assert str(r.value) == "" # NOQA: E501 + assert r.value.error_key == 'assert.unsupported_content' + + def test_length(self): + r = Rule(schema={"type": "int", "length": {"max": 10, "min": 1}}) + assert r.length is not None, "length var not set proper" + assert isinstance(r.length, dict), "range var is not of dict type" + + # this tests that the range key must be a dict + with pytest.raises(RuleError) as r: + Rule(schema={"type": "int", "length": []}) + assert str(r.value) == "" + assert r.value.error_key == 'length.not_map' + + with pytest.raises(RuleError) as r: + Rule(schema={"type": "str", "length": {"max": "z"}}) + assert str(r.value) == "" + assert r.value.error_key == 'length.max.not_number' + + # this tests that min is bigger then max that should not be possible + with pytest.raises(RuleError) as r: + Rule(schema={"type": "int", "length": {"max": 10, "min": 11}}) + assert str(r.value) == "" + assert r.value.error_key == 'length.max_lt_min' + + # test that min-ex is bigger then max-ex, that should not be possible + with pytest.raises(RuleError) as r: + Rule(schema={"type": "int", "length": {"max-ex": 10, "min-ex": 11}}) + assert str(r.value) == "" + assert r.value.error_key == 'length.max-ex_le_min-ex' + + # test that a string has non negative boundaries + with pytest.raises(RuleError) as r: + Rule(schema={"type": "str", "length": {"max": -1, "min": -2}}) + assert str(r.value) == "" + assert r.value.error_key == 'length.min_negative' + + # test that a seq has non negative boundaries + with pytest.raises(RuleError) as r: + Rule(schema={"type": "seq", "length": {"max": 3, "min": -2}}) + assert str(r.value) == "" + assert r.value.error_key == 'length.min_negative' + + def test_range_value(self): + r = Rule(schema={"type": "int", "range": {"max": 10, "min": 1}}) + assert r.range is not None, "range var not set proper" + assert isinstance(r.range, dict), "range var is not of dict type" + + # this tests that the range key must be a dict + with pytest.raises(RuleError) as r: + Rule(schema={"type": "int", "range": []}) + assert str(r.value) == "" + assert r.value.error_key == 'range.not_map' + + with pytest.raises(RuleError) as r: + Rule(schema={"type": "str", "range": {"max": "z"}}) + assert str(r.value) == "" + assert r.value.error_key == 'range.max.not_number' + + # this tests that min is bigger then max that should not be possible + with pytest.raises(RuleError) as r: + Rule(schema={"type": "int", "range": {"max": 10, "min": 11}}) + assert str(r.value) == "" + assert r.value.error_key == 'range.max_lt_min' + + # test that min-ex is bigger then max-ex, that should not be possible + with pytest.raises(RuleError) as r: + Rule(schema={"type": "int", "range": {"max-ex": 10, "min-ex": 11}}) + assert str(r.value) == "" + assert r.value.error_key == 'range.max-ex_le_min-ex' + + # test that a string has non negative boundaries + with pytest.raises(RuleError) as r: + Rule(schema={"type": "str", "range": {"max": -1, "min": -2}}) + assert str(r.value) == "" + assert r.value.error_key == 'range.min_negative' + + # test that a seq has non negative boundaries + with pytest.raises(RuleError) as r: + Rule(schema={"type": "seq", "range": {"max": 3, "min": -2}}) + assert str(r.value) == "" + assert r.value.error_key == 'range.min_negative' + + def test_ident_value(self): + pass + + def test_unique_value(self): + # this tests that this cannot be used in the root level + with pytest.raises(RuleError) as r: + Rule(schema={"type": "str", "unique": True}) + assert str(r.value) == "" + assert r.value.error_key == 'unique.not_on_root_level' + + # this tests that unique cannot be used at root level + with pytest.raises(RuleError) as r: + Rule(schema={"type": "seq", "unique": True}) + assert str(r.value) == "" + assert r.value.error_key == 'unique.not_scalar' + + def test_sequence(self): + # this tests seq type with a internal type of str + r = Rule(schema={"type": "seq", "sequence": [{"type": "str"}]}) + assert r.type is not None, "rule not contain type var" + assert r.type == "seq", "type not 'seq'" + assert r.sequence is not None, "rule not contain sequence var" + assert isinstance(r.sequence, list), "rule is not a list" + + # Test basic sequence rule + r = Rule(schema={"type": "seq", "sequence": [{"type": "str"}]}) + assert r.type == "seq" + assert isinstance(r.sequence, list) + assert isinstance(r.sequence[0], Rule) + assert r.sequence[0].type == "str" + + # Test sequence without explicit type + r = Rule(schema={"sequence": [{"type": "str"}]}) + assert r.type == "seq" + assert isinstance(r.sequence, list) + assert isinstance(r.sequence[0], Rule) + assert r.sequence[0].type == "str" + + # Test short name 'seq' + r = Rule(schema={"seq": [{"type": "str"}]}) + assert r.type == "seq" + assert isinstance(r.sequence, list) + assert isinstance(r.sequence[0], Rule) + assert r.sequence[0].type == "str" + + # Test error is raised when sequence key is missing + with pytest.raises(SchemaConflict) as ex: + Rule(schema={"type": "seq"}) + assert str(ex.value) == "" + + # sequence and pattern can't be used at same time + with pytest.raises(SchemaConflict) as ex: + Rule(schema={"type": "seq", "sequence": [{"type": "str"}], "pattern": "..."}) + assert str(ex.value) == "" + + def test_build_sequence_multiple_values(self): + """ + Test with multiple values. + """ + # Test basic sequence rule + r = Rule(schema={'type': 'seq', 'sequence': [{'type': 'str'}, {'type': 'int'}]}) + assert r.type == "seq" + assert r.matching == "any" + assert len(r.sequence) == 2 + assert isinstance(r.sequence, list) + assert all(isinstance(r.sequence[i], Rule) for i in range(len(r.sequence))) + assert r.sequence[0].type == "str" + assert r.sequence[1].type == "int" + + # Test sequence without explicit type + r = Rule(schema={'sequence': [{'type': 'str'}, {'type': 'int'}]}) + assert r.type == "seq" + assert r.matching == "any" + assert len(r.sequence) == 2 + assert isinstance(r.sequence, list) + assert all(isinstance(r.sequence[i], Rule) for i in range(len(r.sequence))) + assert r.sequence[0].type == "str" + assert r.sequence[1].type == "int" + + # Test adding matchin rules + + def test_mapping(self): + # This tests mapping with a nested type and pattern + r = Rule(schema={"type": "map", "mapping": {"name": {"type": "str", "pattern": ".+@.+"}}}) + assert r.type == "map", "rule type is not map" + assert isinstance(r.mapping, dict), "mapping is not dict" + assert r.mapping["name"].type == "str", "nested mapping is not of string type" + assert r.mapping["name"].pattern is not None, "nested mapping has no pattern var set" + assert r.mapping["name"].pattern == ".+@.+", "pattern is not set to correct value" + + # when type is specefied, 'mapping' key must be present + with pytest.raises(SchemaConflict) as ex: + Rule(schema={"type": "map"}) + assert str(ex.value) == "" + + # 'map' and 'enum' can't be used at same time + # TODO: This do not work because it currently raises RuleError: + # with pytest.raises(SchemaConflict): + # r = Rule(schema={"type": "map", "enum": [1, 2, 3]}) + + # Test that 'map' and 'mapping' can't be at the same level + with pytest.raises(RuleError) as r: + Rule(schema={"map": {"stream": {"type": "any"}}, "mapping": {"seams": {"type": "any"}}}) + assert str(r.value) == "" + assert r.value.error_key == 'mapping.duplicate_keywords' + + # This will test that a invalid regex will throw error when parsing rules + with pytest.raises(RuleError) as r: + Rule(schema={"type": "map", "matching-rule": "any", "mapping": {"regex;(+": {"type": "seq", "sequence": [{"type": "str"}]}}}) + assert str(r.value) == "" + assert r.value.error_key == 'mapping.regex.compile_error' + + # this tests map/dict but with no elements + with pytest.raises(RuleError) as r: + Rule(schema={"type": "map", "mapping": {}}) + assert str(r.value) == "" + assert r.value.error_key == 'mapping.no_elements' + + def test_default_value(self): + pass + + def test_check_conflicts(self): + # TODO: This do not work and enum schema conflict is not raised... RuleError: + # with pytest.raises(SchemaConflict) as ex: + # r = Rule(schema={"type": "seq", "sequence": [{"type": "str"}], "enum": [1, 2, 3]}) + # assert ex.value.msg.startswith("seq.conflict :: enum"), "Wrong exception was raised" + + # Test sequence and mapping can't be used at same level + with pytest.raises(SchemaConflict) as ex: + Rule(schema={"type": "seq", "sequence": [{"type": "str"}], "mapping": {"name": {"type": "str", "pattern": ".+@.+"}}}) + assert str(ex.value) == "" + assert ex.value.error_key == 'seq.conflict.mapping' + + # Mapping and sequence can't used at same time + with pytest.raises(SchemaConflict) as ex: + Rule(schema={"type": "map", "mapping": {"foo": {"type": "str"}}, "sequence": [{"type": "str"}]}) + assert str(ex.value) == "" + assert ex.value.error_key == 'map.conflict.sequence' + + # scalar type and sequence can't be used at same time + with pytest.raises(SchemaConflict) as ex: + Rule(schema={"type": "int", "sequence": [{"type": "str"}]}) + assert str(ex.value) == "" + assert ex.value.error_key == 'scalar.conflict.sequence' + + # scalar type and mapping can't be used at same time + with pytest.raises(SchemaConflict) as ex: + Rule(schema={"type": "int", "mapping": {"foo": {"type": "str"}}}) + assert str(ex.value) == "" + assert ex.value.error_key == 'scalar.conflict.mapping' + + # scalar type and enum can't be used at same time + with pytest.raises(SchemaConflict) as ex: + Rule(schema={"type": "int", "enum": [1, 2, 3], "range": {"max": 10, "min": 1}}) + assert str(ex.value) == "" + assert ex.value.error_key == 'enum.conflict.range' diff --git a/tests/test_types.py b/tests/test_types.py new file mode 100644 index 0000000..de7db87 --- /dev/null +++ b/tests/test_types.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +""" Unit test for pyKwalify - Rule """ + +# python std lib +import unittest + +# pykwalify imports +from pykwalify import types + + +class TestTypes(unittest.TestCase): + + def test_types(self): + """ + Test that all type helper methods works correctly + """ + assert types.type_class("str") == str + + assert types.is_builtin_type("str") + + assert types.is_collection_type("map") + assert types.is_collection_type("seq") + assert not types.is_collection_type("str") + + assert types.is_scalar_type("str") + assert not types.is_scalar_type("seq") + assert not types.is_scalar_type("map") + + assert types.is_collection([]) + assert types.is_collection({}) + assert not types.is_collection("foo") + + assert types.is_scalar("") + assert types.is_scalar(True) + assert not types.is_scalar([]) + + assert types.is_correct_type("", str) + assert types.is_correct_type({}, dict) + + assert types.is_string("foo") + assert not types.is_string([]) + + assert types.is_int(1) + assert not types.is_int("foo") + + assert types.is_bool(True) + assert not types.is_bool(1) + assert not types.is_bool("true") + + assert types.is_float(1.0) + assert not types.is_float("foo") + + assert types.is_number(1) + assert types.is_number(1.0) + assert not types.is_number("foo") + + assert types.is_text("foo") + assert types.is_text(1) + assert types.is_text(1.0) + assert not types.is_text([]) + assert not types.is_text(True) + + assert types.is_any("foo") + assert types.is_any(True) + assert types.is_any(1) + assert types.is_any(1.0) + assert types.is_any({}) + assert types.is_any([]) + + assert types.is_enum("foo") + assert not types.is_enum(1) + + assert types.is_none(None) + assert not types.is_none("foo") diff --git a/tests/test_unicode.py b/tests/test_unicode.py new file mode 100644 index 0000000..36f5549 --- /dev/null +++ b/tests/test_unicode.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- + +""" Unit test for pyKwalify - Core """ + +# python std lib +import os + +# pykwalify imports +import pykwalify +from pykwalify.compat import unicode +from pykwalify.core import Core +from pykwalify.errors import SchemaError + +# 3rd party imports +from pykwalify.compat import yaml +from testfixtures import compare + + +class TestUnicode(object): + + def setUp(self): + pykwalify.partial_schemas = {} + + def f(self, *args): + if os.path.isabs(args[0]): + return args[0] + + return os.path.join(os.path.dirname(os.path.realpath(__file__)), "files", "unicode", *args) + + def test_files_with_unicode_content_success(self, tmpdir): + """ + These tests should pass with no exception raised + """ + fail_data_2s_yaml = { + 'schema': { + 'type': 'map', + 'mapping': { + 'msg': { + 'type': 'int', + }, + } + }, + 'data': { + 'msg': 123, + }, + 'errors': [] + } + + source_f = tmpdir.join(u"2så.json") + source_f.write(yaml.safe_dump(fail_data_2s_yaml, allow_unicode=True)) + + _pass_tests = [ + # Test mapping with unicode key and value + u"1s.yaml", + # # Test unicode filename. + # It is not possible to package a file with unicode characters + # like åäö in the filename in some python versions. + # Mock a file with åäö during testing to properly simulate this again. + unicode(source_f), + # Test sequence with unicode keys + u"3s.yaml", + ] + + for passing_test_files in _pass_tests: + f = self.f(passing_test_files) + + with open(f, "r") as stream: + yaml_data = yaml.safe_load(stream) + data = yaml_data["data"] + schema = yaml_data["schema"] + + try: + print(u"Running test files: {0}".format(f)) + c = Core(source_data=data, schema_data=schema) + c.validate() + compare(c.validation_errors, [], prefix="No validation errors should exist...") + except Exception as e: + print(u"ERROR RUNNING FILES: {0}".format(f)) + 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=u"Parsed rules is not correct, something have changed... files : {0}".format(f)) + + def test_files_with_unicode_content_failing(self, tmpdir): + """ + These tests should fail with the specified exception + """ + # To trigger schema exception we must pass in a source file + fail_data_2f_yaml = { + 'schema': { + 'type': 'map', + 'mapping': { + 'msg': { + 'type': 'int', + }, + } + }, + 'data': { + 'msg': 'Foobar', + }, + 'errors': ["Value 'Foobar' is not of type 'int'. Path: '/msg'"] + } + + source_f = tmpdir.join(u"2få.json") + source_f.write(yaml.safe_dump(fail_data_2f_yaml, allow_unicode=True)) + + _fail_tests = [ + # Test mapping with unicode key and value but wrong type + (u"1f.yaml", SchemaError), + # Test unicode filename with validation errors. + # It is not possible to package a file with unicode characters + # like åäö in the filename in some python versions. + # Mock a file with åäö during testing to properly simulate this again. + (unicode(source_f), SchemaError), + # Test unicode data inside seq but wrong type + (u"3f.yaml", SchemaError), + ] + + for failing_test, exception_type in _fail_tests: + f = self.f(failing_test) + + with open(f, "r") as stream: + yaml_data = yaml.safe_load(stream) + data = yaml_data["data"] + schema = yaml_data["schema"] + errors = yaml_data["errors"] + + try: + print(u"Running test files: {0}".format(f)) + c = Core(source_data=data, schema_data=schema) + c.validate() + except exception_type: + pass # OK + else: + raise AssertionError(u"Exception {0} not raised as expected... FILES: {1} : {2}".format(exception_type, exception_type)) + + compare(sorted(c.validation_errors), sorted(errors), prefix=u"Wrong validation errors when parsing files : {0}".format(f)) -- cgit v1.2.3