此版本包含多项功能更新与问题修复: 1. 新增iOS ShareExtension分享扩展,支持多类型内容分享 2. 修复认证流程日志提示,更新用户名检测逻辑 3. 优化会话列表UI,替换emoji为CupertinoIcons原生图标 4. 修正搜索类型与频道名称映射,新增音频类型支持 5. 调整启动页布局与多语言配置 6. 重构布局约束,修复无界布局崩溃问题 7. 迁移开发者设置到更多设置页,新增日志级别配置 8. 优化TTS健康检查与自动回退逻辑 9. 新增笔记置顶会话跳转功能 10. 更新后端配置与本地化字符串 11. 重构稍后读模块,支持音频内容处理 12. 优化编辑器功能与字体管理页面 13. 新增本地数据库置顶笔记表 14. 修复Android MANAGE_STORAGE权限配置
353 lines
12 KiB
Python
353 lines
12 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
@name Feed权重管理API测试脚本
|
||
@author AI Coder
|
||
@date 2026-06-08
|
||
@desc 测试Feed权重管理系统的install/sync和channels接口,验证44种分类全部可管理
|
||
@update 初始创建
|
||
"""
|
||
|
||
import json
|
||
import sys
|
||
import time
|
||
|
||
try:
|
||
import requests
|
||
except ImportError:
|
||
print("❌ 需要安装requests库: pip3 install requests")
|
||
sys.exit(1)
|
||
|
||
# ============ 配置 ============
|
||
BASE_URL = "https://tools.wktyl.com"
|
||
SYNC_TOKEN = "xianyan_feed_sync_2026"
|
||
TIMEOUT = 30
|
||
|
||
# 全部44种数据源类型
|
||
ALL_CATEGORIES = [
|
||
"poetry", "wisdom", "story", "hitokoto", "riddle", "efs", "brainteaser",
|
||
"saying", "lyric", "why", "composition", "couplet", "cs", "drug", "herbal",
|
||
"food", "wine", "article", "chengyu", "hanzi", "cidian", "prescription",
|
||
"tisana", "joke", "zgjm", "lunyu", "hdnj", "jgj", "mz", "zz", "zuozhuan",
|
||
"sj", "sgz", "sbbf", "warring", "illness", "word", "abbr", "surname",
|
||
"jieqi", "nation", "wlyh", "jiufang", "bot"
|
||
]
|
||
|
||
|
||
def print_header(title):
|
||
"""打印分节标题"""
|
||
print(f"\n{'='*60}")
|
||
print(f" {title}")
|
||
print(f"{'='*60}")
|
||
|
||
|
||
def print_result(name, success, detail=""):
|
||
"""打印测试结果"""
|
||
icon = "✅" if success else "❌"
|
||
print(f" {icon} {name}", end="")
|
||
if detail:
|
||
print(f" — {detail}")
|
||
else:
|
||
print()
|
||
|
||
|
||
def test_install_endpoint():
|
||
"""测试 install/sync 端点 """
|
||
print_header("1. 测试 install 端点 (同步全部分类)")
|
||
|
||
url = f"{BASE_URL}/api/feed/install"
|
||
params = {"token": SYNC_TOKEN}
|
||
|
||
try:
|
||
resp = requests.get(url, params=params, timeout=TIMEOUT)
|
||
data = resp.json()
|
||
print(f" 📡 响应状态码: {resp.status_code}")
|
||
|
||
if data.get("code") == 1:
|
||
result = data.get("data", {})
|
||
added = result.get("added", 0)
|
||
updated = result.get("updated", 0)
|
||
total = result.get("total_types", 0)
|
||
categories = result.get("all_categories", [])
|
||
|
||
print_result("install接口调用成功", True,
|
||
f"新增{added}种, 更新{updated}种, 共{total}种")
|
||
print_result("分类总数匹配", total == 44,
|
||
f"期望44, 实际{total}")
|
||
|
||
# 检查返回的分类列表是否完整
|
||
missing = set(ALL_CATEGORIES) - set(categories)
|
||
print_result("全部分类已包含", len(missing) == 0,
|
||
f"缺失: {missing}" if missing else "全部44种分类已包含")
|
||
|
||
return True, result
|
||
else:
|
||
print_result("install接口调用", False, data.get("msg", "未知错误"))
|
||
return False, None
|
||
|
||
except requests.exceptions.Timeout:
|
||
print_result("install接口调用", False, "请求超时")
|
||
return False, None
|
||
except Exception as e:
|
||
print_result("install接口调用", False, str(e))
|
||
return False, None
|
||
|
||
|
||
def test_install_without_token():
|
||
"""测试无token时install端点的安全校验"""
|
||
print_header("2. 测试 install 端点安全校验 (无token)")
|
||
|
||
url = f"{BASE_URL}/api/feed/install"
|
||
|
||
try:
|
||
resp = requests.get(url, timeout=TIMEOUT)
|
||
data = resp.json()
|
||
|
||
if data.get("code") == 0:
|
||
print_result("无token被拒绝", True, data.get("msg", ""))
|
||
else:
|
||
print_result("无token被拒绝", False, "应该返回错误但返回了成功")
|
||
|
||
except Exception as e:
|
||
print_result("安全校验测试", False, str(e))
|
||
|
||
|
||
def test_channels_endpoint():
|
||
"""测试 channels 端点,验证返回的分类列表"""
|
||
print_header("3. 测试 channels 端点 (频道列表)")
|
||
|
||
url = f"{BASE_URL}/api/feed/channels"
|
||
|
||
try:
|
||
resp = requests.get(url, timeout=TIMEOUT)
|
||
data = resp.json()
|
||
|
||
if data.get("code") == 1:
|
||
channels = data.get("data", {}).get("channels", [])
|
||
channel_keys = [ch["key"] for ch in channels]
|
||
|
||
print(f" 📊 返回频道数: {len(channels)} (含'all'推荐频道)")
|
||
|
||
# 检查每个分类是否在channels中
|
||
missing = []
|
||
for cat in ALL_CATEGORIES:
|
||
if cat not in channel_keys:
|
||
missing.append(cat)
|
||
|
||
print_result("全部分类在channels中", len(missing) == 0,
|
||
f"缺失: {missing}" if missing else "全部44种分类可见")
|
||
|
||
# 检查每个channel的结构
|
||
for ch in channels:
|
||
has_required = all(k in ch for k in ["key", "name", "icon", "count"])
|
||
if not has_required:
|
||
print_result(f"频道 {ch.get('key', '?')} 结构完整", False)
|
||
break
|
||
else:
|
||
print_result("所有频道结构完整", True,
|
||
"均包含key/name/icon/count")
|
||
|
||
# 打印频道概览
|
||
print(f"\n 📋 频道列表:")
|
||
for ch in channels:
|
||
icon = ch.get("icon", "")
|
||
name = ch.get("name", "")
|
||
key = ch.get("key", "")
|
||
count = ch.get("count", 0)
|
||
enabled = ch.get("is_enabled", True)
|
||
status = "✅" if enabled else "❌"
|
||
print(f" {status} {icon} {name} ({key}) — {count}条")
|
||
|
||
return True, channels
|
||
else:
|
||
print_result("channels接口调用", False, data.get("msg", "未知错误"))
|
||
return False, None
|
||
|
||
except Exception as e:
|
||
print_result("channels接口调用", False, str(e))
|
||
return False, None
|
||
|
||
|
||
def test_weight_config_endpoint():
|
||
"""测试 weight_config 端点,验证权重配置"""
|
||
print_header("4. 测试 weight_config 端点 (权重配置)")
|
||
|
||
url = f"{BASE_URL}/api/feed/weight_config"
|
||
|
||
try:
|
||
resp = requests.get(url, timeout=TIMEOUT)
|
||
data = resp.json()
|
||
|
||
if data.get("code") == 1:
|
||
result = data.get("data", {})
|
||
config = result.get("config", [])
|
||
total_types = result.get("total_types", 0)
|
||
enabled_count = result.get("enabled_count", 0)
|
||
|
||
print(f" 📊 配置总数: {total_types}, 启用数: {enabled_count}")
|
||
|
||
config_types = [c["type"] for c in config]
|
||
|
||
# 检查每个分类是否在配置中
|
||
missing = set(ALL_CATEGORIES) - set(config_types)
|
||
print_result("全部分类有权重配置", len(missing) == 0,
|
||
f"缺失: {missing}" if missing else "全部44种分类已配置")
|
||
|
||
# 检查配置结构
|
||
for c in config:
|
||
required = ["type", "name", "icon", "weight", "display_weight",
|
||
"push_limit", "is_enabled"]
|
||
has_all = all(k in c for k in required)
|
||
if not has_all:
|
||
print_result(f"分类 {c.get('type', '?')} 配置完整", False)
|
||
break
|
||
else:
|
||
print_result("所有配置结构完整", True)
|
||
|
||
# 打印权重概览
|
||
print(f"\n 📋 权重配置 (按权重降序):")
|
||
sorted_config = sorted(config, key=lambda x: x["weight"], reverse=True)
|
||
for c in sorted_config[:10]:
|
||
icon = c.get("icon", "")
|
||
name = c.get("name", "")
|
||
weight = c.get("weight", 0)
|
||
enabled = "✅" if c.get("is_enabled") else "❌"
|
||
print(f" {enabled} {icon} {name}: 权重={weight}")
|
||
|
||
if len(sorted_config) > 10:
|
||
print(f" ... 还有 {len(sorted_config) - 10} 个分类")
|
||
|
||
return True, result
|
||
else:
|
||
print_result("weight_config接口调用", False, data.get("msg", "未知错误"))
|
||
return False, None
|
||
|
||
except Exception as e:
|
||
print_result("weight_config接口调用", False, str(e))
|
||
return False, None
|
||
|
||
|
||
def test_stats_endpoint():
|
||
"""测试 stats 端点"""
|
||
print_header("5. 测试 stats 端点 (统计信息)")
|
||
|
||
url = f"{BASE_URL}/api/feed/stats"
|
||
|
||
try:
|
||
resp = requests.get(url, timeout=TIMEOUT)
|
||
data = resp.json()
|
||
|
||
if data.get("code") == 1:
|
||
result = data.get("data", {})
|
||
total_content = result.get("total_content", 0)
|
||
channel_count = result.get("channel_count", 0)
|
||
channels = result.get("channels", [])
|
||
|
||
print(f" 📊 总内容数: {total_content}, 频道数: {channel_count}")
|
||
|
||
# 检查启用的频道数
|
||
print_result("频道数>=44", channel_count >= 44,
|
||
f"实际{channel_count}个")
|
||
|
||
# 检查每个分类是否有统计
|
||
stat_types = [ch["key"] for ch in channels]
|
||
missing = set(ALL_CATEGORIES) - set(stat_types)
|
||
print_result("全部分类有统计", len(missing) == 0,
|
||
f"缺失: {missing}" if missing else "全部44种分类有统计")
|
||
|
||
return True, result
|
||
else:
|
||
print_result("stats接口调用", False, data.get("msg", "未知错误"))
|
||
return False, None
|
||
|
||
except Exception as e:
|
||
print_result("stats接口调用", False, str(e))
|
||
return False, None
|
||
|
||
|
||
def test_idempotent_install():
|
||
"""测试install端点的幂等性(重复调用不会重复插入)"""
|
||
print_header("6. 测试 install 端点幂等性")
|
||
|
||
url = f"{BASE_URL}/api/feed/install"
|
||
params = {"token": SYNC_TOKEN}
|
||
|
||
try:
|
||
# 第一次调用
|
||
resp1 = requests.get(url, params=params, timeout=TIMEOUT)
|
||
data1 = resp1.json()
|
||
|
||
# 第二次调用
|
||
resp2 = requests.get(url, params=params, timeout=TIMEOUT)
|
||
data2 = resp2.json()
|
||
|
||
if data1.get("code") == 1 and data2.get("code") == 1:
|
||
result1 = data1.get("data", {})
|
||
result2 = data2.get("data", {})
|
||
|
||
added1 = result1.get("added", 0)
|
||
added2 = result2.get("added", 0)
|
||
|
||
print_result("第二次调用added应为0", added2 == 0,
|
||
f"第一次added={added1}, 第二次added={added2}")
|
||
print_result("两次调用total一致",
|
||
result1.get("total_types") == result2.get("total_types"),
|
||
f"均为{result1.get('total_types')}")
|
||
else:
|
||
print_result("幂等性测试", False, "接口返回错误")
|
||
|
||
except Exception as e:
|
||
print_result("幂等性测试", False, str(e))
|
||
|
||
|
||
def main():
|
||
"""主测试流程"""
|
||
print("🚀 Feed权重管理系统 — 全44分类扩展测试")
|
||
print(f" 目标服务器: {BASE_URL}")
|
||
print(f" 测试时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
|
||
print(f" 期望分类数: {len(ALL_CATEGORIES)}")
|
||
|
||
results = {}
|
||
|
||
# 1. 测试install端点
|
||
ok, install_result = test_install_endpoint()
|
||
results["install"] = ok
|
||
|
||
# 2. 测试安全校验
|
||
test_install_without_token()
|
||
|
||
# 3. 测试channels端点
|
||
ok, channels_result = test_channels_endpoint()
|
||
results["channels"] = ok
|
||
|
||
# 4. 测试weight_config端点
|
||
ok, weight_result = test_weight_config_endpoint()
|
||
results["weight_config"] = ok
|
||
|
||
# 5. 测试stats端点
|
||
ok, stats_result = test_stats_endpoint()
|
||
results["stats"] = ok
|
||
|
||
# 6. 测试幂等性
|
||
test_idempotent_install()
|
||
|
||
# 汇总
|
||
print_header("测试结果汇总")
|
||
total = len(results)
|
||
passed = sum(1 for v in results.values() if v)
|
||
for name, ok in results.items():
|
||
print_result(name, ok)
|
||
|
||
print(f"\n 📊 通过率: {passed}/{total}")
|
||
|
||
if passed == total:
|
||
print("\n 🎉 全部测试通过!44种分类权重管理已就绪。")
|
||
else:
|
||
print(f"\n ⚠️ 有 {total - passed} 项测试未通过,请检查。")
|
||
|
||
return 0 if passed == total else 1
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|