diff options
| author | Felix Morgner <felix.morgner@gmail.com> | 2026-10-17 12:52:42 +0200 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@gmail.com> | 2026-10-17 12:52:42 +0200 |
| commit | 5edb6e2fd048f369828984aaae6f7ce83d683201 (patch) | |
| tree | 50de87735605b4936d0378754c3d51602e8f0c20 /tests/conftest.py | |
| parent | 3f88638e4d1c380d730274f46bac6670b1c6a35e (diff) | |
| download | comfier-5edb6e2fd048f369828984aaae6f7ce83d683201.tar.xz comfier-5edb6e2fd048f369828984aaae6f7ce83d683201.zip | |
claude: improved dev-setup
Diffstat (limited to 'tests/conftest.py')
| -rw-r--r-- | tests/conftest.py | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..7370405 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,144 @@ +import asyncio +import json +import pathlib +from collections.abc import AsyncIterator, Callable +from typing import Any + +import pytest +import pytest_asyncio +from aiohttp import WSMsgType, web + +from devkit import FakeEnv, install + +ROOT = pathlib.Path(__file__).parent.parent +PNG = bytes.fromhex( + "89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489" + "0000000d49444154789c6360000002000001e221bc330000000049454e44ae426082" +) + + +# Make `import comfier` work at collection time; fixtures re-install per test. +install(FakeEnv(comfyui_url="", workflow="{}")) + + +class FakeComfy: + """Minimal in-process ComfyUI. `script` lists WS messages sent after /prompt.""" + + def __init__(self) -> None: + self.script: list[dict[str, Any]] = [] + self.submitted: list[dict[str, Any]] = [] + self.interrupts: list[Any] = [] + self.drop_ws = False + self.hang = False + self.url = "" + self._ws: web.WebSocketResponse | None = None + self._ws_ready = asyncio.Event() + + def success_script(self, prompt_id: str) -> list[dict[str, Any]]: + return [ + {"type": "executing", "data": {"node": "1", "prompt_id": prompt_id}}, + { + "type": "progress", + "data": {"value": 1, "max": 2, "prompt_id": prompt_id}, + }, + { + "type": "executed", + "data": { + "prompt_id": prompt_id, + "output": {"images": [{"filename": "a.png", "type": "output"}]}, + }, + }, + {"type": "executing", "data": {"node": None, "prompt_id": prompt_id}}, + ] + + async def ws(self, request: web.Request) -> web.WebSocketResponse: + ws = web.WebSocketResponse() + await ws.prepare(request) + self._ws = ws + self._ws_ready.set() + async for msg in ws: + if msg.type == WSMsgType.ERROR: + break + return ws + + async def prompt(self, request: web.Request) -> web.Response: + body = await request.json() + self.submitted.append(body["prompt"]) + pid = "pid-1" + await self._ws_ready.wait() + assert self._ws is not None + ws = self._ws + asyncio.get_running_loop().call_soon( + lambda: asyncio.ensure_future(self._play(ws, pid)) + ) + return web.json_response({"prompt_id": pid}) + + async def _play(self, ws: web.WebSocketResponse, pid: str) -> None: + if self.hang: + return + if self.drop_ws: + await ws.close() + return + for msg in self.script or self.success_script(pid): + await ws.send_str(json.dumps(msg)) + + async def queue(self, request: web.Request) -> web.Response: + if request.method == "POST": + return web.json_response({}) + return web.json_response({"queue_running": [[0, "pid-1"]], "queue_pending": []}) + + async def interrupt(self, request: web.Request) -> web.Response: + self.interrupts.append(await request.json()) + return web.json_response({}) + + async def history(self, request: web.Request) -> web.Response: + return web.json_response({}) + + async def view(self, request: web.Request) -> web.Response: + return web.Response(body=PNG, content_type="image/png") + + +@pytest_asyncio.fixture +async def comfy() -> AsyncIterator[FakeComfy]: + fake = FakeComfy() + app = web.Application() + app.add_routes( + [ + web.get("/ws", fake.ws), + web.post("/prompt", fake.prompt), + web.get("/queue", fake.queue), + web.post("/queue", fake.queue), + web.post("/interrupt", fake.interrupt), + web.get("/history/{id}", fake.history), + web.get("/view", fake.view), + ] + ) + runner = web.AppRunner(app) + await runner.setup() + site = web.TCPSite(runner, "127.0.0.1", 0) + await site.start() + port = site._server.sockets[0].getsockname()[1] # type: ignore[union-attr] + fake.url = f"http://127.0.0.1:{port}" + yield fake + await runner.cleanup() + + +@pytest.fixture +def make_tools( + tmp_path: pathlib.Path, comfy: FakeComfy +) -> Callable[[], tuple[Any, FakeEnv]]: + def factory() -> tuple[Any, FakeEnv]: + env = FakeEnv( + comfyui_url=comfy.url, + workflow=(ROOT / "fixtures" / "workflow.json").read_text(encoding="utf-8"), + nodes=json.loads( + (ROOT / "fixtures" / "nodes.json").read_text(encoding="utf-8") + ), + output_dir=tmp_path, + ) + install(env) + from comfier import Tools + + return Tools(), env + + return factory |
