towerops/test/snmpkit/snmp_lib/manager_test.exs

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