Skip to content

Commit

Permalink
Add basic tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ClausHolbechArista committed Aug 13, 2024
1 parent b90524d commit 3b7d148
Show file tree
Hide file tree
Showing 6 changed files with 426 additions and 1 deletion.
2 changes: 1 addition & 1 deletion python-avd/pyavd/_schema/avdvalidator.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def validate(self, instance: Any, schema: dict | None = None, path: list[str | i
def type_validator(self, schema_type: str, instance: Any, _schema: dict, path: list[str | int]) -> Generator:
if not self.is_type(instance, schema_type):
yield AvdValidationError(
f"Invalid type '{type(instance)}'. Expected a '{schema_type}'.",
f"Invalid type '{type(instance).__name__}'. Expected a '{schema_type}'.",
path=path,
)

Expand Down
80 changes: 80 additions & 0 deletions python-avd/tests/pyavd/schema/test_avdschema_bool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Copyright (c) 2023-2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
import sys
import warnings
from importlib import import_module
from pathlib import Path
from sys import path
from typing import Generator
from unittest import mock
from re import fullmatch

import pytest

# Override global path to load schema from source instead of any installed version.
path.insert(0, str(Path(__file__).parents[3]))

from pyavd._schema.avdschema import AvdSchema
from pyavd._errors import AvdValidationError


# TODO:
# - Test default value with required False.

TEST_SCHEMA = {
"type": "dict",
"keys": {
"test_value": {
"type": "bool",
"convert_types": ["int", "str"], # Part of meta schema but not implemented in converter
"default": True,
"valid_values": [True],
"dynamic_valid_values": "valid_booleans", # Part of meta schema but not implemented in converter
"required": True,
"description": "Some boolean",
"display_name": "Boolean",
},
},
}

TESTS = [
# (test_value, expected_errors: tuple, expected_error_messages: tuple)
(True, None, None), # Valid value. No errors.
(False, (AvdValidationError,), ("'Validation Error: test_value': 'False' is not one of [True]",)), # Valid value. Not a valid value.
(
11.0123,
(AvdValidationError,),
("'Validation Error: test_value': Invalid type 'float'. Expected a 'bool'.",)
), # Invalid value.
(
None,
(AvdValidationError,),
("'Validation Error: ': Required key 'test_value' is not set in dict.",)
), # Required is set, so None is not ignored.
("11", None, None), # Converted to True. No errors.
("", (AvdValidationError,), ("'Validation Error: test_value': 'False' is not one of [True]",)), # Converted to False. Not a valid value.
(12, None, None), # Converted to True. No errors.
(0, (AvdValidationError,), ("'Validation Error: test_value': 'False' is not one of [True]",)), # Converted to False. Not a valid value.
]


@pytest.fixture(scope="module")
def avd_schema() -> AvdSchema:
return AvdSchema(TEST_SCHEMA)


@pytest.mark.parametrize(("test_value", "expected_errors", "expected_error_messages"), TESTS)
def test_generated_schema(test_value, expected_errors: tuple | None, expected_error_messages: tuple | None, avd_schema: AvdSchema):
instance ={"test_value": test_value}
list(avd_schema.convert(instance))
validation_errors = list(avd_schema.validate(instance))
if expected_errors:
for validation_error in validation_errors:
assert isinstance(validation_error, expected_errors)
assert str(validation_error) in expected_error_messages

assert len(validation_errors) == len(expected_error_messages)
else:
# No errors expected.
assert not validation_errors
78 changes: 78 additions & 0 deletions python-avd/tests/pyavd/schema/test_avdschema_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright (c) 2023-2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
import re
import sys
import warnings
from importlib import import_module
from pathlib import Path
from sys import path
from typing import Generator
from unittest import mock

import pytest

# Override global path to load schema from source instead of any installed version.
path.insert(0, str(Path(__file__).parents[3]))

from pyavd._schema.avdschema import AvdSchema
from pyavd._errors import AvdValidationError

# TODO:
# - Test default value with required False.
# - Test dynamic keys

TEST_SCHEMA = {
"type": "dict",
"keys": {
"test_value": {
"type": "dict",
"default": {"pri": 1, "foo": "foo1"},
"required": True,
"description": "Some string",
"display_name": "String",
"keys": {
"pri": {"type": "int", "convert_types": ["str"]},
"foo": {"type": "str", "convert_types": ["int"]},
}
},
},
}

TESTS = [
# (test_value, expected_errors: tuple, expected_error_messages: tuple)
({"pri": 1, "foo": "foo1"}, None, None), # Valid value. No errors.
({"pri": "1", "foo": 123}, None, None), # Valid value after conversion. No errors.
({}, None, None), # Valid value. No errors.
(
None,
(AvdValidationError,),
("'Validation Error: ': Required key 'test_value' is not set in dict.",),
), # Required is set, so None is not ignored.
(
"a",
(AvdValidationError,),
("'Validation Error: test_value': Invalid type 'str'. Expected a 'dict'.",)
), # Invalid type.
]


@pytest.fixture(scope="module")
def avd_schema() -> AvdSchema:
return AvdSchema(TEST_SCHEMA)


@pytest.mark.parametrize(("test_value", "expected_errors", "expected_error_messages"), TESTS)
def test_generated_schema(test_value, expected_errors: tuple | None, expected_error_messages: tuple | None, avd_schema: AvdSchema):
instance ={"test_value": test_value}
list(avd_schema.convert(instance))
validation_errors = list(avd_schema.validate(instance))
if expected_errors:
for validation_error in validation_errors:
assert isinstance(validation_error, expected_errors)
assert str(validation_error) in expected_error_messages

assert len(validation_errors) == len(expected_error_messages)
else:
# No errors expected.
assert not validation_errors
79 changes: 79 additions & 0 deletions python-avd/tests/pyavd/schema/test_avdschema_int.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright (c) 2023-2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
import sys
import warnings
from importlib import import_module
from pathlib import Path
from sys import path
from typing import Generator
from unittest import mock

import pytest

# Override global path to load schema from source instead of any installed version.
path.insert(0, str(Path(__file__).parents[3]))

from pyavd._schema.avdschema import AvdSchema
from pyavd._errors import AvdValidationError

# TODO:
# - Test Dynamic valid values.
# - Test default value with required False.

TEST_SCHEMA = {
"type": "dict",
"keys": {
"test_value": {
"type": "int",
"convert_types": ["bool", "str", "float"],
"default": 11,
"min": 2,
"max": 20,
"valid_values": [0, 11, 22],
"dynamic_valid_values": "valid_values", # Part of meta schema but not implemented in converter
"required": True,
"description": "Some integer",
"display_name": "Integer",
},
},
}

TESTS = [
# (test_value, expected_errors: tuple, expected_error_messages: tuple)
(11, None, None), # Valid value. No errors.
(False, (AvdValidationError,), ("'Validation Error: test_value': '0' is lower than the allowed minimum of 2.",)), # False is converted to 0 which is valid but below min.
(
True,
(AvdValidationError,),
("'Validation Error: test_value': '1' is lower than the allowed minimum of 2.", "'Validation Error: test_value': '1' is not one of [0, 11, 22]"),
), # True is converted to 1 which is not valid.
("11", None, None), # Converted to 11. No errors.
(11.0123, None, None), # Converted to 11. No errors.
(None, (AvdValidationError,), ("'Validation Error: ': Required key 'test_value' is not set in dict.",)), # Required is set, so None is not ignored.
(12, (AvdValidationError,), ("'Validation Error: test_value': '12' is not one of [0, 11, 22]",)), # Invalid value.
([], (AvdValidationError,), ("'Validation Error: test_value': Invalid type 'list'. Expected a 'int'.",)), # Invalid type.
(0, (AvdValidationError,), ("'Validation Error: test_value': '0' is lower than the allowed minimum of 2.",)), # Valid but below min.
(22, (AvdValidationError,), ("'Validation Error: test_value': '22' is higher than the allowed maximum of 20.",)), # Valid but above max.
]


@pytest.fixture(scope="module")
def avd_schema() -> AvdSchema:
return AvdSchema(TEST_SCHEMA)


@pytest.mark.parametrize(("test_value", "expected_errors", "expected_error_messages"), TESTS)
def test_generated_schema(test_value, expected_errors: tuple | None, expected_error_messages: tuple | None, avd_schema: AvdSchema):
instance ={"test_value": test_value}
list(avd_schema.convert(instance))
validation_errors = list(avd_schema.validate(instance))
if expected_errors:
for validation_error in validation_errors:
assert isinstance(validation_error, expected_errors)
assert str(validation_error) in expected_error_messages

assert len(validation_errors) == len(expected_error_messages)
else:
# No errors expected.
assert not validation_errors
86 changes: 86 additions & 0 deletions python-avd/tests/pyavd/schema/test_avdschema_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Copyright (c) 2023-2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
import re
import sys
import warnings
from importlib import import_module
from pathlib import Path
from sys import path
from typing import Generator
from unittest import mock

import pytest

# Override global path to load schema from source instead of any installed version.
path.insert(0, str(Path(__file__).parents[3]))

from pyavd._schema.avdschema import AvdSchema
from pyavd._errors import AvdValidationError

# TODO:
# - Test default value with required False.

TEST_SCHEMA = {
"type": "dict",
"keys": {
"test_value": {
"type": "list",
"default": [{"pri": 1, "foo": "foo1"}, {"pri": 2, "foo": "foo2"}, {"pri": 3, "foo": "foo3"}],
"max_length": 3,
"min_length": 1,
"required": True,
"description": "Some string",
"display_name": "String",
"primary_key": "pri",
"unique_keys": ["foo"],
"items": {
"type": "dict",
"keys": {
"pri": {"type": "int", "convert_types": ["str"]},
"foo": {"type": "str", "convert_types": ["int"]},
}
}
},
},
}

TESTS = [
# (test_value, expected_errors: tuple, expected_error_messages: tuple)
([{"pri": 1, "foo": "foo1"}, {"pri": 2, "foo": "foo2"}], None, None), # Valid value. No errors.
([{"pri": "1", "foo": 123}, {"pri": 2, "foo": "234"}], None, None), # Valid value after conversion. No errors.
([{"pri": 1, "foo": "123"}, {"pri": "1", "foo": 123}], (AvdValidationError,), ("'Validation Error: test_value[0].pri': The value '1' is not unique between all list items as required.", "'Validation Error: test_value[1].pri': The value '1' is not unique between all list items as required.", "'Validation Error: test_value[0].foo': The value '123' is not unique between all list items as required.", "'Validation Error: test_value[1].foo': The value '123' is not unique between all list items as required.")), # Collision on both primary_key and unique_keys
(
None,
(AvdValidationError,),
("'Validation Error: ': Required key 'test_value' is not set in dict.",),
), # Required is set, so None is not ignored.
([], (AvdValidationError,), ("'Validation Error: test_value': The value is shorter (0) than the allowed minimum of 1.",)), # Valid but below min length.
([{"pri": 1, "foo": "foo1"}, {"pri": 2, "foo": "foo2"}, {"pri": 3, "foo": "foo3"}, {"pri": 4, "foo": "foo4"}], (AvdValidationError,), ("'Validation Error: test_value': The value is longer (4) than the allowed maximum of 3.",)), # Valid but amove max length.
(
"a",
(AvdValidationError,),
("'Validation Error: test_value': Invalid type 'str'. Expected a 'list'.",)
), # Invalid type.
]


@pytest.fixture(scope="module")
def avd_schema() -> AvdSchema:
return AvdSchema(TEST_SCHEMA)


@pytest.mark.parametrize(("test_value", "expected_errors", "expected_error_messages"), TESTS)
def test_generated_schema(test_value, expected_errors: tuple | None, expected_error_messages: tuple | None, avd_schema: AvdSchema):
instance ={"test_value": test_value}
list(avd_schema.convert(instance))
validation_errors = list(avd_schema.validate(instance))
if expected_errors:
for validation_error in validation_errors:
assert isinstance(validation_error, expected_errors)
assert str(validation_error) in expected_error_messages

assert len(validation_errors) == len(expected_error_messages)
else:
# No errors expected.
assert not validation_errors
Loading

0 comments on commit 3b7d148

Please sign in to comment.