LEGAL_PRIVACY_BASELINE §4/§6 要求任何订单创建路径必须记录同意审计字段。
portal 路径已落 consent_channel=portal / consent_operator=guardian (见
web_public.py + intake_store.save), admin 路径 6/20 之前完全不入任何 consent
字段 (admin/routes/orders.py grep 'consent' 0 命中), 形成合规盲区。
落地:
- CreateOrderRequest 新增必填 consent: ConsentInfo
- consent_method Literal: verbal_chat / phone_recording / screenshot /
written_form / self_declared
- consent_note Optional[str]
- 缺失或非法 → HTTP 422
- Order 模型 + DAO _WRITABLE_COLUMNS 加 consent_method / consent_given_at
(冗余落库避免每次列表 join order_intakes)
- schema 增量: ALTER TABLE orders ADD COLUMN consent_method / consent_given_at
(幂等)
- create_order 同步写 order_intakes (独立 IntakeStore.for_db, 不复用 OrdersDAO
conn — T12-D conn ownership 修复保驾)
- consent_channel = payload.source (xianyu/wechat/school/web)
- consent_operator 严格按基线白名单 self/guardian/admin_import:
- web 渠道: 'guardian' (与 intake_store.save 默认值一致)
- 其他渠道: 'admin_import' (后台代录, 同意来源是渠道商)
- consent_method / consent_given_at / consent_note 落库
测试:
- test_create_order_rejects_missing_consent_block[xianyu|wechat|school|web] (4 个)
- test_create_order_writes_intake_record_with_consent_audit
- test_create_order_external_channel_marks_consent_operator_as_admin
- test_create_order_rejects_invalid_consent_method
- test_order_detail_returns_consent_method_and_given_at
- 更新 test_create_order_returns_masked_payload_with_history (加 consent)
- 更新 test_admin_orders_alias_list_and_detail (加 consent)
验证:
- 25/25 admin/tests/{test_routes_orders,test_admin_alias_routes} 通过
- ruff + mypy 通过
- 端到端 smoke: 4 笔订单 (2 terminal + 1 pending + 1 paid-in-window) 实测
包含 consent 字段全部通过
- dev-verify: 1186 passed / 2 failed (失败的 2 个均为 worktree 环境限制:
test_backup_restore_service_level 的 subprocess 路径假设 + 6/19 已知问题,
与本改动无关; main 上单跑同样测试通过)
74 lines
2.5 KiB
Python
74 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
|
|
def test_admin_dashboard_alias_served(client):
|
|
resp = client.get("/admin/dashboard")
|
|
assert resp.status_code == 200
|
|
assert "仪表盘" in resp.text
|
|
assert "/static/dashboard.js" in resp.text
|
|
|
|
|
|
def test_admin_orders_alias_list_and_detail(client, auth_headers):
|
|
created = client.post(
|
|
"/api/admin/orders",
|
|
headers=auth_headers,
|
|
json={
|
|
"source": "web",
|
|
"service_version": "standard",
|
|
"amount_cents": 9900,
|
|
"customer_name": "张家长",
|
|
"customer_phone": "13800138000",
|
|
"candidate_name": "张三",
|
|
"candidate_province": "湖南",
|
|
# A-2 (2026-06-20): 后台补录路径强制要求 consent 块
|
|
"consent": {
|
|
"consent_method": "verbal_chat",
|
|
"consent_note": "alias route test",
|
|
},
|
|
},
|
|
)
|
|
assert created.status_code == 201, created.text
|
|
order_id = created.json()["order"]["id"]
|
|
|
|
listed = client.get("/api/admin/orders", headers=auth_headers)
|
|
assert listed.status_code == 200, listed.text
|
|
assert any(item["id"] == order_id for item in listed.json())
|
|
|
|
detail = client.get(f"/api/admin/orders/{order_id}", headers=auth_headers)
|
|
assert detail.status_code == 200, detail.text
|
|
assert detail.json()["order"]["id"] == order_id
|
|
|
|
|
|
def test_admin_cases_alias_crud(client, auth_headers):
|
|
created = client.post(
|
|
"/api/admin/cases",
|
|
headers=auth_headers,
|
|
json={
|
|
"title": "后台别名案例",
|
|
"category": "success",
|
|
"summary": "别名路径创建",
|
|
"content": "别名路径正文",
|
|
"tags": ["alias"],
|
|
},
|
|
)
|
|
assert created.status_code == 201, created.text
|
|
case_id = created.json()["id"]
|
|
|
|
listed = client.get("/api/admin/cases", headers=auth_headers)
|
|
assert listed.status_code == 200, listed.text
|
|
assert any(item["id"] == case_id for item in listed.json()["items"])
|
|
|
|
detail = client.get(f"/api/admin/cases/{case_id}", headers=auth_headers)
|
|
assert detail.status_code == 200, detail.text
|
|
assert detail.json()["id"] == case_id
|
|
|
|
|
|
def test_admin_stats_aliases(client, auth_headers):
|
|
dashboard = client.get("/api/admin/stats/dashboard", headers=auth_headers)
|
|
assert dashboard.status_code == 200, dashboard.text
|
|
assert "summary" in dashboard.json()
|
|
|
|
orders = client.get("/api/admin/stats/orders", headers=auth_headers)
|
|
assert orders.status_code == 200, orders.text
|
|
assert "total_orders" in orders.json()
|