Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

320 lines
11 KiB
Python

"""High-value edge case tests for security/file_integrity/integrity_manager.py.
Tests internal methods: _normalize_path, _get_verifier_for_file,
_needs_verification, _do_verification, _update_stats, register_verifier,
and cleanup logic.
"""
from datetime import datetime, UTC
from pathlib import Path
from unittest.mock import MagicMock
from local_deep_research.security.file_integrity.integrity_manager import (
FileIntegrityManager,
)
def _make_manager():
"""Create a FileIntegrityManager instance bypassing __init__."""
mgr = FileIntegrityManager.__new__(FileIntegrityManager)
mgr.username = "test"
mgr.password = None
mgr.verifiers = []
return mgr
class TestNormalizePath:
"""Test _normalize_path."""
def test_returns_string(self, tmp_path):
"""Returns a string, not a Path object."""
mgr = _make_manager()
result = mgr._normalize_path(tmp_path / "file.txt")
assert isinstance(result, str)
def test_resolves_to_absolute(self, tmp_path):
"""Result is an absolute path."""
mgr = _make_manager()
result = mgr._normalize_path(tmp_path / "file.txt")
assert Path(result).is_absolute()
def test_resolves_symlink(self, tmp_path):
"""Symlink and target resolve to the same normalized path."""
mgr = _make_manager()
real_file = tmp_path / "real.txt"
real_file.write_text("content")
symlink = tmp_path / "link.txt"
symlink.symlink_to(real_file)
assert mgr._normalize_path(symlink) == mgr._normalize_path(real_file)
class TestGetVerifierForFile:
"""Test _get_verifier_for_file."""
def test_returns_none_with_no_verifiers(self, tmp_path):
"""No registered verifiers returns None."""
mgr = _make_manager()
assert mgr._get_verifier_for_file(tmp_path / "test.txt") is None
def test_returns_matching_verifier(self, tmp_path):
"""Returns the first verifier that matches."""
mgr = _make_manager()
v1 = MagicMock()
v1.should_verify.return_value = False
v2 = MagicMock()
v2.should_verify.return_value = True
mgr.verifiers = [v1, v2]
assert mgr._get_verifier_for_file(tmp_path / "test.txt") is v2
def test_first_match_wins(self, tmp_path):
"""When multiple verifiers match, the first one wins."""
mgr = _make_manager()
v1 = MagicMock()
v1.should_verify.return_value = True
v2 = MagicMock()
v2.should_verify.return_value = True
mgr.verifiers = [v1, v2]
assert mgr._get_verifier_for_file(tmp_path / "test.txt") is v1
class TestNeedsVerification:
"""Test _needs_verification mtime logic."""
def test_returns_true_for_missing_file(self, tmp_path):
"""Missing file needs verification."""
mgr = _make_manager()
record = MagicMock()
record.last_verified_at = datetime.now(UTC)
result = mgr._needs_verification(record, tmp_path / "nonexistent.txt")
assert result is True
def test_returns_true_when_never_verified(self, tmp_path):
"""File never verified (last_verified_at=None) needs verification."""
mgr = _make_manager()
f = tmp_path / "test.txt"
f.write_text("content")
record = MagicMock()
record.last_verified_at = None
assert mgr._needs_verification(record, f) is True
def test_returns_true_when_file_mtime_is_none(self, tmp_path):
"""Stored mtime=None means verification needed."""
mgr = _make_manager()
f = tmp_path / "test.txt"
f.write_text("content")
record = MagicMock()
record.last_verified_at = datetime.now(UTC)
record.file_mtime = None
assert mgr._needs_verification(record, f) is True
def test_returns_false_when_mtime_unchanged(self, tmp_path):
"""Unchanged mtime means no verification needed."""
mgr = _make_manager()
f = tmp_path / "test.txt"
f.write_text("content")
record = MagicMock()
record.last_verified_at = datetime.now(UTC)
record.file_mtime = f.stat().st_mtime
assert mgr._needs_verification(record, f) is False
def test_returns_true_when_mtime_changed(self, tmp_path):
"""Changed mtime triggers verification."""
mgr = _make_manager()
f = tmp_path / "test.txt"
f.write_text("content")
record = MagicMock()
record.last_verified_at = datetime.now(UTC)
record.file_mtime = f.stat().st_mtime - 1.0
assert mgr._needs_verification(record, f) is True
def test_mtime_tolerance_below_threshold(self, tmp_path):
"""Difference of 0.0005 is within tolerance (no verification)."""
mgr = _make_manager()
f = tmp_path / "test.txt"
f.write_text("content")
actual_mtime = f.stat().st_mtime
record = MagicMock()
record.last_verified_at = datetime.now(UTC)
record.file_mtime = actual_mtime - 0.0005
assert mgr._needs_verification(record, f) is False
def test_mtime_tolerance_above_threshold(self, tmp_path):
"""Difference of 0.0015 exceeds tolerance (verification needed)."""
mgr = _make_manager()
f = tmp_path / "test.txt"
f.write_text("content")
actual_mtime = f.stat().st_mtime
record = MagicMock()
record.last_verified_at = datetime.now(UTC)
record.file_mtime = actual_mtime - 0.0015
assert mgr._needs_verification(record, f) is True
class TestDoVerification:
"""Test _do_verification."""
def test_file_missing_returns_false(self, tmp_path):
"""Missing file returns (False, 'file_missing')."""
mgr = _make_manager()
record = MagicMock()
session = MagicMock()
result, reason = mgr._do_verification(
record, tmp_path / "missing.txt", session
)
assert result is False
assert reason == "file_missing"
def test_no_verifier_returns_false(self, tmp_path):
"""No matching verifier returns (False, 'no_verifier')."""
mgr = _make_manager()
f = tmp_path / "test.txt"
f.write_text("content")
record = MagicMock()
session = MagicMock()
result, reason = mgr._do_verification(record, f, session)
assert result is False
assert reason == "no_verifier"
def test_checksum_match_returns_true(self, tmp_path):
"""Matching checksum returns (True, None)."""
mgr = _make_manager()
f = tmp_path / "test.txt"
f.write_text("content")
verifier = MagicMock()
verifier.should_verify.return_value = True
verifier.calculate_checksum.return_value = "abc123"
mgr.verifiers = [verifier]
record = MagicMock()
record.checksum = "abc123"
session = MagicMock()
result, reason = mgr._do_verification(record, f, session)
assert result is True
assert reason is None
def test_checksum_mismatch_returns_false(self, tmp_path):
"""Mismatching checksum returns (False, 'checksum_mismatch')."""
mgr = _make_manager()
f = tmp_path / "test.txt"
f.write_text("content")
verifier = MagicMock()
verifier.should_verify.return_value = True
verifier.calculate_checksum.return_value = "different"
mgr.verifiers = [verifier]
record = MagicMock()
record.checksum = "abc123"
session = MagicMock()
result, reason = mgr._do_verification(record, f, session)
assert result is False
assert reason == "checksum_mismatch"
def test_mtime_not_updated_on_mismatch(self, tmp_path):
"""On checksum mismatch, record.file_mtime is not changed."""
mgr = _make_manager()
f = tmp_path / "test.txt"
f.write_text("content")
verifier = MagicMock()
verifier.should_verify.return_value = True
verifier.calculate_checksum.return_value = "different"
mgr.verifiers = [verifier]
record = MagicMock()
record.checksum = "abc123"
sentinel = 12345.0
record.file_mtime = sentinel
session = MagicMock()
mgr._do_verification(record, f, session)
assert record.file_mtime == sentinel
class TestUpdateStats:
"""Test _update_stats."""
def test_pass_increments_successes_resets_failures(self):
"""Passing verification increments successes and resets failures."""
mgr = _make_manager()
record = MagicMock()
record.total_verifications = 0
record.consecutive_successes = 0
record.consecutive_failures = 3
mgr._update_stats(record, True, MagicMock())
assert record.total_verifications == 1
assert record.consecutive_successes == 1
assert record.consecutive_failures == 0
def test_fail_increments_failures_resets_successes(self):
"""Failing verification increments failures and resets successes."""
mgr = _make_manager()
record = MagicMock()
record.total_verifications = 0
record.consecutive_successes = 5
record.consecutive_failures = 0
mgr._update_stats(record, False, MagicMock())
assert record.total_verifications == 1
assert record.consecutive_failures == 1
assert record.consecutive_successes == 0
def test_last_verified_at_set(self):
"""last_verified_at is set to a datetime on every call."""
mgr = _make_manager()
record = MagicMock()
record.total_verifications = 0
record.consecutive_successes = 0
record.consecutive_failures = 0
record.last_verified_at = None
mgr._update_stats(record, True, MagicMock())
assert record.last_verified_at is not None
def test_last_verification_passed_reflects_outcome(self):
"""last_verification_passed matches the passed parameter."""
mgr = _make_manager()
record = MagicMock()
record.total_verifications = 0
record.consecutive_successes = 0
record.consecutive_failures = 0
mgr._update_stats(record, False, MagicMock())
assert record.last_verification_passed is False
class TestRegisterVerifier:
"""Test register_verifier."""
def test_adds_to_verifiers_list(self):
"""Verifier is added to the list."""
mgr = _make_manager()
v = MagicMock()
v.get_file_type.return_value = "test"
mgr.register_verifier(v)
assert v in mgr.verifiers
def test_preserves_insertion_order(self):
"""Verifiers appear in registration order."""
mgr = _make_manager()
v1 = MagicMock()
v1.get_file_type.return_value = "type1"
v2 = MagicMock()
v2.get_file_type.return_value = "type2"
v3 = MagicMock()
v3.get_file_type.return_value = "type3"
mgr.register_verifier(v1)
mgr.register_verifier(v2)
mgr.register_verifier(v3)
assert mgr.verifiers == [v1, v2, v3]
class TestClassConstants:
"""Test FileIntegrityManager class constants."""
def test_max_failures_per_file(self):
assert FileIntegrityManager.MAX_FAILURES_PER_FILE == 100
def test_max_total_failures(self):
assert FileIntegrityManager.MAX_TOTAL_FAILURES == 10000
def test_per_file_less_than_total(self):
"""Per-file limit must be less than total limit."""
assert (
FileIntegrityManager.MAX_FAILURES_PER_FILE
< FileIntegrityManager.MAX_TOTAL_FAILURES
)