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

560 lines
20 KiB
Python

"""
Comprehensive tests for the Google Programmable Search Engine.
Tests initialization, search functionality, error handling, and retry logic.
Note: These tests mock HTTP requests to avoid requiring an API key.
"""
import pytest
from unittest.mock import Mock
class TestGooglePSESearchEngineInit:
"""Tests for Google PSE search engine initialization."""
@pytest.fixture(autouse=True)
def mock_validation(self, monkeypatch):
"""Mock validation to avoid HTTP requests during init."""
# Mock safe_get to return a valid test response
mock_response = Mock()
mock_response.status_code = 200
mock_response.raise_for_status = Mock()
mock_response.json = Mock(return_value={"items": []})
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(return_value=mock_response),
)
def test_init_with_credentials(self):
"""Test initialization with API key and search engine ID."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_api_key", search_engine_id="test_engine_id"
)
assert engine.api_key == "test_api_key"
assert engine.search_engine_id == "test_engine_id"
assert engine.max_results >= 10
assert engine.is_public is True
assert engine.is_generic is True
def test_init_custom_max_results(self):
"""Test initialization with custom max results."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key",
search_engine_id="test_id",
max_results=25,
)
assert engine.max_results >= 25
def test_init_with_region(self):
"""Test initialization with specific region."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key",
search_engine_id="test_id",
region="de",
)
assert engine.region == "de"
def test_init_with_safe_search(self):
"""Test initialization with safe search options."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key",
search_engine_id="test_id",
safe_search=True,
)
assert engine.safe == "active"
engine_off = GooglePSESearchEngine(
api_key="test_key",
search_engine_id="test_id",
safe_search=False,
)
assert engine_off.safe == "off"
def test_init_without_api_key_raises(self, monkeypatch):
"""Test that initialization without API key raises ValueError."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
# Mock get_setting_from_snapshot to return None
monkeypatch.setattr(
"local_deep_research.config.thread_settings.get_setting_from_snapshot",
lambda *args, **kwargs: None,
)
with pytest.raises(ValueError, match="Google API key is required"):
GooglePSESearchEngine(search_engine_id="test_id")
def test_init_without_engine_id_raises(self, monkeypatch):
"""Test that initialization without engine ID raises ValueError."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
# Mock get_setting_from_snapshot to return None for engine_id
def mock_get_setting(key, **kwargs):
if "api_key" in key:
return "test_key"
return None
monkeypatch.setattr(
"local_deep_research.config.thread_settings.get_setting_from_snapshot",
mock_get_setting,
)
with pytest.raises(
ValueError, match="Google Search Engine ID is required"
):
GooglePSESearchEngine()
def test_init_with_language(self):
"""Test initialization with different languages."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key",
search_engine_id="test_id",
search_language="German",
)
assert engine.language == "de"
engine_jp = GooglePSESearchEngine(
api_key="test_key",
search_engine_id="test_id",
search_language="Japanese",
)
assert engine_jp.language == "ja"
class TestGooglePSEEngineType:
"""Tests for Google PSE engine type identification."""
@pytest.fixture(autouse=True)
def mock_validation(self, monkeypatch):
"""Mock validation to avoid HTTP requests during init."""
mock_response = Mock()
mock_response.status_code = 200
mock_response.raise_for_status = Mock()
mock_response.json = Mock(return_value={"items": []})
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(return_value=mock_response),
)
def test_engine_type_set(self):
"""Test that engine type is properly set."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key", search_engine_id="test_id"
)
assert "google" in engine.engine_type.lower()
def test_engine_is_public(self):
"""Test that engine is marked as public."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key", search_engine_id="test_id"
)
assert engine.is_public is True
def test_engine_is_generic(self):
"""Test that engine is marked as generic."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key", search_engine_id="test_id"
)
assert engine.is_generic is True
class TestGooglePSERetryConfig:
"""Tests for Google PSE retry configuration."""
@pytest.fixture(autouse=True)
def mock_validation(self, monkeypatch):
"""Mock validation to avoid HTTP requests during init."""
mock_response = Mock()
mock_response.status_code = 200
mock_response.raise_for_status = Mock()
mock_response.json = Mock(return_value={"items": []})
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(return_value=mock_response),
)
def test_default_retry_config(self):
"""Test default retry configuration."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key", search_engine_id="test_id"
)
assert engine.max_retries == 3
assert engine.retry_delay == 2.0
def test_custom_retry_config(self):
"""Test custom retry configuration."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key",
search_engine_id="test_id",
max_retries=5,
retry_delay=1.0,
)
assert engine.max_retries == 5
assert engine.retry_delay == 1.0
class TestGooglePSERateLimiting:
"""Tests for Google PSE rate limiting."""
@pytest.fixture(autouse=True)
def mock_validation(self, monkeypatch):
"""Mock validation to avoid HTTP requests during init."""
mock_response = Mock()
mock_response.status_code = 200
mock_response.raise_for_status = Mock()
mock_response.json = Mock(return_value={"items": []})
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(return_value=mock_response),
)
def test_min_request_interval_set(self):
"""Test that minimum request interval is set."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key", search_engine_id="test_id"
)
assert hasattr(engine, "min_request_interval")
assert engine.min_request_interval == 0.5
def test_last_request_time_initialized(self):
"""Test that last request time is initialized."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key", search_engine_id="test_id"
)
assert hasattr(engine, "last_request_time")
assert engine.last_request_time == 0
class TestGooglePSESearchExecution:
"""Tests for Google PSE search execution."""
@pytest.fixture
def engine(self, monkeypatch):
"""Create a Google PSE engine with mocked validation."""
mock_response = Mock()
mock_response.status_code = 200
mock_response.raise_for_status = Mock()
mock_response.json = Mock(return_value={"items": []})
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(return_value=mock_response),
)
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
return GooglePSESearchEngine(
api_key="test_key", search_engine_id="test_id"
)
def test_get_previews_success(self, monkeypatch):
"""Test successful preview retrieval."""
# First, mock validation to allow engine creation
validation_response = Mock()
validation_response.status_code = 200
validation_response.raise_for_status = Mock()
validation_response.json = Mock(return_value={"items": []})
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(return_value=validation_response),
)
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
# Create engine with max_results=2 to match expected results
engine = GooglePSESearchEngine(
api_key="test_key", search_engine_id="test_id", max_results=2
)
# Now mock the search response
search_response = Mock()
search_response.status_code = 200
search_response.raise_for_status = Mock()
search_response.json = Mock(
return_value={
"items": [
{
"title": "Test Result 1",
"link": "https://example1.com",
"snippet": "This is a test snippet 1",
},
{
"title": "Test Result 2",
"link": "https://example2.com",
"snippet": "This is a test snippet 2",
},
]
}
)
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(return_value=search_response),
)
previews = engine._get_previews("test query")
assert len(previews) == 2
assert previews[0]["title"] == "Test Result 1"
assert previews[1]["link"] == "https://example2.com"
def test_get_previews_empty_results(self, engine, monkeypatch):
"""Test preview retrieval with no results."""
mock_response = Mock()
mock_response.status_code = 200
mock_response.raise_for_status = Mock()
mock_response.json = Mock(return_value={})
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(return_value=mock_response),
)
previews = engine._get_previews("test query")
assert previews == []
def test_get_previews_skips_results_without_url(self, monkeypatch):
"""Test that results without URLs are skipped."""
# First, mock validation to allow engine creation
validation_response = Mock()
validation_response.status_code = 200
validation_response.raise_for_status = Mock()
validation_response.json = Mock(return_value={"items": []})
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(return_value=validation_response),
)
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
# Create engine with max_results=1 to match expected valid results
engine = GooglePSESearchEngine(
api_key="test_key", search_engine_id="test_id", max_results=1
)
# Now mock the search response
search_response = Mock()
search_response.status_code = 200
search_response.raise_for_status = Mock()
search_response.json = Mock(
return_value={
"items": [
{
"title": "Test Without URL",
"snippet": "No link here",
},
{
"title": "Test With URL",
"link": "https://example.com",
"snippet": "Has a link",
},
]
}
)
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(return_value=search_response),
)
previews = engine._get_previews("test query")
assert len(previews) == 1
assert previews[0]["title"] == "Test With URL"
class TestGooglePSEErrorHandling:
"""Tests for Google PSE error handling."""
@pytest.fixture
def engine(self, monkeypatch):
"""Create a Google PSE engine with mocked validation."""
mock_response = Mock()
mock_response.status_code = 200
mock_response.raise_for_status = Mock()
mock_response.json = Mock(return_value={"items": []})
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(return_value=mock_response),
)
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
return GooglePSESearchEngine(
api_key="test_key", search_engine_id="test_id", max_retries=1
)
def test_rate_limit_429_raises_error(self, engine, monkeypatch):
"""Test that 429 errors raise RateLimitError."""
from requests.exceptions import RequestException
from local_deep_research.web_search_engines.rate_limiting import (
RateLimitError,
)
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(side_effect=RequestException("429 Too Many Requests")),
)
with pytest.raises(RateLimitError):
engine._make_request("test query")
def test_quota_exceeded_raises_rate_limit_error(self, engine, monkeypatch):
"""Test that quota exceeded errors raise RateLimitError."""
from requests.exceptions import RequestException
from local_deep_research.web_search_engines.rate_limiting import (
RateLimitError,
)
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(side_effect=RequestException("quotaExceeded")),
)
with pytest.raises(RateLimitError):
engine._make_request("test query")
def test_general_error_raises_request_exception(self, engine, monkeypatch):
"""Test that general errors raise RequestException after retries."""
from requests.exceptions import RequestException
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(side_effect=RequestException("Network error")),
)
with pytest.raises(RequestException):
engine._make_request("test query")
class TestGooglePSELanguageMapping:
"""Tests for Google PSE language code mapping."""
@pytest.fixture(autouse=True)
def mock_validation(self, monkeypatch):
"""Mock validation to avoid HTTP requests during init."""
mock_response = Mock()
mock_response.status_code = 200
mock_response.raise_for_status = Mock()
mock_response.json = Mock(return_value={"items": []})
monkeypatch.setattr(
"local_deep_research.web_search_engines.engines.search_engine_google_pse.safe_get",
Mock(return_value=mock_response),
)
def test_english_language(self):
"""Test English language mapping."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key",
search_engine_id="test_id",
search_language="English",
)
assert engine.language == "en"
def test_spanish_language(self):
"""Test Spanish language mapping."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key",
search_engine_id="test_id",
search_language="Spanish",
)
assert engine.language == "es"
def test_chinese_language(self):
"""Test Chinese language mapping."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key",
search_engine_id="test_id",
search_language="Chinese",
)
assert engine.language == "zh-CN"
def test_unknown_language_defaults_to_english(self):
"""Test that unknown language defaults to English."""
from local_deep_research.web_search_engines.engines.search_engine_google_pse import (
GooglePSESearchEngine,
)
engine = GooglePSESearchEngine(
api_key="test_key",
search_engine_id="test_id",
search_language="UnknownLanguage",
)
assert engine.language == "en"