towerops/test/mix/tasks/gen_vendor_modules_test.exs
2026-02-03 10:19:56 -06:00

473 lines
13 KiB
Elixir

defmodule Mix.Tasks.GenVendorModulesTest do
use ExUnit.Case, async: false
import ExUnit.CaptureIO
alias Mix.Tasks.GenVendorModules
@moduletag :mix_task
setup do
# Save original directory
original_dir = File.cwd!()
# Create temporary directories for profiles and vendor modules
temp_dir = Path.join(System.tmp_dir!(), "gen_vendor_modules_#{System.unique_integer([:positive])}")
profiles_dir = Path.join([temp_dir, "priv", "profiles", "os_detection"])
vendors_dir = Path.join([temp_dir, "lib", "towerops", "snmp", "profiles", "vendors"])
File.mkdir_p!(profiles_dir)
File.mkdir_p!(vendors_dir)
on_exit(fn ->
File.cd!(original_dir)
File.rm_rf!(temp_dir)
end)
{:ok, temp_dir: temp_dir, profiles_dir: profiles_dir, vendors_dir: vendors_dir}
end
describe "run/1 with dry-run" do
test "reports vendors that would be generated in dry-run mode", %{
temp_dir: temp_dir,
profiles_dir: profiles_dir,
vendors_dir: _vendors_dir
} do
# Create profile YAML with group
File.write!(Path.join(profiles_dir, "mikrotik.yaml"), """
os: mikrotik
group: mikrotik
text: MikroTik
discovery:
- sysObjectID:
- .1.3.6.1.4.1.14988.
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run(["--dry-run"])
end)
assert output =~ "Found 1 YAML profiles"
assert output =~ "Grouped into 1 vendors"
assert output =~ "1 new vendors to generate"
assert output =~ "mikrotik.ex (Mikrotik)"
assert output =~ "Profiles: mikrotik"
assert output =~ "Enterprise OID: 1.3.6.1.4.1.14988"
end)
end
test "reports no work needed when all vendors exist", %{
profiles_dir: profiles_dir,
vendors_dir: vendors_dir,
temp_dir: temp_dir
} do
# Create profile YAML
File.write!(Path.join(profiles_dir, "mikrotik.yaml"), """
os: mikrotik
group: mikrotik
text: MikroTik
""")
# Create existing vendor module
File.write!(Path.join(vendors_dir, "mikrotik.ex"), """
defmodule Towerops.Snmp.Profiles.Vendors.Mikrotik do
def profile_names, do: ["mikrotik"]
end
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run(["--dry-run"])
end)
assert output =~ "0 new vendors to generate"
assert output =~ "No new vendors to generate!"
end)
end
end
describe "run/1 without dry-run" do
test "generates vendor module from profile with group", %{
profiles_dir: profiles_dir,
vendors_dir: vendors_dir,
temp_dir: temp_dir
} do
File.write!(Path.join(profiles_dir, "cisco-ios.yaml"), """
os: cisco-ios
group: cisco
text: Cisco IOS
discovery:
- sysObjectID:
- .1.3.6.1.4.1.9.
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run([])
end)
assert output =~ "Generated: lib/towerops/snmp/profiles/vendors/cisco.ex"
assert output =~ "Generated 1 vendor modules"
# Verify file was created
vendor_file = Path.join(vendors_dir, "cisco.ex")
assert File.exists?(vendor_file)
# Verify file content
content = File.read!(vendor_file)
assert content =~ "defmodule Towerops.Snmp.Profiles.Vendors.Cisco do"
assert content =~ "@moduledoc"
assert content =~ "SNMP profile for Cisco devices"
assert content =~ "@behaviour Towerops.Snmp.Profiles.Vendors.Vendor"
assert content =~ "def profile_names, do: [\"cisco-ios\"]"
assert content =~ "# Enterprise OID: 1.3.6.1.4.1.9"
assert content =~ "def detect_hardware(client_opts)"
assert content =~ "def wireless_oid_defs"
assert content =~ "def discover_wireless_sensors(client_opts)"
end)
end
test "generates vendor module from profile without group using filename inference", %{
temp_dir: temp_dir,
profiles_dir: profiles_dir,
vendors_dir: vendors_dir
} do
File.write!(Path.join(profiles_dir, "cisco_ios.yaml"), """
os: cisco-ios
text: Cisco IOS
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run([])
end)
# Should infer cisco from filename
assert output =~ "Generated: lib/towerops/snmp/profiles/vendors/cisco.ex"
vendor_file = Path.join(vendors_dir, "cisco.ex")
assert File.exists?(vendor_file)
end)
end
test "groups multiple profiles into single vendor module", %{
profiles_dir: profiles_dir,
vendors_dir: vendors_dir,
temp_dir: temp_dir
} do
File.write!(Path.join(profiles_dir, "cisco-ios.yaml"), """
os: cisco-ios
group: cisco
text: Cisco IOS
""")
File.write!(Path.join(profiles_dir, "cisco-nxos.yaml"), """
os: cisco-nxos
group: cisco
text: Cisco NX-OS
""")
File.write!(Path.join(profiles_dir, "cisco-asa.yaml"), """
os: cisco-asa
group: cisco
text: Cisco ASA
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run([])
end)
assert output =~ "Generated 1 vendor modules"
vendor_file = Path.join(vendors_dir, "cisco.ex")
content = File.read!(vendor_file)
# Should include all three profiles
assert content =~ ~s(["cisco-asa", "cisco-ios", "cisco-nxos"])
end)
end
test "generates modules for multiple vendors", %{
profiles_dir: profiles_dir,
vendors_dir: vendors_dir,
temp_dir: temp_dir
} do
File.write!(Path.join(profiles_dir, "mikrotik.yaml"), """
os: mikrotik
group: mikrotik
text: MikroTik
""")
File.write!(Path.join(profiles_dir, "ubnt.yaml"), """
os: ubnt
group: ubiquiti
text: Ubiquiti
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run([])
end)
assert output =~ "Generated 2 vendor modules"
assert File.exists?(Path.join(vendors_dir, "mikrotik.ex"))
assert File.exists?(Path.join(vendors_dir, "ubiquiti.ex"))
end)
end
test "generates module with specific group filter", %{
profiles_dir: profiles_dir,
vendors_dir: vendors_dir,
temp_dir: temp_dir
} do
File.write!(Path.join(profiles_dir, "mikrotik.yaml"), """
os: mikrotik
group: mikrotik
text: MikroTik
""")
File.write!(Path.join(profiles_dir, "cisco.yaml"), """
os: cisco
group: cisco
text: Cisco
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run(["--group", "mikrotik"])
end)
assert output =~ "Generated 1 vendor modules"
assert File.exists?(Path.join(vendors_dir, "mikrotik.ex"))
refute File.exists?(Path.join(vendors_dir, "cisco.ex"))
end)
end
test "handles vendor names starting with numbers", %{
profiles_dir: profiles_dir,
vendors_dir: vendors_dir,
temp_dir: temp_dir
} do
File.write!(Path.join(profiles_dir, "4rf.yaml"), """
os: 4rf
group: 4rf
text: 4RF
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run([])
end)
# Should prefix with "n" for filename and "N" for module
assert output =~ "Generated: lib/towerops/snmp/profiles/vendors/n4rf.ex"
vendor_file = Path.join(vendors_dir, "n4rf.ex")
assert File.exists?(vendor_file)
content = File.read!(vendor_file)
assert content =~ "defmodule Towerops.Snmp.Profiles.Vendors.N4rf do"
end)
end
test "handles vendor names with special characters", %{
profiles_dir: profiles_dir,
vendors_dir: vendors_dir,
temp_dir: temp_dir
} do
File.write!(Path.join(profiles_dir, "device.yaml"), """
os: test-device
group: hp-aruba
text: HP Aruba
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run([])
end)
# Should convert to snake_case for filename
assert output =~ "Generated: lib/towerops/snmp/profiles/vendors/hp_aruba.ex"
vendor_file = Path.join(vendors_dir, "hp_aruba.ex")
assert File.exists?(vendor_file)
content = File.read!(vendor_file)
assert content =~ "defmodule Towerops.Snmp.Profiles.Vendors.HpAruba do"
end)
end
test "extracts enterprise OID from sysObjectID", %{
profiles_dir: profiles_dir,
vendors_dir: vendors_dir,
temp_dir: temp_dir
} do
File.write!(Path.join(profiles_dir, "test.yaml"), """
os: test
group: testvendor
text: Test Vendor
discovery:
- sysObjectID:
- .1.3.6.1.4.1.12345.1.2.3
""")
File.cd!(temp_dir, fn ->
capture_io(fn ->
GenVendorModules.run([])
end)
vendor_file = Path.join(vendors_dir, "testvendor.ex")
content = File.read!(vendor_file)
assert content =~ "# Enterprise OID: 1.3.6.1.4.1.12345"
end)
end
test "handles profile without enterprise OID", %{
profiles_dir: profiles_dir,
vendors_dir: vendors_dir,
temp_dir: temp_dir
} do
File.write!(Path.join(profiles_dir, "generic.yaml"), """
os: generic
group: generic
text: Generic Device
""")
File.cd!(temp_dir, fn ->
capture_io(fn ->
GenVendorModules.run([])
end)
vendor_file = Path.join(vendors_dir, "generic.ex")
content = File.read!(vendor_file)
assert content =~ "# Enterprise OID: unknown - uses standard MIB OIDs"
end)
end
test "includes standard CPU sensor in wireless_oid_defs", %{
profiles_dir: profiles_dir,
vendors_dir: vendors_dir,
temp_dir: temp_dir
} do
File.write!(Path.join(profiles_dir, "test.yaml"), """
os: test
group: test
text: Test
""")
File.cd!(temp_dir, fn ->
capture_io(fn ->
GenVendorModules.run([])
end)
vendor_file = Path.join(vendors_dir, "test.ex")
content = File.read!(vendor_file)
assert content =~ "wireless_oid_defs do"
assert content =~ "1.3.6.1.2.1.25.3.3.1.2.1"
assert content =~ "CPU Load"
assert content =~ "sensor_type: \"load\""
end)
end
test "skips profiles that cannot be grouped to a vendor", %{
profiles_dir: profiles_dir,
vendors_dir: _vendors_dir,
temp_dir: temp_dir
} do
File.write!(Path.join(profiles_dir, "unknown_device.yaml"), """
os: unknown-device
text: Unknown Device
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run([])
end)
assert output =~ "0 new vendors to generate"
assert output =~ "No new vendors to generate!"
end)
end
test "includes instructions after generation", %{
profiles_dir: profiles_dir,
vendors_dir: _vendors_dir,
temp_dir: temp_dir
} do
File.write!(Path.join(profiles_dir, "test.yaml"), """
os: test
group: test
text: Test
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run([])
end)
assert output =~ "Run `mix compile` to check for errors"
assert output =~ "Run `mix test` to verify"
assert output =~ "Add aliases to registry.ex"
assert output =~ "Add vendors to @vendors list in registry.ex"
assert output =~ "Write tests for each vendor"
assert output =~ "Add vendor-specific OIDs if available"
end)
end
end
describe "run/1 error handling" do
test "skips profiles with invalid YAML", %{profiles_dir: profiles_dir, vendors_dir: vendors_dir, temp_dir: temp_dir} do
File.write!(Path.join(profiles_dir, "valid.yaml"), """
os: valid
group: valid
text: Valid
""")
File.write!(Path.join(profiles_dir, "invalid.yaml"), """
this is: [invalid yaml that won't parse
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run([])
end)
# Should still generate valid vendor
assert output =~ "Generated 1 vendor modules"
assert File.exists?(Path.join(vendors_dir, "valid.ex"))
end)
end
test "skips profiles without os field", %{profiles_dir: profiles_dir, vendors_dir: _vendors_dir, temp_dir: temp_dir} do
File.write!(Path.join(profiles_dir, "no_os.yaml"), """
group: test
text: Test
""")
File.cd!(temp_dir, fn ->
output =
capture_io(fn ->
GenVendorModules.run([])
end)
assert output =~ "0 new vendors to generate"
end)
end
end
end