Files
learningcircuit--local-deep…/tests/web_search_engines/test_engine_registry.py
T
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

275 lines
10 KiB
Python

"""Tests for the hardcoded engine registry.
Validates that registry entries are consistent with the security whitelist
and with the default settings JSON files.
"""
import json
from pathlib import Path
import pytest
from local_deep_research.security.module_whitelist import (
ALLOWED_CLASS_NAMES,
ALLOWED_MODULE_PATHS,
)
from local_deep_research.web_search_engines.engine_registry import (
ENGINE_REGISTRY,
EngineEntry,
get_engine_entry,
)
class TestRegistryIntegrity:
"""All registry entries must be consistent with the security whitelist."""
def test_all_module_paths_are_relative(self):
"""Every module_path must start with '.' (relative import)."""
for name, entry in ENGINE_REGISTRY.items():
assert entry.module_path.startswith("."), (
f"Engine '{name}' has non-relative module_path: {entry.module_path}"
)
def test_all_module_paths_in_whitelist(self):
"""Every module_path must be in ALLOWED_MODULE_PATHS."""
for name, entry in ENGINE_REGISTRY.items():
assert entry.module_path in ALLOWED_MODULE_PATHS, (
f"Engine '{name}' module_path {entry.module_path!r} "
f"not in ALLOWED_MODULE_PATHS"
)
def test_all_class_names_in_whitelist(self):
"""Every class_name must be in ALLOWED_CLASS_NAMES."""
for name, entry in ENGINE_REGISTRY.items():
assert entry.class_name in ALLOWED_CLASS_NAMES, (
f"Engine '{name}' class_name {entry.class_name!r} "
f"not in ALLOWED_CLASS_NAMES"
)
def test_full_search_fields_consistent(self):
"""full_search_module and full_search_class must both be set or both be None."""
for name, entry in ENGINE_REGISTRY.items():
has_module = entry.full_search_module is not None
has_class = entry.full_search_class is not None
assert has_module == has_class, (
f"Engine '{name}' has inconsistent full_search fields: "
f"module={entry.full_search_module}, class={entry.full_search_class}"
)
def test_full_search_module_paths_in_whitelist(self):
"""full_search_module values must be in ALLOWED_MODULE_PATHS."""
for name, entry in ENGINE_REGISTRY.items():
if entry.full_search_module:
assert entry.full_search_module in ALLOWED_MODULE_PATHS, (
f"Engine '{name}' full_search_module "
f"{entry.full_search_module!r} not in ALLOWED_MODULE_PATHS"
)
def test_full_search_class_names_in_whitelist(self):
"""full_search_class values must be in ALLOWED_CLASS_NAMES."""
for name, entry in ENGINE_REGISTRY.items():
if entry.full_search_class:
assert entry.full_search_class in ALLOWED_CLASS_NAMES, (
f"Engine '{name}' full_search_class "
f"{entry.full_search_class!r} not in ALLOWED_CLASS_NAMES"
)
class TestRegistryCoverage:
"""Registry should cover all engines that were previously in settings JSON."""
@pytest.fixture()
def default_settings(self):
"""Load the default_settings.json file."""
path = (
Path(__file__).resolve().parents[2]
/ "src"
/ "local_deep_research"
/ "defaults"
/ "default_settings.json"
)
with open(path) as f:
return json.load(f)
def test_covers_all_web_engines_in_defaults(self, default_settings):
"""Registry should have entries for all web engines in default_settings.json."""
engine_names = set()
for key in default_settings:
if key.startswith("search.engine.web."):
parts = key.split(".")
if len(parts) >= 4:
engine_names.add(parts[3])
for name in engine_names:
assert name in ENGINE_REGISTRY, (
f"Engine '{name}' from default_settings.json missing from registry"
)
def test_covers_all_web_engines_in_per_engine_settings(self):
"""Registry should have entries for all engines with per-engine settings files."""
defaults_dir = (
Path(__file__).resolve().parents[2]
/ "src"
/ "local_deep_research"
/ "defaults"
)
# Runtime-registered engines that are intentionally not in the registry
runtime_engines = {"library", "collection"}
engine_names = set()
for settings_file in sorted(defaults_dir.glob("settings_*.json")):
with open(settings_file) as f:
data = json.load(f)
for key in data:
if key.startswith("search.engine.web."):
parts = key.split(".")
if len(parts) >= 4:
engine_names.add(parts[3])
for name in engine_names - runtime_engines:
assert name in ENGINE_REGISTRY, (
f"Engine '{name}' from per-engine settings file missing from registry"
)
def test_no_settings_files_contain_module_paths(self):
"""Settings files should not contain module_path/class_name (now in registry)."""
defaults_dir = (
Path(__file__).resolve().parents[2]
/ "src"
/ "local_deep_research"
/ "defaults"
)
for settings_file in sorted(defaults_dir.glob("*.json")):
with open(settings_file) as f:
content = f.read()
for field in ("module_path", "class_name"):
assert field not in content, (
f"{settings_file.name} still contains '{field}' — "
f"these should be in engine_registry.py instead"
)
def test_removed_meta_engines_not_in_registry(self):
"""The removed meta engines must not reappear in the registry."""
assert "auto" not in ENGINE_REGISTRY
assert "meta" not in ENGINE_REGISTRY
assert "parallel" not in ENGINE_REGISTRY
assert "parallel_scientific" not in ENGINE_REGISTRY
class TestEngineEntryDataclass:
"""Test the EngineEntry dataclass."""
def test_frozen(self):
"""EngineEntry should be immutable."""
entry = EngineEntry(
module_path=".engines.test",
class_name="TestEngine",
)
with pytest.raises(AttributeError):
entry.module_path = "changed"
def test_optional_full_search_defaults_to_none(self):
"""full_search fields default to None."""
entry = EngineEntry(
module_path=".engines.test",
class_name="TestEngine",
)
assert entry.full_search_module is None
assert entry.full_search_class is None
def test_full_search_fields_set(self):
"""full_search fields can be set."""
entry = EngineEntry(
module_path=".engines.test",
class_name="TestEngine",
full_search_module=".engines.full_search",
full_search_class="FullSearchResults",
)
assert entry.full_search_module == ".engines.full_search"
assert entry.full_search_class == "FullSearchResults"
class TestGetEngineEntry:
"""Test the get_engine_entry helper."""
def test_returns_entry_for_known_engine(self):
"""Should return EngineEntry for known engine names."""
entry = get_engine_entry("brave")
assert entry is not None
assert entry.class_name == "BraveSearchEngine"
def test_returns_none_for_unknown_engine(self):
"""Should return None for unknown engine names."""
assert get_engine_entry("nonexistent_engine") is None
def test_returns_none_for_runtime_engines(self):
"""Runtime-registered engines (library, collection_*) are not in registry."""
assert get_engine_entry("library") is None
assert get_engine_entry("collection_1") is None
class TestSearchConfigInjection:
"""Test that search_config() injects registry data."""
def test_search_config_injects_module_path(self):
"""search_config() should inject module_path from registry."""
from unittest.mock import patch
from local_deep_research.web_search_engines.search_engines_config import (
search_config,
)
def mock_get_setting(key, default, **kwargs):
if key == "search.engine.web":
return {
"brave.requires_api_key": True,
"brave.api_key": "test-key",
}
return default
with (
patch(
"local_deep_research.web_search_engines.search_engines_config._get_setting",
side_effect=mock_get_setting,
),
patch(
"local_deep_research.web_search_engines.retriever_registry.retriever_registry"
) as mock_registry,
):
mock_registry.list_registered.return_value = []
result = search_config()
assert "brave" in result
assert result["brave"]["module_path"] == ".engines.search_engine_brave"
assert result["brave"]["class_name"] == "BraveSearchEngine"
assert result["brave"]["full_search_module"] == ".engines.full_search"
assert result["brave"]["full_search_class"] == "FullSearchResults"
def test_removed_meta_engines_absent_from_search_config(self):
"""search_config() should not contain the removed meta engines."""
from unittest.mock import patch
from local_deep_research.web_search_engines.search_engines_config import (
search_config,
)
def mock_get_setting(key, default, **kwargs):
return default
with (
patch(
"local_deep_research.web_search_engines.search_engines_config._get_setting",
side_effect=mock_get_setting,
),
patch(
"local_deep_research.web_search_engines.retriever_registry.retriever_registry"
) as mock_registry,
):
mock_registry.list_registered.return_value = []
result = search_config()
assert "auto" not in result
assert "meta" not in result
assert "parallel" not in result
assert "parallel_scientific" not in result