"""Unit tests for OfficeConnectDriver — fixture-driven, no real device.""" import pathlib from unittest.mock import patch import pytest from napalm_hpe_officeconnect.officeconnect import OfficeConnectDriver FIXTURES = pathlib.Path(__file__).parent.parent / "fixtures" PAGE_FOR_PATH = { OfficeConnectDriver.DASHBOARD: "base_dashboard", OfficeConnectDriver.PORT_SUMMARY: "base_port_summary", OfficeConnectDriver.MAC_TABLE: "base_mac_address_table", OfficeConnectDriver.VLAN_STATUS: "switching_vlan_status", } def fake_fetch(path: str) -> str: """Serve a captured page for the requested path.""" if path.startswith("/htdocs/pages/switching/vlan_per_port.lsp"): vlan = path.rsplit("=", 1)[-1] name = f"switching_vlan_per_port_{vlan if vlan == '10' else '1'}" else: name = PAGE_FOR_PATH[path] return (FIXTURES / f"{name}.html").read_text(encoding="utf-8") @pytest.fixture def driver(): with patch("napalm_hpe_officeconnect.officeconnect.OfficeConnectClient") as client_cls: client = client_cls.return_value client.fetch.side_effect = fake_fetch client.is_authenticated = True d = OfficeConnectDriver("192.0.2.20", "admin", "secret") d._client = client yield d # =========================================================================== # Fingerprinting contract — consumed by netOrk discovery without a connection # =========================================================================== def test_driver_identity(): assert OfficeConnectDriver.DRIVER_NAME == "hpe_officeconnect" assert OfficeConnectDriver.VENDOR == "HPE" def test_snmp_object_id_is_specific_enough_to_not_collide_with_procurve(): """The ProCurve driver claims 1.3.6.1.4.1.11 — the bare HPE arc. This driver must claim the longer OfficeConnect arc or the two compete.""" assert OfficeConnectDriver.SNMP_OBJECT_ID_PREFIX == "1.3.6.1.4.1.11.2.3.7.11" assert "1.3.6.1.4.1.11.2.3.7.11.170".startswith(OfficeConnectDriver.SNMP_OBJECT_ID_PREFIX) def test_http_fingerprint_requires_officeconnect(): mandatory = [r for r in OfficeConnectDriver.HTTP_FINGERPRINT if r.mandatory] assert [r.pattern for r in mandatory] == ["officeconnect"] def test_snmp_fingerprint_matches_real_sysdescr(): """sysDescr as returned by a J9982A over SNMPv2c.""" sys_descr = ( "hpe officeconnect switch 1820 8g poe+ (65w) j9982a, pt.02.19, " "linux 3.6.5-58b5074c, u-boot 2012.10-00116-g3ab515c (jul 30 2014 - 10:52:01)" ) matched = [r.pattern for r in OfficeConnectDriver.SNMP_FINGERPRINT if r.pattern in sys_descr] assert "officeconnect" in matched assert "1820" in matched def test_snmp_fingerprint_has_no_mandatory_rule(): """HTTP already carries the hard requirement. A mandatory SNMP rule would disqualify the driver for any device answering SNMP but not HTTP.""" assert not any(r.mandatory for r in OfficeConnectDriver.SNMP_FINGERPRINT) # =========================================================================== # Connection lifecycle # =========================================================================== def test_open_logs_in(driver): driver.open() driver._client.login.assert_called_once() def test_close_logs_out(driver): driver.close() driver._client.logout.assert_called_once() def test_is_alive_reflects_session_state(driver): assert driver.is_alive() == {"is_alive": True} # =========================================================================== # get_facts # =========================================================================== def test_get_facts(driver): facts = driver.get_facts() assert facts["vendor"] == "HPE" assert facts["hostname"] == "testswitch" assert facts["serial_number"] == "CN0FIXTURE0" assert facts["os_version"] == "PT.02.19" assert facts["model"] == "HPE OfficeConnect Switch 1820 8G PoE+ (65W) J9982A" assert facts["uptime"] == 67480 assert facts["interface_list"][:3] == ["1", "2", "3"] assert "TRK1" in facts["interface_list"] # =========================================================================== # get_interfaces # =========================================================================== def test_get_interfaces_returns_ports_and_trunks(driver): interfaces = driver.get_interfaces() assert len(interfaces) == 12 assert set(interfaces) >= {"1", "8", "TRK1", "TRK4"} def test_get_interfaces_link_state(driver): interfaces = driver.get_interfaces() assert interfaces["3"]["is_up"] is True assert interfaces["1"]["is_up"] is False # Admin state is independent of link state on this platform. assert interfaces["1"]["is_enabled"] is True def test_get_interfaces_speed_is_mbit(driver): interfaces = driver.get_interfaces() assert interfaces["3"]["speed"] == 1000.0 assert interfaces["6"]["speed"] == 100.0 # "100 Mbps Full Duplex" assert interfaces["1"]["speed"] == 0.0 # link down — no speed reported def test_get_interfaces_mtu(driver): assert driver.get_interfaces()["1"]["mtu"] == 1518 # =========================================================================== # get_vlans # =========================================================================== def test_get_vlans_lists_every_vlan(driver): vlans = driver.get_vlans() assert set(vlans) == {"1", "10", "20", "30", "40", "50", "60"} assert vlans["1"]["name"] == "default" assert vlans["10"]["name"] == "VLAN0010" def test_get_vlans_membership_excludes_non_members(driver): """Participation is Untagged / Tagged / Excluded — only the first two make an interface a member.""" vlans = driver.get_vlans() assert vlans["10"]["interfaces"] == ["2", "3", "4", "8"] def test_get_vlans_detail_splits_tagged_and_untagged(driver): detail = driver.get_vlans_detail() assert detail["1"]["untagged"] == ["1", "5", "7", "TRK1", "TRK2", "TRK3", "TRK4"] assert detail["1"]["tagged"] == ["8"] # =========================================================================== # get_mac_address_table # =========================================================================== def test_get_mac_address_table(driver): table = driver.get_mac_address_table() assert len(table) == 17 first = table[0] assert first["mac"] == "00:11:22:33:44:01" assert first["interface"] == "7" assert first["vlan"] == 1 assert first["static"] is False assert first["active"] is True # =========================================================================== # Deliberate gaps — documented, not silently faked # =========================================================================== def test_get_interfaces_counters_is_not_implemented(driver): """port_summary_stats.lsp ships an empty dataset on PT.02.19; returning zeros would look like real counters. See README.""" with pytest.raises(NotImplementedError): driver.get_interfaces_counters()