209 lines
5.7 KiB
Elixir
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
|