From 0179e93a4b6551682dd00b3dd84b37242e2bc0b3 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 21 May 2026 16:55:14 +0200 Subject: [PATCH] test(ocr): narrow training error test to subprocess.run seam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The asyncio.to_thread patch stubbed out the entire _run_training call, hiding the real error path. Replacing it with a failing CompletedProcess from subprocess.run exercises the actual ketos-failed branch and keeps the test's intent — error counter bumps, 500 surfaces — intact. Co-Authored-By: Claude Sonnet 4.6 --- ocr-service/test_metrics.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/ocr-service/test_metrics.py b/ocr-service/test_metrics.py index d8099a24..1e6ab7fa 100644 --- a/ocr-service/test_metrics.py +++ b/ocr-service/test_metrics.py @@ -347,13 +347,21 @@ async def test_ocr_training_runs_total_incremented_with_recognition_success_labe @pytest.mark.asyncio async def test_ocr_training_runs_total_incremented_with_recognition_error_label(fresh_metrics): - """When /train's inner runner raises, the error counter bumps and the exception propagates.""" - async def fake_to_thread(func, *args, **kwargs): - raise RuntimeError("ketos train failed (exit 1): synthetic") + """When ketos exits non-zero, the error counter bumps and the exception propagates. + + Uses the narrowest available seam — `subprocess.run` returning a failing + CompletedProcess — instead of stubbing the asyncio.to_thread boundary, + so the test exercises the real _run_training error path. + """ + from subprocess import CompletedProcess + + failing_proc = CompletedProcess( + args=["ketos"], returncode=1, stdout="", stderr="synthetic ketos failure" + ) with patch("main.TRAINING_TOKEN", "secret-token"), \ patch("main._models_ready", True), \ - patch("main.asyncio.to_thread", side_effect=fake_to_thread): + patch("main.subprocess.run", return_value=failing_proc): transport = ASGITransport(app=app, raise_app_exceptions=False) async with AsyncClient(transport=transport, base_url="http://test") as client: response = await client.post(