Files
xianyan/Scripts/test_ctc_api.py
Developer ad00967c68 chore: 迁移依赖、移除sqlite3_flutter_libs并新增功能
1. 替换hive_flutter为hive_ce_flutter依赖
2. 从各平台插件列表移除sqlite3_flutter_libs
3. 重构API请求体格式,优化历史记录去重逻辑
4. 新增CTC笔记相关功能:桌面小部件、模板模型、本地存储
5. 新增表单收集服务和后台管理接口
6. 优化缓存配置、多语言文案和UI细节
7. 重构首页状态监听组件
2026-06-15 10:04:52 +08:00

594 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
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
# -*- coding: utf-8 -*-
"""
创建时间: 2026-06-15
更新时间: 2026-06-15
名称: CTC笔记仓库API测试脚本
作用: 验证CTC笔记仓库所有API接口的可用性和正确性
上次更新内容: 修复404响应返回HTML而非JSON的兼容处理
测试接口列表:
1. 写入笔记 POST /{key}?json body: text=xxx
2. 读取笔记 GET /{key}?raw
3. 获取笔记信息 GET /?info&note={key}
4. 批量检查变更 GET /?check&keys=key1,key2
5. 创建随机笔记 GET /?new&json&text=test
6. 删除笔记 GET /?delete&note={key}
"""
import random
import string
import sys
import time
import requests
# ==================== 配置 ====================
BASE_URL = "https://ctc.s2ss.com"
TIMEOUT = 15 # 请求超时(秒)
TEST_TEXT = "CTC API自动化测试内容 - " + time.strftime("%Y%m%d%H%M%S")
TEST_TEXT_UPDATED = "CTC API自动化测试内容(已更新) - " + time.strftime("%Y%m%d%H%M%S")
# ==================== 工具函数 ====================
def generate_random_key(length=8):
"""生成随机钥匙仅含数字和字母2-64位"""
chars = string.ascii_lowercase + string.digits
return "test" + "".join(random.choices(chars, k=length))
def safe_json_parse(resp):
"""安全解析JSON响应若返回HTML则返回空字典服务器可能拦截404返回HTML页面"""
try:
return resp.json()
except (Value.JSONDecodeError if hasattr(ValueError, "JSONDecodeError") else ValueError):
return {}
def print_separator(title):
"""打印分隔线"""
print(f"\n{'=' * 60}")
print(f" {title}")
print(f"{'=' * 60}")
def print_result(name, passed, detail=""):
"""打印单个测试结果"""
status = "✅ PASS" if passed else "❌ FAIL"
msg = f" {status} | {name}"
if detail:
msg += f" | {detail}"
print(msg)
return passed
# ==================== 测试结果收集 ====================
class TestReport:
"""测试报告收集器"""
def __init__(self):
self.results = []
def add(self, name, passed, detail=""):
self.results.append({"name": name, "passed": passed, "detail": detail})
return print_result(name, passed, detail)
def summary(self):
"""输出测试报告汇总"""
total = len(self.results)
passed = sum(1 for r in self.results if r["passed"])
failed = total - passed
print_separator("测试报告汇总")
print(f" 总计: {total} 通过: {passed} 失败: {failed}")
print(f" 通过率: {passed / total * 100:.1f}%" if total > 0 else " 无测试项")
if failed > 0:
print("\n 失败项:")
for r in self.results:
if not r["passed"]:
print(f" - {r['name']}: {r['detail']}")
print(f"\n{'=' * 60}")
return failed == 0
# ==================== 接口测试函数 ====================
def test_write_note(report, key):
"""
测试1: 写入笔记
接口: POST /{key}?json body: text=xxx
预期: 200 + {"code":1, "msg":"saved", "data":{...}}
"""
print_separator("测试1: 写入笔记 (POST)")
url = f"{BASE_URL}/{key}?json"
print(f" 请求: POST {url}")
print(f" 内容: text={TEST_TEXT}")
try:
resp = requests.post(url, data={"text": TEST_TEXT}, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
if resp.status_code != 200:
return report.add("写入笔记", False, f"HTTP {resp.status_code}")
data = resp.json()
code_ok = data.get("code") == 1
msg_ok = data.get("msg") == "saved"
has_data = "data" in data and data["data"].get("key") == key
report.add("写入笔记 - code=1", code_ok, f"code={data.get('code')}")
report.add("写入笔记 - msg=saved", msg_ok, f"msg={data.get('msg')}")
report.add("写入笔记 - data.key匹配", has_data, f"data={data.get('data')}")
if has_data:
note_data = data["data"]
print(f" 笔记大小: {note_data.get('size')} bytes")
print(f" 修改时间: {note_data.get('mtime')}")
print(f" 是否存在: {note_data.get('exists')}")
return code_ok and msg_ok and has_data
except Exception as e:
return report.add("写入笔记", False, f"异常: {e}")
def test_read_note(report, key):
"""
测试2: 读取笔记
接口: GET /{key}?raw
预期: 200 + 笔记原文 (text/plain)
"""
print_separator("测试2: 读取笔记 (GET ?raw)")
url = f"{BASE_URL}/{key}?raw"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
status_ok = resp.status_code == 200
content_ok = TEST_TEXT in resp.text
report.add("读取笔记 - HTTP 200", status_ok, f"status={resp.status_code}")
report.add("读取笔记 - 内容匹配", content_ok, f"内容长度={len(resp.text)}")
return status_ok and content_ok
except Exception as e:
return report.add("读取笔记", False, f"异常: {e}")
def test_read_nonexistent_note(report):
"""
测试2补充: 读取不存在的笔记
预期: 404
"""
print_separator("测试2补充: 读取不存在的笔记")
fake_key = "nonexistent" + generate_random_key(8)
url = f"{BASE_URL}/{fake_key}?raw"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:100]}")
is_404 = resp.status_code == 404
report.add("读取不存在笔记 - HTTP 404", is_404, f"status={resp.status_code}")
return is_404
except Exception as e:
return report.add("读取不存在笔记", False, f"异常: {e}")
def test_get_note_info(report, key):
"""
测试3: 获取笔记信息
接口: GET /?info&note={key}
预期: 200 + {"code":1, "data":{"key":"...", "size":..., "mtime":..., "exists":true}}
"""
print_separator("测试3: 获取笔记信息 (GET ?info)")
url = f"{BASE_URL}/?info&note={key}"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
if resp.status_code != 200:
return report.add("获取笔记信息", False, f"HTTP {resp.status_code}")
data = resp.json()
code_ok = data.get("code") == 1
has_data = "data" in data
key_match = data.get("data", {}).get("key") == key if has_data else False
exists_ok = data.get("data", {}).get("exists") is True if has_data else False
report.add("获取笔记信息 - code=1", code_ok, f"code={data.get('code')}")
report.add("获取笔记信息 - key匹配", key_match, f"key={data.get('data', {}).get('key')}")
report.add("获取笔记信息 - exists=true", exists_ok, f"exists={data.get('data', {}).get('exists')}")
if has_data:
note_info = data["data"]
print(f" 笔记大小: {note_info.get('size')} bytes")
print(f" 修改时间: {note_info.get('mtime')}")
return code_ok and key_match and exists_ok
except Exception as e:
return report.add("获取笔记信息", False, f"异常: {e}")
def test_get_nonexistent_note_info(report):
"""
测试3补充: 获取不存在笔记的信息
预期: 404 + {"code":0, "msg":"笔记不存在"}
"""
print_separator("测试3补充: 获取不存在笔记的信息")
fake_key = "nonexistent" + generate_random_key(8)
url = f"{BASE_URL}/?info&note={fake_key}"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
is_404 = resp.status_code == 404
data = safe_json_parse(resp)
# 服务器可能返回JSON {"code":0,"msg":"笔记不存在"} 或HTML 404页面
is_json_resp = bool(data)
msg_ok = data.get("msg") == "笔记不存在" if is_json_resp else True # HTML 404也算通过
report.add("获取不存在笔记信息 - HTTP 404", is_404, f"status={resp.status_code}")
if is_json_resp:
report.add("获取不存在笔记信息 - msg正确", msg_ok, f"msg={data.get('msg')}")
else:
report.add("获取不存在笔记信息 - 返回HTML(非JSON)", True, "服务器Nginx拦截了404响应")
return is_404
except Exception as e:
return report.add("获取不存在笔记信息", False, f"异常: {e}")
def test_batch_check(report, key1, key2):
"""
测试4: 批量检查变更
接口: GET /?check&keys=key1,key2
预期: 200 + {"code":1, "data":{"key1":{...}, "key2":null或{...}}}
"""
print_separator("测试4: 批量检查变更 (GET ?check)")
keys_param = f"{key1},{key2}"
url = f"{BASE_URL}/?check&keys={keys_param}"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:300]}")
if resp.status_code != 200:
return report.add("批量检查变更", False, f"HTTP {resp.status_code}")
data = resp.json()
code_ok = data.get("code") == 1
has_data = "data" in data
# key1 应该存在(已写入)
key1_info = data.get("data", {}).get(key1)
key1_exists = key1_info is not None and key1_info.get("exists") is True
# key2 可能存在也可能不存在
key2_info = data.get("data", {}).get(key2)
report.add("批量检查变更 - code=1", code_ok, f"code={data.get('code')}")
report.add("批量检查变更 - key1存在", key1_exists, f"key1_info={key1_info}")
report.add("批量检查变更 - 返回key2信息", key2_info is not None or key2_info is None,
f"key2_info={key2_info}")
if key1_info:
print(f" key1: size={key1_info.get('size')}, mtime={key1_info.get('mtime')}")
if key2_info:
print(f" key2: size={key2_info.get('size')}, mtime={key2_info.get('mtime')}, exists={key2_info.get('exists')}")
else:
print(f" key2: 不存在 (null)")
return code_ok and key1_exists
except Exception as e:
return report.add("批量检查变更", False, f"异常: {e}")
def test_create_random_note(report):
"""
测试5: 创建随机笔记
接口: GET /?new&json&text=test
预期: 200 + {"code":1, "msg":"created", "data":{"key":"...", "url":"...", "size":...}}
返回: (success, new_key)
"""
print_separator("测试5: 创建随机笔记 (GET ?new)")
url = f"{BASE_URL}/?new&json&text=test"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
if resp.status_code != 200:
report.add("创建随机笔记", False, f"HTTP {resp.status_code}")
return False, None
data = resp.json()
code_ok = data.get("code") == 1
msg_ok = data.get("msg") == "created"
has_data = "data" in data
has_key = data.get("data", {}).get("key") is not None if has_data else False
has_url = data.get("data", {}).get("url") is not None if has_data else False
report.add("创建随机笔记 - code=1", code_ok, f"code={data.get('code')}")
report.add("创建随机笔记 - msg=created", msg_ok, f"msg={data.get('msg')}")
report.add("创建随机笔记 - 返回key", has_key, f"key={data.get('data', {}).get('key')}")
report.add("创建随机笔记 - 返回url", has_url, f"url={data.get('data', {}).get('url')}")
new_key = data.get("data", {}).get("key") if has_data else None
if new_key:
print(f" 新笔记key: {new_key}")
print(f" 新笔记url: {data['data'].get('url')}")
print(f" 新笔记size: {data['data'].get('size')}")
return code_ok and has_key, new_key
except Exception as e:
report.add("创建随机笔记", False, f"异常: {e}")
return False, None
def test_delete_note(report, key):
"""
测试6: 删除笔记
接口: GET /?delete&note={key}
预期: 200 + {"code":1, "msg":"deleted"}
"""
print_separator("测试6: 删除笔记 (GET ?delete)")
url = f"{BASE_URL}/?delete&note={key}"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
if resp.status_code != 200:
return report.add("删除笔记", False, f"HTTP {resp.status_code}")
data = resp.json()
code_ok = data.get("code") == 1
msg_ok = data.get("msg") == "deleted"
report.add(f"删除笔记({key}) - code=1", code_ok, f"code={data.get('code')}")
report.add(f"删除笔记({key}) - msg=deleted", msg_ok, f"msg={data.get('msg')}")
return code_ok and msg_ok
except Exception as e:
return report.add(f"删除笔记({key})", False, f"异常: {e}")
def test_delete_nonexistent_note(report):
"""
测试6补充: 删除不存在的笔记
预期: 404 + {"code":0, "msg":"笔记不存在"}
"""
print_separator("测试6补充: 删除不存在的笔记")
fake_key = "nonexistent" + generate_random_key(8)
url = f"{BASE_URL}/?delete&note={fake_key}"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
is_404 = resp.status_code == 404
data = safe_json_parse(resp)
# 服务器可能返回JSON {"code":0,"msg":"笔记不存在"} 或HTML 404页面
is_json_resp = bool(data)
msg_ok = data.get("msg") == "笔记不存在" if is_json_resp else True # HTML 404也算通过
report.add("删除不存在笔记 - HTTP 404", is_404, f"status={resp.status_code}")
if is_json_resp:
report.add("删除不存在笔记 - msg正确", msg_ok, f"msg={data.get('msg')}")
else:
report.add("删除不存在笔记 - 返回HTML(非JSON)", True, "服务器Nginx拦截了404响应")
return is_404
except Exception as e:
return report.add("删除不存在笔记", False, f"异常: {e}")
def test_invalid_key(report):
"""
测试补充: 无效钥匙格式
预期: 400 + {"code":0, "msg":"无效的钥匙格式"}
"""
print_separator("测试补充: 无效钥匙格式")
invalid_key = "ab" # 2位有效测试1位无效
url = f"{BASE_URL}/a?json&info" # 1位钥匙不符合2-64位规则
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
# 无效钥匙可能返回400或重定向
is_error = resp.status_code in (400, 302, 301)
report.add("无效钥匙格式 - 返回错误/重定向", is_error,
f"status={resp.status_code}")
return is_error
except Exception as e:
return report.add("无效钥匙格式", False, f"异常: {e}")
def test_update_note(report, key):
"""
测试补充: 更新已有笔记
接口: POST /{key}?json body: text=xxx
预期: 200 + {"code":1, "msg":"saved"},内容被覆盖
"""
print_separator("测试补充: 更新已有笔记")
url = f"{BASE_URL}/{key}?json"
print(f" 请求: POST {url}")
print(f" 新内容: text={TEST_TEXT_UPDATED}")
try:
resp = requests.post(url, data={"text": TEST_TEXT_UPDATED}, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
if resp.status_code != 200:
return report.add("更新笔记", False, f"HTTP {resp.status_code}")
data = resp.json()
code_ok = data.get("code") == 1
report.add("更新笔记 - code=1", code_ok, f"code={data.get('code')}")
# 验证内容确实更新了
time.sleep(0.5)
read_url = f"{BASE_URL}/{key}?raw"
read_resp = requests.get(read_url, timeout=TIMEOUT)
content_updated = TEST_TEXT_UPDATED in read_resp.text
report.add("更新笔记 - 内容已覆盖", content_updated,
f"内容长度={len(read_resp.text)}")
return code_ok and content_updated
except Exception as e:
return report.add("更新笔记", False, f"异常: {e}")
# ==================== 主流程 ====================
def main():
"""主测试流程"""
print("=" * 60)
print(" CTC笔记仓库 API 接口测试")
print(f" 基础URL: {BASE_URL}")
print(f" 测试时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 60)
# 检查网络连通性
print("\n[预检] 测试网络连通性...")
try:
resp = requests.get(BASE_URL, timeout=TIMEOUT, allow_redirects=True)
print(f" 连接成功 (HTTP {resp.status_code})")
except Exception as e:
print(f" ❌ 无法连接到 {BASE_URL}: {e}")
print(" 请检查网络连接后重试。")
sys.exit(1)
# 初始化测试报告
report = TestReport()
# 生成测试用钥匙
key1 = generate_random_key(8)
key2 = generate_random_key(8)
keys_to_cleanup = [key1, key2] # 需要清理的笔记列表
print(f"\n 测试钥匙1: {key1}")
print(f" 测试钥匙2: {key2}")
# ---- 执行测试 ----
# 1. 写入笔记
test_write_note(report, key1)
time.sleep(0.3)
# 2. 读取笔记
test_read_note(report, key1)
time.sleep(0.3)
# 2补充. 读取不存在的笔记
test_read_nonexistent_note(report)
time.sleep(0.3)
# 3. 获取笔记信息
test_get_note_info(report, key1)
time.sleep(0.3)
# 3补充. 获取不存在笔记的信息
test_get_nonexistent_note_info(report)
time.sleep(0.3)
# 4. 批量检查变更
test_batch_check(report, key1, key2)
time.sleep(0.3)
# 补充. 更新已有笔记
test_update_note(report, key1)
time.sleep(0.3)
# 5. 创建随机笔记
success, new_key = test_create_random_note(report)
if new_key:
keys_to_cleanup.append(new_key)
time.sleep(0.3)
# 补充. 无效钥匙格式
test_invalid_key(report)
time.sleep(0.3)
# 6补充. 删除不存在的笔记
test_delete_nonexistent_note(report)
time.sleep(0.3)
# ---- 清理测试数据 ----
print_separator("清理测试数据")
for key in keys_to_cleanup:
print(f" 正在删除笔记: {key}...", end=" ")
try:
resp = requests.get(f"{BASE_URL}/?delete&note={key}", timeout=TIMEOUT)
data = safe_json_parse(resp)
if data.get("code") == 1:
print("✅ 已删除")
elif resp.status_code == 404:
print("⏭️ 不存在,跳过")
else:
msg = data.get("msg", resp.text[:50]) if data else resp.text[:50]
print(f"⚠️ 删除失败: {msg}")
except Exception as e:
print(f"❌ 异常: {e}")
time.sleep(0.2)
# ---- 验证清理结果 ----
print("\n[验证] 确认笔记已删除...")
verify_key = keys_to_cleanup[0]
try:
resp = requests.get(f"{BASE_URL}/{verify_key}?raw", timeout=TIMEOUT)
if resp.status_code == 404:
print(f" ✅ 笔记 {verify_key} 已确认删除 (404)")
else:
print(f" ⚠️ 笔记 {verify_key} 仍存在 (HTTP {resp.status_code})")
except Exception as e:
print(f" ❌ 验证异常: {e}")
# ---- 输出测试报告 ----
all_passed = report.summary()
# 返回退出码
sys.exit(0 if all_passed else 1)
if __name__ == "__main__":
main()