"""End-to-end smoke test for the mock pipeline (no network, no GPU). Creates synthetic room images, drives every endpoint via FastAPI's TestClient, and asserts the generated output file actually lands on disk. Run: python scripts/smoke_test.py """ from __future__ import annotations import io import sys from pathlib import Path from PIL import Image, ImageDraw BACKEND_DIR = Path(__file__).resolve().parent.parent sys.path.insert(0, str(BACKEND_DIR)) from fastapi.testclient import TestClient # noqa: E402 from app.config import settings # noqa: E402 from app.main import app # noqa: E402 def make_room(floor_rgb, wall_rgb) -> bytes: """A trivial 'room': wall on the upper half, floor on the lower half.""" w, h = 1024, 768 img = Image.new("RGB", (w, h), wall_rgb) ImageDraw.Draw(img).rectangle([0, int(h * 0.5), w, h], fill=floor_rgb) buf = io.BytesIO() img.save(buf, format="JPEG") return buf.getvalue() def main() -> int: with TestClient(app) as client: # `with` runs the lifespan (init_db) r = client.get("/") assert r.status_code == 200, r.text print("health:", r.json()) r = client.get("/get-catalog") assert r.status_code == 200, r.text catalog = r.json() print("catalog items:", len(catalog)) assert 30 <= len(catalog) <= 40, "catalog must have 30-40 items" r = client.get("/get-catalog", params={"styles": "modern,minimal", "category": "sofa"}) recs = r.json() print("top sofa for modern,minimal:", recs[0]["name"], "score=", recs[0]["score"]) assert recs[0]["score"] >= recs[-1]["score"], "recommendations must be ranked" files = [ ("files", ("room1.jpg", make_room((150, 140, 130), (210, 205, 200)), "image/jpeg")), ("files", ("room2.jpg", make_room((120, 130, 140), (200, 205, 210)), "image/jpeg")), ("files", ("room3.jpg", make_room((140, 120, 110), (205, 200, 195)), "image/jpeg")), ] r = client.post("/upload-room", files=files) assert r.status_code == 200, r.text up = r.json() sid, img_id = up["session_id"], up["images"][0]["id"] print("uploaded:", len(up["images"]), "images; session", sid[:8]) assert len(up["images"]) == 3 # too-few-images guard r = client.post("/upload-room", files=files[:1]) assert r.status_code == 400, "should reject < 3 images" r = client.post("/analyze-room", json={"session_id": sid, "image_id": img_id}) assert r.status_code == 200, r.text an = r.json() print( "analyze: objects=", len(an["detected_objects"]), "floor_pts=", len(an["floor_polygon"]), "free=", an["free_space_ratio"], "ml_used=", an["ml_used"], ) assert len(an["floor_polygon"]) >= 3, "need a usable-floor polygon" item_ids = [catalog[0]["id"], catalog[7]["id"]] r = client.post( "/generate-room", json={"session_id": sid, "image_id": img_id, "item_ids": item_ids}, ) assert r.status_code == 200, r.text gen = r.json() print("generated:", gen["image_url"], "via", gen["provider"]) assert gen["provider"] == "mock" rel = gen["image_url"].split("/static/")[-1] out = settings.STATIC_DIR / rel assert out.exists(), f"missing output file {out}" print("output exists:", out.name, out.stat().st_size, "bytes") for p in gen["placements"]: print(" placement", p["item_id"], "->", p["scale_note"]) # confirm /remove-object does NOT exist r = client.post("/remove-object", json={}) assert r.status_code == 404, "/remove-object must not exist" print("confirmed: /remove-object returns 404 (removal is OUT)") print("\nSMOKE TEST PASSED") return 0 if __name__ == "__main__": raise SystemExit(main())