diff --git a/anta/custom_types.py b/anta/custom_types.py index 8ca3c1c2f..67646f6e7 100644 --- a/anta/custom_types.py +++ b/anta/custom_types.py @@ -23,16 +23,6 @@ REGEXP_TYPE_HOSTNAME = r"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$" """Match hostname like `my-hostname`, `my-hostname-1`, `my-hostname-1-2`.""" -# Regexp BGP AFI/SAFI -REGEXP_BGP_L2VPN_AFI = r"\b(l2[\s\-]?vpn[\s\-]?evpn)\b" -"""Match L2VPN EVPN AFI.""" -REGEXP_BGP_IPV4_MPLS_LABELS = r"\b(ipv4[\s\-]?mpls[\s\-]?label(s)?)\b" -"""Match IPv4 MPLS Labels.""" -REGEX_BGP_IPV4_MPLS_VPN = r"\b(ipv4[\s\-]?mpls[\s\-]?vpn)\b" -"""Match IPv4 MPLS VPN.""" -REGEX_BGP_IPV4_UNICAST = r"\b(ipv4[\s\-]?uni[\s\-]?cast)\b" -"""Match IPv4 Unicast.""" - def aaa_group_prefix(v: str) -> str: """Prefix the AAA method with 'group' if it is known.""" @@ -78,26 +68,57 @@ def interface_case_sensitivity(v: str) -> str: def bgp_multiprotocol_capabilities_abbreviations(value: str) -> str: """Abbreviations for different BGP multiprotocol capabilities. + Handles different separators (hyphen, underscore, space) and case sensitivity. + Examples -------- - - IPv4 Unicast - - L2vpnEVPN - - ipv4 MPLS Labels - - ipv4Mplsvpn - + ```python + >>> bgp_multiprotocol_capabilities_abbreviations("IPv4 Unicast") + 'ipv4Unicast' + >>> bgp_multiprotocol_capabilities_abbreviations("ipv4-Flow_Spec Vpn") + 'ipv4FlowSpecVpn' + >>> bgp_multiprotocol_capabilities_abbreviations("ipv6_labeled-unicast") + 'ipv6MplsLabels' + >>> bgp_multiprotocol_capabilities_abbreviations("ipv4_mpls_vpn") + 'ipv4MplsVpn' + >>> bgp_multiprotocol_capabilities_abbreviations("ipv4 mpls labels") + 'ipv4MplsLabels' + >>> bgp_multiprotocol_capabilities_abbreviations("rt-membership") + 'rtMembership' + >>> bgp_multiprotocol_capabilities_abbreviations("dynamic-path-selection") + 'dps' + ``` """ patterns = { - REGEXP_BGP_L2VPN_AFI: "l2VpnEvpn", - REGEXP_BGP_IPV4_MPLS_LABELS: "ipv4MplsLabels", - REGEX_BGP_IPV4_MPLS_VPN: "ipv4MplsVpn", - REGEX_BGP_IPV4_UNICAST: "ipv4Unicast", + f"{r'dynamic[-_ ]?path[-_ ]?selection$'}": "dps", + f"{r'dps$'}": "dps", + f"{r'ipv4[-_ ]?unicast$'}": "ipv4Unicast", + f"{r'ipv6[-_ ]?unicast$'}": "ipv6Unicast", + f"{r'ipv4[-_ ]?multicast$'}": "ipv4Multicast", + f"{r'ipv6[-_ ]?multicast$'}": "ipv6Multicast", + f"{r'ipv4[-_ ]?labeled[-_ ]?Unicast$'}": "ipv4MplsLabels", + f"{r'ipv4[-_ ]?mpls[-_ ]?labels$'}": "ipv4MplsLabels", + f"{r'ipv6[-_ ]?labeled[-_ ]?Unicast$'}": "ipv6MplsLabels", + f"{r'ipv6[-_ ]?mpls[-_ ]?labels$'}": "ipv6MplsLabels", + f"{r'ipv4[-_ ]?sr[-_ ]?te$'}": "ipv4SrTe", # codespell:ignore + f"{r'ipv6[-_ ]?sr[-_ ]?te$'}": "ipv6SrTe", # codespell:ignore + f"{r'ipv4[-_ ]?mpls[-_ ]?vpn$'}": "ipv4MplsVpn", + f"{r'ipv6[-_ ]?mpls[-_ ]?vpn$'}": "ipv6MplsVpn", + f"{r'ipv4[-_ ]?Flow[-_ ]?spec$'}": "ipv4FlowSpec", + f"{r'ipv6[-_ ]?Flow[-_ ]?spec$'}": "ipv6FlowSpec", + f"{r'ipv4[-_ ]?Flow[-_ ]?spec[-_ ]?vpn$'}": "ipv4FlowSpecVpn", + f"{r'ipv6[-_ ]?Flow[-_ ]?spec[-_ ]?vpn$'}": "ipv6FlowSpecVpn", + f"{r'l2[-_ ]?vpn[-_ ]?vpls$'}": "l2VpnVpls", + f"{r'l2[-_ ]?vpn[-_ ]?evpn$'}": "l2VpnEvpn", + f"{r'link[-_ ]?state$'}": "linkState", + f"{r'rt[-_ ]?membership$'}": "rtMembership", + f"{r'ipv4[-_ ]?rt[-_ ]?membership$'}": "rtMembership", + f"{r'ipv4[-_ ]?mvpn$'}": "ipv4Mvpn", } - for pattern, replacement in patterns.items(): - match = re.search(pattern, value, re.IGNORECASE) + match = re.match(pattern, value, re.IGNORECASE) if match: return replacement - return value @@ -145,7 +166,31 @@ def validate_regex(value: str) -> str: EncryptionAlgorithm = Literal["RSA", "ECDSA"] RsaKeySize = Literal[2048, 3072, 4096] EcdsaKeySize = Literal[256, 384, 512] -MultiProtocolCaps = Annotated[str, BeforeValidator(bgp_multiprotocol_capabilities_abbreviations)] +MultiProtocolCaps = Annotated[ + Literal[ + "dps", + "ipv4Unicast", + "ipv6Unicast", + "ipv4Multicast", + "ipv6Multicast", + "ipv4MplsLabels", + "ipv6MplsLabels", + "ipv4SrTe", + "ipv6SrTe", + "ipv4MplsVpn", + "ipv6MplsVpn", + "ipv4FlowSpec", + "ipv6FlowSpec", + "ipv4FlowSpecVpn", + "ipv6FlowSpecVpn", + "l2VpnVpls", + "l2VpnEvpn", + "linkState", + "rtMembership", + "ipv4Mvpn", + ], + BeforeValidator(bgp_multiprotocol_capabilities_abbreviations), +] BfdInterval = Annotated[int, Field(ge=50, le=60000)] BfdMultiplier = Annotated[int, Field(ge=3, le=50)] ErrDisableReasons = Literal[ diff --git a/anta/input_models/connectivity.py b/anta/input_models/connectivity.py index 1a904ac1d..f967e775b 100644 --- a/anta/input_models/connectivity.py +++ b/anta/input_models/connectivity.py @@ -36,11 +36,10 @@ def __str__(self) -> str: Examples -------- - Host 10.1.1.1 (src: 10.2.2.2, vrf: mgmt, size: 100B, repeat: 2) + Host: 10.1.1.1 Source: 10.2.2.2 VRF: mgmt """ - df_status = ", df-bit: enabled" if self.df_bit else "" - return f"Host {self.destination} (src: {self.source}, vrf: {self.vrf}, size: {self.size}B, repeat: {self.repeat}{df_status})" + return f"Host: {self.destination} Source: {self.source} VRF: {self.vrf}" class LLDPNeighbor(BaseModel): @@ -59,10 +58,10 @@ def __str__(self) -> str: Examples -------- - Port Ethernet1 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet2) + Port: Ethernet1 Neighbor: DC1-SPINE2 Neighbor Port: Ethernet2 """ - return f"Port {self.port} (Neighbor: {self.neighbor_device}, Neighbor Port: {self.neighbor_port})" + return f"Port: {self.port} Neighbor: {self.neighbor_device} Neighbor Port: {self.neighbor_port}" class Neighbor(LLDPNeighbor): # pragma: no cover diff --git a/anta/tests/bfd.py b/anta/tests/bfd.py index 2361a4221..d33d3005d 100644 --- a/anta/tests/bfd.py +++ b/anta/tests/bfd.py @@ -362,5 +362,5 @@ def test(self) -> None: # Check registered protocols difference = sorted(set(protocols) - set(get_value(bfd_output, "peerStatsDetail.apps"))) if difference: - failures = " ".join(f"`{item}`" for item in difference) + failures = ", ".join(f"`{item}`" for item in difference) self.result.is_failure(f"{bfd_peer} - {failures} routing protocol(s) not configured") diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index bffeda7e7..aeab1e474 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -550,7 +550,8 @@ class VerifyBGPPeerMPCaps(AntaTest): vrf: default strict: False capabilities: - - ipv4Unicast + - ipv4 labeled-Unicast + - ipv4MplsVpn ``` """ diff --git a/anta/tests/routing/isis.py b/anta/tests/routing/isis.py index 562ff6d65..3f1f862ed 100644 --- a/anta/tests/routing/isis.py +++ b/anta/tests/routing/isis.py @@ -592,9 +592,6 @@ def test(self) -> None: 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 @@ def test(self) -> None: 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/examples/tests.yaml b/examples/tests.yaml index b190ec3ce..61b138701 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -456,7 +456,8 @@ anta.tests.routing.bgp: vrf: default strict: False capabilities: - - ipv4Unicast + - ipv4 labeled-Unicast + - ipv4MplsVpn - VerifyBGPPeerRouteLimit: # Verifies maximum routes and warning limit for BGP IPv4 peer(s). bgp_peers: diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 21758ea78..f7549923a 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -1384,12 +1384,12 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo { "peer_address": "172.30.11.1", "vrf": "default", - "capabilities": ["Ipv4 Unicast", "ipv4 Mpls labels"], + "capabilities": ["Ipv4Unicast", "ipv4 Mpls labels"], }, { "peer_address": "172.30.11.10", "vrf": "MGMT", - "capabilities": ["ipv4 Unicast", "ipv4 MplsVpn"], + "capabilities": ["ipv4_Unicast", "ipv4 MplsVpn"], }, ] }, @@ -1441,12 +1441,12 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo { "peer_address": "172.30.11.10", "vrf": "default", - "capabilities": ["ipv4Unicast", "L2 Vpn EVPN"], + "capabilities": ["ipv4Unicast", "l2-vpn-EVPN"], }, { "peer_address": "172.30.11.1", "vrf": "MGMT", - "capabilities": ["ipv4Unicast", "L2 Vpn EVPN"], + "capabilities": ["ipv4Unicast", "l2vpnevpn"], }, ] }, @@ -1575,7 +1575,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo { "peer_address": "172.30.11.10", "vrf": "MGMT", - "capabilities": ["ipv4unicast", "ipv4 mplsvpn", "L2vpnEVPN"], + "capabilities": ["ipv4_unicast", "ipv4 mplsvpn", "L2vpnEVPN"], }, { "peer_address": "172.30.11.11", @@ -1656,13 +1656,13 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "peer_address": "172.30.11.1", "vrf": "default", "strict": True, - "capabilities": ["Ipv4 Unicast", "ipv4 Mpls labels"], + "capabilities": ["Ipv4 Unicast", "ipv4MplsLabels"], }, { "peer_address": "172.30.11.10", "vrf": "MGMT", "strict": True, - "capabilities": ["ipv4 Unicast", "ipv4 MplsVpn"], + "capabilities": ["ipv4-Unicast", "ipv4MplsVpn"], }, ] }, diff --git a/tests/units/anta_tests/routing/test_isis.py b/tests/units/anta_tests/routing/test_isis.py index 9c379eae3..a885d065c 100644 --- a/tests/units/anta_tests/routing/test_isis.py +++ b/tests/units/anta_tests/routing/test_isis.py @@ -1405,7 +1405,7 @@ }, "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 @@ }, "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 @@ }, "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 @@ }, "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 @@ }, "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 @@ }, "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_bfd.py b/tests/units/anta_tests/test_bfd.py index 8b234222f..d809b4aca 100644 --- a/tests/units/anta_tests/test_bfd.py +++ b/tests/units/anta_tests/test_bfd.py @@ -737,7 +737,7 @@ "result": "failure", "messages": [ "Peer: 192.0.255.7 VRF: default - `isis` routing protocol(s) not configured", - "Peer: 192.0.255.70 VRF: MGMT - `isis` `ospf` routing protocol(s) not configured", + "Peer: 192.0.255.70 VRF: MGMT - `isis`, `ospf` routing protocol(s) not configured", ], }, }, diff --git a/tests/units/anta_tests/test_connectivity.py b/tests/units/anta_tests/test_connectivity.py index d367258e1..86ada5dea 100644 --- a/tests/units/anta_tests/test_connectivity.py +++ b/tests/units/anta_tests/test_connectivity.py @@ -193,7 +193,7 @@ ], }, ], - "expected": {"result": "failure", "messages": ["Host 10.0.0.11 (src: 10.0.0.5, vrf: default, size: 100B, repeat: 2) - Unreachable"]}, + "expected": {"result": "failure", "messages": ["Host: 10.0.0.11 Source: 10.0.0.5 VRF: default - Unreachable"]}, }, { "name": "failure-ipv6", @@ -210,7 +210,7 @@ }, ], "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"]}, + "expected": {"result": "failure", "messages": ["Host: fd12:3456:789a:1::2 Source: fd12:3456:789a:1::1 VRF: default - Unreachable"]}, }, { "name": "failure-interface", @@ -244,7 +244,7 @@ ], }, ], - "expected": {"result": "failure", "messages": ["Host 10.0.0.11 (src: Management0, vrf: default, size: 100B, repeat: 2) - Unreachable"]}, + "expected": {"result": "failure", "messages": ["Host: 10.0.0.11 Source: Management0 VRF: default - Unreachable"]}, }, { "name": "failure-size", @@ -266,7 +266,7 @@ ], }, ], - "expected": {"result": "failure", "messages": ["Host 10.0.0.1 (src: Management0, vrf: default, size: 1501B, repeat: 5, df-bit: enabled) - Unreachable"]}, + "expected": {"result": "failure", "messages": ["Host: 10.0.0.1 Source: Management0 VRF: default - Unreachable"]}, }, { "name": "success", @@ -387,7 +387,7 @@ {"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"}, ], }, - "expected": {"result": "failure", "messages": ["Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - Port not found"]}, + "expected": {"result": "failure", "messages": ["Port: Ethernet2 Neighbor: DC1-SPINE2 Neighbor Port: Ethernet1 - Port not found"]}, }, { "name": "failure-no-neighbor", @@ -420,7 +420,7 @@ {"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"}, ], }, - "expected": {"result": "failure", "messages": ["Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - No LLDP neighbors"]}, + "expected": {"result": "failure", "messages": ["Port: Ethernet2 Neighbor: DC1-SPINE2 Neighbor Port: Ethernet1 - No LLDP neighbors"]}, }, { "name": "failure-wrong-neighbor", @@ -469,7 +469,7 @@ }, "expected": { "result": "failure", - "messages": ["Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - Wrong LLDP neighbors: DC1-SPINE2/Ethernet2"], + "messages": ["Port: Ethernet2 Neighbor: DC1-SPINE2 Neighbor Port: Ethernet1 - Wrong LLDP neighbors: DC1-SPINE2/Ethernet2"], }, }, { @@ -507,9 +507,9 @@ "expected": { "result": "failure", "messages": [ - "Port Ethernet1 (Neighbor: DC1-SPINE1, Neighbor Port: Ethernet1) - Wrong LLDP neighbors: DC1-SPINE1/Ethernet2", - "Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - No LLDP neighbors", - "Port Ethernet3 (Neighbor: DC1-SPINE3, Neighbor Port: Ethernet1) - Port not found", + "Port: Ethernet1 Neighbor: DC1-SPINE1 Neighbor Port: Ethernet1 - Wrong LLDP neighbors: DC1-SPINE1/Ethernet2", + "Port: Ethernet2 Neighbor: DC1-SPINE2 Neighbor Port: Ethernet1 - No LLDP neighbors", + "Port: Ethernet3 Neighbor: DC1-SPINE3 Neighbor Port: Ethernet1 - Port not found", ], }, }, @@ -555,7 +555,7 @@ }, "expected": { "result": "failure", - "messages": ["Port Ethernet1 (Neighbor: DC1-SPINE3, Neighbor Port: Ethernet1) - Wrong LLDP neighbors: DC1-SPINE1/Ethernet1, DC1-SPINE2/Ethernet1"], + "messages": ["Port: Ethernet1 Neighbor: DC1-SPINE3 Neighbor Port: Ethernet1 - Wrong LLDP neighbors: DC1-SPINE1/Ethernet1, DC1-SPINE2/Ethernet1"], }, }, ] diff --git a/tests/units/test_custom_types.py b/tests/units/test_custom_types.py index 5a92092c7..71f13e088 100644 --- a/tests/units/test_custom_types.py +++ b/tests/units/test_custom_types.py @@ -15,11 +15,7 @@ import pytest from anta.custom_types import ( - REGEX_BGP_IPV4_MPLS_VPN, - REGEX_BGP_IPV4_UNICAST, REGEX_TYPE_PORTCHANNEL, - REGEXP_BGP_IPV4_MPLS_LABELS, - REGEXP_BGP_L2VPN_AFI, REGEXP_INTERFACE_ID, REGEXP_PATH_MARKERS, REGEXP_TYPE_EOS_INTERFACE, @@ -50,40 +46,6 @@ def test_regexp_path_markers() -> None: assert re.search(REGEXP_PATH_MARKERS, ".[]?<>") is None -def test_regexp_bgp_l2vpn_afi() -> None: - """Test REGEXP_BGP_L2VPN_AFI.""" - # Test strings that should match the pattern - assert re.search(REGEXP_BGP_L2VPN_AFI, "l2vpn-evpn") is not None - assert re.search(REGEXP_BGP_L2VPN_AFI, "l2 vpn evpn") is not None - assert re.search(REGEXP_BGP_L2VPN_AFI, "l2-vpn evpn") is not None - assert re.search(REGEXP_BGP_L2VPN_AFI, "l2vpn evpn") is not None - assert re.search(REGEXP_BGP_L2VPN_AFI, "l2vpnevpn") is not None - assert re.search(REGEXP_BGP_L2VPN_AFI, "l2 vpnevpn") is not None - - # Test strings that should not match the pattern - assert re.search(REGEXP_BGP_L2VPN_AFI, "al2vpn evpn") is None - assert re.search(REGEXP_BGP_L2VPN_AFI, "l2vpn-evpna") is None - - -def test_regexp_bgp_ipv4_mpls_labels() -> None: - """Test REGEXP_BGP_IPV4_MPLS_LABELS.""" - assert re.search(REGEXP_BGP_IPV4_MPLS_LABELS, "ipv4-mpls-label") is not None - assert re.search(REGEXP_BGP_IPV4_MPLS_LABELS, "ipv4 mpls labels") is not None - assert re.search(REGEXP_BGP_IPV4_MPLS_LABELS, "ipv4Mplslabel") is None - - -def test_regex_bgp_ipv4_mpls_vpn() -> None: - """Test REGEX_BGP_IPV4_MPLS_VPN.""" - assert re.search(REGEX_BGP_IPV4_MPLS_VPN, "ipv4-mpls-vpn") is not None - assert re.search(REGEX_BGP_IPV4_MPLS_VPN, "ipv4_mplsvpn") is None - - -def test_regex_bgp_ipv4_unicast() -> None: - """Test REGEX_BGP_IPV4_UNICAST.""" - assert re.search(REGEX_BGP_IPV4_UNICAST, "ipv4-uni-cast") is not None - assert re.search(REGEX_BGP_IPV4_UNICAST, "ipv4+unicast") is None - - def test_regexp_type_interface_id() -> None: """Test REGEXP_INTERFACE_ID.""" intf_id_re = re.compile(f"{REGEXP_INTERFACE_ID}") @@ -209,13 +171,29 @@ def test_interface_autocomplete_failure() -> None: ("str_input", "expected_output"), [ pytest.param("L2VPNEVPN", "l2VpnEvpn", id="l2VpnEvpn"), - pytest.param("ipv4-mplsLabels", "ipv4MplsLabels", id="ipv4MplsLabels"), + pytest.param("IPv4 Labeled Unicast", "ipv4MplsLabels", id="ipv4MplsLabels"), pytest.param("ipv4-mpls-vpn", "ipv4MplsVpn", id="ipv4MplsVpn"), - pytest.param("ipv4-unicast", "ipv4Unicast", id="ipv4Unicast"), - pytest.param("BLAH", "BLAH", id="unmatched"), + pytest.param("ipv4_unicast", "ipv4Unicast", id="ipv4Unicast"), + pytest.param("ipv4 Mvpn", "ipv4Mvpn", id="ipv4Mvpn"), + pytest.param("ipv4_Flow-Spec Vpn", "ipv4FlowSpecVpn", id="ipv4FlowSpecVpn"), + pytest.param("Dynamic-Path-Selection", "dps", id="dps"), + pytest.param("ipv6unicast", "ipv6Unicast", id="ipv6Unicast"), + pytest.param("IPv4-Multicast", "ipv4Multicast", id="ipv4Multicast"), + pytest.param("IPv6_multicast", "ipv6Multicast", id="ipv6Multicast"), + pytest.param("ipv6_Mpls-Labels", "ipv6MplsLabels", id="ipv6MplsLabels"), + pytest.param("IPv4_SR_TE", "ipv4SrTe", id="ipv4SrTe"), + pytest.param("iPv6-sR-tE", "ipv6SrTe", id="ipv6SrTe"), + pytest.param("ipv6_mpls-vpn", "ipv6MplsVpn", id="ipv6MplsVpn"), + pytest.param("IPv4 Flow-spec", "ipv4FlowSpec", id="ipv4FlowSpec"), + pytest.param("IPv6Flow_spec", "ipv6FlowSpec", id="ipv6FlowSpec"), + pytest.param("ipv6 Flow-Spec Vpn", "ipv6FlowSpecVpn", id="ipv6FlowSpecVpn"), + pytest.param("L2VPN VPLS", "l2VpnVpls", id="l2VpnVpls"), + pytest.param("link-state", "linkState", id="linkState"), + pytest.param("RT_Membership", "rtMembership", id="rtMembership"), + pytest.param("ipv4-RT_Membership", "rtMembership", id="rtMembership"), ], ) -def test_bgp_multiprotocol_capabilities_abbreviationsh(str_input: str, expected_output: str) -> None: +def test_bgp_multiprotocol_capabilities_abbreviations(str_input: str, expected_output: str) -> None: """Test bgp_multiprotocol_capabilities_abbreviations.""" assert bgp_multiprotocol_capabilities_abbreviations(str_input) == expected_output @@ -257,11 +235,7 @@ def test_interface_case_sensitivity_uppercase() -> None: @pytest.mark.parametrize( "str_input", [ - REGEX_BGP_IPV4_MPLS_VPN, - REGEX_BGP_IPV4_UNICAST, REGEX_TYPE_PORTCHANNEL, - REGEXP_BGP_IPV4_MPLS_LABELS, - REGEXP_BGP_L2VPN_AFI, REGEXP_INTERFACE_ID, REGEXP_PATH_MARKERS, REGEXP_TYPE_EOS_INTERFACE,