Repository: aristanetworks/anta
Pull request #1013 (2025-01-20 04:27:44)

fix(anta.tests): AntaTest.Input subclasses using common input models should have validators for their required fields.
# Description

AntaTest.Input subclasses using common input models should have validators for their required fields.

### Changes

- input_models/bgp : `route_type` marked as required key and no reuse.
- tests/interfaces.py : Added validators for `status` field in VerifyInterfacesStatus test
- tests/interfaces.py : Added validators for `portchannel ` field in VerifyLACPInterfacesStatus test
- tests/routing/generic.py : Added validators for `route_type ` field in VerifyIPv4RouteType test


# Checklist:

- [X] My code follows the style guidelines of this project
- [X] I have performed a self-review of my code
- [X] I have run pre-commit for code linting and typing (`pre-commit run`)
- [ ] I have made corresponding changes to the documentation
- [X] I have added tests that prove my fix is effective or that my feature works
- [X] New and existing unit tests pass locally with my changes (`tox -e testenv`)


sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1013) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1013&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1013&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1013&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [80.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1013&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1013&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1013)


codspeed-hq[bot]: <!-- __CODSPEED_PERFORMANCE_REPORT_COMMENT__ -->
<!-- __CODSPEED_INSTRUMENTATION_PERFORMANCE_REPORT_COMMENT__ -->

## [CodSpeed Performance Report](https://codspeed.io/aristanetworks/anta/branches/vitthalmagadum%3Afix%2Fvalidators)

### Merging #1013 will **not alter performance**

<sub>Comparing <code>vitthalmagadum:fix/validators</code> (59b92e1) with <code>main</code> (419f6ca)</sub>



### Summary

`✅ 22` untouched benchmarks  




sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1013) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1013&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1013&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1013&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1013&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1013&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1013)



Patch:
diff --git a/anta/custom_types.py b/anta/custom_types.py
index 3af27db9..5810620e 100644
--- a/anta/custom_types.py
+++ b/anta/custom_types.py
@@ -263,3 +263,4 @@ SnmpVersion = Literal["v1", "v2c", "v3"]
 SnmpHashingAlgorithm = Literal["MD5", "SHA", "SHA-224", "SHA-256", "SHA-384", "SHA-512"]
 SnmpEncryptionAlgorithm = Literal["AES-128", "AES-192", "AES-256", "DES"]
 DynamicVlanSource = Literal["dmf", "dot1x", "dynvtep", "evpn", "mlag", "mlagsync", "mvpn", "swfwd", "vccbfd"]
+LogSeverityLevel = Literal["alerts", "critical", "debugging", "emergencies", "errors", "informational", "notifications", "warnings"]
diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py
index a34227a1..8b1256e1 100644
--- a/anta/input_models/routing/bgp.py
+++ b/anta/input_models/routing/bgp.py
@@ -224,8 +224,8 @@ class BgpRoute(BaseModel):
     """The IPv4 network address."""
     vrf: str = "default"
     """Optional VRF for the BGP peer. Defaults to `default`."""
-    paths: list[BgpRoutePath] | None = None
-    """A list of paths for the BGP route. Required field in the `VerifyBGPRouteOrigin` test."""
+    paths: list[BgpRoutePath]
+    """A list of paths for the BGP route."""
 
     def __str__(self) -> str:
         """Return a human-readable string representation of the BgpRoute for reporting.
diff --git a/anta/input_models/routing/generic.py b/anta/input_models/routing/generic.py
index b683a458..27761123 100644
--- a/anta/input_models/routing/generic.py
+++ b/anta/input_models/routing/generic.py
@@ -17,11 +17,11 @@ class IPv4Routes(BaseModel):
 
     model_config = ConfigDict(extra="forbid")
     prefix: IPv4Network
-    """The IPV4 network to validate the route type."""
+    """IPv4 prefix in CIDR notation."""
     vrf: str = "default"
     """VRF context. Defaults to `default` VRF."""
-    route_type: IPv4RouteType
-    """List of IPV4 Route type to validate the valid rout type."""
+    route_type: IPv4RouteType | None = None
+    """Expected route type. Required field in the `VerifyIPv4RouteType` test."""
 
     def __str__(self) -> str:
         """Return a human-readable string representation of the IPv4RouteType for reporting."""


Test patch:
diff --git a/anta/tests/interfaces.py b/anta/tests/interfaces.py
index 3191053f..551c416b 100644
--- a/anta/tests/interfaces.py
+++ b/anta/tests/interfaces.py
@@ -9,9 +9,9 @@ from __future__ import annotations
 
 import re
 from ipaddress import IPv4Interface
-from typing import Any, ClassVar
+from typing import Any, ClassVar, TypeVar
 
-from pydantic import BaseModel, Field
+from pydantic import BaseModel, Field, field_validator
 from pydantic_extra_types.mac_address import MacAddress
 
 from anta import GITHUB_SUGGESTION
@@ -23,6 +23,9 @@ from anta.tools import custom_division, format_data, get_failed_logs, get_item,
 
 BPS_GBPS_CONVERSIONS = 1000000000
 
+# Using a TypeVar for the InterfaceState model since mypy thinks it's a ClassVar and not a valid type when used in field validators
+T = TypeVar("T", bound=InterfaceState)
+
 
 class VerifyInterfaceUtilization(AntaTest):
     """Verifies that the utilization of interfaces is below a certain threshold.
@@ -226,6 +229,16 @@ class VerifyInterfacesStatus(AntaTest):
         """List of interfaces with their expected state."""
         InterfaceState: ClassVar[type[InterfaceState]] = InterfaceState
 
+        @field_validator("interfaces")
+        @classmethod
+        def validate_interfaces(cls, interfaces: list[T]) -> list[T]:
+            """Validate that 'status' field is provided in each interface."""
+            for interface in interfaces:
+                if interface.status is None:
+                    msg = f"{interface} 'status' field missing in the input"
+                    raise ValueError(msg)
+            return interfaces
+
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifyInterfacesStatus."""
@@ -891,6 +904,16 @@ class VerifyLACPInterfacesStatus(AntaTest):
         """List of interfaces with their expected state."""
         InterfaceState: ClassVar[type[InterfaceState]] = InterfaceState
 
+        @field_validator("interfaces")
+        @classmethod
+        def validate_interfaces(cls, interfaces: list[T]) -> list[T]:
+            """Validate that 'portchannel' field is provided in each interface."""
+            for interface in interfaces:
+                if interface.portchannel is None:
+                    msg = f"{interface} 'portchannel' field missing in the input"
+                    raise ValueError(msg)
+            return interfaces
+
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifyLACPInterfacesStatus."""
diff --git a/anta/tests/logging.py b/anta/tests/logging.py
index 4a1c594e..15ec3dd4 100644
--- a/anta/tests/logging.py
+++ b/anta/tests/logging.py
@@ -14,13 +14,12 @@ import re
 from ipaddress import IPv4Address
 from typing import TYPE_CHECKING, ClassVar
 
-from anta.models import AntaCommand, AntaTest
+from anta.custom_types import LogSeverityLevel
+from anta.models import AntaCommand, AntaTemplate, AntaTest
 
 if TYPE_CHECKING:
     import logging
 
-    from anta.models import AntaTemplate
-
 
 def _get_logging_states(logger: logging.Logger, command_output: str) -> str:
     """Parse `show logging` output and gets operational logging states used in the tests in this module.
@@ -201,35 +200,43 @@ class VerifyLoggingLogsGeneration(AntaTest):
 
     This test performs the following checks:
 
-      1. Sends a test log message at the **informational** level
-      2. Retrieves the most recent logs (last 30 seconds)
-      3. Verifies that the test message was successfully logged
-
-    !!! warning
-        EOS logging buffer should be set to severity level `informational` or higher for this test to work.
+      1. Sends a test log message at the specified severity log level.
+      2. Retrieves the most recent logs (last 30 seconds).
+      3. Verifies that the test message was successfully logged.
 
     Expected Results
     ----------------
     * Success: If logs are being generated and the test message is found in recent logs.
     * Failure: If any of the following occur:
-        - The test message is not found in recent logs
-        - The logging system is not capturing new messages
-        - No logs are being generated
+        - The test message is not found in recent logs.
+        - The logging system is not capturing new messages.
+        - No logs are being generated.
 
     Examples
     --------
     ```yaml
     anta.tests.logging:
       - VerifyLoggingLogsGeneration:
+          severity_level: informational
     ```
     """
 
     categories: ClassVar[list[str]] = ["logging"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [
-        AntaCommand(command="send log level informational message ANTA VerifyLoggingLogsGeneration validation", ofmt="text"),
-        AntaCommand(command="show logging informational last 30 seconds | grep ANTA", ofmt="text", use_cache=False),
+        AntaTemplate(template="send log level {severity_level} message ANTA VerifyLoggingLogsGeneration validation", ofmt="text"),
+        AntaTemplate(template="show logging {severity_level} last 30 seconds | grep ANTA", ofmt="text", use_cache=False),
     ]
 
+    class Input(AntaTest.Input):
+        """Input model for the VerifyLoggingLogsGeneration test."""
+
+        severity_level: LogSeverityLevel = "informational"
+        """Log severity level. Defaults to informational."""
+
+    def render(self, template: AntaTemplate) -> list[AntaCommand]:
+        """Render the template for log severity level in the input."""
+        return [template.render(severity_level=self.inputs.severity_level)]
+
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifyLoggingLogsGeneration."""
@@ -248,37 +255,45 @@ class VerifyLoggingHostname(AntaTest):
 
     This test performs the following checks:
 
-      1. Retrieves the device's configured FQDN
-      2. Sends a test log message at the **informational** level
-      3. Retrieves the most recent logs (last 30 seconds)
-      4. Verifies that the test message includes the complete FQDN of the device
-
-    !!! warning
-          EOS logging buffer should be set to severity level `informational` or higher for this test to work.
+      1. Retrieves the device's configured FQDN.
+      2. Sends a test log message at the specified severity log level.
+      3. Retrieves the most recent logs (last 30 seconds).
+      4. Verifies that the test message includes the complete FQDN of the device.
 
     Expected Results
     ----------------
     * Success: If logs are generated with the device's complete FQDN.
     * Failure: If any of the following occur:
-        - The test message is not found in recent logs
-        - The log message does not include the device's FQDN
-        - The FQDN in the log message doesn't match the configured FQDN
+        - The test message is not found in recent logs.
+        - The log message does not include the device's FQDN.
+        - The FQDN in the log message doesn't match the configured FQDN.
 
     Examples
     --------
     ```yaml
     anta.tests.logging:
       - VerifyLoggingHostname:
+          severity_level: informational
     ```
     """
 
     categories: ClassVar[list[str]] = ["logging"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [
         AntaCommand(command="show hostname", revision=1),
-        AntaCommand(command="send log level informational message ANTA VerifyLoggingHostname validation", ofmt="text"),
-        AntaCommand(command="show logging informational last 30 seconds | grep ANTA", ofmt="text", use_cache=False),
+        AntaTemplate(template="send log level {severity_level} message ANTA VerifyLoggingHostname validation", ofmt="text"),
+        AntaTemplate(template="show logging {severity_level} last 30 seconds | grep ANTA", ofmt="text", use_cache=False),
     ]
 
+    class Input(AntaTest.Input):
+        """Input model for the VerifyLoggingHostname test."""
+
+        severity_level: LogSeverityLevel = "informational"
+        """Log severity level. Defaults to informational."""
+
+    def render(self, template: AntaTemplate) -> list[AntaCommand]:
+        """Render the template for log severity level in the input."""
+        return [template.render(severity_level=self.inputs.severity_level)]
+
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifyLoggingHostname."""
@@ -303,37 +318,45 @@ class VerifyLoggingTimestamp(AntaTest):
 
     This test performs the following checks:
 
-      1. Sends a test log message at the **informational** level
-      2. Retrieves the most recent logs (last 30 seconds)
-      3. Verifies that the test message is present with a high-resolution RFC3339 timestamp format
-        - Example format: `2024-01-25T15:30:45.123456+00:00`
-        - Includes microsecond precision
-        - Contains timezone offset
-
-    !!! warning
-          EOS logging buffer should be set to severity level `informational` or higher for this test to work.
+      1. Sends a test log message at the specified severity log level.
+      2. Retrieves the most recent logs (last 30 seconds).
+      3. Verifies that the test message is present with a high-resolution RFC3339 timestamp format.
+        - Example format: `2024-01-25T15:30:45.123456+00:00`.
+        - Includes microsecond precision.
+        - Contains timezone offset.
 
     Expected Results
     ----------------
     * Success: If logs are generated with the correct high-resolution RFC3339 timestamp format.
     * Failure: If any of the following occur:
-        - The test message is not found in recent logs
-        - The timestamp format does not match the expected RFC3339 format
+        - The test message is not found in recent logs.
+        - The timestamp format does not match the expected RFC3339 format.
 
     Examples
     --------
     ```yaml
     anta.tests.logging:
       - VerifyLoggingTimestamp:
+          severity_level: informational
     ```
     """
 
     categories: ClassVar[list[str]] = ["logging"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [
-        AntaCommand(command="send log level informational message ANTA VerifyLoggingTimestamp validation", ofmt="text"),
-        AntaCommand(command="show logging informational last 30 seconds | grep ANTA", ofmt="text", use_cache=False),
+        AntaTemplate(template="send log level {severity_level} message ANTA VerifyLoggingTimestamp validation", ofmt="text"),
+        AntaTemplate(template="show logging {severity_level} last 30 seconds | grep ANTA", ofmt="text", use_cache=False),
     ]
 
+    class Input(AntaTest.Input):
+        """Input model for the VerifyLoggingTimestamp test."""
+
+        severity_level: LogSeverityLevel = "informational"
+        """Log severity level. Defaults to informational."""
+
+    def render(self, template: AntaTemplate) -> list[AntaCommand]:
+        """Render the template for log severity level in the input."""
+        return [template.render(severity_level=self.inputs.severity_level)]
+
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifyLoggingTimestamp."""
diff --git a/anta/tests/routing/generic.py b/anta/tests/routing/generic.py
index 97709aa4..c3cd7600 100644
--- a/anta/tests/routing/generic.py
+++ b/anta/tests/routing/generic.py
@@ -11,7 +11,7 @@ from functools import cache
 from ipaddress import IPv4Address, IPv4Interface
 from typing import TYPE_CHECKING, ClassVar, Literal
 
-from pydantic import model_validator
+from pydantic import field_validator, model_validator
 
 from anta.custom_types import PositiveInteger
 from anta.input_models.routing.generic import IPv4Routes
@@ -189,9 +189,10 @@ class VerifyIPv4RouteType(AntaTest):
     """Verifies the route-type of the IPv4 prefixes.
 
     This test performs the following checks for each IPv4 route:
-        1. Verifies that the specified VRF is configured.
-        2. Verifies that the specified IPv4 route is exists in the configuration.
-        3. Verifies that the the specified IPv4 route is of the expected type.
+
+      1. Verifies that the specified VRF is configured.
+      2. Verifies that the specified IPv4 route is exists in the configuration.
+      3. Verifies that the the specified IPv4 route is of the expected type.
 
     Expected Results
     ----------------
@@ -230,6 +231,17 @@ class VerifyIPv4RouteType(AntaTest):
         """Input model for the VerifyIPv4RouteType test."""
 
         routes_entries: list[IPv4Routes]
+        """List of IPv4 route(s)."""
+
+        @field_validator("routes_entries")
+        @classmethod
+        def validate_routes_entries(cls, routes_entries: list[IPv4Routes]) -> list[IPv4Routes]:
+            """Validate that 'route_type' field is provided in each BGP route entry."""
+            for entry in routes_entries:
+                if entry.route_type is None:
+                    msg = f"{entry} 'route_type' field missing in the input"
+                    raise ValueError(msg)
+            return routes_entries
 
     @AntaTest.anta_test
     def test(self) -> None:
diff --git a/examples/tests.yaml b/examples/tests.yaml
index 6a3ff456..f47285a1 100644
--- a/examples/tests.yaml
+++ b/examples/tests.yaml
@@ -291,6 +291,7 @@ anta.tests.logging:
       # Verifies there are no syslog messages with a severity of ERRORS or higher.
   - VerifyLoggingHostname:
       # Verifies if logs are generated with the device FQDN.
+      severity_level: informational
   - VerifyLoggingHosts:
       # Verifies logging hosts (syslog servers) for a specified VRF.
       hosts:
@@ -299,6 +300,7 @@ anta.tests.logging:
       vrf: default
   - VerifyLoggingLogsGeneration:
       # Verifies if logs are generated.
+      severity_level: informational
   - VerifyLoggingPersistent:
       # Verifies if logging persistent is enabled and logs are saved in flash.
   - VerifyLoggingSourceIntf:
@@ -307,6 +309,7 @@ anta.tests.logging:
       vrf: default
   - VerifyLoggingTimestamp:
       # Verifies if logs are generated with the appropriate timestamp.
+      severity_level: informational
   - VerifySyslogLogging:
       # Verifies if syslog logging is enabled.
 anta.tests.mlag:
diff --git a/tests/units/anta_tests/test_logging.py b/tests/units/anta_tests/test_logging.py
index 0c140881..3022d4cd 100644
--- a/tests/units/anta_tests/test_logging.py
+++ b/tests/units/anta_tests/test_logging.py
@@ -167,14 +167,14 @@ DATA: list[dict[str, Any]] = [
             "2023-05-10T13:54:21.463497-05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
             "Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingLogsGeneration validation\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "informational"},
         "expected": {"result": "success"},
     },
     {
         "name": "failure",
         "test": VerifyLoggingLogsGeneration,
         "eos_data": ["", "Log Buffer:\n"],
-        "inputs": None,
+        "inputs": {"severity_level": "notifications"},
         "expected": {"result": "failure", "messages": ["Logs are not generated"]},
     },
     {
@@ -186,7 +186,7 @@ DATA: list[dict[str, Any]] = [
             "2023-05-10T15:41:44.701810-05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
             "Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingHostname validation\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "informational"},
         "expected": {"result": "success"},
     },
     {
@@ -195,10 +195,10 @@ DATA: list[dict[str, Any]] = [
         "eos_data": [
             {"hostname": "NW-CORE", "fqdn": "NW-CORE.example.org"},
             "",
-            "2023-05-10T13:54:21.463497-05:00 NW-CORE ConfigAgent: %SYS-6-LOGMSG_INFO: "
+            "2023-05-10T13:54:21.463497-05:00 NW-CORE ConfigAgent: %SYS-6-LOGMSG_NOTICE: "
             "Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingLogsHostname validation\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "notifications"},
         "expected": {"result": "failure", "messages": ["Logs are not generated with the device FQDN"]},
     },
     {
@@ -211,7 +211,7 @@ DATA: list[dict[str, Any]] = [
             "2023-05-10T15:42:44.680813-05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
             "Other log\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "informational"},
         "expected": {"result": "success"},
     },
     {
@@ -224,7 +224,7 @@ DATA: list[dict[str, Any]] = [
             "2023-05-10T15:42:44.680813+05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
             "Other log\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "informational"},
         "expected": {"result": "success"},
     },
     {
@@ -232,10 +232,10 @@ DATA: list[dict[str, Any]] = [
         "test": VerifyLoggingTimestamp,
         "eos_data": [
             "",
-            "May 10 13:54:22 NE-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
+            "May 10 13:54:22 NE-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_ALERT: "
             "Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingTimestamp validation\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "alerts"},
         "expected": {"result": "failure", "messages": ["Logs are not generated with the appropriate timestamp format"]},
     },
     {
@@ -243,9 +243,9 @@ DATA: list[dict[str, Any]] = [
         "test": VerifyLoggingTimestamp,
         "eos_data": [
             "",
-            "May 10 13:54:22 NE-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: Message from arista on command-api (10.22.1.107): BLAH\n",
+            "May 10 13:54:22 NE-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_NOTICE: Message from arista on command-api (10.22.1.107): BLAH\n",
         ],
-        "inputs": None,
+        "inputs": {"severity_level": "notifications"},
         "expected": {"result": "failure", "messages": ["Logs are not generated with the appropriate timestamp format"]},
     },
     {
diff --git a/tests/units/input_models/routing/test_generic.py b/tests/units/input_models/routing/test_generic.py
new file mode 100644
index 00000000..59d069e0
--- /dev/null
+++ b/tests/units/input_models/routing/test_generic.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2023-2025 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
+"""Tests for anta.input_models.routing.generic.py."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import pytest
+from pydantic import ValidationError
+
+from anta.tests.routing.generic import VerifyIPv4RouteType
+
+if TYPE_CHECKING:
+    from anta.input_models.routing.generic import IPv4Routes
+
+
+class TestVerifyIPv4RouteTypeInput:
+    """Test anta.tests.routing.bgp.VerifyIPv4RouteType.Input."""
+
+    @pytest.mark.parametrize(
+        ("routes_entries"),
+        [
+            pytest.param([{"prefix": "192.168.0.0/24", "vrf": "default", "route_type": "eBGP"}], id="valid"),
+        ],
+    )
+    def test_valid(self, routes_entries: list[IPv4Routes]) -> None:
+        """Test VerifyIPv4RouteType.Input valid inputs."""
+        VerifyIPv4RouteType.Input(routes_entries=routes_entries)
+
+    @pytest.mark.parametrize(
+        ("routes_entries"),
+        [
+            pytest.param([{"prefix": "192.168.0.0/24", "vrf": "default"}], id="invalid"),
+        ],
+    )
+    def test_invalid(self, routes_entries: list[IPv4Routes]) -> None:
+        """Test VerifyIPv4RouteType.Input invalid inputs."""
+        with pytest.raises(ValidationError):
+            VerifyIPv4RouteType.Input(routes_entries=routes_entries)
diff --git a/tests/units/input_models/test_interfaces.py b/tests/units/input_models/test_interfaces.py
index ee850ee7..aefa3194 100644
--- a/tests/units/input_models/test_interfaces.py
+++ b/tests/units/input_models/test_interfaces.py
@@ -9,8 +9,10 @@ from __future__ import annotations
 from typing import TYPE_CHECKING
 
 import pytest
+from pydantic import ValidationError
 
 from anta.input_models.interfaces import InterfaceState
+from anta.tests.interfaces import VerifyInterfacesStatus, VerifyLACPInterfacesStatus
 
 if TYPE_CHECKING:
     from anta.custom_types import Interface, PortChannelInterface
@@ -31,3 +33,53 @@ class TestInterfaceState:
     def test_valid__str__(self, name: Interface, portchannel: PortChannelInterface | None, expected: str) -> None:
         """Test InterfaceState __str__."""
         assert str(InterfaceState(name=name, portchannel=portchannel)) == expected
+
+
+class TestVerifyInterfacesStatusInput:
+    """Test anta.tests.interfaces.VerifyInterfacesStatus.Input."""
+
+    @pytest.mark.parametrize(
+        ("interfaces"),
+        [
+            pytest.param([{"name": "Ethernet1", "status": "up"}], id="valid"),
+        ],
+    )
+    def test_valid(self, interfaces: list[InterfaceState]) -> None:
+        """Test VerifyInterfacesStatus.Input valid inputs."""
+        VerifyInterfacesStatus.Input(interfaces=interfaces)
+
+    @pytest.mark.parametrize(
+        ("interfaces"),
+        [
+            pytest.param([{"name": "Ethernet1"}], id="invalid"),
+        ],
+    )
+    def test_invalid(self, interfaces: list[InterfaceState]) -> None:
+        """Test VerifyInterfacesStatus.Input invalid inputs."""
+        with pytest.raises(ValidationError):
+            VerifyInterfacesStatus.Input(interfaces=interfaces)
+
+
+class TestVerifyLACPInterfacesStatusInput:
+    """Test anta.tests.interfaces.VerifyLACPInterfacesStatus.Input."""
+
+    @pytest.mark.parametrize(
+        ("interfaces"),
+        [
+            pytest.param([{"name": "Ethernet1", "portchannel": "Port-Channel100"}], id="valid"),
+        ],
+    )
+    def test_valid(self, interfaces: list[InterfaceState]) -> None:
+        """Test VerifyLACPInterfacesStatus.Input valid inputs."""
+        VerifyLACPInterfacesStatus.Input(interfaces=interfaces)
+
+    @pytest.mark.parametrize(
+        ("interfaces"),
+        [
+            pytest.param([{"name": "Ethernet1"}], id="invalid"),
+        ],
+    )
+    def test_invalid(self, interfaces: list[InterfaceState]) -> None:
+        """Test VerifyLACPInterfacesStatus.Input invalid inputs."""
+        with pytest.raises(ValidationError):
+            VerifyLACPInterfacesStatus.Input(interfaces=interfaces)