173 lines
5.9 KiB
Python
173 lines
5.9 KiB
Python
"""End-to-end verification tests for unwrap-clothes."""
|
|
|
|
import http.server
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
|
|
import base64
|
|
import io
|
|
from unittest.mock import patch
|
|
|
|
from fastapi.testclient import TestClient
|
|
from PIL import Image
|
|
|
|
from app import app
|
|
|
|
|
|
def _create_synthetic_image(width: int, height: int, color: str = "red") -> bytes:
|
|
"""Helper to generate a JPEG image with exact dimensions."""
|
|
buf = io.BytesIO()
|
|
img = Image.new("RGB", (width, height), color=color)
|
|
img.save(buf, format="JPEG")
|
|
return buf.getvalue()
|
|
|
|
|
|
def test_e2e_root_serves_html_with_title_and_dropzone():
|
|
"""GET / serves HTML containing web application title and dropzone."""
|
|
client = TestClient(app)
|
|
response = client.get("/")
|
|
assert response.status_code == 200
|
|
assert "text/html" in response.headers.get("content-type", "")
|
|
|
|
html_lower = response.text.lower()
|
|
# Verify web application title
|
|
assert "unwrap-clothes" in html_lower
|
|
# Verify dropzone element exists in HTML
|
|
assert "drop-zone" in html_lower or "dropzone" in html_lower
|
|
|
|
|
|
import pytest
|
|
|
|
@pytest.mark.parametrize(
|
|
"orig_dims,model_dims,prompt,cost,color",
|
|
[
|
|
((1200, 1600), (600, 800), "Soft warm beige studio background, evenly lit, no shadows, no props.", 0.0125, "navy"),
|
|
((1500, 2000), (750, 1000), "Minimalist bright studio setting with directional softbox lighting.", 0.015, "darkgreen"),
|
|
],
|
|
)
|
|
def test_e2e_api_clean_end_to_end(orig_dims, model_dims, prompt, cost, color):
|
|
"""Test FastAPI POST /api/clean end-to-end with high-res images, Lanczos restoration, and custom prompts."""
|
|
client = TestClient(app)
|
|
orig_width, orig_height = orig_dims
|
|
model_width, model_height = model_dims
|
|
|
|
high_res_bytes = _create_synthetic_image(orig_width, orig_height, color=color)
|
|
low_res_bytes = _create_synthetic_image(model_width, model_height, color="beige")
|
|
b64_low_res = base64.b64encode(low_res_bytes).decode()
|
|
|
|
mock_openrouter_response = {
|
|
"data": [{"b64_json": b64_low_res, "media_type": "image/jpeg"}],
|
|
"usage": {"cost": cost},
|
|
}
|
|
|
|
class MockResponse:
|
|
status_code = 200
|
|
|
|
def json(self):
|
|
return mock_openrouter_response
|
|
|
|
files = {"file": ("garment.jpg", high_res_bytes, "image/jpeg")}
|
|
form_data = {
|
|
"prompt": prompt,
|
|
"api_key": "sk-or-v1-fake-e2e-key",
|
|
"restore_res": "true",
|
|
}
|
|
|
|
with patch("cleaner.requests.post", return_value=MockResponse()) as mock_post:
|
|
response = client.post("/api/clean", files=files, data=form_data)
|
|
|
|
assert response.status_code == 200
|
|
mock_post.assert_called_once()
|
|
|
|
payload = response.json()
|
|
assert payload["was_rescaled"] is True
|
|
assert payload["original_dimensions"] == [orig_width, orig_height]
|
|
assert payload["model_dimensions"] == [model_width, model_height]
|
|
assert payload["width"] == orig_width
|
|
assert payload["height"] == orig_height
|
|
assert payload["cost"] == cost
|
|
|
|
encoded_data = payload["image"].split(",", 1)[1]
|
|
decoded_bytes = base64.b64decode(encoded_data)
|
|
with Image.open(io.BytesIO(decoded_bytes)) as result_img:
|
|
assert result_img.size == (orig_width, orig_height)
|
|
|
|
def test_e2e_cli_subprocess_end_to_end(tmp_path):
|
|
"""Test CLI unwrap_clothes.py end-to-end via subprocess with mocked OpenRouter."""
|
|
orig_width, orig_height = 1200, 1600
|
|
model_width, model_height = 600, 800
|
|
|
|
# Create source image in temporary directory
|
|
src_image_path = tmp_path / "garment.jpg"
|
|
src_bytes = _create_synthetic_image(orig_width, orig_height, color="purple")
|
|
src_image_path.write_bytes(src_bytes)
|
|
|
|
# Expected output path: <image_path>_clean.jpg
|
|
expected_clean_path = tmp_path / "garment_clean.jpg"
|
|
|
|
# Create mock lower-resolution image
|
|
low_res_bytes = _create_synthetic_image(model_width, model_height, color="white")
|
|
b64_low_res = base64.b64encode(low_res_bytes).decode()
|
|
|
|
mock_payload = {
|
|
"data": [{"b64_json": b64_low_res, "media_type": "image/jpeg"}],
|
|
"usage": {"cost": 0.02},
|
|
}
|
|
|
|
class MockOpenRouterHandler(http.server.BaseHTTPRequestHandler):
|
|
def do_POST(self):
|
|
length = int(self.headers.get("Content-Length", 0))
|
|
_ = self.rfile.read(length)
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps(mock_payload).encode())
|
|
|
|
def log_message(self, format, *args):
|
|
pass
|
|
|
|
server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), MockOpenRouterHandler)
|
|
server_thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
server_thread.start()
|
|
|
|
repo_root = Path(__file__).resolve().parent.parent
|
|
env = {
|
|
**os.environ,
|
|
"OPENROUTER_API_URL": f"http://127.0.0.1:{server.server_port}/api/v1/images",
|
|
}
|
|
|
|
try:
|
|
cmd = [
|
|
sys.executable,
|
|
"unwrap_clothes.py",
|
|
str(src_image_path),
|
|
"--bg",
|
|
"white",
|
|
"--api-key",
|
|
"fake-key",
|
|
]
|
|
proc = subprocess.run(
|
|
cmd,
|
|
cwd=str(repo_root),
|
|
env=env,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=15,
|
|
)
|
|
|
|
# Verify exit code 0
|
|
assert proc.returncode == 0, f"CLI failed with stderr: {proc.stderr}\nstdout: {proc.stdout}"
|
|
assert "Restored resolution" in proc.stdout
|
|
assert "cost $0.02" in proc.stdout
|
|
assert "Saved:" in proc.stdout
|
|
|
|
assert expected_clean_path.is_file(), f"Output file {expected_clean_path} was not created"
|
|
with Image.open(expected_clean_path) as out_img:
|
|
assert out_img.size == (orig_width, orig_height)
|
|
finally:
|
|
server.shutdown()
|
|
server.server_close()
|