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

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