from __future__ import annotations import httpx import pytest from opensquilla.gateway.config import GatewayConfig from opensquilla.search.providers.brave import BraveSearchProvider from opensquilla.search.providers.duckduckgo import DuckDuckGoProvider from opensquilla.search.providers.exa import ExaSearchProvider from opensquilla.search.types import SearchProviderError, SearchResult from opensquilla.tools.builtin import web def test_gateway_config_accepts_search_api_key() -> None: config = GatewayConfig(search_api_key="brave-test-key") assert config.search_api_key == "brave-test-key" def test_brave_provider_prefers_explicit_api_key(monkeypatch) -> None: monkeypatch.delenv("BRAVE_SEARCH_API_KEY", raising=False) provider = BraveSearchProvider(api_key="brave-test-key") assert provider._api_key == "brave-test-key" def test_brave_provider_strips_trailing_paste_punctuation(monkeypatch) -> None: monkeypatch.delenv("BRAVE_SEARCH_API_KEY", raising=False) provider = BraveSearchProvider(api_key="brave-test-key、") assert provider._api_key == "brave-test-key" def test_web_search_kwargs_pass_brave_api_key() -> None: web.configure_search("brave", api_key="brave-test-key") assert web._search_provider_kwargs("brave")["api_key"] == "brave-test-key" def test_web_search_kwargs_pass_tavily_api_key() -> None: web.configure_search("tavily", api_key="tavily-test-key") assert web._search_provider_kwargs("tavily")["api_key"] == "tavily-test-key" def test_web_search_kwargs_pass_exa_api_key() -> None: web.configure_search("exa", api_key="exa-test-key") assert web._search_provider_kwargs("exa")["api_key"] == "exa-test-key" def test_exa_provider_prefers_explicit_api_key(monkeypatch) -> None: monkeypatch.delenv("EXA_API_KEY", raising=False) provider = ExaSearchProvider(api_key="exa-test-key") assert provider._api_key == "exa-test-key" @pytest.mark.asyncio async def test_brave_provider_maps_provider_source_and_published_at() -> None: def handler(request: httpx.Request) -> httpx.Response: return httpx.Response( 200, json={ "web": { "results": [ { "title": "Brave title", "url": "https://example.com/brave", "description": "Brave snippet", "age": "2026-06-19", } ] } }, ) provider = BraveSearchProvider( api_key="brave-test-key", transport=httpx.MockTransport(handler), ) result = (await provider.search("brave"))[0] assert result.provider == "brave" assert result.source == "brave" assert result.published_at == "2026-06-19" @pytest.mark.asyncio async def test_brave_provider_passes_recency_as_freshness() -> None: requests: list[httpx.Request] = [] def handler(request: httpx.Request) -> httpx.Response: requests.append(request) return httpx.Response(200, json={"web": {"results": []}}) provider = BraveSearchProvider( api_key="brave-test-key", transport=httpx.MockTransport(handler), ) await provider.search("brave", recency="week") assert requests[0].url.params["freshness"] == "pw" @pytest.mark.asyncio async def test_duckduckgo_provider_maps_provider_and_source() -> None: html = """
""" def handler(request: httpx.Request) -> httpx.Response: return httpx.Response(200, text=html) provider = DuckDuckGoProvider(transport=httpx.MockTransport(handler)) result = (await provider.search("duck"))[0] assert result.provider == "duckduckgo" assert result.source == "duckduckgo" @pytest.mark.asyncio async def test_duckduckgo_provider_treats_anomaly_challenge_as_blocked() -> None: html = """ """ def handler(request: httpx.Request) -> httpx.Response: return httpx.Response(202, text=html) provider = DuckDuckGoProvider(transport=httpx.MockTransport(handler)) with pytest.raises(SearchProviderError) as exc_info: await provider.search("duck") assert exc_info.value.provider == "duckduckgo" assert exc_info.value.kind == "blocked" assert exc_info.value.retryable is True @pytest.mark.asyncio async def test_duckduckgo_provider_allows_real_no_results() -> None: html = """