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