PixText-Studio / tests /test_api.py
lsmpp's picture
Make example switching reuse preloaded images and sessions instead of re-encoding and downloading
3ad81a7 verified
Raw History Blame Contribute Delete
5.73 kB
import io
import pytest
from fastapi.testclient import TestClient
from PIL import Image
import app as studio
@pytest.fixture
def client(tmp_path, monkeypatch):
monkeypatch.setattr(studio, "RUNTIME", tmp_path)
monkeypatch.setattr(studio, "SESSIONS", {})
monkeypatch.setattr(studio, "JOBS", {})
return TestClient(studio.app)
def new_session(client, example="english"):
response = client.post("/api/session", json={"example": example})
assert response.status_code == 200
return response.json()["id"]
@pytest.mark.parametrize(("example", "count"), [("english", 4), ("chinese", 13)])
def test_examples_load_complete_ready_to_run_presets(client, example, count):
session = client.post("/api/session", json={"example": example}).json()
sid = session["id"]
targets = session["regions"]
settings = session["settings"]
image = Image.open(io.BytesIO(client.get(f"/api/sessions/{sid}/source.png").content))
assert len(targets) == count
assert settings == {"variant": "meanflow", "steps": 4, "seed": 42,
"guidance": 2.6, "text_threshold": 0.5}
for row in targets:
x0, y0, x1, y1 = row["bbox"]
assert 0 <= x0 < x1 <= image.width and 0 <= y0 < y1 <= image.height
assert row["origin"] == "example" and row["enabled"] and row["text"].strip()
# The first response is already a valid inference request; no OCR/setup request is needed.
request = studio.RefineRequest.model_validate({**settings, "regions": targets})
assert studio.validate_refine(sid, request)["size"] == image.size
restored = client.get(f"/api/sessions/{sid}/targets").json()
assert restored == {"regions": targets, "settings": settings}
def test_upload_rejects_invalid_data_and_normalizes_valid_images(client):
assert client.post("/api/upload", files={"file": ("test.png", b"not a png")}).status_code == 422
buffer = io.BytesIO()
Image.new("RGB", (128, 80), "red").save(buffer, "JPEG")
response = client.post("/api/upload", files={"file": ("test.jpg", buffer.getvalue())})
assert response.status_code == 200
session = response.json()
assert (session["width"], session["height"]) == (128, 80)
assert not session.get("regions") and "settings" not in session
assert client.get(session["image"]).headers["content-type"] == "image/webp"
assert client.get(f"/api/sessions/{session['id']}/source.png").headers["content-type"] == "image/png"
assert client.get(f"/api/sessions/{session['id']}/targets").status_code == 404
def test_invalid_regions_never_enter_the_model_queue(client, monkeypatch):
sid = new_session(client)
calls = []
monkeypatch.setattr(studio.POOL, "submit", lambda *args: calls.append(args))
for box, text in [([-2, 1, 10, 10], "word"), ([0, 0, 99999, 20], "word"), ([5, 1, 3, 4], "word"), ([1, 1, 5, 5], " ")]:
response = client.post(f"/api/sessions/{sid}/refine", json={"regions": [{"id": "x", "bbox": box, "text": text}]})
assert response.status_code == 422
assert not calls
def test_duplicate_clicks_share_one_job_and_sessions_cannot_read_each_other(client, monkeypatch):
first, second = new_session(client), new_session(client, "chinese")
calls = []
monkeypatch.setattr(studio.POOL, "submit", lambda *args: calls.append(args))
path = f"/api/sessions/{first}/detect"
a, b = client.post(path, json={}).json(), client.post(path, json={}).json()
assert a["job_id"] == b["job_id"]
assert len(calls) == 1
assert client.get(f"/api/sessions/{second}/jobs/{a['job_id']}").status_code == 404
assert client.get(f"/api/sessions/{first}/jobs/{a['job_id']}").status_code == 200
assert client.get(f"/api/sessions/{first}/job").json()["id"] == a["job_id"]
assert client.get(f"/api/sessions/{second}/job").status_code == 404
# A second session cannot retrieve a result by guessing its name.
own = studio.SESSIONS[first]["directory"] / "unique.png"
Image.new("RGB", (16, 16)).save(own)
assert client.get(f"/api/sessions/{second}/unique.png").status_code == 404
assert client.get(f"/api/sessions/{first}/unique.png").status_code == 200
def test_sessions_and_jobs_expire_together(client, monkeypatch):
sid = new_session(client)
directory = studio.SESSIONS[sid]["directory"]
studio.SESSIONS[sid]["touched"] -= studio.SESSION_TTL + 1
new_session(client)
assert not directory.exists()
assert client.get(f"/api/sessions/{sid}/source.png").status_code == 404
@pytest.mark.parametrize('example', ['english', 'chinese'])
def test_example_selection_reuses_preview_without_reencoding(client, monkeypatch, example):
def unexpected_encoding(*args, **kwargs):
raise AssertionError('Example selection is re-encoding images on the request path')
with monkeypatch.context() as patch:
patch.setattr(Image.Image, 'save', unexpected_encoding)
first = client.post('/api/session', json={'example': example}).json()
second = client.post('/api/session', json={'example': example}).json()
assert first['id'] != second['id']
assert first['image'] == second['image']
preview = client.get(first['image'])
assert preview.status_code == 200
assert 'public' in preview.headers['cache-control']
assert 'immutable' in preview.headers['cache-control']
assert client.get(first['image'], headers={'If-None-Match': preview.headers['etag']}).status_code == 304
assert first['image'] in client.get('/').text
assert Image.open(io.BytesIO(preview.content)).size == (first['width'], first['height'])
original = client.get(f"/api/sessions/{first['id']}/source.png")
assert original.headers['cache-control'] == 'private, no-store'