@@ -37,6 +37,11 @@ def _mock_status_response(
3737 return httpx .Response (200 , json = json_data , request = httpx .Request ("GET" , "http://test" ))
3838
3939
40+ def _mock_failed_result_response (error_message : str = "conversion failed" , status : str = "failure" ) -> httpx .Response :
41+ json_data = {"status" : status , "errors" : [{"error_message" : error_message }]}
42+ return httpx .Response (200 , json = json_data , request = httpx .Request ("GET" , "http://test" ))
43+
44+
4045class TestDoclingServeConverterInit :
4146 def test_defaults (self , monkeypatch ):
4247 monkeypatch .delenv ("DOCLING_SERVE_API_KEY" , raising = False )
@@ -88,10 +93,16 @@ def test_api_key_none_overrides_env(self, monkeypatch):
8893
8994 def test_invalid_async_job_settings (self ):
9095 with pytest .raises (ValueError , match = "poll_interval" ):
91- DoclingServeConverter (poll_interval = 0 )
96+ DoclingServeConverter (mode = ConversionMode . ASYNC , poll_interval = 0 )
9297
9398 with pytest .raises (ValueError , match = "job_timeout" ):
94- DoclingServeConverter (job_timeout = 0 )
99+ DoclingServeConverter (mode = ConversionMode .ASYNC , job_timeout = 0 )
100+
101+ def test_sync_mode_allows_unused_async_job_settings (self ):
102+ converter = DoclingServeConverter (poll_interval = 0 , job_timeout = 0 )
103+ assert converter .mode == ConversionMode .SYNC
104+ assert converter .poll_interval == 0
105+ assert converter .job_timeout == 0
95106
96107
97108class TestDoclingServeConverterSerialization :
@@ -290,6 +301,30 @@ def test_run_skips_when_no_content(self, caplog):
290301 assert result ["documents" ] == []
291302 assert "No content returned" in caplog .text
292303
304+ def test_run_skips_failed_sync_url_response_details (self , caplog ):
305+ converter = DoclingServeConverter (api_key = None )
306+ mock_resp = _mock_failed_result_response ("url conversion failed" )
307+
308+ with patch ("httpx.Client.post" , return_value = mock_resp ):
309+ with caplog .at_level (logging .WARNING ):
310+ result = converter .run (sources = ["https://example.com/doc.pdf" ])
311+
312+ assert result ["documents" ] == []
313+ assert "url conversion failed" in caplog .text
314+
315+ def test_run_skips_failed_sync_file_response_details (self , tmp_path , caplog ):
316+ pdf = tmp_path / "test.pdf"
317+ pdf .write_bytes (b"%PDF-test" )
318+ converter = DoclingServeConverter (api_key = None )
319+ mock_resp = _mock_failed_result_response ("file conversion failed" )
320+
321+ with patch ("httpx.Client.post" , return_value = mock_resp ):
322+ with caplog .at_level (logging .WARNING ):
323+ result = converter .run (sources = [pdf ])
324+
325+ assert result ["documents" ] == []
326+ assert "file conversion failed" in caplog .text
327+
293328 def test_run_text_export (self ):
294329 converter = DoclingServeConverter (export_type = ExportType .TEXT , api_key = None )
295330 mock_resp = _mock_httpx_response ("plain text content" , ExportType .TEXT )
@@ -400,6 +435,50 @@ def test_run_async_mode_skips_failed_job(self, caplog):
400435 assert result ["documents" ] == []
401436 assert "conversion failed" in caplog .text
402437
438+ def test_run_async_mode_skips_failed_result (self , caplog ):
439+ converter = DoclingServeConverter (api_key = None , mode = ConversionMode .ASYNC )
440+
441+ with (
442+ patch ("httpx.Client.post" , return_value = _mock_task_response ()),
443+ patch ("httpx.Client.get" , side_effect = [_mock_status_response (), _mock_failed_result_response ("bad file" )]),
444+ ):
445+ with caplog .at_level (logging .WARNING ):
446+ result = converter .run (sources = ["https://example.com/doc.pdf" ])
447+
448+ assert result ["documents" ] == []
449+ assert "bad file" in caplog .text
450+
451+ def test_run_async_mode_skips_skipped_result (self , caplog ):
452+ converter = DoclingServeConverter (api_key = None , mode = ConversionMode .ASYNC )
453+
454+ with (
455+ patch ("httpx.Client.post" , return_value = _mock_task_response ()),
456+ patch (
457+ "httpx.Client.get" ,
458+ side_effect = [_mock_status_response (), _mock_failed_result_response ("skipped file" , status = "skipped" )],
459+ ),
460+ ):
461+ with caplog .at_level (logging .WARNING ):
462+ result = converter .run (sources = ["https://example.com/doc.pdf" ])
463+
464+ assert result ["documents" ] == []
465+ assert "skipped file" in caplog .text
466+
467+ def test_run_async_mode_skips_timed_out_job (self , caplog ):
468+ converter = DoclingServeConverter (
469+ api_key = None , mode = ConversionMode .ASYNC , poll_interval = 0.001 , job_timeout = 0.001
470+ )
471+
472+ with (
473+ patch ("httpx.Client.post" , return_value = _mock_task_response ()),
474+ patch ("httpx.Client.get" , return_value = _mock_status_response (status = "pending" )),
475+ ):
476+ with caplog .at_level (logging .WARNING ):
477+ result = converter .run (sources = ["https://example.com/doc.pdf" ])
478+
479+ assert result ["documents" ] == []
480+ assert "Timed out" in caplog .text
481+
403482
404483class TestDoclingServeConverterFilename :
405484 def test_bytestream_filename_from_file_name (self ):
@@ -463,6 +542,37 @@ async def test_run_async_url_source(self):
463542 assert len (result ["documents" ]) == 1
464543 assert result ["documents" ][0 ].content == "# Async content"
465544
545+ @pytest .mark .asyncio
546+ async def test_run_async_file_source (self , tmp_path ):
547+ pdf = tmp_path / "test.pdf"
548+ pdf .write_bytes (b"%PDF-test" )
549+ converter = DoclingServeConverter (api_key = None )
550+ mock_resp = httpx .Response (
551+ 200 ,
552+ json = {"document" : {"md_content" : "# Async file content" }, "status" : "success" },
553+ request = httpx .Request ("POST" , "http://test" ),
554+ )
555+
556+ with patch ("httpx.AsyncClient.post" , new_callable = AsyncMock , return_value = mock_resp ) as mock_post :
557+ result = await converter .run_async (sources = [pdf ])
558+
559+ post_url = mock_post .call_args [0 ][0 ]
560+ assert post_url .endswith ("/v1/convert/file" )
561+ assert result ["documents" ][0 ].content == "# Async file content"
562+
563+ @pytest .mark .asyncio
564+ async def test_run_async_skips_failed_sync_response_details (self , caplog ):
565+ converter = DoclingServeConverter (api_key = None )
566+
567+ with patch (
568+ "httpx.AsyncClient.post" , new_callable = AsyncMock , return_value = _mock_failed_result_response ("async failure" )
569+ ):
570+ with caplog .at_level (logging .WARNING ):
571+ result = await converter .run_async (sources = ["https://example.com/doc.pdf" ])
572+
573+ assert result ["documents" ] == []
574+ assert "async failure" in caplog .text
575+
466576 @pytest .mark .asyncio
467577 async def test_run_async_skips_on_status_error (self , caplog ):
468578 converter = DoclingServeConverter (api_key = None )
@@ -534,6 +644,66 @@ async def test_run_async_async_mode_uses_job_endpoints(self):
534644 assert status_call .kwargs ["params" ] == {"wait" : 1.5 }
535645 assert result_call .args [0 ].endswith ("/v1/result/task-1" )
536646
647+ @pytest .mark .asyncio
648+ async def test_run_async_async_mode_uses_job_endpoints_for_file_source (self , tmp_path ):
649+ pdf = tmp_path / "test.pdf"
650+ pdf .write_bytes (b"%PDF-test" )
651+ converter = DoclingServeConverter (api_key = None , mode = ConversionMode .ASYNC )
652+
653+ with (
654+ patch ("httpx.AsyncClient.post" , new_callable = AsyncMock , return_value = _mock_task_response ()) as mock_post ,
655+ patch (
656+ "httpx.AsyncClient.get" ,
657+ new_callable = AsyncMock ,
658+ side_effect = [_mock_status_response (), _mock_httpx_response ("async file content" )],
659+ ),
660+ ):
661+ result = await converter .run_async (sources = [pdf ])
662+
663+ post_url = mock_post .call_args [0 ][0 ]
664+ data = mock_post .call_args [1 ]["data" ]
665+ assert post_url .endswith ("/v1/convert/file/async" )
666+ assert data ["target_type" ] == "inbody"
667+ assert result ["documents" ][0 ].content == "async file content"
668+
669+ @pytest .mark .asyncio
670+ async def test_run_async_async_mode_skips_failed_result (self , caplog ):
671+ converter = DoclingServeConverter (api_key = None , mode = ConversionMode .ASYNC )
672+
673+ with (
674+ patch ("httpx.AsyncClient.post" , new_callable = AsyncMock , return_value = _mock_task_response ()),
675+ patch (
676+ "httpx.AsyncClient.get" ,
677+ new_callable = AsyncMock ,
678+ side_effect = [_mock_status_response (), _mock_failed_result_response ("bad async result" )],
679+ ),
680+ ):
681+ with caplog .at_level (logging .WARNING ):
682+ result = await converter .run_async (sources = ["https://example.com/doc.pdf" ])
683+
684+ assert result ["documents" ] == []
685+ assert "bad async result" in caplog .text
686+
687+ @pytest .mark .asyncio
688+ async def test_run_async_async_mode_skips_timed_out_job (self , caplog ):
689+ converter = DoclingServeConverter (
690+ api_key = None , mode = ConversionMode .ASYNC , poll_interval = 0.001 , job_timeout = 0.001
691+ )
692+
693+ with (
694+ patch ("httpx.AsyncClient.post" , new_callable = AsyncMock , return_value = _mock_task_response ()),
695+ patch (
696+ "httpx.AsyncClient.get" ,
697+ new_callable = AsyncMock ,
698+ return_value = _mock_status_response (status = "pending" ),
699+ ),
700+ ):
701+ with caplog .at_level (logging .WARNING ):
702+ result = await converter .run_async (sources = ["https://example.com/doc.pdf" ])
703+
704+ assert result ["documents" ] == []
705+ assert "Timed out" in caplog .text
706+
537707
538708class TestDoclingServeConverterIntegration :
539709 @pytest .mark .integration
0 commit comments