保留5号历史三档实现,新增中下庭高度定位、扶正裁切、矩形叠加与30案例对比页。 worker仅允许本机和配置白名单访问;网关不增加IP限制。空图片URL字段改写限定为52号,保持旧接口兼容。同步接口、裁切与部署文档。 验证:113项测试通过;1项历史数值基线失败,在未修改的a3fddbb版本复现。浏览器验证5/52及30案例对比页通过。
70 lines
3.2 KiB
Python
70 lines
3.2 KiB
Python
"""Nullable/deprecated worker images retain their documented gateway keys."""
|
|
import asyncio
|
|
import json
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock
|
|
|
|
import httpx
|
|
import pytest
|
|
from starlette.requests import Request
|
|
|
|
from gateway.forward import rewrite_base64_to_url
|
|
|
|
|
|
def test_nullable_image_fields_keep_external_names(tmp_path):
|
|
value = {"hairline_images": [{"image_high_base64": None, "image_low_base64": None,
|
|
"grown_image_base64": None}],
|
|
"high_hairline_center_point": None, "face_measure": None}
|
|
actual = rewrite_base64_to_url(value, "https://example.test", str(tmp_path), rewrite_null_images=True)
|
|
assert actual == {"hairline_images": [{"image_high_url": None, "image_low_url": None,
|
|
"grown_image_url": None}],
|
|
"high_hairline_center_point": None, "face_measure": None}
|
|
assert not list(tmp_path.iterdir())
|
|
|
|
|
|
def test_nonempty_images_still_rewrite_alongside_null_fields(tmp_path):
|
|
import base64
|
|
import io
|
|
from PIL import Image
|
|
buf = io.BytesIO()
|
|
Image.new("RGBA", (2, 2), (255, 255, 255, 0)).save(buf, format="PNG")
|
|
value = {"image_middle_base64": base64.b64encode(buf.getvalue()).decode(),
|
|
"image_high_base64": None}
|
|
actual = rewrite_base64_to_url(value, "https://example.test", str(tmp_path), rewrite_null_images=True)
|
|
assert actual["image_middle_url"].startswith("https://example.test/static/annotations/")
|
|
assert actual["image_high_url"] is None
|
|
assert len(list(tmp_path.iterdir())) == 1
|
|
|
|
|
|
def test_legacy_nullable_image_field_names_remain_unchanged(tmp_path):
|
|
value = {"hairline_images": [{"grown_image_base64": None}], "face_measure": None}
|
|
assert rewrite_base64_to_url(value, "https://example.test", str(tmp_path)) == value
|
|
assert not list(tmp_path.iterdir())
|
|
|
|
|
|
@pytest.mark.parametrize("path,expected_key", [
|
|
("/api/v1/hairline/generate", "grown_image_base64"),
|
|
("/api/v1/hairline/generate-52", "grown_image_url"),
|
|
])
|
|
def test_proxy_rewrites_null_images_only_for_interface52(tmp_path, monkeypatch, path, expected_key):
|
|
import gateway.config as config
|
|
import gateway.forward as forward
|
|
|
|
monkeypatch.setattr(config, "get_config", lambda: {
|
|
"dispatch": {"request_timeout_seconds": 10}, "shared_password": "test-token",
|
|
"public_base_url": "https://example.test", "static_dir": str(tmp_path),
|
|
})
|
|
monkeypatch.setattr(forward, "acquire_worker", AsyncMock(return_value=SimpleNamespace(url="http://worker")))
|
|
monkeypatch.setattr(forward, "release_worker", AsyncMock())
|
|
worker_response = httpx.Response(200, json={"code": 0, "data": {"hairline_images": [
|
|
{"grown_image_base64": None}]}})
|
|
monkeypatch.setattr(forward, "get_client", lambda: SimpleNamespace(request=AsyncMock(return_value=worker_response)))
|
|
|
|
async def receive():
|
|
return {"type": "http.request", "body": b"", "more_body": False}
|
|
|
|
request = Request({"type": "http", "method": "POST", "path": path, "headers": []}, receive)
|
|
response = asyncio.run(forward.proxy_request(request, path))
|
|
assert response.status_code == 200
|
|
assert json.loads(response.body)["data"]["hairline_images"] == [{expected_key: None}]
|