Files
napalm-netgear/tests/unit/test_driver.py
T
Christian Manivong efe004a295 fix(lldp): parse the v7 neighbour table, which uses a different command
get_lldp_neighbors() existed but spoke to the older CLI generation: it ran
`show lldp remote-device all` and expected FASTPATH-style `0/1` ports in
whitespace-separated columns. v7 firmware answers `Unknown command` — as it
does for `show lldp remote-device`, a bare `show lldp`, and the plural
`show lldp neighbors`. Only the singular works, though `show ?` lists `lldp`
plainly enough:

     Port |   Device ID       |     Port ID      |      SysName      | ...
     ---- + ----------------- + ---------------- + ----------------- + ...
       g9 | 10:01:02:44:37:26 |10:01:02:44:37:28 | pve-eze.eze.local | ...

Ports are named g9, and the columns are separated by `|` with irregular
padding, so splitting on whitespace tears the values apart. The port ID is
not always a port name either — a neighbour may identify its port by MAC.

_get_lldp_table() now branches on _detect_cli_v7() into a second parser, the
same shape get_mac_address_table() already uses for this divergence.
get_lldp_neighbors() itself is untouched.

Confirmed against a GS110TPv3 on 7.1.1.17: _detect_cli_v7() returns True
there, and the switch reports two neighbours it had never surfaced. Without
them NetOrk's map had nothing to draw for the site this switch serves, so
four APs and a firewall behind it stood unconnected.
2026-08-18 15:18:56 +07:00

808 lines
29 KiB
Python

"""Unit tests for NetgearSmartDriver — no real device required."""
import pytest
from unittest.mock import MagicMock, patch
from napalm_netgear.netgear_smart import NetgearSmartDriver
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def driver():
"""Return a driver instance with a mocked Netmiko connection."""
with patch("napalm_netgear.netgear_smart.ConnectHandler"):
drv = NetgearSmartDriver(
hostname="192.168.0.239",
username="admin",
password="password",
)
drv.device = MagicMock()
drv.device.base_prompt = "(GS110TP) "
yield drv
# ---------------------------------------------------------------------------
# Sample CLI output fixtures
# ---------------------------------------------------------------------------
SHOW_SYSINFO = """\
System Description: GS110TP Gigabit Smart Managed Pro Switch
Machine Model: GS110TP
Machine Type: Ethernet Switch
Burned In MAC Address: C4:AD:34:AB:12:34
System OID String: 1.3.6.1.4.1.4526.100.5.4
System Uptime: 2 days 3 hrs 15 mins 42 secs
System Location: Server Room
System Contact: admin@example.com
System Name: myswitch
"""
SHOW_VERSION = """\
Software Version............................. 6.6.3
Loader Version............................... 1.0.0.15
Boot ROM Version............................. B1.0.0.15
Hardware Version............................. V1
Serial Number................................ 1FE2A0B1C2
"""
SHOW_PORT_ALL = """\
Intf Type Admin Physical Physical Link Link LACP Actor Partner Admin
Mode Mode Status Status Trap Mode Port Port LACP
Priority Priority Timeout
--------- ------ ------- ---------- ---------- ------ ----- ----- ------ ------ -------
0/1 Enable Auto 1G/Full Up Enable Disable 128 0 Long
0/2 Enable Auto - Down Enable Disable 128 0 Long
0/3 Disable Auto - Down Enable Disable 128 0 Long
"""
SHOW_INTERFACE_ALL = """\
Interface................................ 0/1
Description.............................. uplink-to-router
MTU...................................... 1518
Interface................................ 0/2
Description.............................. server-1
MTU...................................... 1518
Interface................................ 0/3
MTU...................................... 1518
"""
SHOW_IP_INTERFACE = """\
IP Address....................................... 192.168.0.239
Subnet Mask...................................... 255.255.255.0
Default Gateway.................................. 192.168.0.1
"""
SHOW_ARP = """\
IP Address MAC Address Interface Age (min) Type
--------- ------------------ --------- --------- -------
192.168.0.1 00:11:22:33:44:55 0/0 - Local
192.168.0.100 aa:bb:cc:dd:ee:ff 0/0 5 Dynamic
"""
SHOW_MAC = """\
VLAN ID MAC Address Type Port
------- ------------------ ---------- ------
1 00:11:22:33:44:55 Dynamic 0/1
1 aa:bb:cc:dd:ee:ff Static 0/2
1 ff:ff:ff:ff:ff:ff Management CPU
"""
SHOW_VLAN = """\
VLAN ID VLAN Name VLAN Type Interface(s)
------- --------------- ---------- -----------------------------------
1 Default Default 0/1, 0/2, 0/3
10 Management Static 0/1
20 Servers Static 0/2, 0/3
"""
SHOW_LLDP_ALL = """\
Local RemID Chassis ID Port ID System Name
Interface
--------- ----- -------------------- --------------- ---------------
0/1 1 00:1a:2b:3c:4d:5e Gi0/1 core-router
"""
SHOW_LLDP_DETAIL = """\
Chassis ID Subtype............................. MAC Address
Chassis ID................................... 00:1a:2b:3c:4d:5e
Port ID Subtype.............................. Interface Name
Port ID...................................... Gi0/1
Port Description............................. WAN uplink
System Name.................................. core-router
System Description........................... Cisco IOS XE
System Capabilities.......................... Bridge, Router
Enabled Capabilities......................... Router
"""
SHOW_SNMP = """\
SNMP Community
Community Name Access Mode Status
-------------- ----------- ------
public Read Only Enable
private Read Write Enable
"""
SHOW_USERS = """\
User Access Level Session Timeout Password Strength
-------- ------------ --------------- -----------------
admin Read/Write 5 Enabled
guest Read Only 5 Enabled
"""
SHOW_SNTP = """\
SNTP Server
SNTP Server Address....................... 192.168.0.1
SNTP Server Port......................... 123
"""
# ---------------------------------------------------------------------------
# Tests: get_facts
# ---------------------------------------------------------------------------
class TestGetFacts:
def test_returns_required_keys(self, driver):
driver._send_command = lambda cmd: SHOW_SYSINFO
driver._get_interface_list = lambda: []
facts = driver.get_facts()
for key in (
"vendor",
"model",
"hostname",
"os_version",
"serial_number",
"uptime",
"interface_list",
"fqdn",
):
assert key in facts
def test_vendor(self, driver):
driver._send_command = lambda cmd: SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
driver._get_interface_list = lambda: []
facts = driver.get_facts()
assert facts["vendor"] == "Netgear"
def test_model(self, driver):
driver._send_command = lambda cmd: SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
driver._get_interface_list = lambda: []
facts = driver.get_facts()
assert facts["model"] == "GS110TP"
def test_hostname(self, driver):
driver._send_command = lambda cmd: SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
driver._get_interface_list = lambda: []
facts = driver.get_facts()
assert facts["hostname"] == "myswitch"
def test_os_version(self, driver):
driver._send_command = lambda cmd: SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
driver._get_interface_list = lambda: []
facts = driver.get_facts()
assert facts["os_version"] == "6.6.3"
def test_serial_number(self, driver):
driver._send_command = lambda cmd: SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
driver._get_interface_list = lambda: []
facts = driver.get_facts()
assert facts["serial_number"] == "1FE2A0B1C2"
def test_uptime_parsing(self, driver):
driver._send_command = lambda cmd: SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
driver._get_interface_list = lambda: []
facts = driver.get_facts()
expected = 2 * 86400 + 3 * 3600 + 15 * 60 + 42
assert facts["uptime"] == float(expected)
# ---------------------------------------------------------------------------
# Tests: _parse_uptime_seconds
# ---------------------------------------------------------------------------
class TestParseUptimeSeconds:
def test_full(self):
assert NetgearSmartDriver._parse_uptime_seconds("2 days 3 hrs 15 mins 42 secs") == float(
2 * 86400 + 3 * 3600 + 15 * 60 + 42
)
def test_zero(self):
assert NetgearSmartDriver._parse_uptime_seconds("0 days 0 hrs 0 mins 0 secs") == 0.0
def test_hours_only(self):
assert NetgearSmartDriver._parse_uptime_seconds("1 hrs 0 mins 0 secs") == 3600.0
# ---------------------------------------------------------------------------
# Tests: _parse_key_value
# ---------------------------------------------------------------------------
class TestParseKeyValue:
def test_colon_separator(self):
output = "System Name: myswitch"
assert NetgearSmartDriver._parse_key_value(output, "System Name") == "myswitch"
def test_dot_separator(self):
output = "Software Version............................. 6.6.3"
assert NetgearSmartDriver._parse_key_value(output, "Software Version") == "6.6.3"
def test_missing_key_returns_empty(self):
assert NetgearSmartDriver._parse_key_value("some output", "Nonexistent Key") == ""
# ---------------------------------------------------------------------------
# Tests: get_interfaces
# ---------------------------------------------------------------------------
class TestGetInterfaces:
def _mock(self, driver):
def _send(cmd):
if "port" in cmd:
return SHOW_PORT_ALL
return SHOW_INTERFACE_ALL
driver._send_command = _send
def test_all_ports_present(self, driver):
self._mock(driver)
ifaces = driver.get_interfaces()
assert "0/1" in ifaces
assert "0/2" in ifaces
assert "0/3" in ifaces
def test_link_up(self, driver):
self._mock(driver)
assert driver.get_interfaces()["0/1"]["is_up"] is True
def test_link_down(self, driver):
self._mock(driver)
assert driver.get_interfaces()["0/2"]["is_up"] is False
def test_admin_disabled(self, driver):
self._mock(driver)
assert driver.get_interfaces()["0/3"]["is_enabled"] is False
def test_speed_1g(self, driver):
self._mock(driver)
assert driver.get_interfaces()["0/1"]["speed"] == 1000.0
def test_description_from_interface_all(self, driver):
self._mock(driver)
assert driver.get_interfaces()["0/1"]["description"] == "uplink-to-router"
# ---------------------------------------------------------------------------
# Tests: get_interfaces_ip
# ---------------------------------------------------------------------------
class TestGetInterfacesIp:
def test_management_ip(self, driver):
driver._send_command = lambda cmd: SHOW_IP_INTERFACE
result = driver.get_interfaces_ip()
assert "vlan1" in result
assert "192.168.0.239" in result["vlan1"]["ipv4"]
def test_prefix_length(self, driver):
driver._send_command = lambda cmd: SHOW_IP_INTERFACE
result = driver.get_interfaces_ip()
assert result["vlan1"]["ipv4"]["192.168.0.239"]["prefix_length"] == 24
# ---------------------------------------------------------------------------
# Tests: get_arp_table
# ---------------------------------------------------------------------------
class TestGetArpTable:
def test_entry_count(self, driver):
driver._send_command = lambda cmd: SHOW_ARP
arp = driver.get_arp_table()
assert len(arp) == 2
def test_entry_keys(self, driver):
driver._send_command = lambda cmd: SHOW_ARP
entry = driver.get_arp_table()[0]
for k in ("interface", "mac", "ip", "age"):
assert k in entry
def test_mac_normalised(self, driver):
driver._send_command = lambda cmd: SHOW_ARP
macs = {e["mac"] for e in driver.get_arp_table()}
# napalm_helpers.mac normalises to "AA:BB:CC:DD:EE:FF" format
assert any(":" in mac for mac in macs)
# ---------------------------------------------------------------------------
# Tests: get_mac_address_table
# ---------------------------------------------------------------------------
class TestGetMacAddressTable:
def test_entry_count(self, driver):
driver._send_command = lambda cmd: SHOW_MAC
mac_table = driver.get_mac_address_table()
assert len(mac_table) == 3
def test_static_flag(self, driver):
driver._send_command = lambda cmd: SHOW_MAC
statics = [e for e in driver.get_mac_address_table() if e["static"]]
# "Static" and "Management" rows are static
assert len(statics) == 2
def test_dynamic_flag(self, driver):
driver._send_command = lambda cmd: SHOW_MAC
dynamics = [e for e in driver.get_mac_address_table() if not e["static"]]
assert len(dynamics) == 1
# ---------------------------------------------------------------------------
# Tests: get_vlans
# ---------------------------------------------------------------------------
class TestGetVlans:
def test_vlan_ids(self, driver):
driver._send_command = lambda cmd: SHOW_VLAN
vlans = driver.get_vlans()
assert set(vlans.keys()) == {"1", "10", "20"}
def test_vlan_name(self, driver):
driver._send_command = lambda cmd: SHOW_VLAN
assert driver.get_vlans()["10"]["name"] == "Management"
def test_vlan_interfaces(self, driver):
driver._send_command = lambda cmd: SHOW_VLAN
assert "0/1" in driver.get_vlans()["1"]["interfaces"]
# ---------------------------------------------------------------------------
# Tests: _parse_vlan_ports
# ---------------------------------------------------------------------------
class TestParseVlanPorts:
def test_single_port(self):
assert NetgearSmartDriver._parse_vlan_ports("0/1") == ["0/1"]
def test_range(self):
assert NetgearSmartDriver._parse_vlan_ports("0/1-0/4") == ["0/1", "0/2", "0/3", "0/4"]
def test_short_range(self):
assert NetgearSmartDriver._parse_vlan_ports("0/1-4") == ["0/1", "0/2", "0/3", "0/4"]
def test_comma_separated(self):
result = NetgearSmartDriver._parse_vlan_ports("0/1, 0/3, 0/5")
assert result == ["0/1", "0/3", "0/5"]
# ---------------------------------------------------------------------------
# Tests: get_lldp_neighbors
# ---------------------------------------------------------------------------
class TestGetLldpNeighbors:
def test_neighbor_present(self, driver):
driver._send_command = lambda cmd: SHOW_LLDP_ALL
neighbors = driver.get_lldp_neighbors()
assert "0/1" in neighbors
def test_neighbor_hostname(self, driver):
driver._send_command = lambda cmd: SHOW_LLDP_ALL
neighbors = driver.get_lldp_neighbors()
assert neighbors["0/1"][0]["hostname"] == "core-router"
# ---------------------------------------------------------------------------
# Tests: _parse_lldp_detail
# ---------------------------------------------------------------------------
class TestParseLldpDetail:
def test_chassis_id(self):
r = NetgearSmartDriver._parse_lldp_detail(SHOW_LLDP_DETAIL)
assert r["remote_chassis_id"] == "00:1a:2b:3c:4d:5e"
def test_port_id(self):
r = NetgearSmartDriver._parse_lldp_detail(SHOW_LLDP_DETAIL)
assert r["remote_port"] == "Gi0/1"
def test_system_name(self):
r = NetgearSmartDriver._parse_lldp_detail(SHOW_LLDP_DETAIL)
assert r["remote_system_name"] == "core-router"
def test_capabilities(self):
r = NetgearSmartDriver._parse_lldp_detail(SHOW_LLDP_DETAIL)
assert "bridge" in r["remote_system_capab"]
assert "router" in r["remote_system_capab"]
# ---------------------------------------------------------------------------
# Tests: get_snmp_information
# ---------------------------------------------------------------------------
class TestGetSnmpInformation:
def test_communities(self, driver):
def _send(cmd):
if "sysinfo" in cmd:
return SHOW_SYSINFO
return SHOW_SNMP
driver._send_command = _send
snmp = driver.get_snmp_information()
assert "public" in snmp["community"]
assert snmp["community"]["public"]["mode"] == "ro"
assert snmp["community"]["private"]["mode"] == "rw"
def test_location(self, driver):
def _send(cmd):
if "sysinfo" in cmd:
return SHOW_SYSINFO
return SHOW_SNMP
driver._send_command = _send
snmp = driver.get_snmp_information()
assert snmp["location"] == "Server Room"
# ---------------------------------------------------------------------------
# Tests: get_users
# ---------------------------------------------------------------------------
class TestGetUsers:
def test_users_present(self, driver):
driver._send_command = lambda cmd: SHOW_USERS
users = driver.get_users()
assert "admin" in users
assert "guest" in users
def test_admin_level(self, driver):
driver._send_command = lambda cmd: SHOW_USERS
assert driver.get_users()["admin"]["level"] == 15
def test_guest_level(self, driver):
driver._send_command = lambda cmd: SHOW_USERS
assert driver.get_users()["guest"]["level"] == 1
# ---------------------------------------------------------------------------
# Tests: get_ntp_servers
# ---------------------------------------------------------------------------
class TestGetNtpServers:
def test_server_found(self, driver):
driver._send_command = lambda cmd: SHOW_SNTP
servers = driver.get_ntp_servers()
assert "192.168.0.1" in servers
# ---------------------------------------------------------------------------
# Tests: config management
# ---------------------------------------------------------------------------
class TestConfigManagement:
def test_load_merge_from_string(self, driver):
driver.load_merge_candidate(config="interface 0/1\n description test\nexit")
assert driver._candidate_config is not None
assert driver._candidate_mode == "merge"
def test_load_replace_from_string(self, driver):
driver.load_replace_candidate(config="! running config\nvlan 10\n name test\nexit")
assert driver._candidate_mode == "replace"
def test_discard_clears_candidate(self, driver):
driver.load_merge_candidate(config="vlan 99\n name test\nexit")
driver.discard_config()
assert driver._candidate_config is None
assert driver._candidate_mode is None
def test_has_pending_commit_false(self, driver):
assert driver.has_pending_commit() is False
def test_has_pending_commit_true(self, driver):
driver.load_merge_candidate(config="vlan 99\n name test\nexit")
assert driver.has_pending_commit() is True
def test_compare_config_merge(self, driver):
driver.load_merge_candidate(config="vlan 99\n name test\nexit")
diff = driver.compare_config()
assert diff.startswith("+")
def test_compare_config_empty_when_no_candidate(self, driver):
assert driver.compare_config() == ""
def test_load_merge_from_file(self, driver, tmp_path):
cfg_file = tmp_path / "candidate.txt"
cfg_file.write_text("vlan 99\n name test\nexit\n")
driver.load_merge_candidate(filename=str(cfg_file))
assert "vlan 99" in driver._candidate_config
def test_load_merge_raises_without_input(self, driver):
from napalm.base.exceptions import MergeConfigException
with pytest.raises(MergeConfigException):
driver.load_merge_candidate()
def test_load_replace_raises_without_input(self, driver):
from napalm.base.exceptions import ReplaceConfigException
with pytest.raises(ReplaceConfigException):
driver.load_replace_candidate()
def test_rollback_raises_without_backup(self, driver):
from napalm.base.exceptions import CommandErrorException
with pytest.raises(CommandErrorException):
driver.rollback()
# ---------------------------------------------------------------------------
# Tests: _parse_config_blocks / _diff_to_commands
# ---------------------------------------------------------------------------
class TestConfigDiff:
BACKUP = """\
vlan database
vlan 10
name Management
exit
interface 0/1
description uplink
exit
"""
CURRENT = """\
vlan database
vlan 10
name Management
exit
interface 0/1
description changed
exit
"""
def test_diff_detects_change(self):
cmds = NetgearSmartDriver._diff_to_commands(TestConfigDiff.BACKUP, TestConfigDiff.CURRENT)
# Should contain a "no description" and "description uplink" line
joined = " ".join(cmds)
assert "description" in joined
# ---------------------------------------------------------------------------
# Tests: get_config
# ---------------------------------------------------------------------------
_RUNNING_CONFIG_WITH_HEADER = """\
show running-config
SYSTEM CONFIG FILE ::= BEGIN
! Model: GS110TPv3
! Firmware Version: 7.1.1.17 [Apr 16 2026 - 17:26:24]
! MAC Address: 28:94:01:6D:26:7D
! Serial Number: 7LE4535SA0117
! System Up Time: 3 days, 9 hours, 22 mins, 45 secs
!
vlan database
vlan 10
exit
"""
class TestGetConfigDropsRuntimeState:
"""`show running-config` prefixes a header block that includes uptime.
Uptime is state, not configuration: it differs between any two reads, so
every caller diffing consecutive results sees a change each time. On a
real GS110TPv3 that marked 876 of 894 config snapshots as changed and
buried any genuine change in the noise.
"""
def test_uptime_line_is_dropped(self, driver):
driver._send_paged_command = lambda cmd: _RUNNING_CONFIG_WITH_HEADER
cfg = driver.get_config(retrieve="running")
assert "System Up Time" not in cfg["running"]
def test_two_reads_with_different_uptimes_are_equal(self, driver):
"""The whole point: consecutive reads must compare equal."""
driver._send_paged_command = lambda cmd: _RUNNING_CONFIG_WITH_HEADER
first = driver.get_config(retrieve="running")["running"]
later = _RUNNING_CONFIG_WITH_HEADER.replace(
"3 days, 9 hours, 22 mins, 45 secs", "3 days, 9 hours, 27 mins, 51 secs"
)
driver._send_paged_command = lambda cmd: later
assert driver.get_config(retrieve="running")["running"] == first
def test_stable_header_lines_are_kept(self, driver):
"""Firmware, model and serial belong in a config backup."""
driver._send_paged_command = lambda cmd: _RUNNING_CONFIG_WITH_HEADER
running = driver.get_config(retrieve="running")["running"]
assert "! Model: GS110TPv3" in running
assert "! Firmware Version: 7.1.1.17 [Apr 16 2026 - 17:26:24]" in running
assert "! Serial Number: 7LE4535SA0117" in running
def test_actual_configuration_survives(self, driver):
driver._send_paged_command = lambda cmd: _RUNNING_CONFIG_WITH_HEADER
running = driver.get_config(retrieve="running")["running"]
assert "vlan 10" in running
def test_startup_config_is_filtered_too(self, driver):
driver._send_paged_command = lambda cmd: _RUNNING_CONFIG_WITH_HEADER
cfg = driver.get_config(retrieve="startup")
assert "System Up Time" not in cfg["startup"]
def test_config_without_the_header_is_untouched(self, driver):
plain = "vlan database\nvlan 10\nexit\n"
driver._send_paged_command = lambda cmd: plain
assert driver.get_config(retrieve="running")["running"] == plain
class TestGetConfig:
def test_running_retrieved(self, driver):
driver._send_paged_command = lambda cmd: "! running config"
cfg = driver.get_config(retrieve="running")
assert cfg["running"] == "! running config"
assert cfg["startup"] == ""
def test_startup_retrieved(self, driver):
driver._send_paged_command = lambda cmd: "! startup config"
cfg = driver.get_config(retrieve="startup")
assert cfg["startup"] == "! startup config"
assert cfg["running"] == ""
def test_uses_paged_command_not_plain_send_command(self, driver):
"""Regression: get_config() called _send_command() (Netmiko
send_command with expect_string=<base_prompt>[>#]) for
"show running-config"/"show startup-config". On "v7" CLI firmware
there is no "terminal length 0" equivalent (see
_send_paged_command's docstring), so a config long enough to
paginate emits "--More--" prompts that expect_string never matches
— send_command hangs until read_timeout and fails with
"Pattern not detected: '<prompt>[>#]' in output.". Real device:
Netgear GS110TPv3 (7 VLANs, 10 interfaces) hit this on every
scheduled config-backup poll, 30s timeout, no config ever saved.
get_mac_address_table()/get_vlans() already use
_send_paged_command() for exactly this reason — get_config() must
too."""
driver._send_command = MagicMock(
side_effect=AssertionError(
"get_config() must use _send_paged_command(), not _send_command()"
)
)
driver._send_paged_command = MagicMock(return_value="! config")
driver.get_config()
assert driver._send_paged_command.call_count == 2
driver._send_command.assert_not_called()
def test_candidate_always_empty(self, driver):
driver._send_paged_command = lambda cmd: ""
cfg = driver.get_config()
assert cfg["candidate"] == ""
# ---------------------------------------------------------------------------
# Tests: is_alive
# ---------------------------------------------------------------------------
class TestIsAlive:
def test_alive_when_connected(self, driver):
driver.device.remote_conn.transport.is_active.return_value = True
assert driver.is_alive() == {"is_alive": True}
def test_dead_when_no_device(self, driver):
driver.device = None
assert driver.is_alive() == {"is_alive": False}
# ---------------------------------------------------------------------------
# Tests: cli
# ---------------------------------------------------------------------------
class TestCli:
def test_returns_output_per_command(self, driver):
driver._send_command = lambda cmd: f"output of {cmd}"
result = driver.cli(["show sysinfo", "show version"])
assert result["show sysinfo"] == "output of show sysinfo"
assert result["show version"] == "output of show version"
def test_raises_on_non_text_encoding(self, driver):
with pytest.raises(NotImplementedError):
driver.cli(["show sysinfo"], encoding="json")
# ---------------------------------------------------------------------------
# Tests: get_lldp_neighbors
# ---------------------------------------------------------------------------
LLDP_NEIGHBOR_OUTPUT = """\
show lldp neighbor
Port | Device ID | Port ID | SysName | Capabilities | TTL
---- + ----------------- + ---------------- + ----------------- + -------------- + -----
g9 | 10:01:02:44:37:26 |10:01:02:44:37:28 | pve-eze.eze.local | Bridge | 97
g10 | 10:01:02:44:37:26 |10:01:02:44:37:26 | pve-eze.eze.local | Bridge | 97
g1 | 28:94:01:6d:26:7d |g3 | swt-core | Bridge,Router | 93
GS110TPv3#
"""
class TestGetLldpNeighbors:
"""`show lldp neighbor` — the switch does speak LLDP, the driver did not.
Without this the map has nothing to draw for the site this switch serves:
it is the core switch, so every AP and firewall behind it hangs
unconnected. Measured on a GS110TPv3 running 7.1.1.17.
"""
def _send(self, cmd, **kw):
return LLDP_NEIGHBOR_OUTPUT if "lldp" in cmd else ""
@staticmethod
def _as_v7(driver):
"""v7 firmware — the only generation whose CLI answers `show lldp neighbor`."""
driver._cli_v7 = True
def test_returns_one_entry_per_local_port(self, driver):
self._as_v7(driver)
driver._send_paged_command = self._send
result = driver.get_lldp_neighbors()
assert set(result) == {"g9", "g10", "g1"}
def test_neighbour_hostname_is_taken_from_sysname(self, driver):
self._as_v7(driver)
driver._send_paged_command = self._send
result = driver.get_lldp_neighbors()
assert result["g9"][0]["hostname"] == "pve-eze.eze.local"
def test_remote_port_is_reported(self, driver):
self._as_v7(driver)
driver._send_paged_command = self._send
result = driver.get_lldp_neighbors()
assert result["g1"][0]["port"] == "g3"
def test_a_mac_shaped_port_id_is_still_reported_verbatim(self, driver):
"""Some neighbours identify their port by MAC rather than by name."""
self._as_v7(driver)
driver._send_paged_command = self._send
result = driver.get_lldp_neighbors()
assert result["g9"][0]["port"] == "10:01:02:44:37:28"
def test_header_and_prompt_lines_are_not_parsed_as_neighbours(self, driver):
self._as_v7(driver)
driver._send_paged_command = self._send
result = driver.get_lldp_neighbors()
assert "Port" not in result
assert not any(k.startswith("GS110") for k in result)
def test_no_neighbours_yields_an_empty_mapping(self, driver):
self._as_v7(driver)
driver._send_paged_command = lambda cmd, **kw: (
"show lldp neighbor\n\n Port | Device ID | Port ID | SysName | Capabilities | TTL\n"
" ---- + --------- + ------- + ------- + ------------ + -----\n\nGS110TPv3#\n"
)
assert driver.get_lldp_neighbors() == {}
def test_unsupported_firmware_does_not_raise(self, driver):
self._as_v7(driver)
driver._send_paged_command = lambda cmd, **kw: "Unknown command"
assert driver.get_lldp_neighbors() == {}