686 lines
26 KiB
Elixir
686 lines
26 KiB
Elixir
defmodule SnmpKit.SnmpLib.ManagerTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias SnmpKit.SnmpLib.Error
|
|
alias SnmpKit.SnmpLib.Manager
|
|
alias SnmpKit.SnmpLib.Types
|
|
|
|
doctest Manager
|
|
|
|
@moduletag :manager_test
|
|
|
|
describe "Manager.get/3" do
|
|
test "performs basic GET operation with default options" do
|
|
# Mock a simple GET response - this would normally connect to a real device
|
|
# For testing, we can use a known OID that should work
|
|
|
|
# Test OID normalization
|
|
assert is_function(&Manager.get/3)
|
|
|
|
# Test with list OID
|
|
oid_list = [1, 3, 6, 1, 2, 1, 1, 1, 0]
|
|
assert {:error, _} = Manager.get("invalid.host.test", oid_list, timeout: 100)
|
|
|
|
# Test with string OID
|
|
oid_string = "1.3.6.1.2.1.1.1.0"
|
|
assert {:error, _} = Manager.get("invalid.host.test", oid_string, timeout: 100)
|
|
end
|
|
|
|
test "validates input parameters" do
|
|
# Test invalid host
|
|
assert {:error, _} = Manager.get("", [1, 3, 6, 1], timeout: 50)
|
|
|
|
# Test invalid OID (empty)
|
|
assert {:error, _} = Manager.get("192.168.1.1", [], timeout: 50)
|
|
|
|
# Test with valid parameters but non-existent host
|
|
assert {:error, _} =
|
|
Manager.get("192.0.2.1", [1, 3, 6, 1, 2, 1, 1, 1, 0], timeout: 50)
|
|
end
|
|
|
|
test "handles community string options" do
|
|
opts = [community: "private", timeout: 100]
|
|
|
|
# Should attempt connection with private community
|
|
assert {:error, _} = Manager.get("192.0.2.1", [1, 3, 6, 1, 2, 1, 1, 1, 0], opts)
|
|
end
|
|
|
|
test "handles timeout options" do
|
|
# Short timeout should fail quickly
|
|
start_time = System.monotonic_time(:millisecond)
|
|
{:error, _} = Manager.get("192.0.2.1", [1, 3, 6, 1, 2, 1, 1, 1, 0], timeout: 50)
|
|
end_time = System.monotonic_time(:millisecond)
|
|
|
|
# Should complete within reasonable time of timeout
|
|
# Allow for overhead: socket creation, encoding, etc. can add ~25ms
|
|
# Using 200ms threshold to avoid flaky tests under load
|
|
assert end_time - start_time < 200
|
|
end
|
|
|
|
test "normalizes OID formats correctly" do
|
|
# Both should work the same way
|
|
list_oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
|
|
string_oid = "1.3.6.1.2.1.1.1.0"
|
|
|
|
result1 = Manager.get("invalid.host.test", list_oid, timeout: 100)
|
|
result2 = Manager.get("invalid.host.test", string_oid, timeout: 100)
|
|
|
|
# Both should fail the same way (since host is invalid)
|
|
assert {:error, _} = result1
|
|
assert {:error, _} = result2
|
|
end
|
|
end
|
|
|
|
describe "Manager.get_bulk/3" do
|
|
test "validates GETBULK requires SNMPv2c" do
|
|
# v1 should be rejected
|
|
assert {:error, :getbulk_requires_v2c} =
|
|
Manager.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], version: :v1)
|
|
|
|
# v2c should be accepted (but fail due to invalid host)
|
|
assert {:error, _} =
|
|
Manager.get_bulk("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2],
|
|
version: :v2c,
|
|
timeout: 100
|
|
)
|
|
end
|
|
|
|
test "handles bulk operation parameters" do
|
|
opts = [
|
|
max_repetitions: 50,
|
|
non_repeaters: 0,
|
|
timeout: 100
|
|
]
|
|
|
|
assert {:error, _} = Manager.get_bulk("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], opts)
|
|
end
|
|
|
|
test "validates bulk parameters" do
|
|
# Should work with valid bulk parameters
|
|
opts = [max_repetitions: 10, non_repeaters: 0, timeout: 100]
|
|
assert {:error, _} = Manager.get_bulk("invalid.host.test", [1, 3, 6, 1], opts)
|
|
end
|
|
end
|
|
|
|
describe "Manager.set/4" do
|
|
test "accepts different value types" do
|
|
host = "invalid.host.test"
|
|
oid = [1, 3, 6, 1, 2, 1, 1, 5, 0]
|
|
opts = [timeout: 100]
|
|
|
|
# String value
|
|
assert {:error, _} = Manager.set(host, oid, {:string, "test"}, opts)
|
|
|
|
# Integer value
|
|
assert {:error, _} = Manager.set(host, oid, {:integer, 42}, opts)
|
|
|
|
# Counter32 value
|
|
assert {:error, _} = Manager.set(host, oid, {:counter32, 123}, opts)
|
|
end
|
|
|
|
test "validates SET parameters" do
|
|
opts = [timeout: 100]
|
|
|
|
# Invalid value format should be handled
|
|
assert {:error, _} = Manager.set("invalid.host.test", [1, 3, 6, 1], {:string, "test"}, opts)
|
|
end
|
|
end
|
|
|
|
describe "Manager.get_multi/3" do
|
|
test "handles multiple OIDs efficiently" do
|
|
oids = [
|
|
[1, 3, 6, 1, 2, 1, 1, 1, 0],
|
|
[1, 3, 6, 1, 2, 1, 1, 3, 0],
|
|
[1, 3, 6, 1, 2, 1, 1, 5, 0]
|
|
]
|
|
|
|
opts = [timeout: 100]
|
|
|
|
# Should return results for all OIDs (errors in this case)
|
|
assert {:error, _} = Manager.get_multi("invalid.host.test", oids, opts)
|
|
end
|
|
|
|
test "validates multi-get parameters" do
|
|
# Empty OID list should be handled
|
|
assert {:error, _} = Manager.get_multi("192.168.1.1", [], timeout: 100)
|
|
|
|
# Invalid OIDs should be handled - empty list case
|
|
assert {:error, _} = Manager.get_multi("invalid.host.test", [], timeout: 100)
|
|
end
|
|
end
|
|
|
|
describe "Manager.ping/2" do
|
|
test "performs SNMP reachability test" do
|
|
# Should attempt sysUpTime GET
|
|
assert {:error, _} = Manager.ping("invalid.host.test", timeout: 100)
|
|
|
|
# Test with custom community
|
|
assert {:error, _} = Manager.ping("invalid.host.test", community: "private", timeout: 100)
|
|
end
|
|
|
|
test "validates ping parameters" do
|
|
# Should handle various input formats
|
|
assert {:error, _} = Manager.ping("", timeout: 100)
|
|
assert {:error, _} = Manager.ping("192.168.255.255", timeout: 50)
|
|
end
|
|
end
|
|
|
|
describe "Manager option handling" do
|
|
test "merges default options correctly" do
|
|
# Test that defaults are applied
|
|
assert {:error, _} = Manager.get("invalid.host.test", [1, 3, 6, 1])
|
|
|
|
# Test that custom options override defaults
|
|
custom_opts = [
|
|
community: "test",
|
|
version: :v1,
|
|
timeout: 200,
|
|
port: 1161
|
|
]
|
|
|
|
assert {:error, _} = Manager.get("invalid.host.test", [1, 3, 6, 1], custom_opts)
|
|
end
|
|
|
|
test "validates option values" do
|
|
# Test various option combinations
|
|
opts = [
|
|
community: "public",
|
|
version: :v2c,
|
|
timeout: 5000,
|
|
retries: 3,
|
|
port: 161,
|
|
local_port: 0
|
|
]
|
|
|
|
assert {:error, _} = Manager.get("invalid.host.test", [1, 3, 6, 1], opts)
|
|
end
|
|
end
|
|
|
|
describe "Manager host:port parsing" do
|
|
test "supports host:port string format (backward compatibility)" do
|
|
# Host with port in string format should work
|
|
assert {:error, _} = Manager.get("invalid.host.test:1161", [1, 3, 6, 1], timeout: 100)
|
|
assert {:error, _} = Manager.get("192.168.255.255:162", [1, 3, 6, 1], timeout: 100)
|
|
|
|
# Test with get_bulk
|
|
assert {:error, _} = Manager.get_bulk("invalid.host.test:1161", [1, 3, 6, 1], timeout: 100)
|
|
|
|
# Test with set
|
|
assert {:error, _} =
|
|
Manager.set("invalid.host.test:1161", [1, 3, 6, 1], {:string, "test"}, timeout: 100)
|
|
|
|
# Test with get_multi
|
|
oids = [[1, 3, 6, 1, 2, 1, 1, 1, 0], [1, 3, 6, 1, 2, 1, 1, 3, 0]]
|
|
assert {:error, _} = Manager.get_multi("invalid.host.test:1161", oids, timeout: 100)
|
|
|
|
# Test with ping
|
|
assert {:error, _} = Manager.ping("invalid.host.test:1161", timeout: 100)
|
|
end
|
|
|
|
test "supports :port option format (new functionality)" do
|
|
# Host without port, using :port option
|
|
assert {:error, _} =
|
|
Manager.get("invalid.host.test", [1, 3, 6, 1], port: 1161, timeout: 100)
|
|
|
|
assert {:error, _} = Manager.get("192.168.255.255", [1, 3, 6, 1], port: 162, timeout: 100)
|
|
|
|
# Test with get_bulk
|
|
assert {:error, _} =
|
|
Manager.get_bulk("invalid.host.test", [1, 3, 6, 1], port: 1161, timeout: 100)
|
|
|
|
# Test with set
|
|
assert {:error, _} =
|
|
Manager.set("invalid.host.test", [1, 3, 6, 1], {:string, "test"},
|
|
port: 1161,
|
|
timeout: 100
|
|
)
|
|
|
|
# Test with get_multi
|
|
oids = [[1, 3, 6, 1, 2, 1, 1, 1, 0], [1, 3, 6, 1, 2, 1, 1, 3, 0]]
|
|
assert {:error, _} = Manager.get_multi("invalid.host.test", oids, port: 1161, timeout: 100)
|
|
|
|
# Test with ping
|
|
assert {:error, _} = Manager.ping("invalid.host.test", port: 1161, timeout: 100)
|
|
end
|
|
|
|
test "host:port string takes precedence over :port option" do
|
|
# When both are specified, host:port should take precedence for backward compatibility
|
|
# We can't easily test the actual port being used without mocking transport,
|
|
# but we can verify both forms don't cause errors
|
|
assert {:error, _} =
|
|
Manager.get("invalid.host.test:1161", [1, 3, 6, 1], port: 162, timeout: 100)
|
|
|
|
assert {:error, _} =
|
|
Manager.get_bulk("invalid.host.test:1161", [1, 3, 6, 1], port: 162, timeout: 100)
|
|
|
|
assert {:error, _} =
|
|
Manager.set("invalid.host.test:1161", [1, 3, 6, 1], {:string, "test"},
|
|
port: 162,
|
|
timeout: 100
|
|
)
|
|
|
|
# Test with get_multi
|
|
oids = [[1, 3, 6, 1, 2, 1, 1, 1, 0], [1, 3, 6, 1, 2, 1, 1, 3, 0]]
|
|
|
|
assert {:error, _} =
|
|
Manager.get_multi("invalid.host.test:1161", oids, port: 162, timeout: 100)
|
|
|
|
# Test with ping
|
|
assert {:error, _} = Manager.ping("invalid.host.test:1161", port: 162, timeout: 100)
|
|
end
|
|
|
|
test "handles IPv6 addresses without confusing them with port specifications" do
|
|
# Plain IPv6 addresses contain colons but shouldn't be treated as host:port
|
|
# Use invalid IPv6 addresses to avoid network calls
|
|
assert {:error, _} = Manager.get("invalid::ipv6", [1, 3, 6, 1], timeout: 25)
|
|
assert {:error, _} = Manager.get("test:db8::invalid", [1, 3, 6, 1], timeout: 25)
|
|
assert {:error, _} = Manager.get("fake::1234:5678:9abc:def0", [1, 3, 6, 1], timeout: 25)
|
|
|
|
# IPv6 with :port option should work (but fail due to invalid host)
|
|
assert {:error, _} = Manager.get("invalid::ipv6", [1, 3, 6, 1], port: 1161, timeout: 25)
|
|
|
|
assert {:error, _} =
|
|
Manager.get("test:db8::invalid", [1, 3, 6, 1], port: 1161, timeout: 25)
|
|
|
|
assert {:error, _} =
|
|
Manager.get("fake::1234:5678:9abc:def0", [1, 3, 6, 1], port: 1161, timeout: 25)
|
|
end
|
|
|
|
test "supports RFC 3986 bracket notation for IPv6 with ports" do
|
|
# IPv6 with port using bracket notation [addr]:port
|
|
assert {:error, _} = Manager.get("[::1]:1161", [1, 3, 6, 1], timeout: 100)
|
|
assert {:error, _} = Manager.get("[2001:db8::1]:162", [1, 3, 6, 1], timeout: 100)
|
|
|
|
assert {:error, _} =
|
|
Manager.get("[fe80::1234:5678:9abc:def0]:2001", [1, 3, 6, 1], timeout: 100)
|
|
|
|
# Test with get_bulk
|
|
assert {:error, _} = Manager.get_bulk("[::1]:1161", [1, 3, 6, 1], timeout: 100)
|
|
assert {:error, _} = Manager.get_bulk("[2001:db8::1]:162", [1, 3, 6, 1], timeout: 100)
|
|
|
|
# Test with set
|
|
assert {:error, _} =
|
|
Manager.set("[::1]:1161", [1, 3, 6, 1], {:string, "test"}, timeout: 100)
|
|
|
|
assert {:error, _} =
|
|
Manager.set("[2001:db8::1]:162", [1, 3, 6, 1], {:string, "test"}, timeout: 100)
|
|
|
|
# Test with get_multi
|
|
oids = [[1, 3, 6, 1, 2, 1, 1, 1, 0], [1, 3, 6, 1, 2, 1, 1, 3, 0]]
|
|
assert {:error, _} = Manager.get_multi("[::1]:1161", oids, timeout: 100)
|
|
assert {:error, _} = Manager.get_multi("[2001:db8::1]:162", oids, timeout: 100)
|
|
|
|
# Test with ping
|
|
assert {:error, _} = Manager.ping("[::1]:1161", timeout: 100)
|
|
assert {:error, _} = Manager.ping("[2001:db8::1]:162", timeout: 100)
|
|
end
|
|
|
|
test "bracket notation takes precedence over :port option for IPv6" do
|
|
# When both bracket notation and :port option are provided, bracket should take precedence
|
|
assert {:error, _} = Manager.get("[::1]:1161", [1, 3, 6, 1], port: 162, timeout: 100)
|
|
|
|
assert {:error, _} =
|
|
Manager.get("[2001:db8::1]:2001", [1, 3, 6, 1], port: 161, timeout: 100)
|
|
end
|
|
|
|
test "handles malformed IPv6 bracket notation gracefully" do
|
|
# Invalid bracket notation should be treated as hostnames and not cause crashes
|
|
# Missing closing bracket
|
|
assert {:error, _} = Manager.get("[::1", [1, 3, 6, 1], timeout: 25)
|
|
# Missing opening bracket
|
|
assert {:error, _} = Manager.get("::1]", [1, 3, 6, 1], timeout: 25)
|
|
# Invalid format
|
|
assert {:error, _} = Manager.get("[::1:abc", [1, 3, 6, 1], timeout: 25)
|
|
# Invalid port
|
|
assert {:error, _} = Manager.get("[::1]:99999", [1, 3, 6, 1], timeout: 25)
|
|
# Non-numeric port
|
|
assert {:error, _} = Manager.get("[::1]:abc", [1, 3, 6, 1], timeout: 25)
|
|
end
|
|
|
|
test "handles mixed IPv4/IPv6 scenarios correctly" do
|
|
# IPv4 with port should still work
|
|
assert {:error, _} = Manager.get("192.168.1.1:1161", [1, 3, 6, 1], timeout: 100)
|
|
|
|
# IPv6 without port should use :port option
|
|
assert {:error, _} = Manager.get("::1", [1, 3, 6, 1], port: 1161, timeout: 100)
|
|
|
|
# IPv6 with bracket notation should override :port option
|
|
assert {:error, _} = Manager.get("[::1]:2001", [1, 3, 6, 1], port: 1161, timeout: 100)
|
|
|
|
# Complex IPv6 addresses should work with both patterns
|
|
assert {:error, _} =
|
|
Manager.get("2001:0db8:85a3:0000:0000:8a2e:0370:7334", [1, 3, 6, 1],
|
|
port: 1161,
|
|
timeout: 100
|
|
)
|
|
|
|
assert {:error, _} =
|
|
Manager.get("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:2002", [1, 3, 6, 1], timeout: 100)
|
|
end
|
|
|
|
test "validates port numbers in host:port format" do
|
|
# Invalid port numbers should be handled gracefully
|
|
assert {:error, _} = Manager.get("invalid.host.test:99999", [1, 3, 6, 1], timeout: 100)
|
|
assert {:error, _} = Manager.get("invalid.host.test:0", [1, 3, 6, 1], timeout: 100)
|
|
assert {:error, _} = Manager.get("invalid.host.test:abc", [1, 3, 6, 1], timeout: 100)
|
|
end
|
|
|
|
test "falls back to default port 161 when no port specified" do
|
|
# No port in host string and no :port option should use default 161
|
|
assert {:error, _} = Manager.get("invalid.host.test", [1, 3, 6, 1], timeout: 100)
|
|
assert {:error, _} = Manager.get("192.168.255.255", [1, 3, 6, 1], timeout: 100)
|
|
end
|
|
end
|
|
|
|
describe "Manager error handling" do
|
|
test "handles network errors gracefully" do
|
|
# Network errors (timeout or network unreachable)
|
|
assert {:error, _} = Manager.get("192.168.255.255", [1, 3, 6, 1], timeout: 50)
|
|
|
|
# Network unreachable errors (using non-routable IP to avoid DNS lookup)
|
|
assert {:error, _} = Manager.get("192.0.2.1", [1, 3, 6, 1], timeout: 50)
|
|
end
|
|
|
|
test "handles SNMP protocol errors" do
|
|
# These would test actual SNMP error responses
|
|
# For now, we test that error handling structure is in place
|
|
assert is_function(&Manager.get/3)
|
|
assert is_function(&Manager.get_bulk/3)
|
|
assert is_function(&Manager.set/4)
|
|
end
|
|
end
|
|
|
|
describe "Manager error status mapping" do
|
|
test "correctly maps SNMP error status codes" do
|
|
# Test that Manager.decode_error_status works correctly through public API
|
|
# We'll test this by simulating PDU responses with different error status codes
|
|
|
|
# This tests the internal logic but through a way that's observable
|
|
# The decode_error_status function should map:
|
|
# 0 -> :no_error, 1 -> :too_big, 2 -> :no_such_name, 3 -> :bad_value, 4 -> :read_only, 5 -> :gen_err
|
|
|
|
# We can verify this works by using the Error module which should have the same mapping
|
|
assert Error.error_atom(0) == :no_error
|
|
assert Error.error_atom(1) == :too_big
|
|
assert Error.error_atom(2) == :no_such_name
|
|
assert Error.error_atom(3) == :bad_value
|
|
assert Error.error_atom(4) == :read_only
|
|
assert Error.error_atom(5) == :gen_err
|
|
end
|
|
|
|
test "error status takes precedence over varbinds" do
|
|
# This test verifies that when error_status != 0, it's handled immediately
|
|
# regardless of what's in varbinds (which is the bug we just fixed)
|
|
|
|
# The fix ensures that error_status is checked BEFORE varbinds pattern matching
|
|
# This is critical for proper SNMP error handling
|
|
|
|
# We can't directly test the private extract_get_result function,
|
|
# but we can verify the logic is sound by testing error interpretation
|
|
# The interpret_error function only changes :gen_err, other errors pass through
|
|
assert Manager.interpret_error(:no_such_name, :get, :v2c) == :no_such_name
|
|
assert Manager.interpret_error(:gen_err, :get, :v1) == :no_such_name
|
|
end
|
|
|
|
test "handles edge cases in error responses" do
|
|
# Verify error handling doesn't break with various inputs
|
|
assert Manager.interpret_error(:timeout, :get, :v2c) == :timeout
|
|
assert Manager.interpret_error(:network_error, :get, :v1) == :network_error
|
|
assert Manager.interpret_error(:invalid_response, :get, :v2c) == :invalid_response
|
|
end
|
|
|
|
test "correctly handles SNMPv2c exception values in varbinds" do
|
|
# Test that exception values are properly extracted regardless of format
|
|
# The fix handles both formats:
|
|
# 1. {oid, :end_of_mib_view, nil} - simulator format (type field)
|
|
# 2. {oid, :octet_string, {:end_of_mib_view, nil}} - standard format (value field)
|
|
|
|
# This verifies the logic works but we can't test the private function directly
|
|
# Instead verify that SNMPv2c exception types are recognized
|
|
assert Types.exception_type?(:no_such_object) == true
|
|
assert Types.exception_type?(:no_such_instance) == true
|
|
assert Types.exception_type?(:end_of_mib_view) == true
|
|
assert Types.exception_type?(:integer) == false
|
|
end
|
|
end
|
|
|
|
describe "Manager error interpretation" do
|
|
test "interpret_error provides better semantics for genErr" do
|
|
# SNMPv1 GET operations
|
|
assert Manager.interpret_error(:gen_err, :get, :v1) == :no_such_name
|
|
|
|
# SNMPv2c+ GET operations
|
|
assert Manager.interpret_error(:gen_err, :get, :v2c) == :no_such_object
|
|
assert Manager.interpret_error(:gen_err, :get, :v2) == :no_such_object
|
|
assert Manager.interpret_error(:gen_err, :get, :v3) == :no_such_object
|
|
|
|
# SNMPv2c+ GETBULK operations
|
|
assert Manager.interpret_error(:gen_err, :get_bulk, :v2c) == :no_such_object
|
|
assert Manager.interpret_error(:gen_err, :get_bulk, :v2) == :no_such_object
|
|
|
|
# Other errors pass through unchanged
|
|
assert Manager.interpret_error(:too_big, :get, :v2c) == :too_big
|
|
assert Manager.interpret_error(:no_such_name, :get, :v1) == :no_such_name
|
|
assert Manager.interpret_error(:bad_value, :set, :v2c) == :bad_value
|
|
|
|
# SET operations with genErr remain as genErr
|
|
assert Manager.interpret_error(:gen_err, :set, :v2c) == :gen_err
|
|
end
|
|
|
|
test "interpret_error handles edge cases" do
|
|
# Unknown operation types
|
|
assert Manager.interpret_error(:gen_err, :unknown_op, :v2c) == :gen_err
|
|
|
|
# Unknown SNMP versions
|
|
assert Manager.interpret_error(:gen_err, :get, :unknown_version) == :gen_err
|
|
|
|
# nil or invalid inputs
|
|
assert Manager.interpret_error(nil, :get, :v2c) == nil
|
|
assert Manager.interpret_error(:gen_err, nil, :v2c) == :gen_err
|
|
end
|
|
end
|
|
|
|
describe "Manager integration" do
|
|
test "integrates with existing SnmpLib modules" do
|
|
# Verify Manager uses other SnmpLib modules correctly
|
|
|
|
# Test OID normalization (should use SnmpKit.SnmpLib.OID)
|
|
string_oid = "1.3.6.1.2.1.1.1.0"
|
|
assert {:error, _} = Manager.get("invalid.host.test", string_oid, timeout: 100)
|
|
|
|
# Test PDU creation (should use SnmpKit.SnmpLib.PDU)
|
|
assert {:error, _} = Manager.get("invalid.host.test", [1, 3, 6, 1], timeout: 100)
|
|
|
|
# Test transport (should use SnmpKit.SnmpLib.Transport)
|
|
assert {:error, _} = Manager.ping("invalid.host.test", timeout: 100)
|
|
end
|
|
end
|
|
|
|
# Performance and stress tests
|
|
describe "Manager performance" do
|
|
@tag :performance
|
|
test "handles concurrent operations" do
|
|
# Test multiple concurrent operations
|
|
tasks =
|
|
Enum.map(1..5, fn _i ->
|
|
Task.async(fn ->
|
|
Manager.get("invalid.host.test", [1, 3, 6, 1], timeout: 100)
|
|
end)
|
|
end)
|
|
|
|
results = Task.await_many(tasks, 1000)
|
|
|
|
# All should fail gracefully (invalid host)
|
|
assert Enum.all?(results, fn result ->
|
|
match?({:error, _}, result)
|
|
end)
|
|
end
|
|
|
|
@tag :performance
|
|
test "get_multi is more efficient than individual gets" do
|
|
oids = Enum.map(1..10, fn i -> [1, 3, 6, 1, 2, 1, 1, i, 0] end)
|
|
|
|
# Time individual gets
|
|
{time_individual, _} =
|
|
:timer.tc(fn ->
|
|
Enum.map(oids, fn oid ->
|
|
Manager.get("invalid.host.test", oid, timeout: 50)
|
|
end)
|
|
end)
|
|
|
|
# Time multi get
|
|
{time_multi, _} =
|
|
:timer.tc(fn ->
|
|
Manager.get_multi("invalid.host.test", oids, timeout: 50)
|
|
end)
|
|
|
|
# Multi should complete faster (though both will fail)
|
|
# At minimum, they should both complete within reasonable time
|
|
assert time_individual > 0
|
|
assert time_multi > 0
|
|
end
|
|
end
|
|
|
|
describe "Manager.get_next/3" do
|
|
test "performs basic GETNEXT operation with default options" do
|
|
# Test function exists and has correct signature
|
|
assert is_function(&Manager.get_next/3)
|
|
|
|
# Test with list OID - should fail with invalid host but function should work
|
|
oid_list = [1, 3, 6, 1, 2, 1, 1, 1, 0]
|
|
assert {:error, _} = Manager.get_next("invalid.host.test", oid_list, timeout: 100)
|
|
|
|
# Test with string OID
|
|
oid_string = "1.3.6.1.2.1.1.1.0"
|
|
assert {:error, _} = Manager.get_next("invalid.host.test", oid_string, timeout: 100)
|
|
end
|
|
|
|
test "uses proper GETNEXT PDU for SNMP v1" do
|
|
# SNMP v1 should use actual GETNEXT PDU, not GETBULK
|
|
oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
|
|
opts = [version: :v1, timeout: 100]
|
|
|
|
# Should attempt GETNEXT operation (will fail due to invalid host)
|
|
assert {:error, _} = Manager.get_next("invalid.host.test", oid, opts)
|
|
end
|
|
|
|
test "uses GETBULK with max_repetitions=1 for SNMP v2c+" do
|
|
# SNMP v2c should use GETBULK for efficiency
|
|
oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
|
|
opts = [version: :v2c, timeout: 100]
|
|
|
|
# Should attempt GETBULK operation (will fail due to invalid host)
|
|
assert {:error, _} = Manager.get_next("invalid.host.test", oid, opts)
|
|
end
|
|
|
|
test "validates input parameters" do
|
|
# Test invalid host
|
|
assert {:error, _} = Manager.get_next("", [1, 3, 6, 1], timeout: 50)
|
|
|
|
# Test invalid OID (empty)
|
|
assert {:error, _} = Manager.get_next("192.168.1.1", [], timeout: 50)
|
|
|
|
# Test with valid parameters but non-existent host
|
|
assert {:error, _} =
|
|
Manager.get_next("192.0.2.1", [1, 3, 6, 1, 2, 1, 1, 1, 0], timeout: 50)
|
|
end
|
|
|
|
test "handles community string options" do
|
|
opts = [community: "private", timeout: 100]
|
|
|
|
# Should attempt connection with private community
|
|
assert {:error, _} =
|
|
Manager.get_next("192.0.2.1", [1, 3, 6, 1, 2, 1, 1, 1, 0], opts)
|
|
end
|
|
|
|
test "handles timeout options" do
|
|
# Short timeout should fail quickly
|
|
start_time = System.monotonic_time(:millisecond)
|
|
{:error, _} = Manager.get_next("192.0.2.1", [1, 3, 6, 1, 2, 1, 1, 1, 0], timeout: 50)
|
|
end_time = System.monotonic_time(:millisecond)
|
|
|
|
# Should complete within reasonable time of timeout
|
|
# Allow for overhead: socket creation, encoding, etc. can add ~25ms
|
|
# Using 200ms threshold to avoid flaky tests under load
|
|
assert end_time - start_time < 200
|
|
end
|
|
|
|
test "normalizes OID formats correctly" do
|
|
# Both should work the same way
|
|
list_oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
|
|
string_oid = "1.3.6.1.2.1.1.1.0"
|
|
|
|
result1 = Manager.get_next("invalid.host.test", list_oid, timeout: 100)
|
|
result2 = Manager.get_next("invalid.host.test", string_oid, timeout: 100)
|
|
|
|
# Both should fail the same way (since host is invalid)
|
|
assert {:error, _} = result1
|
|
assert {:error, _} = result2
|
|
end
|
|
|
|
test "supports both SNMP versions" do
|
|
oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
|
|
|
|
# Test v1 explicitly
|
|
assert {:error, _} = Manager.get_next("invalid.host.test", oid, version: :v1, timeout: 100)
|
|
|
|
# Test v2c explicitly
|
|
assert {:error, _} = Manager.get_next("invalid.host.test", oid, version: :v2c, timeout: 100)
|
|
|
|
# Test default (should be v2c)
|
|
assert {:error, _} = Manager.get_next("invalid.host.test", oid, timeout: 100)
|
|
end
|
|
|
|
test "handles host:port format correctly" do
|
|
# Host with port in string format should work
|
|
assert {:error, _} = Manager.get_next("invalid.host.test:1161", [1, 3, 6, 1], timeout: 100)
|
|
assert {:error, _} = Manager.get_next("192.168.255.255:162", [1, 3, 6, 1], timeout: 100)
|
|
|
|
# IPv6 bracket notation
|
|
assert {:error, _} = Manager.get_next("[::1]:1161", [1, 3, 6, 1], timeout: 100)
|
|
|
|
# Port option
|
|
assert {:error, _} =
|
|
Manager.get_next("invalid.host.test", [1, 3, 6, 1], port: 1161, timeout: 100)
|
|
end
|
|
|
|
test "expected return format is {:ok, {next_oid, type, value}} tuple" do
|
|
# While we can't test successful responses without a real SNMP device,
|
|
# we can verify the function signature and error return format
|
|
result = Manager.get_next("invalid.host.test", [1, 3, 6, 1, 2, 1, 1, 1, 0], timeout: 100)
|
|
|
|
# Should return error tuple (since host is invalid)
|
|
assert {:error, _reason} = result
|
|
|
|
# The successful format would be {:ok, {next_oid, type, value}}
|
|
# This is documented in the function spec and examples
|
|
end
|
|
|
|
test "handles various option combinations" do
|
|
oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
|
|
|
|
# Test with multiple options
|
|
opts = [
|
|
community: "test",
|
|
version: :v1,
|
|
timeout: 200,
|
|
retries: 2,
|
|
port: 1161,
|
|
local_port: 0
|
|
]
|
|
|
|
assert {:error, _} = Manager.get_next("invalid.host.test", oid, opts)
|
|
|
|
# Test v2c with different options
|
|
opts_v2c = [
|
|
community: "public",
|
|
version: :v2c,
|
|
timeout: 500,
|
|
retries: 1,
|
|
port: 161
|
|
]
|
|
|
|
assert {:error, _} = Manager.get_next("invalid.host.test", oid, opts_v2c)
|
|
end
|
|
end
|
|
end
|