449 lines
17 KiB
Elixir
449 lines
17 KiB
Elixir
defmodule SnmpKit.SnmpMgr.ErrorsTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias SnmpKit.SnmpMgr.Errors
|
|
|
|
doctest 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
|