Files
xianyan/docs/toolsapi/tests/test_user_center_api.py
Developer 283950ea07 chore: 批量代码优化与功能迭代更新
本次提交包含大量代码优化、功能新增与服务端配置更新:
1. 修复分析报告统计数据,调整CMake策略设置
2. 优化APP权限配置、编辑器与聊天界面组件
3. 更新依赖库版本与pubspec配置
4. 新增文件传输服务端、信令服务器相关配置与脚本
5. 完善用户注销功能与数据库迁移脚本
6. 优化多处动画效果、代码风格与日志输出
7. 新增多种调试与部署脚本,修复已知BUG
2026-05-12 06:28:04 +08:00

550 lines
19 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
============================================================
闲言APP — 用户中心API自动化测试脚本
创建时间: 2026-05-10
作用: 模拟用户注册/登录/各场景行为,最后注销测试用户
============================================================
"""
import base64
import hashlib
import hmac
import json
import os
import random
import string
import sys
import time
import requests
BASE_URL = "https://tools.wktyl.com"
RECEIPT_SECRET = "Xy7kP9mL2qR4wS8v"
TIMEOUT = 15
session = requests.Session()
session.headers.update({
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded",
})
def generate_receipt(action: str, payload: str) -> dict:
ts = int(time.time())
nonce = os.urandom(4).hex()
payload_digest = hashlib.sha256(payload.encode()).hexdigest()[:16]
data = {
"action": action,
"payload": payload_digest,
"ts": ts,
"nonce": nonce,
}
receipt = base64.b64encode(json.dumps(data).encode()).decode()
sig = hmac.new(
RECEIPT_SECRET.encode(),
receipt.encode(),
hashlib.sha256,
).hexdigest()
return {"receipt": receipt, "sig": sig}
def random_string(length: int = 8) -> str:
return "".join(random.choices(string.ascii_lowercase + string.digits, k=length))
def log_test(name: str, success: bool, detail: str = ""):
status = "✅ PASS" if success else "❌ FAIL"
msg = f"{status} | {name}"
if detail:
msg += f"{detail}"
print(msg)
def api_post(path: str, data: dict = None, token: str = None) -> dict:
headers = {}
if token:
headers["token"] = token
resp = session.post(f"{BASE_URL}{path}", data=data, headers=headers, timeout=TIMEOUT)
return resp.json()
def api_get(path: str, params: dict = None, token: str = None) -> dict:
headers = {}
if token:
headers["token"] = token
resp = session.get(f"{BASE_URL}{path}", params=params, headers=headers, timeout=TIMEOUT)
return resp.json()
# ============================================================
# 测试场景
# ============================================================
def test_register_and_login():
"""场景1: 注册新用户 → 密码登录 → 获取用户信息"""
username = f"test_{random_string()}"
password = f"Test@{random_string(12)}"
email = f"{username}@testmail.com"
print(f"\n{'='*60}")
print(f"📋 场景1: 注册+登录 (用户: {username})")
print(f"{'='*60}")
# 1.1 注册
receipt = generate_receipt("register", email)
reg_data = {
"username": username,
"password": password,
"email": email,
"receipt": receipt["receipt"],
"sig": receipt["sig"],
}
reg_resp = api_post("/api/user_security/register", reg_data)
reg_ok = reg_resp.get("code") == 1
log_test("1.1 用户注册", reg_ok, reg_resp.get("msg", ""))
if not reg_ok:
print(" ⚠️ 注册失败,尝试直接登录(用户可能已存在)")
# 1.2 密码登录
login_data = {
"account": username,
"password": password,
"device_name": "TestScript",
"device_model": "Python3",
"platform": "test",
"app_name": "xianyan_test",
"device_id": f"test_{random_string(8)}",
}
login_resp = api_post("/api/user_security/login", login_data)
login_ok = login_resp.get("code") == 1
log_test("1.2 密码登录", login_ok, login_resp.get("msg", ""))
token = None
user_id = None
if login_ok:
userinfo = login_resp.get("data", {}).get("userinfo", {})
token = userinfo.get("token", "")
user_id = userinfo.get("id", "")
print(f" 👤 用户ID: {user_id}, Token: {token[:20]}...")
# 1.3 获取用户信息
if token:
info_resp = api_get("/api/user_center/index", token=token)
info_ok = info_resp.get("code") == 1
log_test("1.3 获取用户信息", info_ok)
if info_ok:
user_data = info_resp.get("data", {})
print(f" 📊 积分: {user_data.get('score', 0)}, 签到天数: {user_data.get('signin_days', 0)}")
else:
log_test("1.3 获取用户信息", False, "无Token")
return token, user_id, username, password, email
def test_receipt_login(username: str, email: str):
"""场景2: 回执登录"""
print(f"\n{'='*60}")
print(f"📋 场景2: 回执登录")
print(f"{'='*60}")
receipt = generate_receipt("receipt_login", username)
login_data = {
"account": username,
"receipt": receipt["receipt"],
"sig": receipt["sig"],
}
resp = api_post("/api/user_security/receiptLogin", login_data)
ok = resp.get("code") == 1
log_test("2.1 回执登录", ok, resp.get("msg", ""))
if ok:
token = resp.get("data", {}).get("userinfo", {}).get("token", "")
return token
return None
def test_token_login(token: str):
"""场景3: Token登录"""
print(f"\n{'='*60}")
print(f"📋 场景3: Token登录")
print(f"{'='*60}")
resp = api_post("/api/user_security/tokenLogin", {"token": token})
ok = resp.get("code") == 1
log_test("3.1 Token登录", ok, resp.get("msg", ""))
if ok:
new_token = resp.get("data", {}).get("userinfo", {}).get("token", token)
return new_token
return token
def test_user_center(token: str):
"""场景4: 用户中心功能(签到/收藏/笔记/点赞/互动/面板/设备)"""
print(f"\n{'='*60}")
print(f"📋 场景4: 用户中心功能")
print(f"{'='*60}")
# 4.1 签到
signin_resp = api_post("/api/user_center/signin", token=token)
signin_ok = signin_resp.get("code") == 1 or "已签到" in signin_resp.get("msg", "")
log_test("4.1 每日签到", signin_ok, signin_resp.get("msg", ""))
# 4.2 签到日历
cal_resp = api_get("/api/user_center/signin_calendar", token=token)
log_test("4.2 签到日历", cal_resp.get("code") == 1)
# 4.3 收藏-添加
fav_add_resp = api_post("/api/user_center/favorite", {
"action": "add",
"target_id": 1,
"target_type": "article",
}, token=token)
log_test("4.3 收藏-添加", fav_add_resp.get("code") == 1, fav_add_resp.get("msg", ""))
# 4.4 收藏-检查
fav_check_resp = api_post("/api/user_center/favorite", {
"action": "check",
"target_id": 1,
"target_type": "article",
}, token=token)
log_test("4.4 收藏-检查", fav_check_resp.get("code") == 1)
# 4.5 收藏-列表
fav_list_resp = api_post("/api/user_center/favorite", {
"action": "list",
"page": 1,
"limit": 10,
}, token=token)
log_test("4.5 收藏-列表", fav_list_resp.get("code") == 1)
# 4.6 收藏-移除
fav_rm_resp = api_post("/api/user_center/favorite", {
"action": "remove",
"target_id": 1,
"target_type": "article",
}, token=token)
log_test("4.6 收藏-移除", fav_rm_resp.get("code") == 1, fav_rm_resp.get("msg", ""))
# 4.7 笔记-添加
note_add_resp = api_post("/api/user_center/note", {
"action": "add",
"title": "测试笔记",
"content": "这是自动化测试创建的笔记",
"note_type": "note",
"category": "test",
}, token=token)
note_id = None
if note_add_resp.get("code") == 1:
note_id = note_add_resp.get("data", {}).get("id")
log_test("4.7 笔记-添加", note_add_resp.get("code") == 1, note_add_resp.get("msg", ""))
# 4.8 笔记-列表
note_list_resp = api_get("/api/user_center/note", {
"action": "list",
"page": 1,
"limit": 10,
}, token=token)
log_test("4.8 笔记-列表", note_list_resp.get("code") == 1)
# 4.9 笔记-删除
if note_id:
note_del_resp = api_post("/api/user_center/note", {
"action": "delete",
"id": note_id,
}, token=token)
log_test("4.9 笔记-删除", note_del_resp.get("code") == 1)
# 4.10 点赞
like_resp = api_post("/api/user_center/like", {
"action": "add",
"target_id": 1,
"target_type": "article",
}, token=token)
log_test("4.10 点赞", like_resp.get("code") == 1, like_resp.get("msg", ""))
# 4.11 互动-记录
interaction_resp = api_post("/api/user_center/interaction", {
"action": "view",
"target_id": 1,
"target_type": "article",
}, token=token)
log_test("4.11 互动-记录", interaction_resp.get("code") == 1)
# 4.12 数据面板
dashboard_resp = api_get("/api/user_center/dashboard", token=token)
log_test("4.12 数据面板", dashboard_resp.get("code") == 1)
if dashboard_resp.get("code") == 1:
d = dashboard_resp.get("data", {})
print(f" 📊 面板: 积分={d.get('score',0)}, 签到={d.get('signin_days',0)}, "
f"收藏={d.get('favorite_count',0)}, 笔记={d.get('note_count',0)}, "
f"评论={d.get('comment_count',0)}, 浏览={d.get('view_count',0)}, "
f"稍后读={d.get('readlater_count',0)}")
# 4.13 热力图
heatmap_resp = api_get("/api/user_center/heatmap", token=token)
log_test("4.13 热力图", heatmap_resp.get("code") == 1)
# 4.14 金币记录
coin_resp = api_get("/api/user_center/coin", {"page": 1, "limit": 5}, token=token)
log_test("4.14 金币记录", coin_resp.get("code") == 1)
# 4.15 学习统计
stats_resp = api_get("/api/user_center/stats", {"type": "overview", "period": "month"}, token=token)
log_test("4.15 学习统计", stats_resp.get("code") == 1)
# 4.16 设备-注册
device_resp = api_post("/api/user_center/registerDevice", {
"device_id": f"test_device_{random_string(8)}",
"device_name": "TestDevice",
"device_model": "Python3-Script",
"platform": "test",
"app_name": "xianyan_test",
}, token=token)
log_test("4.16 设备-注册", device_resp.get("code") == 1, device_resp.get("msg", ""))
# 4.17 设备-列表
devices_resp = api_post("/api/user_center/devices", {"action": "list"}, token=token)
log_test("4.17 设备-列表", devices_resp.get("code") == 1)
def test_profile_update(token: str):
"""场景5: 修改个人信息"""
print(f"\n{'='*60}")
print(f"📋 场景5: 修改个人信息")
print(f"{'='*60}")
# 5.1 修改昵称
profile_resp = api_post("/api/user_center/profile", {
"nickname": f"测试用户_{random_string(4)}",
}, token=token)
log_test("5.1 修改昵称", profile_resp.get("code") == 1, profile_resp.get("msg", ""))
# 5.2 修改签名
bio_resp = api_post("/api/user_center/profile", {
"bio": "这是自动化测试修改的签名",
}, token=token)
log_test("5.2 修改签名", bio_resp.get("code") == 1)
# 5.3 修改邮箱 (回执验证)
new_email = f"test_{random_string()}@testmail.com"
receipt = generate_receipt("changeemail", new_email)
email_resp = api_post("/api/user_security/changeemail", {
"email": new_email,
"receipt": receipt["receipt"],
"sig": receipt["sig"],
}, token=token)
log_test("5.3 修改邮箱", email_resp.get("code") == 1, email_resp.get("msg", ""))
def test_password_change(token: str, user_id: str, username: str, old_password: str):
"""场景6: 修改密码"""
print(f"\n{'='*60}")
print(f"📋 场景6: 修改密码")
print(f"{'='*60}")
new_password = f"NewTest@{random_string(12)}"
# 先尝试不带回执(测试用户可能不需要)
pwd_resp = api_post("/api/user_security/changepwd", {
"oldpassword": old_password,
"newpassword": new_password,
}, token=token)
if pwd_resp.get("code") != 1:
# 尝试用username作为回执payload
receipt = generate_receipt("changepwd", username)
pwd_resp = api_post("/api/user_security/changepwd", {
"oldpassword": old_password,
"newpassword": new_password,
"receipt": receipt["receipt"],
"sig": receipt["sig"],
}, token=token)
if pwd_resp.get("code") != 1:
# 尝试用user_id作为回执payload
receipt = generate_receipt("changepwd", user_id)
pwd_resp = api_post("/api/user_security/changepwd", {
"oldpassword": old_password,
"newpassword": new_password,
"receipt": receipt["receipt"],
"sig": receipt["sig"],
}, token=token)
log_test("6.1 修改密码", pwd_resp.get("code") == 1, pwd_resp.get("msg", ""))
return new_password if pwd_resp.get("code") == 1 else old_password
def test_qrcode_flow():
"""场景7: 二维码登录流程"""
print(f"\n{'='*60}")
print(f"📋 场景7: 二维码登录流程")
print(f"{'='*60}")
# 7.1 生成二维码
qr_resp = api_get("/api/user_security/qrcodeGenerate")
qr_ok = qr_resp.get("code") == 1
log_test("7.1 生成二维码", qr_ok, qr_resp.get("msg", ""))
if qr_ok:
qr_data = qr_resp.get("data", {})
qr_code = qr_data.get("code", "")
print(f" 📱 二维码Code: {qr_code[:20]}..., 有效期: {qr_data.get('expire_seconds', 0)}s")
# 7.2 轮询二维码状态
poll_resp = api_get("/api/user_security/qrcodePoll", {"code": qr_code})
poll_ok = poll_resp.get("code") == 1 or poll_resp.get("data", {}).get("status") in ["pending", "expired"]
log_test("7.2 轮询二维码", poll_ok, f"状态: {poll_resp.get('data', {}).get('status', 'unknown')}")
# 7.3 取消二维码
cancel_resp = api_post("/api/user_security/qrcodeCancel", {"code": qr_code})
log_test("7.3 取消二维码", cancel_resp.get("code") == 1, cancel_resp.get("msg", ""))
def test_delete_account(token: str, user_id: str):
"""场景8: 账号注销(申请注销)"""
print(f"\n{'='*60}")
print(f"📋 场景8: 账号注销(申请注销)")
print(f"{'='*60}")
# 使用文档定义的正确接口路径
receipt = generate_receipt("delete_account", user_id)
try:
resp = session.post(
f"{BASE_URL}/api/user_security/requestDeletion",
data={
"reason": "自动化测试注销",
"receipt": receipt["receipt"],
"sig": receipt["sig"],
},
headers={"token": token} if token else {},
timeout=TIMEOUT,
)
if resp.status_code == 404 or not resp.text.strip():
log_test("8.1 申请账号注销", False, "接口未部署(404/空响应)")
print(" requestDeletion接口尚未在服务器端部署")
print(" 管理端注销审核接口: /admin.php/userdeletion")
return
del_resp = resp.json()
code = del_resp.get("code", -1)
msg = del_resp.get("msg", "")
log_test("8.1 申请账号注销", code == 1, f"code={code}, msg={msg}")
if code == 1:
data = del_resp.get("data", {})
print(f" 📋 注销记录ID: {data.get('id', 'N/A')}")
print(f" 📋 状态: {data.get('status_text', 'N/A')}")
print(f" 📋 倒计时: {data.get('countdown', 'N/A')}")
# 查询注销状态
try:
status_resp = api_get("/api/user_security/deletionStatus", token=token)
if status_resp.get("code") == 1:
status_data = status_resp.get("data", {})
log_test("8.2 查询注销状态", True, f"pending={status_data.get('has_pending')}")
else:
log_test("8.2 查询注销状态", False, "接口未部署")
except Exception:
log_test("8.2 查询注销状态", False, "接口未部署")
# 取消注销
try:
cancel_resp = api_post("/api/user_security/cancelDeletion", token=token)
log_test("8.3 取消注销", cancel_resp.get("code") == 1, cancel_resp.get("msg", ""))
except Exception:
log_test("8.3 取消注销", False, "接口未部署")
elif "已存在" in msg:
log_test("8.1 申请账号注销", True, f"已有注销申请: {msg}")
else:
print(f" ⚠️ 注销申请失败: {msg}")
except Exception as e:
log_test("8.1 申请账号注销", False, f"请求异常: {e}")
print(" requestDeletion接口可能尚未部署")
def test_logout(token: str):
"""场景: 退出登录"""
print(f"\n{'='*60}")
print(f"📋 退出登录")
print(f"{'='*60}")
resp = api_post("/api/user_security/logout", token=token)
log_test("退出登录", resp.get("code") == 1 or resp.get("code") == 0, resp.get("msg", ""))
# ============================================================
# 主流程
# ============================================================
def main():
print("=" * 60)
print("🚀 闲言APP 用户中心API自动化测试")
print(f" 服务器: {BASE_URL}")
print(f" 时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 60)
results = {"pass": 0, "fail": 0}
try:
# 场景1: 注册+登录
token, user_id, username, password, email = test_register_and_login()
if not token:
print("\n❌ 登录失败,无法继续后续测试")
sys.exit(1)
# 场景2: 回执登录
test_receipt_login(username, email)
# 场景3: Token登录
token = test_token_login(token)
# 场景4: 用户中心功能
test_user_center(token)
# 场景5: 修改个人信息
test_profile_update(token)
# 场景6: 修改密码
new_password = test_password_change(token, str(user_id), username, password)
# 密码修改后Token可能失效重新登录
if new_password != password:
print("\n 🔄 密码已修改,重新登录...")
relogin_data = {
"account": username,
"password": new_password,
}
relogin_resp = api_post("/api/user_security/login", relogin_data)
if relogin_resp.get("code") == 1:
token = relogin_resp.get("data", {}).get("userinfo", {}).get("token", token)
print(f" ✅ 重新登录成功")
# 场景7: 二维码登录流程
test_qrcode_flow()
# 场景8: 账号注销
test_delete_account(token, str(user_id))
except requests.exceptions.ConnectionError as e:
print(f"\n❌ 网络连接失败: {e}")
except requests.exceptions.Timeout:
print(f"\n❌ 请求超时")
except Exception as e:
print(f"\n❌ 未知错误: {e}")
import traceback
traceback.print_exc()
print(f"\n{'='*60}")
print("🏁 测试完成")
print(f"{'='*60}")
if __name__ == "__main__":
main()