Files
soul-yongping/scripts/test/process/test_backfill_persons_ckb_api_key.py

167 lines
5.7 KiB
Python
Raw Normal View History

# -*- 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_KEYCKB_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 更新 PersonPOST 带 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")