7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
277 lines
10 KiB
Python
277 lines
10 KiB
Python
"""
|
|
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)
|