Jackson Tolins

11 papers B 8Journal 3
YearRankTypeTitle / Venue / Authors
2026 J jnl
CoRR
Claudiu Cristian Musat, Jackson Tolins, Diego Antognini, Jingling Li, Martin Klissarov, Tom Duerig
2016 B conf
LREC
Jackson Tolins, Kris Liu, Yingying Wang, Jean E. Fox Tree, Marilyn A. Walker, Michael Neff
2016 B conf
LREC
Jackson Tolins, Kris Liu, Michael Neff, Marilyn A. Walker, Jean E. Fox Tree
2016 J jnl
Cogn. Sci.
Jackson Tolins, Jean E. Fox Tree
2016 J jnl
IEEE Trans. Affect. Comput.
Kris Liu, Jackson Tolins, Jean E. Fox Tree, Michael Neff, Marilyn A. Walker
2015 B conf
CogSci
Jackson Tolins, Jean E. Fox Tree
2014 B conf
CogSci
Jackson Tolins, Jean E. Fox Tree
2014 B conf
CogSci
Kris Liu, Jackson Tolins, Jean E. Fox Tree, Marilyn A. Walker, Michael Neff
2013 B conf
CogSci
Jackson Tolins, Eliana Colunga
2013 B conf
IVA
Kris Liu, Jackson Tolins, Jean E. Fox Tree, Marilyn A. Walker, Michael Neff
2012 B conf
CogSci
Jackson Tolins
tests/integration/test_hashes.py
← Index tests/integration/test_hashes.py python
"""
Integration tests for the HashExtractor class.
"""
import pytest
import hashlib
from unittest.mock import Mock, patch, MagicMock

pytestmark = [pytest.mark.integration]


class TestHashExtractorInitialization:
    """Tests for HashExtractor initialization."""

    def test_initialization_elf(self, elf_binary_path, mock_logger):
        """Test initialization with ELF file."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            assert extractor.filepath == elf_binary_path
            assert extractor.hashes is None
            assert extractor.filetype == "elf"
            assert extractor.macho is None

    def test_initialization_with_macho_object(self, elf_binary_path, mock_logger):
        """Test initialization with pre-parsed macho object."""
        from redb.extractors.hashes import HashExtractor

        mock_macho = MagicMock()
        mock_macho.get_architectures.return_value = ["x86_64"]

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger, macho=mock_macho)

            assert extractor.macho is mock_macho

    def test_initialization_pe(self, pe_binary_path, mock_logger):
        """Test initialization with PE file."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(pe_binary_path, mock_logger)

            assert extractor.filepath == pe_binary_path
            assert extractor.filetype == "pebin"
            # PE object should be created
            assert extractor.pe is not None

    def test_elastic_index_suffix(self, elf_binary_path, mock_logger):
        """Test that elastic_index has correct suffix."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            assert "-hashes" in extractor.elastic_index


class TestHashExtractorTag:
    """Tests for tag method."""

    def test_tag_value(self, elf_binary_path, mock_logger):
        """Test that tag returns correct value."""
        from redb.extractors.hashes import HashExtractor
        from redb.extractors.enum import Tag

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            assert extractor.tag() == Tag.HASH.value
            assert extractor.tag() == "hash"


class TestHashExtractorBasicHashes:
    """Tests for basic hash extraction."""

    def test_extract_md5(self, elf_binary_path, mock_logger, known_hashes):
        """Test MD5 hash extraction."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            assert result.md5 == known_hashes['md5']

    def test_extract_sha1(self, elf_binary_path, mock_logger, known_hashes):
        """Test SHA1 hash extraction."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            assert result.sha1 == known_hashes['sha1']

    def test_extract_sha256(self, elf_binary_path, mock_logger, known_hashes):
        """Test SHA256 hash extraction."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            assert result.sha256 == known_hashes['sha256']

    def test_extract_ssdeep(self, elf_binary_path, mock_logger):
        """Test ssdeep hash extraction."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # ssdeep should be a string
            assert isinstance(result.ssdeep_hash, str)

    def test_extract_tlsh(self, elf_binary_path, mock_logger):
        """Test TLSH hash extraction."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # TLSH should be a string
            assert isinstance(result.tlsh_hash, str)


class TestHashExtractorPEHashes:
    """Tests for PE-specific hash extraction."""

    def test_extract_imphash(self, pe_binary_path, mock_logger):
        """Test imphash extraction for PE file."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(pe_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # Imphash should be set for PE files
            if extractor.pe is not None:
                assert result.imphash is not None or result.imphash is None  # May or may not have imports

    def test_extract_authentihash(self, pe_binary_path, mock_logger):
        """Test authentihash extraction for PE file."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(pe_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # Authentihash should be set for PE files
            if extractor.pe is not None:
                assert result.authentihash is not None

    def test_pe_hashes_not_set_for_elf(self, elf_binary_path, mock_logger):
        """Test that PE-specific hashes are not set for ELF files."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # PE-specific hashes should be None for ELF
            assert result.imphash is None
            assert result.authentihash is None
            assert result.richhash is None


class TestHashExtractorRichHeaderHashes:
    """Tests for Rich header hash extraction."""

    def test_compute_richhash(self, pe_binary_path, mock_logger):
        """Test Rich header hash computation."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(pe_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # Rich hash may or may not be present depending on the PE
            if result.richhash is not None:
                assert isinstance(result.richhash, str)
                assert len(result.richhash) == 32  # MD5 hex length

    def test_compute_richpe_hash(self, pe_binary_path, mock_logger):
        """Test RichPE hash computation."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(pe_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # RichPE hash may or may not be present
            if result.richpe_hash is not None:
                assert isinstance(result.richpe_hash, str)
                assert len(result.richpe_hash) == 32  # MD5 hex length

    def test_compute_richpv_hash(self, pe_binary_path, mock_logger):
        """Test RichPV hash computation."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(pe_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # RichPV hashes may or may not be present
            if result.richpv_hash is not None:
                assert isinstance(result.richpv_hash, str)
            if result.richpv_hash_sorted is not None:
                assert isinstance(result.richpv_hash_sorted, str)


class TestHashExtractorELFHashes:
    """Tests for ELF-specific hash extraction."""

    def test_extract_elf_import_hash(self, elf_binary_path, mock_logger):
        """Test ELF import hash extraction."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # Import hash may or may not be present
            if result.import_hash is not None:
                assert isinstance(result.import_hash, str)
                assert len(result.import_hash) == 32

    def test_extract_elf_export_hash(self, elf_binary_path, mock_logger):
        """Test ELF export hash extraction."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # Export hash may or may not be present
            if result.export_hash is not None:
                assert isinstance(result.export_hash, str)

    def test_extract_elf_section_hash(self, elf_binary_path, mock_logger):
        """Test ELF section hash extraction."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # Section hash should be present for valid ELF
            if result.section_hash is not None:
                assert isinstance(result.section_hash, str)

    def test_extract_elf_symbol_hash(self, elf_binary_path, mock_logger):
        """Test ELF symbol hash extraction."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # Symbol hash (symhash) may or may not be present
            if result.symbol_hash is not None:
                assert isinstance(result.symbol_hash, str)

    def test_extract_elf_dynamic_hash(self, elf_binary_path, mock_logger):
        """Test ELF dynamic hash extraction."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            # Dynamic hash may or may not be present
            if result.dynamic_hash is not None:
                assert isinstance(result.dynamic_hash, str)


class TestHashExtractorPrepareExportData:
    """Tests for prepare_export_data method."""

    def test_prepare_export_elasticsearch(self, elf_binary_path, mock_logger):
        """Test prepare_export_data for Elasticsearch."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)
            extractor.extract()

            result = extractor.prepare_export_data("ElasticsearchExporter")

            assert result is extractor.hashes

    def test_prepare_export_clickhouse(self, elf_binary_path, mock_logger):
        """Test prepare_export_data for ClickHouse."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)
            extractor.extract()

            result = extractor.prepare_export_data("ClickHouseExporter")

            # Should return tuple of (data, column_names, column_type_names)
            assert isinstance(result, tuple)
            assert len(result) == 3

            data, column_names, column_type_names = result

            assert isinstance(data, list)
            assert isinstance(column_names, list)
            assert isinstance(column_type_names, list)

            # Check expected columns
            assert 'sha256' in column_names
            assert 'md5' in column_names
            assert 'sha1' in column_names
            assert 'ssdeep_hash' in column_names
            assert 'tlsh_hash' in column_names

    def test_prepare_export_clickhouse_pe(self, pe_binary_path, mock_logger):
        """Test prepare_export_data for ClickHouse with PE file."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(pe_binary_path, mock_logger)
            extractor.extract()

            result = extractor.prepare_export_data("ClickHouseExporter")

            data, column_names, column_type_names = result

            # PE-specific columns should be present
            assert 'authentihash' in column_names
            assert 'imphash' in column_names
            assert 'richhash' in column_names

    def test_prepare_export_clickhouse_has_macho_columns(self, elf_binary_path, mock_logger):
        """Test prepare_export_data for ClickHouse includes Mach-O hash columns."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)
            extractor.extract()

            result = extractor.prepare_export_data("ClickHouseExporter")

            data, column_names, column_type_names = result

            # Mach-O hash columns should be present
            assert 'macho_dylib_hash' in column_names
            assert 'macho_import_hash' in column_names
            assert 'macho_export_hash' in column_names
            assert 'macho_entitlement_hash' in column_names
            assert 'macho_symhash' in column_names


class TestHashExtractorClickHouseTable:
    """Tests for get_clickhouse_table method."""

    def test_clickhouse_table_name(self, elf_binary_path, mock_logger):
        """Test correct ClickHouse table name."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            assert extractor.get_clickhouse_table() == "redb_hashes"


@pytest.mark.unit
@pytest.mark.dataclass
class TestHashExtractorHashesDataclass:
    """Tests for Hashes dataclass structure."""

    def test_hashes_dataclass_structure(self, elf_binary_path, mock_logger):
        """Test that Hashes dataclass has expected structure."""
        from redb.extractors.hashes import HashExtractor
        from redb.models.dataclasses import Hashes

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert isinstance(result, Hashes)
            # Required fields
            assert hasattr(result, 'md5')
            assert hasattr(result, 'sha1')
            assert hasattr(result, 'sha256')
            assert hasattr(result, 'ssdeep_hash')
            assert hasattr(result, 'tlsh_hash')
            # Optional fields
            assert hasattr(result, 'authentihash')
            assert hasattr(result, 'imphash')
            assert hasattr(result, 'richhash')

    def test_hashes_dataclass_has_macho_fields(self):
        """Test that Hashes dataclass has Mach-O hash fields."""
        from redb.models.dataclasses import Hashes

        # Create instance with default values
        hashes = Hashes(
            md5="d41d8cd98f00b204e9800998ecf8427e",
            sha1="da39a3ee5e6b4b0d3255bfef95601890afd80709",
            sha256="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
            ssdeep_hash="",
            tlsh_hash=""
        )

        # Verify Mach-O hash fields exist and default to None
        assert hasattr(hashes, 'macho_dylib_hash')
        assert hasattr(hashes, 'macho_import_hash')
        assert hasattr(hashes, 'macho_export_hash')
        assert hasattr(hashes, 'macho_entitlement_hash')
        assert hasattr(hashes, 'macho_symhash')

        assert hashes.macho_dylib_hash is None
        assert hashes.macho_import_hash is None
        assert hashes.macho_export_hash is None
        assert hashes.macho_entitlement_hash is None
        assert hashes.macho_symhash is None

    def test_hashes_dataclass_with_macho_values(self):
        """Test that Hashes dataclass can be created with Mach-O hash values."""
        from redb.models.dataclasses import Hashes

        hashes = Hashes(
            md5="d41d8cd98f00b204e9800998ecf8427e",
            sha1="da39a3ee5e6b4b0d3255bfef95601890afd80709",
            sha256="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
            ssdeep_hash="",
            tlsh_hash="",
            macho_dylib_hash="abc123def456789012345678901234567",
            macho_import_hash="def456abc123789012345678901234567",
            macho_export_hash="789012abc123def456345678901234567",
            macho_entitlement_hash="345678abc123def456789012901234567",
            macho_symhash="901234abc123def456789012345678567"
        )

        assert hashes.macho_dylib_hash == "abc123def456789012345678901234567"
        assert hashes.macho_import_hash == "def456abc123789012345678901234567"
        assert hashes.macho_export_hash == "789012abc123def456345678901234567"
        assert hashes.macho_entitlement_hash == "345678abc123def456789012901234567"
        assert hashes.macho_symhash == "901234abc123def456789012345678567"


class TestHashExtractorErrorHandling:
    """Tests for error handling in HashExtractor."""

    def test_extract_handles_hash_errors(self, elf_binary_path, mock_logger):
        """Test that extract handles hash computation errors gracefully."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            # Even if some hashes fail, we should get a result
            result = extractor.extract()

            assert result is not None

    def test_extract_with_invalid_pe(self, elf_binary_path, mock_logger):
        """Test extraction when PE parsing fails."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            # Force filetype to be 'pebin' but use ELF file
            with patch.object(HashExtractor, '__init__', lambda self, *args, **kwargs: None):
                extractor = HashExtractor.__new__(HashExtractor)
                extractor.filepath = elf_binary_path
                extractor.log = mock_logger
                extractor.hashes = None
                extractor.pe = None
                extractor.filetype = 'pebin'

                with open(elf_binary_path, 'rb') as f:
                    extractor.binary = f.read()

                # Should not crash
                result = extractor._extract_hashes()
                assert result is not None


class TestHashExtractorMachoHashes:
    """Tests for Mach-O specific hash extraction."""

    def test_macho_hashes_not_set_for_elf(self, elf_binary_path, mock_logger):
        """Test that Mach-O hashes are not set for ELF files."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            assert result.macho_dylib_hash is None
            assert result.macho_import_hash is None
            assert result.macho_export_hash is None
            assert result.macho_entitlement_hash is None
            assert result.macho_symhash is None

    def test_macho_hashes_not_set_for_pe(self, pe_binary_path, mock_logger):
        """Test that Mach-O hashes are not set for PE files."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(pe_binary_path, mock_logger)

            result = extractor.extract()

            assert result is not None
            assert result.macho_dylib_hash is None
            assert result.macho_import_hash is None
            assert result.macho_export_hash is None
            assert result.macho_entitlement_hash is None
            assert result.macho_symhash is None

    def test_extract_macho_hashes_with_mock(self, elf_binary_path, mock_logger):
        """Test Mach-O hash extraction with mocked macho object."""
        from redb.extractors.hashes import HashExtractor

        # Create mock macho object
        mock_macho = MagicMock()
        mock_macho.get_architectures.return_value = ["x86_64"]
        mock_macho.get_similarity_hashes.return_value = {
            'dylib_hash': 'abc123def456789012345678901234ab',
            'import_hash': 'def456abc789012345678901234567cd',
            'export_hash': '789012abc456def345678901234567ef',
            'entitlement_hash': '345678abc123def789012901234567gh',
            'symhash': '901234abc123def456789012345678ij'
        }

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger, macho=mock_macho)
            # Override filetype to trigger macho hash extraction
            extractor.filetype = 'macho'

            result = extractor.extract()

            assert result is not None
            assert result.macho_dylib_hash == 'abc123def456789012345678901234ab'
            assert result.macho_import_hash == 'def456abc789012345678901234567cd'
            assert result.macho_export_hash == '789012abc456def345678901234567ef'
            assert result.macho_entitlement_hash == '345678abc123def789012901234567gh'
            assert result.macho_symhash == '901234abc123def456789012345678ij'

    def test_extract_macho_hashes_fat_binary_mock(self, elf_binary_path, mock_logger):
        """Test Mach-O hash extraction for FAT binary with mocked macho object."""
        from redb.extractors.hashes import HashExtractor

        # Create mock macho object for FAT binary
        mock_macho = MagicMock()
        mock_macho.get_architectures.return_value = ["x86_64", "arm64"]  # Multiple archs = FAT
        mock_macho.get_similarity_hashes.return_value = {
            'fat': {
                'dylib_hash': 'fat_dylib_hash_12345678901234567',
                'import_hash': 'fat_import_hash_12345678901234567',
            },
            'combined': {
                'dylib_hash': 'combined_dylib_hash_1234567890123',
            }
        }

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger, macho=mock_macho)
            extractor.filetype = 'macho'

            result = extractor.extract()

            assert result is not None
            # Should use 'fat' key for FAT binaries
            assert result.macho_dylib_hash == 'fat_dylib_hash_12345678901234567'
            assert result.macho_import_hash == 'fat_import_hash_12345678901234567'

    def test_extract_macho_hashes_handles_empty(self, elf_binary_path, mock_logger):
        """Test Mach-O hash extraction handles empty hash dict."""
        from redb.extractors.hashes import HashExtractor

        mock_macho = MagicMock()
        mock_macho.get_architectures.return_value = ["x86_64"]
        mock_macho.get_similarity_hashes.return_value = {}

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger, macho=mock_macho)
            extractor.filetype = 'macho'

            result = extractor.extract()

            # Should not crash, hashes should be None
            assert result is not None
            assert result.macho_dylib_hash is None

    def test_extract_macho_hashes_handles_none(self, elf_binary_path, mock_logger):
        """Test Mach-O hash extraction handles None return."""
        from redb.extractors.hashes import HashExtractor

        mock_macho = MagicMock()
        mock_macho.get_architectures.return_value = ["x86_64"]
        mock_macho.get_similarity_hashes.return_value = None

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger, macho=mock_macho)
            extractor.filetype = 'macho'

            result = extractor.extract()

            # Should not crash
            assert result is not None


class TestHashExtractorConsistency:
    """Tests for hash consistency."""

    def test_hash_consistency_across_extractions(self, elf_binary_path, mock_logger):
        """Test that hashes are consistent across multiple extractions."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor1 = HashExtractor(elf_binary_path, mock_logger)
            result1 = extractor1.extract()

            extractor2 = HashExtractor(elf_binary_path, mock_logger)
            result2 = extractor2.extract()

            assert result1.md5 == result2.md5
            assert result1.sha1 == result2.sha1
            assert result1.sha256 == result2.sha256

    def test_hash_values_are_lowercase(self, elf_binary_path, mock_logger):
        """Test that hash values are lowercase hex strings."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert result.md5 == result.md5.lower()
            assert result.sha1 == result.sha1.lower()
            assert result.sha256 == result.sha256.lower()

    def test_hash_lengths_correct(self, elf_binary_path, mock_logger):
        """Test that hash lengths are correct."""
        from redb.extractors.hashes import HashExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = HashExtractor(elf_binary_path, mock_logger)

            result = extractor.extract()

            assert len(result.md5) == 32  # MD5 = 128 bits = 32 hex chars
            assert len(result.sha1) == 40  # SHA1 = 160 bits = 40 hex chars
            assert len(result.sha256) == 64  # SHA256 = 256 bits = 64 hex chars