167 lines
5.7 KiB
Python
167 lines
5.7 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
流程测试:从存客宝获取所有计划的 apiKey,补齐本地 persons.ckb_api_key
|
|||
|
|
|
|||
|
|
场景:persons 表有 ckb_plan_id 但 ckb_api_key 为空时,调用存客宝 plan/detail 获取 apiKey 并更新本地
|
|||
|
|
|
|||
|
|
前置条件:
|
|||
|
|
- 测试环境(SOUL_TEST_ENV=souldev 或 local)
|
|||
|
|
- soul-api 可连通,CKB_OPEN_API_KEY、CKB_OPEN_ACCOUNT 已配置(soul-api/.env)
|
|||
|
|
"""
|
|||
|
|
import hashlib
|
|||
|
|
import time
|
|||
|
|
|
|||
|
|
import pytest
|
|||
|
|
import requests
|
|||
|
|
|
|||
|
|
from config import SOUL_API_ENV
|
|||
|
|
from util import admin_headers
|
|||
|
|
|
|||
|
|
CKB_OPEN_BASE = "https://ckbapi.quwanzhi.com"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _ckb_open_sign(account: str, ts: int, api_key: str) -> str:
|
|||
|
|
"""存客宝开放 API 签名:sign = MD5(MD5(account+timestamp) + apiKey)"""
|
|||
|
|
plain = account + str(ts)
|
|||
|
|
first = hashlib.md5(plain.encode()).hexdigest()
|
|||
|
|
return hashlib.md5((first + api_key).encode()).hexdigest()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _ckb_get_token(api_key: str, account: str) -> str:
|
|||
|
|
"""获取存客宝开放 API JWT"""
|
|||
|
|
ts = int(time.time())
|
|||
|
|
sign = _ckb_open_sign(account, ts, api_key)
|
|||
|
|
r = requests.post(
|
|||
|
|
f"{CKB_OPEN_BASE}/v1/open/auth/token",
|
|||
|
|
json={"apiKey": api_key, "account": account, "timestamp": ts, "sign": sign},
|
|||
|
|
timeout=15,
|
|||
|
|
)
|
|||
|
|
data = r.json()
|
|||
|
|
if data.get("code") != 200:
|
|||
|
|
raise RuntimeError(f"存客宝鉴权失败: {data.get('message', r.text)}")
|
|||
|
|
token = (data.get("data") or {}).get("token")
|
|||
|
|
if not token:
|
|||
|
|
raise RuntimeError("存客宝返回无 token")
|
|||
|
|
return token
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _ckb_get_plan_api_key(token: str, plan_id: int) -> str:
|
|||
|
|
"""调用 plan/detail 获取计划级 apiKey"""
|
|||
|
|
r = requests.get(
|
|||
|
|
f"{CKB_OPEN_BASE}/v1/plan/detail",
|
|||
|
|
params={"planId": plan_id},
|
|||
|
|
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
|||
|
|
timeout=15,
|
|||
|
|
)
|
|||
|
|
data = r.json()
|
|||
|
|
if data.get("code") != 200:
|
|||
|
|
raise RuntimeError(f"获取计划详情失败 planId={plan_id}: {data.get('message', r.text)}")
|
|||
|
|
api_key = (data.get("data") or {}).get("apiKey")
|
|||
|
|
if not api_key:
|
|||
|
|
raise RuntimeError(f"计划 {plan_id} 详情中无 apiKey")
|
|||
|
|
return api_key
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _load_ckb_config() -> tuple[str, str]:
|
|||
|
|
"""从 soul-api/.env 加载 CKB 配置"""
|
|||
|
|
def _parse_env(path):
|
|||
|
|
out = {}
|
|||
|
|
if not path.exists():
|
|||
|
|
return out
|
|||
|
|
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
|
|||
|
|
line = line.strip()
|
|||
|
|
if not line or line.startswith("#") or "=" not in line:
|
|||
|
|
continue
|
|||
|
|
k, v = line.split("=", 1)
|
|||
|
|
out[k.strip()] = v.strip().strip('"').strip("'")
|
|||
|
|
return out
|
|||
|
|
|
|||
|
|
for name in [".env", ".env.development", ".env.production"]:
|
|||
|
|
env_path = SOUL_API_ENV / name
|
|||
|
|
loaded = _parse_env(env_path)
|
|||
|
|
api_key = (loaded.get("CKB_OPEN_API_KEY") or "").strip()
|
|||
|
|
account = (loaded.get("CKB_OPEN_ACCOUNT") or "").strip()
|
|||
|
|
if api_key and account:
|
|||
|
|
return api_key, account
|
|||
|
|
return "", ""
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_backfill_persons_ckb_api_key(admin_token, base_url):
|
|||
|
|
"""
|
|||
|
|
从存客宝获取所有计划的 apiKey,补齐本地 persons.ckb_api_key 为空的记录
|
|||
|
|
"""
|
|||
|
|
if not admin_token:
|
|||
|
|
pytest.skip("admin 登录失败,跳过")
|
|||
|
|
|
|||
|
|
ckb_api_key, ckb_account = _load_ckb_config()
|
|||
|
|
if not ckb_api_key or not ckb_account:
|
|||
|
|
pytest.skip("CKB_OPEN_API_KEY 或 CKB_OPEN_ACCOUNT 未配置,跳过")
|
|||
|
|
|
|||
|
|
# 1. 拉取 persons 列表
|
|||
|
|
r = requests.get(
|
|||
|
|
f"{base_url}/api/db/persons",
|
|||
|
|
headers=admin_headers(admin_token),
|
|||
|
|
timeout=10,
|
|||
|
|
)
|
|||
|
|
assert r.status_code == 200, f"拉取 persons 失败: {r.text}"
|
|||
|
|
data = r.json()
|
|||
|
|
assert data.get("success") is True, f"拉取 persons 失败: {data}"
|
|||
|
|
persons = data.get("persons") or []
|
|||
|
|
|
|||
|
|
# 2. 筛选需要补全的:ckb_plan_id > 0 且 ckb_api_key 为空
|
|||
|
|
need_backfill = [
|
|||
|
|
p
|
|||
|
|
for p in persons
|
|||
|
|
if (p.get("ckbPlanId") or 0) > 0
|
|||
|
|
and not (p.get("ckbApiKey") or "").strip()
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
if not need_backfill:
|
|||
|
|
pytest.skip("无需要补全 ckb_api_key 的 Person,跳过")
|
|||
|
|
|
|||
|
|
# 3. 获取存客宝 JWT
|
|||
|
|
ckb_token = _ckb_get_token(ckb_api_key, ckb_account)
|
|||
|
|
|
|||
|
|
# 4. 逐个补全
|
|||
|
|
updated = 0
|
|||
|
|
failed = []
|
|||
|
|
for p in need_backfill:
|
|||
|
|
plan_id = p.get("ckbPlanId") or 0
|
|||
|
|
person_id = p.get("personId") or ""
|
|||
|
|
name = p.get("name") or ""
|
|||
|
|
try:
|
|||
|
|
api_key = _ckb_get_plan_api_key(ckb_token, plan_id)
|
|||
|
|
except Exception as e:
|
|||
|
|
failed.append((name, str(e)))
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
# 5. 调用 soul-api 更新 Person(POST 带 personId 为更新,传完整字段避免覆盖)
|
|||
|
|
payload = {
|
|||
|
|
"personId": person_id,
|
|||
|
|
"name": name,
|
|||
|
|
"label": (p.get("label") or ""),
|
|||
|
|
"ckbApiKey": api_key,
|
|||
|
|
"greeting": (p.get("greeting") or ""),
|
|||
|
|
"tips": (p.get("tips") or ""),
|
|||
|
|
"remarkType": (p.get("remarkType") or ""),
|
|||
|
|
"remarkFormat": (p.get("remarkFormat") or ""),
|
|||
|
|
"startTime": (p.get("startTime") or "09:00"),
|
|||
|
|
"endTime": (p.get("endTime") or "18:00"),
|
|||
|
|
}
|
|||
|
|
if p.get("addFriendInterval"):
|
|||
|
|
payload["addFriendInterval"] = p["addFriendInterval"]
|
|||
|
|
r_update = requests.post(
|
|||
|
|
f"{base_url}/api/db/persons",
|
|||
|
|
headers=admin_headers(admin_token),
|
|||
|
|
json=payload,
|
|||
|
|
timeout=15,
|
|||
|
|
)
|
|||
|
|
if r_update.status_code == 200 and r_update.json().get("success"):
|
|||
|
|
updated += 1
|
|||
|
|
else:
|
|||
|
|
failed.append((name, r_update.text or "更新失败"))
|
|||
|
|
|
|||
|
|
assert not failed, f"补全失败: {failed}"
|
|||
|
|
assert updated > 0, f"应至少补全 1 条,实际补全 {updated} 条"
|
|||
|
|
print(f"\n[backfill] 成功补全 {updated} 条 persons.ckb_api_key")
|