mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 17:00:23 +00:00
313 lines
14 KiB
Python
313 lines
14 KiB
Python
"""Tests for OCR preflight in run_ocr().
|
|
|
|
Verifies that missing PDFs are caught before API submission and produce
|
|
ocr_status: nopdf.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
class TestOcrPreflight:
|
|
"""Tests for PDF preflight behavior in run_ocr()."""
|
|
|
|
def _make_vault(self, tmp_path: Path) -> Path:
|
|
vault = tmp_path / "vault"
|
|
vault.mkdir()
|
|
(vault / "paperforge.json").write_text("{}", encoding="utf-8")
|
|
return vault
|
|
|
|
def _mock_paths(self, vault: Path) -> dict:
|
|
return {
|
|
"exports": vault / "exports",
|
|
"ocr": vault / "ocr",
|
|
"ocr_queue": vault / "ocr_queue.json",
|
|
"library_records": vault / "library-records",
|
|
"literature": vault / "literature",
|
|
}
|
|
|
|
def test_has_pdf_false_sets_nopdf(self, tmp_path: Path) -> None:
|
|
"""has_pdf=False -> ocr_status: nopdf, no API call."""
|
|
vault = self._make_vault(tmp_path)
|
|
mock_paths = self._mock_paths(vault)
|
|
mock_paths["exports"].mkdir()
|
|
mock_paths["ocr"].mkdir()
|
|
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.pipeline_paths",
|
|
return_value=mock_paths,
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.load_control_actions",
|
|
return_value={"ABC123": {"do_ocr": True}},
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.load_export_rows",
|
|
return_value=[
|
|
{
|
|
"key": "ABC123",
|
|
"attachments": [],
|
|
"title": "No PDF Paper",
|
|
}
|
|
],
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.sync_ocr_queue",
|
|
return_value=[
|
|
{
|
|
"zotero_key": "ABC123",
|
|
"has_pdf": False,
|
|
"pdf_path": "",
|
|
}
|
|
],
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.ensure_ocr_meta",
|
|
return_value={},
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.write_json"
|
|
) as mock_write:
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.run_selection_sync"
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.run_index_refresh"
|
|
):
|
|
from pipeline.worker.scripts.literature_pipeline import (
|
|
run_ocr,
|
|
)
|
|
|
|
run_ocr(vault)
|
|
|
|
calls = mock_write.call_args_list
|
|
assert any(
|
|
call[0][1].get("ocr_status") == "nopdf"
|
|
for call in calls
|
|
if isinstance(call[0][1], dict)
|
|
)
|
|
|
|
def test_missing_pdf_file_sets_nopdf(self, tmp_path: Path) -> None:
|
|
"""has_pdf=True but file missing -> ocr_status: nopdf."""
|
|
vault = self._make_vault(tmp_path)
|
|
mock_paths = self._mock_paths(vault)
|
|
mock_paths["exports"].mkdir()
|
|
mock_paths["ocr"].mkdir()
|
|
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.pipeline_paths",
|
|
return_value=mock_paths,
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.load_control_actions",
|
|
return_value={"ABC123": {"do_ocr": True}},
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.load_export_rows",
|
|
return_value=[
|
|
{
|
|
"key": "ABC123",
|
|
"attachments": [
|
|
{
|
|
"contentType": "application/pdf",
|
|
"path": str(tmp_path / "nonexistent.pdf"),
|
|
}
|
|
],
|
|
"title": "Missing PDF Paper",
|
|
}
|
|
],
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.sync_ocr_queue",
|
|
return_value=[
|
|
{
|
|
"zotero_key": "ABC123",
|
|
"has_pdf": True,
|
|
"pdf_path": str(tmp_path / "nonexistent.pdf"),
|
|
}
|
|
],
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.ensure_ocr_meta",
|
|
return_value={},
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.write_json"
|
|
) as mock_write:
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.run_selection_sync"
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.run_index_refresh"
|
|
):
|
|
from pipeline.worker.scripts.literature_pipeline import (
|
|
run_ocr,
|
|
)
|
|
|
|
run_ocr(vault)
|
|
|
|
calls = mock_write.call_args_list
|
|
assert any(
|
|
call[0][1].get("ocr_status") == "nopdf"
|
|
for call in calls
|
|
if isinstance(call[0][1], dict)
|
|
)
|
|
|
|
def test_valid_pdf_proceeds(self, tmp_path: Path) -> None:
|
|
"""Valid resolved PDF -> proceeds to API submission with resolved path."""
|
|
vault = self._make_vault(tmp_path)
|
|
pdf = tmp_path / "test.pdf"
|
|
pdf.write_text("PDF content")
|
|
mock_paths = self._mock_paths(vault)
|
|
mock_paths["exports"].mkdir()
|
|
mock_paths["ocr"].mkdir()
|
|
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.pipeline_paths",
|
|
return_value=mock_paths,
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.load_control_actions",
|
|
return_value={"ABC123": {"do_ocr": True}},
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.load_export_rows",
|
|
return_value=[
|
|
{
|
|
"key": "ABC123",
|
|
"attachments": [
|
|
{
|
|
"contentType": "application/pdf",
|
|
"path": str(pdf),
|
|
}
|
|
],
|
|
"title": "Valid PDF Paper",
|
|
}
|
|
],
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.sync_ocr_queue",
|
|
return_value=[
|
|
{
|
|
"zotero_key": "ABC123",
|
|
"has_pdf": True,
|
|
"pdf_path": str(pdf),
|
|
}
|
|
],
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.ensure_ocr_meta",
|
|
return_value={},
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.write_json"
|
|
):
|
|
with patch("builtins.open") as mock_open:
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.requests.post"
|
|
) as mock_post:
|
|
mock_post.return_value = MagicMock()
|
|
mock_post.return_value.json.return_value = {
|
|
"data": {"jobId": "123"}
|
|
}
|
|
mock_post.return_value.raise_for_status = (
|
|
lambda: None
|
|
)
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.run_selection_sync"
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.run_index_refresh"
|
|
):
|
|
from pipeline.worker.scripts.literature_pipeline import (
|
|
run_ocr,
|
|
)
|
|
|
|
run_ocr(vault)
|
|
|
|
mock_open.assert_called_once()
|
|
opened_path = mock_open.call_args[0][0]
|
|
assert Path(opened_path).resolve() == pdf.resolve()
|
|
|
|
def test_junction_path_resolved(self, tmp_path: Path) -> None:
|
|
"""Junction path is resolved before open() call."""
|
|
vault = self._make_vault(tmp_path)
|
|
target = tmp_path / "target.pdf"
|
|
target.write_text("PDF content")
|
|
mock_paths = self._mock_paths(vault)
|
|
mock_paths["exports"].mkdir()
|
|
mock_paths["ocr"].mkdir()
|
|
|
|
with patch(
|
|
"paperforge_lite.pdf_resolver.resolve_pdf_path",
|
|
return_value=str(target),
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.pipeline_paths",
|
|
return_value=mock_paths,
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.load_control_actions",
|
|
return_value={"ABC123": {"do_ocr": True}},
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.load_export_rows",
|
|
return_value=[
|
|
{
|
|
"key": "ABC123",
|
|
"attachments": [
|
|
{
|
|
"contentType": "application/pdf",
|
|
"path": str(tmp_path / "junction.pdf"),
|
|
}
|
|
],
|
|
"title": "Junction PDF Paper",
|
|
}
|
|
],
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.sync_ocr_queue",
|
|
return_value=[
|
|
{
|
|
"zotero_key": "ABC123",
|
|
"has_pdf": True,
|
|
"pdf_path": str(tmp_path / "junction.pdf"),
|
|
}
|
|
],
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.ensure_ocr_meta",
|
|
return_value={},
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.write_json"
|
|
):
|
|
with patch("builtins.open") as mock_open:
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.requests.post"
|
|
) as mock_post:
|
|
mock_post.return_value = MagicMock()
|
|
mock_post.return_value.json.return_value = {
|
|
"data": {"jobId": "123"}
|
|
}
|
|
mock_post.return_value.raise_for_status = (
|
|
lambda: None
|
|
)
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.run_selection_sync"
|
|
):
|
|
with patch(
|
|
"pipeline.worker.scripts.literature_pipeline.run_index_refresh"
|
|
):
|
|
from pipeline.worker.scripts.literature_pipeline import (
|
|
run_ocr,
|
|
)
|
|
|
|
run_ocr(vault)
|
|
|
|
mock_open.assert_called_once()
|
|
opened_path = mock_open.call_args[0][0]
|
|
assert Path(opened_path).resolve() == target.resolve()
|