openspec-api-test
OpenSpec工作流完成后手动触发,使用curl命令对spec描述的接口变动做自动化测试
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
OpenSpec工作流完成后手动触发,使用curl命令对spec描述的接口变动做自动化测试
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
CodingHub 工具广场操作指南。当用户要求搜索/安装/发布/更新 CodingHub 工具,发帖到论坛,管理知识库,或与 CodingHub 平台交互时使用。支持双通道:MCP 优先,HTTP 直连自动降级(Python 或 Node.js CJS CLI 封装,跨平台)。
使用 CodeWiki-CN MCP 工具为代码仓库生成 Wiki 文档并管理 LLM Wiki 知识库。支持三层增强模式:codebase-memory-mcp(深度增强:Leiden 聚类、Cypher 查询、跨服务追踪、复杂度分析)、CodeGraph(调用图增强)、或纯 CodeWiki(标准模式)。自动检测可用 MCP 服务器并选择最优模式。当用户要求生成 Wiki、代码文档、仓库文档、分析代码库结构时使用。
UI/UX design intelligence with searchable database
Use when building and packaging the project for deployment, including frontend build, backend JAR, and creating distribution zip
中文 review 沟通参考——话术模板、分级标注(必须修复/建议修改/仅供参考)、国内团队常见反模式应对。仅在用户显式 /chinese-code-review 时调用,不要根据上下文自动触发。
中文 commit 与 changelog 配置参考——Conventional Commits 中文适配、commitlint/husky/commitizen 中文模板、conventional-changelog 中文配置。仅在用户显式 /chinese-commit-conventions 时调用,不要根据上下文自动触发。
| name | openspec-api-test |
| description | OpenSpec工作流完成后手动触发,使用curl命令对spec描述的接口变动做自动化测试 |
重要: 这是一个手动触发的 skill,用于在开发任务和单元测试全部完成后,基于 OpenSpec change 的
specs/**/*.md中描述的接口变动,使用 curl 命令做接口自动化测试验证。
在以下条件满足后执行:
cd backend && ./gradlew test)make backend 或 ./gradlew bootRun)┌─────────────────────────────────────────────────────────────┐
│ specs/**/*.md ──解析──▶ Scenario 列表 │
│ │ │ │
│ │ 映射为 curl 命令 │
│ │ │ │
│ ▼ ▼ │
│ WHEN: HTTP方法+路径+角色 ──▶ curl -X METHOD URL \ │
│ THEN: 预期状态码+字段 -H "Authorization: Bearer ..."│
│ -d '{...}' │
│ │ │
│ ▼ │
│ 对比实际响应 vs 预期 │
│ │ │
│ ▼ │
│ ✅ PASS / ❌ FAIL 报告 │
└─────────────────────────────────────────────────────────────┘
读取当前 change 目录下的 specs/**/*.md,提取所有 #### Scenario 块。
每个 Scenario 的结构:
#### Scenario: <名称>
- **WHEN** 角色为 ADMIN 的用户对他人创建的工具调用 `DELETE /api/v1/tools/{id}`
- **THEN** 工具状态标记为 DELETED,返回 200 成功响应
解析出以下要素:
| 要素 | 提取规则 | 示例 |
|---|---|---|
| HTTP 方法 | WHEN 中反引号内的 METHOD /path | DELETE |
| 请求路径 | 同上,{id} 替换为实际 ID | /api/v1/tools/1 |
| 调用角色 | WHEN 中"角色为 X 的用户"或"创建者"或"游客" | ADMIN |
| 请求体 | WHEN 中"传入新的 title"等描述 | {"title":"新标题"} |
| 预期状态码 | THEN 中"返回 XXX" | 200 |
| 预期字段 | THEN 中"状态标记为 DELETED"等 | 响应含 DELETED 或查库验证 |
角色映射表(根据项目实际账号配置):
| spec 中的角色描述 | 账号 | 密码 | 用途 |
|---|---|---|---|
| 超级管理员 / SUPER_ADMIN | admin | Cloud@1234 | 系统初始化账号,可操作任意内容 |
| 管理员 / ADMIN | 测试前动态创建并审批 | — | 需先注册 ADMIN 角色,再由超管审批 |
| 创建者 / 普通用户 / USER | 测试前动态创建 | — | 内容的原始创建者 |
| 游客 / 未登录 | 无需登录 | — | 不带 Authorization 头 |
# 超管登录获取 token
SUPER_ADMIN_TOKEN=$(curl -s -X POST http://localhost:8082/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"Cloud@1234"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['data']['accessToken'])")
echo "Super Admin Token: ${SUPER_ADMIN_TOKEN:0:20}..."
# 创建普通用户 A(内容创建者)
curl -s -X POST http://localhost:8082/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"test_owner","nickname":"测试创建者","password":"test123456"}'
# 创建普通用户 B(无权限用户)
curl -s -X POST http://localhost:8082/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"test_user","nickname":"测试用户","password":"test123456"}'
# 创建管理员账号(注册后状态为 PENDING,需超管审批)
curl -s -X POST http://localhost:8082/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"test_admin","nickname":"测试管理员","password":"test123456","role":"ADMIN"}'
# 超管审批管理员账号
ADMIN_USER_ID=$(curl -s http://localhost:8082/api/v1/admin/pending-users \
-H "Authorization: Bearer $SUPER_ADMIN_TOKEN" \
| python3 -c "import sys,json; users=json.load(sys.stdin)['data']; print([u['id'] for u in users if u['username']=='test_admin'][0])")
curl -s -X PUT "http://localhost:8082/api/v1/admin/approve/$ADMIN_USER_ID" \
-H "Authorization: Bearer $SUPER_ADMIN_TOKEN"
get_token() {
local username=$1
local password=$2
curl -s -X POST http://localhost:8082/api/v1/auth/login \
-H "Content-Type: application/json" \
-d "{\"username\":\"$username\",\"password\":\"$password\"}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['data']['accessToken'])"
}
OWNER_TOKEN=$(get_token "test_owner" "test123456")
USER_TOKEN=$(get_token "test_user" "test123456")
ADMIN_TOKEN=$(get_token "test_admin" "test123456")
SUPER_ADMIN_TOKEN=$(get_token "admin" "Cloud@1234")
# 创建者创建测试工具
TOOL_ID=$(curl -s -X POST http://localhost:8082/api/v1/tools \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OWNER_TOKEN" \
-d '{"name":"测试工具_API_TEST","categoryId":1,"content":"测试内容"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['data']['id'])")
# 创建者创建测试帖子
POST_ID=$(curl -s -X POST http://localhost:8082/api/forum/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OWNER_TOKEN" \
-d '{"title":"测试帖子_API_TEST","content":"测试内容","categoryId":1}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
# 创建者创建测试微课(需视频文件,可跳过或用占位)
# VIDEO_ID=...
# 通用测试函数
run_test() {
local tc_id=$1
local tc_name=$2
local method=$3
local url=$4
local token=$5
local body=$6
local expect_code=$7
local expect_contains=$8
local auth_header=""
if [ -n "$token" ] && [ "$token" != "null" ]; then
auth_header="-H \"Authorization: Bearer $token\""
fi
local body_arg=""
if [ -n "$body" ]; then
body_arg="-H \"Content-Type: application/json\" -d '$body'"
fi
echo "── $tc_id: $tc_name ──"
echo " $method $url expect=$expect_code"
# 执行请求
local response
response=$(eval curl -s -w '"\\n%{http_code}"' -X "$method" "$url" $auth_header $body_arg)
local http_code=$(echo "$response" | tail -1)
local body_text=$(echo "$response" | sed '$d')
# 断言状态码
if [ "$http_code" == "$expect_code" ]; then
local status="✅ PASS"
else
local status="❌ FAIL"
fi
# 断言响应包含(可选)
if [ -n "$expect_contains" ] && ! echo "$body_text" | grep -q "$expect_contains"; then
status="❌ FAIL"
fi
echo " actual=$http_code $status"
if [ "$status" == "❌ FAIL" ]; then
echo " response: ${body_text:0:200}"
fi
echo ""
}
以 specs/content-moderation/spec.md 为例:
BASE="http://localhost:8082"
# Scenario: 管理员删除他人创建的工具
# WHEN: ADMIN 调用 DELETE /api/v1/tools/{id}
# THEN: 返回 200
run_test "TC-001" "管理员删除他人工具" \
"DELETE" "$BASE/api/v1/tools/$TOOL_ID" \
"$ADMIN_TOKEN" "" "200" ""
# Scenario: 普通用户无法删除他人内容
# WHEN: USER 调用 DELETE /api/v1/tools/{id}
# THEN: 返回 403
run_test "TC-002" "普通用户删除他人工具被拒" \
"DELETE" "$BASE/api/v1/tools/$TOOL_ID" \
"$USER_TOKEN" "" "403" ""
# Scenario: 创建者仍可删除自己的内容
run_test "TC-003" "创建者删除自己工具" \
"DELETE" "$BASE/api/v1/tools/$TOOL_ID" \
"$OWNER_TOKEN" "" "200" ""
# Scenario: 管理员编辑他人帖子
# WHEN: ADMIN 调用 PUT /api/forum/posts/{id}
# THEN: 返回 200
run_test "TC-004" "管理员编辑他人帖子" \
"PUT" "$BASE/api/forum/posts/$POST_ID" \
"$ADMIN_TOKEN" \
'{"title":"管理员修改的标题","content":"修改后内容","categoryId":1}' \
"200" ""
# Scenario: 普通用户无法编辑他人帖子
run_test "TC-005" "普通用户编辑他人帖子被拒" \
"PUT" "$BASE/api/forum/posts/$POST_ID" \
"$USER_TOKEN" \
'{"title":"无权修改","content":"内容","categoryId":1}' \
"403" ""
| spec WHEN 中的模式 | curl 构造 |
|---|---|
GET /path | curl -X GET $BASE/path |
POST /path + "包含 title、content" | curl -X POST $BASE/path -H "Content-Type: application/json" -d '{"title":"...","content":"..."}' |
PUT /path + "更新 title" | curl -X PUT $BASE/path -H "Content-Type: application/json" -d '{"title":"新标题"}' |
DELETE /path | curl -X DELETE $BASE/path |
| "角色为 ADMIN" | -H "Authorization: Bearer $ADMIN_TOKEN" |
| "角色为 USER" | -H "Authorization: Bearer $USER_TOKEN" |
| "创建者" | -H "Authorization: Bearer $OWNER_TOKEN" |
| "游客" / "未登录" | 无 Authorization 头 |
{id} 占位符 | 替换为第二步创建的测试内容 ID |
| spec THEN 中的模式 | 断言 |
|---|---|
| "返回 200" / "返回 201" / "返回 204" | HTTP 状态码 == 预期值 |
| "返回 403 Forbidden" | HTTP 状态码 == 403 |
| "返回 404" | HTTP 状态码 == 404 |
| "状态标记为 DELETED" | 查库验证或 GET 请求返回 404 |
| "返回帖子列表" | 响应体包含 content 数组 |
| "返回 201,authorId 为当前用户" | 状态码 201 + 响应含 authorId |
将所有测试写入一个 shell 脚本执行,输出报告:
#!/bin/bash
# api-test.sh — 基于 spec 的接口自动化测试
BASE="http://localhost:8082"
PASS=0
FAIL=0
RESULTS=""
run_test() {
# ... (见第三步的函数实现)
if [ "$status" == "✅ PASS" ]; then
PASS=$((PASS+1))
else
FAIL=$((FAIL+1))
fi
RESULTS="$RESULTS\n| $tc_id | $tc_name | $status | expect=$expect_code actual=$http_code |"
}
# === 测试用例(从 spec 自动映射)===
# ... 所有 run_test 调用 ...
# === 汇总 ===
echo ""
echo "=============================="
echo " API Test Summary"
echo " Passed: $PASS"
echo " Failed: $FAIL"
echo " Total: $((PASS+FAIL))"
echo "=============================="
echo ""
echo "| TC ID | Test Case | Status | Notes |"
echo "|-------|-----------|--------|-------|"
echo -e "$RESULTS"
测试完成后生成报告,保存到 openspec/changes/<change-name>/api-test-report.md:
# API Test Report: <change-name>
## 测试环境
- 后端地址: http://localhost:8082
- 测试时间: YYYY-MM-DD HH:MM
- 测试账号: test_owner(USER), test_user(USER), test_admin(ADMIN), admin(SUPER_ADMIN)
## 测试结果
| TC ID | Test Case | Status | Notes |
|-------|-----------|--------|-------|
| TC-001 | 管理员删除他人工具 | ✅ PASS | expect=200 actual=200 |
| TC-002 | 普通用户删除他人工具被拒 | ✅ PASS | expect=403 actual=403 |
| TC-003 | 创建者删除自己工具 | ✅ PASS | expect=200 actual=200 |
| TC-004 | 管理员编辑他人帖子 | ✅ PASS | expect=200 actual=200 |
| TC-005 | 普通用户编辑他人帖子被拒 | ❌ FAIL | expect=403 actual=200 |
## 失败用例详情
### TC-005: 普通用户编辑他人帖子被拒
- 请求: PUT /api/forum/posts/3
- Token: test_user (USER)
- Body: {"title":"无权修改","content":"内容","categoryId":1}
- 预期: 403 Forbidden
- 实际: 200
- 响应体: {"code":200,"message":"更新成功","data":{...}}
- 分析: 后端权限校验未拦截普通用户,需检查 ForumPostService.updatePost 的 isAdmin 判断
## 总结
**Overall: 4/5 Passed**
失败用例表明后端权限校验存在缺陷,需修复后重新测试。
1. 读取 specs/**/*.md
└─ 提取所有 #### Scenario 块
└─ 解析 WHEN(HTTP方法/路径/角色/请求体) + THEN(预期状态码/字段)
2. 准备测试环境
└─ 超管登录 → 审批测试管理员 → 创建测试用户
└─ 创建者创建测试内容(工具/帖子/微课) → 记录 ID
3. 生成 curl 测试脚本
└─ 每个 Scenario → 一个 run_test 调用
└─ 角色映射到对应 token
└─ {id} 替换为测试内容 ID
4. 执行测试
└─ 运行脚本,捕获每个请求的 HTTP 状态码和响应体
└─ 对比预期,标记 PASS/FAIL
5. 生成报告
└─ 保存到 openspec/changes/<change-name>/api-test-report.md
└─ 失败用例附请求/响应详情和初步分析