From 1028315d37b134f6f1bb683342bfbedc7bc7addf Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 19:05:56 +0000 Subject: [PATCH] test(009): failing tests for llama.cpp image content-parts (T006-T) llama.cpp's OpenAI endpoint takes images as image_url parts inside content; text-only messages must keep a plain-string content (byte-identical). Red: mapping absent and model_dump() leaks images:None. Witnesses both us2_both_backends.feature scenarios. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01UvS9TFMCFYNHC4MMvzJaZS --- tests/test_llamacpp_provider.py | 52 +++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/test_llamacpp_provider.py b/tests/test_llamacpp_provider.py index 89e57ae..4aa6ecb 100644 --- a/tests/test_llamacpp_provider.py +++ b/tests/test_llamacpp_provider.py @@ -479,3 +479,55 @@ def test_fetch_models_degrades_to_empty_when_unreachable(monkeypatch): names = _run_async(_fetch_models("http://localhost:19999")) assert names == [] + + +# --------------------------------------------------------------------------- +# chat — image input (spec 009, US2; features/us2_both_backends.feature) +# --------------------------------------------------------------------------- + + +def test_chat_maps_images_to_openai_content_parts(monkeypatch): + """llama.cpp's OpenAI-compatible endpoint takes images as image_url parts + inside content, not a flat images field (ADR-008).""" + captured = {} + + async def fake_post(url, payload, *, timeout=120.0, headers=None): + captured["payload"] = payload + return {"choices": [{"message": {"content": "a red square"}}]} + + monkeypatch.setattr(provider_mod, "_post_json", fake_post) + _run_async( + LlamaCppProvider("http://localhost:8080").chat( + "m", [Message(role="user", content="describe", images=["QUJD"])] + ) + ) + + assert captured["payload"]["messages"][-1] == { + "role": "user", + "content": [ + {"type": "text", "text": "describe"}, + { + "type": "image_url", + "image_url": {"url": "data:image/png;base64,QUJD"}, + }, + ], + } + + +def test_chat_text_only_content_stays_plain_string(monkeypatch): + """FR-003/SC-004: an image-less message keeps a plain string content, + byte-identical to today (no content-parts, no images key).""" + captured = {} + + async def fake_post(url, payload, *, timeout=120.0, headers=None): + captured["payload"] = payload + return {"choices": [{"message": {"content": "ok"}}]} + + monkeypatch.setattr(provider_mod, "_post_json", fake_post) + _run_async( + LlamaCppProvider("http://localhost:8080").chat( + "m", [Message(role="user", content="hi")] + ) + ) + + assert captured["payload"]["messages"] == [{"role": "user", "content": "hi"}]