Skip to content

Commit

Permalink
Add positive and negative unit tests for the schema fields
Browse files Browse the repository at this point in the history
  • Loading branch information
pszpetkowski committed Jul 8, 2023
1 parent 1844ec3 commit 47226ef
Showing 1 changed file with 265 additions and 0 deletions.
265 changes: 265 additions & 0 deletions tests/test_core/test_schema/test_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
from typing import Any, Dict, Literal

import pytest

from hidori_core.schema import fields as schema_fields
from hidori_core.schema.base import FIELDS_REGISTRY, Field, Schema, _sentinel
from hidori_core.schema.errors import SchemaError, SkipFieldError, ValidationError


class NestedSchema(Schema):
bar: str


class SimpleSchema(Schema):
foo: str
nested: NestedSchema


class SimpleField(Field):
def __init__(self, required: bool) -> None:
self.required = required


@pytest.fixture(scope="module")
def required_simple_field():
return SimpleField(required=True)


@pytest.fixture(scope="module")
def optional_simple_field():
return SimpleField(required=False)


def test_sanity_fields_registry_has_all_fields():
assert FIELDS_REGISTRY == [
schema_fields.Anything,
schema_fields.Text,
schema_fields.OneOf,
schema_fields.SubSchema,
schema_fields.Dictionary,
SimpleField,
]


def test_field_validation_fail_sentinel_for_required(required_simple_field):
with pytest.raises(ValidationError) as e:
required_simple_field.validate(_sentinel)

assert str(e.value) == "value for required field not provided"


def test_field_validation_skip_sentinel_for_optional(optional_simple_field):
with pytest.raises(SkipFieldError):
optional_simple_field.validate(_sentinel)


def test_field_validation_with_value_ok(required_simple_field, optional_simple_field):
required_simple_field.validate(42)
optional_simple_field.validate(42)


@pytest.mark.parametrize(
"required,exc", [(True, ValidationError), (False, SkipFieldError)]
)
def test_anything_field_setup_and_validation(required, exc):
assert schema_fields.Anything.from_annotation(str, required) is None
assert schema_fields.Anything.from_annotation(int, required) is None
assert schema_fields.Anything.from_annotation(Literal[42], required) is None
assert schema_fields.Anything.from_annotation(Schema, required) is None
assert schema_fields.Anything.from_annotation(SimpleSchema, required) is None

field = schema_fields.Anything.from_annotation(Any, required)
assert isinstance(field, schema_fields.Anything)
assert field.required is required
with pytest.raises(exc):
field.validate(_sentinel)

# Scenario: valid data
field.validate(1)
field.validate(True)
field.validate(object())
field.validate("foo")


@pytest.mark.parametrize(
"required,exc", [(True, ValidationError), (False, SkipFieldError)]
)
def test_text_field_setup_and_validation(required, exc):
assert schema_fields.Text.from_annotation(Any, required) is None
assert schema_fields.Text.from_annotation(int, required) is None
assert schema_fields.Text.from_annotation(Literal[42], required) is None
assert schema_fields.Text.from_annotation(Schema, required) is None
assert schema_fields.Text.from_annotation(SimpleSchema, required) is None

field = schema_fields.Text.from_annotation(str, required)
assert isinstance(field, schema_fields.Text)
assert field.required is required
with pytest.raises(exc):
field.validate(_sentinel)

with pytest.raises(ValidationError) as e:
field.validate(1)
assert str(e.value) == "expected str, got int"
with pytest.raises(ValidationError) as e:
field.validate(True)
assert str(e.value) == "expected str, got bool"
with pytest.raises(ValidationError) as e:
field.validate(object())
assert str(e.value) == "expected str, got object"

# Scenario: valid data
field.validate("foo")
field.validate("")


@pytest.mark.parametrize(
"required,exc", [(True, ValidationError), (False, SkipFieldError)]
)
def test_oneof_field_setup_and_validation(required, exc):
assert schema_fields.OneOf.from_annotation(Any, required) is None
assert schema_fields.OneOf.from_annotation(int, required) is None
assert schema_fields.OneOf.from_annotation(str, required) is None
assert schema_fields.OneOf.from_annotation(Schema, required) is None
assert schema_fields.OneOf.from_annotation(SimpleSchema, required) is None

field = schema_fields.OneOf.from_annotation(Literal[42, "foo"], required)
assert isinstance(field, schema_fields.OneOf)
assert field.required is required
with pytest.raises(exc):
field.validate(_sentinel)

with pytest.raises(ValidationError) as e:
field.validate(1)
assert str(e.value) == "not one of allowed values: (42, 'foo')"
with pytest.raises(ValidationError) as e:
field.validate(True)
assert str(e.value) == "not one of allowed values: (42, 'foo')"
with pytest.raises(ValidationError) as e:
field.validate(object())
assert str(e.value) == "not one of allowed values: (42, 'foo')"
with pytest.raises(ValidationError) as e:
field.validate("value")
assert str(e.value) == "not one of allowed values: (42, 'foo')"

# Scenario: valid data
field.validate("foo")
field.validate(42)


@pytest.mark.parametrize(
"required,exc", [(True, ValidationError), (False, SkipFieldError)]
)
def test_schema_field_setup_and_validation(required, exc):
assert schema_fields.SubSchema.from_annotation(Any, required) is None
assert schema_fields.SubSchema.from_annotation(int, required) is None
assert schema_fields.SubSchema.from_annotation(str, required) is None
assert schema_fields.SubSchema.from_annotation(Schema, required) is None
assert schema_fields.SubSchema.from_annotation(Literal[42, "foo"], required) is None

field = schema_fields.SubSchema.from_annotation(SimpleSchema, required)
assert isinstance(field, schema_fields.SubSchema)
assert field.required is required
with pytest.raises(exc):
field.validate(_sentinel)

with pytest.raises(ValidationError) as e:
field.validate(1)
assert str(e.value) == "expected dict, got int"
with pytest.raises(ValidationError) as e:
field.validate(True)
assert str(e.value) == "expected dict, got bool"
with pytest.raises(ValidationError) as e:
field.validate(object())
assert str(e.value) == "expected dict, got object"
with pytest.raises(ValidationError) as e:
field.validate("value")
assert str(e.value) == "expected dict, got str"

# Scenario: empty data is invalid for schema with required fields
with pytest.raises(SchemaError) as e:
field.validate({})
assert e.value.errors == {
"foo": "value for required field not provided",
"nested": "value for required field not provided",
}
# Scenario: data with invalid value type and required field not provided
with pytest.raises(SchemaError) as e:
field.validate({"foo": 42})
assert e.value.errors == {
"foo": "expected str, got int",
"nested": "value for required field not provided",
}
# Scenario: valid data for one field and empty data for the subschema
with pytest.raises(SchemaError) as e:
field.validate({"foo": "example", "nested": {}})
assert e.value.errors == {
"nested": {"bar": "value for required field not provided"}
}
# Scenario: valid data
field.validate({"foo": "example", "nested": {"bar": "example"}})


@pytest.mark.parametrize("dict_type", [dict, Dict])
@pytest.mark.parametrize(
"required,exc", [(True, ValidationError), (False, SkipFieldError)]
)
def test_dict_field_setup_and_validation(dict_type, required, exc):
assert schema_fields.Dictionary.from_annotation(Any, required) is None
assert schema_fields.Dictionary.from_annotation(int, required) is None
assert schema_fields.Dictionary.from_annotation(str, required) is None
assert schema_fields.Dictionary.from_annotation(Schema, required) is None
assert (
schema_fields.Dictionary.from_annotation(Literal[42, "foo"], required) is None
)

field = schema_fields.Dictionary.from_annotation(
dict_type[str, dict_type[str, Literal[42, "foo"]]], required
)
assert isinstance(field, schema_fields.Dictionary)
assert isinstance(field.key_field, schema_fields.Text)
assert isinstance(field.val_field, schema_fields.Dictionary)
assert isinstance(field.val_field.key_field, schema_fields.Text)
assert isinstance(field.val_field.val_field, schema_fields.OneOf)
assert field.required is required
with pytest.raises(exc):
field.validate(_sentinel)

with pytest.raises(ValidationError) as e:
field.validate(1)
assert str(e.value) == "expected dict, got int"
with pytest.raises(ValidationError) as e:
field.validate(True)
assert str(e.value) == "expected dict, got bool"
with pytest.raises(ValidationError) as e:
field.validate(object())
assert str(e.value) == "expected dict, got object"
with pytest.raises(ValidationError) as e:
field.validate("value")
assert str(e.value) == "expected dict, got str"

# Scenario: invalid key and value data types
with pytest.raises(ValidationError) as e:
field.validate({13: "foo"})
assert str(e.value) == "expected str, got int"
# Scenario: invalid value data type
with pytest.raises(ValidationError) as e:
field.validate({"foo": "example"})
assert str(e.value) == "expected dict, got str"
# Scenario: invalid key and value data for nested dict
with pytest.raises(ValidationError) as e:
field.validate({"foo": {13: "example"}})
assert str(e.value) == "expected str, got int"
# Scenario: invalid value for the nested dict
with pytest.raises(ValidationError) as e:
field.validate({"foo": {"example": 13}})
assert str(e.value) == "not one of allowed values: (42, 'foo')"
# Scenario: invalid value for one of the nested dict values
with pytest.raises(ValidationError) as e:
field.validate({"foo": {"example": 42, "another": "bar"}})
assert str(e.value) == "not one of allowed values: (42, 'foo')"
# Scenario: valid data
field.validate({})
field.validate({"foo": {}})
field.validate({"foo": {"example": 42}})
field.validate({"foo": {"example": 42, "another": "foo"}})

0 comments on commit 47226ef

Please sign in to comment.