#!/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¬e={key} 4. 批量检查变更 GET /?check&keys=key1,key2 5. 创建随机笔记 GET /?new&json&text=test 6. 删除笔记 GET /?delete¬e={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¬e={key} 预期: 200 + {"code":1, "data":{"key":"...", "size":..., "mtime":..., "exists":true}} """ print_separator("测试3: 获取笔记信息 (GET ?info)") url = f"{BASE_URL}/?info¬e={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¬e={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¬e={key} 预期: 200 + {"code":1, "msg":"deleted"} """ print_separator("测试6: 删除笔记 (GET ?delete)") url = f"{BASE_URL}/?delete¬e={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¬e={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¬e={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()