441 lines
13 KiB
Elixir
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
|