defmodule SnmpKit.SnmpLib.WalkerTest do use ExUnit.Case, async: true alias SnmpKit.SnmpLib.Walker doctest Walker @moduletag :walker_test describe "Walker.walk_table/3" do test "selects appropriate walking strategy based on SNMP version" do # v2c should use bulk walking assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], version: :v2c, timeout: 100 ) # v1 should use sequential walking assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], version: :v1, timeout: 100 ) end test "handles table OID normalization" do # List OID list_oid = [1, 3, 6, 1, 2, 1, 2, 2] assert {:error, _} = Walker.walk_table("invalid.host.test", list_oid, timeout: 100) # String OID string_oid = "1.3.6.1.2.1.2.2" assert {:error, _} = Walker.walk_table("invalid.host.test", string_oid, timeout: 100) end test "validates walking options" do opts = [ community: "public", version: :v2c, timeout: 5000, max_repetitions: 25, max_retries: 3, retry_delay: 1000 ] assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], opts) end test "handles bulk size configuration" do # Small bulk size small_opts = [max_repetitions: 5, timeout: 100] assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], small_opts) # Large bulk size large_opts = [max_repetitions: 100, timeout: 100] assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], large_opts) end end describe "Walker.walk_subtree/3" do test "walks entire subtree under base OID" do # system subtree base_oid = [1, 3, 6, 1, 2, 1, 1] assert {:error, _} = Walker.walk_subtree("192.168.255.254", base_oid, timeout: 50) end test "handles subtree boundary detection" do # Should stop when OIDs no longer match prefix assert {:error, _} = Walker.walk_subtree("192.168.255.254", [1, 3, 6, 1], timeout: 50) end test "uses appropriate strategy for subtree walking" do opts_v1 = [version: :v1, timeout: 100] opts_v2c = [version: :v2c, timeout: 100] # Both should attempt but fail due to invalid host assert {:error, _} = Walker.walk_subtree("invalid.host.test", [1, 3, 6, 1], opts_v1) assert {:error, _} = Walker.walk_subtree("invalid.host.test", [1, 3, 6, 1], opts_v2c) end end describe "Walker.stream_table/3" do test "returns a stream for table walking" do stream = Walker.stream_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100) # Should be enumerable assert Enumerable.impl_for(stream) # Should handle empty results gracefully when enumerated # (will be empty due to invalid host) results = Enum.take(stream, 5) assert results == [] end test "supports streaming configuration options" do opts = [ chunk_size: 10, max_repetitions: 20, timeout: 100 ] stream = Walker.stream_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], opts) # Should create stream with custom options assert Enumerable.impl_for(stream) end test "integrates with Stream operations" do stream = Walker.stream_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100) # Should work with standard Stream operations filtered = stream # Flatten chunks |> Stream.flat_map(& &1) |> Stream.filter(fn {_oid, value} -> value != nil end) |> Stream.take(10) # Should enumerate without error (though empty due to invalid host) results = Enum.to_list(filtered) assert results == [] end end describe "Walker.walk_column/3" do test "extracts single column from table" do # Interface description column column_oid = [1, 3, 6, 1, 2, 1, 2, 2, 1, 2] assert {:error, _} = Walker.walk_column("invalid.host.test", column_oid, timeout: 100) end test "returns index-value pairs for column data" do # Test that the function signature is correct assert is_function(&Walker.walk_column/3) # Would test actual column extraction with mock data column_oid = [1, 3, 6, 1, 2, 1, 2, 2, 1, 1] assert {:error, _} = Walker.walk_column("invalid.host.test", column_oid, timeout: 100) end end describe "Walker.estimate_table_size/3" do test "estimates table size by walking first column" do table_oid = [1, 3, 6, 1, 2, 1, 2, 2] assert {:error, _} = Walker.estimate_table_size("invalid.host.test", table_oid, timeout: 100) end test "provides count estimate for planning" do # Test interface for size estimation assert is_function(&Walker.estimate_table_size/3) # Should return count when successful # routing table table_oid = [1, 3, 6, 1, 2, 1, 4, 21] assert {:error, _} = Walker.estimate_table_size("invalid.host.test", table_oid, timeout: 100) end end describe "Walker strategy selection" do test "chooses bulk walking for SNMPv2c" do opts = [version: :v2c, timeout: 100] # Should attempt bulk operations assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], opts) end test "falls back to sequential for SNMPv1" do opts = [version: :v1, timeout: 100] # Should use sequential walking assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], opts) end test "handles strategy-specific parameters" do bulk_opts = [version: :v2c, max_repetitions: 50, timeout: 100] seq_opts = [version: :v1, timeout: 100] # Both should handle appropriately assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], bulk_opts) assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], seq_opts) end end describe "Walker error handling" do test "handles network timeouts gracefully" do opts = [timeout: 20] # Should timeout quickly assert {:error, _} = Walker.walk_table("192.168.255.255", [1, 3, 6, 1], opts) end @tag :skip test "implements retry logic" do # Test with unreachable host opts = [max_retries: 1, retry_delay: 50, timeout: 20] # Should retry on failures start_time = System.monotonic_time(:millisecond) {:error, _} = Walker.walk_table("192.168.255.255", [1, 3, 6, 1], opts) end_time = System.monotonic_time(:millisecond) # Should take longer due to retries # At least one retry delay assert end_time - start_time >= 50 end test "handles table boundary conditions" do # Test with various table scenarios assert {:error, _} = Walker.walk_table("192.168.255.254", [1, 3, 6, 1], timeout: 20) assert {:error, _} = Walker.walk_subtree("192.168.255.254", [1, 3, 6, 1], timeout: 20) end test "gracefully handles empty tables" do # Should handle tables with no entries assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1, 99], timeout: 100) end end describe "Walker performance features" do test "supports adaptive bulk sizing" do opts = [adaptive_bulk: true, max_repetitions: 25, timeout: 100] # Should attempt adaptive sizing assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], opts) end test "handles concurrent walking operations" do # Test multiple concurrent table walks tasks = Enum.map(1..3, fn i -> Task.async(fn -> Walker.walk_table("invalid.host.test", [1, 3, 6, 1, i], timeout: 100) end) end) results = Task.await_many(tasks, 10_000) # All should complete (with errors due to invalid host) assert length(results) == 3 assert Enum.all?(results, fn result -> match?({:error, _}, result) end) end @tag :performance test "streaming is memory efficient" do # Create stream but don't fully enumerate stream = Walker.stream_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100) # Take just a few items (should not load entire table) partial_results = Enum.take(stream, 2) # Should handle partial enumeration gracefully assert partial_results == [] end @tag :performance test "bulk operations are more efficient than sequential" do # This would test actual performance differences # For now, verify that both strategies are available bulk_opts = [version: :v2c, max_repetitions: 50, timeout: 100] seq_opts = [version: :v1, timeout: 100] # Both should be callable assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], bulk_opts) assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], seq_opts) end end describe "Walker integration" do test "integrates with Manager for individual operations" do # Walker should use Manager for SNMP operations assert is_function(&Walker.walk_table/3) assert is_function(&Walker.estimate_table_size/3) # Should handle Manager responses appropriately assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], timeout: 100) end test "uses SnmpKit.SnmpLib.OID for OID manipulation" do # Should handle both string and list OIDs list_oid = [1, 3, 6, 1, 2, 1, 2, 2] string_oid = "1.3.6.1.2.1.2.2" # Both should work assert {:error, _} = Walker.walk_table("invalid.host.test", list_oid, timeout: 100) assert {:error, _} = Walker.walk_table("invalid.host.test", string_oid, timeout: 100) end test "handles SnmpLib error responses correctly" do # Should properly handle various SNMP error conditions # These would be tested with actual SNMP responses assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], timeout: 100) end end describe "Walker option validation" do test "validates and applies default options" do # Test with minimal options assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1]) # Test with custom options custom_opts = [ community: "private", timeout: 10_000, max_repetitions: 100, chunk_size: 50 ] assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], custom_opts) end test "handles invalid option values gracefully" do # Test with various option edge cases opts = [ # Very short timeout timeout: 1, # Very large bulk size max_repetitions: 1000, # No retries max_retries: 0 ] assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], opts) end end end