Files

242 lines
8.1 KiB
Python

"""Unit tests for domain errors and exit codes.
Covers all 13 error types with their exit code mappings.
"""
from __future__ import annotations
import sys
from pathlib import Path
_scripts_dir = Path(__file__).resolve().parents[2] / "scripts"
if str(_scripts_dir) not in sys.path:
sys.path.insert(0, str(_scripts_dir))
from binary_analysis.domain.enums import ExitCode
from binary_analysis.domain.errors import (
AmbiguousSelectorError,
AnalysisFailedError,
BackendFailureError,
BinaryAnalysisError,
BinaryNotFoundError,
DependencyMissingError,
EntityNotFoundError,
ImportFailedError,
InvalidArgsError,
InvalidConfigError,
OperationTimeoutError,
ProjectNotFoundError,
UnsupportedFormatError,
error_type_for,
)
class TestExitCodes:
"""Tests for exit code enumeration."""
def test_success_is_0(self) -> None:
assert ExitCode.SUCCESS == 0
def test_invalid_args_is_2(self) -> None:
assert ExitCode.INVALID_ARGS == 2
def test_dependency_missing_is_3(self) -> None:
assert ExitCode.DEPENDENCY_MISSING == 3
def test_all_codes_are_unique(self) -> None:
values = [e.value for e in ExitCode]
assert len(values) == len(set(values))
def test_all_14_exit_codes_defined(self) -> None:
assert len(ExitCode) == 14
class TestBinaryAnalysisError:
"""Tests for BinaryAnalysisError base class."""
def test_default_exit_code(self) -> None:
error = BinaryAnalysisError("test error")
assert error.exit_code == ExitCode.GENERIC_ERROR
assert error.message == "test error"
def test_custom_exit_code(self) -> None:
error = BinaryAnalysisError("test error", ExitCode.BACKEND_FAILURE)
assert error.exit_code == ExitCode.BACKEND_FAILURE
def test_to_diagnostic(self) -> None:
error = BinaryAnalysisError("something went wrong")
diag = error.to_diagnostic()
assert diag["severity"] == "ERROR"
assert diag["message"] == "something went wrong"
def test_is_exception(self) -> None:
error = BinaryAnalysisError("test")
assert isinstance(error, Exception)
class TestInvalidArgsError:
"""Tests for InvalidArgsError."""
def test_exit_code_is_2(self) -> None:
error = InvalidArgsError("bad args")
assert error.exit_code == ExitCode.INVALID_ARGS
def test_message_preserved(self) -> None:
error = InvalidArgsError("limit must be a positive integer")
assert "limit must be a positive integer" in error.message
class TestDependencyMissingError:
"""Tests for DependencyMissingError."""
def test_exit_code_is_3(self) -> None:
error = DependencyMissingError("Ghidra not found")
assert error.exit_code == ExitCode.DEPENDENCY_MISSING
def test_message_preserved(self) -> None:
error = DependencyMissingError("Java not installed")
assert "Java not installed" in error.message
class TestInvalidConfigError:
"""Tests for InvalidConfigError."""
def test_exit_code_is_4(self) -> None:
error = InvalidConfigError("corrupt project.json")
assert error.exit_code == ExitCode.INVALID_CONFIG
def test_to_diagnostic(self) -> None:
error = InvalidConfigError("corrupt project.json")
diag = error.to_diagnostic()
assert diag["severity"] == "ERROR"
assert diag["category"] == "config"
class TestUnsupportedFormatError:
"""Tests for UnsupportedFormatError."""
def test_exit_code_is_5(self) -> None:
error = UnsupportedFormatError("unknown format")
assert error.exit_code == ExitCode.UNSUPPORTED_FORMAT
class TestProjectNotFoundError:
"""Tests for ProjectNotFoundError."""
def test_exit_code_is_6(self) -> None:
error = ProjectNotFoundError("my-project")
assert error.exit_code == ExitCode.PROJECT_NOT_FOUND
assert "my-project" in error.message
class TestBinaryNotFoundError:
"""Tests for BinaryNotFoundError."""
def test_exit_code_is_7(self) -> None:
error = BinaryNotFoundError()
assert error.exit_code == ExitCode.BINARY_NOT_FOUND
assert "binary" in error.message.lower()
class TestAmbiguousSelectorError:
"""Tests for AmbiguousSelectorError."""
def test_exit_code_is_8(self) -> None:
error = AmbiguousSelectorError("ambiguous", [])
assert error.exit_code == ExitCode.AMBIGUOUS_SELECTOR
def test_to_diagnostic_with_candidates(self) -> None:
candidates = [{"name": "func1"}, {"name": "func2"}]
error = AmbiguousSelectorError("ambiguous selector", candidates)
diag = error.to_diagnostic()
assert "candidates" in diag
assert len(diag["candidates"]) == 2
class TestEntityNotFoundError:
"""Tests for EntityNotFoundError."""
def test_exit_code_is_9(self) -> None:
error = EntityNotFoundError("Function", "my_func")
assert error.exit_code == ExitCode.ENTITY_NOT_FOUND
assert error.entity_type == "Function"
assert error.selector == "my_func"
def test_message_contains_type_and_selector(self) -> None:
error = EntityNotFoundError("Function", "nonexistent")
assert "Function" in error.message
assert "nonexistent" in error.message
class TestImportFailedError:
"""Tests for ImportFailedError."""
def test_exit_code_is_10(self) -> None:
error = ImportFailedError("disk full")
assert error.exit_code == ExitCode.IMPORT_FAILED
def test_binary_path_preserved(self) -> None:
error = ImportFailedError("failed", binary_path="/tmp/test.bin")
assert error.binary_path == "/tmp/test.bin"
class TestAnalysisFailedError:
"""Tests for AnalysisFailedError."""
def test_exit_code_is_11(self) -> None:
error = AnalysisFailedError("analysis crashed")
assert error.exit_code == ExitCode.ANALYSIS_FAILED
def test_project_preserved(self) -> None:
error = AnalysisFailedError("analysis crashed", project="my-proj")
assert error.project == "my-proj"
class TestOperationTimeoutError:
"""Tests for OperationTimeoutError."""
def test_exit_code_is_12(self) -> None:
error = OperationTimeoutError()
assert error.exit_code == ExitCode.OPERATION_TIMEOUT
def test_to_diagnostic(self) -> None:
error = OperationTimeoutError("timed out after 5s")
diag = error.to_diagnostic()
assert diag["severity"] == "ERROR"
assert diag["category"] == "timeout"
assert diag["recoverable"] is True
class TestBackendFailureError:
"""Tests for BackendFailureError."""
def test_exit_code_is_13(self) -> None:
error = BackendFailureError("backend internal error")
assert error.exit_code == ExitCode.BACKEND_FAILURE
def test_to_diagnostic_with_original_error(self) -> None:
error = BackendFailureError("backend failed", original_error="NullPointerException")
diag = error.to_diagnostic()
assert diag["backend_error"] == "NullPointerException"
class TestErrorTypeFor:
"""Tests for exit code to error type mapping."""
def test_all_codes_mapped(self) -> None:
for code in ExitCode:
error_cls = error_type_for(code)
assert issubclass(error_cls, BinaryAnalysisError)
def test_specific_mappings(self) -> None:
assert error_type_for(ExitCode.INVALID_ARGS) is InvalidArgsError
assert error_type_for(ExitCode.INVALID_CONFIG) is InvalidConfigError
assert error_type_for(ExitCode.UNSUPPORTED_FORMAT) is UnsupportedFormatError
assert error_type_for(ExitCode.PROJECT_NOT_FOUND) is ProjectNotFoundError
assert error_type_for(ExitCode.BINARY_NOT_FOUND) is BinaryNotFoundError
assert error_type_for(ExitCode.AMBIGUOUS_SELECTOR) is AmbiguousSelectorError
assert error_type_for(ExitCode.ENTITY_NOT_FOUND) is EntityNotFoundError
assert error_type_for(ExitCode.IMPORT_FAILED) is ImportFailedError
assert error_type_for(ExitCode.ANALYSIS_FAILED) is AnalysisFailedError
assert error_type_for(ExitCode.OPERATION_TIMEOUT) is OperationTimeoutError
assert error_type_for(ExitCode.BACKEND_FAILURE) is BackendFailureError