defmodule SnmpKit.SnmpLib.UtilsTest do use ExUnit.Case, async: true alias SnmpKit.SnmpLib.Utils doctest Utils describe "Utils.pretty_print_pdu/1" do test "formats basic GET request PDU" do pdu = %{ type: :get_request, request_id: 123, varbinds: [ {[1, 3, 6, 1, 2, 1, 1, 1, 0], :null} ] } result = Utils.pretty_print_pdu(pdu) assert String.contains?(result, "GET Request") assert String.contains?(result, "ID: 123") assert String.contains?(result, "1.3.6.1.2.1.1.1.0") end test "formats error response PDU" do pdu = %{ type: :get_response, request_id: 456, error_status: 2, error_index: 1, varbinds: [] } result = Utils.pretty_print_pdu(pdu) assert String.contains?(result, "Response") assert String.contains?(result, "ID: 456") assert String.contains?(result, "Error:") assert String.contains?(result, "no varbinds") end test "formats GET-BULK request PDU" do pdu = %{ type: :get_bulk_request, request_id: 789, non_repeaters: 1, max_repetitions: 10, varbinds: [ {[1, 3, 6, 1, 2, 1, 2, 2, 1, 1], :null} ] } result = Utils.pretty_print_pdu(pdu) assert String.contains?(result, "GET-BULK Request") assert String.contains?(result, "Non-repeaters: 1") assert String.contains?(result, "Max-repetitions: 10") end test "handles invalid PDU gracefully" do result = Utils.pretty_print_pdu("not a map") assert result == "Invalid PDU" end end describe "Utils.pretty_print_varbinds/1" do test "formats varbinds list" do varbinds = [ {[1, 3, 6, 1, 2, 1, 1, 1, 0], "Linux server"}, {[1, 3, 6, 1, 2, 1, 1, 3, 0], {:timeticks, 12_345}} ] result = Utils.pretty_print_varbinds(varbinds) assert String.contains?(result, "1. 1.3.6.1.2.1.1.1.0") assert String.contains?(result, "2. 1.3.6.1.2.1.1.3.0") assert String.contains?(result, "Linux server") assert String.contains?(result, "TimeTicks") end test "handles empty varbinds list" do result = Utils.pretty_print_varbinds([]) assert result == " (no varbinds)" end test "handles invalid varbinds" do result = Utils.pretty_print_varbinds("not a list") assert result == "Invalid varbinds" end end describe "Utils.pretty_print_oid/1" do test "formats OID list" do oid = [1, 3, 6, 1, 2, 1, 1, 1, 0] result = Utils.pretty_print_oid(oid) assert result == "1.3.6.1.2.1.1.1.0" end test "handles OID string" do oid = "1.3.6.1.2.1.1.1.0" result = Utils.pretty_print_oid(oid) assert result == "1.3.6.1.2.1.1.1.0" end test "handles invalid OID" do result = Utils.pretty_print_oid(123) assert result == "invalid-oid" end end describe "Utils.pretty_print_value/1" do test "formats null value" do result = Utils.pretty_print_value(:null) assert result == "NULL" end test "formats integer values" do result = Utils.pretty_print_value({:integer, 42}) assert result == "INTEGER: 42" result = Utils.pretty_print_value(42) assert result == "INTEGER: 42" end test "formats string values" do result = Utils.pretty_print_value({:octet_string, "Hello"}) assert result == ~s(OCTET STRING: "Hello") result = Utils.pretty_print_value("Hello") assert result == ~s("Hello") end test "formats binary data as hex" do binary_data = <<0x00, 0x1B, 0x21, 0x3C>> result = Utils.pretty_print_value({:octet_string, binary_data}) assert String.contains?(result, "00 1B 21 3C") result = Utils.pretty_print_value(binary_data) assert String.contains?(result, "00 1B 21 3C") end test "formats SNMP-specific types" do result = Utils.pretty_print_value({:counter32, 12_345}) assert result == "Counter32: 12,345" result = Utils.pretty_print_value({:gauge32, 67_890}) assert result == "Gauge32: 67,890" result = Utils.pretty_print_value({:timeticks, 123_456}) assert String.contains?(result, "TimeTicks: 123,456") result = Utils.pretty_print_value({:counter64, 9_876_543_210}) assert result == "Counter64: 9,876,543,210" end test "formats IP address" do result = Utils.pretty_print_value({:ip_address, <<192, 168, 1, 1>>}) assert result == "IpAddress: 192.168.1.1" end test "formats OID value" do result = Utils.pretty_print_value({:object_identifier, [1, 3, 6, 1, 2, 1, 1, 1, 0]}) assert result == "OID: 1.3.6.1.2.1.1.1.0" end test "formats SNMPv2c exception values" do result = Utils.pretty_print_value({:no_such_object, nil}) assert result == "noSuchObject" result = Utils.pretty_print_value({:no_such_instance, nil}) assert result == "noSuchInstance" result = Utils.pretty_print_value({:end_of_mib_view, nil}) assert result == "endOfMibView" end test "formats unknown values" do result = Utils.pretty_print_value({:unknown_type, "data"}) assert String.contains?(result, "unknown_type") end end describe "Utils.format_bytes/1" do test "formats bytes" do assert Utils.format_bytes(512) == "512 B" assert Utils.format_bytes(0) == "0 B" end test "formats kilobytes" do assert Utils.format_bytes(1024) == "1.0 KB" assert Utils.format_bytes(1536) == "1.5 KB" end test "formats megabytes" do assert Utils.format_bytes(1_048_576) == "1.0 MB" assert Utils.format_bytes(2_621_440) == "2.5 MB" end test "formats gigabytes" do assert Utils.format_bytes(1_073_741_824) == "1.0 GB" assert Utils.format_bytes(2_147_483_648) == "2.0 GB" end test "handles invalid input" do assert Utils.format_bytes(-1) == "Invalid byte count" assert Utils.format_bytes("not a number") == "Invalid byte count" end end describe "Utils.format_rate/2" do test "formats basic rates" do assert Utils.format_rate(100, "bps") == "100 bps" assert Utils.format_rate(45, "pps") == "45 pps" end test "formats kilo rates" do assert Utils.format_rate(1500, "bps") == "1.5 Kbps" assert Utils.format_rate(2000, "pps") == "2.0 Kpps" end test "formats mega rates" do assert Utils.format_rate(1_500_000, "bps") == "1.5 Mbps" assert Utils.format_rate(10_000_000, "Hz") == "10.0 MHz" end test "formats giga rates" do assert Utils.format_rate(1_000_000_000, "bps") == "1.0 Gbps" assert Utils.format_rate(2_500_000_000, "Hz") == "2.5 GHz" end test "handles invalid input" do assert Utils.format_rate("not a number", "bps") == "Invalid rate" assert Utils.format_rate(100, 123) == "Invalid rate" end end describe "Utils.truncate_string/2" do test "truncates long strings" do result = Utils.truncate_string("Hello, World!", 10) assert result == "Hello, ..." end test "leaves short strings unchanged" do result = Utils.truncate_string("Short", 10) assert result == "Short" end test "handles exact length" do result = Utils.truncate_string("1234567890", 10) assert result == "1234567890" end test "handles edge cases" do result = Utils.truncate_string("Test", 3) # max_length too small for ellipsis assert result == "Test" result = Utils.truncate_string("", 10) assert result == "" result = Utils.truncate_string(123, 10) assert result == "" end end describe "Utils.format_hex/2" do test "formats hex with default separator" do data = <<0x00, 0x1B, 0x21, 0x3C, 0x92, 0xEB>> result = Utils.format_hex(data) assert result == "00:1B:21:3C:92:EB" end test "formats hex with custom separator" do data = <<0xDE, 0xAD, 0xBE, 0xEF>> result = Utils.format_hex(data, " ") assert result == "DE AD BE EF" result = Utils.format_hex(data, "-") assert result == "DE-AD-BE-EF" end test "handles empty binary" do result = Utils.format_hex(<<>>) assert result == "" end test "handles single byte" do result = Utils.format_hex(<<0xFF>>) assert result == "FF" end test "handles invalid input" do result = Utils.format_hex(12_345, ":") assert result == "Invalid binary data" result = Utils.format_hex(<<0x00>>, 123) assert result == "Invalid binary data" end end describe "Utils.format_number/1" do test "formats numbers with commas" do assert Utils.format_number(1_234_567) == "1,234,567" assert Utils.format_number(12_345) == "12,345" assert Utils.format_number(1234) == "1,234" end test "handles small numbers" do assert Utils.format_number(123) == "123" assert Utils.format_number(42) == "42" assert Utils.format_number(0) == "0" end test "handles negative numbers" do assert Utils.format_number(-12_345) == "-12,345" assert Utils.format_number(-123) == "-123" end test "handles invalid input" do assert Utils.format_number("not a number") == "Invalid number" assert Utils.format_number(12.34) == "Invalid number" end end describe "Utils.measure_request_time/1" do test "measures function execution time" do {result, time} = Utils.measure_request_time(fn -> :timer.sleep(10) :test_result end) assert result == :test_result # At least 10ms in microseconds assert time >= 10_000 assert is_integer(time) end test "handles functions that return various types" do {result, time} = Utils.measure_request_time(fn -> {:ok, "success"} end) assert result == {:ok, "success"} assert is_integer(time) assert_raise RuntimeError, fn -> Utils.measure_request_time(fn -> raise "error" end) end end end describe "Utils.format_response_time/1" do test "formats microseconds" do assert Utils.format_response_time(500) == "500μs" assert Utils.format_response_time(999) == "999μs" end test "formats milliseconds" do assert Utils.format_response_time(1500) == "1.50ms" assert Utils.format_response_time(10_000) == "10.00ms" assert Utils.format_response_time(999_999) == "1000.00ms" end test "formats seconds" do assert Utils.format_response_time(1_500_000) == "1.50s" assert Utils.format_response_time(2_500_000) == "2.50s" end test "handles edge cases" do assert Utils.format_response_time(0) == "0μs" assert Utils.format_response_time(1000) == "1.00ms" assert Utils.format_response_time(1_000_000) == "1.00s" end test "handles invalid input" do assert Utils.format_response_time(-1) == "Invalid time" assert Utils.format_response_time("not a number") == "Invalid time" end end describe "Utils.valid_snmp_version?/1" do test "validates numeric versions" do assert Utils.valid_snmp_version?(0) == true assert Utils.valid_snmp_version?(1) == true assert Utils.valid_snmp_version?(2) == true assert Utils.valid_snmp_version?(3) == true end test "validates atom versions" do assert Utils.valid_snmp_version?(:v1) == true assert Utils.valid_snmp_version?(:v2c) == true assert Utils.valid_snmp_version?(:v3) == true end test "rejects invalid versions" do assert Utils.valid_snmp_version?(4) == false assert Utils.valid_snmp_version?(-1) == false assert Utils.valid_snmp_version?(:v4) == false assert Utils.valid_snmp_version?("1") == false end end describe "Utils.valid_community_string?/1" do test "validates good community strings" do assert Utils.valid_community_string?("public") == true assert Utils.valid_community_string?("private") == true assert Utils.valid_community_string?("community123") == true assert Utils.valid_community_string?("My-Community_String") == true end test "rejects invalid community strings" do assert Utils.valid_community_string?("") == false assert Utils.valid_community_string?(123) == false assert Utils.valid_community_string?(nil) == false end test "rejects non-printable characters" do # This test assumes non-printable characters would be rejected # The exact behavior depends on String.printable?/1 binary_with_null = "test\0string" assert Utils.valid_community_string?(binary_with_null) == false end end describe "Utils.sanitize_community/1" do test "sanitizes community strings" do assert Utils.sanitize_community("public") == "******" assert Utils.sanitize_community("private") == "*******" assert Utils.sanitize_community("a") == "*" end test "handles empty community" do assert Utils.sanitize_community("") == "" end test "handles invalid input" do assert Utils.sanitize_community(123) == "" assert Utils.sanitize_community(nil) == "" end end describe "integration tests" do test "pretty printing works with complex PDU" do pdu = %{ type: :get_response, request_id: 42, error_status: 0, error_index: 0, varbinds: [ {[1, 3, 6, 1, 2, 1, 1, 1, 0], "Linux server 4.19.0"}, {[1, 3, 6, 1, 2, 1, 1, 3, 0], {:timeticks, 123_456}}, {[1, 3, 6, 1, 2, 1, 2, 1, 0], {:integer, 2}}, {[1, 3, 6, 1, 2, 1, 1, 6, 0], {:object_identifier, [1, 3, 6, 1, 4, 1, 8072, 3, 2, 10]}} ] } result = Utils.pretty_print_pdu(pdu) # Should contain all the expected elements assert String.contains?(result, "Response") assert String.contains?(result, "Linux server") assert String.contains?(result, "TimeTicks") assert String.contains?(result, "INTEGER: 2") assert String.contains?(result, "OID:") end test "timing utilities work with SNMP operations" do # Simulate an SNMP operation mock_snmp_operation = fn -> # Simulate 5ms operation :timer.sleep(5) {:ok, "SNMP response"} end {result, time_us} = Utils.measure_request_time(mock_snmp_operation) formatted_time = Utils.format_response_time(time_us) assert result == {:ok, "SNMP response"} # At least 5ms in microseconds assert time_us >= 5000 assert formatted_time =~ ~r/ms|μs/ end test "data formatting works with SNMP values" do # Test formatting of various SNMP counter values interface_octets = 1_234_567_890 # About 1.4 days uptime_ticks = 12_345_600 formatted_bytes = Utils.format_bytes(interface_octets) formatted_number = Utils.format_number(interface_octets) formatted_timeticks = Utils.pretty_print_value({:timeticks, uptime_ticks}) assert formatted_bytes =~ ~r/GB|MB/ assert String.contains?(formatted_number, ",") assert formatted_timeticks =~ ~r/d|h/ end end end