summaryrefslogtreecommitdiffstats
path: root/tests/units/test_models.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/units/test_models.py301
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