auto trim TOC, rewrite test to use actual MPC calls
This commit is contained in:
@@ -161,11 +161,102 @@ def create_search_targets():
|
||||
doc.close()
|
||||
|
||||
|
||||
def create_large_toc():
|
||||
"""PDF with a very large multi-level TOC to test auto-trimming."""
|
||||
doc = fitz.open()
|
||||
toc = []
|
||||
page_num = 0
|
||||
|
||||
for part in range(1, 6):
|
||||
page_num += 1
|
||||
page = doc.new_page(width=595, height=842)
|
||||
page.insert_text((72, 80), f"Part {part}", fontsize=18)
|
||||
toc.append([1, f"Part {part}", page_num])
|
||||
|
||||
for chapter in range(1, 9):
|
||||
page_num += 1
|
||||
page = doc.new_page(width=595, height=842)
|
||||
title = f"Chapter {part}.{chapter}"
|
||||
page.insert_text((72, 80), title, fontsize=14)
|
||||
toc.append([2, title, page_num])
|
||||
|
||||
for section in range(1, 5):
|
||||
page_num += 1
|
||||
page = doc.new_page(width=595, height=842)
|
||||
title = f"Section {part}.{chapter}.{section}"
|
||||
page.insert_text((72, 80), title, fontsize=12)
|
||||
toc.append([3, title, page_num])
|
||||
|
||||
doc.set_toc(toc)
|
||||
doc.save(ASSETS / "large_toc.pdf")
|
||||
doc.close()
|
||||
|
||||
|
||||
def create_mega_toc():
|
||||
"""PDF with 400+ pages and 4-level deep TOC for comprehensive auto-trim testing.
|
||||
|
||||
Structure:
|
||||
2 volumes (L1) × 8 chapters (L2) × 8 sections (L3) × 2 subsections (L4)
|
||||
|
||||
Entry counts:
|
||||
Level 1: 2
|
||||
Level 2: 16 (cumulative ≤L2: 18)
|
||||
Level 3: 128 (cumulative ≤L3: 146)
|
||||
Level 4: 256 (cumulative ≤L4: 402)
|
||||
|
||||
Full-TOC auto-trim (threshold 100):
|
||||
≤L2 = 18 fits → best_level=2, total=18, auto_trimmed_to_level=2
|
||||
|
||||
Volume 1 subtree (parent="Volume 1"):
|
||||
8 chapters + 64 sections + 128 subsections = 200 children
|
||||
≤L2 = 8, ≤L3 = 72 fits → best_level=3, total=72, auto_trimmed_to_level=2 (relative)
|
||||
|
||||
Chapter 1.1 subtree (parent="Chapter 1.1"):
|
||||
8 sections + 16 subsections = 24 children (< 100, no auto-trim)
|
||||
"""
|
||||
doc = fitz.open()
|
||||
toc = []
|
||||
page_num = 0
|
||||
|
||||
for vol in range(1, 3):
|
||||
page_num += 1
|
||||
page = doc.new_page(width=595, height=842)
|
||||
page.insert_text((72, 80), f"Volume {vol}", fontsize=18)
|
||||
toc.append([1, f"Volume {vol}", page_num])
|
||||
|
||||
for ch in range(1, 9):
|
||||
page_num += 1
|
||||
page = doc.new_page(width=595, height=842)
|
||||
title = f"Chapter {vol}.{ch}"
|
||||
page.insert_text((72, 80), title, fontsize=14)
|
||||
toc.append([2, title, page_num])
|
||||
|
||||
for sec in range(1, 9):
|
||||
page_num += 1
|
||||
page = doc.new_page(width=595, height=842)
|
||||
title = f"Section {vol}.{ch}.{sec}"
|
||||
page.insert_text((72, 80), title, fontsize=12)
|
||||
toc.append([3, title, page_num])
|
||||
|
||||
for sub in range(1, 3):
|
||||
page_num += 1
|
||||
page = doc.new_page(width=595, height=842)
|
||||
title = f"Subsection {vol}.{ch}.{sec}.{sub}"
|
||||
page.insert_text((72, 80), title, fontsize=11)
|
||||
toc.append([4, title, page_num])
|
||||
|
||||
doc.set_toc(toc)
|
||||
doc.save(ASSETS / "mega_toc.pdf")
|
||||
doc.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_basic()
|
||||
create_with_diagrams()
|
||||
create_nested_toc()
|
||||
create_search_targets()
|
||||
create_large_toc()
|
||||
create_mega_toc()
|
||||
print(f"Generated test PDFs in {ASSETS}")
|
||||
for f in sorted(ASSETS.glob("*.pdf")):
|
||||
print(f" {f.name}")
|
||||
|
||||
+94
-13
@@ -13,14 +13,15 @@ mcp = FastMCP(
|
||||
)
|
||||
|
||||
HEADER_FOOTER_MARGIN_PTS = 50
|
||||
TOC_AUTO_TRIM_THRESHOLD = 100
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_pdf_info(filename: str) -> dict:
|
||||
"""Get metadata and basic info about a PDF file.
|
||||
|
||||
Returns page count, title, author, subject, creator, producer,
|
||||
creation/modification dates, and encryption info.
|
||||
Args:
|
||||
filename: Path to a PDF file.
|
||||
"""
|
||||
doc = fitz.open(filename)
|
||||
info = {
|
||||
@@ -34,16 +35,94 @@ def get_pdf_info(filename: str) -> dict:
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_table_of_contents(filename: str) -> list[dict]:
|
||||
def get_table_of_contents(
|
||||
filename: str,
|
||||
parent: str | None = None,
|
||||
max_level: int | None = None,
|
||||
) -> dict:
|
||||
"""Get the table of contents (bookmarks/outline) from a PDF.
|
||||
|
||||
Returns a list of entries with level, title, and page number.
|
||||
These correspond to the sections shown in a PDF reader's sidebar.
|
||||
When max_level is not specified and the TOC is very large, the depth is automatically
|
||||
reduced to keep the response manageable. The response will include an
|
||||
``auto_trimmed_to_level`` field and a hint when this happens.
|
||||
|
||||
Args:
|
||||
filename: Path to a PDF file.
|
||||
parent: Return only children of the entry whose title contains this string
|
||||
(case-insensitive). For example, pass a chapter title to get its sections.
|
||||
max_level: Only include entries up to this depth (1=chapters only, 2=sections, etc.).
|
||||
When used with parent, levels are relative: 1 means direct children only,
|
||||
2 means children and grandchildren, etc.
|
||||
"""
|
||||
doc = fitz.open(filename)
|
||||
toc = doc.get_toc()
|
||||
doc.close()
|
||||
return [{"level": level, "title": title, "page": page} for level, title, page in toc]
|
||||
|
||||
# Track whether max_level was explicitly requested
|
||||
explicit_max_level = max_level is not None
|
||||
base_level = 0
|
||||
|
||||
if parent is not None:
|
||||
parent_lower = parent.lower()
|
||||
# Find the first entry whose title matches
|
||||
parent_idx = None
|
||||
parent_level = None
|
||||
for i, (level, title, _page) in enumerate(toc):
|
||||
if parent_lower in title.lower():
|
||||
parent_idx = i
|
||||
parent_level = level
|
||||
break
|
||||
if parent_idx is None:
|
||||
return {
|
||||
"error": f"No TOC entry matching '{parent}'",
|
||||
"total_entries": 0,
|
||||
"entries": [],
|
||||
}
|
||||
# Collect all subsequent entries until we hit an entry at the same or higher level
|
||||
children = []
|
||||
for level, title, page in toc[parent_idx + 1 :]:
|
||||
if level <= parent_level:
|
||||
break
|
||||
children.append((level, title, page))
|
||||
toc = children
|
||||
base_level = parent_level
|
||||
# Make max_level relative to parent
|
||||
if max_level is not None:
|
||||
abs_max_level = parent_level + max_level
|
||||
toc = [entry for entry in toc if entry[0] <= abs_max_level]
|
||||
elif max_level is not None:
|
||||
toc = [entry for entry in toc if entry[0] <= max_level]
|
||||
|
||||
# Auto-trim depth when max_level was not explicitly requested and the TOC is large
|
||||
applied_max_level = None
|
||||
untrimmed_total = len(toc)
|
||||
if not explicit_max_level and len(toc) > TOC_AUTO_TRIM_THRESHOLD:
|
||||
levels_present = sorted(set(entry[0] for entry in toc))
|
||||
# Find the deepest level that keeps entries at or below the threshold.
|
||||
# If even the shallowest level exceeds it, use that (can't go shallower).
|
||||
best_level = levels_present[0]
|
||||
for try_level in levels_present:
|
||||
count = sum(1 for entry in toc if entry[0] <= try_level)
|
||||
if count <= TOC_AUTO_TRIM_THRESHOLD:
|
||||
best_level = try_level
|
||||
else:
|
||||
break
|
||||
toc = [entry for entry in toc if entry[0] <= best_level]
|
||||
applied_max_level = best_level - base_level
|
||||
|
||||
entries = [
|
||||
{"level": level, "title": title, "page": page} for level, title, page in toc
|
||||
]
|
||||
|
||||
result = {"total_entries": len(entries), "entries": entries}
|
||||
if applied_max_level is not None:
|
||||
result["auto_trimmed_to_level"] = applied_max_level
|
||||
result["hint"] = (
|
||||
f"TOC has {untrimmed_total} entries; trimmed to level {applied_max_level} "
|
||||
f"({len(entries)} entries). Use max_level to request a specific depth, "
|
||||
"or use parent to narrow to a specific section."
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@@ -57,7 +136,7 @@ def get_page_text(
|
||||
"""Extract text content from one or more PDF pages.
|
||||
|
||||
Args:
|
||||
filename: Path to the PDF file.
|
||||
filename: Path to a PDF file.
|
||||
start_page: First page number (1-indexed, inclusive).
|
||||
end_page: Last page number (1-indexed, inclusive). Defaults to start_page.
|
||||
format: Output format. "json" returns structured page data with block/line/span
|
||||
@@ -132,7 +211,7 @@ def get_page_image(
|
||||
"""Render a single PDF page as a PNG image.
|
||||
|
||||
Args:
|
||||
filename: Path to the PDF file.
|
||||
filename: Path to a PDF file.
|
||||
page: Page number (1-indexed).
|
||||
dpi: Image resolution. Default 150 (good balance of readability and size).
|
||||
output: "base64" returns the image inline as MCP image content.
|
||||
@@ -166,7 +245,7 @@ def search_text(
|
||||
Returns a list of hits with page number and surrounding context.
|
||||
|
||||
Args:
|
||||
filename: Path to the PDF file.
|
||||
filename: Path to a PDF file.
|
||||
query: Text to search for.
|
||||
context_chars: Characters of context to include around each hit. Default 100.
|
||||
"""
|
||||
@@ -185,10 +264,12 @@ def search_text(
|
||||
break
|
||||
ctx_start = max(0, idx - context_chars)
|
||||
ctx_end = min(len(text), idx + len(query) + context_chars)
|
||||
hits.append({
|
||||
"page": page_idx + 1,
|
||||
"context": text[ctx_start:ctx_end],
|
||||
})
|
||||
hits.append(
|
||||
{
|
||||
"page": page_idx + 1,
|
||||
"context": text[ctx_start:ctx_end],
|
||||
}
|
||||
)
|
||||
pos = idx + 1
|
||||
|
||||
doc.close()
|
||||
|
||||
+24
-1
@@ -1,23 +1,36 @@
|
||||
"""Shared fixtures for pdf-mcp tests."""
|
||||
|
||||
import asyncio
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from pdf_mcp.server import mcp
|
||||
|
||||
ASSETS = Path(__file__).parent.parent / "assets"
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
"""Generate test PDFs before the test session if they don't exist."""
|
||||
expected = ["basic.pdf", "diagrams.pdf", "nested_toc.pdf", "search_targets.pdf"]
|
||||
expected = ["basic.pdf", "diagrams.pdf", "nested_toc.pdf", "search_targets.pdf", "large_toc.pdf", "mega_toc.pdf"]
|
||||
if not all((ASSETS / f).exists() for f in expected):
|
||||
subprocess.check_call(
|
||||
[sys.executable, str(ASSETS / "generate.py")],
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def call_tool():
|
||||
"""Call an MCP tool by name, going through the real FastMCP call path."""
|
||||
def _call(tool_name: str, **kwargs):
|
||||
tools = mcp._tool_manager.list_tools()
|
||||
tool = next(t for t in tools if t.name == tool_name)
|
||||
return asyncio.run(tool.run(kwargs))
|
||||
return _call
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def basic_pdf() -> str:
|
||||
return str(ASSETS / "basic.pdf")
|
||||
@@ -36,3 +49,13 @@ def nested_toc_pdf() -> str:
|
||||
@pytest.fixture
|
||||
def search_pdf() -> str:
|
||||
return str(ASSETS / "search_targets.pdf")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def large_toc_pdf() -> str:
|
||||
return str(ASSETS / "large_toc.pdf")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mega_toc_pdf() -> str:
|
||||
return str(ASSETS / "mega_toc.pdf")
|
||||
|
||||
@@ -3,75 +3,70 @@ from pathlib import Path
|
||||
|
||||
from mcp.server.fastmcp import Image
|
||||
|
||||
from pdf_mcp.server import get_page_image
|
||||
|
||||
|
||||
class TestGetPageImageBase64:
|
||||
def test_returns_image_object(self, basic_pdf):
|
||||
result = get_page_image(basic_pdf, page=1)
|
||||
def test_returns_image_object(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_image", filename=basic_pdf, page=1)
|
||||
assert isinstance(result, Image)
|
||||
|
||||
def test_image_has_data(self, basic_pdf):
|
||||
result = get_page_image(basic_pdf, page=1)
|
||||
def test_image_has_data(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_image", filename=basic_pdf, page=1)
|
||||
assert len(result.data) > 0
|
||||
|
||||
def test_png_signature(self, basic_pdf):
|
||||
result = get_page_image(basic_pdf, page=1)
|
||||
# PNG files start with the 8-byte signature
|
||||
def test_png_signature(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_image", filename=basic_pdf, page=1)
|
||||
assert result.data[:4] == b"\x89PNG"
|
||||
|
||||
def test_different_pages(self, basic_pdf):
|
||||
img1 = get_page_image(basic_pdf, page=1)
|
||||
img2 = get_page_image(basic_pdf, page=2)
|
||||
def test_different_pages(self, basic_pdf, call_tool):
|
||||
img1 = call_tool("get_page_image", filename=basic_pdf, page=1)
|
||||
img2 = call_tool("get_page_image", filename=basic_pdf, page=2)
|
||||
assert img1.data != img2.data
|
||||
|
||||
|
||||
class TestGetPageImageFile:
|
||||
def test_returns_string_with_path(self, basic_pdf):
|
||||
result = get_page_image(basic_pdf, page=1, output="file")
|
||||
def test_returns_string_with_path(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_image", filename=basic_pdf, page=1, output="file")
|
||||
assert isinstance(result, str)
|
||||
assert "Image saved to" in result
|
||||
|
||||
def test_file_exists(self, basic_pdf):
|
||||
result = get_page_image(basic_pdf, page=1, output="file")
|
||||
# Extract path from "Image saved to /path/to/file.png (WxHpx)"
|
||||
def test_file_exists(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_image", filename=basic_pdf, page=1, output="file")
|
||||
path = result.split("Image saved to ")[1].split(" (")[0]
|
||||
assert Path(path).exists()
|
||||
os.unlink(path)
|
||||
|
||||
def test_file_is_valid_png(self, basic_pdf):
|
||||
result = get_page_image(basic_pdf, page=1, output="file")
|
||||
def test_file_is_valid_png(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_image", filename=basic_pdf, page=1, output="file")
|
||||
path = result.split("Image saved to ")[1].split(" (")[0]
|
||||
with open(path, "rb") as f:
|
||||
assert f.read(4) == b"\x89PNG"
|
||||
os.unlink(path)
|
||||
|
||||
def test_file_includes_dimensions(self, basic_pdf):
|
||||
result = get_page_image(basic_pdf, page=1, output="file")
|
||||
# Should contain something like "(1240x1754px)"
|
||||
def test_file_includes_dimensions(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_image", filename=basic_pdf, page=1, output="file")
|
||||
assert "px)" in result
|
||||
|
||||
|
||||
class TestGetPageImageDpi:
|
||||
def test_higher_dpi_larger_file(self, basic_pdf):
|
||||
low = get_page_image(basic_pdf, page=1, dpi=72)
|
||||
high = get_page_image(basic_pdf, page=1, dpi=300)
|
||||
def test_higher_dpi_larger_file(self, basic_pdf, call_tool):
|
||||
low = call_tool("get_page_image", filename=basic_pdf, page=1, dpi=72)
|
||||
high = call_tool("get_page_image", filename=basic_pdf, page=1, dpi=300)
|
||||
assert len(high.data) > len(low.data)
|
||||
|
||||
def test_default_dpi(self, basic_pdf):
|
||||
default = get_page_image(basic_pdf, page=1)
|
||||
explicit_150 = get_page_image(basic_pdf, page=1, dpi=150)
|
||||
def test_default_dpi(self, basic_pdf, call_tool):
|
||||
default = call_tool("get_page_image", filename=basic_pdf, page=1)
|
||||
explicit_150 = call_tool("get_page_image", filename=basic_pdf, page=1, dpi=150)
|
||||
assert len(default.data) == len(explicit_150.data)
|
||||
|
||||
|
||||
class TestGetPageImageDiagrams:
|
||||
def test_diagrams_renders(self, diagrams_pdf):
|
||||
result = get_page_image(diagrams_pdf, page=1)
|
||||
def test_diagrams_renders(self, diagrams_pdf, call_tool):
|
||||
result = call_tool("get_page_image", filename=diagrams_pdf, page=1)
|
||||
assert isinstance(result, Image)
|
||||
assert len(result.data) > 0
|
||||
|
||||
def test_diagrams_file_output(self, diagrams_pdf):
|
||||
result = get_page_image(diagrams_pdf, page=1, output="file")
|
||||
def test_diagrams_file_output(self, diagrams_pdf, call_tool):
|
||||
result = call_tool("get_page_image", filename=diagrams_pdf, page=1, output="file")
|
||||
path = result.split("Image saved to ")[1].split(" (")[0]
|
||||
assert Path(path).exists()
|
||||
assert Path(path).stat().st_size > 0
|
||||
|
||||
+43
-47
@@ -1,120 +1,116 @@
|
||||
from pdf_mcp.server import get_page_text
|
||||
|
||||
|
||||
class TestGetPageTextJson:
|
||||
def test_default_format_is_json(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1)
|
||||
def test_default_format_is_json(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1)
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1
|
||||
assert isinstance(result[0], dict)
|
||||
|
||||
def test_json_has_page_number(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=2)
|
||||
def test_json_has_page_number(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=2)
|
||||
assert result[0]["page_number"] == 2
|
||||
|
||||
def test_json_has_blocks(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1)
|
||||
def test_json_has_blocks(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1)
|
||||
assert "blocks" in result[0]
|
||||
assert len(result[0]["blocks"]) > 0
|
||||
|
||||
def test_json_page_range(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1, end_page=3, format="json")
|
||||
def test_json_page_range(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1, end_page=3, format="json")
|
||||
assert len(result) == 3
|
||||
assert result[0]["page_number"] == 1
|
||||
assert result[2]["page_number"] == 3
|
||||
|
||||
|
||||
class TestGetPageTextPlain:
|
||||
def test_text_format_returns_string(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1, format="text")
|
||||
def test_text_format_returns_string(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1, format="text")
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_text_contains_content(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1, format="text")
|
||||
def test_text_contains_content(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1, format="text")
|
||||
assert "Chapter 1" in result
|
||||
assert "introductory material" in result
|
||||
|
||||
def test_text_page_range_has_breaks(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1, end_page=2, format="text")
|
||||
def test_text_page_range_has_breaks(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1, end_page=2, format="text")
|
||||
assert "--- Page Break ---" in result
|
||||
assert "Chapter 1" in result
|
||||
assert "Chapter 2" in result
|
||||
|
||||
def test_text_single_page_no_break(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1, format="text")
|
||||
def test_text_single_page_no_break(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1, format="text")
|
||||
assert "--- Page Break ---" not in result
|
||||
|
||||
|
||||
class TestGetPageTextMarkdown:
|
||||
def test_markdown_returns_string(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1, format="markdown")
|
||||
def test_markdown_returns_string(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1, format="markdown")
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_markdown_contains_content(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1, format="markdown")
|
||||
def test_markdown_contains_content(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1, format="markdown")
|
||||
assert "Chapter 1" in result
|
||||
|
||||
def test_markdown_page_range(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1, end_page=3, format="markdown")
|
||||
def test_markdown_page_range(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1, end_page=3, format="markdown")
|
||||
assert "Chapter 1" in result
|
||||
assert "Chapter 3" in result
|
||||
|
||||
|
||||
class TestGetPageTextHtml:
|
||||
def test_html_returns_string(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1, format="html")
|
||||
def test_html_returns_string(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1, format="html")
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_html_has_tags(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1, format="html")
|
||||
def test_html_has_tags(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1, format="html")
|
||||
assert "<div" in result
|
||||
assert "<span" in result
|
||||
|
||||
def test_html_contains_content(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1, format="html")
|
||||
def test_html_contains_content(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1, format="html")
|
||||
assert "Chapter 1" in result
|
||||
|
||||
|
||||
class TestGetPageTextHeaderFooter:
|
||||
def test_with_headers_footers(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=1, format="text", include_headers_footers=True)
|
||||
def test_with_headers_footers(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=1, format="text", include_headers_footers=True)
|
||||
assert "Header" in result
|
||||
assert "Page 1 of 3" in result
|
||||
|
||||
def test_without_headers_footers(self, basic_pdf):
|
||||
result = get_page_text(
|
||||
basic_pdf, start_page=1, format="text", include_headers_footers=False
|
||||
def test_without_headers_footers(self, basic_pdf, call_tool):
|
||||
result = call_tool(
|
||||
"get_page_text", filename=basic_pdf, start_page=1, format="text", include_headers_footers=False
|
||||
)
|
||||
# Header text is at y=30, footer at y=820 — both within the 50pt margin crop
|
||||
assert "Header" not in result
|
||||
assert "Page 1 of 3" not in result
|
||||
|
||||
def test_body_preserved_without_headers_footers(self, basic_pdf):
|
||||
result = get_page_text(
|
||||
basic_pdf, start_page=1, format="text", include_headers_footers=False
|
||||
def test_body_preserved_without_headers_footers(self, basic_pdf, call_tool):
|
||||
result = call_tool(
|
||||
"get_page_text", filename=basic_pdf, start_page=1, format="text", include_headers_footers=False
|
||||
)
|
||||
assert "Chapter 1" in result
|
||||
assert "introductory material" in result
|
||||
|
||||
def test_markdown_without_headers_footers(self, basic_pdf):
|
||||
result = get_page_text(
|
||||
basic_pdf, start_page=1, format="markdown", include_headers_footers=False
|
||||
def test_markdown_without_headers_footers(self, basic_pdf, call_tool):
|
||||
result = call_tool(
|
||||
"get_page_text", filename=basic_pdf, start_page=1, format="markdown", include_headers_footers=False
|
||||
)
|
||||
assert "Chapter 1" in result
|
||||
assert "Header" not in result
|
||||
|
||||
|
||||
class TestGetPageTextEndPageDefault:
|
||||
def test_end_page_defaults_to_start(self, basic_pdf):
|
||||
result = get_page_text(basic_pdf, start_page=2, format="text")
|
||||
def test_end_page_defaults_to_start(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=basic_pdf, start_page=2, format="text")
|
||||
assert "Chapter 2" in result
|
||||
assert "Chapter 1" not in result
|
||||
assert "Chapter 3" not in result
|
||||
|
||||
|
||||
class TestGetPageTextDiagrams:
|
||||
def test_diagrams_text_extraction(self, diagrams_pdf):
|
||||
result = get_page_text(diagrams_pdf, start_page=1, format="text")
|
||||
def test_diagrams_text_extraction(self, diagrams_pdf, call_tool):
|
||||
result = call_tool("get_page_text", filename=diagrams_pdf, start_page=1, format="text")
|
||||
assert "Component A" in result
|
||||
assert "Component B" in result
|
||||
assert "Pin Configuration" in result
|
||||
|
||||
+10
-13
@@ -1,9 +1,6 @@
|
||||
from pdf_mcp.server import get_pdf_info
|
||||
|
||||
|
||||
class TestGetPdfInfo:
|
||||
def test_basic_metadata(self, basic_pdf):
|
||||
info = get_pdf_info(basic_pdf)
|
||||
def test_basic_metadata(self, basic_pdf, call_tool):
|
||||
info = call_tool("get_pdf_info", filename=basic_pdf)
|
||||
assert info["page_count"] == 3
|
||||
assert info["metadata"]["title"] == "Test Document"
|
||||
assert info["metadata"]["author"] == "Test Author"
|
||||
@@ -12,18 +9,18 @@ class TestGetPdfInfo:
|
||||
assert info["metadata"]["creator"] == "generate.py"
|
||||
assert info["is_encrypted"] is False
|
||||
|
||||
def test_filename_returned(self, basic_pdf):
|
||||
info = get_pdf_info(basic_pdf)
|
||||
def test_filename_returned(self, basic_pdf, call_tool):
|
||||
info = call_tool("get_pdf_info", filename=basic_pdf)
|
||||
assert info["filename"] == basic_pdf
|
||||
|
||||
def test_single_page_pdf(self, diagrams_pdf):
|
||||
info = get_pdf_info(diagrams_pdf)
|
||||
def test_single_page_pdf(self, diagrams_pdf, call_tool):
|
||||
info = call_tool("get_pdf_info", filename=diagrams_pdf)
|
||||
assert info["page_count"] == 1
|
||||
|
||||
def test_multi_page_pdf(self, nested_toc_pdf):
|
||||
info = get_pdf_info(nested_toc_pdf)
|
||||
def test_multi_page_pdf(self, nested_toc_pdf, call_tool):
|
||||
info = call_tool("get_pdf_info", filename=nested_toc_pdf)
|
||||
assert info["page_count"] == 9
|
||||
|
||||
def test_search_targets_page_count(self, search_pdf):
|
||||
info = get_pdf_info(search_pdf)
|
||||
def test_search_targets_page_count(self, search_pdf, call_tool):
|
||||
info = call_tool("get_pdf_info", filename=search_pdf)
|
||||
assert info["page_count"] == 3
|
||||
|
||||
@@ -1,39 +1,177 @@
|
||||
from pdf_mcp.server import get_table_of_contents
|
||||
|
||||
|
||||
class TestGetTableOfContents:
|
||||
def test_basic_toc(self, basic_pdf):
|
||||
toc = get_table_of_contents(basic_pdf)
|
||||
assert len(toc) == 3
|
||||
assert toc[0] == {"level": 1, "title": "Chapter 1", "page": 1}
|
||||
assert toc[1] == {"level": 1, "title": "Chapter 2", "page": 2}
|
||||
assert toc[2] == {"level": 1, "title": "Chapter 3", "page": 3}
|
||||
def test_basic_toc(self, basic_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=basic_pdf)
|
||||
assert result["total_entries"] == 3
|
||||
entries = result["entries"]
|
||||
assert entries[0] == {"level": 1, "title": "Chapter 1", "page": 1}
|
||||
assert entries[1] == {"level": 1, "title": "Chapter 2", "page": 2}
|
||||
assert entries[2] == {"level": 1, "title": "Chapter 3", "page": 3}
|
||||
|
||||
def test_nested_toc_entries(self, nested_toc_pdf):
|
||||
toc = get_table_of_contents(nested_toc_pdf)
|
||||
assert len(toc) == 9
|
||||
def test_nested_toc_entries(self, nested_toc_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=nested_toc_pdf)
|
||||
assert result["total_entries"] == 9
|
||||
|
||||
def test_nested_toc_levels(self, nested_toc_pdf):
|
||||
toc = get_table_of_contents(nested_toc_pdf)
|
||||
levels = [e["level"] for e in toc]
|
||||
def test_nested_toc_levels(self, nested_toc_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=nested_toc_pdf)
|
||||
levels = [e["level"] for e in result["entries"]]
|
||||
assert levels == [1, 1, 2, 2, 3, 3, 1, 2, 2]
|
||||
|
||||
def test_nested_toc_titles(self, nested_toc_pdf):
|
||||
toc = get_table_of_contents(nested_toc_pdf)
|
||||
titles = [e["title"] for e in toc]
|
||||
def test_nested_toc_titles(self, nested_toc_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=nested_toc_pdf)
|
||||
titles = [e["title"] for e in result["entries"]]
|
||||
assert "Introduction" in titles
|
||||
assert "Database Layer" in titles
|
||||
assert "Kubernetes" in titles
|
||||
|
||||
def test_nested_toc_page_numbers(self, nested_toc_pdf):
|
||||
toc = get_table_of_contents(nested_toc_pdf)
|
||||
pages = [e["page"] for e in toc]
|
||||
def test_nested_toc_page_numbers(self, nested_toc_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=nested_toc_pdf)
|
||||
pages = [e["page"] for e in result["entries"]]
|
||||
assert pages == list(range(1, 10))
|
||||
|
||||
def test_no_toc(self, search_pdf):
|
||||
toc = get_table_of_contents(search_pdf)
|
||||
assert toc == []
|
||||
def test_no_toc(self, search_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=search_pdf)
|
||||
assert result["total_entries"] == 0
|
||||
assert result["entries"] == []
|
||||
|
||||
def test_no_toc_diagrams(self, diagrams_pdf):
|
||||
toc = get_table_of_contents(diagrams_pdf)
|
||||
assert toc == []
|
||||
def test_no_toc_diagrams(self, diagrams_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=diagrams_pdf)
|
||||
assert result["total_entries"] == 0
|
||||
assert result["entries"] == []
|
||||
|
||||
def test_no_auto_trim_small_toc(self, nested_toc_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=nested_toc_pdf)
|
||||
assert "auto_trimmed_to_level" not in result
|
||||
assert "hint" not in result
|
||||
|
||||
|
||||
class TestAutoTrim:
|
||||
"""Test auto-trimming with 402-entry, 4-level deep mega TOC.
|
||||
|
||||
mega_toc.pdf structure:
|
||||
2 volumes (L1) × 8 chapters (L2) × 8 sections (L3) × 2 subsections (L4)
|
||||
|
||||
Cumulative: ≤L1=2, ≤L2=18, ≤L3=146, ≤L4=402
|
||||
Threshold: 100
|
||||
"""
|
||||
|
||||
def test_full_toc_auto_trims_to_level_2(self, mega_toc_pdf, call_tool):
|
||||
"""Full TOC (402 entries) auto-trims to level 2 (18 entries ≤ 100)."""
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf)
|
||||
assert result["auto_trimmed_to_level"] == 2
|
||||
assert result["total_entries"] == 18
|
||||
assert all(e["level"] <= 2 for e in result["entries"])
|
||||
|
||||
def test_auto_trim_hint_present(self, mega_toc_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf)
|
||||
assert "hint" in result
|
||||
assert "402" in result["hint"]
|
||||
assert "18" in result["hint"]
|
||||
assert "level 2" in result["hint"]
|
||||
|
||||
def test_auto_trim_entries_are_volumes_and_chapters(self, mega_toc_pdf, call_tool):
|
||||
"""Trimmed entries should be exactly the 2 volumes + 16 chapters."""
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf)
|
||||
entries = result["entries"]
|
||||
assert len(entries) == 18
|
||||
volumes = [e for e in entries if e["level"] == 1]
|
||||
chapters = [e for e in entries if e["level"] == 2]
|
||||
assert len(volumes) == 2
|
||||
assert len(chapters) == 16
|
||||
|
||||
def test_explicit_max_level_skips_auto_trim(self, mega_toc_pdf, call_tool):
|
||||
"""When max_level is explicitly set, auto-trim must not activate."""
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf, max_level=4)
|
||||
assert "auto_trimmed_to_level" not in result
|
||||
assert "hint" not in result
|
||||
assert result["total_entries"] == 402
|
||||
|
||||
def test_explicit_max_level_3(self, mega_toc_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf, max_level=3)
|
||||
assert "auto_trimmed_to_level" not in result
|
||||
assert result["total_entries"] == 146
|
||||
|
||||
def test_explicit_max_level_1(self, mega_toc_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf, max_level=1)
|
||||
assert result["total_entries"] == 2
|
||||
assert all(e["level"] == 1 for e in result["entries"])
|
||||
|
||||
|
||||
class TestAutoTrimWithParent:
|
||||
"""Test auto-trim when filtering by parent.
|
||||
|
||||
Volume 1 subtree: 8 ch + 64 sec + 128 subsec = 200 children
|
||||
≤L2=8, ≤L3=72, ≤L4=200 → auto-trim to L3 (72 entries)
|
||||
applied_max_level = 3 - 1 = 2 (relative to Volume 1)
|
||||
|
||||
Chapter 1.1 subtree: 8 sec + 16 subsec = 24 children (< 100, no trim)
|
||||
"""
|
||||
|
||||
def test_parent_subtree_auto_trims(self, mega_toc_pdf, call_tool):
|
||||
"""Volume 1's 200-entry subtree should auto-trim to 72 entries."""
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf, parent="Volume 1")
|
||||
assert result["auto_trimmed_to_level"] == 2 # relative to parent
|
||||
assert result["total_entries"] == 72
|
||||
assert "hint" in result
|
||||
|
||||
def test_parent_subtree_trimmed_levels(self, mega_toc_pdf, call_tool):
|
||||
"""After trimming, only chapters (L2) and sections (L3) remain."""
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf, parent="Volume 1")
|
||||
levels = {e["level"] for e in result["entries"]}
|
||||
assert levels == {2, 3}
|
||||
|
||||
def test_parent_small_subtree_no_trim(self, mega_toc_pdf, call_tool):
|
||||
"""Chapter 1.1 subtree (24 entries) should not trigger auto-trim."""
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf, parent="Chapter 1.1")
|
||||
assert "auto_trimmed_to_level" not in result
|
||||
assert "hint" not in result
|
||||
assert result["total_entries"] == 24
|
||||
|
||||
def test_parent_small_subtree_all_levels(self, mega_toc_pdf, call_tool):
|
||||
"""Small subtree should include all levels (sections + subsections)."""
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf, parent="Chapter 1.1")
|
||||
levels = {e["level"] for e in result["entries"]}
|
||||
assert levels == {3, 4}
|
||||
|
||||
def test_parent_with_explicit_max_level_1(self, mega_toc_pdf, call_tool):
|
||||
"""parent + max_level=1 → direct children only, no auto-trim."""
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf, parent="Volume 1", max_level=1)
|
||||
assert "auto_trimmed_to_level" not in result
|
||||
assert result["total_entries"] == 8
|
||||
assert all(e["level"] == 2 for e in result["entries"])
|
||||
|
||||
def test_parent_with_explicit_max_level_2(self, mega_toc_pdf, call_tool):
|
||||
"""parent + max_level=2 → children + grandchildren, no auto-trim."""
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf, parent="Volume 1", max_level=2)
|
||||
assert "auto_trimmed_to_level" not in result
|
||||
assert result["total_entries"] == 72
|
||||
|
||||
def test_parent_not_found(self, mega_toc_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf, parent="Nonexistent Section")
|
||||
assert result["error"] == "No TOC entry matching 'Nonexistent Section'"
|
||||
assert result["total_entries"] == 0
|
||||
|
||||
def test_parent_case_insensitive(self, mega_toc_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf, parent="volume 1")
|
||||
assert result["total_entries"] > 0
|
||||
|
||||
def test_parent_partial_match(self, mega_toc_pdf, call_tool):
|
||||
"""Parent matching is substring-based."""
|
||||
result = call_tool("get_table_of_contents", filename=mega_toc_pdf, parent="Chapter 2.")
|
||||
assert result["total_entries"] > 0
|
||||
|
||||
|
||||
class TestAutoTrimOldPdf:
|
||||
"""Regression tests with large_toc.pdf (205 entries, 3 levels)."""
|
||||
|
||||
def test_auto_trim_large_toc(self, large_toc_pdf, call_tool):
|
||||
"""large_toc.pdf: 205 entries, auto-trims to level 2 (45 entries)."""
|
||||
result = call_tool("get_table_of_contents", filename=large_toc_pdf)
|
||||
assert result["auto_trimmed_to_level"] == 2
|
||||
assert result["total_entries"] == 45
|
||||
assert "hint" in result
|
||||
assert all(e["level"] <= 2 for e in result["entries"])
|
||||
|
||||
def test_no_auto_trim_when_explicit_max_level(self, large_toc_pdf, call_tool):
|
||||
result = call_tool("get_table_of_contents", filename=large_toc_pdf, max_level=3)
|
||||
assert "auto_trimmed_to_level" not in result
|
||||
assert "hint" not in result
|
||||
|
||||
+34
-38
@@ -1,88 +1,84 @@
|
||||
from pdf_mcp.server import search_text
|
||||
|
||||
|
||||
class TestSearchTextBasic:
|
||||
def test_unique_marker_on_two_pages(self, search_pdf):
|
||||
hits = search_text(search_pdf, query="UNIQUE_MARKER_ALPHA")
|
||||
def test_unique_marker_on_two_pages(self, search_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=search_pdf, query="UNIQUE_MARKER_ALPHA")
|
||||
pages = [h["page"] for h in hits]
|
||||
assert pages == [1, 3]
|
||||
|
||||
def test_unique_marker_single_page(self, search_pdf):
|
||||
hits = search_text(search_pdf, query="UNIQUE_MARKER_BETA")
|
||||
def test_unique_marker_single_page(self, search_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=search_pdf, query="UNIQUE_MARKER_BETA")
|
||||
assert len(hits) == 1
|
||||
assert hits[0]["page"] == 2
|
||||
|
||||
def test_no_results(self, search_pdf):
|
||||
hits = search_text(search_pdf, query="NONEXISTENT_TEXT_XYZ")
|
||||
def test_no_results(self, search_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=search_pdf, query="NONEXISTENT_TEXT_XYZ")
|
||||
assert hits == []
|
||||
|
||||
def test_multi_page_hits(self, search_pdf):
|
||||
hits = search_text(search_pdf, query="resistance")
|
||||
def test_multi_page_hits(self, search_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=search_pdf, query="resistance")
|
||||
pages = [h["page"] for h in hits]
|
||||
assert 1 in pages
|
||||
assert 2 in pages
|
||||
|
||||
|
||||
class TestSearchTextCaseInsensitive:
|
||||
def test_lowercase_query(self, search_pdf):
|
||||
hits = search_text(search_pdf, query="unique_marker_alpha")
|
||||
def test_lowercase_query(self, search_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=search_pdf, query="unique_marker_alpha")
|
||||
assert len(hits) == 2
|
||||
|
||||
def test_mixed_case_query(self, search_pdf):
|
||||
hits = search_text(search_pdf, query="Unique_Marker_Alpha")
|
||||
def test_mixed_case_query(self, search_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=search_pdf, query="Unique_Marker_Alpha")
|
||||
assert len(hits) == 2
|
||||
|
||||
def test_case_insensitive_content(self, search_pdf):
|
||||
lower = search_text(search_pdf, query="resistance")
|
||||
upper = search_text(search_pdf, query="RESISTANCE")
|
||||
def test_case_insensitive_content(self, search_pdf, call_tool):
|
||||
lower = call_tool("search_text", filename=search_pdf, query="resistance")
|
||||
upper = call_tool("search_text", filename=search_pdf, query="RESISTANCE")
|
||||
assert len(lower) == len(upper)
|
||||
|
||||
|
||||
class TestSearchTextContext:
|
||||
def test_context_contains_query(self, search_pdf):
|
||||
hits = search_text(search_pdf, query="UNIQUE_MARKER_BETA")
|
||||
def test_context_contains_query(self, search_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=search_pdf, query="UNIQUE_MARKER_BETA")
|
||||
assert "UNIQUE_MARKER_BETA" in hits[0]["context"]
|
||||
|
||||
def test_context_has_surrounding_text(self, search_pdf):
|
||||
hits = search_text(search_pdf, query="UNIQUE_MARKER_BETA")
|
||||
# The context should include text around the match
|
||||
def test_context_has_surrounding_text(self, search_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=search_pdf, query="UNIQUE_MARKER_BETA")
|
||||
assert len(hits[0]["context"]) > len("UNIQUE_MARKER_BETA")
|
||||
|
||||
def test_custom_context_chars(self, search_pdf):
|
||||
short = search_text(search_pdf, query="UNIQUE_MARKER_BETA", context_chars=10)
|
||||
long = search_text(search_pdf, query="UNIQUE_MARKER_BETA", context_chars=200)
|
||||
def test_custom_context_chars(self, search_pdf, call_tool):
|
||||
short = call_tool("search_text", filename=search_pdf, query="UNIQUE_MARKER_BETA", context_chars=10)
|
||||
long = call_tool("search_text", filename=search_pdf, query="UNIQUE_MARKER_BETA", context_chars=200)
|
||||
assert len(short[0]["context"]) <= len(long[0]["context"])
|
||||
|
||||
def test_zero_context(self, search_pdf):
|
||||
hits = search_text(search_pdf, query="UNIQUE_MARKER_BETA", context_chars=0)
|
||||
def test_zero_context(self, search_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=search_pdf, query="UNIQUE_MARKER_BETA", context_chars=0)
|
||||
assert hits[0]["context"] == "UNIQUE_MARKER_BETA"
|
||||
|
||||
|
||||
class TestSearchTextHitStructure:
|
||||
def test_hit_has_page(self, search_pdf):
|
||||
hits = search_text(search_pdf, query="resistance")
|
||||
def test_hit_has_page(self, search_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=search_pdf, query="resistance")
|
||||
for hit in hits:
|
||||
assert "page" in hit
|
||||
assert isinstance(hit["page"], int)
|
||||
|
||||
def test_hit_has_context(self, search_pdf):
|
||||
hits = search_text(search_pdf, query="resistance")
|
||||
def test_hit_has_context(self, search_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=search_pdf, query="resistance")
|
||||
for hit in hits:
|
||||
assert "context" in hit
|
||||
assert isinstance(hit["context"], str)
|
||||
|
||||
|
||||
class TestSearchTextOnOtherPdfs:
|
||||
def test_search_basic_pdf(self, basic_pdf):
|
||||
hits = search_text(basic_pdf, query="introductory")
|
||||
def test_search_basic_pdf(self, basic_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=basic_pdf, query="introductory")
|
||||
assert len(hits) == 1
|
||||
assert hits[0]["page"] == 1
|
||||
|
||||
def test_search_diagrams_pdf(self, diagrams_pdf):
|
||||
hits = search_text(diagrams_pdf, query="Component A")
|
||||
def test_search_diagrams_pdf(self, diagrams_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=diagrams_pdf, query="Component A")
|
||||
assert len(hits) == 1
|
||||
assert hits[0]["page"] == 1
|
||||
|
||||
def test_search_nested_toc_pdf(self, nested_toc_pdf):
|
||||
hits = search_text(nested_toc_pdf, query="Kubernetes")
|
||||
def test_search_nested_toc_pdf(self, nested_toc_pdf, call_tool):
|
||||
hits = call_tool("search_text", filename=nested_toc_pdf, query="Kubernetes")
|
||||
assert len(hits) >= 1
|
||||
|
||||
Reference in New Issue
Block a user