- "engine/api/routes.py" - "engine/tests/test_api.py" - "engine/main.py" GSD-Task: S01/T05
178 lines
5.5 KiB
Python
178 lines
5.5 KiB
Python
"""Integration tests for the /engine/trace endpoint."""
|
|
|
|
import json
|
|
|
|
import cv2
|
|
import numpy as np
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from main import app
|
|
|
|
client = TestClient(app)
|
|
|
|
|
|
def _make_test_png(width: int = 100, height: int = 100) -> bytes:
|
|
"""Create a simple test PNG with a white rectangle on black background."""
|
|
img = np.zeros((height, width, 3), dtype=np.uint8)
|
|
cv2.rectangle(img, (20, 20), (80, 80), (255, 255, 255), -1)
|
|
ok, buf = cv2.imencode(".png", img)
|
|
assert ok
|
|
return buf.tobytes()
|
|
|
|
|
|
@pytest.fixture
|
|
def test_png() -> bytes:
|
|
return _make_test_png()
|
|
|
|
|
|
class TestTraceEndpointPotrace:
|
|
"""Tests for /engine/trace with mode=potrace."""
|
|
|
|
def test_basic_trace(self, test_png):
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("test.png", test_png, "image/png")},
|
|
data={"mode": "potrace"},
|
|
)
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body["format"] == "svg"
|
|
assert "<svg" in body["output"]
|
|
assert "metadata" in body
|
|
|
|
def test_metadata_shape(self, test_png):
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("test.png", test_png, "image/png")},
|
|
data={"mode": "potrace"},
|
|
)
|
|
body = resp.json()
|
|
meta = body["metadata"]
|
|
assert "path_count" in meta
|
|
assert "node_count_total" in meta
|
|
assert "open_paths" in meta
|
|
assert "warnings" in meta
|
|
assert "processing_ms" in meta
|
|
assert isinstance(meta["path_count"], int)
|
|
assert isinstance(meta["processing_ms"], float)
|
|
|
|
def test_svg_has_paths(self, test_png):
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("test.png", test_png, "image/png")},
|
|
data={"mode": "potrace"},
|
|
)
|
|
body = resp.json()
|
|
assert body["metadata"]["path_count"] >= 1
|
|
|
|
def test_custom_params(self, test_png):
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("test.png", test_png, "image/png")},
|
|
data={
|
|
"mode": "potrace",
|
|
"params": json.dumps({"turdsize": 5, "alphamax": 0.5}),
|
|
},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert "<svg" in resp.json()["output"]
|
|
|
|
|
|
class TestTraceEndpointVtracer:
|
|
"""Tests for /engine/trace with mode=vtracer."""
|
|
|
|
def test_basic_trace(self, test_png):
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("test.png", test_png, "image/png")},
|
|
data={"mode": "vtracer"},
|
|
)
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body["format"] == "svg"
|
|
assert "<svg" in body["output"].lower() or "svg" in body["output"]
|
|
|
|
def test_metadata_present(self, test_png):
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("test.png", test_png, "image/png")},
|
|
data={"mode": "vtracer"},
|
|
)
|
|
body = resp.json()
|
|
meta = body["metadata"]
|
|
assert isinstance(meta["path_count"], int)
|
|
assert isinstance(meta["processing_ms"], float)
|
|
|
|
def test_custom_params(self, test_png):
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("test.png", test_png, "image/png")},
|
|
data={
|
|
"mode": "vtracer",
|
|
"params": json.dumps({"filter_speckle": 10, "corner_threshold": 90}),
|
|
},
|
|
)
|
|
assert resp.status_code == 200
|
|
|
|
|
|
class TestTraceEndpointValidation:
|
|
"""Tests for input validation on /engine/trace."""
|
|
|
|
def test_invalid_mode(self, test_png):
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("test.png", test_png, "image/png")},
|
|
data={"mode": "invalid"},
|
|
)
|
|
assert resp.status_code == 422
|
|
|
|
def test_unsupported_output_format(self, test_png):
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("test.png", test_png, "image/png")},
|
|
data={"mode": "potrace", "output_format": "pdf"},
|
|
)
|
|
assert resp.status_code == 422
|
|
|
|
def test_invalid_params_json(self, test_png):
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("test.png", test_png, "image/png")},
|
|
data={"mode": "potrace", "params": "not-json"},
|
|
)
|
|
assert resp.status_code == 422
|
|
|
|
def test_empty_file(self):
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("empty.png", b"", "image/png")},
|
|
data={"mode": "potrace"},
|
|
)
|
|
assert resp.status_code == 422
|
|
|
|
def test_corrupt_image(self):
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("bad.png", b"not an image", "image/png")},
|
|
data={"mode": "potrace"},
|
|
)
|
|
assert resp.status_code == 422
|
|
|
|
def test_defaults_to_potrace(self, test_png):
|
|
"""Mode defaults to potrace when not specified."""
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("test.png", test_png, "image/png")},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert "<svg" in resp.json()["output"]
|
|
|
|
def test_preset_ignored(self, test_png):
|
|
"""Preset param is accepted but ignored for now."""
|
|
resp = client.post(
|
|
"/engine/trace",
|
|
files={"file": ("test.png", test_png, "image/png")},
|
|
data={"mode": "potrace", "preset": "logo"},
|
|
)
|
|
assert resp.status_code == 200
|