Repository: aristanetworks/anta
Pull request #1030 (2025-02-05 11:23:38)

fix(anta.tests): Implement logic to not test against all inputs for VerifyISISSegmentRoutingTunnels
# Description

Provides a better implementation of `anta.tests.routing.isis.VerifyISISSegmentRoutingTunnels` to ensure that each path definition is correctly tests and not against all entrie nodes.

<!-- PR description !-->

Fixes # (issue id)

# Checklist:

<!-- Delete not relevant items !-->

- [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`)
- [x] 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=1030) **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=1030&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=1030&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=1030&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [84.6% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1030&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=1030&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1030)


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=1030) **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=1030&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=1030&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=1030&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [91.7% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1030&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=1030&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1030)


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=1030) **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=1030&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=1030&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=1030&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [91.7% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1030&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=1030&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1030)



Patch:
diff --git a/anta/custom_types.py b/anta/custom_types.py
index c9cb1789..8ca3c1c2 100644
--- a/anta/custom_types.py
+++ b/anta/custom_types.py
@@ -55,7 +55,7 @@ def interface_autocomplete(v: str) -> str:
         raise ValueError(msg)
     intf_id = m[0]
 
-    alias_map = {"et": "Ethernet", "eth": "Ethernet", "po": "Port-Channel", "lo": "Loopback"}
+    alias_map = {"et": "Ethernet", "eth": "Ethernet", "po": "Port-Channel", "lo": "Loopback", "vl": "Vlan"}
 
     return next((f"{full_name}{intf_id}" for alias, full_name in alias_map.items() if v.lower().startswith(alias)), v)
 
diff --git a/anta/input_models/connectivity.py b/anta/input_models/connectivity.py
index 53581ea3..1a904ac1 100644
--- a/anta/input_models/connectivity.py
+++ b/anta/input_models/connectivity.py
@@ -5,7 +5,7 @@
 
 from __future__ import annotations
 
-from ipaddress import IPv4Address
+from ipaddress import IPv4Address, IPv6Address
 from typing import Any
 from warnings import warn
 
@@ -18,10 +18,10 @@ class Host(BaseModel):
     """Model for a remote host to ping."""
 
     model_config = ConfigDict(extra="forbid")
-    destination: IPv4Address
-    """IPv4 address to ping."""
-    source: IPv4Address | Interface
-    """IPv4 address source IP or egress interface to use."""
+    destination: IPv4Address | IPv6Address
+    """Destination address to ping."""
+    source: IPv4Address | IPv6Address | Interface
+    """Source address IP or egress interface to use."""
     vrf: str = "default"
     """VRF context. Defaults to `default`."""
     repeat: int = 2


Test patch:
diff --git a/anta/tests/connectivity.py b/anta/tests/connectivity.py
index 245baf46..2fd58c1b 100644
--- a/anta/tests/connectivity.py
+++ b/anta/tests/connectivity.py
@@ -7,11 +7,16 @@
 # mypy: disable-error-code=attr-defined
 from __future__ import annotations
 
-from typing import ClassVar
+from typing import ClassVar, TypeVar
+
+from pydantic import field_validator
 
 from anta.input_models.connectivity import Host, LLDPNeighbor, Neighbor
 from anta.models import AntaCommand, AntaTemplate, AntaTest
 
+# Using a TypeVar for the Host model since mypy thinks it's a ClassVar and not a valid type when used in field validators
+T = TypeVar("T", bound=Host)
+
 
 class VerifyReachability(AntaTest):
     """Test network reachability to one or many destination IP(s).
@@ -37,6 +42,11 @@ class VerifyReachability(AntaTest):
               vrf: MGMT
               df_bit: True
               size: 100
+            - source: fd12:3456:789a:1::1
+              destination: fd12:3456:789a:1::2
+              vrf: default
+              df_bit: True
+              size: 100
     ```
     """
 
@@ -54,6 +64,16 @@ class VerifyReachability(AntaTest):
         Host: ClassVar[type[Host]] = Host
         """To maintain backward compatibility."""
 
+        @field_validator("hosts")
+        @classmethod
+        def validate_hosts(cls, hosts: list[T]) -> list[T]:
+            """Validate the 'destination' and 'source' IP address family in each host."""
+            for host in hosts:
+                if not isinstance(host.source, str) and host.destination.version != host.source.version:
+                    msg = f"{host} IP address family for destination does not match source"
+                    raise ValueError(msg)
+            return hosts
+
     def render(self, template: AntaTemplate) -> list[AntaCommand]:
         """Render the template for each host in the input list."""
         return [
diff --git a/anta/tests/logging.py b/anta/tests/logging.py
index 15ec3dd4..8c234fc0 100644
--- a/anta/tests/logging.py
+++ b/anta/tests/logging.py
@@ -192,7 +192,7 @@ class VerifyLoggingHosts(AntaTest):
         if not not_configured:
             self.result.is_success()
         else:
-            self.result.is_failure(f"Syslog servers {not_configured} are not configured in VRF {self.inputs.vrf}")
+            self.result.is_failure(f"Syslog servers {', '.join(not_configured)} are not configured in VRF {self.inputs.vrf}")
 
 
 class VerifyLoggingLogsGeneration(AntaTest):
diff --git a/anta/tests/routing/generic.py b/anta/tests/routing/generic.py
index 139e1816..338aa615 100644
--- a/anta/tests/routing/generic.py
+++ b/anta/tests/routing/generic.py
@@ -112,7 +112,7 @@ class VerifyRoutingTableSize(AntaTest):
         if self.inputs.minimum <= total_routes <= self.inputs.maximum:
             self.result.is_success()
         else:
-            self.result.is_failure(f"routing-table has {total_routes} routes and not between min ({self.inputs.minimum}) and maximum ({self.inputs.maximum})")
+            self.result.is_failure(f"Routing table has {total_routes} routes, outside the range of {self.inputs.minimum} to {self.inputs.maximum}")
 
 
 class VerifyRoutingTableEntry(AntaTest):
@@ -182,7 +182,7 @@ class VerifyRoutingTableEntry(AntaTest):
         if not missing_routes:
             self.result.is_success()
         else:
-            self.result.is_failure(f"The following route(s) are missing from the routing table of VRF {self.inputs.vrf}: {missing_routes}")
+            self.result.is_failure(f"The following route(s) are missing from the routing table of VRF {self.inputs.vrf}: {', '.join(missing_routes)}")
 
 
 class VerifyIPv4RouteType(AntaTest):
diff --git a/anta/tests/routing/isis.py b/anta/tests/routing/isis.py
index 562ff6d6..3f1f862e 100644
--- a/anta/tests/routing/isis.py
+++ b/anta/tests/routing/isis.py
@@ -592,9 +592,6 @@ class VerifyISISSegmentRoutingTunnels(AntaTest):
         command_output = self.instance_commands[0].json_output
         self.result.is_success()
 
-        # initiate defaults
-        failure_message = []
-
         if len(command_output["entries"]) == 0:
             self.result.is_skipped("IS-IS-SR is not running on device.")
             return
@@ -602,129 +599,31 @@ class VerifyISISSegmentRoutingTunnels(AntaTest):
         for input_entry in self.inputs.entries:
             eos_entry = self._eos_entry_lookup(search_value=input_entry.endpoint, entries=command_output["entries"])
             if eos_entry is None:
-                failure_message.append(f"Tunnel to {input_entry} is not found.")
+                self.result.is_failure(f"Tunnel to {input_entry.endpoint!s} is not found.")
             elif input_entry.vias is not None:
-                failure_src = []
                 for via_input in input_entry.vias:
-                    if not self._check_tunnel_type(via_input, eos_entry):
-                        failure_src.append("incorrect tunnel type")
-                    if not self._check_tunnel_nexthop(via_input, eos_entry):
-                        failure_src.append("incorrect nexthop")
-                    if not self._check_tunnel_interface(via_input, eos_entry):
-                        failure_src.append("incorrect interface")
-                    if not self._check_tunnel_id(via_input, eos_entry):
-                        failure_src.append("incorrect tunnel ID")
-
-                if failure_src:
-                    failure_message.append(f"Tunnel to {input_entry.endpoint!s} is incorrect: {', '.join(failure_src)}")
-
-        if failure_message:
-            self.result.is_failure("\n".join(failure_message))
-
-    def _check_tunnel_type(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool:
-        """Check if the tunnel type specified in `via_input` matches any of the tunnel types in `eos_entry`.
-
-        Parameters
-        ----------
-        via_input : VerifyISISSegmentRoutingTunnels.Input.Entry.Vias
-            The input tunnel type to check.
-        eos_entry : dict[str, Any]
-            The EOS entry containing the tunnel types.
-
-        Returns
-        -------
-        bool
-            True if the tunnel type matches any of the tunnel types in `eos_entry`, False otherwise.
-        """
-        if via_input.type is not None:
-            return any(
-                via_input.type
-                == get_value(
-                    dictionary=eos_via,
-                    key="type",
-                    default="undefined",
-                )
-                for eos_via in eos_entry["vias"]
-            )
-        return True
-
-    def _check_tunnel_nexthop(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool:
-        """Check if the tunnel nexthop matches the given input.
-
-        Parameters
-        ----------
-        via_input : VerifyISISSegmentRoutingTunnels.Input.Entry.Vias
-            The input via object.
-        eos_entry : dict[str, Any]
-            The EOS entry dictionary.
-
-        Returns
-        -------
-        bool
-            True if the tunnel nexthop matches, False otherwise.
-        """
-        if via_input.nexthop is not None:
-            return any(
-                str(via_input.nexthop)
-                == get_value(
-                    dictionary=eos_via,
-                    key="nexthop",
-                    default="undefined",
-                )
-                for eos_via in eos_entry["vias"]
-            )
-        return True
-
-    def _check_tunnel_interface(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool:
-        """Check if the tunnel interface exists in the given EOS entry.
-
-        Parameters
-        ----------
-        via_input : VerifyISISSegmentRoutingTunnels.Input.Entry.Vias
-            The input via object.
-        eos_entry : dict[str, Any]
-            The EOS entry dictionary.
+                    via_search_result = any(self._via_matches(via_input, eos_via) for eos_via in eos_entry["vias"])
+                    if not via_search_result:
+                        self.result.is_failure(f"Tunnel to {input_entry.endpoint!s} is incorrect.")
 
-        Returns
-        -------
-        bool
-            True if the tunnel interface exists, False otherwise.
-        """
-        if via_input.interface is not None:
-            return any(
-                via_input.interface
-                == get_value(
-                    dictionary=eos_via,
-                    key="interface",
-                    default="undefined",
-                )
-                for eos_via in eos_entry["vias"]
-            )
-        return True
-
-    def _check_tunnel_id(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool:
-        """Check if the tunnel ID matches any of the tunnel IDs in the EOS entry's vias.
+    def _via_matches(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_via: dict[str, Any]) -> bool:
+        """Check if the via input matches the eos via.
 
         Parameters
         ----------
         via_input : VerifyISISSegmentRoutingTunnels.Input.Entry.Vias
-            The input vias to check.
-        eos_entry : dict[str, Any])
-            The EOS entry to compare against.
+            The input via to check.
+        eos_via : dict[str, Any]
+            The EOS via to compare against.
 
         Returns
         -------
         bool
-            True if the tunnel ID matches any of the tunnel IDs in the EOS entry's vias, False otherwise.
+            True if the via input matches the eos via, False otherwise.
         """
-        if via_input.tunnel_id is not None:
-            return any(
-                via_input.tunnel_id.upper()
-                == get_value(
-                    dictionary=eos_via,
-                    key="tunnelId.type",
-                    default="undefined",
-                ).upper()
-                for eos_via in eos_entry["vias"]
-            )
-        return True
+        return (
+            (via_input.type is None or via_input.type == eos_via.get("type"))
+            and (via_input.nexthop is None or str(via_input.nexthop) == eos_via.get("nexthop"))
+            and (via_input.interface is None or via_input.interface == eos_via.get("interface"))
+            and (via_input.tunnel_id is None or via_input.tunnel_id.upper() == get_value(eos_via, "tunnelId.type", default="").upper())
+        )
diff --git a/anta/tests/security.py b/anta/tests/security.py
index 78908050..12a3faee 100644
--- a/anta/tests/security.py
+++ b/anta/tests/security.py
@@ -96,7 +96,7 @@ class VerifySSHIPv4Acl(AntaTest):
         not_configured_acl = [acl["name"] for acl in ipv4_acl_list if self.inputs.vrf not in acl["configuredVrfs"] or self.inputs.vrf not in acl["activeVrfs"]]
 
         if not_configured_acl:
-            self.result.is_failure(f"SSH IPv4 ACL(s) not configured or active in vrf {self.inputs.vrf}: {not_configured_acl}")
+            self.result.is_failure(f"SSH IPv4 ACL(s) not configured or active in vrf {self.inputs.vrf}: {', '.join(not_configured_acl)}")
         else:
             self.result.is_success()
 
@@ -144,7 +144,7 @@ class VerifySSHIPv6Acl(AntaTest):
         not_configured_acl = [acl["name"] for acl in ipv6_acl_list if self.inputs.vrf not in acl["configuredVrfs"] or self.inputs.vrf not in acl["activeVrfs"]]
 
         if not_configured_acl:
-            self.result.is_failure(f"SSH IPv6 ACL(s) not configured or active in vrf {self.inputs.vrf}: {not_configured_acl}")
+            self.result.is_failure(f"SSH IPv6 ACL(s) not configured or active in vrf {self.inputs.vrf}: {', '.join(not_configured_acl)}")
         else:
             self.result.is_success()
 
@@ -224,7 +224,6 @@ class VerifyAPIHttpsSSL(AntaTest):
     ```
     """
 
-    description = "Verifies if the eAPI has a valid SSL profile."
     categories: ClassVar[list[str]] = ["security"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show management api http-commands", revision=1)]
 
@@ -242,10 +241,10 @@ class VerifyAPIHttpsSSL(AntaTest):
             if command_output["sslProfile"]["name"] == self.inputs.profile and command_output["sslProfile"]["state"] == "valid":
                 self.result.is_success()
             else:
-                self.result.is_failure(f"eAPI HTTPS server SSL profile ({self.inputs.profile}) is misconfigured or invalid")
+                self.result.is_failure(f"eAPI HTTPS server SSL profile {self.inputs.profile} is misconfigured or invalid")
 
         except KeyError:
-            self.result.is_failure(f"eAPI HTTPS server SSL profile ({self.inputs.profile}) is not configured")
+            self.result.is_failure(f"eAPI HTTPS server SSL profile {self.inputs.profile} is not configured")
 
 
 class VerifyAPIIPv4Acl(AntaTest):
@@ -290,7 +289,7 @@ class VerifyAPIIPv4Acl(AntaTest):
         not_configured_acl = [acl["name"] for acl in ipv4_acl_list if self.inputs.vrf not in acl["configuredVrfs"] or self.inputs.vrf not in acl["activeVrfs"]]
 
         if not_configured_acl:
-            self.result.is_failure(f"eAPI IPv4 ACL(s) not configured or active in vrf {self.inputs.vrf}: {not_configured_acl}")
+            self.result.is_failure(f"eAPI IPv4 ACL(s) not configured or active in vrf {self.inputs.vrf}: {', '.join(not_configured_acl)}")
         else:
             self.result.is_success()
 
@@ -338,7 +337,7 @@ class VerifyAPIIPv6Acl(AntaTest):
         not_configured_acl = [acl["name"] for acl in ipv6_acl_list if self.inputs.vrf not in acl["configuredVrfs"] or self.inputs.vrf not in acl["activeVrfs"]]
 
         if not_configured_acl:
-            self.result.is_failure(f"eAPI IPv6 ACL(s) not configured or active in vrf {self.inputs.vrf}: {not_configured_acl}")
+            self.result.is_failure(f"eAPI IPv6 ACL(s) not configured or active in vrf {self.inputs.vrf}: {', '.join(not_configured_acl)}")
         else:
             self.result.is_success()
 
diff --git a/examples/tests.yaml b/examples/tests.yaml
index 790018ad..b190ec3c 100644
--- a/examples/tests.yaml
+++ b/examples/tests.yaml
@@ -135,6 +135,11 @@ anta.tests.connectivity:
           vrf: MGMT
           df_bit: True
           size: 100
+        - source: fd12:3456:789a:1::1
+          destination: fd12:3456:789a:1::2
+          vrf: default
+          df_bit: True
+          size: 100
 anta.tests.cvx:
   - VerifyActiveCVXConnections:
       # Verifies the number of active CVX Connections.
@@ -664,7 +669,7 @@ anta.tests.security:
   - VerifyAPIHttpStatus:
       # Verifies if eAPI HTTP server is disabled globally.
   - VerifyAPIHttpsSSL:
-      # Verifies if the eAPI has a valid SSL profile.
+      # Verifies if eAPI HTTPS server SSL profile is configured and valid.
       profile: default
   - VerifyAPIIPv4Acl:
       # Verifies if eAPI has the right number IPv4 ACL(s) configured for a specified VRF.
diff --git a/tests/units/anta_tests/routing/test_generic.py b/tests/units/anta_tests/routing/test_generic.py
index 2f4a6c56..57ad584a 100644
--- a/tests/units/anta_tests/routing/test_generic.py
+++ b/tests/units/anta_tests/routing/test_generic.py
@@ -68,7 +68,7 @@ DATA: list[dict[str, Any]] = [
             },
         ],
         "inputs": {"minimum": 42, "maximum": 666},
-        "expected": {"result": "failure", "messages": ["routing-table has 1000 routes and not between min (42) and maximum (666)"]},
+        "expected": {"result": "failure", "messages": ["Routing table has 1000 routes, outside the range of 42 to 666"]},
     },
     {
         "name": "success",
@@ -204,9 +204,20 @@ DATA: list[dict[str, Any]] = [
                     },
                 },
             },
+            {
+                "vrfs": {
+                    "default": {
+                        "routingDisabled": False,
+                        "allRoutesProgrammedHardware": True,
+                        "allRoutesProgrammedKernel": True,
+                        "defaultRouteState": "notSet",
+                        "routes": {},
+                    },
+                },
+            },
         ],
-        "inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2"]},
-        "expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: ['10.1.0.1']"]},
+        "inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2", "10.1.0.3"]},
+        "expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: 10.1.0.1, 10.1.0.3"]},
     },
     {
         "name": "failure-wrong-route",
@@ -260,7 +271,7 @@ DATA: list[dict[str, Any]] = [
             },
         ],
         "inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2"]},
-        "expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: ['10.1.0.2']"]},
+        "expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: 10.1.0.2"]},
     },
     {
         "name": "failure-wrong-route-collect-all",
@@ -302,7 +313,7 @@ DATA: list[dict[str, Any]] = [
             },
         ],
         "inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2"], "collect": "all"},
-        "expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: ['10.1.0.2']"]},
+        "expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: 10.1.0.2"]},
     },
     {
         "name": "success-valid-route-type",
diff --git a/tests/units/anta_tests/routing/test_isis.py b/tests/units/anta_tests/routing/test_isis.py
index 9c379eae..a885d065 100644
--- a/tests/units/anta_tests/routing/test_isis.py
+++ b/tests/units/anta_tests/routing/test_isis.py
@@ -1405,7 +1405,7 @@ DATA: list[dict[str, Any]] = [
         },
         "expected": {
             "result": "failure",
-            "messages": ["Tunnel to endpoint=IPv4Network('1.0.0.122/32') vias=None is not found."],
+            "messages": ["Tunnel to 1.0.0.122/32 is not found."],
         },
     },
     {
@@ -1486,7 +1486,7 @@ DATA: list[dict[str, Any]] = [
         },
         "expected": {
             "result": "failure",
-            "messages": ["Tunnel to 1.0.0.13/32 is incorrect: incorrect tunnel type"],
+            "messages": ["Tunnel to 1.0.0.13/32 is incorrect."],
         },
     },
     {
@@ -1574,7 +1574,7 @@ DATA: list[dict[str, Any]] = [
         },
         "expected": {
             "result": "failure",
-            "messages": ["Tunnel to 1.0.0.122/32 is incorrect: incorrect nexthop"],
+            "messages": ["Tunnel to 1.0.0.122/32 is incorrect."],
         },
     },
     {
@@ -1662,7 +1662,7 @@ DATA: list[dict[str, Any]] = [
         },
         "expected": {
             "result": "failure",
-            "messages": ["Tunnel to 1.0.0.122/32 is incorrect: incorrect interface"],
+            "messages": ["Tunnel to 1.0.0.122/32 is incorrect."],
         },
     },
     {
@@ -1750,7 +1750,7 @@ DATA: list[dict[str, Any]] = [
         },
         "expected": {
             "result": "failure",
-            "messages": ["Tunnel to 1.0.0.122/32 is incorrect: incorrect nexthop"],
+            "messages": ["Tunnel to 1.0.0.122/32 is incorrect."],
         },
     },
     {
@@ -1837,7 +1837,28 @@ DATA: list[dict[str, Any]] = [
         },
         "expected": {
             "result": "failure",
-            "messages": ["Tunnel to 1.0.0.111/32 is incorrect: incorrect tunnel ID"],
+            "messages": ["Tunnel to 1.0.0.111/32 is incorrect."],
+        },
+    },
+    {
+        "test": VerifyISISSegmentRoutingTunnels,
+        "name": "skipped with ISIS-SR not running",
+        "eos_data": [{"entries": {}}],
+        "inputs": {
+            "entries": [
+                {"endpoint": "1.0.0.122/32"},
+                {"endpoint": "1.0.0.13/32", "vias": [{"type": "ip"}]},
+                {
+                    "endpoint": "1.0.0.111/32",
+                    "vias": [
+                        {"type": "tunnel", "tunnel_id": "unset"},
+                    ],
+                },
+            ]
+        },
+        "expected": {
+            "result": "skipped",
+            "messages": ["IS-IS-SR is not running on device."],
         },
     },
 ]
diff --git a/tests/units/anta_tests/test_connectivity.py b/tests/units/anta_tests/test_connectivity.py
index 0e37e053..d367258e 100644
--- a/tests/units/anta_tests/test_connectivity.py
+++ b/tests/units/anta_tests/test_connectivity.py
@@ -45,6 +45,46 @@ DATA: list[dict[str, Any]] = [
         ],
         "expected": {"result": "success"},
     },
+    {
+        "name": "success-ipv6",
+        "test": VerifyReachability,
+        "eos_data": [
+            {
+                "messages": [
+                    """PING fd12:3456:789a:1::2(fd12:3456:789a:1::2) from fd12:3456:789a:1::1 : 52 data bytes
+                60 bytes from fd12:3456:789a:1::2: icmp_seq=1 ttl=64 time=0.097 ms
+                60 bytes from fd12:3456:789a:1::2: icmp_seq=2 ttl=64 time=0.033 ms
+
+                --- fd12:3456:789a:1::2 ping statistics ---
+                2 packets transmitted, 2 received, 0% packet loss, time 0ms
+                rtt min/avg/max/mdev = 0.033/0.065/0.097/0.032 ms, ipg/ewma 0.148/0.089 ms
+                """,
+                ],
+            },
+        ],
+        "inputs": {"hosts": [{"destination": "fd12:3456:789a:1::2", "source": "fd12:3456:789a:1::1"}]},
+        "expected": {"result": "success"},
+    },
+    {
+        "name": "success-ipv6-vlan",
+        "test": VerifyReachability,
+        "eos_data": [
+            {
+                "messages": [
+                    """PING fd12:3456:789a:1::2(fd12:3456:789a:1::2) 52 data bytes
+                60 bytes from fd12:3456:789a:1::2: icmp_seq=1 ttl=64 time=0.094 ms
+                60 bytes from fd12:3456:789a:1::2: icmp_seq=2 ttl=64 time=0.027 ms
+
+                --- fd12:3456:789a:1::2 ping statistics ---
+                2 packets transmitted, 2 received, 0% packet loss, time 0ms
+                rtt min/avg/max/mdev = 0.027/0.060/0.094/0.033 ms, ipg/ewma 0.152/0.085 ms
+                """,
+                ],
+            },
+        ],
+        "inputs": {"hosts": [{"destination": "fd12:3456:789a:1::2", "source": "vl110"}]},
+        "expected": {"result": "success"},
+    },
     {
         "name": "success-interface",
         "test": VerifyReachability,
@@ -155,6 +195,23 @@ DATA: list[dict[str, Any]] = [
         ],
         "expected": {"result": "failure", "messages": ["Host 10.0.0.11 (src: 10.0.0.5, vrf: default, size: 100B, repeat: 2) - Unreachable"]},
     },
+    {
+        "name": "failure-ipv6",
+        "test": VerifyReachability,
+        "eos_data": [
+            {
+                "messages": [
+                    """PING fd12:3456:789a:1::2(fd12:3456:789a:1::2) from fd12:3456:789a:1::1 : 52 data bytes
+
+                    --- fd12:3456:789a:1::3 ping statistics ---
+                    2 packets transmitted, 0 received, 100% packet loss, time 10ms
+                """,
+                ],
+            },
+        ],
+        "inputs": {"hosts": [{"destination": "fd12:3456:789a:1::2", "source": "fd12:3456:789a:1::1"}]},
+        "expected": {"result": "failure", "messages": ["Host fd12:3456:789a:1::2 (src: fd12:3456:789a:1::1, vrf: default, size: 100B, repeat: 2) - Unreachable"]},
+    },
     {
         "name": "failure-interface",
         "test": VerifyReachability,
diff --git a/tests/units/anta_tests/test_logging.py b/tests/units/anta_tests/test_logging.py
index 3022d4cd..a4b93e72 100644
--- a/tests/units/anta_tests/test_logging.py
+++ b/tests/units/anta_tests/test_logging.py
@@ -142,7 +142,7 @@ DATA: list[dict[str, Any]] = [
                 """,
         ],
         "inputs": {"hosts": ["10.22.10.92", "10.22.10.93", "10.22.10.94"], "vrf": "MGMT"},
-        "expected": {"result": "failure", "messages": ["Syslog servers ['10.22.10.93', '10.22.10.94'] are not configured in VRF MGMT"]},
+        "expected": {"result": "failure", "messages": ["Syslog servers 10.22.10.93, 10.22.10.94 are not configured in VRF MGMT"]},
     },
     {
         "name": "failure-vrf",
@@ -157,7 +157,7 @@ DATA: list[dict[str, Any]] = [
                 """,
         ],
         "inputs": {"hosts": ["10.22.10.92", "10.22.10.93", "10.22.10.94"], "vrf": "MGMT"},
-        "expected": {"result": "failure", "messages": ["Syslog servers ['10.22.10.93', '10.22.10.94'] are not configured in VRF MGMT"]},
+        "expected": {"result": "failure", "messages": ["Syslog servers 10.22.10.93, 10.22.10.94 are not configured in VRF MGMT"]},
     },
     {
         "name": "success",
diff --git a/tests/units/anta_tests/test_security.py b/tests/units/anta_tests/test_security.py
index a3e71013..5974eb92 100644
--- a/tests/units/anta_tests/test_security.py
+++ b/tests/units/anta_tests/test_security.py
@@ -90,7 +90,7 @@ DATA: list[dict[str, Any]] = [
         "test": VerifySSHIPv4Acl,
         "eos_data": [{"ipAclList": {"aclList": [{"type": "Ip4Acl", "name": "ACL_IPV4_SSH", "configuredVrfs": ["default"], "activeVrfs": ["default"]}]}}],
         "inputs": {"number": 1, "vrf": "MGMT"},
-        "expected": {"result": "failure", "messages": ["SSH IPv4 ACL(s) not configured or active in vrf MGMT: ['ACL_IPV4_SSH']"]},
+        "expected": {"result": "failure", "messages": ["SSH IPv4 ACL(s) not configured or active in vrf MGMT: ACL_IPV4_SSH"]},
     },
     {
         "name": "success",
@@ -111,7 +111,7 @@ DATA: list[dict[str, Any]] = [
         "test": VerifySSHIPv6Acl,
         "eos_data": [{"ipv6AclList": {"aclList": [{"type": "Ip6Acl", "name": "ACL_IPV6_SSH", "configuredVrfs": ["default"], "activeVrfs": ["default"]}]}}],
         "inputs": {"number": 1, "vrf": "MGMT"},
-        "expected": {"result": "failure", "messages": ["SSH IPv6 ACL(s) not configured or active in vrf MGMT: ['ACL_IPV6_SSH']"]},
+        "expected": {"result": "failure", "messages": ["SSH IPv6 ACL(s) not configured or active in vrf MGMT: ACL_IPV6_SSH"]},
     },
     {
         "name": "success",
@@ -192,7 +192,7 @@ DATA: list[dict[str, Any]] = [
             },
         ],
         "inputs": {"profile": "API_SSL_Profile"},
-        "expected": {"result": "failure", "messages": ["eAPI HTTPS server SSL profile (API_SSL_Profile) is not configured"]},
+        "expected": {"result": "failure", "messages": ["eAPI HTTPS server SSL profile API_SSL_Profile is not configured"]},
     },
     {
         "name": "failure-misconfigured-invalid",
@@ -209,7 +209,7 @@ DATA: list[dict[str, Any]] = [
             },
         ],
         "inputs": {"profile": "API_SSL_Profile"},
-        "expected": {"result": "failure", "messages": ["eAPI HTTPS server SSL profile (API_SSL_Profile) is misconfigured or invalid"]},
+        "expected": {"result": "failure", "messages": ["eAPI HTTPS server SSL profile API_SSL_Profile is misconfigured or invalid"]},
     },
     {
         "name": "success",
@@ -230,7 +230,7 @@ DATA: list[dict[str, Any]] = [
         "test": VerifyAPIIPv4Acl,
         "eos_data": [{"ipAclList": {"aclList": [{"type": "Ip4Acl", "name": "ACL_IPV4_API", "configuredVrfs": ["default"], "activeVrfs": ["default"]}]}}],
         "inputs": {"number": 1, "vrf": "MGMT"},
-        "expected": {"result": "failure", "messages": ["eAPI IPv4 ACL(s) not configured or active in vrf MGMT: ['ACL_IPV4_API']"]},
+        "expected": {"result": "failure", "messages": ["eAPI IPv4 ACL(s) not configured or active in vrf MGMT: ACL_IPV4_API"]},
     },
     {
         "name": "success",
@@ -251,7 +251,7 @@ DATA: list[dict[str, Any]] = [
         "test": VerifyAPIIPv6Acl,
         "eos_data": [{"ipv6AclList": {"aclList": [{"type": "Ip6Acl", "name": "ACL_IPV6_API", "configuredVrfs": ["default"], "activeVrfs": ["default"]}]}}],
         "inputs": {"number": 1, "vrf": "MGMT"},
-        "expected": {"result": "failure", "messages": ["eAPI IPv6 ACL(s) not configured or active in vrf MGMT: ['ACL_IPV6_API']"]},
+        "expected": {"result": "failure", "messages": ["eAPI IPv6 ACL(s) not configured or active in vrf MGMT: ACL_IPV6_API"]},
     },
     {
         "name": "success",
diff --git a/tests/units/input_models/test_connectivity.py b/tests/units/input_models/test_connectivity.py
new file mode 100644
index 00000000..9e4288cf
--- /dev/null
+++ b/tests/units/input_models/test_connectivity.py
@@ -0,0 +1,43 @@
+# 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.connectivity.py."""
+
+# pylint: disable=C0302
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import pytest
+from pydantic import ValidationError
+
+from anta.tests.connectivity import VerifyReachability
+
+if TYPE_CHECKING:
+    from anta.input_models.connectivity import Host
+
+
+class TestVerifyReachabilityInput:
+    """Test anta.tests.connectivity.VerifyReachability.Input."""
+
+    @pytest.mark.parametrize(
+        ("hosts"),
+        [
+            pytest.param([{"destination": "fd12:3456:789a:1::2", "source": "fd12:3456:789a:1::1"}], id="valid"),
+        ],
+    )
+    def test_valid(self, hosts: list[Host]) -> None:
+        """Test VerifyReachability.Input valid inputs."""
+        VerifyReachability.Input(hosts=hosts)
+
+    @pytest.mark.parametrize(
+        ("hosts"),
+        [
+            pytest.param([{"destination": "fd12:3456:789a:1::2", "source": "192.168.0.10"}], id="invalid-source"),
+            pytest.param([{"destination": "192.168.0.10", "source": "fd12:3456:789a:1::2"}], id="invalid-destination"),
+        ],
+    )
+    def test_invalid(self, hosts: list[Host]) -> None:
+        """Test VerifyReachability.Input invalid inputs."""
+        with pytest.raises(ValidationError):
+            VerifyReachability.Input(hosts=hosts)
diff --git a/tests/units/test_custom_types.py b/tests/units/test_custom_types.py
index b82686b3..5a92092c 100644
--- a/tests/units/test_custom_types.py
+++ b/tests/units/test_custom_types.py
@@ -189,6 +189,7 @@ def test_interface_autocomplete_success() -> None:
     assert interface_autocomplete("lo4") == "Loopback4"
     assert interface_autocomplete("Po1000") == "Port-Channel1000"
     assert interface_autocomplete("Po 1000") == "Port-Channel1000"
+    assert interface_autocomplete("Vl1000") == "Vlan1000"
 
 
 def test_interface_autocomplete_no_alias() -> None: