test: expand Types module test coverage
Added comprehensive test coverage for previously untested functionality: - IP address parsing (valid IPs, invalid inputs, IPv6 rejection) - Zero-filling behavior for incomplete IPs (Erlang :inet standard) - Type inference for all SNMP types (integers, strings, lists, booleans, nulls) - Value decoding for all SNMP types (strings, integers, IPs, OIDs, exceptions) - Value encoding with type inference and validation controls - Unsigned32 validation (valid ranges, overflow, underflow) - Coercion edge cases (IP addresses, unsigned32, octet_string, OIDs, booleans) Fixes: - Corrected type inference expectation for large integers (returns :unsigned32) - Fixed boolean assertion to use equality check instead of pattern match - Documented :inet.parse_address zero-fill behavior for incomplete IPs Total: 97 tests, all passing
This commit is contained in:
parent
76d70dd3f4
commit
35ee87952f
1 changed files with 205 additions and 0 deletions
|
|
@ -479,4 +479,209 @@ defmodule SnmpKit.SnmpLib.TypesTest do
|
|||
assert :ok = Types.validate_integer(42)
|
||||
end
|
||||
end
|
||||
|
||||
describe "IP address parsing" do
|
||||
test "parses valid IP address strings" do
|
||||
assert {:ok, {192, 168, 1, 1}} = Types.parse_ip_address("192.168.1.1")
|
||||
assert {:ok, {127, 0, 0, 1}} = Types.parse_ip_address("127.0.0.1")
|
||||
assert {:ok, {255, 255, 255, 255}} = Types.parse_ip_address("255.255.255.255")
|
||||
assert {:ok, {0, 0, 0, 0}} = Types.parse_ip_address("0.0.0.0")
|
||||
end
|
||||
|
||||
test "rejects invalid IP address strings" do
|
||||
assert {:error, :invalid_format} = Types.parse_ip_address("invalid")
|
||||
assert {:error, :invalid_format} = Types.parse_ip_address("256.0.0.1")
|
||||
end
|
||||
|
||||
test "zero-fills incomplete IP addresses" do
|
||||
# :inet.parse_address accepts incomplete IPs and zero-fills them
|
||||
# Missing octets are filled at the right with the last octet going to the end
|
||||
assert {:ok, {192, 168, 0, 1}} = Types.parse_ip_address("192.168.1")
|
||||
assert {:ok, {0, 0, 0, 10}} = Types.parse_ip_address("10")
|
||||
end
|
||||
|
||||
test "rejects IPv6 addresses" do
|
||||
assert {:error, :not_ipv4} = Types.parse_ip_address("::1")
|
||||
assert {:error, :not_ipv4} = Types.parse_ip_address("2001:db8::1")
|
||||
end
|
||||
|
||||
test "rejects non-binary input" do
|
||||
assert {:error, :invalid_input} = Types.parse_ip_address(nil)
|
||||
assert {:error, :invalid_input} = Types.parse_ip_address(123)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Type inference" do
|
||||
test "infers integer types" do
|
||||
assert Types.infer_type(100) == :unsigned32
|
||||
assert Types.infer_type(-100) == :integer
|
||||
assert Types.infer_type(2_147_483_647) == :unsigned32
|
||||
end
|
||||
|
||||
test "infers string types" do
|
||||
assert Types.infer_type("hello") == :string
|
||||
assert Types.infer_type("192.168.1.1") == :ip_address
|
||||
end
|
||||
|
||||
test "infers list types" do
|
||||
assert Types.infer_type([1, 3, 6, 1]) == :object_identifier
|
||||
assert Types.infer_type(~c"hello") == :string
|
||||
end
|
||||
|
||||
test "infers boolean types" do
|
||||
assert Types.infer_type(true) == :boolean
|
||||
assert Types.infer_type(false) == :boolean
|
||||
end
|
||||
|
||||
test "infers null types" do
|
||||
assert Types.infer_type(:null) == :null
|
||||
assert Types.infer_type(nil) == :null
|
||||
end
|
||||
|
||||
test "infers ip_address from tuple" do
|
||||
assert Types.infer_type({192, 168, 1, 1}) == :ip_address
|
||||
end
|
||||
|
||||
test "returns unknown for unrecognized types" do
|
||||
assert Types.infer_type(%{}) == :unknown
|
||||
assert Types.infer_type({256, 0, 0, 0}) == :unknown
|
||||
end
|
||||
end
|
||||
|
||||
describe "Value decoding" do
|
||||
test "decodes string values" do
|
||||
assert "hello" = Types.decode_value({:string, "hello"})
|
||||
assert "world" = Types.decode_value({:string, ~c"world"})
|
||||
end
|
||||
|
||||
test "decodes octet string values" do
|
||||
assert "test" = Types.decode_value({:octet_string, "test"})
|
||||
assert "data" = Types.decode_value({:octet_string, ~c"data"})
|
||||
end
|
||||
|
||||
test "decodes integer values" do
|
||||
assert 42 = Types.decode_value({:integer, 42})
|
||||
assert 100 = Types.decode_value({:unsigned32, 100})
|
||||
assert 200 = Types.decode_value({:counter32, 200})
|
||||
assert 300 = Types.decode_value({:gauge32, 300})
|
||||
assert 400 = Types.decode_value({:timeticks, 400})
|
||||
assert 500 = Types.decode_value({:counter64, 500})
|
||||
end
|
||||
|
||||
test "decodes boolean values" do
|
||||
assert true == Types.decode_value({:boolean, true})
|
||||
assert false == Types.decode_value({:boolean, false})
|
||||
end
|
||||
|
||||
test "decodes null" do
|
||||
assert nil == Types.decode_value({:null, :null})
|
||||
end
|
||||
|
||||
test "decodes ip_address from tuple" do
|
||||
assert "192.168.1.1" = Types.decode_value({:ip_address, {192, 168, 1, 1}})
|
||||
end
|
||||
|
||||
test "decodes ip_address from binary" do
|
||||
assert "192.168.1.1" = Types.decode_value({:ip_address, <<192, 168, 1, 1>>})
|
||||
end
|
||||
|
||||
test "decodes oid" do
|
||||
assert [1, 3, 6, 1] = Types.decode_value({:object_identifier, [1, 3, 6, 1]})
|
||||
assert [1, 3, 6, 1] = Types.decode_value({:oid, [1, 3, 6, 1]})
|
||||
end
|
||||
|
||||
test "decodes opaque" do
|
||||
assert <<1, 2, 3>> = Types.decode_value({:opaque, <<1, 2, 3>>})
|
||||
end
|
||||
|
||||
test "decodes exception types" do
|
||||
assert :no_such_object = Types.decode_value({:no_such_object, nil})
|
||||
assert :no_such_instance = Types.decode_value({:no_such_instance, nil})
|
||||
assert :end_of_mib_view = Types.decode_value({:end_of_mib_view, nil})
|
||||
end
|
||||
|
||||
test "passes through untyped values" do
|
||||
assert 42 = Types.decode_value(42)
|
||||
assert "test" = Types.decode_value("test")
|
||||
end
|
||||
end
|
||||
|
||||
describe "Value encoding" do
|
||||
test "encodes with inferred type" do
|
||||
assert {:ok, {:string, "hello"}} = Types.encode_value("hello")
|
||||
assert {:ok, {:unsigned32, 42}} = Types.encode_value(42)
|
||||
end
|
||||
|
||||
test "encodes with explicit type" do
|
||||
assert {:ok, {:counter32, 42}} = Types.encode_value(42, type: :counter32)
|
||||
assert {:ok, {:string, "test"}} = Types.encode_value("test", type: :string)
|
||||
end
|
||||
|
||||
test "validates encoded values by default" do
|
||||
assert {:error, :out_of_range} = Types.encode_value(-1, type: :counter32)
|
||||
assert {:error, :out_of_range} = Types.encode_value(5_000_000_000, type: :counter32)
|
||||
end
|
||||
|
||||
test "skips validation when requested" do
|
||||
assert {:ok, {:counter32, -1}} = Types.encode_value(-1, type: :counter32, validate: false)
|
||||
end
|
||||
|
||||
test "returns error for uninferable types" do
|
||||
assert {:error, :cannot_infer_type} = Types.encode_value(%{})
|
||||
end
|
||||
|
||||
test "encodes IP addresses" do
|
||||
assert {:ok, {:ip_address, {192, 168, 1, 1}}} =
|
||||
Types.encode_value("192.168.1.1", type: :ip_address)
|
||||
end
|
||||
|
||||
test "encodes OIDs" do
|
||||
assert {:ok, {:object_identifier, [1, 3, 6, 1]}} =
|
||||
Types.encode_value([1, 3, 6, 1], type: :object_identifier)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Unsigned32 validation" do
|
||||
test "accepts valid unsigned32 values" do
|
||||
assert :ok = Types.validate_unsigned32(0)
|
||||
assert :ok = Types.validate_unsigned32(100)
|
||||
assert :ok = Types.validate_unsigned32(4_294_967_295)
|
||||
end
|
||||
|
||||
test "rejects out of range unsigned32" do
|
||||
assert {:error, :out_of_range} = Types.validate_unsigned32(-1)
|
||||
assert {:error, :out_of_range} = Types.validate_unsigned32(4_294_967_296)
|
||||
end
|
||||
|
||||
test "rejects non-integer unsigned32" do
|
||||
assert {:error, :not_integer} = Types.validate_unsigned32("42")
|
||||
assert {:error, :not_integer} = Types.validate_unsigned32(nil)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Coercion edge cases" do
|
||||
test "coerces ip_address from string" do
|
||||
assert {:ok, {:ip_address, {192, 168, 1, 1}}} =
|
||||
Types.coerce_value(:ip_address, "192.168.1.1")
|
||||
end
|
||||
|
||||
test "coerces unsigned32" do
|
||||
assert {:ok, {:unsigned32, 42}} = Types.coerce_value(:unsigned32, 42)
|
||||
assert {:error, :out_of_range} = Types.coerce_value(:unsigned32, -1)
|
||||
end
|
||||
|
||||
test "coerces octet_string" do
|
||||
assert {:ok, {:octet_string, "test"}} = Types.coerce_value(:octet_string, "test")
|
||||
end
|
||||
|
||||
test "coerces object_identifier from list" do
|
||||
assert {:ok, {:object_identifier, [1, 3, 6, 1]}} =
|
||||
Types.coerce_value(:object_identifier, [1, 3, 6, 1])
|
||||
end
|
||||
|
||||
test "coerces boolean" do
|
||||
assert {:ok, {:boolean, true}} = Types.coerce_value(:boolean, true)
|
||||
assert {:ok, {:boolean, false}} = Types.coerce_value(:boolean, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue