Files
hair/tests/test_reqlog.py
T
xsl a0f53ad6ad feat: ComfyUI 默认提示词统一改为「填充遮罩区域的头发,人物增加一点磨皮」
- app.py 各接口 Form 默认值与说明、hairline/service.py _REDRAW_PROMPT、
  hairline/redraw.py _DEFAULT_PROMPT、face_analysis/hairline_grow.py 兜底值
- add_hair.json / add_hair2.json / 0716add-hair-api.json / 0716add-hair.json /
  hair_repaint.json 工作流节点60内置文本
- static 测试页、local_test、benchmark 脚本、接口文档同步
注意:新提示词首次请求会因 CLIP 缓存失效变慢一次,之后恢复
2026-09-16 00:27:33 +08:00

110 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""gateway.reqlog 单测:multipart 入参解析(标量 + 图片存盘转 URL)+ 出参摘要。
不依赖网络 / GPU,纯函数级。
"""
import base64
import json
from gateway.reqlog import extract_form_params, summarize_for_log
# 1x1 透明 PNG(合法 magic byte)
_PNG = (
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
b"\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\nIDATx\x9cc\x00\x01"
b"\x00\x00\x05\x00\x01\r\n-\xb4\x00\x00\x00\x00IEND\xaeB`\x82"
)
def _multipart(fields, files, boundary=b"----testbnd"):
"""手搓 multipart/form-data body。fields: {name:str}; files: {name:(fname,ctype,bytes)}."""
parts = []
for name, val in fields.items():
parts.append(b"--" + boundary + b"\r\n")
parts.append(b'Content-Disposition: form-data; name="' + name.encode() + b'"\r\n\r\n')
parts.append(val.encode("utf-8") + b"\r\n")
for name, (fname, ctype, data) in files.items():
parts.append(b"--" + boundary + b"\r\n")
parts.append(b'Content-Disposition: form-data; name="' + name.encode() + b'"; filename="'
+ fname.encode() + b'"\r\n')
parts.append(b"Content-Type: " + ctype.encode() + b"\r\n\r\n")
parts.append(data + b"\r\n")
parts.append(b"--" + boundary + b"--\r\n")
return b"".join(parts)
def test_extract_scalars_and_images(tmp_path):
b64 = "data:image/png;base64," + base64.b64encode(_PNG).decode()
body = _multipart(
fields={"gender": "female", "hair_style": "1,3", "use_mask": "True",
"prompt": "填充遮罩区域的头发,人物增加一点磨皮", "image_base64": b64},
files={"image_file": ("x.png", "image/png", _PNG)},
)
ct = "multipart/form-data; boundary=----testbnd"
res = extract_form_params(ct, body, str(tmp_path), "https://test.local")
# 标量原样
assert res["gender"] == "female"
assert res["hair_style"] == "1,3"
assert res["use_mask"] == "True"
# 图片字段是元信息 dict(不是 base64 文本)
assert res["image_file"]["_image"] is True
assert res["image_file"]["source"] == "file"
assert res["image_file"]["filename"] == "x.png"
assert res["image_file"]["url"] is not None
assert res["image_base64"]["_image"] is True
assert res["image_base64"]["source"] == "base64"
assert res["image_base64"]["url"] is not None
# 图片已落盘
saved = [p.name for p in tmp_path.iterdir()]
assert any(n.startswith("in_") and n.endswith(".png") for n in saved)
# URL 指向正确路径
assert res["image_file"]["url"].startswith("https://test.local/static/annotations/in_")
# 关键:日志里绝不包含 base64 文本
blob = json.dumps(res, ensure_ascii=False)
assert b64.split(",", 1)[1][:40] not in blob
def test_extract_non_multipart(tmp_path):
res = extract_form_params("application/json", b'{"a":1}', str(tmp_path), "https://t.local")
assert res == {"_content_type": "application/json", "_body_bytes": 7}
def test_extract_empty_content_type(tmp_path):
res = extract_form_params("", b"hello", str(tmp_path), "https://t.local")
assert res["_body_bytes"] == 5
def test_summarize_truncates_long_values():
big = {
"landmarks": [[i, i] for i in range(500)],
"name": "x" * 1000,
"deep": {"a": {"b": {"c": {"d": {"e": {"f": {"g": 1}}}}}}},
}
sm = summarize_for_log(big)
# 长数组被截断 + 标注剩余数
assert isinstance(sm["landmarks"], list)
assert sm["landmarks"][-1].startswith("…(+")
assert len(sm["landmarks"]) == 6 # 5 项 + 1 个标注
# 长字符串被截断
assert "…(+" in sm["name"]
# 总体积受控
assert len(json.dumps(sm, ensure_ascii=False).encode()) <= 8192
def test_summarize_keeps_small_intact():
obj = {"code": 0, "data": {"hairline_images": [{"order": 1, "image_middle_url": "https://x/y.png"}]}}
assert summarize_for_log(obj) == obj
def test_summarize_byte_cap_stub():
# 构造大量小字段触发字节上限
obj = {f"k{i}": {f"j{j}": "abcdefghij" for j in range(50)} for i in range(50)}
sm = summarize_for_log(obj, max_bytes=2048)
assert isinstance(sm, dict) and sm.get("_truncated") is True
assert "size_bytes" in sm