File: test_custom_types.py

package info (click to toggle)
anta 1.7.0-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 8,048 kB
  • sloc: python: 48,164; sh: 28; javascript: 9; makefile: 4
file content (297 lines) | stat: -rw-r--r-- 13,125 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# 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.custom_types`.

The intention is only to test here what is not used already in other places.

TODO: Expand later.
"""

from __future__ import annotations

import re

import pytest

from anta.custom_types import (
    REGEX_TYPE_PORTCHANNEL,
    REGEXP_INTERFACE_ID,
    REGEXP_PATH_MARKERS,
    REGEXP_TYPE_EOS_INTERFACE,
    REGEXP_TYPE_HOSTNAME,
    REGEXP_TYPE_VXLAN_SRC_INTERFACE,
    aaa_group_prefix,
    bgp_multiprotocol_capabilities_abbreviations,
    convert_reload_cause,
    interface_autocomplete,
    interface_case_sensitivity,
    snmp_v3_prefix,
    validate_regex,
)

# ------------------------------------------------------------------------------
# TEST custom_types.py regular expressions
# ------------------------------------------------------------------------------


def test_regexp_path_markers() -> None:
    """Test REGEXP_PATH_MARKERS."""
    # Test strings that should match the pattern
    assert re.search(REGEXP_PATH_MARKERS, "show/bgp/interfaces") is not None
    assert re.search(REGEXP_PATH_MARKERS, "show\\bgp") is not None
    assert re.search(REGEXP_PATH_MARKERS, "show bgp") is not None

    # Test strings that should not match the pattern
    assert re.search(REGEXP_PATH_MARKERS, "aaaa") is None
    assert re.search(REGEXP_PATH_MARKERS, "11111") is None
    assert re.search(REGEXP_PATH_MARKERS, ".[]?<>") is None


def test_regexp_type_interface_id() -> None:
    """Test REGEXP_INTERFACE_ID."""
    intf_id_re = re.compile(f"{REGEXP_INTERFACE_ID}")

    # Test strings that should match the pattern
    assert intf_id_re.search("123") is not None
    assert intf_id_re.search("123/456") is not None
    assert intf_id_re.search("123.456") is not None
    assert intf_id_re.search("123/456.789") is not None


def test_regexp_type_eos_interface() -> None:
    """Test REGEXP_TYPE_EOS_INTERFACE."""
    # Test strings that should match the pattern
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Ethernet0") is not None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Vlan100") is not None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Port-Channel1/0") is not None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Loopback0.1") is not None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Management0/0/0") is not None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Tunnel1") is not None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Vxlan1") is not None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Fabric1") is not None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Dps1") is not None

    # Test strings that should not match the pattern
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Ethernet") is None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Vlan") is None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Port-Channel") is None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Loopback.") is None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Management/") is None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Tunnel") is None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Vxlan") is None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Fabric") is None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Dps") is None

    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Ethernet1/a") is None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Port-Channel-100") is None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Loopback.10") is None
    assert re.match(REGEXP_TYPE_EOS_INTERFACE, "Management/10") is None


def test_regexp_type_vxlan_src_interface() -> None:
    """Test REGEXP_TYPE_VXLAN_SRC_INTERFACE."""
    # Test strings that should match the pattern
    assert re.match(REGEXP_TYPE_VXLAN_SRC_INTERFACE, "Loopback0") is not None
    assert re.match(REGEXP_TYPE_VXLAN_SRC_INTERFACE, "Loopback1") is not None
    assert re.match(REGEXP_TYPE_VXLAN_SRC_INTERFACE, "Loopback99") is not None
    assert re.match(REGEXP_TYPE_VXLAN_SRC_INTERFACE, "Loopback100") is not None
    assert re.match(REGEXP_TYPE_VXLAN_SRC_INTERFACE, "Loopback8190") is not None
    assert re.match(REGEXP_TYPE_VXLAN_SRC_INTERFACE, "Dps1") is not None

    # Test strings that should not match the pattern
    assert re.match(REGEXP_TYPE_VXLAN_SRC_INTERFACE, "Loopback") is None
    assert re.match(REGEXP_TYPE_VXLAN_SRC_INTERFACE, "Loopback8192") is None
    assert re.match(REGEXP_TYPE_VXLAN_SRC_INTERFACE, "Loopback9001") is None
    assert re.match(REGEXP_TYPE_VXLAN_SRC_INTERFACE, "Loopback9000") is None
    assert re.match(REGEXP_TYPE_VXLAN_SRC_INTERFACE, "Dps2") is None


def test_regexp_type_portchannel() -> None:
    """Test REGEX_TYPE_PORTCHANNEL."""
    # Test strings that should match the pattern
    assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel5") is not None
    assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel100") is not None
    assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel999") is not None
    assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel1000") is not None

    # Test strings that should not match the pattern
    assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel") is None
    assert re.match(REGEX_TYPE_PORTCHANNEL, "Port_Channel") is None
    assert re.match(REGEX_TYPE_PORTCHANNEL, "Port_Channel1000") is None
    assert re.match(REGEX_TYPE_PORTCHANNEL, "Port_Channel5/1") is None
    assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel-100") is None


def test_regexp_type_hostname() -> None:
    """Test REGEXP_TYPE_HOSTNAME."""
    # Test strings that should match the pattern
    assert re.match(REGEXP_TYPE_HOSTNAME, "hostname") is not None
    assert re.match(REGEXP_TYPE_HOSTNAME, "hostname.com") is not None
    assert re.match(REGEXP_TYPE_HOSTNAME, "host-name.com") is not None
    assert re.match(REGEXP_TYPE_HOSTNAME, "host.name.com") is not None
    assert re.match(REGEXP_TYPE_HOSTNAME, "host-name1.com") is not None

    # Test strings that should not match the pattern
    assert re.match(REGEXP_TYPE_HOSTNAME, "-hostname.com") is None
    assert re.match(REGEXP_TYPE_HOSTNAME, ".hostname.com") is None
    assert re.match(REGEXP_TYPE_HOSTNAME, "hostname-.com") is None
    assert re.match(REGEXP_TYPE_HOSTNAME, "hostname..com") is None


# ------------------------------------------------------------------------------
# TEST custom_types.py functions
# ------------------------------------------------------------------------------


def test_interface_autocomplete_success() -> None:
    """Test interface_autocomplete with valid inputs."""
    assert interface_autocomplete("et1") == "Ethernet1"
    assert interface_autocomplete("et1/1") == "Ethernet1/1"
    assert interface_autocomplete("et1.1") == "Ethernet1.1"
    assert interface_autocomplete("et1/1.1") == "Ethernet1/1.1"
    assert interface_autocomplete("eth2") == "Ethernet2"
    assert interface_autocomplete("po3") == "Port-Channel3"
    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:
    """Test interface_autocomplete with inputs that don't have aliases."""
    assert interface_autocomplete("GigabitEthernet1") == "GigabitEthernet1"
    assert interface_autocomplete("Vlan10") == "Vlan10"
    assert interface_autocomplete("Tunnel100") == "Tunnel100"


def test_interface_autocomplete_failure() -> None:
    """Trigger ValueError for interface_autocomplete."""
    with pytest.raises(ValueError, match="Could not parse interface ID in interface"):
        interface_autocomplete("ThisIsNotAnInterface")


@pytest.mark.parametrize(
    ("str_input", "expected_output"),
    [
        pytest.param("L2VPNEVPN", "l2VpnEvpn", id="l2VpnEvpn"),
        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("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_abbreviations(str_input: str, expected_output: str) -> None:
    """Test bgp_multiprotocol_capabilities_abbreviations."""
    assert bgp_multiprotocol_capabilities_abbreviations(str_input) == expected_output


def test_aaa_group_prefix_known_method() -> None:
    """Test aaa_group_prefix with a known method."""
    assert aaa_group_prefix("local") == "local"
    assert aaa_group_prefix("none") == "none"
    assert aaa_group_prefix("logging") == "logging"


def test_aaa_group_prefix_unknown_method() -> None:
    """Test aaa_group_prefix with an unknown method."""
    assert aaa_group_prefix("demo") == "group demo"
    assert aaa_group_prefix("group1") == "group group1"


def test_interface_case_sensitivity_lowercase() -> None:
    """Test interface_case_sensitivity with lowercase inputs."""
    assert interface_case_sensitivity("ethernet") == "Ethernet"
    assert interface_case_sensitivity("vlan") == "Vlan"
    assert interface_case_sensitivity("loopback") == "Loopback"


def test_interface_case_sensitivity_mixed_case() -> None:
    """Test interface_case_sensitivity with mixed case inputs."""
    assert interface_case_sensitivity("Ethernet") == "Ethernet"
    assert interface_case_sensitivity("Vlan") == "Vlan"
    assert interface_case_sensitivity("Loopback") == "Loopback"


def test_interface_case_sensitivity_uppercase() -> None:
    """Test interface_case_sensitivity with uppercase inputs."""
    assert interface_case_sensitivity("ETHERNET") == "ETHERNET"
    assert interface_case_sensitivity("VLAN") == "VLAN"
    assert interface_case_sensitivity("LOOPBACK") == "LOOPBACK"


@pytest.mark.parametrize(
    "str_input",
    [
        REGEX_TYPE_PORTCHANNEL,
        REGEXP_INTERFACE_ID,
        REGEXP_PATH_MARKERS,
        REGEXP_TYPE_EOS_INTERFACE,
        REGEXP_TYPE_HOSTNAME,
        REGEXP_TYPE_VXLAN_SRC_INTERFACE,
    ],
)
def test_validate_regex_valid(str_input: str) -> None:
    """Test validate_regex with valid regex."""
    assert validate_regex(str_input) == str_input


@pytest.mark.parametrize(
    ("str_input", "error"),
    [
        pytest.param("[", "Invalid regex: unterminated character set at position 0", id="unterminated character"),
        pytest.param("\\", r"Invalid regex: bad escape \(end of pattern\) at position 0", id="bad escape"),
    ],
)
def test_validate_regex_invalid(str_input: str, error: str) -> None:
    """Test validate_regex with invalid regex."""
    with pytest.raises(ValueError, match=error):
        validate_regex(str_input)


def test_snmp_v3_prefix_valid_input() -> None:
    """Test snmp_v3_prefix with valid authentication type."""
    assert snmp_v3_prefix("auth") == "v3Auth"
    assert snmp_v3_prefix("noauth") == "v3NoAuth"
    assert snmp_v3_prefix("priv") == "v3Priv"


@pytest.mark.parametrize(
    ("str_input", "expected_output"),
    [
        pytest.param("Ztp", "System reloaded due to Zero Touch Provisioning", id="valid"),
        pytest.param("USER", "Reload requested by the user.", id="valid"),
        pytest.param("fpga", "Reload requested after FPGA upgrade", id="valid"),
    ],
)
def test_convert_reload_cause(str_input: str, expected_output: str) -> None:
    """Test convert_reload_cause."""
    assert convert_reload_cause(str_input) == expected_output


@pytest.mark.parametrize(
    ("str_input"),
    [
        pytest.param("ztp2", id="invalid"),
    ],
)
def test_invalid_convert_reload_cause(str_input: str) -> None:
    """Test invalid convert_reload_cause."""
    with pytest.raises(ValueError, match=r"Invalid reload cause: 'ztp2' - expected causes are \['ZTP', 'USER', 'FPGA', 'USER_HITLESS'\]"):
        convert_reload_cause(str_input)