"""Tests for the ServeConverter adapter (Docling Serve HTTP client).""" from __future__ import annotations import json from unittest.mock import AsyncMock, MagicMock, mock_open, patch import httpx import pytest from domain.value_objects import ConversionOptions, ConversionResult, PageDetail, PageElement from infra.serve_converter import ( ServeConverter, _extract_pages, _map_item_type, _parse_response, ) # --------------------------------------------------------------------------- # Unit tests — response parsing # --------------------------------------------------------------------------- class TestParseResponse: """Verify _parse_response correctly maps Docling Serve JSON to ConversionResult.""" def test_minimal_response(self): data = { "document": { "md_content": "# Hello", "html_content": "
text
", "pages": { "1": {"size": {"width": 612.0, "height": 792.0}}, "2": {"size": {"width": 595.0, "height": 842.0}}, }, } } result = _parse_response(data) assert result.page_count == 2 assert result.pages[0].page_number == 1 assert result.pages[1].page_number == 2 assert result.pages[1].width == 595.0 # A4 def test_response_with_body_elements(self): data = { "document": { "md_content": "# Title\nSome text", "html_content": "Some text
", "pages": {"1": {"size": {"width": 612.0, "height": 792.0}}}, "body": [ { "type": "title", "text": "Title", "level": 1, "prov": [{"page_no": 1, "bbox": {"l": 10, "t": 20, "r": 200, "b": 40}}], }, { "type": "text", "text": "Some text", "level": 0, "prov": [{"page_no": 1, "bbox": {"l": 10, "t": 50, "r": 200, "b": 70}}], }, ], } } result = _parse_response(data) assert len(result.pages[0].elements) == 2 assert result.pages[0].elements[0].type == "title" assert result.pages[0].elements[0].content == "Title" assert result.pages[0].elements[0].bbox == [10, 20, 200, 40] assert result.pages[0].elements[1].type == "text" def test_empty_response(self): data = {"document": {"pages": {}}} result = _parse_response(data) assert result.content_markdown == "" assert result.content_html == "" assert result.page_count == 1 # fallback minimum def test_bbox_as_list(self): data = { "document": { "md_content": "", "html_content": "", "pages": {"1": {"size": {"width": 612.0, "height": 792.0}}}, "body": [ { "type": "text", "text": "hello", "prov": [{"page_no": 1, "bbox": [10.0, 20.0, 200.0, 40.0]}], }, ], } } result = _parse_response(data) assert result.pages[0].elements[0].bbox == [10.0, 20.0, 200.0, 40.0] # --------------------------------------------------------------------------- # Unit tests — item type mapping # --------------------------------------------------------------------------- class TestMapItemType: @pytest.mark.parametrize("input_type,expected", [ ("table", "table"), ("picture", "picture"), ("figure", "picture"), ("title", "title"), ("section_header", "section_header"), ("section-header", "section_header"), ("list_item", "list"), ("formula", "formula"), ("equation", "formula"), ("code", "code"), ("text", "text"), ("paragraph", "text"), ("unknown_type", "text"), ]) def test_type_mapping(self, input_type, expected): assert _map_item_type({"type": input_type}) == expected def test_missing_type_defaults_to_text(self): assert _map_item_type({}) == "text" # --------------------------------------------------------------------------- # Unit tests — ServeConverter # --------------------------------------------------------------------------- class TestServeConverter: def test_headers_with_api_key(self): conv = ServeConverter(base_url="http://localhost:5001", api_key="secret") assert conv._headers() == {"X-Api-Key": "secret"} def test_headers_without_api_key(self): conv = ServeConverter(base_url="http://localhost:5001") assert conv._headers() == {} def test_build_conversion_options(self): conv = ServeConverter(base_url="http://localhost:5001") opts = ConversionOptions(do_ocr=False, table_mode="fast", images_scale=2.0) result = conv._build_conversion_options(opts) assert result["do_ocr"] is False assert result["table_mode"] == "fast" assert result["images_scale"] == 2.0 assert result["to_formats"] == ["md", "html"] def test_base_url_trailing_slash_stripped(self): conv = ServeConverter(base_url="http://localhost:5001/") assert conv._base_url == "http://localhost:5001" # --------------------------------------------------------------------------- # Integration tests — HTTP calls (mocked) # --------------------------------------------------------------------------- class TestServeConverterConvert: @pytest.mark.asyncio async def test_successful_conversion(self, tmp_path): # Create a temp file to "upload" test_file = tmp_path / "test.pdf" test_file.write_bytes(b"%PDF-1.4 fake content") serve_response = { "document": { "md_content": "# Converted", "html_content": "