feat: serve responsive WebP srcsets for faster mobile browsing
On-demand ?w= resize with a disk cache under webcache/; downloads stay full-resolution. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+3
-1
@@ -14,7 +14,8 @@ def data_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
intermediates = tmp_path / "intermediates"
|
||||
meta = tmp_path / "meta"
|
||||
inbox = tmp_path / "incoming"
|
||||
for directory in (variants, intermediates, meta, inbox):
|
||||
webcache = tmp_path / "webcache"
|
||||
for directory in (variants, intermediates, meta, inbox, webcache):
|
||||
directory.mkdir()
|
||||
|
||||
monkeypatch.setattr("app.config.DATA_DIR", tmp_path)
|
||||
@@ -22,5 +23,6 @@ def data_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
monkeypatch.setattr("app.config.INTERMEDIATES_DIR", intermediates)
|
||||
monkeypatch.setattr("app.config.META_DIR", meta)
|
||||
monkeypatch.setattr("app.config.INBOX_DIR", inbox)
|
||||
monkeypatch.setattr("app.config.WEB_CACHE_DIR", webcache)
|
||||
monkeypatch.setattr("app.config.PROCESSED_FILE", tmp_path / "processed.json")
|
||||
return tmp_path
|
||||
|
||||
+52
-1
@@ -5,10 +5,11 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from app import config, pipeline
|
||||
from app import config, images, pipeline
|
||||
from app.main import _safe_job_file, _validate_job_id, app
|
||||
from fastapi import HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
from PIL import Image
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -16,6 +17,11 @@ def client(data_dir: Path) -> TestClient:
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
def _write_test_png(path: Path, size: tuple[int, int] = (1200, 800)) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
Image.new("RGB", size, color=(40, 80, 120)).save(path, format="PNG")
|
||||
|
||||
|
||||
def test_validate_job_id_sanitizes_input() -> None:
|
||||
assert _validate_job_id("foo@bar") == "foo_bar"
|
||||
assert _validate_job_id("photo01") == "photo01"
|
||||
@@ -50,3 +56,48 @@ def test_index_empty(client: TestClient) -> None:
|
||||
def test_job_not_found(client: TestClient) -> None:
|
||||
response = client.get("/jobs/does-not-exist")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_job_file_srcset_resize(client: TestClient, data_dir: Path) -> None:
|
||||
stem = "photo01"
|
||||
paths = pipeline.job_paths(stem)
|
||||
png = paths.variants / f"{stem}_v1.png"
|
||||
_write_test_png(png)
|
||||
pipeline.write_status(paths, "done", created_at="2026-01-01T00:00:00")
|
||||
pipeline.write_manifest(
|
||||
paths,
|
||||
{
|
||||
"variants": [{"id": "v1", "file": png.name}],
|
||||
"created_at": "2026-01-01T00:00:00",
|
||||
},
|
||||
)
|
||||
|
||||
full = client.get(f"/jobs/{stem}/files/{png.name}")
|
||||
assert full.status_code == 200
|
||||
assert full.headers["content-type"].startswith("image/")
|
||||
|
||||
bad = client.get(f"/jobs/{stem}/files/{png.name}?w=999")
|
||||
assert bad.status_code == 400
|
||||
|
||||
resized = client.get(f"/jobs/{stem}/files/{png.name}?w=320")
|
||||
assert resized.status_code == 200
|
||||
assert resized.headers["content-type"] == "image/webp"
|
||||
assert len(resized.content) < len(full.content)
|
||||
|
||||
cached = config.WEB_CACHE_DIR / f"{png.name}.w320.webp"
|
||||
assert cached.is_file()
|
||||
with Image.open(cached) as img:
|
||||
assert img.width == 320
|
||||
|
||||
index = client.get("/")
|
||||
assert index.status_code == 200
|
||||
assert f"/jobs/{stem}/files/{png.name}?w=640" in index.text
|
||||
assert "srcset=" in index.text
|
||||
assert "data-full-src=" in index.text
|
||||
|
||||
|
||||
def test_img_attrs_builds_srcset() -> None:
|
||||
attrs = images.img_attrs("job1", "job1_v1.png")
|
||||
assert attrs["src"].endswith("?w=640")
|
||||
assert "320w" in attrs["srcset"]
|
||||
assert attrs["full_src"].endswith("?w=1280")
|
||||
|
||||
Reference in New Issue
Block a user