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
563 lines
21 KiB
Python
563 lines
21 KiB
Python
"""Tests for the calculate_warnings orchestrator (__init__.py).
|
|
|
|
These test the integration logic: session reading, DB session lifecycle,
|
|
setting dispatch to sub-checks, and error handling.
|
|
"""
|
|
|
|
from unittest.mock import Mock, patch
|
|
|
|
|
|
def _make_settings_manager(overrides=None):
|
|
"""Build a mock SettingsManager with sensible defaults."""
|
|
defaults = {
|
|
"llm.provider": "ollama",
|
|
"llm.local_context_window_size": 4096,
|
|
"llm.model": "llama3",
|
|
"app.warnings.dismiss_high_context": False,
|
|
"app.warnings.dismiss_model_mismatch": False,
|
|
"app.warnings.dismiss_context_reduced": False,
|
|
"app.warnings.dismiss_legacy_config": False,
|
|
"app.warnings.dismiss_no_backups": False,
|
|
"app.warnings.dismiss_backup_disabled": False,
|
|
"backup.enabled": True,
|
|
}
|
|
if overrides:
|
|
defaults.update(overrides)
|
|
|
|
mgr = Mock()
|
|
mgr.get_setting.side_effect = lambda key, default=None: defaults.get(
|
|
key, default
|
|
)
|
|
return mgr
|
|
|
|
|
|
def _patch_orchestrator(settings_manager, db_session=None):
|
|
"""Return a nested context manager that patches session, get_user_db_session,
|
|
and get_settings_manager for the warnings orchestrator.
|
|
"""
|
|
if db_session is None:
|
|
db_session = Mock()
|
|
|
|
class _Ctx:
|
|
def __enter__(self_ctx):
|
|
self_ctx.p1 = patch(
|
|
"local_deep_research.web.warning_checks.session",
|
|
{"username": "test"},
|
|
)
|
|
self_ctx.p2 = patch(
|
|
"local_deep_research.web.warning_checks.get_user_db_session"
|
|
)
|
|
self_ctx.p3 = patch(
|
|
"local_deep_research.web.warning_checks.get_settings_manager",
|
|
return_value=settings_manager,
|
|
)
|
|
self_ctx.p1.start()
|
|
mock_ctx = self_ctx.p2.start()
|
|
mock_ctx.return_value.__enter__ = Mock(return_value=db_session)
|
|
mock_ctx.return_value.__exit__ = Mock(return_value=False)
|
|
self_ctx.p3.start()
|
|
return self_ctx
|
|
|
|
def __exit__(self_ctx, *args):
|
|
self_ctx.p3.stop()
|
|
self_ctx.p2.stop()
|
|
self_ctx.p1.stop()
|
|
|
|
return _Ctx()
|
|
|
|
|
|
class TestCalculateWarningsNoDbSession:
|
|
"""Tests for when db_session is None."""
|
|
|
|
def test_returns_empty_list_when_db_session_is_none(self):
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.session",
|
|
{"username": "test"},
|
|
):
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.get_user_db_session"
|
|
) as mock_ctx:
|
|
mock_ctx.return_value.__enter__ = Mock(return_value=None)
|
|
mock_ctx.return_value.__exit__ = Mock(return_value=False)
|
|
|
|
result = calculate_warnings()
|
|
assert result == []
|
|
|
|
def test_returns_empty_list_when_no_username(self):
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.session",
|
|
{},
|
|
):
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.get_user_db_session"
|
|
) as mock_ctx:
|
|
mock_ctx.return_value.__enter__ = Mock(return_value=None)
|
|
mock_ctx.return_value.__exit__ = Mock(return_value=False)
|
|
|
|
result = calculate_warnings()
|
|
assert result == []
|
|
|
|
|
|
class TestCalculateWarningsErrorHandling:
|
|
"""Tests for exception handling."""
|
|
|
|
def test_returns_empty_list_on_exception(self):
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.session",
|
|
{"username": "test"},
|
|
):
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.get_user_db_session",
|
|
side_effect=RuntimeError("DB exploded"),
|
|
):
|
|
result = calculate_warnings()
|
|
assert result == []
|
|
|
|
def test_returns_empty_list_on_settings_manager_error(self):
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.session",
|
|
{"username": "test"},
|
|
):
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.get_user_db_session"
|
|
) as mock_ctx:
|
|
mock_ctx.return_value.__enter__ = Mock(return_value=Mock())
|
|
mock_ctx.return_value.__exit__ = Mock(return_value=False)
|
|
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.get_settings_manager",
|
|
side_effect=ValueError("bad settings"),
|
|
):
|
|
result = calculate_warnings()
|
|
assert result == []
|
|
|
|
|
|
class TestCalculateWarningsProviderNormalization:
|
|
"""Provider string is lowercased before checks."""
|
|
|
|
def test_uppercase_provider_is_normalized(self):
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mgr = _make_settings_manager(
|
|
{
|
|
"llm.provider": "OLLAMA",
|
|
"llm.local_context_window_size": 16384,
|
|
}
|
|
)
|
|
with _patch_orchestrator(mgr):
|
|
warnings = calculate_warnings()
|
|
assert any(w["type"] == "high_context" for w in warnings)
|
|
|
|
|
|
class TestCalculateWarningsContextCheckGating:
|
|
"""Context checks are gated on is_local AND not dismissed."""
|
|
|
|
def test_context_checks_skipped_for_non_local_provider(self):
|
|
"""Non-local provider should never trigger context history checks,
|
|
even when context is low and history would normally warn."""
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mock_db_session = Mock()
|
|
|
|
mgr = _make_settings_manager(
|
|
{
|
|
"llm.provider": "openai",
|
|
"llm.local_context_window_size": 2048,
|
|
"app.warnings.dismiss_context_reduced": False,
|
|
}
|
|
)
|
|
with _patch_orchestrator(mgr, db_session=mock_db_session):
|
|
warnings = calculate_warnings()
|
|
|
|
# DB should not have been queried for context history at all
|
|
mock_db_session.query.assert_not_called()
|
|
assert not any(w["type"] == "context_below_history" for w in warnings)
|
|
assert not any(
|
|
w["type"] == "context_truncation_history" for w in warnings
|
|
)
|
|
|
|
def test_context_checks_skipped_when_dismissed(self):
|
|
"""Local provider with dismiss_context_reduced=True should skip DB queries."""
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mock_db_session = Mock()
|
|
|
|
mgr = _make_settings_manager(
|
|
{
|
|
"llm.provider": "ollama",
|
|
"llm.local_context_window_size": 2048,
|
|
"app.warnings.dismiss_context_reduced": True,
|
|
}
|
|
)
|
|
with _patch_orchestrator(mgr, db_session=mock_db_session):
|
|
warnings = calculate_warnings()
|
|
|
|
mock_db_session.query.assert_not_called()
|
|
assert not any(w["type"] == "context_below_history" for w in warnings)
|
|
|
|
|
|
class TestCalculateWarningsMultipleWarnings:
|
|
"""Multiple warnings can fire simultaneously."""
|
|
|
|
def test_high_context_and_model_mismatch_together(self):
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mgr = _make_settings_manager(
|
|
{
|
|
"llm.provider": "ollama",
|
|
"llm.local_context_window_size": 16384,
|
|
"llm.model": "llama3.1:70b",
|
|
}
|
|
)
|
|
with _patch_orchestrator(mgr):
|
|
warnings = calculate_warnings()
|
|
|
|
types = {w["type"] for w in warnings}
|
|
assert "high_context" in types
|
|
assert "model_mismatch" in types
|
|
|
|
def test_high_context_and_context_history_together(self):
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mock_db_session = Mock()
|
|
|
|
# DB queries for context history
|
|
context_query = Mock()
|
|
context_query.filter.return_value.order_by.return_value.limit.return_value.all.return_value = (
|
|
[(32768,)] * 10
|
|
)
|
|
|
|
truncation_query = Mock()
|
|
truncation_query.filter.return_value.filter.return_value.scalar.return_value = 5
|
|
|
|
mock_db_session.query.side_effect = [context_query, truncation_query]
|
|
|
|
mgr = _make_settings_manager(
|
|
{
|
|
"llm.provider": "ollama",
|
|
"llm.local_context_window_size": 16384,
|
|
}
|
|
)
|
|
with _patch_orchestrator(mgr, db_session=mock_db_session):
|
|
warnings = calculate_warnings()
|
|
|
|
types = {w["type"] for w in warnings}
|
|
assert "high_context" in types
|
|
assert "context_below_history" in types
|
|
assert "context_truncation_history" in types
|
|
|
|
def test_all_hardware_and_context_warnings_simultaneously(self):
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mock_db_session = Mock()
|
|
|
|
context_query = Mock()
|
|
context_query.filter.return_value.order_by.return_value.limit.return_value.all.return_value = (
|
|
[(32768,)] * 10
|
|
)
|
|
|
|
truncation_query = Mock()
|
|
truncation_query.filter.return_value.filter.return_value.scalar.return_value = 2
|
|
|
|
mock_db_session.query.side_effect = [context_query, truncation_query]
|
|
|
|
mgr = _make_settings_manager(
|
|
{
|
|
"llm.provider": "ollama",
|
|
"llm.local_context_window_size": 16384,
|
|
"llm.model": "llama3.1:70b",
|
|
}
|
|
)
|
|
with _patch_orchestrator(mgr, db_session=mock_db_session):
|
|
with patch(
|
|
"local_deep_research.web.server_config.get_server_config_path",
|
|
return_value=Mock(exists=Mock(return_value=False)),
|
|
):
|
|
warnings = calculate_warnings()
|
|
|
|
types = {w["type"] for w in warnings}
|
|
assert types == {
|
|
"high_context",
|
|
"model_mismatch",
|
|
"context_below_history",
|
|
"context_truncation_history",
|
|
"no_backups",
|
|
# The egress policy defaults to scope="adaptive" (unacknowledged):
|
|
# the public-egress banner fires (adaptive can resolve to a
|
|
# public-allowing scope) alongside the informational banner that
|
|
# states what adaptive resolves to.
|
|
"public_egress_enabled",
|
|
"egress_effective_scope",
|
|
}
|
|
|
|
|
|
class TestCalculateWarningsSingleSession:
|
|
"""Verify all settings are read from a single DB session."""
|
|
|
|
def test_get_user_db_session_called_once(self):
|
|
"""The orchestrator should open exactly one DB session."""
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mgr = _make_settings_manager()
|
|
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.session",
|
|
{"username": "test"},
|
|
):
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.get_user_db_session"
|
|
) as mock_ctx:
|
|
mock_ctx.return_value.__enter__ = Mock(return_value=Mock())
|
|
mock_ctx.return_value.__exit__ = Mock(return_value=False)
|
|
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.get_settings_manager",
|
|
return_value=mgr,
|
|
):
|
|
calculate_warnings()
|
|
|
|
# Should be called exactly once (the whole point of the refactor)
|
|
assert mock_ctx.call_count == 1
|
|
|
|
def test_get_user_db_session_called_with_username(self):
|
|
"""Verify username from session is passed to get_user_db_session."""
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mgr = _make_settings_manager()
|
|
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.session",
|
|
{"username": "alice"},
|
|
):
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.get_user_db_session"
|
|
) as mock_get_db:
|
|
mock_get_db.return_value.__enter__ = Mock(return_value=Mock())
|
|
mock_get_db.return_value.__exit__ = Mock(return_value=False)
|
|
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.get_settings_manager",
|
|
return_value=mgr,
|
|
):
|
|
calculate_warnings()
|
|
|
|
mock_get_db.assert_called_once_with("alice")
|
|
|
|
def test_get_settings_manager_called_with_session_and_username(self):
|
|
"""Verify both db_session and username are passed to get_settings_manager."""
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mgr = _make_settings_manager()
|
|
mock_db = Mock()
|
|
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.session",
|
|
{"username": "bob"},
|
|
):
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.get_user_db_session"
|
|
) as mock_get_db:
|
|
mock_get_db.return_value.__enter__ = Mock(return_value=mock_db)
|
|
mock_get_db.return_value.__exit__ = Mock(return_value=False)
|
|
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.get_settings_manager",
|
|
return_value=mgr,
|
|
) as mock_get_sm:
|
|
calculate_warnings()
|
|
|
|
mock_get_sm.assert_called_once_with(mock_db, "bob")
|
|
|
|
def test_all_seven_settings_read(self):
|
|
"""All 7 required settings are read from the manager."""
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mgr = _make_settings_manager()
|
|
|
|
with _patch_orchestrator(mgr):
|
|
calculate_warnings()
|
|
|
|
called_keys = [call.args[0] for call in mgr.get_setting.call_args_list]
|
|
assert "llm.provider" in called_keys
|
|
assert "llm.local_context_window_size" in called_keys
|
|
assert "llm.model" in called_keys
|
|
assert "app.warnings.dismiss_high_context" in called_keys
|
|
assert "app.warnings.dismiss_model_mismatch" in called_keys
|
|
assert "app.warnings.dismiss_context_reduced" in called_keys
|
|
assert "app.warnings.dismiss_legacy_config" in called_keys
|
|
|
|
|
|
class TestCalculateWarningsLegacyServerConfig:
|
|
"""Orchestrator coverage for the legacy_server_config check."""
|
|
|
|
def test_legacy_server_config_warning_when_file_exists(self):
|
|
"""If server_config.json exists on disk with non-default values, warning appears."""
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mgr = _make_settings_manager()
|
|
mock_path = Mock()
|
|
mock_path.exists.return_value = True
|
|
# Provide JSON with a non-default value so the warning fires
|
|
mock_path.read_text.return_value = '{"port": 9999}'
|
|
with _patch_orchestrator(mgr):
|
|
with patch(
|
|
"local_deep_research.web.server_config.get_server_config_path",
|
|
return_value=mock_path,
|
|
):
|
|
warnings = calculate_warnings()
|
|
|
|
types = {w["type"] for w in warnings}
|
|
assert "legacy_server_config" in types
|
|
|
|
def test_no_legacy_server_config_warning_when_file_absent(self):
|
|
"""If server_config.json does not exist, no warning."""
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mgr = _make_settings_manager()
|
|
with _patch_orchestrator(mgr):
|
|
with patch(
|
|
"local_deep_research.web.server_config.get_server_config_path",
|
|
return_value=Mock(exists=Mock(return_value=False)),
|
|
):
|
|
warnings = calculate_warnings()
|
|
|
|
types = {w["type"] for w in warnings}
|
|
assert "legacy_server_config" not in types
|
|
|
|
def test_no_legacy_server_config_warning_when_dismissed(self):
|
|
"""If dismissed, no warning even when file exists."""
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mgr = _make_settings_manager(
|
|
{"app.warnings.dismiss_legacy_config": True}
|
|
)
|
|
with _patch_orchestrator(mgr):
|
|
with patch(
|
|
"local_deep_research.web.server_config.get_server_config_path",
|
|
return_value=Mock(exists=Mock(return_value=True)),
|
|
):
|
|
warnings = calculate_warnings()
|
|
|
|
types = {w["type"] for w in warnings}
|
|
assert "legacy_server_config" not in types
|
|
|
|
|
|
class TestCalculateWarningsFailureIsolation:
|
|
"""Verify _safe_check isolates individual check failures."""
|
|
|
|
def test_first_check_crash_does_not_kill_second(self):
|
|
"""If check_high_context raises, check_model_mismatch still runs."""
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mgr = _make_settings_manager(
|
|
{
|
|
"llm.provider": "ollama",
|
|
"llm.local_context_window_size": 16384,
|
|
"llm.model": "llama3.1:70b",
|
|
}
|
|
)
|
|
with _patch_orchestrator(mgr):
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.check_high_context",
|
|
side_effect=RuntimeError("boom"),
|
|
):
|
|
warnings = calculate_warnings()
|
|
|
|
types = {w["type"] for w in warnings}
|
|
assert "high_context" not in types
|
|
assert "model_mismatch" in types
|
|
|
|
def test_context_check_crash_does_not_kill_other_context_check(self):
|
|
"""If check_context_below_history raises, check_context_truncation_history still runs."""
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mock_db_session = Mock()
|
|
|
|
truncation_query = Mock()
|
|
truncation_query.filter.return_value.filter.return_value.scalar.return_value = 3
|
|
|
|
mock_db_session.query.return_value = truncation_query
|
|
|
|
mgr = _make_settings_manager(
|
|
{
|
|
"llm.provider": "ollama",
|
|
"llm.local_context_window_size": 4096,
|
|
}
|
|
)
|
|
with _patch_orchestrator(mgr, db_session=mock_db_session):
|
|
with patch(
|
|
"local_deep_research.web.warning_checks.check_context_below_history",
|
|
side_effect=RuntimeError("db query failed"),
|
|
):
|
|
warnings = calculate_warnings()
|
|
|
|
types = {w["type"] for w in warnings}
|
|
assert "context_below_history" not in types
|
|
assert "context_truncation_history" in types
|
|
|
|
|
|
class TestCalculateWarningsNoneProvider:
|
|
"""Document behavior when provider setting is None."""
|
|
|
|
def test_none_provider_does_not_crash(self):
|
|
"""If get_setting returns None for provider, outer try/except catches the AttributeError."""
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
mgr = Mock()
|
|
mgr.get_setting.side_effect = lambda key, default=None: {
|
|
"llm.provider": None,
|
|
"llm.local_context_window_size": 4096,
|
|
"llm.model": "llama3",
|
|
"app.warnings.dismiss_high_context": False,
|
|
"app.warnings.dismiss_model_mismatch": False,
|
|
"app.warnings.dismiss_context_reduced": False,
|
|
}.get(key, default)
|
|
|
|
with _patch_orchestrator(mgr):
|
|
result = calculate_warnings()
|
|
|
|
# Should not crash — outer except catches AttributeError from None.lower()
|
|
assert isinstance(result, list)
|
|
|
|
|
|
class TestCalculateWarningsBackupGlobHardening:
|
|
"""The backup glob in calculate_warnings() must skip symlinks, so a
|
|
planted symlink cannot inflate the backup count/size used for warnings.
|
|
"""
|
|
|
|
def test_symlink_is_not_counted_as_a_backup(self, tmp_path):
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
backup_dir = tmp_path / "backups"
|
|
backup_dir.mkdir()
|
|
|
|
# The ONLY entry matching the glob is a symlink to an existing file
|
|
# outside the backup dir. With the hardening it is skipped, leaving
|
|
# zero real backups; without it, stat() would follow the link and
|
|
# count it.
|
|
outside = tmp_path / "outside_secret.db"
|
|
outside.write_bytes(b"x" * 4096)
|
|
(backup_dir / "ldr_backup_29991231_235959.db").symlink_to(outside)
|
|
|
|
settings_manager = _make_settings_manager() # backup.enabled=True
|
|
with _patch_orchestrator(settings_manager):
|
|
with patch(
|
|
"local_deep_research.config.paths.get_user_backup_directory",
|
|
return_value=backup_dir,
|
|
):
|
|
warnings = calculate_warnings()
|
|
|
|
types = {w["type"] for w in warnings}
|
|
# Symlink excluded -> backup_count == 0 -> the "no backups" warning
|
|
# fires and no "backup_info" (which would carry the link target size).
|
|
assert "no_backups" in types
|
|
assert "backup_info" not in types
|