OpenAPI 自举测试
使用 Shepherd 自身的能力测试其正在运行的 HTTP API:API 定义 + 用例 + 场景编排 + 执行。
运行
# A. 单链自举(登录 → 提取 → 认证链 + 负面 401):
# 验证真实链式执行机制
python3 .claude/skills/openapi-bootstrap/selftest.py
# B. 完整模块场景覆盖:每个 OpenAPI 标签一个 CRUD/生命周期场景,
# 附带每个模块的报告
python3 .claude/skills/openapi-bootstrap/scenarios_all.py
# C. 测试计划操作自检(计划 CRUD、计划文档、用例链接、
# 计划运行、单用例重跑、调度)
python3 .claude/skills/openapi-bootstrap/plans_selftest.py
scenarios_all.py 为每个业务模块(共 20 个模块)构建一个真实的链式场景,引用 API 用例(kind=CASE,包含认证头、变量提取、断言),按 CRUD/生命周期链排序,并在进程中通过环境执行。失败策略为 CONTINUE → 一次运行会提供每个步骤的通过/失败状态及原因。目前全部通过:20/20 个模块,114/114 个步骤。没有“更新用例”端点 → 每次运行会删除旧的持有者定义(级联删除其用例)并重新构建。持久化资源(项目/用户/需求)带有每次运行的唯一后缀,因此重复运行不会触发唯一约束冲突。
构建过程中确认的真实约束(已融入链中;如果后端枚举发生变化,请保持同步):
- 资源池
poolType∈ {Node,Kubernetes}(无 LOCAL);allOrg=false时,orgIds不能为空。 - 任务创建
POST /decomposition/{id}/task返回{"taskId": <slug>}(而非id);积分/状态端点使用该taskId。 - 交付执行器
CLAUDE_CODE/CODEX是同步桩:POST /delivery一步完成,直接创建为DELIVERED状态(无 running/complete 异步转换)。 /runner/probe和/runner-agent/{id}/run需要一个在线的 runner 代理(否则返回 502);本地链仅覆盖管理平面。- 多个列表端点需要
?projectId=参数(需求/技能/功能用例/用例审查)。 - 创建返回 201,删除/部分更新返回 204 → 使用通用的
ResponseCode < 400成功断言,而非精确的StatusIs(200)。
| 环境变量 | 默认值 | 描述 |
|---|---|---|
SHEPHERD_BASE | http://127.0.0.1:9180 | 后端地址 |
SHEPHERD_USER / SHEPHERD_PASS | admin / s3cret | 登录凭据(SHEPHERD_ADMIN_PASSWORD) |
SHEPHERD_PROJECT_ID | 自动解析 | 目标项目;默认为第一个组织的第一个项目,不存在时自动创建 |
脚本、环境、定义、用例和场景均按名称创建或重用——重复运行不会产生累积。
(预存的资源名称如 自举环境 / 自举链路 保留中文原名:它们是按名称重用的持久化键;重命名会导致已有行孤立。)
流程(7 步)
- 登录获取令牌(管理员调用仅用于资源设置)
- 解析/创建组织 → 项目
- 获取本系统的 OpenAPI(
GET /api-docs/openapi.json)→ 幂等导入(POST /api/definition/import;相同方法+路径会覆盖规范,不会产生重复);抽样检查规范是否已填充,且每个接口都有一个带断言的用例 - 创建或重用指向此主机的环境(
baseUrl= 本服务器) - 在自举链定义下创建或重用 4 个真实用例
- 创建或重用引用这些用例作为步骤的场景(
kind=CASE),排列顺序,使用环境执行 - 获取报告;打印每个步骤 ✅/❌ + 提取的变量 + 断言计数
这是真实链,而非硬编码的 GET 请求
场景引用 API 用例(kind=CASE)而非内联硬编码请求。这些用例涵盖 POST + GET + 认证头 + 变量提取 + 跨步骤链:
① 登录并提取令牌 POST /auth/login 断言 status 200 + contains "token" → 提取 token = $.token
② 认证后列出组织 GET /organization 头部 Authorization: Bearer ${token} → 提取 orgId = $.items[0].id
③ 按组织 ID 列出认证后的项目 GET /project?organizationId=${orgId} (token 和 orgId 均被替换)
④ 负面测试:无令牌被拒绝 GET /organization 断言 status 401
关键执行机制(参见 crates/api-test/src/adapters/{plan,local,pg}.rs、crates/api-runner/src/domain/runner.rs):
- CASE 步骤在进程中运行,加载用例的完整方法/URL/请求体/头部/认证/断言/处理器——无需资源池(内联
REQUEST步骤会删除头部,因此使用 CASE)。 - EXTRACT 处理器将
$.token等写入运行变量,并在步骤间传递。 ${var}单花括号替换适用于 URL / 头部值 / 请求体(注意:不是{{}})。- 环境的 baseUrl 会前缀到相对 URL 上;默认头部填充空白(同名用例头部优先)。
- 已知限制:
Variable断言无法在场景内看到运行变量(plan.rs不会将变量传递给断言评估),因此本脚本使用StatusIs/BodyContains而非Variable断言。
测试计划操作自检
plans_selftest.py 通过 HTTP API 端到端验证测试计划操作:计划创建/重用(计划 ID 保存在本地状态文件 .plan_selftest_id 中,因为没有计划列表/删除端点)、PUT 更新往返(描述/标签/通过阈值)、计划文档保存 + 链接同步、用例链接(一个场景 + 一个 API 用例)、完整计划运行(所有行均为 SUCCESS,场景行携带 reportId,其所有步骤均为 SUCCESS)、单用例重跑、调度创建/删除(201/204/404)以及用例取消链接(204 + 列表缩小)。幂等性:连续运行两次必须全部通过。
后端导入器增强(同期发布)
POST /api/definition/import 现在:
- 解析每个操作的
parameters(查询/头部/路径;必需标志编码到备注中)、requestBody($ref/allOf通过components解析为 bodySchema 树 + 示例)和responses(状态码 + 模式示例)→ 写入定义spec; - 为每个新接口生成一个默认用例(状态码 + 基本业务断言);
- 是幂等的:已存在的项目+方法+路径仅覆盖其 spec(用户编辑的用例得以保留);返回
{created, updated, skipped}。
代码:crates/api-definition/src/domain/import.rs、application/import_api_definitions.rs、domain/api_definition.rs(with_spec)。
退出码
0 全部通过;1 某些步骤失败;2 自举流程本身出错。