defmodule SnmpKit.SnmpMgr.ErrorsTest do use ExUnit.Case, async: true alias SnmpKit.SnmpMgr.Errors describe "code_to_atom/1" do test "converts standard SNMP error codes to atoms" do assert Errors.code_to_atom(0) == :no_error assert Errors.code_to_atom(1) == :too_big assert Errors.code_to_atom(2) == :no_such_name assert Errors.code_to_atom(3) == :bad_value assert Errors.code_to_atom(4) == :read_only assert Errors.code_to_atom(5) == :gen_err end test "converts SNMPv2c error codes to atoms" do assert Errors.code_to_atom(6) == :no_access assert Errors.code_to_atom(7) == :wrong_type assert Errors.code_to_atom(8) == :wrong_length assert Errors.code_to_atom(9) == :wrong_encoding assert Errors.code_to_atom(10) == :wrong_value assert Errors.code_to_atom(11) == :no_creation assert Errors.code_to_atom(12) == :inconsistent_value assert Errors.code_to_atom(13) == :resource_unavailable assert Errors.code_to_atom(14) == :commit_failed assert Errors.code_to_atom(15) == :undo_failed assert Errors.code_to_atom(16) == :authorization_error assert Errors.code_to_atom(17) == :not_writable assert Errors.code_to_atom(18) == :inconsistent_name end test "returns :unknown_error for invalid codes" do assert Errors.code_to_atom(999) == :unknown_error assert Errors.code_to_atom(-1) == :unknown_error end end describe "description/1" do test "provides descriptions for standard errors" do assert Errors.description(:no_error) == "No error occurred" assert Errors.description(:too_big) == "Response too big to fit in message" assert Errors.description(:no_such_name) == "Variable name not found" assert Errors.description(:bad_value) == "Invalid value for variable" assert Errors.description(:read_only) == "Variable is read-only" assert Errors.description(:gen_err) == "General error" end test "provides descriptions for SNMPv2c errors" do assert Errors.description(:no_access) == "Access denied" assert Errors.description(:wrong_type) == "Wrong data type" assert Errors.description(:authorization_error) == "Authorization failed" assert Errors.description(:not_writable) == "Variable not writable" end test "returns 'Unknown error' for unknown atoms" do assert Errors.description(:unknown_error) == "Unknown error" assert Errors.description(:some_random_atom) == "Unknown error" end end describe "code_to_description/1" do test "converts code directly to description" do assert Errors.code_to_description(2) == "Variable name not found" assert Errors.code_to_description(18) == "Inconsistent name" assert Errors.code_to_description(5) == "General error" end test "handles unknown codes" do assert Errors.code_to_description(999) == "Unknown error" end end describe "v2c_error?/1" do test "returns true for SNMPv2c-specific errors" do assert Errors.v2c_error?(:no_access) == true assert Errors.v2c_error?(:wrong_type) == true assert Errors.v2c_error?(:wrong_length) == true assert Errors.v2c_error?(:authorization_error) == true assert Errors.v2c_error?(:not_writable) == true end test "returns false for SNMPv1 errors" do assert Errors.v2c_error?(:no_such_name) == false assert Errors.v2c_error?(:too_big) == false assert Errors.v2c_error?(:bad_value) == false assert Errors.v2c_error?(:read_only) == false assert Errors.v2c_error?(:gen_err) == false end test "returns false for unknown errors" do assert Errors.v2c_error?(:unknown_error) == false assert Errors.v2c_error?(:timeout) == false end end describe "format_error/1" do test "formats SNMP errors with integer codes" do result = Errors.format_error({:snmp_error, 2}) assert result == "SNMP Error (2): Variable name not found" end test "formats SNMP errors with atom codes" do result = Errors.format_error({:snmp_error, :no_such_name}) assert result == "SNMP Error: Variable name not found" end test "formats SNMPv2c errors without details" do result = Errors.format_error({:v2c_error, :no_access}) assert result == "SNMPv2c Error: Access denied" end test "formats SNMPv2c errors with details" do result = Errors.format_error({:v2c_error, :no_access, oid: "1.2.3.4"}) assert result == "SNMPv2c Error: Access denied (OID: 1.2.3.4)" end test "formats SNMPv2c errors with multiple details" do result = Errors.format_error({:v2c_error, :wrong_type, [oid: "1.2.3.4", value: "test"]}) assert result =~ "SNMPv2c Error: Wrong data type" assert result =~ "OID: 1.2.3.4" assert result =~ "Value: \"test\"" end test "formats network errors" do result = Errors.format_error({:network_error, :timeout}) assert result == "Network Error: :timeout" end test "formats timeout errors" do result = Errors.format_error({:timeout, nil}) assert result == "Timeout Error: Request timed out" end test "formats encoding errors" do result = Errors.format_error({:encoding_error, "bad data"}) assert result == "Encoding Error: \"bad data\"" end test "formats decoding errors" do result = Errors.format_error({:decoding_error, "corrupt packet"}) assert result == "Decoding Error: \"corrupt packet\"" end test "formats unknown errors" do result = Errors.format_error(:some_random_error) assert result == "Unknown Error: :some_random_error" end end describe "recoverable?/1" do test "returns false for unrecoverable network errors" do assert Errors.recoverable?({:network_error, :host_unreachable}) == false assert Errors.recoverable?({:network_error, :network_unreachable}) == false end test "returns false for permanent SNMP errors" do assert Errors.recoverable?({:snmp_error, :no_such_name}) == false assert Errors.recoverable?({:snmp_error, :bad_value}) == false assert Errors.recoverable?({:snmp_error, :read_only}) == false end test "returns false for permanent SNMPv2c errors" do assert Errors.recoverable?({:v2c_error, :no_access}) == false assert Errors.recoverable?({:v2c_error, :not_writable}) == false assert Errors.recoverable?({:v2c_error, :wrong_type}) == false end test "returns true for recoverable errors" do assert Errors.recoverable?(:timeout) == true assert Errors.recoverable?({:snmp_error, :too_big}) == true assert Errors.recoverable?({:snmp_error, :gen_err}) == true end test "returns false for unknown errors" do assert Errors.recoverable?(:unknown_error) == false assert Errors.recoverable?({:custom_error, "test"}) == false end end describe "analyze_error/1" do test "analyzes SNMP protocol errors with integer codes" do result = Errors.analyze_error({:snmp_error, 2}) assert result.type == :snmp_protocol assert result.atom == :no_such_name assert result.code == 2 assert result.severity == :error assert result.retriable == false assert result.category == :user_error assert result.description == "Variable name not found" end test "analyzes SNMP protocol errors with atom codes" do result = Errors.analyze_error({:snmp_error, :too_big}) assert result.type == :snmp_protocol assert result.atom == :too_big assert result.code == 1 assert result.retriable == true assert result.category == :resource_error end test "analyzes network timeout errors" do result = Errors.analyze_error(:timeout) assert result.type == :network assert result.atom == :timeout assert result.retriable == true assert result.category == :transient_error assert result.description == "Operation timed out" end test "analyzes host unreachable errors" do result = Errors.analyze_error(:host_unreachable) assert result.type == :network assert result.atom == :host_unreachable assert result.retriable == false assert result.category == :configuration_error end test "analyzes connection refused errors" do result = Errors.analyze_error(:connection_refused) assert result.type == :network assert result.retriable == false assert result.category == :service_error end test "analyzes unknown errors" do result = Errors.analyze_error({:custom_error, "test"}) assert result.type == :other assert result.retriable == false # classify_error returns a tuple for unknown errors, but as subcategory assert result.category == :unknown_error assert result.subcategory == {:custom_error, "test"} end end describe "classify_error/2" do test "classifies user errors" do assert Errors.classify_error({:snmp_error, :no_such_name}) == {:user_error, :invalid_oid} assert Errors.classify_error({:snmp_error, :bad_value}) == {:user_error, :invalid_value} assert Errors.classify_error({:snmp_error, :read_only}) == {:user_error, :write_to_readonly} assert Errors.classify_error({:v2c_error, :wrong_type}) == {:user_error, :type_mismatch} end test "classifies security errors" do assert Errors.classify_error({:v2c_error, :no_access}) == {:security_error, :access_denied} assert Errors.classify_error({:v2c_error, :authorization_error}) == {:security_error, :authorization_failed} end test "classifies device errors" do assert Errors.classify_error({:snmp_error, :gen_err}) == {:device_error, :general_failure} assert Errors.classify_error({:v2c_error, :resource_unavailable}) == {:device_error, :resource_exhausted} end test "classifies recoverable errors" do assert Errors.classify_error({:snmp_error, :too_big}) == {:recoverable_error, :response_too_large} end test "classifies network errors" do assert Errors.classify_error({:network_error, :timeout}) == {:transient_error, :network_timeout} assert Errors.classify_error({:network_error, :host_unreachable}) == {:configuration_error, :unreachable_host} assert Errors.classify_error({:network_error, :network_unreachable}) == {:configuration_error, :network_unavailable} end test "classifies protocol errors" do assert Errors.classify_error({:encoding_error, "bad data"}) == {:protocol_error, :message_encoding_failed} assert Errors.classify_error({:decoding_error, "corrupt"}) == {:protocol_error, :message_decoding_failed} end test "classifies validation errors" do assert Errors.classify_error(:invalid_oid_values) == :validation_error assert Errors.classify_error({:snmp_encoding_error, "test"}) == :validation_error assert Errors.classify_error(:encoding_failed) == :validation_error end test "classifies authentication errors" do assert Errors.classify_error(:authentication_error) == :authentication_error assert Errors.classify_error(:invalid_community) == :invalid_community assert Errors.classify_error(:bad_community) == :invalid_community end test "classifies system errors" do assert Errors.classify_error(:snmp_modules_not_available) == :system_error end test "classifies timeout errors" do assert Errors.classify_error(:timeout) == {:transient_error, :operation_timeout} end test "accepts optional context parameter" do # Context doesn't change behavior but is accepted assert Errors.classify_error({:snmp_error, :no_such_name}, "Get request") == {:user_error, :invalid_oid} end test "classifies unknown errors" do assert Errors.classify_error(:unknown_atom) == {:unknown_error, :unknown_atom} assert Errors.classify_error({:custom, "test"}) == {:unknown_error, {:custom, "test"}} end end describe "format_user_friendly_error/2" do test "formats SNMP errors with context" do assert Errors.format_user_friendly_error({:snmp_error, :no_such_name}, "Getting sysDescr") == "Getting sysDescr failed: The requested OID does not exist on the device" assert Errors.format_user_friendly_error({:snmp_error, :bad_value}, "Setting value") == "Setting value failed: The provided value is invalid for this OID" end test "formats network errors with context" do assert Errors.format_user_friendly_error({:network_error, :timeout}, "Polling device") == "Polling device failed: The device did not respond within the timeout period" assert Errors.format_user_friendly_error( {:network_error, :host_unreachable}, "Connecting" ) == "Connecting failed: Cannot reach the device. Check the IP address and network connectivity" end test "formats timeout errors" do assert Errors.format_user_friendly_error(:timeout, "Discovery") == "Discovery timed out: The operation took too long to complete" end test "uses default context when not provided" do assert Errors.format_user_friendly_error({:snmp_error, :no_such_name}) == "Operation failed: The requested OID does not exist on the device" end test "formats encoding/decoding errors" do assert Errors.format_user_friendly_error({:encoding_error, "test"}, "Sending request") == "Sending request failed: Unable to encode the SNMP message" assert Errors.format_user_friendly_error({:decoding_error, "test"}, "Reading response") == "Reading response failed: Unable to decode the SNMP response" end test "formats unknown errors" do result = Errors.format_user_friendly_error({:custom_error, "test"}, "Testing") assert result =~ "Testing failed:" end end describe "get_recovery_suggestions/1" do test "provides suggestions for no_such_name errors" do suggestions = Errors.get_recovery_suggestions({:snmp_error, :no_such_name}) assert "Verify the OID is correct" in suggestions assert "Check if the OID is supported by this device" in suggestions assert "Try using MIB browser to explore available OIDs" in suggestions assert "Ensure you're using the correct SNMP version" in suggestions end test "provides suggestions for bad_value errors" do suggestions = Errors.get_recovery_suggestions({:snmp_error, :bad_value}) assert "Check the value format and type" in suggestions assert "Verify the value is within acceptable range" in suggestions end test "provides suggestions for read_only errors" do suggestions = Errors.get_recovery_suggestions({:snmp_error, :read_only}) assert "This OID cannot be modified" in suggestions assert "Use GET operation instead of SET" in suggestions end test "provides suggestions for too_big errors" do suggestions = Errors.get_recovery_suggestions({:snmp_error, :too_big}) assert "Reduce the number of OIDs in the request" in suggestions assert "Use GETBULK operation with smaller max-repetitions" in suggestions assert "Split the request into multiple smaller requests" in suggestions end test "provides suggestions for access errors" do suggestions = Errors.get_recovery_suggestions({:v2c_error, :no_access}) assert "Verify the community string is correct" in suggestions assert "Check device SNMP access configuration" in suggestions end test "provides suggestions for timeout errors" do suggestions = Errors.get_recovery_suggestions({:network_error, :timeout}) assert "Increase timeout value" in suggestions assert "Check network connectivity to the device" in suggestions assert "Verify device is responding to ping" in suggestions end test "provides suggestions for host unreachable errors" do suggestions = Errors.get_recovery_suggestions({:network_error, :host_unreachable}) assert "Verify the IP address is correct" in suggestions assert "Check network routing" in suggestions assert "Test basic connectivity with ping" in suggestions end test "provides suggestions for encoding errors" do suggestions = Errors.get_recovery_suggestions({:encoding_error, "test"}) assert "Check OID format" in suggestions assert "Verify value types are supported" in suggestions end test "provides suggestions for decoding errors" do suggestions = Errors.get_recovery_suggestions({:decoding_error, "test"}) assert "Device may not support SNMP" in suggestions assert "Check SNMP version compatibility" in suggestions end test "provides suggestions for timeout atom" do suggestions = Errors.get_recovery_suggestions(:timeout) assert "Increase operation timeout" in suggestions assert "Check if operation is too complex" in suggestions end test "provides generic suggestions for unknown errors" do suggestions = Errors.get_recovery_suggestions({:custom_error, "test"}) assert "Check device documentation" in suggestions assert "Verify SNMP configuration" in suggestions assert "Contact device vendor support" in suggestions end end end