towerops/lib/mix/tasks/gen_vendor_tests.ex
2026-01-22 11:51:35 -06:00

209 lines
5.7 KiB
Elixir

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