feat(uploads): 本地磁盘直传(COS 降级路线)+ 报告照片存在性校验
CI / lint / typecheck / import-linter / tests / secrets (push) Canceled after 0s
CI / lint / typecheck / import-linter / tests / secrets (push) Canceled after 0s
- POST /uploads/direct:原始字节 body + Content-Type 申报 + scene query(默认 report_photo),场景/类型/大小三重校验复用 storage 既有规则,落盘 var/uploads/<scene>/<user_id>/<uuid>.<ext>(config 新增 upload_dir),key 布局与 COS 同构;不加 python-multipart 依赖 - storage/local.py:save_direct_upload + require_local_upload(resolve 归并 + is_relative_to 归属收敛 + 存在性校验,越界/模拟/不存在一律 404) - report/service.create_report:资格校验后、配额预扣前插入照片存在性校验(模拟 key 被拦且不消耗额度;regenerate 复用已验 photo_meta 不复核) - 测试:test_upload_direct 14 单测 + test_upload_direct_flow 4 集成(真 key 全链路/假 key 404 零流水/401/40010) 偏差说明:test_report_flow._create_report 改走 /uploads/direct 拿真 key(存在性校验必然封死假 key),40920 与幂等重放两条断言语义未动。
This commit is contained in:
@@ -133,6 +133,20 @@ async def _login(client: Any) -> tuple[dict[str, str], int]:
|
||||
return {"Authorization": f"Bearer {data['access_token']}"}, int(data["user_id"])
|
||||
|
||||
|
||||
async def _upload_photo(client: Any, headers: dict[str, str]) -> str:
|
||||
"""直传一张假 JPEG 拿真实 object_key(建报告对 photo_object_key 做存在性校验)。
|
||||
|
||||
字节内容不需要是真图片:上传链路只校验类型/大小,不嗅探内容。
|
||||
"""
|
||||
resp = await client.post(
|
||||
"/api/v1/uploads/direct?scene=report_photo",
|
||||
headers={**headers, "Content-Type": "image/jpeg"},
|
||||
content=b"\xff\xd8\xff\xe0" + b"\x00" * 64,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
return str(resp.json()["data"]["object_key"])
|
||||
|
||||
|
||||
async def _create_report(
|
||||
client: Any, headers: dict[str, str], *, request_key: str | None = None
|
||||
) -> dict[str, Any]:
|
||||
@@ -141,7 +155,7 @@ async def _create_report(
|
||||
headers=headers,
|
||||
json={
|
||||
"questionnaire": {"style": "通勤", "height_cm": 168},
|
||||
"photo_object_key": "report_photo/u1.jpg",
|
||||
"photo_object_key": await _upload_photo(client, headers),
|
||||
"request_key": request_key or f"rk-{uuid.uuid4().hex}",
|
||||
},
|
||||
)
|
||||
@@ -223,7 +237,8 @@ async def test_create_reserves_lifetime_quota_and_creates_pending_task(
|
||||
report = await _fetch_report(report_id)
|
||||
assert report.status == "pending"
|
||||
rp_input = await _fetch_input(report_id)
|
||||
assert rp_input.photo_meta == {"object_key": "report_photo/u1.jpg"}
|
||||
# photo_object_key 已由直传接口真实落盘(布局 report_photo/{user_id}/{uuid}.jpg)
|
||||
assert str(rp_input.photo_meta["object_key"]).startswith(f"report_photo/{user_id}/")
|
||||
assert rp_input.derived_features == {}
|
||||
|
||||
rows = await _ledger_rows(user_id, "report_basic")
|
||||
|
||||
Reference in New Issue
Block a user