defmodule SnmpKit.SnmpLib.MIB.UtilitiesTest do use ExUnit.Case, async: true alias SnmpKit.SnmpLib.MIB.Utilities describe "register_oid/4" do test "registers a new OID" do oid_table = %{} result = Utilities.register_oid("sysDescr", [1, 3, 6, 1, 2, 1, 1, 1], :resolved, oid_table) assert Map.has_key?(result, "sysDescr") entry = result["sysDescr"] assert entry.name == "sysDescr" assert entry.oid == [1, 3, 6, 1, 2, 1, 1, 1] assert entry.status == :resolved assert entry.parent == [1, 3, 6, 1, 2, 1, 1] end test "registers root OID without parent" do oid_table = %{} result = Utilities.register_oid("root", [1], :resolved, oid_table) entry = result["root"] assert entry.parent == nil end end describe "resolve_oids/1" do test "resolves OID tree successfully" do oid_table = %{ "root" => %{ name: "root", oid: [1], status: :resolved, parent: nil, children: [] }, "child" => %{ name: "child", oid: [%{name: "root", value: 3}], status: :unresolved, parent: nil, children: [] } } # This tests OID resolution - should either succeed or return error result = Utilities.resolve_oids(oid_table) # Should return either ok or error assert match?({:ok, _}, result) or match?({:error, _}, result) end test "handles already resolved OIDs" do oid_table = %{ "sysDescr" => %{ name: "sysDescr", oid: [1, 3, 6, 1, 2, 1, 1, 1], status: :resolved, parent: [1, 3, 6, 1, 2, 1, 1], children: [] } } assert {:ok, result} = Utilities.resolve_oids(oid_table) assert result == oid_table end end describe "tr_oid/2" do test "translates resolved OID name to numeric" do oid_table = %{ "sysDescr" => %{ name: "sysDescr", oid: [1, 3, 6, 1, 2, 1, 1, 1], status: :resolved, parent: nil, children: [] } } assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1]} = Utilities.tr_oid("sysDescr", oid_table) end test "returns error for unresolved OID" do oid_table = %{ "test" => %{ name: "test", oid: [1, 2, 3], status: :unresolved, parent: nil, children: [] } } assert {:error, :unresolved} = Utilities.tr_oid("test", oid_table) end test "returns error for unknown OID" do oid_table = %{} assert {:error, :not_found} = Utilities.tr_oid("unknown", oid_table) end end describe "update_me_oids/3" do test "updates MIB entries with resolved OIDs" do oid_table = %{ "system" => %{ name: "system", oid: [1, 3, 6, 1, 2, 1, 1], status: :resolved, parent: nil, children: [] } } entries = [ %{name: "sysDescr", oid: "system"}, %{name: "other", value: 123} ] result = Utilities.update_me_oids(entries, oid_table, :silent) # First entry should have resolved OID first = Enum.at(result, 0) assert first.oid == [1, 3, 6, 1, 2, 1, 1] # Second entry has no oid field, should remain unchanged second = Enum.at(result, 1) assert second.value == 123 end test "handles entries without OIDs" do entries = [%{name: "test", value: 123}] result = Utilities.update_me_oids(entries, %{}, :debug) assert result == entries end end describe "make_asn1_type/1" do test "validates integer type" do assert {:ok, type_def} = Utilities.make_asn1_type({:integer, []}) assert type_def.type == :integer end test "validates integer with range constraint" do assert {:ok, type_def} = Utilities.make_asn1_type({:integer, [{:range, 0, 100}]}) assert type_def.type == :integer assert type_def.constraints == [{:range, 0, 100}] end test "validates octet string type" do assert {:ok, type_def} = Utilities.make_asn1_type({:octet_string, []}) assert type_def.type == :octet_string end test "validates octet string with size constraint" do assert {:ok, type_def} = Utilities.make_asn1_type({:octet_string, [1, {:range, 0, 255}]}) assert type_def.type == :octet_string end test "validates object identifier type" do assert {:ok, type_def} = Utilities.make_asn1_type({:object_identifier}) assert type_def.type == :object_identifier end test "validates named type" do assert {:ok, type_def} = Utilities.make_asn1_type({:named_type, "DisplayString"}) assert type_def.type == :named_type assert type_def.name == "DisplayString" end test "validates atom type" do assert {:ok, type_def} = Utilities.make_asn1_type(:integer) assert type_def.type == :integer end test "rejects invalid type" do assert {:error, msg} = Utilities.make_asn1_type({:invalid_type, "bad"}) assert msg =~ "Invalid type definition" end test "rejects invalid integer constraint" do assert {:error, msg} = Utilities.make_asn1_type({:integer, [{:range, 100, 0}]}) assert msg =~ "Invalid integer constraint" end test "rejects invalid size constraint" do assert {:error, msg} = Utilities.make_asn1_type({:octet_string, [-1]}) assert msg =~ "Invalid size constraint" end end describe "test_kibbles/2" do test "validates unique bit names and values" do bits = [ %{name: "bit1", value: 0}, %{name: "bit2", value: 1}, %{name: "bit3", value: 2} ] assert :ok = Utilities.test_kibbles(bits, :debug) end test "rejects duplicate bit names" do bits = [ %{name: "bit1", value: 0}, %{name: "bit1", value: 1} ] assert {:error, msg} = Utilities.test_kibbles(bits, :warning) assert msg =~ "Duplicate bit names" end test "rejects duplicate bit values" do bits = [ %{name: "bit1", value: 0}, %{name: "bit2", value: 0} ] assert {:error, msg} = Utilities.test_kibbles(bits, :warning) assert msg =~ "Duplicate bit values" end end describe "allow_size_rfc1902/1" do test "allows size for octet_string" do assert Utilities.allow_size_rfc1902(:octet_string) end test "disallows size for integer" do refute Utilities.allow_size_rfc1902(:integer) end test "disallows size for object_identifier" do refute Utilities.allow_size_rfc1902(:object_identifier) end end describe "check_sub_ids/3" do test "accepts valid sub-identifiers" do assert :ok = Utilities.check_sub_ids([1, 3, 6, 1, 2, 1], 0, 255) end test "rejects sub-identifier below minimum" do assert {:error, msg} = Utilities.check_sub_ids([1, 3, -1], 0, 255) assert msg =~ "out of range" end test "rejects sub-identifier above maximum" do assert {:error, msg} = Utilities.check_sub_ids([1, 3, 300], 0, 255) assert msg =~ "out of range" end end describe "print_error/2" do test "prints error when not silent" do assert :ok = Utilities.print_error("Test error", :warning) end test "suppresses error when silent" do assert :ok = Utilities.print_error("Test error", :silent) end end describe "print_error/3" do test "prints formatted error when not silent" do assert :ok = Utilities.print_error("Error: ~p", ["test"], :info) end test "suppresses formatted error when silent" do assert :ok = Utilities.print_error("Error: ~p", ["test"], :silent) end end describe "compilation_error/1" do test "throws error with message" do assert catch_throw(Utilities.compilation_error("Test error")) == {:error, "Test error"} end end describe "compilation_error/2" do test "throws formatted error" do result = catch_throw(Utilities.compilation_error("Error: ~s", ["test"])) assert {:error, msg} = result assert msg =~ "Error: test" end end describe "vprint/6" do test "prints debug message when verbosity is high enough" do assert :ok = Utilities.vprint(:debug, :debug, "TestModule", "test_function", "Message: ~s", [ "test" ]) end test "suppresses message when verbosity is too low" do assert :ok = Utilities.vprint(:silent, :debug, "TestModule", "test_function", "Message", []) end end describe "printable?/2" do test "returns true when current >= required" do assert Utilities.printable?(:debug, :warning) assert Utilities.printable?(:info, :warning) assert Utilities.printable?(:warning, :warning) end test "returns false when current < required" do refute Utilities.printable?(:warning, :debug) refute Utilities.printable?(:silent, :info) end end describe "vvalidate/1" do test "validates valid verbosity levels" do assert {:ok, :silent} = Utilities.vvalidate(:silent) assert {:ok, :warning} = Utilities.vvalidate(:warning) assert {:ok, :info} = Utilities.vvalidate(:info) assert {:ok, :debug} = Utilities.vvalidate(:debug) end test "rejects invalid verbosity" do assert {:error, msg} = Utilities.vvalidate(:invalid) assert msg =~ "Invalid verbosity level" end test "rejects non-atom verbosity" do assert {:error, msg} = Utilities.vvalidate("debug") assert msg =~ "Invalid verbosity level" end end describe "key1search/2" do test "finds tuple by first element" do list = [{:a, 1}, {:b, 2}, {:c, 3}] assert {:value, {:b, 2}} = Utilities.key1search(:b, list) end test "returns false when not found" do list = [{:a, 1}, {:b, 2}] assert false == Utilities.key1search(:c, list) end end describe "key1search/3" do test "returns tuple when found" do list = [{:a, 1}, {:b, 2}] assert {:b, 2} = Utilities.key1search(:b, list, :default) end test "returns default when not found" do list = [{:a, 1}] assert :default = Utilities.key1search(:b, list, :default) end end describe "set_dir/2" do test "joins directory when filename has no path" do result = Utilities.set_dir("test.mib", "/usr/share/mibs") assert result == "/usr/share/mibs/test.mib" end test "preserves absolute path" do result = Utilities.set_dir("/home/user/test.mib", "/usr/share/mibs") assert result == "/home/user/test.mib" end test "preserves relative path with directory" do result = Utilities.set_dir("mibs/test.mib", "/usr/share/mibs") assert result == "mibs/test.mib" end end describe "lookup/2" do test "finds item by key in tuple list" do list = [{:a, 1}, {:b, 2}] assert {:ok, {:b, 2}} = Utilities.lookup(:b, list) end test "finds item by name in map list" do list = [%{name: "test1", value: 1}, %{name: "test2", value: 2}] assert {:ok, %{name: "test2", value: 2}} = Utilities.lookup("test2", list) end test "finds item by id in map list" do list = [%{id: 1, data: "a"}, %{id: 2, data: "b"}] assert {:ok, %{id: 2, data: "b"}} = Utilities.lookup(2, list) end test "returns error when not found" do list = [{:a, 1}] assert {:error, :not_found} = Utilities.lookup(:b, list) end end end