chore: 清理敏感与开发文档,仅同步代码
- 永久忽略并从仓库移除 开发文档/ - 移除并忽略 .env 与小程序私有配置 - 同步小程序/管理端/API与脚本改动 Made-with: Cursor
This commit is contained in:
38
scripts/test/process/2026-03-16-文章@某人自动创建-测试报告.md
Normal file
38
scripts/test/process/2026-03-16-文章@某人自动创建-测试报告.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# 测试报告 - 文章 @某人 自动创建存客宝获客计划
|
||||
|
||||
**日期**:2026-03-16
|
||||
**测试环境**:local (http://localhost:8080)
|
||||
**用例位置**:`scripts/test/process/test_article_mention_ckb_flow.py`
|
||||
|
||||
---
|
||||
|
||||
## 测试结论
|
||||
|
||||
| 用例 | 结果 | 说明 |
|
||||
|------|------|------|
|
||||
| test_person_ensure_creates_ckb_plan_when_not_exists | ❌ 失败 | 后端会调用存客宝创建计划,但存客宝 API 返回 400 |
|
||||
| test_person_ensure_returns_existing_when_name_exists | ❌ 失败 | 依赖上一条(需先创建成功) |
|
||||
| test_article_mention_flow_persons_list_contains_new | ❌ 失败 | 同上 |
|
||||
| test_person_ensure_rejects_empty_name | ✅ 通过 | name 为空时正确返回错误 |
|
||||
|
||||
---
|
||||
|
||||
## 失败原因
|
||||
|
||||
```
|
||||
error: 创建存客宝计划失败: 创建计划失败
|
||||
ckbResponse: {"code": 400, "data": [], "message": ""}
|
||||
```
|
||||
|
||||
后端逻辑正确:POST /api/db/persons 仅传 name 时,会按 name 查找,不存在则创建 Person 并调用存客宝创建获客计划。
|
||||
存客宝开放 API 返回 400,可能原因:
|
||||
- CKB_OPEN_API_KEY / CKB_OPEN_ACCOUNT 配置有误
|
||||
- deviceGroups 为空时存客宝不允许创建(需求文档有提及)
|
||||
- 存客宝 API 参数格式变更
|
||||
|
||||
---
|
||||
|
||||
## 建议
|
||||
|
||||
1. 后端:排查存客宝 create plan 400 原因,确认 deviceGroups 空时是否允许
|
||||
2. 测试:存客宝可连通后重新跑 `pytest process/test_article_mention_ckb_flow.py -v`
|
||||
20
scripts/test/process/README.md
Normal file
20
scripts/test/process/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 流程测试 (process)
|
||||
|
||||
> 跨端业务流程测试用例。验证多步骤、多接口串联的完整流程。
|
||||
|
||||
---
|
||||
|
||||
## 测试范围
|
||||
|
||||
- **下单→支付→回调→分润**:购买全链路
|
||||
- **推荐码绑定→访问记录→分润计算**:推广流程
|
||||
- **VIP 开通→资料填写→排行展示**:会员流程
|
||||
- **提现申请→审核→到账**:提现流程
|
||||
- **内容发布→审核→上架→用户可见**:内容流转
|
||||
- **文章 @某人 自动创建**:编辑文章输入 @新人物(不存在)→ 自动创建 Person + 存客宝获客计划(`test_article_mention_ckb_flow.py`)
|
||||
|
||||
---
|
||||
|
||||
## 用例编写
|
||||
|
||||
在此目录下新增 `.md` 或测试脚本,按业务流程组织用例。流程测试通常涉及 miniprogram + admin + API 多端联动。
|
||||
75
scripts/test/process/cleanup_test_data.py
Normal file
75
scripts/test/process/cleanup_test_data.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
清理流程测试产生的数据:persons(测试自动创建_、测试新人物_)、chapters(t 开头的 6 位数字 id)
|
||||
用法:cd scripts/test && python process/cleanup_test_data.py
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
import requests
|
||||
|
||||
API_BASE = os.environ.get("SOUL_API_BASE", "http://localhost:8080").rstrip("/")
|
||||
ADMIN_USER = os.environ.get("SOUL_ADMIN_USERNAME", "admin")
|
||||
ADMIN_PASS = os.environ.get("SOUL_ADMIN_PASSWORD", "admin123")
|
||||
|
||||
|
||||
def main():
|
||||
r = requests.post(
|
||||
f"{API_BASE}/api/admin",
|
||||
json={"username": ADMIN_USER, "password": ADMIN_PASS},
|
||||
timeout=10,
|
||||
)
|
||||
if not r.json().get("success") or not r.json().get("token"):
|
||||
print("登录失败")
|
||||
return 1
|
||||
token = r.json()["token"]
|
||||
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
|
||||
# 1. 删除测试 Person
|
||||
rp = requests.get(f"{API_BASE}/api/db/persons", headers=headers, timeout=10)
|
||||
if rp.json().get("success"):
|
||||
persons = rp.json().get("persons") or []
|
||||
for p in persons:
|
||||
name = p.get("name") or ""
|
||||
if name.startswith("测试自动创建_") or name.startswith("测试新人物_"):
|
||||
pid = p.get("personId")
|
||||
rd = requests.delete(
|
||||
f"{API_BASE}/api/db/persons?personId={pid}",
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
)
|
||||
if rd.json().get("success"):
|
||||
print(f" 已删除 Person: {name} ({pid})")
|
||||
else:
|
||||
print(f" 删除 Person 失败: {name} - {rd.json()}")
|
||||
|
||||
# 2. 删除测试 Chapter(id 形如 t123456)
|
||||
rl = requests.get(
|
||||
f"{API_BASE}/api/db/book?action=list",
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
)
|
||||
if rl.json().get("success"):
|
||||
sections = rl.json().get("sections") or []
|
||||
for s in sections:
|
||||
sid = s.get("id") or ""
|
||||
if re.match(r"^t\d{6}$", sid):
|
||||
rd = requests.delete(
|
||||
f"{API_BASE}/api/db/book?id={sid}",
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
)
|
||||
if rd.json().get("success"):
|
||||
print(f" 已删除 Chapter: {sid} ({s.get('title', '')})")
|
||||
else:
|
||||
print(f" 删除 Chapter 失败: {sid} - {rd.json()}")
|
||||
|
||||
print("清理完成")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
2
scripts/test/process/conftest.py
Normal file
2
scripts/test/process/conftest.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""process 专用 fixtures,继承 scripts/test/conftest.py"""
|
||||
218
scripts/test/process/test_article_mention_ckb_flow.py
Normal file
218
scripts/test/process/test_article_mention_ckb_flow.py
Normal file
@@ -0,0 +1,218 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
流程测试:文章编辑 @某人 不存在时自动创建 Person + 存客宝获客计划
|
||||
|
||||
需求来源:临时需求池/2026-03-16-文章编辑自动创建@和#.md
|
||||
验收:编辑文章输入 @新人物(链接人与事中无)→ 保存 → 链接人与事列表出现「新人物」,存客宝有对应计划
|
||||
|
||||
流程:管理端 ensureMentionsAndTags 对 content 中 @name 调用 POST /api/db/persons {name}
|
||||
→ 后端按 name 查找,不存在则创建 Person + 调用存客宝创建获客计划
|
||||
|
||||
前置条件:存客宝 API 可连通(CKB_OPEN_API_KEY 等配置正确),且存在名为 soul 的设备;否则创建新 Person 会失败
|
||||
"""
|
||||
import random
|
||||
import time
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from util import admin_headers
|
||||
|
||||
|
||||
def _unique_name():
|
||||
"""生成唯一名称,避免与已有 Person 冲突"""
|
||||
return f"测试自动创建_{int(time.time() * 1000)}"
|
||||
|
||||
|
||||
def test_person_ensure_creates_ckb_plan_when_not_exists(admin_token, base_url):
|
||||
"""
|
||||
@某人 不存在时:POST /api/db/persons 仅传 name → 应创建 Person 并自动创建存客宝获客计划
|
||||
"""
|
||||
if not admin_token:
|
||||
pytest.skip("admin 登录失败,跳过")
|
||||
name = _unique_name()
|
||||
r = requests.post(
|
||||
f"{base_url}/api/db/persons",
|
||||
headers=admin_headers(admin_token),
|
||||
json={"name": name},
|
||||
timeout=15,
|
||||
)
|
||||
assert r.status_code == 200, f"响应: {r.text}"
|
||||
data = r.json()
|
||||
assert data.get("success") is True, f"success 应为 true: {data}"
|
||||
person = data.get("person")
|
||||
assert person is not None, "应返回 person"
|
||||
assert person.get("name") == name
|
||||
assert person.get("personId"), "应有 personId"
|
||||
assert person.get("token"), "应有 token(小程序 @ 点击时兑换密钥)"
|
||||
# 存客宝获客计划应已创建
|
||||
ckb_plan_id = person.get("ckbPlanId") or 0
|
||||
assert ckb_plan_id > 0, f"应自动创建存客宝计划,ckbPlanId 应 > 0,实际: {ckb_plan_id}"
|
||||
assert person.get("ckbApiKey"), "应有 ckbApiKey"
|
||||
|
||||
|
||||
def test_person_ensure_returns_existing_when_name_exists(admin_token, base_url):
|
||||
"""
|
||||
@某人 已存在时:POST /api/db/persons 仅传 name → 应返回已有 Person,不重复创建
|
||||
"""
|
||||
if not admin_token:
|
||||
pytest.skip("admin 登录失败,跳过")
|
||||
name = _unique_name()
|
||||
# 第一次创建
|
||||
r1 = requests.post(
|
||||
f"{base_url}/api/db/persons",
|
||||
headers=admin_headers(admin_token),
|
||||
json={"name": name},
|
||||
timeout=15,
|
||||
)
|
||||
assert r1.status_code == 200 and r1.json().get("success")
|
||||
first_id = r1.json()["person"]["personId"]
|
||||
# 第二次相同 name,应返回已有
|
||||
r2 = requests.post(
|
||||
f"{base_url}/api/db/persons",
|
||||
headers=admin_headers(admin_token),
|
||||
json={"name": name},
|
||||
timeout=15,
|
||||
)
|
||||
assert r2.status_code == 200 and r2.json().get("success")
|
||||
second = r2.json()["person"]
|
||||
assert second["personId"] == first_id, "相同 name 应返回同一 Person"
|
||||
|
||||
|
||||
def test_person_ensure_rejects_empty_name(admin_token, base_url):
|
||||
"""name 为空时 POST /api/db/persons 应返回错误(不依赖存客宝)"""
|
||||
if not admin_token:
|
||||
pytest.skip("admin 登录失败,跳过")
|
||||
r = requests.post(
|
||||
f"{base_url}/api/db/persons",
|
||||
headers=admin_headers(admin_token),
|
||||
json={"name": ""},
|
||||
timeout=10,
|
||||
)
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert data.get("success") is False
|
||||
assert "name" in (data.get("error") or "").lower() or "必填" in (data.get("error") or "")
|
||||
|
||||
|
||||
def test_article_mention_flow_persons_list_contains_new(admin_token, base_url):
|
||||
"""
|
||||
流程:创建新 Person 后,GET /api/db/persons 列表应包含该人
|
||||
"""
|
||||
if not admin_token:
|
||||
pytest.skip("admin 登录失败,跳过")
|
||||
name = _unique_name()
|
||||
r_create = requests.post(
|
||||
f"{base_url}/api/db/persons",
|
||||
headers=admin_headers(admin_token),
|
||||
json={"name": name},
|
||||
timeout=15,
|
||||
)
|
||||
assert r_create.status_code == 200 and r_create.json().get("success")
|
||||
person_id = r_create.json()["person"]["personId"]
|
||||
# 拉列表
|
||||
r_list = requests.get(
|
||||
f"{base_url}/api/db/persons",
|
||||
headers=admin_headers(admin_token),
|
||||
timeout=10,
|
||||
)
|
||||
assert r_list.status_code == 200 and r_list.json().get("success")
|
||||
persons = r_list.json().get("persons") or []
|
||||
found = [p for p in persons if p.get("personId") == person_id]
|
||||
assert len(found) == 1, f"列表应包含新建的 Person {person_id}"
|
||||
assert found[0].get("ckbPlanId", 0) > 0, "列表中应有 ckbPlanId"
|
||||
|
||||
|
||||
def test_new_article_save_auto_creates_person_and_ckb(admin_token, base_url):
|
||||
"""
|
||||
完整流程:新建文章,content 含 @新人物 → 保存时 ensureMentionsAndTags 自动 POST persons
|
||||
→ 创建 Person + 存客宝获客计划 → 再 PUT book 保存文章
|
||||
"""
|
||||
if not admin_token:
|
||||
pytest.skip("admin 登录失败,跳过")
|
||||
ts = int(time.time() * 1000)
|
||||
rnd = random.randint(100000, 999999)
|
||||
name = f"测试新人物_{ts}_{rnd}"
|
||||
# chapters.id 限制 size:20,用短 id
|
||||
section_id = f"t{rnd}"
|
||||
|
||||
# 1. 获取 book 结构,取第一个 part/chapter
|
||||
r_list = requests.get(
|
||||
f"{base_url}/api/db/book?action=list",
|
||||
headers=admin_headers(admin_token),
|
||||
timeout=10,
|
||||
)
|
||||
assert r_list.status_code == 200 and r_list.json().get("success")
|
||||
sections = r_list.json().get("sections") or []
|
||||
part_id = "part-1"
|
||||
chapter_id = "chapter-1"
|
||||
part_title = "未分类"
|
||||
chapter_title = "未分类"
|
||||
if sections:
|
||||
first = sections[0]
|
||||
part_id = first.get("partId") or part_id
|
||||
chapter_id = first.get("chapterId") or chapter_id
|
||||
part_title = first.get("partTitle") or part_title
|
||||
chapter_title = first.get("chapterTitle") or chapter_title
|
||||
|
||||
# 2. 模拟 ensureMentionsAndTags:content 含 @name 时先 POST persons
|
||||
content = f"这是一篇测试文章,@{name} 会被自动创建并同步存客宝。"
|
||||
r_person = requests.post(
|
||||
f"{base_url}/api/db/persons",
|
||||
headers=admin_headers(admin_token),
|
||||
json={"name": name},
|
||||
timeout=15,
|
||||
)
|
||||
assert r_person.status_code == 200, f"创建 Person 失败: {r_person.text}"
|
||||
person_data = r_person.json()
|
||||
assert person_data.get("success") is True, f"Person 创建失败: {person_data}"
|
||||
person = person_data.get("person")
|
||||
assert person and person.get("ckbPlanId", 0) > 0, "应自动创建存客宝获客计划"
|
||||
|
||||
# 3. 新建文章(PUT /api/db/book)
|
||||
payload = {
|
||||
"id": section_id,
|
||||
"title": f"测试自动创建_{ts}",
|
||||
"content": content,
|
||||
"price": 1,
|
||||
"isFree": False,
|
||||
"partId": part_id,
|
||||
"partTitle": part_title,
|
||||
"chapterId": chapter_id,
|
||||
"chapterTitle": chapter_title,
|
||||
"editionStandard": True,
|
||||
"editionPremium": False,
|
||||
"isNew": False,
|
||||
"hotScore": 0,
|
||||
}
|
||||
r_put = requests.put(
|
||||
f"{base_url}/api/db/book",
|
||||
headers=admin_headers(admin_token),
|
||||
json=payload,
|
||||
timeout=15,
|
||||
)
|
||||
assert r_put.status_code == 200, f"保存文章失败: {r_put.text}"
|
||||
put_data = r_put.json()
|
||||
assert put_data.get("success") is True, f"保存文章失败: {put_data}"
|
||||
|
||||
# 4. 验证 persons 列表包含新人物
|
||||
r_persons = requests.get(
|
||||
f"{base_url}/api/db/persons",
|
||||
headers=admin_headers(admin_token),
|
||||
timeout=10,
|
||||
)
|
||||
assert r_persons.status_code == 200
|
||||
persons = r_persons.json().get("persons") or []
|
||||
found = [p for p in persons if p.get("name") == name]
|
||||
assert len(found) == 1, f"链接人与事列表应包含「{name}」"
|
||||
assert found[0].get("ckbPlanId", 0) > 0, "应有存客宝获客计划"
|
||||
|
||||
# 5. 清理:删除测试文章(避免重复运行冲突)
|
||||
try:
|
||||
requests.delete(
|
||||
f"{base_url}/api/db/book?id={section_id}",
|
||||
headers=admin_headers(admin_token),
|
||||
timeout=10,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
166
scripts/test/process/test_backfill_persons_ckb_api_key.py
Normal file
166
scripts/test/process/test_backfill_persons_ckb_api_key.py
Normal file
@@ -0,0 +1,166 @@
|
||||
# -*- 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")
|
||||
11
scripts/test/process/test_health.py
Normal file
11
scripts/test/process/test_health.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
流程测试前置:健康检查。确保 soul-api 已启动。
|
||||
"""
|
||||
import requests
|
||||
|
||||
|
||||
def test_health(base_url):
|
||||
"""GET /health 健康检查"""
|
||||
r = requests.get(f"{base_url}/health", timeout=5)
|
||||
assert r.status_code == 200
|
||||
Reference in New Issue
Block a user