303 lines
11 KiB
Elixir
303 lines
11 KiB
Elixir
defmodule SnmpKit.OidArchitectureValidationTest do
|
|
@moduledoc """
|
|
Tests to validate that the OID handling architecture follows the established principles:
|
|
|
|
1. All internal OID handling uses lists of integers [1,3,6,1,2,1,1,1,0]
|
|
2. All external APIs accept both string "1.3.6.1.2.1.1.1.0" and list [1,3,6,1,2,1,1,1,0] formats
|
|
3. Conversion happens only at API boundaries
|
|
4. No unnecessary string/list conversions in internal operations
|
|
"""
|
|
|
|
use ExUnit.Case
|
|
|
|
alias SnmpKit.SnmpLib.OID
|
|
alias SnmpKit.SnmpMgr.Core
|
|
alias SnmpKit.SnmpMgr.Format
|
|
alias SnmpKit.SnmpMgr.MIB
|
|
|
|
doctest SnmpKit
|
|
|
|
describe "Core API Entry Points" do
|
|
test "parse_oid function always returns lists" do
|
|
# Test string input
|
|
assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
|
|
Core.parse_oid("1.3.6.1.2.1.1.1.0")
|
|
|
|
assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
|
|
Core.parse_oid(".1.3.6.1.2.1.1.1.0")
|
|
|
|
# Test list input - should validate and return same list
|
|
assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
|
|
Core.parse_oid([1, 3, 6, 1, 2, 1, 1, 1, 0])
|
|
|
|
# Test empty cases get proper fallbacks
|
|
assert {:ok, [1, 3]} = Core.parse_oid("")
|
|
assert {:ok, [1, 3]} = Core.parse_oid([])
|
|
|
|
# Test invalid cases
|
|
assert {:error, :invalid_oid_input} = Core.parse_oid(123)
|
|
assert {:error, :invalid_oid_input} = Core.parse_oid(nil)
|
|
end
|
|
|
|
test "API functions accept both string and list OID formats" do
|
|
# These would normally make network calls, but we're testing the input validation
|
|
# The functions should not crash on valid OID formats
|
|
|
|
string_oid = "1.3.6.1.2.1.1.1.0"
|
|
list_oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
|
|
|
|
# Test that both formats are accepted (they will fail with network errors, but not format errors)
|
|
result1 = SnmpKit.SnmpMgr.get("invalid.host", string_oid, timeout: 100)
|
|
result2 = SnmpKit.SnmpMgr.get("invalid.host", list_oid, timeout: 100)
|
|
|
|
# Both should fail with network errors, not format errors
|
|
assert match?({:error, _}, result1)
|
|
assert match?({:error, _}, result2)
|
|
|
|
# Neither should fail with format-specific errors
|
|
refute match?({:error, :invalid_oid_format}, result1)
|
|
refute match?({:error, :invalid_oid_format}, result2)
|
|
refute match?({:error, :invalid_oid_input}, result1)
|
|
refute match?({:error, :invalid_oid_input}, result2)
|
|
end
|
|
end
|
|
|
|
describe "Internal Consistency" do
|
|
test "OID validation uses list format internally" do
|
|
# Test the OID validation functions work with lists
|
|
assert :ok = OID.valid_oid?([1, 3, 6, 1, 2, 1, 1, 1, 0])
|
|
assert {:error, :empty_oid} = OID.valid_oid?([])
|
|
assert {:error, :invalid_component} = OID.valid_oid?([1, -1, 3])
|
|
assert {:error, :invalid_input} = OID.valid_oid?("string")
|
|
end
|
|
|
|
test "OID normalization returns lists consistently" do
|
|
# Test the normalize function in OID module
|
|
assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
|
|
OID.normalize("1.3.6.1.2.1.1.1.0")
|
|
|
|
assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
|
|
OID.normalize([1, 3, 6, 1, 2, 1, 1, 1, 0])
|
|
|
|
# Empty cases return error for OID module (but get fallback in parse_oid)
|
|
assert {:error, :empty_oid} = OID.normalize("")
|
|
assert {:error, :empty_oid} = OID.normalize([])
|
|
end
|
|
|
|
test "bulk operations maintain list format internally" do
|
|
# Test that bulk resolve_oid function returns lists
|
|
test_cases = [
|
|
{"1.3.6.1.2.1.1.1.0", [1, 3, 6, 1, 2, 1, 1, 1, 0]},
|
|
{[1, 3, 6, 1, 2, 1, 1, 1, 0], [1, 3, 6, 1, 2, 1, 1, 1, 0]},
|
|
{"", [1, 3]},
|
|
{[], [1, 3]}
|
|
]
|
|
|
|
Enum.each(test_cases, fn {input, expected} ->
|
|
# Access the private resolve_oid function through a test helper pattern
|
|
# This validates internal consistency without exposing private APIs
|
|
case Core.parse_oid(input) do
|
|
{:ok, result} ->
|
|
assert result == expected,
|
|
"resolve_oid(#{inspect(input)}) should return #{inspect(expected)}, got #{inspect(result)}"
|
|
|
|
error ->
|
|
flunk("resolve_oid(#{inspect(input)}) returned error: #{inspect(error)}")
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
describe "API Boundaries and Conversion" do
|
|
test "string-to-list conversion happens at entry points" do
|
|
# Test OID string parsing
|
|
assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
|
|
OID.string_to_list("1.3.6.1.2.1.1.1.0")
|
|
|
|
assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
|
|
OID.string_to_list(".1.3.6.1.2.1.1.1.0")
|
|
|
|
# Test edge cases
|
|
assert {:error, :empty_oid} = OID.string_to_list("")
|
|
assert {:error, :invalid_oid_string} = OID.string_to_list("1.3.6.x")
|
|
assert {:error, :invalid_input} = OID.string_to_list(123)
|
|
end
|
|
|
|
test "list-to-string conversion for output only" do
|
|
# Test OID list to string conversion
|
|
assert {:ok, "1.3.6.1.2.1.1.1.0"} =
|
|
OID.list_to_string([1, 3, 6, 1, 2, 1, 1, 1, 0])
|
|
|
|
# Test edge cases
|
|
assert {:error, :empty_oid} = OID.list_to_string([])
|
|
assert {:error, :invalid_component} = OID.list_to_string([1, -1, 3])
|
|
assert {:error, :invalid_input} = OID.list_to_string("string")
|
|
end
|
|
|
|
test "format functions handle mixed OID value types correctly" do
|
|
# Test that format functions handle object_identifier values correctly
|
|
# Note: pretty_print expects string OIDs as input (final output format)
|
|
|
|
test_cases = [
|
|
{{"1.3.6.1.2.1.1.1.0", :object_identifier, [1, 3, 6, 1, 4, 1, 9]}, "1.3.6.1.4.1.9"},
|
|
{{"1.3.6.1.2.1.1.2.0", :object_identifier, "1.3.6.1.4.1.9"}, "1.3.6.1.4.1.9"},
|
|
{{"1.3.6.1.2.1.1.3.0", :integer, 12_345}, "12345"}
|
|
]
|
|
|
|
Enum.each(test_cases, fn {input_tuple, expected_formatted_value} ->
|
|
{oid_string, type, formatted_value} = Format.pretty_print(input_tuple)
|
|
|
|
# OID should be string in output
|
|
assert is_binary(oid_string), "OID in output should be string format"
|
|
|
|
# Type should be preserved
|
|
{_, original_type, _} = input_tuple
|
|
assert type == original_type, "Type should be preserved"
|
|
|
|
# Value formatting should handle both list and string object identifiers
|
|
if original_type == :object_identifier do
|
|
assert formatted_value == expected_formatted_value,
|
|
"Object identifier should be formatted as string: #{inspect(formatted_value)}"
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
describe "Error Handling and Validation" do
|
|
test "invalid OID inputs are properly rejected" do
|
|
invalid_inputs = [
|
|
"1.3.6.invalid",
|
|
"1.3.6.-1",
|
|
[1, 3, -1],
|
|
[1, 3, "invalid"],
|
|
123,
|
|
nil,
|
|
%{},
|
|
{:invalid}
|
|
]
|
|
|
|
Enum.each(invalid_inputs, fn invalid_input ->
|
|
result = Core.parse_oid(invalid_input)
|
|
|
|
assert match?({:error, _}, result),
|
|
"Invalid input #{inspect(invalid_input)} should be rejected"
|
|
end)
|
|
end
|
|
|
|
test "empty OID cases have consistent fallbacks" do
|
|
# Only test cases that actually get fallback treatment in parse_oid
|
|
empty_cases = ["", [], " "]
|
|
|
|
Enum.each(empty_cases, fn empty_input ->
|
|
case Core.parse_oid(empty_input) do
|
|
{:ok, [1, 3]} ->
|
|
# Expected fallback
|
|
:ok
|
|
|
|
{:ok, other} ->
|
|
flunk("Empty input #{inspect(empty_input)} should fallback to [1, 3], got #{inspect(other)}")
|
|
|
|
{:error, reason} ->
|
|
flunk("Empty input #{inspect(empty_input)} should not error, got #{inspect(reason)}")
|
|
end
|
|
end)
|
|
|
|
# "." is treated as an invalid OID string, not empty
|
|
assert {:error, _} = Core.parse_oid(".")
|
|
end
|
|
|
|
test "type information is preserved throughout operations" do
|
|
# This test validates that our architecture preserves SNMP type information
|
|
# Note: Format functions expect string OIDs (final output format)
|
|
|
|
mock_snmp_results = [
|
|
{"1.3.6.1.2.1.1.1.0", :octet_string, "System Description"},
|
|
{"1.3.6.1.2.1.1.2.0", :object_identifier, [1, 3, 6, 1, 4, 1, 9]},
|
|
{"1.3.6.1.2.1.1.3.0", :timeticks, 123_456}
|
|
]
|
|
|
|
# Test that formatting preserves type information
|
|
formatted_results =
|
|
Enum.map(mock_snmp_results, fn result ->
|
|
Format.pretty_print(result)
|
|
end)
|
|
|
|
# Every result should have 3-tuple format with preserved type
|
|
mock_snmp_results
|
|
|> Enum.zip(formatted_results)
|
|
|> Enum.each(fn {{_, original_type, _}, {_, formatted_type, _}} ->
|
|
assert formatted_type == original_type,
|
|
"Type information should be preserved: #{original_type} != #{formatted_type}"
|
|
end)
|
|
|
|
# OIDs should be converted to strings in final output
|
|
Enum.each(formatted_results, fn {oid_string, _type, _value} ->
|
|
assert is_binary(oid_string), "Final output OID should be string format"
|
|
|
|
assert String.match?(oid_string, ~r/^\d+(\.\d+)*$/),
|
|
"OID string should be valid dotted decimal format"
|
|
end)
|
|
end
|
|
end
|
|
|
|
describe "Performance and Efficiency" do
|
|
test "no unnecessary conversions in processing chain" do
|
|
# Test that we don't do redundant string<->list conversions
|
|
# This is more of a design validation than a direct test
|
|
|
|
list_oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
|
|
|
|
# If we start with a list, parse_oid should not do unnecessary conversions
|
|
assert {:ok, result} = Core.parse_oid(list_oid)
|
|
assert result == list_oid, "List input should return same list without conversion"
|
|
|
|
# The result should be the same object (no unnecessary copying)
|
|
# Note: This is more about design efficiency than strict object identity
|
|
end
|
|
end
|
|
|
|
describe "Integration Consistency" do
|
|
test "MIB operations maintain OID list format" do
|
|
# Test MIB reverse lookup expects lists
|
|
test_oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
|
|
|
|
# This will return an error since no MIBs are loaded, but should not fail on format
|
|
result = MIB.reverse_lookup(test_oid)
|
|
|
|
# Should not fail with format errors
|
|
refute match?({:error, :invalid_oid_format}, result)
|
|
|
|
# String version should convert to list internally
|
|
string_result = MIB.reverse_lookup("1.3.6.1.2.1.1.1.0")
|
|
refute match?({:error, :invalid_oid_format}, string_result)
|
|
end
|
|
|
|
test "walk operations use consistent OID formats" do
|
|
# Test that internal walk functions expect list OIDs
|
|
# We can't test actual walks without network, but we can test OID resolution
|
|
|
|
walk_test_cases = [
|
|
"1.3.6.1.2.1.1",
|
|
"system",
|
|
[1, 3, 6, 1, 2, 1, 1],
|
|
""
|
|
]
|
|
|
|
Enum.each(walk_test_cases, fn test_input ->
|
|
# All these should be resolved to lists internally
|
|
case Core.parse_oid(test_input) do
|
|
{:ok, result} ->
|
|
assert is_list(result),
|
|
"Walk OID resolution should return lists: #{inspect(test_input)} -> #{inspect(result)}"
|
|
|
|
assert Enum.all?(result, &is_integer/1),
|
|
"All OID components should be integers: #{inspect(result)}"
|
|
|
|
{:error, _} ->
|
|
# Some inputs may be invalid, that's expected
|
|
:ok
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
end
|