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