defmodule SnmpKit.SnmpMgrIntegrationTest do use ExUnit.Case, async: false alias SnmpKit.SnmpMgr @moduletag :integration @moduletag :snmpkit describe "SnmpMgr get/3" do test "successfully retrieves sysUpTime from localhost simulator" do target = "127.0.0.1" oid = "1.3.6.1.2.1.1.3.0" opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] case SnmpMgr.get(target, oid, opts) do {:ok, value} -> assert is_tuple(value) {type, _data} = value assert type == :timeticks {:error, :timeout} -> # Simulator not running, skip test :ok {:error, reason} -> flunk("Unexpected error: #{inspect(reason)}") end end test "handles string OID format" do target = "127.0.0.1" oid = "1.3.6.1.2.1.1.1.0" opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] result = SnmpMgr.get(target, oid, opts) assert match?({:ok, _}, result) or match?({:error, :timeout}, result) end test "handles list OID format" do target = "127.0.0.1" oid = [1, 3, 6, 1, 2, 1, 1, 1, 0] opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] result = SnmpMgr.get(target, oid, opts) assert match?({:ok, _}, result) or match?({:error, :timeout}, result) end test "returns error for invalid OID" do target = "127.0.0.1" oid = "1.3.6.1.99.99.99.99" opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] case SnmpMgr.get(target, oid, opts) do {:error, _reason} -> :ok {:ok, _} -> flunk("Expected error for invalid OID") end end test "handles timeout gracefully" do target = "192.0.2.1" # RFC 5737 TEST-NET-1 address that should not route oid = "1.3.6.1.2.1.1.1.0" opts = [community: "public", version: :v2c, port: 161, timeout: 1000] assert {:error, :timeout} = SnmpMgr.get(target, oid, opts) end test "returns error for wrong community string" do target = "127.0.0.1" oid = "1.3.6.1.2.1.1.1.0" opts = [community: "wrong_community", version: :v2c, port: 1161, timeout: 5000] case SnmpMgr.get(target, oid, opts) do {:error, _reason} -> :ok {:ok, _} -> # Simulator may allow any community :ok end end end describe "SnmpMgr walk/3" do test "successfully walks interface table" do target = "127.0.0.1" oid = "1.3.6.1.2.1.2.2.1.2" opts = [community: "public", version: :v2c, port: 1161, timeout: 10_000] case SnmpMgr.walk(target, oid, opts) do {:ok, results} -> assert is_list(results) case results do [first | _rest] -> # Verify structure of results assert is_map(first) assert Map.has_key?(first, :oid) assert Map.has_key?(first, :value) [] -> :ok end {:error, :timeout} -> # Simulator not running :ok {:error, reason} -> # Other errors are acceptable (no such object, etc.) assert is_atom(reason) end end test "returns empty list for non-existent subtree" do target = "127.0.0.1" oid = "1.3.6.1.99.99.99" opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] case SnmpMgr.walk(target, oid, opts) do {:ok, results} -> assert is_list(results) assert results == [] {:error, _reason} -> # Error is also acceptable :ok end end test "handles walk timeout" do target = "192.0.2.1" oid = "1.3.6.1.2.1.2.2.1" opts = [community: "public", version: :v2c, port: 161, timeout: 1000] assert {:error, :timeout} = SnmpMgr.walk(target, oid, opts) end test "walk result format is consistent" do target = "127.0.0.1" oid = "1.3.6.1.2.1.1" opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] case SnmpMgr.walk(target, oid, opts) do {:ok, results} when is_list(results) -> # All results should be maps with :oid and :value keys for result <- results do assert is_map(result) assert Map.has_key?(result, :oid) assert Map.has_key?(result, :value) assert is_binary(result.oid) or is_list(result.oid) end {:error, :timeout} -> :ok {:error, _} -> :ok end end end describe "SnmpMgr get_bulk/3" do test "successfully performs get_bulk operation" do target = "127.0.0.1" oid = "1.3.6.1.2.1.2.2.1.10" opts = [ community: "public", version: :v2c, port: 1161, timeout: 5000, max_repetitions: 10 ] case SnmpMgr.get_bulk(target, oid, opts) do {:ok, results} -> assert is_list(results) case results do [first | _rest] -> assert is_map(first) assert Map.has_key?(first, :oid) assert Map.has_key?(first, :value) [] -> :ok end {:error, :timeout} -> :ok {:error, reason} -> assert is_atom(reason) end end test "respects max_repetitions parameter" do target = "127.0.0.1" oid = "1.3.6.1.2.1.2.2.1" opts = [ community: "public", version: :v2c, port: 1161, timeout: 5000, max_repetitions: 5 ] case SnmpMgr.get_bulk(target, oid, opts) do {:ok, results} -> # Should get at most max_repetitions results per column assert is_list(results) {:error, :timeout} -> :ok {:error, _} -> :ok end end test "get_bulk only works with SNMPv2c" do target = "127.0.0.1" oid = "1.3.6.1.2.1.2.2.1.10" # Try with v1 - should not support get_bulk opts_v1 = [community: "public", version: :v1, port: 1161, timeout: 5000] case SnmpMgr.get_bulk(target, oid, opts_v1) do {:error, _reason} -> # Expected - v1 doesn't support get_bulk :ok {:ok, _} -> # Some implementations may allow it or fall back :ok end # Try with v2c - should work opts_v2c = [community: "public", version: :v2c, port: 1161, timeout: 5000] case SnmpMgr.get_bulk(target, oid, opts_v2c) do {:ok, results} -> assert is_list(results) {:error, :timeout} -> :ok {:error, _} -> :ok end end end describe "SnmpMgr with different versions" do test "SNMPv1 get operation" do target = "127.0.0.1" oid = "1.3.6.1.2.1.1.3.0" opts = [community: "public", version: :v1, port: 1161, timeout: 5000] result = SnmpMgr.get(target, oid, opts) assert match?({:ok, _}, result) or match?({:error, _}, result) end test "SNMPv2c get operation" do target = "127.0.0.1" oid = "1.3.6.1.2.1.1.3.0" opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] result = SnmpMgr.get(target, oid, opts) assert match?({:ok, _}, result) or match?({:error, _}, result) end end describe "SnmpMgr error handling" do test "handles network errors gracefully" do target = "192.0.2.1" oid = "1.3.6.1.2.1.1.1.0" opts = [community: "public", version: :v2c, port: 161, timeout: 1000] assert {:error, :timeout} = SnmpMgr.get(target, oid, opts) end test "handles invalid target format" do target = "not-a-valid-ip" oid = "1.3.6.1.2.1.1.1.0" opts = [community: "public", version: :v2c, port: 161, timeout: 1000] case SnmpMgr.get(target, oid, opts) do {:error, _reason} -> :ok {:ok, _} -> :ok # Some systems may resolve hostnames end end test "handles malformed OID" do target = "127.0.0.1" # Invalid OID format oid = "not.an.oid" opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] case SnmpMgr.get(target, oid, opts) do {:error, _reason} -> # Expected :ok {:ok, _} -> # Some parsers may handle this :ok end end end describe "SnmpMgr real-world scenarios" do test "retrieve system information bundle" do target = "127.0.0.1" opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] system_oids = [ "1.3.6.1.2.1.1.1.0", # sysDescr "1.3.6.1.2.1.1.2.0", # sysObjectID "1.3.6.1.2.1.1.3.0", # sysUpTime "1.3.6.1.2.1.1.4.0", # sysContact "1.3.6.1.2.1.1.5.0", # sysName "1.3.6.1.2.1.1.6.0" # sysLocation ] # Try to get all system info results = Enum.map(system_oids, fn oid -> SnmpMgr.get(target, oid, opts) end) # At least some should succeed (if simulator running) or all timeout assert Enum.all?(results, fn result -> match?({:ok, _}, result) or match?({:error, _}, result) end) end test "walk and count interfaces" do target = "127.0.0.1" oid = "1.3.6.1.2.1.2.2.1.1" # ifIndex opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] case SnmpMgr.walk(target, oid, opts) do {:ok, results} -> interface_count = length(results) assert interface_count >= 0 {:error, :timeout} -> :ok {:error, _} -> :ok end end test "retrieve interface statistics" do target = "127.0.0.1" opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] # Common interface stats OIDs stats_oids = [ "1.3.6.1.2.1.2.2.1.10.1", # ifInOctets for interface 1 "1.3.6.1.2.1.2.2.1.16.1", # ifOutOctets for interface 1 "1.3.6.1.2.1.2.2.1.8.1" # ifOperStatus for interface 1 ] results = Enum.map(stats_oids, fn oid -> SnmpMgr.get(target, oid, opts) end) assert Enum.all?(results, fn result -> match?({:ok, _}, result) or match?({:error, _}, result) end) end end describe "SnmpMgr performance and reliability" do test "handles multiple concurrent requests" do target = "127.0.0.1" oid = "1.3.6.1.2.1.1.3.0" opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] tasks = for _ <- 1..5 do Task.async(fn -> SnmpMgr.get(target, oid, opts) end) end results = Task.await_many(tasks, 10_000) # All requests should complete assert length(results) == 5 # All should either succeed or have consistent errors assert Enum.all?(results, fn result -> match?({:ok, _}, result) or match?({:error, _}, result) end) end test "respects timeout parameter" do target = "192.0.2.1" oid = "1.3.6.1.2.1.1.1.0" opts = [community: "public", version: :v2c, port: 161, timeout: 500] start_time = System.monotonic_time(:millisecond) result = SnmpMgr.get(target, oid, opts) end_time = System.monotonic_time(:millisecond) assert {:error, :timeout} = result # Should timeout around the specified timeout value duration = end_time - start_time # Allow some overhead assert duration < 2000 end test "handles rapid sequential requests" do target = "127.0.0.1" oid = "1.3.6.1.2.1.1.3.0" opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] results = for _ <- 1..10 do SnmpMgr.get(target, oid, opts) end # All should complete assert length(results) == 10 # All should have consistent results assert Enum.all?(results, fn result -> match?({:ok, _}, result) or match?({:error, _}, result) end) end end describe "SnmpMgr data type handling" do test "correctly handles timeticks values" do target = "127.0.0.1" oid = "1.3.6.1.2.1.1.3.0" # sysUpTime opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] case SnmpMgr.get(target, oid, opts) do {:ok, value} -> assert is_tuple(value) assert elem(value, 0) == :timeticks {:error, :timeout} -> :ok {:error, _} -> :ok end end test "correctly handles octet string values" do target = "127.0.0.1" oid = "1.3.6.1.2.1.1.1.0" # sysDescr opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] case SnmpMgr.get(target, oid, opts) do {:ok, value} -> assert is_tuple(value) # Could be :octet_string type = elem(value, 0) assert is_atom(type) {:error, :timeout} -> :ok {:error, _} -> :ok end end test "correctly handles integer values" do target = "127.0.0.1" oid = "1.3.6.1.2.1.2.2.1.8.1" # ifOperStatus opts = [community: "public", version: :v2c, port: 1161, timeout: 5000] case SnmpMgr.get(target, oid, opts) do {:ok, value} -> assert is_tuple(value) or is_integer(value) {:error, :timeout} -> :ok {:error, _} -> :ok end end end end