Considerably expand test coverage
This commit is contained in:
parent
0246be6a14
commit
b49582a5dc
2 changed files with 134 additions and 17 deletions
|
@ -8,12 +8,110 @@ import pytest
|
|||
|
||||
|
||||
@pytest.mark.parametrize('schema, obj', [
|
||||
({"type": "number"}, "---\n1.0"),
|
||||
({"type": "integer"}, "---\n3"),
|
||||
({"type": "string"}, "---\nfoo bar baz"),
|
||||
({"type": "string", "maxLength": 15}, "---\nfoo bar baz"),
|
||||
({"type": "string", "minLength": 10}, "---\nfoo bar baz"),
|
||||
({"type": "string", "pattern": "^foo.*"}, "---\nfoo bar baz"),
|
||||
({"type": "number"},
|
||||
"---\n1.0"),
|
||||
({"type": "integer"},
|
||||
"---\n3"),
|
||||
({"type": "string"},
|
||||
"---\nfoo bar baz"),
|
||||
({"type": "string",
|
||||
"maxLength": 15},
|
||||
"---\nfoo bar baz"),
|
||||
({"type": "string",
|
||||
"minLength": 10},
|
||||
"---\nfoo bar baz"),
|
||||
({"type": "string",
|
||||
"pattern": "^foo.*"},
|
||||
"---\nfoo bar baz"),
|
||||
({"type": "object",
|
||||
"additionalProperties": True},
|
||||
"---\nfoo: bar\nbaz: qux"),
|
||||
({"type": "object",
|
||||
"properties": {"foo": {"type": "string"}}},
|
||||
"---\nfoo: bar\nbaz: qux"),
|
||||
({"type": "object",
|
||||
"properties": {"foo": {"type": "string"}},
|
||||
"additionalProperties": False},
|
||||
"---\nfoo: bar"),
|
||||
({"type": "object",
|
||||
"properties": {"foo": {"type": "object"}}},
|
||||
"---\nfoo: {}"),
|
||||
({"type": "object",
|
||||
"properties": {"foo": {
|
||||
"type": "array",
|
||||
"items": {"type": "object"}}}},
|
||||
"---\nfoo: [{}, {}, {foo: bar}]"),
|
||||
])
|
||||
def test_lint_document_ok(schema, obj):
|
||||
assert not list(lint_buffer(schema, obj))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('msg, schema, obj', [
|
||||
# Numerics
|
||||
("Floats are not ints",
|
||||
{"type": "integer"},
|
||||
"---\n1.0"),
|
||||
("Ints are not floats",
|
||||
{"type": "number"},
|
||||
"---\n1"),
|
||||
|
||||
# Numerics - range limits. Integer edition
|
||||
("1 is the limit of the range",
|
||||
{"type": "integer",
|
||||
"exclusiveMaximum": 1},
|
||||
"---\n1"),
|
||||
("1 is the limit of the range",
|
||||
{"type": "integer",
|
||||
"exclusiveMinimum": 1},
|
||||
"---\n1"),
|
||||
("1 is out of the range",
|
||||
{"type": "integer",
|
||||
"minimum": 2},
|
||||
"---\n1"),
|
||||
("1 is out of the range",
|
||||
{"type": "integer",
|
||||
"maximum": 0},
|
||||
"---\n1"),
|
||||
("1 is out of the range",
|
||||
{"type": "integer",
|
||||
"exclusiveMinimum": 1},
|
||||
"---\n1"),
|
||||
|
||||
# Numerics - range limits. Number/Float edition
|
||||
("1 is the limit of the range",
|
||||
{"type": "number",
|
||||
"exclusiveMaximum": 1},
|
||||
"---\n1.0"),
|
||||
("1 is the limit of the range",
|
||||
{"type": "number",
|
||||
"exclusiveMinimum": 1},
|
||||
"---\n1.0"),
|
||||
("1 is out of the range",
|
||||
{"type": "number",
|
||||
"minimum": 2},
|
||||
"---\n1.0"),
|
||||
("1 is out of the range",
|
||||
{"type": "number",
|
||||
"maximum": 0},
|
||||
"---\n1.0"),
|
||||
("1 is out of the range",
|
||||
{"type": "number",
|
||||
"exclusiveMinimum": 1},
|
||||
"---\n1.0"),
|
||||
|
||||
# String shit
|
||||
("String too short",
|
||||
{"type": "string", "minLength": 1},
|
||||
"---\n''"),
|
||||
("String too long",
|
||||
{"type": "string", "maxLength": 1},
|
||||
"---\nfoo"),
|
||||
("String does not match pattern",
|
||||
{"type": "string", "pattern": "bar"},
|
||||
"---\nfoo"),
|
||||
("String does not fully match pattern",
|
||||
{"type": "string", "pattern": "foo"},
|
||||
"---\nfooooooooo"),
|
||||
])
|
||||
def test_lint_document_fails(msg, schema, obj):
|
||||
assert list(lint_buffer(schema, obj)), msg
|
||||
|
|
|
@ -45,7 +45,10 @@ class YamlLinter(object):
|
|||
def dereference(self, schema):
|
||||
"""Dereference a {"$ref": ""} form."""
|
||||
|
||||
if ref := schema.get("$ref"):
|
||||
if schema in [True, False]:
|
||||
return schema
|
||||
|
||||
elif ref := schema.get("$ref"):
|
||||
assert ref.startswith("#/")
|
||||
path = ref.lstrip("#/").split("/")
|
||||
schema = self._schema
|
||||
|
@ -85,10 +88,10 @@ class YamlLinter(object):
|
|||
|
||||
for k, v in node.value:
|
||||
if k.value in properties:
|
||||
yield from self.lint_document(properties.get(k.value), v)
|
||||
yield from self.lint_document(v, properties.get(k.value))
|
||||
|
||||
elif additional_type:
|
||||
yield from self.lint_document(additional_type, v)
|
||||
yield from self.lint_document(v, additional_type)
|
||||
|
||||
else:
|
||||
yield LintRecord(
|
||||
|
@ -116,7 +119,7 @@ class YamlLinter(object):
|
|||
subschema = schema.get("items")
|
||||
if subschema:
|
||||
for item in node.value:
|
||||
yield from self.lint_document(subschema, item)
|
||||
yield from self.lint_document(item, subschema)
|
||||
|
||||
def lint_scalar(self, schema, node: Node) -> t.Iterable[str]:
|
||||
"""FIXME.
|
||||
|
@ -204,7 +207,7 @@ class YamlLinter(object):
|
|||
def _lint_num_range(self, schema, node: Node, value) -> t.Iterable[str]:
|
||||
""""FIXME."""
|
||||
|
||||
if base := schema.get("multipleOf"):
|
||||
if (base := schema.get("multipleOf")) is not None:
|
||||
if value % base != 0:
|
||||
yield LintRecord(
|
||||
LintLevel.MISSMATCH,
|
||||
|
@ -213,7 +216,7 @@ class YamlLinter(object):
|
|||
f"Expected a multiple of {base}, got {value}"
|
||||
)
|
||||
|
||||
if max := schema.get("exclusiveMaximum"):
|
||||
if (max := schema.get("exclusiveMaximum")) is not None:
|
||||
if value >= max:
|
||||
yield LintRecord(
|
||||
LintLevel.MISSMATCH,
|
||||
|
@ -222,7 +225,7 @@ class YamlLinter(object):
|
|||
f"Expected a value less than {max}, got {value}"
|
||||
)
|
||||
|
||||
if max := schema.get("maximum"):
|
||||
if (max := schema.get("maximum")) is not None:
|
||||
if value > max:
|
||||
yield LintRecord(
|
||||
LintLevel.MISSMATCH,
|
||||
|
@ -231,7 +234,7 @@ class YamlLinter(object):
|
|||
f"Expected a value less than or equal to {max}, got {value}"
|
||||
)
|
||||
|
||||
if min := schema.get("exclusiveMinimum"):
|
||||
if (min := schema.get("exclusiveMinimum")) is not None:
|
||||
if value <= min:
|
||||
yield LintRecord(
|
||||
LintLevel.MISSMATCH,
|
||||
|
@ -240,7 +243,7 @@ class YamlLinter(object):
|
|||
f"Expected a value greater than {min}, got {value}"
|
||||
)
|
||||
|
||||
if min := schema.get("minimum"):
|
||||
if (min := schema.get("minimum")) is not None:
|
||||
if value < min:
|
||||
yield LintRecord(
|
||||
LintLevel.MISSMATCH,
|
||||
|
@ -260,20 +263,36 @@ class YamlLinter(object):
|
|||
schema = schema or self._schema # Fixing up the schema source
|
||||
schema = self.dereference(schema) # And dereference it if needed
|
||||
|
||||
# Special schemas
|
||||
# These are schemas that accept everything.
|
||||
if schema == True or schema == {}:
|
||||
yield from []
|
||||
|
||||
# This is the schema that rejects everything.
|
||||
elif schema == False:
|
||||
yield LintRecord(
|
||||
LintLevel.UNEXPECTED,
|
||||
node,
|
||||
schema,
|
||||
"Received an unexpected value"
|
||||
)
|
||||
|
||||
# Walking the PyYAML node hierarchy
|
||||
elif isinstance(node, MappingNode):
|
||||
yield from self.lint_mapping(schema, node)
|
||||
|
||||
elif isinstance(node, SequenceNode):
|
||||
yield from self.lint_sequence(schema, node)
|
||||
|
||||
elif isinstance(node, ScalarNode):
|
||||
yield from self.lint_scalar(schema, node)
|
||||
|
||||
else:
|
||||
yield from []
|
||||
raise RuntimeError(f"Unsupported PyYAML node {type(node)}")
|
||||
|
||||
|
||||
def lint_node(schema, node, cls=YamlLinter):
|
||||
"""Lint a document using a schema and linter."""
|
||||
"""Lint a composed PyYAML AST node using a schema and linter."""
|
||||
|
||||
print(repr(node))
|
||||
linter = cls(schema)
|
||||
|
|
Loading…
Reference in a new issue