Files
2026-07-13 13:12:33 +08:00

298 lines
9.4 KiB
Python

from __future__ import annotations
import sys
from opensquilla.search.providers.exa import ExaSearchProvider
from opensquilla.search.runtime_config import SearchRuntimeConfig, resolve_search_runtime
from opensquilla.search.types import SearchOptions
def _clear_search_env(monkeypatch) -> None:
for key in (
"BOCHA_SEARCH_API_KEY",
"BRAVE_SEARCH_API_KEY",
"IQS_SEARCH_API_KEY",
"TAVILY_API_KEY",
"EXA_API_KEY",
"CUSTOM_EXA_KEY",
):
monkeypatch.delenv(key, raising=False)
def test_resolver_no_key_default_uses_duckduckgo_without_keyed_attempts(monkeypatch) -> None:
_clear_search_env(monkeypatch)
runtime = resolve_search_runtime(SearchRuntimeConfig(provider="duckduckgo"))
assert runtime.provider_order(SearchOptions(query="q")) == ("duckduckgo",)
duckduckgo = runtime.provider_config("duckduckgo")
assert duckduckgo.available is True
assert duckduckgo.credential_source == "none"
assert runtime.provider_config("tavily").available is False
def test_resolver_uses_configured_env_for_active_provider(monkeypatch) -> None:
_clear_search_env(monkeypatch)
monkeypatch.setenv("CUSTOM_EXA_KEY", "env-exa-key")
runtime = resolve_search_runtime(
SearchRuntimeConfig(provider="exa", api_key_env="CUSTOM_EXA_KEY")
)
exa = runtime.provider_config("exa")
assert exa.available is True
assert exa.credential_source == "configured_env"
assert exa.provider_kwargs()["api_key"] == "env-exa-key"
def test_resolver_prefers_configured_exa_key_over_configured_env(monkeypatch) -> None:
_clear_search_env(monkeypatch)
monkeypatch.setenv("CUSTOM_EXA_KEY", "env-exa-key")
runtime = resolve_search_runtime(
SearchRuntimeConfig(
provider="exa",
api_key="configured-exa-key",
api_key_env="CUSTOM_EXA_KEY",
)
)
exa = runtime.provider_config("exa")
assert exa.available is True
assert exa.credential_source == "configured"
assert exa.provider_kwargs()["api_key"] == "configured-exa-key"
provider = runtime.build_provider("exa")
assert isinstance(provider, ExaSearchProvider)
assert provider._api_key == "configured-exa-key"
def test_resolver_partial_key_orders_configured_provider_then_duckduckgo(monkeypatch) -> None:
_clear_search_env(monkeypatch)
runtime = resolve_search_runtime(
SearchRuntimeConfig(provider="brave", api_key="brave-key")
)
assert runtime.provider_order(SearchOptions(query="q")) == ("brave", "duckduckgo")
def test_resolver_bocha_default_env_orders_bocha_then_duckduckgo(monkeypatch) -> None:
_clear_search_env(monkeypatch)
monkeypatch.setenv("BOCHA_SEARCH_API_KEY", "bocha-key")
runtime = resolve_search_runtime(SearchRuntimeConfig(provider="duckduckgo"))
assert runtime.provider_order(SearchOptions(query="q")) == ("bocha", "duckduckgo")
bocha = runtime.provider_config("bocha")
assert bocha.available is True
assert bocha.credential_source == "spec_env"
def test_resolver_iqs_default_env_orders_iqs_then_duckduckgo(monkeypatch) -> None:
_clear_search_env(monkeypatch)
monkeypatch.setenv("IQS_SEARCH_API_KEY", "iqs-key")
runtime = resolve_search_runtime(SearchRuntimeConfig(provider="duckduckgo"))
assert runtime.provider_order(SearchOptions(query="q")) == ("iqs", "duckduckgo")
iqs = runtime.provider_config("iqs")
assert iqs.available is True
assert iqs.credential_source == "spec_env"
def test_resolver_all_key_mode_tie_breakers(monkeypatch) -> None:
_clear_search_env(monkeypatch)
monkeypatch.setenv("BOCHA_SEARCH_API_KEY", "bocha-key")
monkeypatch.setenv("BRAVE_SEARCH_API_KEY", "brave-key")
monkeypatch.setenv("TAVILY_API_KEY", "tavily-key")
monkeypatch.setenv("EXA_API_KEY", "exa-key")
runtime = resolve_search_runtime(SearchRuntimeConfig(provider="duckduckgo"))
assert runtime.provider_order(SearchOptions(query="q")) == (
"bocha",
"tavily",
"brave",
"exa",
"duckduckgo",
)
assert runtime.provider_order(SearchOptions(query="q", mode="technical")) == (
"exa",
"bocha",
"brave",
"tavily",
"duckduckgo",
)
assert runtime.provider_order(SearchOptions(query="q", recency="week")) == (
"bocha",
"tavily",
"brave",
"exa",
"duckduckgo",
)
assert runtime.provider_order(SearchOptions(query="q", mode="news")) == (
"bocha",
"tavily",
"brave",
"exa",
"duckduckgo",
)
def test_resolver_all_key_mode_tie_breakers_with_iqs(monkeypatch) -> None:
_clear_search_env(monkeypatch)
monkeypatch.setenv("BOCHA_SEARCH_API_KEY", "bocha-key")
monkeypatch.setenv("BRAVE_SEARCH_API_KEY", "brave-key")
monkeypatch.setenv("IQS_SEARCH_API_KEY", "iqs-key")
monkeypatch.setenv("TAVILY_API_KEY", "tavily-key")
monkeypatch.setenv("EXA_API_KEY", "exa-key")
runtime = resolve_search_runtime(SearchRuntimeConfig(provider="duckduckgo"))
assert runtime.provider_order(SearchOptions(query="q")) == (
"bocha",
"tavily",
"iqs",
"brave",
"exa",
"duckduckgo",
)
assert runtime.provider_order(SearchOptions(query="q", mode="technical")) == (
"exa",
"bocha",
"brave",
"tavily",
"iqs",
"duckduckgo",
)
assert runtime.provider_order(SearchOptions(query="q", recency="week")) == (
"bocha",
"tavily",
"iqs",
"brave",
"exa",
"duckduckgo",
)
assert runtime.provider_order(
SearchOptions(query="q", include_domains=("python.org",))
) == ("tavily", "iqs", "exa")
def test_resolver_domain_constrained_auto_prefers_domain_filter_providers(
monkeypatch,
) -> None:
_clear_search_env(monkeypatch)
monkeypatch.setenv("BOCHA_SEARCH_API_KEY", "bocha-key")
monkeypatch.setenv("BRAVE_SEARCH_API_KEY", "brave-key")
monkeypatch.setenv("TAVILY_API_KEY", "tavily-key")
monkeypatch.setenv("EXA_API_KEY", "exa-key")
runtime = resolve_search_runtime(SearchRuntimeConfig(provider="duckduckgo"))
assert runtime.provider_order(
SearchOptions(query="q", include_domains=("python.org",))
) == ("tavily", "exa")
assert runtime.provider_order(
SearchOptions(query="q", exclude_domains=("spam.example",))
) == ("tavily", "exa")
def test_resolver_domain_constrained_technical_prefers_exa_then_tavily(
monkeypatch,
) -> None:
_clear_search_env(monkeypatch)
monkeypatch.setenv("BOCHA_SEARCH_API_KEY", "bocha-key")
monkeypatch.setenv("BRAVE_SEARCH_API_KEY", "brave-key")
monkeypatch.setenv("TAVILY_API_KEY", "tavily-key")
monkeypatch.setenv("EXA_API_KEY", "exa-key")
runtime = resolve_search_runtime(SearchRuntimeConfig(provider="duckduckgo"))
assert runtime.provider_order(
SearchOptions(
query="q",
mode="technical",
include_domains=("python.org",),
)
) == ("exa", "tavily")
def test_resolver_domain_constrained_freshness_skips_bocha_without_domain_filter(
monkeypatch,
) -> None:
_clear_search_env(monkeypatch)
monkeypatch.setenv("BOCHA_SEARCH_API_KEY", "bocha-key")
monkeypatch.setenv("BRAVE_SEARCH_API_KEY", "brave-key")
monkeypatch.setenv("TAVILY_API_KEY", "tavily-key")
monkeypatch.setenv("EXA_API_KEY", "exa-key")
runtime = resolve_search_runtime(SearchRuntimeConfig(provider="duckduckgo"))
assert runtime.provider_order(
SearchOptions(
query="q",
include_domains=("python.org",),
recency="week",
)
) == ("tavily", "exa")
def test_resolver_domain_constrained_bocha_only_uses_duckduckgo_local_filter(
monkeypatch,
) -> None:
_clear_search_env(monkeypatch)
monkeypatch.setenv("BOCHA_SEARCH_API_KEY", "bocha-key")
runtime = resolve_search_runtime(SearchRuntimeConfig(provider="duckduckgo"))
assert runtime.provider_order(
SearchOptions(query="q", include_domains=("python.org",))
) == ("duckduckgo",)
def test_resolver_provider_kwargs_include_proxy_and_diagnostics(monkeypatch) -> None:
_clear_search_env(monkeypatch)
runtime = resolve_search_runtime(
SearchRuntimeConfig(
provider="duckduckgo",
proxy="http://proxy.test",
use_env_proxy=True,
diagnostics=True,
)
)
kwargs = runtime.provider_config("duckduckgo").provider_kwargs()
assert kwargs == {
"proxy": "http://proxy.test",
"use_env_proxy": True,
"diagnostics": True,
}
def test_runtime_build_provider_registers_builtin_providers_in_fresh_process(
monkeypatch,
) -> None:
import opensquilla.search.registry as registry
for module_name in (
"opensquilla.search.providers.bocha",
"opensquilla.search.providers.iqs",
"opensquilla.search.providers.tavily",
"opensquilla.search.providers.brave",
"opensquilla.search.providers.exa",
"opensquilla.search.providers.duckduckgo",
):
monkeypatch.delitem(sys.modules, module_name, raising=False)
monkeypatch.setattr(registry, "_providers", {})
runtime = resolve_search_runtime(
SearchRuntimeConfig(provider="bocha", api_key="bocha-key")
)
provider = runtime.build_provider("bocha")
assert provider.__class__.__name__ == "BochaSearchProvider"
assert provider.name == "bocha"