feat(gateway): 接入接口22与账号鉴权测试页

This commit is contained in:
xsl
2026-11-09 21:42:16 +08:00
parent 5eca74c9c0
commit 6d003dbf80
8 changed files with 588 additions and 12 deletions
+68 -1
View File
@@ -1,8 +1,9 @@
"""Gateway-only interface 52 integration: raw forwarding, token and image URLs."""
"""Gateway integration for interfaces 52 and 22: raw forwarding, token and image URLs."""
import asyncio
import base64
import importlib
import io
from contextlib import closing
from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock
@@ -15,6 +16,9 @@ from gateway import config, forward
gateway = importlib.import_module("gateway.app")
PATH = "/api/v1/hairline/generate-52"
GROW22 = "/api/v1/hair/grow-22"
@pytest.fixture
def client_headers(monkeypatch):
monkeypatch.setenv("HAIR_API_TOKEN_SECRET", "gateway-test-signing-secret-at-least-32")
@@ -167,3 +171,66 @@ def test_public_interface52_page_has_no_internal_password():
assert client.get("/static/hairline_reference_box.js").status_code == 200
finally:
client.close()
def test_interface22_forwards_repeated_density_fields_and_serves_image_urls(
proxy_setup, monkeypatch, client_headers,
):
cfg, acquire, release, release_worker, unhealthy = proxy_setup
png, jpg = image_bytes("PNG"), image_bytes("JPEG")
calls = []
def worker(request):
calls.append(request)
return httpx.Response(200, json={"code": 0, "data": {"results": [
{"order": 1, "hairline_type": "ellipse", "hair_density": "sparse",
"image_base64": base64.b64encode(png).decode(),
"grown_image_base64": base64.b64encode(jpg).decode()},
{"order": 1, "hairline_type": "ellipse", "hair_density": "dense",
"image_base64": base64.b64encode(png).decode(), "grown_image_base64": None},
]}})
upstream = httpx.AsyncClient(transport=httpx.MockTransport(worker))
monkeypatch.setattr(forward, "get_client", lambda: upstream)
source = httpx.Request(
"POST", "http://testserver" + GROW22,
files={"image_file": ("face.png", png, "image/png")},
data={"gender": "female", "hair_style": "1", "hairline_height": "-0.5",
"hair_density": ["sparse", "dense"]},
)
raw = source.read()
assert raw.count(b'name="hair_density"') == 2
try:
with closing(TestClient(gateway.app)) as client:
response = client.post(GROW22, content=raw, headers={
**client_headers, "Content-Type": source.headers["content-type"],
})
assert response.status_code == 200
sparse, dense = response.json()["data"]["results"]
assert [sparse["hair_density"], dense["hair_density"]] == ["sparse", "dense"]
assert client.get(sparse["image_url"]).content == png
assert client.get(sparse["grown_image_url"]).content == jpg
assert dense["grown_image_base64"] is None
finally:
asyncio.run(upstream.aclose())
assert len(calls) == 1
assert str(calls[0].url) == "http://worker" + GROW22
assert calls[0].content == raw
assert calls[0].headers["x-internal-token"] == cfg["shared_password"]
assert "authorization" not in calls[0].headers
acquire.assert_awaited_once_with(cfg, 0)
release.assert_called_once()
release_worker.assert_awaited_once()
unhealthy.assert_not_awaited()
def test_interface22_page_uses_gateway_login_and_authenticated_requests():
with closing(TestClient(gateway.app)) as client:
page = client.get("/static/test_interface22.html")
assert page.status_code == 200
assert 'id="client-auth"' in page.text
for path in (GROW22, PATH):
assert f"window.hairClientFetch('{path}'" in page.text
assert "X-Internal-Token" not in page.text
assert "localhost:8187" not in page.text
assert client.get("/static/client_auth.js").status_code == 200