CtrlK
BlogDocsLog inGet started
Tessl Logo

jobe-skills/online-book-lookup

Use when the user asks to look up online book metadata by ISBN, title, author, publication details, editions, subjects, covers, or Open Library source URLs.

100

Quality

100%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

test_lookup_book.pytests/

from __future__ import annotations

import importlib.util
import io
import json
import sys
import unittest
from pathlib import Path
from unittest import mock
from urllib import error


SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "lookup_book.py"
SPEC = importlib.util.spec_from_file_location("lookup_book", SCRIPT_PATH)
if SPEC is None:
    raise ImportError(f"Could not load spec for {SCRIPT_PATH}")
lookup_book = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
sys.modules[SPEC.name] = lookup_book
SPEC.loader.exec_module(lookup_book)


class FakeResponse:
    def __init__(self, payload):
        self.payload = payload

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc, tb):
        return False

    def read(self):
        if isinstance(self.payload, bytes):
            return self.payload
        return json.dumps(self.payload).encode("utf-8")


class LookupBookTests(unittest.TestCase):
    def test_search_query_constructs_url_and_normalizes_result(self):
        payload = {
            "numFound": 1,
            "docs": [
                {
                    "key": "/works/OL893415W",
                    "title": "Dune",
                    "author_name": ["Frank Herbert"],
                    "first_publish_year": 1965,
                    "publish_date": ["1965", "1990"],
                    "publisher": ["Chilton Books"],
                    "isbn": ["0441172717", "9780441172719"],
                    "cover_i": 12345,
                    "edition_key": ["OL12345M"],
                    "subject": ["Science fiction", "Arrakis"],
                }
            ],
        }

        with mock.patch.object(
            lookup_book.request,
            "urlopen",
            return_value=FakeResponse(payload),
        ) as urlopen:
            result = lookup_book.lookup_search(
                query="Dune",
                title=None,
                author=None,
                limit=5,
                timeout=15,
            )

        requested_url = urlopen.call_args.args[0].full_url
        self.assertIn("/search.json?", requested_url)
        self.assertIn("q=Dune", requested_url)
        self.assertIn("fields=", requested_url)
        self.assertEqual(result["num_found"], 1)
        self.assertEqual(result["results"][0]["title"], "Dune")
        self.assertEqual(result["results"][0]["authors"], ["Frank Herbert"])
        self.assertEqual(result["results"][0]["isbn_10"], ["0441172717"])
        self.assertEqual(result["results"][0]["isbn_13"], ["9780441172719"])
        self.assertEqual(result["results"][0]["openlibrary_edition_key"], "/books/OL12345M")
        self.assertIn("medium", result["results"][0]["cover_urls"])

    def test_search_result_drops_invalid_remote_keys_and_cover_ids(self):
        payload = {
            "numFound": 1,
            "docs": [
                {
                    "key": "/works/../../bad",
                    "title": "Injected\nTitle",
                    "author_name": ["Author"],
                    "edition_key": ["https://example.com/evil"],
                    "cover_i": "../../not-a-cover",
                }
            ],
        }

        with mock.patch.object(
            lookup_book.request,
            "urlopen",
            return_value=FakeResponse(payload),
        ):
            result = lookup_book.lookup_search(
                query="Injected Title",
                title=None,
                author=None,
                limit=5,
                timeout=15,
            )

        book = result["results"][0]
        self.assertEqual(book["title"], "Injected Title")
        self.assertIsNone(book["openlibrary_work_key"])
        self.assertIsNone(book["openlibrary_edition_key"])
        self.assertEqual(book["cover_urls"], {})
        self.assertEqual(book["source_urls"], {})

    def test_title_author_search_constructs_structured_query(self):
        with mock.patch.object(
            lookup_book.request,
            "urlopen",
            return_value=FakeResponse({"numFound": 0, "docs": []}),
        ) as urlopen:
            result = lookup_book.lookup_search(
                query=None,
                title="The Left Hand of Darkness",
                author="Ursula K. Le Guin",
                limit=3,
                timeout=15,
            )

        requested_url = urlopen.call_args.args[0].full_url
        self.assertIn("title=The+Left+Hand+of+Darkness", requested_url)
        self.assertIn("author=Ursula+K.+Le+Guin", requested_url)
        self.assertIn("limit=3", requested_url)
        self.assertEqual(result["query"]["title"], "The Left Hand of Darkness")
        self.assertEqual(result["query"]["author"], "Ursula K. Le Guin")
        self.assertEqual(result["results"], [])

    def test_isbn_lookup_uses_fixed_isbn_search_for_enrichment(self):
        edition = {
            "key": "/books/OL7353617M",
            "title": "Fantastic Mr. Fox",
            "authors": [{"key": "/authors/OL34184A"}],
            "works": [{"key": "/works/OL45804W"}],
            "publish_date": "1998",
            "publishers": ["Puffin"],
            "isbn_10": ["0140328726"],
            "isbn_13": ["9780140328721"],
            "covers": [8739161],
        }
        isbn_search = {
            "numFound": 1,
            "docs": [
                {
                    "key": "/works/OL45804W",
                    "title": "Fantastic Mr. Fox",
                    "author_name": ["Roald Dahl"],
                    "first_publish_year": 1970,
                    "subject": ["Foxes", "Farmers"],
                    "edition_key": ["OL7353617M"],
                    "isbn": ["0140328726", "9780140328721"],
                }
            ],
        }

        def fake_urlopen(req, timeout):
            url = req.full_url
            if url.endswith("/isbn/9780140328721.json"):
                return FakeResponse(edition)
            if "/search.json?" in url and "isbn=9780140328721" in url:
                return FakeResponse(isbn_search)
            raise AssertionError(f"unexpected URL {url}")

        with mock.patch.object(lookup_book.request, "urlopen", side_effect=fake_urlopen):
            result = lookup_book.lookup_isbn("978-0-140-32872-1", timeout=15)

        self.assertEqual(result["lookup_type"], "isbn")
        self.assertEqual(result["query"]["isbn"], "9780140328721")
        self.assertEqual(result["num_found"], 1)
        book = result["results"][0]
        self.assertEqual(book["title"], "Fantastic Mr. Fox")
        self.assertEqual(book["authors"], ["Roald Dahl"])
        self.assertEqual(book["first_publish_year"], 1970)
        self.assertEqual(book["isbn_10"], ["0140328726"])
        self.assertEqual(book["isbn_13"], ["9780140328721"])
        self.assertEqual(book["subjects"], ["Foxes", "Farmers"])
        self.assertEqual(book["source_urls"]["work_api"], "https://openlibrary.org/works/OL45804W.json")
        self.assertEqual(book["source_urls"]["isbn_api"], "https://openlibrary.org/isbn/9780140328721.json")
        self.assertEqual(book["source_urls"]["edition_api"], "https://openlibrary.org/books/OL7353617M.json")

    def test_isbn_lookup_does_not_follow_invalid_remote_keys(self):
        edition = {
            "key": "/books/OL7353617M",
            "title": "Untrusted Edition",
            "authors": [{"key": "/authors/../../bad"}],
            "works": [{"key": "https://example.com/works/OL45804W"}],
            "publishers": ["Publisher"],
            "isbn_13": ["9780140328721"],
        }

        def fake_urlopen(req, timeout):
            url = req.full_url
            if url.endswith("/isbn/9780140328721.json"):
                return FakeResponse(edition)
            if "/search.json?" in url and "isbn=9780140328721" in url:
                return FakeResponse({"numFound": 0, "docs": []})
            raise AssertionError(f"unexpected follow-up URL {url}")

        with mock.patch.object(lookup_book.request, "urlopen", side_effect=fake_urlopen):
            result = lookup_book.lookup_isbn("9780140328721", timeout=15)

        book = result["results"][0]
        self.assertEqual(book["title"], "Untrusted Edition")
        self.assertEqual(book["authors"], [])
        self.assertIsNone(book["openlibrary_work_key"])
        self.assertEqual(book["openlibrary_edition_key"], "/books/OL7353617M")
        self.assertNotIn("work_api", book["source_urls"])

    def test_isbn_404_returns_empty_success(self):
        def fake_urlopen(req, timeout):
            raise error.HTTPError(
                req.full_url,
                404,
                "Not Found",
                {},
                io.BytesIO(b"not found"),
            )

        with mock.patch.object(lookup_book.request, "urlopen", side_effect=fake_urlopen):
            result = lookup_book.lookup_isbn("9780000000000", timeout=15)

        self.assertEqual(result["num_found"], 0)
        self.assertEqual(result["results"], [])

    def test_limit_validation_rejects_values_above_cap(self):
        with self.assertRaises(lookup_book.ScriptError) as ctx:
            lookup_book.validate_limit(21)

        self.assertEqual(ctx.exception.error_code, "INVALID_LIMIT")

    def test_invalid_json_is_reported_as_structured_error(self):
        with mock.patch.object(
            lookup_book.request,
            "urlopen",
            return_value=FakeResponse(b"not-json"),
        ):
            with self.assertRaises(lookup_book.ScriptError) as ctx:
                lookup_book.fetch_json("https://openlibrary.org/search.json?q=x", timeout=15)

        self.assertEqual(ctx.exception.error_code, "INVALID_JSON_RESPONSE")

    def test_run_reports_http_error_without_traceback(self):
        def fake_urlopen(req, timeout):
            raise error.HTTPError(
                req.full_url,
                500,
                "Server Error",
                {},
                io.BytesIO(b"upstream failed"),
            )

        with mock.patch.object(lookup_book.request, "urlopen", side_effect=fake_urlopen):
            with mock.patch("sys.stdout", new_callable=io.StringIO) as stdout:
                rc = lookup_book.run(["--query", "Dune"])

        self.assertEqual(rc, 3)
        payload = json.loads(stdout.getvalue())
        self.assertEqual(payload["error_code"], "HTTP_ERROR")
        self.assertIn("HTTP 500", payload["error"])

    def test_invalid_argument_combination_is_reported(self):
        with mock.patch("sys.stdout", new_callable=io.StringIO) as stdout:
            rc = lookup_book.run(["--isbn", "9780140328721", "--query", "fox"])

        self.assertEqual(rc, 2)
        payload = json.loads(stdout.getvalue())
        self.assertEqual(payload["error_code"], "INVALID_ARGUMENTS")


if __name__ == "__main__":
    unittest.main()

SKILL.md

tile.json