import base64 import httpx import pytest import respx from tests.conftest import COMFY, png_bytes, png_size EDITS = "http://adapter.test/v1/images/edits" PROMPT_ID = "p-edit" UPLOADED = "adapter_deadbeef_11223344.png" HISTORY = { PROMPT_ID: { "outputs": {"60": {"images": [{"filename": "ed_00001_.png", "subfolder": "", "type": "output"}]}}, "status": {"status_str": "success", "completed": True}, } } def _mock_pipeline(): upload_route = respx.post(f"{COMFY}/upload/image").mock(return_value=httpx.Response(200, json={"name": UPLOADED, "type": "input"})) prompt_route = respx.post(f"{COMFY}/prompt").mock(return_value=httpx.Response(200, json={"prompt_id": PROMPT_ID})) respx.get(f"{COMFY}/history/{PROMPT_ID}").mock(return_value=httpx.Response(200, json=HISTORY)) respx.get(f"{COMFY}/view").mock(return_value=httpx.Response(200, content=png_bytes(1024, 1024))) return upload_route, prompt_route @respx.mock async def test_edits_multipart_success(client: httpx.AsyncClient): upload_route, prompt_route = _mock_pipeline() files = {"image": ("in.png", png_bytes(64, 64), "image/png")} r = await client.post(EDITS, files=files, data={"prompt": "make it night"}) assert r.status_code == 200 body = r.json() img = base64.b64decode(body["data"][0]["b64_json"]) assert png_size(img) == (1024, 1024) # uploaded file name landed in LoadImage node; prompt in positive TextEncode upload_body = upload_route.calls.last.request.content assert b"in.png" in upload_body or b"adapter_" in upload_body sent = prompt_route.calls.last.request.content.decode() assert UPLOADED in sent assert "make it night" in sent graph = prompt_route.calls.last.request.content # json bytes assert b'"115:110"' in graph # negative node present with empty prompt @respx.mock async def test_edits_json_base64_success(client: httpx.AsyncClient): _, prompt_route = _mock_pipeline() b64 = base64.b64encode(png_bytes(64, 64)).decode() r = await client.post(EDITS, json={"image": b64, "prompt": "add a hat", "model": "qwen-image-edit"}) assert r.status_code == 200 assert "add a hat" in prompt_route.calls.last.request.content.decode() @respx.mock async def test_edits_size_ignored(client: httpx.AsyncClient): _, prompt_route = _mock_pipeline() b64 = base64.b64encode(png_bytes(64, 64)).decode() r = await client.post(EDITS, json={"image": b64, "prompt": "x", "size": "512x512"}) assert r.status_code == 200 sent = prompt_route.calls.last.request.content.decode() assert "512" not in sent # size never reaches the workflow async def test_edits_missing_image_400(client): r = await client.post(EDITS, data={"prompt": "x"}) assert r.status_code == 400 async def test_edits_empty_image_400(client): files = {"image": ("in.png", b"", "image/png")} r = await client.post(EDITS, files=files, data={"prompt": "x"}) assert r.status_code == 400 async def test_edits_garbage_image_400(client): files = {"image": ("in.png", b"not-an-image", "image/png")} r = await client.post(EDITS, files=files, data={"prompt": "x"}) assert r.status_code == 400 async def test_edits_bad_base64_400(client): r = await client.post(EDITS, json={"image": "!!!not base64!!!", "prompt": "x"}) assert r.status_code == 400 async def test_edits_empty_prompt_400(client): b64 = base64.b64encode(png_bytes(8, 8)).decode() r = await client.post(EDITS, json={"image": b64, "prompt": " "}) assert r.status_code == 400 async def test_edits_wrong_model_400(client): b64 = base64.b64encode(png_bytes(8, 8)).decode() r = await client.post(EDITS, json={"image": b64, "prompt": "x", "model": "z-image-turbo"}) assert r.status_code == 400 @respx.mock async def test_edits_comfy_failure_502(client): respx.post(f"{COMFY}/upload/image").mock(return_value=httpx.Response(200, json={"name": UPLOADED})) respx.post(f"{COMFY}/prompt").mock(return_value=httpx.Response(400, json={"node_errors": {"x": {"errors": [{"message": "bad graph", "details": ""}]}}})) b64 = base64.b64encode(png_bytes(8, 8)).decode() r = await client.post(EDITS, json={"image": b64, "prompt": "x"}) assert r.status_code == 502