""" Tests for FAISS Index Verifier - File integrity verification for FAISS vector indexes. Tests cover: - File type detection (should_verify) - FAISS-specific policy enforcement - Checksum calculation - Edge cases and error handling """ import hashlib import tempfile from pathlib import Path import pytest from local_deep_research.security.file_integrity.verifiers.faiss_verifier import ( FAISSIndexVerifier, ) from local_deep_research.security.file_integrity.base_verifier import FileType @pytest.fixture def verifier(): """Create a FAISS verifier instance.""" return FAISSIndexVerifier() @pytest.fixture def temp_faiss_file(): """Create a temporary .faiss file.""" with tempfile.NamedTemporaryFile(suffix=".faiss", delete=False) as f: f.write(b"fake faiss index content") return Path(f.name) @pytest.fixture def temp_non_faiss_file(): """Create a temporary non-.faiss file.""" with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f: f.write(b"text content") return Path(f.name) class TestFAISSVerifierInitialization: """Tests for FAISS verifier initialization.""" def test_verifier_creates_successfully(self): """Verifier can be instantiated.""" verifier = FAISSIndexVerifier() assert verifier is not None def test_verifier_inherits_base_verifier(self): """Verifier inherits from BaseFileVerifier.""" from local_deep_research.security.file_integrity.base_verifier import ( BaseFileVerifier, ) verifier = FAISSIndexVerifier() assert isinstance(verifier, BaseFileVerifier) class TestShouldVerify: """Tests for should_verify method.""" def test_should_verify_faiss_extension(self, verifier): """Should return True for .faiss files.""" path = Path("/data/indexes/my_index.faiss") assert verifier.should_verify(path) is True def test_should_verify_faiss_uppercase_extension(self, verifier): """Should return True for .FAISS uppercase extension.""" path = Path("/data/indexes/my_index.FAISS") assert verifier.should_verify(path) is True def test_should_verify_faiss_mixed_case_extension(self, verifier): """Should return True for .Faiss mixed case extension.""" path = Path("/data/indexes/my_index.Faiss") assert verifier.should_verify(path) is True def test_should_not_verify_pdf_extension(self, verifier): """Should return False for .pdf files.""" path = Path("/data/docs/document.pdf") assert verifier.should_verify(path) is False def test_should_not_verify_txt_extension(self, verifier): """Should return False for .txt files.""" path = Path("/data/notes.txt") assert verifier.should_verify(path) is False def test_should_not_verify_no_extension(self, verifier): """Should return False for files without extension.""" path = Path("/data/file_without_extension") assert verifier.should_verify(path) is False def test_should_not_verify_faiss_in_name(self, verifier): """Should return False for files with 'faiss' in name but different extension.""" path = Path("/data/faiss_backup.json") assert verifier.should_verify(path) is False def test_should_verify_nested_directory_path(self, verifier): """Should return True for .faiss files in nested directories.""" path = Path("/data/embeddings/user123/collection_abc/index.faiss") assert verifier.should_verify(path) is True def test_should_verify_hidden_file(self, verifier): """Should return True for hidden .faiss files.""" path = Path("/data/.hidden_index.faiss") assert verifier.should_verify(path) is True def test_should_not_verify_empty_path(self, verifier): """Should handle empty file name gracefully.""" path = Path("") # Empty path has no suffix assert verifier.should_verify(path) is False class TestGetFileType: """Tests for get_file_type method.""" def test_returns_faiss_index_type(self, verifier): """Should return FileType.FAISS_INDEX.""" assert verifier.get_file_type() == FileType.FAISS_INDEX def test_file_type_is_enum_member(self, verifier): """File type should be a valid FileType enum member.""" file_type = verifier.get_file_type() assert isinstance(file_type, FileType) def test_file_type_value_is_string(self, verifier): """File type value should be a string.""" file_type = verifier.get_file_type() assert file_type.value == "faiss_index" class TestAllowsModifications: """Tests for allows_modifications method.""" def test_does_not_allow_modifications(self, verifier): """FAISS indexes should never allow modifications.""" assert verifier.allows_modifications() is False def test_modification_policy_is_strict(self, verifier): """Modification policy should be consistently False.""" # Multiple calls should return same result for _ in range(10): assert verifier.allows_modifications() is False class TestChecksumCalculation: """Tests for checksum calculation (inherited from BaseFileVerifier).""" def test_calculate_checksum_returns_hex_string( self, verifier, temp_faiss_file ): """Checksum should be a hex string.""" checksum = verifier.calculate_checksum(temp_faiss_file) assert isinstance(checksum, str) # SHA256 produces 64 hex characters assert len(checksum) == 64 # All characters should be valid hex assert all(c in "0123456789abcdef" for c in checksum) def test_calculate_checksum_consistency(self, verifier, temp_faiss_file): """Same file should produce same checksum.""" checksum1 = verifier.calculate_checksum(temp_faiss_file) checksum2 = verifier.calculate_checksum(temp_faiss_file) assert checksum1 == checksum2 def test_calculate_checksum_different_files(self, verifier): """Different files should produce different checksums.""" with tempfile.NamedTemporaryFile(suffix=".faiss", delete=False) as f1: f1.write(b"content A") path1 = Path(f1.name) with tempfile.NamedTemporaryFile(suffix=".faiss", delete=False) as f2: f2.write(b"content B") path2 = Path(f2.name) checksum1 = verifier.calculate_checksum(path1) checksum2 = verifier.calculate_checksum(path2) assert checksum1 != checksum2 def test_calculate_checksum_file_not_found(self, verifier): """Should raise FileNotFoundError for non-existent file.""" path = Path("/nonexistent/path/index.faiss") with pytest.raises(FileNotFoundError): verifier.calculate_checksum(path) def test_calculate_checksum_large_file(self, verifier): """Should handle large files (chunked reading).""" with tempfile.NamedTemporaryFile(suffix=".faiss", delete=False) as f: # Write 1MB of data f.write(b"x" * (1024 * 1024)) path = Path(f.name) checksum = verifier.calculate_checksum(path) assert len(checksum) == 64 def test_calculate_checksum_empty_file(self, verifier): """Should handle empty files.""" with tempfile.NamedTemporaryFile(suffix=".faiss", delete=False) as f: # Write nothing - empty file path = Path(f.name) checksum = verifier.calculate_checksum(path) # SHA256 of empty string expected = hashlib.sha256(b"").hexdigest() assert checksum == expected class TestGetAlgorithm: """Tests for get_algorithm method (inherited from BaseFileVerifier).""" def test_get_algorithm_returns_sha256(self, verifier): """Should return sha256 as the algorithm.""" assert verifier.get_algorithm() == "sha256" class TestEdgeCases: """Edge case tests for FAISS verifier.""" def test_path_with_special_characters(self, verifier): """Should handle paths with special characters.""" path = Path("/data/user's files/my (index) [v2].faiss") assert verifier.should_verify(path) is True def test_path_with_unicode_characters(self, verifier): """Should handle paths with unicode characters.""" path = Path("/données/索引/インデックス.faiss") assert verifier.should_verify(path) is True def test_path_with_spaces(self, verifier): """Should handle paths with spaces.""" path = Path("/my documents/vector indexes/main index.faiss") assert verifier.should_verify(path) is True def test_very_long_path(self, verifier): """Should handle very long paths.""" long_dir = "a" * 200 path = Path(f"/{long_dir}/index.faiss") assert verifier.should_verify(path) is True def test_relative_path(self, verifier): """Should handle relative paths.""" path = Path("./relative/path/index.faiss") assert verifier.should_verify(path) is True def test_multiple_extensions(self, verifier): """Should only check final extension.""" path = Path("/data/index.backup.faiss") assert verifier.should_verify(path) is True path_wrong = Path("/data/index.faiss.backup") assert verifier.should_verify(path_wrong) is False class TestIntegrationWithIntegrityManager: """Tests for integration with the integrity manager.""" def test_verifier_can_be_registered(self, verifier): """Verifier should have all methods required for registration.""" # Check all required methods exist assert hasattr(verifier, "should_verify") assert hasattr(verifier, "get_file_type") assert hasattr(verifier, "allows_modifications") assert hasattr(verifier, "calculate_checksum") assert hasattr(verifier, "get_algorithm") def test_verifier_methods_are_callable(self, verifier): """All verifier methods should be callable.""" assert callable(verifier.should_verify) assert callable(verifier.get_file_type) assert callable(verifier.allows_modifications) assert callable(verifier.calculate_checksum) assert callable(verifier.get_algorithm)