towerops/test/snmpkit/snmp_lib/mib/error_test.exs
2026-06-15 11:15:46 -05:00

441 lines
13 KiB
Elixir

defmodule SnmpKit.SnmpLib.MIB.ErrorTest do
use ExUnit.Case, async: true
alias SnmpKit.SnmpLib.MIB.Error
doctest Error
describe "new/2" do
test "creates error with type and message" do
error = Error.new(:syntax_error)
assert error.type == :syntax_error
assert error.message == "Syntax error"
assert error.line == nil
assert error.column == nil
assert error.context == %{}
assert error.suggestions == []
end
test "creates error with line and column" do
error = Error.new(:semantic_error, line: 42, column: 10)
assert error.type == :semantic_error
assert error.line == 42
assert error.column == 10
end
test "creates error with context" do
context = %{file: "test.mib", token: "ACCESS"}
error = Error.new(:type_error, context: context)
assert error.context == context
end
test "generates message for unexpected_token error" do
error = Error.new(:unexpected_token, expected: :max_access, actual: :access)
assert error.message =~ "Expected"
assert error.message =~ "max_access"
assert error.message =~ "access"
end
test "generates message for unexpected_token with value" do
error = Error.new(:unexpected_token, expected: :semicolon, actual: :identifier, value: "test")
assert error.message =~ "test"
end
test "generates message for unexpected_eof" do
error = Error.new(:unexpected_eof, expected: "closing brace")
assert error.message == "Unexpected end of file, expected closing brace"
end
test "generates message for unterminated_string" do
error = Error.new(:unterminated_string)
assert error.message == "Unterminated string literal"
end
test "generates message for invalid_number" do
error = Error.new(:invalid_number, value: "12.34.56")
assert error.message == "Invalid number format: '12.34.56'"
end
test "generates message for invalid_identifier" do
error = Error.new(:invalid_identifier, value: "1invalid")
assert error.message == "Invalid identifier: '1invalid'"
end
test "generates message for file_not_found" do
error = Error.new(:file_not_found, file: "/path/to/missing.mib")
assert error.message == "File not found: /path/to/missing.mib"
end
test "generates message for import_error" do
error = Error.new(:import_error, symbol: "sysDescr", from_module: "SNMPv2-MIB")
assert error.message == "Cannot import 'sysDescr' from 'SNMPv2-MIB'"
end
test "generates message for duplicate_definition" do
error = Error.new(:duplicate_definition, name: "testObject")
assert error.message == "Duplicate definition of 'testObject'"
end
test "generates generic message for unknown error type" do
error = Error.new(:constraint_error)
assert error.message == "Constraint error"
end
test "generates suggestions for unexpected_token MAX-ACCESS" do
error = Error.new(:unexpected_token, expected: :max_access, actual: :access)
refute Enum.empty?(error.suggestions)
assert Enum.any?(error.suggestions, &String.contains?(&1, "MAX-ACCESS"))
end
test "generates suggestions for unexpected_token mandatory/current" do
error = Error.new(:unexpected_token, expected: :current, actual: :mandatory)
refute Enum.empty?(error.suggestions)
assert Enum.any?(error.suggestions, &String.contains?(&1, "deprecated"))
end
test "generates suggestions for unexpected_token OBJECT-TYPE" do
error = Error.new(:unexpected_token, expected: :object_type, actual: :object_identity)
refute Enum.empty?(error.suggestions)
assert Enum.any?(error.suggestions, &String.contains?(&1, "OBJECT-TYPE"))
end
test "generates suggestions for unterminated_string" do
error = Error.new(:unterminated_string)
refute Enum.empty?(error.suggestions)
assert Enum.any?(error.suggestions, &String.contains?(&1, "quote"))
end
test "generates suggestions for file_not_found with RFC file" do
error = Error.new(:file_not_found, file: "RFC1213-MIB.txt")
refute Enum.empty?(error.suggestions)
assert Enum.any?(error.suggestions, &String.contains?(&1, "naming conventions"))
end
test "generates suggestions for file_not_found with MIB file" do
error = Error.new(:file_not_found, file: "TEST-MIB.mib")
refute Enum.empty?(error.suggestions)
assert Enum.any?(error.suggestions, &String.contains?(&1, "extension"))
end
test "generates suggestions for file_not_found without RFC/MIB" do
error = Error.new(:file_not_found, file: "/random/file.txt")
assert length(error.suggestions) == 1
assert List.first(error.suggestions) =~ "file path is correct"
end
test "generates suggestions for import_error" do
error = Error.new(:import_error, symbol: "test", from_module: "TEST-MIB")
refute Enum.empty?(error.suggestions)
assert Enum.any?(error.suggestions, &String.contains?(&1, "import"))
end
test "generates no suggestions for unknown error types" do
error = Error.new(:semantic_error)
assert error.suggestions == []
end
end
describe "format/2" do
test "formats error without location" do
error = Error.new(:syntax_error)
formatted = Error.format(error)
assert formatted =~ "Syntax error"
end
test "formats error with line only" do
error = Error.new(:type_error, line: 42)
formatted = Error.format(error)
assert formatted =~ "Line 42"
end
test "formats error with column only" do
error = Error.new(:semantic_error, column: 10)
formatted = Error.format(error)
assert formatted =~ "Column 10"
end
test "formats error with line and column" do
error = Error.new(:unexpected_token, expected: :semicolon, actual: :identifier, line: 42, column: 10)
formatted = Error.format(error)
assert formatted =~ "Line 42, column 10"
assert formatted =~ "Expected"
end
test "formats error with suggestions" do
error = Error.new(:unterminated_string, line: 5)
formatted = Error.format(error)
assert formatted =~ "Suggestions:"
assert formatted =~ "quote"
end
test "formats error without suggestions" do
error = Error.new(:semantic_error, line: 10)
formatted = Error.format(error)
refute formatted =~ "Suggestions:"
end
test "formats multiple suggestions" do
error = Error.new(:import_error, symbol: "test", from_module: "TEST")
formatted = Error.format(error)
assert formatted =~ "Suggestions:"
# Count bullets
bullet_count = formatted |> String.split("") |> length() |> Kernel.-(1)
assert bullet_count == length(error.suggestions)
end
test "accepts format options" do
error = Error.new(:syntax_error, line: 1)
# Should not crash with options
assert is_binary(Error.format(error, color: true)) and byte_size(Error.format(error, color: true)) > 0
assert is_binary(Error.format(error, verbose: true)) and byte_size(Error.format(error, verbose: true)) > 0
assert is_binary(Error.format(error, [])) and byte_size(Error.format(error, [])) > 0
end
test "formats unexpected_token error with custom message" do
error = Error.new(:unexpected_token, message: "Custom error message", line: 5)
formatted = Error.format(error)
assert formatted =~ "Custom error message"
assert formatted =~ "Line 5"
end
test "formats unexpected_token with nil values" do
error = Error.new(:unexpected_token, expected: nil, actual: nil)
formatted = Error.format(error)
assert byte_size(formatted) > 0
assert formatted =~ "unknown"
end
test "formats unexpected_token with empty string values" do
error = Error.new(:unexpected_token, expected: "", actual: "")
formatted = Error.format(error)
assert byte_size(formatted) > 0
assert formatted =~ "unknown"
end
test "formats unexpected_token with end of input" do
error = Error.new(:unexpected_token, expected: :semicolon, actual: "")
formatted = Error.format(error)
# Empty string actual is converted to "unknown" by format_token_value
assert formatted =~ "unknown"
end
test "formats unexpected_token with nil actual" do
error = Error.new(:unexpected_token, expected: :identifier, actual: nil)
formatted = Error.format(error)
# nil actual is converted to "unknown" by format_token_value
assert formatted =~ "unknown"
end
test "formats unexpected_token with various atom values" do
error = Error.new(:unexpected_token, expected: :keyword, actual: :identifier)
formatted = Error.format(error)
assert formatted =~ "keyword"
assert formatted =~ "identifier"
end
test "formats unexpected_token with binary values" do
error = Error.new(:unexpected_token, expected: "semicolon", actual: "comma")
formatted = Error.format(error)
assert formatted =~ "semicolon"
assert formatted =~ "comma"
end
test "formats unexpected_token with mixed value types" do
error = Error.new(:unexpected_token, expected: :keyword, actual: "string")
formatted = Error.format(error)
assert formatted =~ "keyword"
assert formatted =~ "string"
end
test "formats unexpected_token with non-standard values" do
error = Error.new(:unexpected_token, expected: 123, actual: [:list])
formatted = Error.format(error)
# Should use inspect for non-atom, non-binary values
assert byte_size(formatted) > 0
end
end
describe "error struct" do
test "has correct structure" do
error = %Error{
type: :syntax_error,
message: "test",
line: 1,
column: 2,
context: %{key: "value"},
suggestions: ["suggestion"]
}
assert error.type == :syntax_error
assert error.message == "test"
assert error.line == 1
assert error.column == 2
assert error.context == %{key: "value"}
assert error.suggestions == ["suggestion"]
end
test "suggestions default to empty list" do
error = %Error{type: :test, message: "test"}
assert error.suggestions == []
end
end
describe "edge cases" do
test "handles nil options gracefully" do
error = Error.new(:syntax_error, nil)
assert error.type == :syntax_error
assert is_binary(error.message) and byte_size(error.message) > 0
end
test "handles empty options list" do
error = Error.new(:semantic_error, [])
assert error.type == :semantic_error
assert error.context == %{}
end
test "handles error with all fields nil" do
error = Error.new(:type_error, line: nil, column: nil, context: nil)
formatted = Error.format(error)
assert byte_size(formatted) > 0
end
test "generates messages for all supported error types" do
error_types = [
:syntax_error,
:semantic_error,
:import_error,
:type_error,
:constraint_error,
:duplicate_definition,
:file_not_found,
:unterminated_string,
:unexpected_token,
:unexpected_eof,
:invalid_number,
:invalid_identifier
]
for type <- error_types do
error = Error.new(type)
assert byte_size(error.message) > 0
end
end
test "handles context with various data types" do
contexts = [
%{string: "value"},
%{integer: 42},
%{atom: :test},
%{list: [1, 2, 3]},
%{nested: %{key: "value"}},
%{}
]
for context <- contexts do
error = Error.new(:test_error, context: context)
assert error.context == context
end
end
test "handles line and column edge values" do
# Large line numbers
error1 = Error.new(:test, line: 999_999)
assert Error.format(error1) =~ "999999"
# Zero line/column
error2 = Error.new(:test, line: 0, column: 0)
formatted2 = Error.format(error2)
assert formatted2 =~ "Line 0"
assert formatted2 =~ "column 0"
# Negative values (shouldn't happen but should handle)
error3 = Error.new(:test, line: -1, column: -5)
assert byte_size(Error.format(error3)) > 0
end
end
describe "message generation variations" do
test "unexpected_token with only expected" do
error = Error.new(:unexpected_token, expected: :semicolon)
assert error.message =~ "semicolon"
end
test "unexpected_token with only actual" do
error = Error.new(:unexpected_token, actual: :comma)
assert error.message =~ "comma"
end
test "unexpected_token with all fields" do
error =
Error.new(:unexpected_token,
expected: :semicolon,
actual: :comma,
value: ",",
line: 10,
column: 5
)
assert error.message =~ "semicolon"
assert error.message =~ "comma"
assert error.message =~ ","
end
test "file_not_found without file option" do
error = Error.new(:file_not_found)
assert is_binary(error.message) and byte_size(error.message) > 0
end
test "import_error without options" do
error = Error.new(:import_error)
assert is_binary(error.message) and byte_size(error.message) > 0
end
test "duplicate_definition without name" do
error = Error.new(:duplicate_definition)
assert is_binary(error.message) and byte_size(error.message) > 0
end
end
end