diff options
Diffstat (limited to 'tests/units/test_models.py')
-rw-r--r-- | tests/units/test_models.py | 301 |
1 files changed, 219 insertions, 82 deletions
diff --git a/tests/units/test_models.py b/tests/units/test_models.py index c0585a4..bc6a1ce 100644 --- a/tests/units/test_models.py +++ b/tests/units/test_models.py @@ -1,209 +1,242 @@ # 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 -""" +"""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 +from typing import TYPE_CHECKING, Any, ClassVar 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 +if TYPE_CHECKING: + from anta.device import AntaDevice + class FakeTest(AntaTest): - """ANTA test that always succeed""" + """ANTA test that always succeed.""" name = "FakeTest" description = "ANTA test that always succeed" - categories = [] - commands = [] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [] @AntaTest.anta_test def test(self) -> None: + """Test function.""" self.result.is_success() class FakeTestWithFailedCommand(AntaTest): - """ANTA test with a command that failed""" + """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"])] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show version", errors=["failed command"])] @AntaTest.anta_test def test(self) -> None: + """Test function.""" self.result.is_success() class FakeTestWithUnsupportedCommand(AntaTest): - """ANTA test with an unsupported command""" + """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')"])] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [ + 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: + """Test function.""" self.result.is_success() class FakeTestWithInput(AntaTest): - """ANTA test with inputs that always succeed""" + """ANTA test with inputs that always succeed.""" name = "FakeTestWithInput" description = "ANTA test with inputs that always succeed" - categories = [] - commands = [] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [] + + class Input(AntaTest.Input): + """Inputs for FakeTestWithInput test.""" - class Input(AntaTest.Input): # pylint: disable=missing-class-docstring string: str @AntaTest.anta_test def test(self) -> None: + """Test function.""" self.result.is_success(self.inputs.string) class FakeTestWithTemplate(AntaTest): - """ANTA test with template that always succeed""" + """ANTA test with template that always succeed.""" name = "FakeTestWithTemplate" description = "ANTA test with template that always succeed" - categories = [] - commands = [AntaTemplate(template="show interface {interface}")] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show interface {interface}")] + + class Input(AntaTest.Input): + """Inputs for FakeTestWithTemplate test.""" - class Input(AntaTest.Input): # pylint: disable=missing-class-docstring interface: str def render(self, template: AntaTemplate) -> list[AntaCommand]: + """Render function.""" return [template.render(interface=self.inputs.interface)] @AntaTest.anta_test def test(self) -> None: + """Test function.""" self.result.is_success(self.instance_commands[0].command) class FakeTestWithTemplateNoRender(AntaTest): - """ANTA test with template that miss the render() method""" + """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}")] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show interface {interface}")] + + class Input(AntaTest.Input): + """Inputs for FakeTestWithTemplateNoRender test.""" - class Input(AntaTest.Input): # pylint: disable=missing-class-docstring interface: str @AntaTest.anta_test def test(self) -> None: + """Test function.""" self.result.is_success(self.instance_commands[0].command) class FakeTestWithTemplateBadRender1(AntaTest): - """ANTA test with template that raises a AntaTemplateRenderError exception""" + """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}")] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show interface {interface}")] + + class Input(AntaTest.Input): + """Inputs for FakeTestWithTemplateBadRender1 test.""" - class Input(AntaTest.Input): # pylint: disable=missing-class-docstring interface: str def render(self, template: AntaTemplate) -> list[AntaCommand]: + """Render function.""" return [template.render(wrong_template_param=self.inputs.interface)] @AntaTest.anta_test def test(self) -> None: + """Test function.""" self.result.is_success(self.instance_commands[0].command) class FakeTestWithTemplateBadRender2(AntaTest): - """ANTA test with template that raises an arbitrary exception""" + """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}")] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show interface {interface}")] + + class Input(AntaTest.Input): + """Inputs for FakeTestWithTemplateBadRender2 test.""" - 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 + """Render function.""" + raise RuntimeError(template) @AntaTest.anta_test def test(self) -> None: + """Test function.""" self.result.is_success(self.instance_commands[0].command) class SkipOnPlatformTest(AntaTest): - """ANTA test that is skipped""" + """ANTA test that is skipped.""" name = "SkipOnPlatformTest" description = "ANTA test that is skipped on a specific platform" - categories = [] - commands = [] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [] @skip_on_platforms([DEVICE_HW_MODEL]) @AntaTest.anta_test def test(self) -> None: + """Test function.""" self.result.is_success() class UnSkipOnPlatformTest(AntaTest): - """ANTA test that is skipped""" + """ANTA test that is skipped.""" name = "UnSkipOnPlatformTest" description = "ANTA test that is skipped on a specific platform" - categories = [] - commands = [] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [] @skip_on_platforms(["dummy"]) @AntaTest.anta_test def test(self) -> None: + """Test function.""" self.result.is_success() class SkipOnPlatformTestWithInput(AntaTest): - """ANTA test skipped on platforms but with Input""" + """ANTA test skipped on platforms but with Input.""" name = "SkipOnPlatformTestWithInput" description = "ANTA test skipped on platforms but with Input" - categories = [] - commands = [] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [] + + class Input(AntaTest.Input): + """Inputs for SkipOnPlatformTestWithInput test.""" - class Input(AntaTest.Input): # pylint: disable=missing-class-docstring string: str @skip_on_platforms([DEVICE_HW_MODEL]) @AntaTest.anta_test def test(self) -> None: + """Test function.""" self.result.is_success(self.inputs.string) class DeprecatedTestWithoutNewTest(AntaTest): - """ANTA test that is deprecated without new test""" + """ANTA test that is deprecated without new test.""" name = "DeprecatedTestWitouthNewTest" description = "ANTA test that is deprecated without new test" - categories = [] - commands = [] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [] @deprecated_test() @AntaTest.anta_test def test(self) -> None: + """Test function.""" self.result.is_success() @@ -212,52 +245,88 @@ class DeprecatedTestWithNewTest(AntaTest): name = "DeprecatedTestWithNewTest" description = "ANTA deprecated test with New Test" - categories = [] - commands = [] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [] @deprecated_test(new_tests=["NewTest"]) @AntaTest.anta_test def test(self) -> None: + """Test function.""" 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": "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"}}, + "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"}}, + "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"}}, + "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"]}}, + "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"]}}, + "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"}}, + "expected": { + "__init__": { + "result": "error", + "messages": ["Input should be a valid string"], + }, + "test": {"result": "error"}, + }, }, { "name": "wrong render definition", @@ -284,13 +353,13 @@ ANTATEST_DATA: list[dict[str, Any]] = [ }, }, { - "name": "Exception in render()", + "name": "RuntimeError in render()", "test": FakeTestWithTemplateBadRender2, "inputs": {"interface": "Ethernet1"}, "expected": { "__init__": { "result": "error", - "messages": ["Exception in tests.units.test_models.FakeTestWithTemplateBadRender2.render(): Exception"], + "messages": ["Exception in tests.units.test_models.FakeTestWithTemplateBadRender2.render(): RuntimeError"], }, "test": {"result": "error"}, }, @@ -317,7 +386,10 @@ ANTATEST_DATA: list[dict[str, Any]] = [ "name": "skip on platforms, not unset", "test": SkipOnPlatformTestWithInput, "inputs": None, - "expected": {"__init__": {"result": "error", "messages": ["Field required"]}, "test": {"result": "error"}}, + "expected": { + "__init__": {"result": "error", "messages": ["Field required"]}, + "test": {"result": "error"}, + }, }, { "name": "deprecate test without new test", @@ -341,7 +413,13 @@ ANTATEST_DATA: list[dict[str, Any]] = [ "name": "failed command", "test": FakeTestWithFailedCommand, "inputs": None, - "expected": {"__init__": {"result": "unset"}, "test": {"result": "error", "messages": ["show version has failed: failed command"]}}, + "expected": { + "__init__": {"result": "unset"}, + "test": { + "result": "error", + "messages": ["show version has failed: failed command"], + }, + }, }, { "name": "unsupported command", @@ -349,29 +427,30 @@ ANTATEST_DATA: list[dict[str, Any]] = [ "inputs": None, "expected": { "__init__": {"result": "unset"}, - "test": {"result": "skipped", "messages": ["Skipped because show hardware counter drop is not supported on pytest"]}, + "test": { + "result": "skipped", + "messages": ["'show hardware counter drop' is not supported on pytest"], + }, }, }, ] -class Test_AntaTest: - """ - Test for anta.models.AntaTest - """ +class TestAntaTest: + """Test for anta.models.AntaTest.""" def test__init_subclass__name(self) -> None: - """Test __init_subclass__""" + """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""" + """ANTA test that is missing a name.""" description = "ANTA test that is missing a name" - categories = [] - commands = [] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [] @AntaTest.anta_test def test(self) -> None: @@ -382,11 +461,11 @@ class Test_AntaTest: with pytest.raises(NotImplementedError) as exec_info: class WrongTestNoDescription(AntaTest): - """ANTA test that is missing a description""" + """ANTA test that is missing a description.""" name = "WrongTestNoDescription" - categories = [] - commands = [] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [] @AntaTest.anta_test def test(self) -> None: @@ -397,11 +476,11 @@ class Test_AntaTest: with pytest.raises(NotImplementedError) as exec_info: class WrongTestNoCategories(AntaTest): - """ANTA test that is missing categories""" + """ANTA test that is missing categories.""" name = "WrongTestNoCategories" description = "ANTA test that is missing categories" - commands = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [] @AntaTest.anta_test def test(self) -> None: @@ -412,11 +491,11 @@ class Test_AntaTest: with pytest.raises(NotImplementedError) as exec_info: class WrongTestNoCommands(AntaTest): - """ANTA test that is missing commands""" + """ANTA test that is missing commands.""" name = "WrongTestNoCommands" description = "ANTA test that is missing commands" - categories = [] + categories: ClassVar[list[str]] = [] @AntaTest.anta_test def test(self) -> None: @@ -432,14 +511,14 @@ class Test_AntaTest: @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""" + """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""" + """Test the AntaTest.test method.""" expected = data["expected"]["test"] test = data["test"](device, inputs=data["inputs"]) asyncio.run(test.test()) @@ -454,12 +533,12 @@ def test_blacklist(device: AntaDevice, data: str) -> None: """Test for blacklisting function.""" class FakeTestWithBlacklist(AntaTest): - """Fake Test for blacklist""" + """Fake Test for blacklist.""" name = "FakeTestWithBlacklist" description = "ANTA test that has blacklisted command" - categories = [] - commands = [AntaCommand(command=data)] + categories: ClassVar[list[str]] = [] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command=data)] @AntaTest.anta_test def test(self) -> None: @@ -470,3 +549,61 @@ def test_blacklist(device: AntaDevice, data: str) -> None: # Run the test() method asyncio.run(test_instance.test()) assert test_instance.result.result == "error" + + +class TestAntaComamnd: + """Test for anta.models.AntaCommand.""" + + # ruff: noqa: B018 + # pylint: disable=pointless-statement + + def test_empty_output_access(self) -> None: + """Test for both json and text ofmt.""" + json_cmd = AntaCommand(command="show dummy") + text_cmd = AntaCommand(command="show dummy", ofmt="text") + msg = "There is no output for command 'show dummy'" + with pytest.raises(RuntimeError, match=msg): + json_cmd.json_output + with pytest.raises(RuntimeError, match=msg): + text_cmd.text_output + + def test_wrong_format_output_access(self) -> None: + """Test for both json and text ofmt.""" + json_cmd = AntaCommand(command="show dummy", output={}) + json_cmd_2 = AntaCommand(command="show dummy", output="not_json") + text_cmd = AntaCommand(command="show dummy", ofmt="text", output="blah") + text_cmd_2 = AntaCommand(command="show dummy", ofmt="text", output={"not_a": "string"}) + msg = "Output of command 'show dummy' is invalid" + msg = "Output of command 'show dummy' is invalid" + with pytest.raises(RuntimeError, match=msg): + json_cmd.text_output + with pytest.raises(RuntimeError, match=msg): + text_cmd.json_output + with pytest.raises(RuntimeError, match=msg): + json_cmd_2.text_output + with pytest.raises(RuntimeError, match=msg): + text_cmd_2.json_output + + def test_supported(self) -> None: + """Test if the supported property.""" + command = AntaCommand(command="show hardware counter drop", errors=["Unavailable command (not supported on this hardware platform) (at token 2: 'counter')"]) + assert command.supported is False + command = AntaCommand( + command="show hardware counter drop", output={"totalAdverseDrops": 0, "totalCongestionDrops": 0, "totalPacketProcessorDrops": 0, "dropEvents": {}} + ) + assert command.supported is True + + def test_requires_privileges(self) -> None: + """Test if the requires_privileges property.""" + command = AntaCommand(command="show aaa methods accounting", errors=["Invalid input (privileged mode required)"]) + assert command.requires_privileges is True + command = AntaCommand( + command="show aaa methods accounting", + output={ + "commandsAcctMethods": {"privilege0-15": {"defaultMethods": [], "consoleMethods": []}}, + "execAcctMethods": {"exec": {"defaultMethods": [], "consoleMethods": []}}, + "systemAcctMethods": {"system": {"defaultMethods": [], "consoleMethods": []}}, + "dot1xAcctMethods": {"dot1x": {"defaultMethods": [], "consoleMethods": []}}, + }, + ) + assert command.requires_privileges is False |