summaryrefslogtreecommitdiffstats
path: root/tests/units/test_models.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/units/test_models.py')
-rw-r--r--tests/units/test_models.py472
1 files changed, 472 insertions, 0 deletions
diff --git a/tests/units/test_models.py b/tests/units/test_models.py
new file mode 100644
index 0000000..c0585a4
--- /dev/null
+++ b/tests/units/test_models.py
@@ -0,0 +1,472 @@
+# 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.
+"""
+test anta.models.py
+"""
+# Mypy does not understand AntaTest.Input typing
+# mypy: disable-error-code=attr-defined
+from __future__ import annotations
+
+import asyncio
+from typing import Any
+
+import pytest
+
+from anta.decorators import deprecated_test, skip_on_platforms
+from anta.device import AntaDevice
+from anta.models import AntaCommand, AntaTemplate, AntaTest
+from tests.lib.fixture import DEVICE_HW_MODEL
+from tests.lib.utils import generate_test_ids
+
+
+class FakeTest(AntaTest):
+ """ANTA test that always succeed"""
+
+ name = "FakeTest"
+ description = "ANTA test that always succeed"
+ categories = []
+ commands = []
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
+
+
+class FakeTestWithFailedCommand(AntaTest):
+ """ANTA test with a command that failed"""
+
+ name = "FakeTestWithFailedCommand"
+ description = "ANTA test with a command that failed"
+ categories = []
+ commands = [AntaCommand(command="show version", errors=["failed command"])]
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
+
+
+class FakeTestWithUnsupportedCommand(AntaTest):
+ """ANTA test with an unsupported command"""
+
+ name = "FakeTestWithUnsupportedCommand"
+ description = "ANTA test with an unsupported command"
+ categories = []
+ commands = [AntaCommand(command="show hardware counter drop", errors=["Unavailable command (not supported on this hardware platform) (at token 2: 'counter')"])]
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
+
+
+class FakeTestWithInput(AntaTest):
+ """ANTA test with inputs that always succeed"""
+
+ name = "FakeTestWithInput"
+ description = "ANTA test with inputs that always succeed"
+ categories = []
+ commands = []
+
+ class Input(AntaTest.Input): # pylint: disable=missing-class-docstring
+ string: str
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success(self.inputs.string)
+
+
+class FakeTestWithTemplate(AntaTest):
+ """ANTA test with template that always succeed"""
+
+ name = "FakeTestWithTemplate"
+ description = "ANTA test with template that always succeed"
+ categories = []
+ commands = [AntaTemplate(template="show interface {interface}")]
+
+ class Input(AntaTest.Input): # pylint: disable=missing-class-docstring
+ interface: str
+
+ def render(self, template: AntaTemplate) -> list[AntaCommand]:
+ return [template.render(interface=self.inputs.interface)]
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success(self.instance_commands[0].command)
+
+
+class FakeTestWithTemplateNoRender(AntaTest):
+ """ANTA test with template that miss the render() method"""
+
+ name = "FakeTestWithTemplateNoRender"
+ description = "ANTA test with template that miss the render() method"
+ categories = []
+ commands = [AntaTemplate(template="show interface {interface}")]
+
+ class Input(AntaTest.Input): # pylint: disable=missing-class-docstring
+ interface: str
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success(self.instance_commands[0].command)
+
+
+class FakeTestWithTemplateBadRender1(AntaTest):
+ """ANTA test with template that raises a AntaTemplateRenderError exception"""
+
+ name = "FakeTestWithTemplateBadRender"
+ description = "ANTA test with template that raises a AntaTemplateRenderError exception"
+ categories = []
+ commands = [AntaTemplate(template="show interface {interface}")]
+
+ class Input(AntaTest.Input): # pylint: disable=missing-class-docstring
+ interface: str
+
+ def render(self, template: AntaTemplate) -> list[AntaCommand]:
+ return [template.render(wrong_template_param=self.inputs.interface)]
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success(self.instance_commands[0].command)
+
+
+class FakeTestWithTemplateBadRender2(AntaTest):
+ """ANTA test with template that raises an arbitrary exception"""
+
+ name = "FakeTestWithTemplateBadRender2"
+ description = "ANTA test with template that raises an arbitrary exception"
+ categories = []
+ commands = [AntaTemplate(template="show interface {interface}")]
+
+ class Input(AntaTest.Input): # pylint: disable=missing-class-docstring
+ interface: str
+
+ def render(self, template: AntaTemplate) -> list[AntaCommand]:
+ raise Exception() # pylint: disable=broad-exception-raised
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success(self.instance_commands[0].command)
+
+
+class SkipOnPlatformTest(AntaTest):
+ """ANTA test that is skipped"""
+
+ name = "SkipOnPlatformTest"
+ description = "ANTA test that is skipped on a specific platform"
+ categories = []
+ commands = []
+
+ @skip_on_platforms([DEVICE_HW_MODEL])
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
+
+
+class UnSkipOnPlatformTest(AntaTest):
+ """ANTA test that is skipped"""
+
+ name = "UnSkipOnPlatformTest"
+ description = "ANTA test that is skipped on a specific platform"
+ categories = []
+ commands = []
+
+ @skip_on_platforms(["dummy"])
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
+
+
+class SkipOnPlatformTestWithInput(AntaTest):
+ """ANTA test skipped on platforms but with Input"""
+
+ name = "SkipOnPlatformTestWithInput"
+ description = "ANTA test skipped on platforms but with Input"
+ categories = []
+ commands = []
+
+ class Input(AntaTest.Input): # pylint: disable=missing-class-docstring
+ string: str
+
+ @skip_on_platforms([DEVICE_HW_MODEL])
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success(self.inputs.string)
+
+
+class DeprecatedTestWithoutNewTest(AntaTest):
+ """ANTA test that is deprecated without new test"""
+
+ name = "DeprecatedTestWitouthNewTest"
+ description = "ANTA test that is deprecated without new test"
+ categories = []
+ commands = []
+
+ @deprecated_test()
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
+
+
+class DeprecatedTestWithNewTest(AntaTest):
+ """ANTA test that is deprecated with new test."""
+
+ name = "DeprecatedTestWithNewTest"
+ description = "ANTA deprecated test with New Test"
+ categories = []
+ commands = []
+
+ @deprecated_test(new_tests=["NewTest"])
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
+
+
+ANTATEST_DATA: list[dict[str, Any]] = [
+ {"name": "no input", "test": FakeTest, "inputs": None, "expected": {"__init__": {"result": "unset"}, "test": {"result": "success"}}},
+ {
+ "name": "extra input",
+ "test": FakeTest,
+ "inputs": {"string": "culpa! veniam quas quas veniam molestias, esse"},
+ "expected": {"__init__": {"result": "error", "messages": ["Extra inputs are not permitted"]}, "test": {"result": "error"}},
+ },
+ {
+ "name": "no input",
+ "test": FakeTestWithInput,
+ "inputs": None,
+ "expected": {"__init__": {"result": "error", "messages": ["Field required"]}, "test": {"result": "error"}},
+ },
+ {
+ "name": "wrong input type",
+ "test": FakeTestWithInput,
+ "inputs": {"string": 1},
+ "expected": {"__init__": {"result": "error", "messages": ["Input should be a valid string"]}, "test": {"result": "error"}},
+ },
+ {
+ "name": "good input",
+ "test": FakeTestWithInput,
+ "inputs": {"string": "culpa! veniam quas quas veniam molestias, esse"},
+ "expected": {"__init__": {"result": "unset"}, "test": {"result": "success", "messages": ["culpa! veniam quas quas veniam molestias, esse"]}},
+ },
+ {
+ "name": "good input",
+ "test": FakeTestWithTemplate,
+ "inputs": {"interface": "Ethernet1"},
+ "expected": {"__init__": {"result": "unset"}, "test": {"result": "success", "messages": ["show interface Ethernet1"]}},
+ },
+ {
+ "name": "wrong input type",
+ "test": FakeTestWithTemplate,
+ "inputs": {"interface": 1},
+ "expected": {"__init__": {"result": "error", "messages": ["Input should be a valid string"]}, "test": {"result": "error"}},
+ },
+ {
+ "name": "wrong render definition",
+ "test": FakeTestWithTemplateNoRender,
+ "inputs": {"interface": "Ethernet1"},
+ "expected": {
+ "__init__": {
+ "result": "error",
+ "messages": ["AntaTemplate are provided but render() method has not been implemented for tests.units.test_models.FakeTestWithTemplateNoRender"],
+ },
+ "test": {"result": "error"},
+ },
+ },
+ {
+ "name": "AntaTemplateRenderError",
+ "test": FakeTestWithTemplateBadRender1,
+ "inputs": {"interface": "Ethernet1"},
+ "expected": {
+ "__init__": {
+ "result": "error",
+ "messages": ["Cannot render template {template='show interface {interface}' version='latest' revision=None ofmt='json' use_cache=True}"],
+ },
+ "test": {"result": "error"},
+ },
+ },
+ {
+ "name": "Exception in render()",
+ "test": FakeTestWithTemplateBadRender2,
+ "inputs": {"interface": "Ethernet1"},
+ "expected": {
+ "__init__": {
+ "result": "error",
+ "messages": ["Exception in tests.units.test_models.FakeTestWithTemplateBadRender2.render(): Exception"],
+ },
+ "test": {"result": "error"},
+ },
+ },
+ {
+ "name": "unskip on platforms",
+ "test": UnSkipOnPlatformTest,
+ "inputs": None,
+ "expected": {
+ "__init__": {"result": "unset"},
+ "test": {"result": "success"},
+ },
+ },
+ {
+ "name": "skip on platforms, unset",
+ "test": SkipOnPlatformTest,
+ "inputs": None,
+ "expected": {
+ "__init__": {"result": "unset"},
+ "test": {"result": "skipped"},
+ },
+ },
+ {
+ "name": "skip on platforms, not unset",
+ "test": SkipOnPlatformTestWithInput,
+ "inputs": None,
+ "expected": {"__init__": {"result": "error", "messages": ["Field required"]}, "test": {"result": "error"}},
+ },
+ {
+ "name": "deprecate test without new test",
+ "test": DeprecatedTestWithoutNewTest,
+ "inputs": None,
+ "expected": {
+ "__init__": {"result": "unset"},
+ "test": {"result": "success"},
+ },
+ },
+ {
+ "name": "deprecate test with new test",
+ "test": DeprecatedTestWithNewTest,
+ "inputs": None,
+ "expected": {
+ "__init__": {"result": "unset"},
+ "test": {"result": "success"},
+ },
+ },
+ {
+ "name": "failed command",
+ "test": FakeTestWithFailedCommand,
+ "inputs": None,
+ "expected": {"__init__": {"result": "unset"}, "test": {"result": "error", "messages": ["show version has failed: failed command"]}},
+ },
+ {
+ "name": "unsupported command",
+ "test": FakeTestWithUnsupportedCommand,
+ "inputs": None,
+ "expected": {
+ "__init__": {"result": "unset"},
+ "test": {"result": "skipped", "messages": ["Skipped because show hardware counter drop is not supported on pytest"]},
+ },
+ },
+]
+
+
+class Test_AntaTest:
+ """
+ Test for anta.models.AntaTest
+ """
+
+ def test__init_subclass__name(self) -> None:
+ """Test __init_subclass__"""
+ # Pylint detects all the classes in here as unused which is on purpose
+ # pylint: disable=unused-variable
+ with pytest.raises(NotImplementedError) as exec_info:
+
+ class WrongTestNoName(AntaTest):
+ """ANTA test that is missing a name"""
+
+ description = "ANTA test that is missing a name"
+ categories = []
+ commands = []
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
+
+ assert exec_info.value.args[0] == "Class tests.units.test_models.WrongTestNoName is missing required class attribute name"
+
+ with pytest.raises(NotImplementedError) as exec_info:
+
+ class WrongTestNoDescription(AntaTest):
+ """ANTA test that is missing a description"""
+
+ name = "WrongTestNoDescription"
+ categories = []
+ commands = []
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
+
+ assert exec_info.value.args[0] == "Class tests.units.test_models.WrongTestNoDescription is missing required class attribute description"
+
+ with pytest.raises(NotImplementedError) as exec_info:
+
+ class WrongTestNoCategories(AntaTest):
+ """ANTA test that is missing categories"""
+
+ name = "WrongTestNoCategories"
+ description = "ANTA test that is missing categories"
+ commands = []
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
+
+ assert exec_info.value.args[0] == "Class tests.units.test_models.WrongTestNoCategories is missing required class attribute categories"
+
+ with pytest.raises(NotImplementedError) as exec_info:
+
+ class WrongTestNoCommands(AntaTest):
+ """ANTA test that is missing commands"""
+
+ name = "WrongTestNoCommands"
+ description = "ANTA test that is missing commands"
+ categories = []
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
+
+ assert exec_info.value.args[0] == "Class tests.units.test_models.WrongTestNoCommands is missing required class attribute commands"
+
+ def _assert_test(self, test: AntaTest, expected: dict[str, Any]) -> None:
+ assert test.result.result == expected["result"]
+ if "messages" in expected:
+ for result_msg, expected_msg in zip(test.result.messages, expected["messages"]): # NOTE: zip(strict=True) has been added in Python 3.10
+ assert expected_msg in result_msg
+
+ @pytest.mark.parametrize("data", ANTATEST_DATA, ids=generate_test_ids(ANTATEST_DATA))
+ def test__init__(self, device: AntaDevice, data: dict[str, Any]) -> None:
+ """Test the AntaTest constructor"""
+ expected = data["expected"]["__init__"]
+ test = data["test"](device, inputs=data["inputs"])
+ self._assert_test(test, expected)
+
+ @pytest.mark.parametrize("data", ANTATEST_DATA, ids=generate_test_ids(ANTATEST_DATA))
+ def test_test(self, device: AntaDevice, data: dict[str, Any]) -> None:
+ """Test the AntaTest.test method"""
+ expected = data["expected"]["test"]
+ test = data["test"](device, inputs=data["inputs"])
+ asyncio.run(test.test())
+ self._assert_test(test, expected)
+
+
+ANTATEST_BLACKLIST_DATA = ["reload", "reload --force", "write", "wr mem"]
+
+
+@pytest.mark.parametrize("data", ANTATEST_BLACKLIST_DATA)
+def test_blacklist(device: AntaDevice, data: str) -> None:
+ """Test for blacklisting function."""
+
+ class FakeTestWithBlacklist(AntaTest):
+ """Fake Test for blacklist"""
+
+ name = "FakeTestWithBlacklist"
+ description = "ANTA test that has blacklisted command"
+ categories = []
+ commands = [AntaCommand(command=data)]
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
+
+ test_instance = FakeTestWithBlacklist(device)
+
+ # Run the test() method
+ asyncio.run(test_instance.test())
+ assert test_instance.result.result == "error"