"""The worker is a thin shell: it receives an image and a graph, and answers with numbers.""" import io import json import pytest # Same reasoning as src/vision/tests/test_runner.py: the vision tree (and # therefore this worker, which imports src.vision.runner) requires Python # >=3.13. This must run before `import numpy` / `from src.vision_worker.main # import app` so that `uv run pytest` on the default 3.11 environment reports # this module as skipped, with a reason, instead of erroring out of # collection. pytest.importorskip( "visionsuite.task", reason=( "the vision tree requires Python >=3.13 (vs-task/vs-measure/vs-pm2d " "declare it upstream); run it with " "`uv run --python 3.13 --extra vision --extra dev pytest " "src/vision_worker/tests`" ), ) import numpy as np from httpx import ASGITransport, AsyncClient from PIL import Image from src.vision_worker.main import app def a_disc_png(radius: int = 60, size: int = 400) -> bytes: image = np.zeros((size, size), dtype=np.uint8) yy, xx = np.ogrid[:size, :size] centre = size // 2 image[(yy - centre) ** 2 + (xx - centre) ** 2 <= radius ** 2] = 255 buffer = io.BytesIO() Image.fromarray(image).save(buffer, format="PNG") return buffer.getvalue() def a_circle_graph() -> dict: from visionsuite.task import Task from visionsuite.task.tools import DetectCircleTool, DiameterTool task = Task(name="prova") found = task.add(DetectCircleTool(name="disco")) found.center_row, found.center_col, found.radius = 200.0, 200.0, 60.0 found.scan_size = 30.0 quota = task.add(DiameterTool(name="diametro")) quota.link("circle", found, "circle") return task.to_dict() @pytest.mark.asyncio async def test_health_says_which_engine_it_carries(): transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://worker") as client: response = await client.get("/health") assert response.status_code == 200 assert len(response.json()["engine_version"]) == 40 @pytest.mark.asyncio async def test_run_returns_the_named_outputs(): transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://worker") as client: response = await client.post( "/run", files={"image": ("piece.png", a_disc_png(), "image/png")}, data={"graph": json.dumps(a_circle_graph())}, ) assert response.status_code == 200 body = response.json() assert body["failures"] == [] assert any(key.endswith(".diameter") for key in body["outputs"]) @pytest.mark.asyncio async def test_a_malformed_graph_is_refused_not_crashed(): transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://worker") as client: response = await client.post( "/run", files={"image": ("piece.png", a_disc_png(), "image/png")}, data={"graph": json.dumps({"schema_version": 99, "tools": []})}, ) assert response.status_code == 422 assert "99" in response.json()["detail"]