defmodule Mix.Tasks.GenVendorTests do @shortdoc "Generate test files for vendor modules" @moduledoc """ Generates test files for vendor modules that don't have tests. ## Usage mix gen_vendor_tests [--dry-run] ## Options * `--dry-run` - Show what would be generated without creating files """ use Mix.Task @vendors_dir "lib/towerops/snmp/profiles/vendors" @tests_dir "test/towerops/snmp/profiles/vendors" @impl Mix.Task def run(args) do {opts, _, _} = OptionParser.parse(args, switches: [dry_run: :boolean]) dry_run = Keyword.get(opts, :dry_run, false) # Get all vendor modules (excluding registry and vendor behaviour) vendor_files = "#{@vendors_dir}/*.ex" |> Path.wildcard() |> Enum.map(&Path.basename(&1, ".ex")) |> Enum.reject(&(&1 in ["registry", "vendor"])) # Get existing tests existing_tests = "#{@tests_dir}/*_test.exs" |> Path.wildcard() |> Enum.map(&Path.basename(&1, "_test.exs")) # Find vendors without tests vendors_without_tests = vendor_files |> Enum.reject(&(&1 in existing_tests)) |> Enum.sort() IO.puts("Found #{length(vendor_files)} vendor modules") IO.puts("Found #{length(existing_tests)} existing tests") IO.puts("#{length(vendors_without_tests)} vendors need tests") if vendors_without_tests == [] do IO.puts("\nAll vendor modules have tests!") :ok else Enum.each(vendors_without_tests, fn vendor_file -> generate_vendor_test(vendor_file, dry_run) end) if !dry_run do IO.puts("\n\nGenerated #{length(vendors_without_tests)} test files.") IO.puts("Run `mix test test/towerops/snmp/profiles/vendors/` to verify.") end end end defp generate_vendor_test(vendor_file, dry_run) do module_name = Macro.camelize(vendor_file) # Read the vendor module to get profile names vendor_path = "#{@vendors_dir}/#{vendor_file}.ex" profile_names = extract_profile_names(vendor_path) content = generate_test_content(module_name, profile_names) if dry_run do IO.puts("Would generate: #{@tests_dir}/#{vendor_file}_test.exs") else path = "#{@tests_dir}/#{vendor_file}_test.exs" File.write!(path, content) IO.puts("Generated: #{path}") end end defp extract_profile_names(path) do case File.read(path) do {:ok, content} -> case Regex.run(~r/def profile_names.*?do:?\s*(\[.*?\])/s, content) do [_, list_str] -> # Parse the list from the string {result, _} = Code.eval_string(list_str) result _ -> [] end _ -> [] end end defp generate_test_content(module_name, profile_names) do profiles_inspect = inspect(profile_names) """ defmodule Towerops.Snmp.Profiles.Vendors.#{module_name}Test do use Towerops.DataCase, async: true import Mox alias Towerops.Snmp.Profiles.Vendors.#{module_name} alias Towerops.Snmp.SnmpMock setup :verify_on_exit! @client_opts [ ip: "192.168.1.1", community: "public", version: "2c", port: 161, timeout: 5000 ] describe "profile_names/0" do test "returns expected profile names" do assert #{module_name}.profile_names() == #{profiles_inspect} end end describe "detect_hardware/1" do test "returns hardware description when SNMP responds" do expect(SnmpMock, :get, fn _, "1.3.6.1.2.1.1.1.0", _ -> {:ok, "Test Device Model XYZ"} end) assert #{module_name}.detect_hardware(@client_opts) == "Test Device Model XYZ" end test "returns nil when SNMP fails" do expect(SnmpMock, :get, fn _, _, _ -> {:error, :timeout} end) assert #{module_name}.detect_hardware(@client_opts) == nil end test "truncates long descriptions to 64 characters" do long_descr = String.duplicate("A", 100) expect(SnmpMock, :get, fn _, "1.3.6.1.2.1.1.1.0", _ -> {:ok, long_descr} end) result = #{module_name}.detect_hardware(@client_opts) assert String.length(result) == 64 end end describe "wireless_oid_defs/0" do test "returns list of sensor definitions" do defs = #{module_name}.wireless_oid_defs() assert is_list(defs) assert length(defs) >= 1 end test "each definition has required fields" do defs = #{module_name}.wireless_oid_defs() Enum.each(defs, fn def -> assert Map.has_key?(def, :oid) assert Map.has_key?(def, :sensor_descr) assert Map.has_key?(def, :sensor_type) assert Map.has_key?(def, :sensor_unit) assert Map.has_key?(def, :sensor_divisor) end) end end describe "discover_wireless_sensors/1" do test "discovers sensors when SNMP responds" do defs = #{module_name}.wireless_oid_defs() num_defs = length(defs) expect(SnmpMock, :get, num_defs, fn _, _oid, _ -> {:ok, 42} end) sensors = #{module_name}.discover_wireless_sensors(@client_opts) assert is_list(sensors) assert length(sensors) == num_defs end test "returns empty list when no sensors respond" do defs = #{module_name}.wireless_oid_defs() num_defs = length(defs) expect(SnmpMock, :get, num_defs, fn _, _, _ -> {:error, :no_such_object} end) sensors = #{module_name}.discover_wireless_sensors(@client_opts) assert sensors == [] end end end """ end end