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
563 lines
20 KiB
Python
563 lines
20 KiB
Python
"""
|
|
Tests for the Open Library search engine.
|
|
Tests initialization, search functionality, and error handling.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock
|
|
|
|
|
|
class TestOpenLibrarySearchEngineInit:
|
|
"""Tests for Open Library search engine initialization."""
|
|
|
|
def test_init_default_parameters(self):
|
|
"""Test initialization with default parameters."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine()
|
|
|
|
assert engine.max_results == 10
|
|
assert engine.sort == "relevance"
|
|
assert engine.language is None
|
|
assert engine.search_field is None
|
|
assert engine.is_public is True
|
|
assert engine.is_books is True
|
|
assert engine.is_generic is False
|
|
|
|
def test_init_custom_parameters(self):
|
|
"""Test initialization with custom parameters."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine(
|
|
max_results=50,
|
|
sort="new",
|
|
language="eng",
|
|
search_field="title",
|
|
)
|
|
|
|
assert engine.max_results == 50
|
|
assert engine.sort == "new"
|
|
assert engine.language == "eng"
|
|
assert engine.search_field == "title"
|
|
|
|
def test_base_url_set(self):
|
|
"""Test that API base URL is correctly set."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine()
|
|
assert engine.base_url == "https://openlibrary.org"
|
|
assert engine.search_url == "https://openlibrary.org/search.json"
|
|
|
|
def test_user_agent_header_set(self):
|
|
"""Test that User-Agent header is set."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine()
|
|
assert "User-Agent" in engine.headers
|
|
assert "Local-Deep-Research" in engine.headers["User-Agent"]
|
|
|
|
|
|
class TestOpenLibraryQueryBuilding:
|
|
"""Tests for Open Library query parameter building."""
|
|
|
|
def test_build_query_params_basic(self):
|
|
"""Test basic query params building."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine(max_results=20)
|
|
params = engine._build_query_params("lord of the rings")
|
|
|
|
assert params["q"] == "lord of the rings"
|
|
assert params["limit"] == 20
|
|
assert "fields" in params
|
|
|
|
def test_build_query_params_title_search(self):
|
|
"""Test query params with title search field."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine(search_field="title")
|
|
params = engine._build_query_params("1984")
|
|
|
|
assert params["title"] == "1984"
|
|
assert "q" not in params
|
|
|
|
def test_build_query_params_author_search(self):
|
|
"""Test query params with author search field."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine(search_field="author")
|
|
params = engine._build_query_params("Tolkien")
|
|
|
|
assert params["author"] == "Tolkien"
|
|
assert "q" not in params
|
|
|
|
def test_build_query_params_with_language(self):
|
|
"""Test query params with language filter."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine(language="eng")
|
|
params = engine._build_query_params("test")
|
|
|
|
assert params["language"] == "eng"
|
|
|
|
def test_build_query_params_with_sort(self):
|
|
"""Test query params with sort option."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine(sort="new")
|
|
params = engine._build_query_params("test")
|
|
|
|
assert params["sort"] == "new"
|
|
|
|
|
|
class TestOpenLibraryCoverUrl:
|
|
"""Tests for Open Library cover URL generation."""
|
|
|
|
def test_get_cover_url_with_id(self):
|
|
"""Test cover URL generation with valid ID."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine()
|
|
url = engine._get_cover_url(12345)
|
|
|
|
assert url == "https://covers.openlibrary.org/b/id/12345-M.jpg"
|
|
|
|
def test_get_cover_url_with_size(self):
|
|
"""Test cover URL generation with custom size."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine()
|
|
url = engine._get_cover_url(12345, size="L")
|
|
|
|
assert url == "https://covers.openlibrary.org/b/id/12345-L.jpg"
|
|
|
|
def test_get_cover_url_without_id(self):
|
|
"""Test cover URL generation without ID returns None."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine()
|
|
url = engine._get_cover_url(None)
|
|
|
|
assert url is None
|
|
|
|
|
|
class TestOpenLibrarySearchExecution:
|
|
"""Tests for Open Library search execution."""
|
|
|
|
@pytest.fixture
|
|
def engine(self):
|
|
"""Create an Open Library engine."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
return OpenLibrarySearchEngine(max_results=10)
|
|
|
|
def test_get_previews_success(self, engine, monkeypatch):
|
|
"""Test successful preview retrieval."""
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json = Mock(
|
|
return_value={
|
|
"num_found": 100,
|
|
"docs": [
|
|
{
|
|
"key": "/works/OL27448W",
|
|
"title": "The Lord of the Rings",
|
|
"author_name": ["J.R.R. Tolkien"],
|
|
"author_key": ["OL26320A"],
|
|
"first_publish_year": 1954,
|
|
"publisher": ["HarperCollins"],
|
|
"subject": ["Fantasy", "Middle Earth", "Hobbits"],
|
|
"isbn": ["9780261103252"],
|
|
"cover_i": 8743908,
|
|
"edition_count": 500,
|
|
"has_fulltext": True,
|
|
"ebook_access": "borrowable",
|
|
}
|
|
],
|
|
}
|
|
)
|
|
mock_response.raise_for_status = Mock()
|
|
|
|
monkeypatch.setattr(
|
|
"local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
|
|
Mock(return_value=mock_response),
|
|
)
|
|
|
|
previews = engine._get_previews("lord of the rings")
|
|
|
|
assert len(previews) == 1
|
|
assert previews[0]["title"] == "The Lord of the Rings"
|
|
assert "J.R.R. Tolkien" in previews[0]["authors"]
|
|
assert previews[0]["first_publish_year"] == 1954
|
|
assert previews[0]["source"] == "Open Library"
|
|
assert previews[0]["cover_url"] is not None
|
|
|
|
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.json = Mock(return_value={"num_found": 0, "docs": []})
|
|
mock_response.raise_for_status = Mock()
|
|
|
|
monkeypatch.setattr(
|
|
"local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
|
|
Mock(return_value=mock_response),
|
|
)
|
|
|
|
previews = engine._get_previews("xyznonexistentbook123")
|
|
assert previews == []
|
|
|
|
def test_get_previews_rate_limit_error(self, engine, monkeypatch):
|
|
"""Test that 429 errors raise RateLimitError."""
|
|
from local_deep_research.web_search_engines.rate_limiting import (
|
|
RateLimitError,
|
|
)
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 429
|
|
|
|
monkeypatch.setattr(
|
|
"local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
|
|
Mock(return_value=mock_response),
|
|
)
|
|
|
|
with pytest.raises(RateLimitError):
|
|
engine._get_previews("test query")
|
|
|
|
def test_get_previews_handles_exception(self, engine, monkeypatch):
|
|
"""Test that exceptions are handled gracefully."""
|
|
import requests
|
|
|
|
monkeypatch.setattr(
|
|
"local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
|
|
Mock(side_effect=requests.RequestException("Network error")),
|
|
)
|
|
|
|
previews = engine._get_previews("test query")
|
|
assert previews == []
|
|
|
|
def test_get_previews_passes_user_agent_header(self, engine, monkeypatch):
|
|
"""Test that User-Agent header is passed to API requests."""
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json = Mock(return_value={"num_found": 0, "docs": []})
|
|
mock_response.raise_for_status = Mock()
|
|
|
|
mock_safe_get = Mock(return_value=mock_response)
|
|
monkeypatch.setattr(
|
|
"local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
|
|
mock_safe_get,
|
|
)
|
|
|
|
engine._get_previews("test")
|
|
|
|
# Verify headers were passed
|
|
call_kwargs = mock_safe_get.call_args[1]
|
|
assert "headers" in call_kwargs
|
|
assert "User-Agent" in call_kwargs["headers"]
|
|
|
|
|
|
class TestOpenLibraryFullContent:
|
|
"""Tests for Open Library full content retrieval."""
|
|
|
|
def test_get_full_content_builds_content(self):
|
|
"""Test that full content builds proper content string."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine()
|
|
|
|
items = [
|
|
{
|
|
"title": "Test Book",
|
|
"authors": ["Author One", "Author Two"],
|
|
"first_publish_year": 2020,
|
|
"edition_count": 5,
|
|
"subjects": ["Fiction", "Adventure"],
|
|
"has_fulltext": True,
|
|
"_raw": {
|
|
"language": ["eng", "fre"],
|
|
"subject": ["Fiction", "Adventure", "Fantasy"],
|
|
"publisher": ["Publisher One", "Publisher Two"],
|
|
},
|
|
}
|
|
]
|
|
|
|
from unittest.mock import patch
|
|
|
|
with patch.object(engine, "_fetch_work_details", return_value=None):
|
|
results = engine._get_full_content(items)
|
|
|
|
assert len(results) == 1
|
|
assert "Authors: Author One, Author Two" in results[0]["content"]
|
|
assert "First published: 2020" in results[0]["content"]
|
|
assert "Subjects: Fiction, Adventure, Fantasy" in results[0]["content"]
|
|
assert "Full text available" in results[0]["content"]
|
|
assert "_raw" not in results[0]
|
|
|
|
|
|
class TestOpenLibraryEdgeCases:
|
|
"""Tests for Open Library edge cases and error handling."""
|
|
|
|
@pytest.fixture
|
|
def engine(self):
|
|
"""Create an Open Library engine."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
return OpenLibrarySearchEngine(max_results=10)
|
|
|
|
def test_get_previews_unicode_in_title(self, engine, monkeypatch):
|
|
"""Test handling of Unicode characters in book title."""
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json = Mock(
|
|
return_value={
|
|
"num_found": 1,
|
|
"docs": [
|
|
{
|
|
"key": "/works/OL123W",
|
|
"title": "Les Misérables",
|
|
"author_name": ["Victor Hugo"],
|
|
}
|
|
],
|
|
}
|
|
)
|
|
mock_response.raise_for_status = Mock()
|
|
|
|
monkeypatch.setattr(
|
|
"local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
|
|
Mock(return_value=mock_response),
|
|
)
|
|
|
|
previews = engine._get_previews("les miserables")
|
|
assert len(previews) == 1
|
|
assert "Misérables" in previews[0]["title"]
|
|
|
|
def test_get_previews_html_entities_in_title(self, engine, monkeypatch):
|
|
"""Test that HTML entities in title are decoded."""
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json = Mock(
|
|
return_value={
|
|
"num_found": 1,
|
|
"docs": [
|
|
{
|
|
"key": "/works/OL123W",
|
|
"title": "Harry Potter & the Philosopher's Stone",
|
|
"author_name": ["J.K. Rowling"],
|
|
}
|
|
],
|
|
}
|
|
)
|
|
mock_response.raise_for_status = Mock()
|
|
|
|
monkeypatch.setattr(
|
|
"local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
|
|
Mock(return_value=mock_response),
|
|
)
|
|
|
|
previews = engine._get_previews("harry potter")
|
|
assert len(previews) == 1
|
|
# HTML entities should be decoded
|
|
assert "&" not in previews[0]["title"]
|
|
assert "&" in previews[0]["title"]
|
|
|
|
def test_get_previews_missing_optional_fields(self, engine, monkeypatch):
|
|
"""Test handling of missing optional fields."""
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json = Mock(
|
|
return_value={
|
|
"num_found": 1,
|
|
"docs": [
|
|
{
|
|
"key": "/works/OL123W",
|
|
"title": "Minimal Book",
|
|
# Missing: author_name, first_publish_year, publisher, etc.
|
|
}
|
|
],
|
|
}
|
|
)
|
|
mock_response.raise_for_status = Mock()
|
|
|
|
monkeypatch.setattr(
|
|
"local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
|
|
Mock(return_value=mock_response),
|
|
)
|
|
|
|
previews = engine._get_previews("test")
|
|
assert len(previews) == 1
|
|
assert previews[0]["title"] == "Minimal Book"
|
|
assert previews[0]["authors"] == []
|
|
assert previews[0]["cover_url"] is None
|
|
|
|
def test_get_previews_string_author_converted_to_list(
|
|
self, engine, monkeypatch
|
|
):
|
|
"""Test that string author is converted to list."""
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json = Mock(
|
|
return_value={
|
|
"num_found": 1,
|
|
"docs": [
|
|
{
|
|
"key": "/works/OL123W",
|
|
"title": "Test Book",
|
|
"author_name": "Single Author", # String instead of list
|
|
}
|
|
],
|
|
}
|
|
)
|
|
mock_response.raise_for_status = Mock()
|
|
|
|
monkeypatch.setattr(
|
|
"local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
|
|
Mock(return_value=mock_response),
|
|
)
|
|
|
|
previews = engine._get_previews("test")
|
|
assert len(previews) == 1
|
|
assert isinstance(previews[0]["authors"], list)
|
|
assert "Single Author" in previews[0]["authors"]
|
|
|
|
def test_get_previews_limits_authors_to_five(self, engine, monkeypatch):
|
|
"""Test that authors are limited to 5."""
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json = Mock(
|
|
return_value={
|
|
"num_found": 1,
|
|
"docs": [
|
|
{
|
|
"key": "/works/OL123W",
|
|
"title": "Multi-Author Book",
|
|
"author_name": [f"Author {i}" for i in range(10)],
|
|
}
|
|
],
|
|
}
|
|
)
|
|
mock_response.raise_for_status = Mock()
|
|
|
|
monkeypatch.setattr(
|
|
"local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
|
|
Mock(return_value=mock_response),
|
|
)
|
|
|
|
previews = engine._get_previews("test")
|
|
assert len(previews) == 1
|
|
assert len(previews[0]["authors"]) == 5
|
|
|
|
def test_get_full_content_without_raw(self, engine):
|
|
"""Test full content handling when _raw is missing."""
|
|
items = [
|
|
{
|
|
"title": "Test Book",
|
|
"authors": ["Test Author"],
|
|
}
|
|
]
|
|
|
|
results = engine._get_full_content(items)
|
|
assert len(results) == 1
|
|
# Should not crash
|
|
|
|
def test_all_sort_options(self):
|
|
"""Test that various sort options can be used."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
sort_options = ["relevance", "new", "old", "random"]
|
|
for sort in sort_options:
|
|
engine = OpenLibrarySearchEngine(sort=sort)
|
|
assert engine.sort == sort
|
|
|
|
def test_all_search_fields(self):
|
|
"""Test that all search field options work."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
fields = [None, "title", "author", "subject"]
|
|
for field in fields:
|
|
engine = OpenLibrarySearchEngine(search_field=field)
|
|
assert engine.search_field == field
|
|
|
|
|
|
class TestOpenLibraryHelperMethods:
|
|
"""Tests for Open Library helper methods."""
|
|
|
|
def test_get_book_by_isbn_success(self, monkeypatch):
|
|
"""Test successful book retrieval by ISBN."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
|
|
engine = OpenLibrarySearchEngine()
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json = Mock(
|
|
return_value={
|
|
"title": "Test Book",
|
|
"authors": [{"key": "/authors/OL123A"}],
|
|
}
|
|
)
|
|
mock_response.raise_for_status = Mock()
|
|
|
|
monkeypatch.setattr(
|
|
"local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
|
|
Mock(return_value=mock_response),
|
|
)
|
|
|
|
book = engine.get_book_by_isbn("9780261103252")
|
|
|
|
assert book is not None
|
|
assert book["title"] == "Test Book"
|
|
|
|
def test_get_book_by_isbn_handles_error(self, monkeypatch):
|
|
"""Test that errors are handled in ISBN lookup."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_openlibrary import (
|
|
OpenLibrarySearchEngine,
|
|
)
|
|
import requests
|
|
|
|
engine = OpenLibrarySearchEngine()
|
|
|
|
monkeypatch.setattr(
|
|
"local_deep_research.web_search_engines.engines.search_engine_openlibrary.safe_get",
|
|
Mock(side_effect=requests.RequestException("Not found")),
|
|
)
|
|
|
|
book = engine.get_book_by_isbn("invalid")
|
|
assert book is None
|