towerops/test/snmpkit/snmp_mgr/bulk_test.exs
2026-06-15 11:15:46 -05:00

521 lines
16 KiB
Elixir

defmodule SnmpKit.SnmpMgr.BulkTest do
use ExUnit.Case, async: true
alias SnmpKit.SnmpMgr.Bulk
doctest Bulk
describe "get_bulk/3 version validation" do
test "rejects SNMPv1 with error" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1)
assert {:error, {:unsupported_operation, :get_bulk_requires_v2c}} = result
end
test "rejects SNMPv3 with error" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v3)
assert {:error, {:unsupported_operation, :get_bulk_requires_v2c}} = result
end
test "accepts SNMPv2c (explicit)" do
# Will timeout or error without proper SNMP infrastructure
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v2c, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "defaults to SNMPv2c when version not specified" do
# Should use v2c by default
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
end
describe "get_bulk/3 OID handling" do
test "accepts single OID as list" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "accepts single OID as string" do
result = Bulk.get_bulk("192.168.1.1", "1.3.6.1.2.1.1", timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "accepts multiple OIDs as list" do
result = Bulk.get_bulk("192.168.1.1", [[1, 3, 6, 1, 2, 1, 1], [1, 3, 6, 1, 2, 1, 2]], timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "converts single OID to list internally" do
# Single OID should be wrapped in a list
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "returns error for invalid OID" do
result = Bulk.get_bulk("192.168.1.1", "invalid.oid", timeout: 100)
assert {:error, _} = result
end
end
describe "get_bulk/3 options handling" do
test "uses default max_repetitions when not specified" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
# Default is 30
assert elem(result, 0) in [:ok, :error]
end
test "uses specified max_repetitions" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], max_repetitions: 10, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "uses default non_repeaters when not specified" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
# Default is 0
assert elem(result, 0) in [:ok, :error]
end
test "uses specified non_repeaters" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], non_repeaters: 1, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "forces version to v2c even if different version passed" do
# Should override any other version setting
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "accepts community string option" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], community: "private", timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "accepts port option" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], port: 1161, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "accepts timeout option" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 5000)
assert elem(result, 0) in [:ok, :error]
end
end
describe "get_table_bulk/3" do
test "retrieves table using GETBULK" do
result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "accepts string table OID" do
result = Bulk.get_table_bulk("192.168.1.1", "1.3.6.1.2.1.2.2", timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "uses default max_entries when not specified" do
result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100)
# Default is 1000
assert elem(result, 0) in [:ok, :error]
end
test "respects max_entries option" do
result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], max_entries: 100, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "respects max_repetitions option" do
result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], max_repetitions: 20, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "returns error for invalid table OID" do
result = Bulk.get_table_bulk("192.168.1.1", "invalid.table", timeout: 100)
assert {:error, _} = result
end
test "accepts community string" do
result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], community: "public", timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
end
describe "walk_bulk/3" do
test "performs bulk walk of subtree" do
result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "accepts string root OID" do
result = Bulk.walk_bulk("192.168.1.1", "1.3.6.1.2.1.1", timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "uses default max_entries when not specified" do
result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
# Default is 1000
assert elem(result, 0) in [:ok, :error]
end
test "respects max_entries option" do
result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], max_entries: 50, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "respects max_repetitions option" do
result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], max_repetitions: 15, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "returns error for invalid root OID" do
result = Bulk.walk_bulk("192.168.1.1", "invalid", timeout: 100)
assert {:error, _} = result
end
test "accepts community string" do
result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], community: "private", timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "accepts port option" do
result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], port: 1161, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
end
describe "get_bulk_multi/2" do
test "performs concurrent GETBULK operations" do
requests = [
{"192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0]},
{"192.168.1.2", [1, 3, 6, 1, 2, 1, 1, 2, 0]},
{"192.168.1.3", [1, 3, 6, 1, 2, 1, 1, 3, 0]}
]
results = Bulk.get_bulk_multi(requests, timeout: 100)
assert length(results) == 3
# Each result is either {:ok, _} or {:error, _}
assert Enum.all?(results, fn result ->
match?({:ok, _}, result) or match?({:error, _}, result)
end)
end
test "handles empty request list" do
results = Bulk.get_bulk_multi([], timeout: 100)
assert results == []
end
test "handles single request" do
requests = [{"192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0]}]
results = Bulk.get_bulk_multi(requests, timeout: 100)
assert length(results) == 1
end
test "returns timeout error for tasks that don't complete" do
requests = [
{"192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0]},
{"192.168.1.2", [1, 3, 6, 1, 2, 1, 1, 2, 0]}
]
# Very short timeout to force timeout
results = Bulk.get_bulk_multi(requests, timeout: 1)
assert length(results) == 2
# All should error (either timeout or other error like invalid_oid_input)
assert Enum.all?(results, fn result ->
match?({:error, _}, result)
end)
end
test "uses default timeout when not specified" do
requests = [{"192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0]}]
results = Bulk.get_bulk_multi(requests)
assert length(results) == 1
end
test "passes options to individual requests" do
requests = [
{"192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0]},
{"192.168.1.2", [1, 3, 6, 1, 2, 1, 1, 2, 0]}
]
results = Bulk.get_bulk_multi(requests, community: "public", max_repetitions: 10, timeout: 100)
assert length(results) == 2
end
test "handles task failures gracefully" do
requests = [
{"192.168.1.1", "invalid.oid"},
{"192.168.1.2", [1, 3, 6, 1, 2, 1, 1, 2, 0]}
]
results = Bulk.get_bulk_multi(requests, timeout: 100)
assert length(results) == 2
# First should error due to invalid OID
assert match?({:error, _}, Enum.at(results, 0))
end
test "accepts string OIDs in requests" do
requests = [
{"192.168.1.1", "1.3.6.1.2.1.1.1.0"},
{"192.168.1.2", "1.3.6.1.2.1.1.2.0"}
]
results = Bulk.get_bulk_multi(requests, timeout: 100)
assert length(results) == 2
end
test "accepts mixed OID formats in requests" do
requests = [
{"192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0]},
{"192.168.1.2", "1.3.6.1.2.1.1.2.0"}
]
results = Bulk.get_bulk_multi(requests, timeout: 100)
assert length(results) == 2
end
end
describe "target formats" do
test "accepts IP address as target" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "accepts hostname as target" do
result = Bulk.get_bulk("device.local", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "accepts target with port" do
result = Bulk.get_bulk("192.168.1.1:161", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
end
describe "error handling" do
test "handles network timeout" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 1)
assert match?({:error, _}, result)
end
test "handles invalid target" do
result = Bulk.get_bulk("999.999.999.999", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
assert match?({:error, _}, result)
end
test "handles unreachable target" do
result = Bulk.get_bulk("192.168.255.254", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
assert match?({:error, _}, result)
end
test "returns error for malformed options" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], max_repetitions: -1, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
end
describe "edge cases" do
test "handles empty table results" do
result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 999], timeout: 100)
# Empty table should return {:ok, []}
assert elem(result, 0) in [:ok, :error]
end
test "handles max_entries limit reached" do
result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], max_entries: 1, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "handles zero max_entries" do
result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], max_entries: 0, timeout: 100)
# Zero max_entries should return empty results immediately
assert elem(result, 0) in [:ok, :error]
end
test "handles very large max_repetitions" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], max_repetitions: 1000, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "handles zero max_repetitions" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], max_repetitions: 0, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "handles single OID result" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0], max_repetitions: 1, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
end
describe "get_bulk/3 with non_repeaters" do
test "uses non_repeaters for multiple OIDs" do
# When retrieving multiple OIDs, non_repeaters controls how many are scalar
result =
Bulk.get_bulk("192.168.1.1", [[1, 3, 6, 1, 2, 1, 1], [1, 3, 6, 1, 2, 1, 2]], non_repeaters: 1, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
test "defaults non_repeaters to 0" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
# Should use non_repeaters: 0 by default
assert elem(result, 0) in [:ok, :error]
end
test "accepts large non_repeaters value" do
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], non_repeaters: 100, timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
end
describe "walk_bulk/3 vs get_table_bulk/3" do
test "walk_bulk and get_table_bulk produce similar results for tables" do
# Both should work for walking tables
walk_result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100)
table_result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100)
# Both should either succeed or fail, but with same error type
same_outcome =
(match?({:ok, _}, walk_result) and match?({:ok, _}, table_result)) or
(match?({:error, _}, walk_result) and match?({:error, _}, table_result))
assert same_outcome == true
end
test "walk_bulk works for non-table subtrees" do
result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)
assert elem(result, 0) in [:ok, :error]
end
end
describe "multiple options combinations" do
test "accepts all options together" do
opts = [
version: :v2c,
community: "public",
port: 161,
timeout: 100,
max_repetitions: 20,
non_repeaters: 0,
max_entries: 500
]
result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], opts)
assert elem(result, 0) in [:ok, :error]
end
test "get_bulk with all options" do
opts = [
community: "private",
port: 1161,
timeout: 3000,
max_repetitions: 15,
non_repeaters: 1
]
result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], opts)
assert elem(result, 0) in [:ok, :error]
end
test "get_table_bulk with all options" do
opts = [
community: "public",
port: 161,
timeout: 100,
max_repetitions: 25,
max_entries: 100
]
result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], opts)
assert elem(result, 0) in [:ok, :error]
end
end
describe "get_bulk_multi/2 with various request counts" do
test "handles large number of concurrent requests" do
requests =
for i <- 1..20 do
{"192.168.1.#{i}", [1, 3, 6, 1, 2, 1, 1, 1, 0]}
end
results = Bulk.get_bulk_multi(requests, timeout: 100)
assert length(results) == 20
end
test "all concurrent requests timeout consistently" do
requests =
for i <- 1..5 do
{"192.168.255.#{i}", [1, 3, 6, 1, 2, 1, 1, 1, 0]}
end
results = Bulk.get_bulk_multi(requests, timeout: 10)
assert length(results) == 5
# All should timeout or error
assert Enum.all?(results, fn result -> match?({:error, _}, result) end)
end
end
end