""" 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