test: Profiles.Base storage/printer/processor/Cambium branches

This commit is contained in:
Graham McIntire 2026-05-09 12:29:23 -05:00
parent 0f87ec5b89
commit 19d9ef9e52

View file

@ -0,0 +1,277 @@
defmodule Towerops.Snmp.Profiles.BaseExtraTest do
@moduledoc """
Additional `Towerops.Snmp.Profiles.Base` coverage targeting branches that
remained uncovered after the existing test files.
Covered here:
* `discover_ipv6_addresses/1` via the modern RFC 4293 ipAddressTable
(the prior tests only hit the IPV6-MIB fallback)
* `discover_transceivers/1` empty-result and walk-failure branches
* `discover_printer_supplies/1` empty / supplies parsing including
`parse_supply_capacity` for binary values
* `discover_processors/1` UCD-SNMP-MIB three-value branch
* `discover_storage/1` happy path covering disk types
* `discover_physical_entities/1` for entity_class string mapping
* `identify_device/1` Cambium ePMP and other Ubiquiti antenna branches
"""
use Towerops.DataCase, async: true
alias Towerops.Snmp.Adapters.Replay
alias Towerops.Snmp.Profiles.Base
describe "discover_ipv6_addresses/1 via ipAddressTable (RFC 4293)" do
test "ipAddressTable with only IPv4 entries returns empty list (then falls through to IPV6-MIB)" do
# Modern table has IPv4 addresses (type 1), but discover_ipv6_addresses
# should return [] from the modern path, then fall through to IPV6-MIB
# which is also empty -> empty list.
ipv4_suffix = "1.4.10.0.0.1"
client_opts = [
adapter: Replay,
oid_map: %{
("1.3.6.1.2.1.4.34.1.3." <> ipv4_suffix) => "1"
}
]
assert {:ok, []} = Base.discover_ipv6_addresses(client_opts)
end
end
describe "discover_transceivers/1 empty paths" do
test "returns empty list when no entity descriptions exist" do
assert {:ok, []} = Base.discover_transceivers(adapter: Replay, oid_map: %{})
end
test "filters out entities that lack transceiver keywords in description" do
client_opts = [
adapter: Replay,
oid_map: %{
# entPhysicalDescr that doesn't match transceiver keywords
"1.3.6.1.2.1.47.1.1.1.1.2.1" => "Power Module",
"1.3.6.1.2.1.47.1.1.1.1.5.1" => "9"
}
]
assert {:ok, []} = Base.discover_transceivers(client_opts)
end
end
describe "discover_printer_supplies/1" do
test "returns empty list when no supplies present" do
assert {:ok, []} = Base.discover_printer_supplies(adapter: Replay, oid_map: %{})
end
test "discovers printer supplies with all attributes including capacity parsing" do
client_opts = [
adapter: Replay,
oid_map: %{
# prtMarkerSuppliesType: 3 = toner, 10 = drum
"1.3.6.1.2.1.43.11.1.1.3.1.1" => "3",
"1.3.6.1.2.1.43.11.1.1.3.1.2" => "10",
# prtMarkerSuppliesDescription
"1.3.6.1.2.1.43.11.1.1.4.1.1" => "Black Toner Cartridge",
"1.3.6.1.2.1.43.11.1.1.4.1.2" => "Imaging Drum",
# prtMarkerSuppliesUnit: 19=percent, 18=items
"1.3.6.1.2.1.43.11.1.1.7.1.1" => "19",
"1.3.6.1.2.1.43.11.1.1.7.1.2" => "18",
# prtMarkerSuppliesMaxCapacity (string -> tests parse_supply_capacity binary branch)
"1.3.6.1.2.1.43.11.1.1.8.1.1" => "100",
"1.3.6.1.2.1.43.11.1.1.8.1.2" => "30000",
# prtMarkerSuppliesLevel
"1.3.6.1.2.1.43.11.1.1.9.1.1" => "75",
"1.3.6.1.2.1.43.11.1.1.9.1.2" => "12500",
# prtMarkerColorantValue
"1.3.6.1.2.1.43.11.1.1.12.1.1" => "black",
"1.3.6.1.2.1.43.11.1.1.12.1.2" => "n/a"
}
]
assert {:ok, supplies} = Base.discover_printer_supplies(client_opts)
assert length(supplies) == 2
toner = Enum.find(supplies, &(&1.supply_index == "1"))
assert toner.supply_type == "toner"
assert toner.supply_description == "Black Toner Cartridge"
assert toner.supply_unit == "percent"
assert toner.max_capacity == 100
assert toner.current_level == 75
assert toner.color_name == "black"
drum = Enum.find(supplies, &(&1.supply_index == "2"))
assert drum.supply_type == "drum"
assert drum.max_capacity == 30_000
assert drum.current_level == 12_500
end
test "handles unknown supply type code by mapping to 'unknown'" do
client_opts = [
adapter: Replay,
oid_map: %{
# Unknown supply type code 999
"1.3.6.1.2.1.43.11.1.1.3.1.1" => "999"
}
]
assert {:ok, [supply]} = Base.discover_printer_supplies(client_opts)
assert supply.supply_type == "unknown"
end
end
describe "discover_processors/1 UCD-SNMP path" do
test "synthesizes a UCD CPU when other MIBs are silent" do
# This path exercises discover_ucd_cpu when both HR and Cisco return [].
# The Replay adapter with empty oid_map returns nil for UCD OIDs which
# parse as 0; we still get a synthetic processor.
assert {:ok, processors} = Base.discover_processors(adapter: Replay, oid_map: %{})
assert length(processors) == 1
[cpu] = processors
assert cpu.processor_type == "ucd_cpu"
assert cpu.processor_index == "ucd_0"
assert is_map(cpu.metadata)
end
end
describe "discover_storage/1 (Replay)" do
test "discovers fixed disks while ignoring RAM/swap entries" do
client_opts = [
adapter: Replay,
oid_map: %{
# hrStorageType: RAM (1.3.6.1.2.1.25.2.1.2) - filtered out
"1.3.6.1.2.1.25.2.3.1.2.1" => "1.3.6.1.2.1.25.2.1.2",
# fixed disk (1.3.6.1.2.1.25.2.1.4)
"1.3.6.1.2.1.25.2.3.1.2.2" => "1.3.6.1.2.1.25.2.1.4",
# flash memory (1.3.6.1.2.1.25.2.1.9)
"1.3.6.1.2.1.25.2.3.1.2.3" => "1.3.6.1.2.1.25.2.1.9",
# Descriptions
"1.3.6.1.2.1.25.2.3.1.3.2" => "/var",
"1.3.6.1.2.1.25.2.3.1.3.3" => "/boot",
# Allocation units
"1.3.6.1.2.1.25.2.3.1.4.2" => "4096",
"1.3.6.1.2.1.25.2.3.1.4.3" => "1024",
# Sizes (in units)
"1.3.6.1.2.1.25.2.3.1.5.2" => "1000000",
"1.3.6.1.2.1.25.2.3.1.5.3" => "100000",
# Used (in units)
"1.3.6.1.2.1.25.2.3.1.6.2" => "500000",
"1.3.6.1.2.1.25.2.3.1.6.3" => "75000"
}
]
assert {:ok, storage} = Base.discover_storage(client_opts)
assert length(storage) == 2
fixed = Enum.find(storage, &(&1.storage_type == "fixed_disk"))
assert fixed.description == "/var"
assert fixed.total_bytes == 1_000_000 * 4096
flash = Enum.find(storage, &(&1.storage_type == "flash_memory"))
assert flash.description == "/boot"
assert flash.used_bytes == 75_000 * 1024
end
test "returns empty list when storage walk has no entries" do
assert {:ok, []} = Base.discover_storage(adapter: Replay, oid_map: %{})
end
end
describe "discover_physical_entities/1 entity class mapping" do
test "discovers entities with class strings mapped from class codes" do
client_opts = [
adapter: Replay,
oid_map: %{
# entPhysicalDescr
"1.3.6.1.2.1.47.1.1.1.1.2.1" => "Chassis",
"1.3.6.1.2.1.47.1.1.1.1.2.2" => "PSU",
"1.3.6.1.2.1.47.1.1.1.1.2.3" => "Fan Module",
"1.3.6.1.2.1.47.1.1.1.1.2.4" => "Sensor X",
"1.3.6.1.2.1.47.1.1.1.1.2.5" => "Linecard",
"1.3.6.1.2.1.47.1.1.1.1.2.6" => "Port 1",
# entPhysicalClass
# chassis
"1.3.6.1.2.1.47.1.1.1.1.5.1" => "3",
# power
"1.3.6.1.2.1.47.1.1.1.1.5.2" => "6",
# fan
"1.3.6.1.2.1.47.1.1.1.1.5.3" => "7",
# sensor
"1.3.6.1.2.1.47.1.1.1.1.5.4" => "8",
# module
"1.3.6.1.2.1.47.1.1.1.1.5.5" => "9",
# port
"1.3.6.1.2.1.47.1.1.1.1.5.6" => "10"
}
]
assert {:ok, entities} = Base.discover_physical_entities(client_opts)
assert length(entities) == 6
classes = entities |> Enum.map(& &1.entity_class) |> Enum.sort()
assert "chassis" in classes
assert "power" in classes
assert "fan" in classes
assert "sensor" in classes
assert "module" in classes
assert "port" in classes
end
test "returns empty list when entity walk has no descriptions" do
assert {:ok, []} = Base.discover_physical_entities(adapter: Replay, oid_map: %{})
end
end
describe "identify_device/1 additional vendor patterns" do
test "identifies Cambium ePMP with model number from sysDescr" do
info = %{sys_descr: "Cambium ePMP 3000 AP", sys_object_id: ""}
device = Base.identify_device(info)
assert device.manufacturer == "Cambium Networks"
assert device.model == "ePMP 3000"
end
test "identifies bare Cambium ePMP without model number" do
info = %{sys_descr: "Cambium ePMP wireless", sys_object_id: ""}
device = Base.identify_device(info)
# The regex matches "ePMP" followed by no digits and the
# extract_model_with_number helper returns "ePMP" only
assert device.manufacturer == "Cambium Networks"
assert device.model == "ePMP"
end
test "identifies Cambium PMP from sysDescr with model number" do
info = %{sys_descr: "Cambium PMP 450 SM", sys_object_id: ""}
device = Base.identify_device(info)
assert device.manufacturer == "Cambium Networks"
assert device.model == "PMP 450"
end
test "Ubiquiti AF-prefix antenna model extraction" do
info = %{sys_descr: "Ubiquiti AF-5XHD wireless", sys_object_id: ""}
device = Base.identify_device(info)
assert device.manufacturer == "Ubiquiti"
assert String.starts_with?(device.model, "AF")
end
test "Cambium with no specific descr falls back to product line via sysObjectID (ePMP)" do
info = %{
sys_descr: "Generic device",
sys_object_id: "1.3.6.1.4.1.17713.21.5.1"
}
device = Base.identify_device(info)
assert device.manufacturer == "Cambium Networks"
assert device.model == "ePMP"
end
test "Cambium with no descr and no matching OID line falls back to Wireless" do
info = %{
sys_descr: "Cambium device",
# 17713 prefix but unknown product line (.99)
sys_object_id: "1.3.6.1.4.1.17713.99.1"
}
device = Base.identify_device(info)
assert device.manufacturer == "Cambium Networks"
assert device.model == "Wireless"
end
end
end