test: add comprehensive tests for PDU.Constants and MIB.Logger

Added complete test coverage for two low-coverage modules:

**PDU.Constants (11.65% -> target 100%)**:
- Error status code accessors and conversions
- SNMPv3 constants (USM security model, max message size)
- PDU type constants and tag conversion (get, getnext, response, set, getbulk)
- Data type constants and tag conversion (all SNMP types)
- SNMP version normalization (:v1, :v2c, :v3)
- OID normalization (list and string formats)
- SNMPv3 message flags encoding/decoding
- Default message flags for security levels
- Error status code/atom conversion
- Roundtrip encoding/decoding verification
- 43 new tests

**MIB.Logger (11.11% -> target 100%)**:
- Compilation lifecycle logging (start, success, error, warning, complete)
- Batch compilation logging (start, progress, success, error)
- Parse progress and import resolution logging
- Code generation and tokenization statistics
- Dependency order and performance metrics
- Vendor quirk handling
- Warning logging with context
- Error type extraction and aggregation
- 40 new tests

Total: 83 new tests added
Test count: 1,128 -> 1,211 (all passing)
This commit is contained in:
Graham McIntire 2026-02-06 08:36:20 -06:00 committed by Graham McIntire
parent 35ee87952f
commit 82b4dbdf28
No known key found for this signature in database
2 changed files with 801 additions and 0 deletions

View file

@ -0,0 +1,405 @@
defmodule SnmpKit.SnmpLib.MIB.LoggerTest do
use ExUnit.Case, async: false
import ExUnit.CaptureLog
alias SnmpKit.SnmpLib.MIB.Logger, as: MibLogger
alias SnmpKit.SnmpLib.MIB.Error
describe "log_compilation_start/2" do
test "logs compilation start with options" do
opts = [
output_dir: "/tmp/mibs",
format: :binary,
optimize: true,
vendor_quirks: [:cisco]
]
capture_log(fn ->
assert :ok = MibLogger.log_compilation_start("/path/to/test.mib", opts)
end)
end
test "handles empty options" do
capture_log(fn ->
assert :ok = MibLogger.log_compilation_start("/path/to/test.mib", [])
end)
end
end
describe "log_compilation_success/2" do
test "logs successful compilation" do
capture_log(fn ->
assert :ok = MibLogger.log_compilation_success("TEST-MIB", "/tmp/mibs/TEST-MIB.bin")
end)
end
end
describe "log_compilation_error/2" do
test "logs compilation errors with error list" do
errors = [
%Error{type: :syntax_error, message: "Unexpected token"},
%Error{type: :semantic_error, message: "Undefined symbol"}
]
log =
capture_log(fn ->
assert :ok = MibLogger.log_compilation_error("TEST-MIB", errors)
end)
assert log =~ "MIB compilation failed"
end
test "logs empty error list" do
capture_log(fn ->
assert :ok = MibLogger.log_compilation_error("TEST-MIB", [])
end)
end
end
describe "log_compilation_warning/3" do
test "logs compilation with warnings" do
warnings = [
%Error{type: :deprecated_syntax, message: "Old syntax used"},
%Error{type: :naming_convention, message: "Non-standard naming"}
]
capture_log(fn ->
assert :ok =
MibLogger.log_compilation_warning("TEST-MIB", "/tmp/mibs/TEST-MIB.bin", warnings)
end)
end
test "logs with empty warnings" do
capture_log(fn ->
assert :ok = MibLogger.log_compilation_warning("TEST-MIB", "/tmp/output", [])
end)
end
end
describe "log_batch_compilation_start/1" do
test "logs batch compilation start" do
capture_log(fn ->
assert :ok = MibLogger.log_batch_compilation_start(10)
end)
end
test "handles zero file count" do
capture_log(fn ->
assert :ok = MibLogger.log_batch_compilation_start(0)
end)
end
end
describe "log_batch_compilation_success/1" do
test "logs batch compilation success" do
capture_log(fn ->
assert :ok = MibLogger.log_batch_compilation_success(8)
end)
end
end
describe "log_batch_compilation_error/2" do
test "logs batch compilation with errors" do
log =
capture_log(fn ->
assert :ok = MibLogger.log_batch_compilation_error(5, 3)
end)
assert log =~ "Batch MIB compilation completed with errors"
end
test "handles all errors" do
capture_log(fn ->
assert :ok = MibLogger.log_batch_compilation_error(0, 10)
end)
end
end
describe "log_compilation_complete/2" do
test "logs compilation complete with results" do
result = %{
objects_count: 150,
output_path: "/tmp/mibs/TEST-MIB.bin",
compilation_time: 1250
}
capture_log(fn ->
assert :ok = MibLogger.log_compilation_complete("TEST-MIB", result)
end)
end
test "handles empty result map" do
capture_log(fn ->
assert :ok = MibLogger.log_compilation_complete("TEST-MIB", %{})
end)
end
end
describe "log_compilation_failed/2" do
test "logs compilation failure with error types" do
errors = [
%Error{type: :syntax_error, message: "Error 1"},
%Error{type: :syntax_error, message: "Error 2"},
%Error{type: :semantic_error, message: "Error 3"}
]
log =
capture_log(fn ->
assert :ok = MibLogger.log_compilation_failed("/path/to/test.mib", errors)
end)
assert log =~ "MIB compilation failed"
end
test "handles errors with different formats" do
errors = [
%Error{type: :syntax_error, message: "Error"},
%{type: :custom_error},
:atom_error,
"string error"
]
capture_log(fn ->
assert :ok = MibLogger.log_compilation_failed("/path/to/test.mib", errors)
end)
end
end
describe "log_parse_progress/2" do
test "logs parsing progress" do
log =
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_parse_progress("tokenization", 1500)
end)
assert log =~ "Parse progress" or log == ""
end
test "handles zero count" do
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_parse_progress("parsing", 0)
end)
end
end
describe "log_import_resolution/2" do
test "logs import resolution" do
imported_mibs = ["SNMPv2-SMI", "SNMPv2-TC", "SNMPv2-CONF"]
log =
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_import_resolution("TEST-MIB", imported_mibs)
end)
assert log =~ "Resolving imports" or log == ""
end
test "handles empty imports" do
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_import_resolution("TEST-MIB", [])
end)
end
end
describe "log_imports_resolved/3" do
test "logs successful import resolution" do
log =
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_imports_resolved("TEST-MIB", 8, 10)
end)
assert log =~ "Imports resolved" or log == ""
end
test "handles all imports resolved" do
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_imports_resolved("TEST-MIB", 5, 5)
end)
end
test "handles zero total imports" do
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_imports_resolved("TEST-MIB", 0, 0)
end)
end
end
describe "log_codegen/3" do
test "logs code generation results" do
capture_log(fn ->
assert :ok = MibLogger.log_codegen("TEST-MIB", 100, 350)
end)
end
test "handles zero objects" do
capture_log(fn ->
assert :ok = MibLogger.log_codegen("TEST-MIB", 0, 0)
end)
end
end
describe "log_tokenization/3" do
test "logs tokenization statistics" do
log =
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_tokenization("TEST-MIB", 5000, 250)
end)
assert log =~ "Tokenization complete" or log == ""
end
test "handles zero lines" do
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_tokenization("TEST-MIB", 0, 0)
end)
end
end
describe "log_dependency_order/1" do
test "logs dependency resolution order" do
mib_order = ["SNMPv2-SMI", "SNMPv2-TC", "IF-MIB", "TEST-MIB"]
log =
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_dependency_order(mib_order)
end)
assert log =~ "Dependency resolution complete" or log == ""
end
test "handles empty order" do
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_dependency_order([])
end)
end
end
describe "log_performance/2" do
test "logs performance metrics" do
metrics = [
duration_ms: 1234,
memory_mb: 45.6,
cpu_usage: 23.5
]
log =
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_performance("compilation", metrics)
end)
assert log =~ "Performance metrics" or log == ""
end
test "handles empty metrics" do
capture_log([level: :debug], fn ->
assert :ok = MibLogger.log_performance("test_phase", [])
end)
end
end
describe "log_warning/2" do
test "logs warning with context" do
context = %{file: "test.mib", line: 42, column: 10}
capture_log(fn ->
assert :ok = MibLogger.log_warning("Deprecated syntax detected", context)
end)
end
test "logs warning without context" do
capture_log(fn ->
assert :ok = MibLogger.log_warning("Simple warning")
end)
end
test "handles empty context" do
capture_log(fn ->
assert :ok = MibLogger.log_warning("Warning message", %{})
end)
end
end
describe "log_vendor_quirk/3" do
test "logs vendor quirk handling" do
log =
capture_log([level: :debug], fn ->
assert :ok =
MibLogger.log_vendor_quirk(
"CISCO-MIB",
"Cisco",
"Non-standard SEQUENCE syntax"
)
end)
assert log =~ "Vendor quirk handled" or log == ""
end
end
describe "log_batch_progress/2" do
test "logs batch compilation progress" do
capture_log(fn ->
assert :ok = MibLogger.log_batch_progress(7, 10)
end)
end
test "handles zero total" do
capture_log(fn ->
assert :ok = MibLogger.log_batch_progress(0, 0)
end)
end
test "handles completion" do
capture_log(fn ->
assert :ok = MibLogger.log_batch_progress(10, 10)
end)
end
end
describe "error type extraction" do
test "extracts error types from different error formats" do
errors = [
%Error{type: :syntax_error, message: "Test"},
%Error{type: :syntax_error, message: "Test2"},
%Error{type: :semantic_error, message: "Test3"},
%{type: :custom_error},
:atom_error,
"string error",
123
]
# This tests the private extract_error_type function indirectly
log =
capture_log(fn ->
assert :ok = MibLogger.log_compilation_failed("/test.mib", errors)
end)
assert log =~ "MIB compilation failed"
end
end
describe "return values" do
test "all logging functions return :ok" do
# Verify return values without log capture
assert :ok = MibLogger.log_compilation_start("/test.mib", [])
assert :ok = MibLogger.log_compilation_success("TEST", "/output")
assert :ok = MibLogger.log_compilation_error("TEST", [])
assert :ok = MibLogger.log_compilation_warning("TEST", "/output", [])
assert :ok = MibLogger.log_batch_compilation_start(5)
assert :ok = MibLogger.log_batch_compilation_success(5)
assert :ok = MibLogger.log_batch_compilation_error(3, 2)
assert :ok = MibLogger.log_compilation_complete("TEST", %{})
assert :ok = MibLogger.log_compilation_failed("/test.mib", [])
assert :ok = MibLogger.log_parse_progress("phase", 0)
assert :ok = MibLogger.log_import_resolution("TEST", [])
assert :ok = MibLogger.log_imports_resolved("TEST", 0, 0)
assert :ok = MibLogger.log_codegen("TEST", 0, 0)
assert :ok = MibLogger.log_tokenization("TEST", 0, 0)
assert :ok = MibLogger.log_dependency_order([])
assert :ok = MibLogger.log_performance("phase", [])
assert :ok = MibLogger.log_warning("message")
assert :ok = MibLogger.log_vendor_quirk("TEST", "Vendor", "quirk")
assert :ok = MibLogger.log_batch_progress(0, 0)
end
end
end

View file

@ -0,0 +1,396 @@
defmodule SnmpKit.SnmpLib.PDU.ConstantsTest do
use ExUnit.Case, async: true
alias SnmpKit.SnmpLib.PDU.Constants
describe "error status code accessors" do
test "returns correct error codes" do
assert Constants.no_error() == 0
assert Constants.too_big() == 1
assert Constants.no_such_name() == 2
assert Constants.bad_value() == 3
assert Constants.read_only() == 4
assert Constants.gen_err() == 5
end
end
describe "SNMPv3 constants" do
test "returns USM security model constant" do
assert Constants.usm_security_model() == 3
end
test "returns default max message size" do
assert Constants.default_max_message_size() == 65_507
end
end
describe "PDU type constants accessors" do
test "returns correct PDU type tags" do
assert Constants.get_request() == 0xA0
assert Constants.getnext_request() == 0xA1
assert Constants.get_response() == 0xA2
assert Constants.set_request() == 0xA3
assert Constants.getbulk_request() == 0xA5
end
end
describe "data type constants accessors" do
test "returns correct data type tags" do
assert Constants.integer() == 0x02
assert Constants.octet_string() == 0x04
assert Constants.null() == 0x05
assert Constants.object_identifier() == 0x06
assert Constants.counter32() == 0x41
assert Constants.gauge32() == 0x42
assert Constants.timeticks() == 0x43
assert Constants.counter64() == 0x46
assert Constants.ip_address() == 0x40
assert Constants.opaque_type() == 0x44
assert Constants.no_such_object() == 0x80
assert Constants.no_such_instance() == 0x81
assert Constants.end_of_mib_view() == 0x82
end
end
describe "normalize_version/1" do
test "normalizes SNMP version atoms to integers" do
assert Constants.normalize_version(:v1) == 0
assert Constants.normalize_version(:v2c) == 1
assert Constants.normalize_version(:v2) == 1
assert Constants.normalize_version(:v3) == 3
end
test "passes through integer versions" do
assert Constants.normalize_version(0) == 0
assert Constants.normalize_version(1) == 1
assert Constants.normalize_version(3) == 3
end
test "returns 0 for invalid versions" do
assert Constants.normalize_version(:invalid) == 0
assert Constants.normalize_version("v1") == 0
assert Constants.normalize_version(nil) == 0
end
end
describe "normalize_oid/1" do
test "passes through list OIDs unchanged" do
oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
assert Constants.normalize_oid(oid) == oid
end
test "converts string OIDs to list format" do
assert Constants.normalize_oid("1.3.6.1.2.1.1.1.0") == [1, 3, 6, 1, 2, 1, 1, 1, 0]
assert Constants.normalize_oid("1.3.6.1") == [1, 3, 6, 1]
assert Constants.normalize_oid("0.0") == [0, 0]
end
test "returns default OID for invalid string OIDs" do
assert Constants.normalize_oid("invalid.oid") == [1, 3, 6, 1]
assert Constants.normalize_oid("1.2.abc.4") == [1, 3, 6, 1]
assert Constants.normalize_oid("1.-2.3") == [1, 3, 6, 1]
end
test "returns default OID for non-list, non-string types" do
assert Constants.normalize_oid(nil) == [1, 3, 6, 1]
assert Constants.normalize_oid(123) == [1, 3, 6, 1]
assert Constants.normalize_oid(%{}) == [1, 3, 6, 1]
end
end
describe "pdu_type_to_tag/1" do
test "converts PDU type atoms to tag values" do
assert Constants.pdu_type_to_tag(:get_request) == 0xA0
assert Constants.pdu_type_to_tag(:get_next_request) == 0xA1
assert Constants.pdu_type_to_tag(:get_response) == 0xA2
assert Constants.pdu_type_to_tag(:set_request) == 0xA3
assert Constants.pdu_type_to_tag(:get_bulk_request) == 0xA5
end
end
describe "tag_to_pdu_type/1" do
test "converts tag values to PDU type atoms" do
assert Constants.tag_to_pdu_type(0xA0) == :get_request
assert Constants.tag_to_pdu_type(0xA1) == :get_next_request
assert Constants.tag_to_pdu_type(0xA2) == :get_response
assert Constants.tag_to_pdu_type(0xA3) == :set_request
assert Constants.tag_to_pdu_type(0xA5) == :get_bulk_request
end
test "returns nil for invalid tags" do
assert Constants.tag_to_pdu_type(0xFF) == nil
assert Constants.tag_to_pdu_type(0x00) == nil
end
end
describe "data_type_to_tag/1" do
test "converts data type atoms to tag values" do
assert Constants.data_type_to_tag(:integer) == 0x02
assert Constants.data_type_to_tag(:octet_string) == 0x04
assert Constants.data_type_to_tag(:null) == 0x05
assert Constants.data_type_to_tag(:object_identifier) == 0x06
assert Constants.data_type_to_tag(:counter32) == 0x41
assert Constants.data_type_to_tag(:gauge32) == 0x42
assert Constants.data_type_to_tag(:timeticks) == 0x43
assert Constants.data_type_to_tag(:counter64) == 0x46
assert Constants.data_type_to_tag(:ip_address) == 0x40
assert Constants.data_type_to_tag(:opaque) == 0x44
assert Constants.data_type_to_tag(:no_such_object) == 0x80
assert Constants.data_type_to_tag(:no_such_instance) == 0x81
assert Constants.data_type_to_tag(:end_of_mib_view) == 0x82
end
end
describe "tag_to_data_type/1" do
test "converts tag values to data type atoms" do
assert Constants.tag_to_data_type(0x02) == :integer
assert Constants.tag_to_data_type(0x04) == :octet_string
assert Constants.tag_to_data_type(0x05) == :null
assert Constants.tag_to_data_type(0x06) == :object_identifier
assert Constants.tag_to_data_type(0x41) == :counter32
assert Constants.tag_to_data_type(0x42) == :gauge32
assert Constants.tag_to_data_type(0x43) == :timeticks
assert Constants.tag_to_data_type(0x46) == :counter64
assert Constants.tag_to_data_type(0x40) == :ip_address
assert Constants.tag_to_data_type(0x44) == :opaque
assert Constants.tag_to_data_type(0x80) == :no_such_object
assert Constants.tag_to_data_type(0x81) == :no_such_instance
assert Constants.tag_to_data_type(0x82) == :end_of_mib_view
end
test "returns nil for invalid tags" do
assert Constants.tag_to_data_type(0xFF) == nil
assert Constants.tag_to_data_type(0x00) == nil
end
end
describe "normalize_type/1" do
test "normalizes string type to octet_string" do
assert Constants.normalize_type(:string) == :octet_string
end
test "passes through other types unchanged" do
assert Constants.normalize_type(:integer) == :integer
assert Constants.normalize_type(:counter32) == :counter32
assert Constants.normalize_type(:gauge32) == :gauge32
assert Constants.normalize_type(:null) == :null
end
end
describe "error_status_to_code/1" do
test "converts error status atoms to numeric codes" do
assert Constants.error_status_to_code(:no_error) == 0
assert Constants.error_status_to_code(:too_big) == 1
assert Constants.error_status_to_code(:no_such_name) == 2
assert Constants.error_status_to_code(:bad_value) == 3
assert Constants.error_status_to_code(:read_only) == 4
assert Constants.error_status_to_code(:gen_err) == 5
end
test "passes through integer codes unchanged" do
assert Constants.error_status_to_code(0) == 0
assert Constants.error_status_to_code(1) == 1
assert Constants.error_status_to_code(5) == 5
assert Constants.error_status_to_code(99) == 99
end
end
describe "error_status_to_atom/1" do
test "converts error status codes to atoms" do
assert Constants.error_status_to_atom(0) == :no_error
assert Constants.error_status_to_atom(1) == :too_big
assert Constants.error_status_to_atom(2) == :no_such_name
assert Constants.error_status_to_atom(3) == :bad_value
assert Constants.error_status_to_atom(4) == :read_only
assert Constants.error_status_to_atom(5) == :gen_err
end
test "passes through unknown codes unchanged" do
assert Constants.error_status_to_atom(99) == 99
assert Constants.error_status_to_atom(100) == 100
end
end
describe "encode_msg_flags/1" do
test "encodes message flags with no security" do
flags = %{auth: false, priv: false, reportable: false}
assert Constants.encode_msg_flags(flags) == <<0>>
end
test "encodes message flags with auth only" do
flags = %{auth: true, priv: false, reportable: false}
assert Constants.encode_msg_flags(flags) == <<0x01>>
end
test "encodes message flags with priv only" do
flags = %{auth: false, priv: true, reportable: false}
assert Constants.encode_msg_flags(flags) == <<0x02>>
end
test "encodes message flags with reportable only" do
flags = %{auth: false, priv: false, reportable: true}
assert Constants.encode_msg_flags(flags) == <<0x04>>
end
test "encodes message flags with auth and priv" do
flags = %{auth: true, priv: true, reportable: false}
assert Constants.encode_msg_flags(flags) == <<0x03>>
end
test "encodes message flags with all flags set" do
flags = %{auth: true, priv: true, reportable: true}
assert Constants.encode_msg_flags(flags) == <<0x07>>
end
end
describe "decode_msg_flags/1" do
test "decodes message flags with no security" do
assert Constants.decode_msg_flags(<<0>>) == %{
auth: false,
priv: false,
reportable: false
}
end
test "decodes message flags with auth only" do
assert Constants.decode_msg_flags(<<0x01>>) == %{
auth: true,
priv: false,
reportable: false
}
end
test "decodes message flags with priv only" do
assert Constants.decode_msg_flags(<<0x02>>) == %{
auth: false,
priv: true,
reportable: false
}
end
test "decodes message flags with reportable only" do
assert Constants.decode_msg_flags(<<0x04>>) == %{
auth: false,
priv: false,
reportable: true
}
end
test "decodes message flags with auth and priv" do
assert Constants.decode_msg_flags(<<0x03>>) == %{
auth: true,
priv: true,
reportable: false
}
end
test "decodes message flags with all flags set" do
assert Constants.decode_msg_flags(<<0x07>>) == %{
auth: true,
priv: true,
reportable: true
}
end
end
describe "default_msg_flags/1" do
test "returns correct flags for no_auth_no_priv" do
assert Constants.default_msg_flags(:no_auth_no_priv) == %{
auth: false,
priv: false,
reportable: true
}
end
test "returns correct flags for auth_no_priv" do
assert Constants.default_msg_flags(:auth_no_priv) == %{
auth: true,
priv: false,
reportable: true
}
end
test "returns correct flags for auth_priv" do
assert Constants.default_msg_flags(:auth_priv) == %{
auth: true,
priv: true,
reportable: true
}
end
end
describe "roundtrip encoding/decoding" do
test "message flags roundtrip correctly" do
test_cases = [
%{auth: false, priv: false, reportable: false},
%{auth: true, priv: false, reportable: false},
%{auth: false, priv: true, reportable: false},
%{auth: false, priv: false, reportable: true},
%{auth: true, priv: true, reportable: false},
%{auth: true, priv: false, reportable: true},
%{auth: false, priv: true, reportable: true},
%{auth: true, priv: true, reportable: true}
]
for original_flags <- test_cases do
encoded = Constants.encode_msg_flags(original_flags)
decoded = Constants.decode_msg_flags(encoded)
assert decoded == original_flags
end
end
test "PDU type tags roundtrip correctly" do
pdu_types = [
:get_request,
:get_next_request,
:get_response,
:set_request,
:get_bulk_request
]
for pdu_type <- pdu_types do
tag = Constants.pdu_type_to_tag(pdu_type)
assert Constants.tag_to_pdu_type(tag) == pdu_type
end
end
test "data type tags roundtrip correctly" do
data_types = [
:integer,
:octet_string,
:null,
:object_identifier,
:counter32,
:gauge32,
:timeticks,
:counter64,
:ip_address,
:opaque,
:no_such_object,
:no_such_instance,
:end_of_mib_view
]
for data_type <- data_types do
tag = Constants.data_type_to_tag(data_type)
assert Constants.tag_to_data_type(tag) == data_type
end
end
test "error status codes roundtrip correctly" do
error_statuses = [
:no_error,
:too_big,
:no_such_name,
:bad_value,
:read_only,
:gen_err
]
for error_status <- error_statuses do
code = Constants.error_status_to_code(error_status)
assert Constants.error_status_to_atom(code) == error_status
end
end
end
end