"""Tests for MinerU content_list → Block mapping.""" from __future__ import annotations import tempfile import subprocess import unittest from pathlib import Path from unittest.mock import Mock, patch from rag_cut.models import BlockType from rag_cut.parsers.mineru_adapter import ( _blocks_from_content_list, _find_content_list, _run_mineru, ) class MineruAdapterProcessTest(unittest.TestCase): def test_default_timeout_allows_large_manual_to_finish(self) -> None: with patch.dict("os.environ", {}, clear=True): from rag_cut.parsers.mineru_adapter import _mineru_timeout_sec self.assertEqual(_mineru_timeout_sec(), 540.0) def test_configured_api_url_is_passed_to_mineru(self) -> None: process = Mock() process.communicate.return_value = ("", "") process.returncode = 0 with ( tempfile.TemporaryDirectory() as tmp, patch("rag_cut.parsers.mineru_adapter._mineru_command", return_value="mineru"), patch.dict("os.environ", {"RAG_CUT_MINERU_API_URL": "http://127.0.0.1:30000"}), patch("rag_cut.parsers.mineru_adapter.subprocess.Popen", return_value=process) as popen, ): ok = _run_mineru(Path(tmp) / "manual.pdf", Path(tmp) / "output") self.assertTrue(ok) args = popen.call_args.args[0] self.assertEqual(args[-2:], ["--api-url", "http://127.0.0.1:30000"]) def test_timeout_terminates_windows_process_tree(self) -> None: process = Mock() process.pid = 4321 process.communicate.side_effect = subprocess.TimeoutExpired("mineru", 30) process.poll.return_value = None with ( tempfile.TemporaryDirectory() as tmp, patch("rag_cut.parsers.mineru_adapter._mineru_command", return_value="mineru"), patch("rag_cut.parsers.mineru_adapter._mineru_timeout_sec", return_value=30), patch("rag_cut.parsers.mineru_adapter.os.name", "nt"), patch("rag_cut.parsers.mineru_adapter.subprocess.Popen", return_value=process), patch("rag_cut.parsers.mineru_adapter.subprocess.run") as run, ): ok = _run_mineru(Path(tmp) / "manual.pdf", Path(tmp) / "output") self.assertFalse(ok) run.assert_called_once_with( ["taskkill", "/PID", "4321", "/T", "/F"], check=False, capture_output=True, timeout=10, ) class MineruAdapterMappingTest(unittest.TestCase): def test_chart_becomes_image_block(self) -> None: with tempfile.TemporaryDirectory() as tmp: assets = Path(tmp) img = assets / "chart.jpg" img.write_bytes(b"fake") items = [ { "type": "text", "text": "第二十節 TradingView", "text_level": 2, "page_idx": 30, "bbox": [127, 86, 363, 104], }, { "type": "chart", "img_path": "chart.jpg", "page_idx": 30, "bbox": [129, 171, 878, 428], "image_caption": [], }, ] with patch("rag_cut.parsers.mineru_adapter._mineru_ocr_enabled", return_value=False): blocks = _blocks_from_content_list(items, assets / "content_list.json", assets) types = [b.type for b in blocks] self.assertEqual(types, [BlockType.HEADING, BlockType.IMAGE]) self.assertEqual(blocks[1].image_id, "chart.jpg") self.assertEqual(blocks[1].meta.get("mineru_type"), "chart") self.assertEqual(blocks[1].meta.get("page"), 31) def test_table_keeps_markdown_and_screenshot(self) -> None: with tempfile.TemporaryDirectory() as tmp: assets = Path(tmp) img = assets / "table.png" img.write_bytes(b"png") items = [ { "type": "table", "img_path": "table.png", "page_idx": 1, "bbox": [100, 100, 400, 300], "table_body": "
| A | B |