test: Profiles.Dynamic vendor post-processing + debug data branches

This commit is contained in:
Graham McIntire 2026-05-09 12:23:02 -05:00
parent 587e81bdad
commit 910298b6eb

View file

@ -0,0 +1,248 @@
defmodule Towerops.Snmp.Profiles.DynamicExtraTest do
@moduledoc """
Targets uncovered branches in `Towerops.Snmp.Profiles.Dynamic`:
* `collect_vendor_debug_data/2` debug data with state sensors
(state_descr branch) and wireless OID error/value paths
* `walk_descr_oid/2` empty walk fallback (no descr map)
* `apply_vendor_post_processing` for `arista_eos`, `arista-mos`,
`dell-powervault` profile names (delegates to vendor modules)
* `get_vendor_wireless_oids/1` no-name fallback path via
`collect_vendor_debug_data` on profile without vendor module
* `has_mib_symbolic_name?/1` non-binary clause (e.g. `nil` description)
"""
use Towerops.DataCase, async: true
import Mox
alias Towerops.Snmp.Profiles.Dynamic
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@client_opts [
ip: "192.168.1.1",
community: "public",
version: "2c",
port: 161,
timeout: 5000
]
defp base_profile(overrides \\ %{}) do
Map.merge(
%{
name: "test_device",
vendor: "TestVendor",
type: "router",
device_oids: %{},
sensor_oids: [],
table_sensor_oids: [],
processor_oids: [],
count_sensor_oids: [],
state_sensor_oids: []
},
overrides
)
end
describe "collect_vendor_debug_data/2" do
test "includes state sensors with state_descr in discovered_sensors" do
profile =
base_profile(%{
state_sensor_oids: [
%{
base_oid: "1.3.6.1.4.1.99999.4.1",
oid_name: "gps_status",
sensor_type: "state",
sensor_descr: "GPS Status",
states: %{0 => "No Fix", 2 => "3D Fix"}
}
]
})
stub(SnmpMock, :get, fn _, _, _ -> {:error, :no_such_object} end)
stub(SnmpMock, :walk, fn _, oid, _ ->
case oid do
"1.3.6.1.4.1.99999.4.1" ->
{:ok, [%{oid: "1.3.6.1.4.1.99999.4.1.0", value: {:integer, 2}}]}
_ ->
{:ok, []}
end
end)
result = Dynamic.collect_vendor_debug_data(profile, @client_opts)
assert is_list(result.discovered_sensors)
assert is_map(result.wireless_sensors)
# State sensor adds state_descr field to debug entry
state_entry = Enum.find(result.discovered_sensors, fn s -> Map.has_key?(s, :state_descr) end)
assert state_entry, "expected at least one entry with :state_descr key"
assert state_entry.state_descr == "3D Fix"
assert state_entry.type == "state"
end
test "returns empty wireless_sensors map for profile with no vendor module" do
# `nonexistent_vendor` has no module in VendorRegistry, so
# get_vendor_wireless_oids returns []. wireless_sensors stays %{}.
profile = base_profile(%{name: "nonexistent_vendor_zzz"})
stub(SnmpMock, :get, fn _, _, _ -> {:error, :no_such_object} end)
stub(SnmpMock, :walk, fn _, _, _ -> {:ok, []} end)
result = Dynamic.collect_vendor_debug_data(profile, @client_opts)
assert result.wireless_sensors == %{}
assert is_list(result.discovered_sensors)
end
test "handles profile without :name key in get_vendor_wireless_oids" do
# When profile has no :name key, get_vendor_wireless_oids returns []
# (the function-clause fallback). We need to provide all keys that
# discover_sensors reads via Map.get.
profile = %{
vendor: "Anon",
type: "router",
device_oids: %{},
sensor_oids: [],
table_sensor_oids: [],
processor_oids: [],
count_sensor_oids: [],
state_sensor_oids: []
}
stub(SnmpMock, :get, fn _, _, _ -> {:error, :no_such_object} end)
stub(SnmpMock, :walk, fn _, _, _ -> {:ok, []} end)
result = Dynamic.collect_vendor_debug_data(profile, @client_opts)
assert result.wireless_sensors == %{}
assert result.discovered_sensors == []
end
end
describe "discover_sensors/2 descr_oid empty walk fallback" do
test "uses default sensor description when descr_oid walk returns error" do
# descr_oid returns error -> walk_descr_oid returns %{} -> default desc used
profile =
base_profile(%{
table_sensor_oids: [
%{
base_oid: "1.3.6.1.4.1.99999.3.1",
descr_oid: "1.3.6.1.4.1.99999.3.2",
sensor_type: "temperature",
sensor_descr: "Temp",
sensor_unit: "C",
sensor_divisor: 1
}
]
})
stub(SnmpMock, :get, fn _, _, _ -> {:error, :no_such_object} end)
stub(SnmpMock, :walk, fn _, oid, _ ->
case oid do
"1.3.6.1.4.1.99999.3.1" ->
{:ok, [%{oid: "1.3.6.1.4.1.99999.3.1.1", value: {:integer, 42}}]}
"1.3.6.1.4.1.99999.3.2" ->
# Errored descr walk - hits walk_descr_oid catch-all returning %{}
{:error, :timeout}
_ ->
{:ok, []}
end
end)
assert {:ok, sensors} = Dynamic.discover_sensors(profile, @client_opts)
sensor = Enum.find(sensors, &(&1.sensor_type == "temperature"))
assert sensor.sensor_descr == "Temp"
end
end
describe "discover_sensors/2 vendor post-processing" do
test "arista_eos profile delegates to Arista.post_process_sensors" do
profile =
Map.merge(base_profile(), %{
name: "arista_eos",
vendor: "Arista"
})
stub(SnmpMock, :get, fn _, _, _ -> {:error, :no_such_object} end)
stub(SnmpMock, :walk, fn _, _, _ -> {:ok, []} end)
assert {:ok, sensors} = Dynamic.discover_sensors(profile, @client_opts)
# No sensors discovered - post_process_sensors is called with empty list
# and returns it unchanged.
assert is_list(sensors)
end
test "arista-mos profile delegates to Arista.post_process_sensors" do
profile =
Map.merge(base_profile(), %{
name: "arista-mos",
vendor: "Arista"
})
stub(SnmpMock, :get, fn _, _, _ -> {:error, :no_such_object} end)
stub(SnmpMock, :walk, fn _, _, _ -> {:ok, []} end)
assert {:ok, sensors} = Dynamic.discover_sensors(profile, @client_opts)
assert is_list(sensors)
end
test "dell-powervault profile delegates to Powervault.post_process_sensors" do
profile =
Map.merge(base_profile(), %{
name: "dell-powervault",
vendor: "Dell"
})
stub(SnmpMock, :get, fn _, _, _ -> {:error, :no_such_object} end)
stub(SnmpMock, :walk, fn _, _, _ -> {:ok, []} end)
assert {:ok, sensors} = Dynamic.discover_sensors(profile, @client_opts)
assert is_list(sensors)
end
end
describe "discover_sensors/2 deduplication with non-binary description" do
test "does not crash when sensor description is nil" do
# has_mib_symbolic_name? must handle non-binary (nil) values without
# crashing — covered by the catch-all clause `defp has_mib_symbolic_name?(_)`.
# Discover sensors so that one ends up with no sensor_descr.
profile =
base_profile(%{
table_sensor_oids: [
%{
base_oid: "1.3.6.1.4.1.99999.3.1",
sensor_type: nil,
# No sensor_descr, no oid_name -> falls back to sensor_type which is nil
sensor_unit: "",
sensor_divisor: 1,
descr_oid: nil
}
]
})
stub(SnmpMock, :get, fn _, _, _ -> {:error, :no_such_object} end)
stub(SnmpMock, :walk, fn _, oid, _ ->
case oid do
"1.3.6.1.4.1.99999.3.1" ->
{:ok, [%{oid: "1.3.6.1.4.1.99999.3.1.1", value: {:integer, 42}}]}
_ ->
{:ok, []}
end
end)
# Just verifying no crash occurs
assert {:ok, _sensors} = Dynamic.discover_sensors(profile, @client_opts)
end
end
end