Files
label_backend/docs/swagger-blackbox-test-cases.md
2026-04-14 20:00:37 +08:00

571 lines
30 KiB
Markdown
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.

# label-backend Swagger 接口黑盒测试用例
## 1. 文档说明
- 生成时间2026-04-14
- 适用项目:`label-backend`
- 覆盖范围:当前 `controller` 中已开放的全部 Swagger/OpenAPI 接口
- 生成依据:
- `src/main/java/com/label/controller/**/*.java`
- `src/main/java/com/label/service/**/*.java`
- `src/main/resources/sql/init.sql`
- `src/test/java/com/label/integration/**/*.java`
- 说明:
- 当前会话内未能直接访问 `http://127.0.0.1:8080/v3/api-docs`,本文档按代码、设计和现有集成测试反推生成。
- 文中“预期结果”以黑盒测试应验证的业务契约为主;若当前实现存在契约空洞,用例应保留,用于发现缺陷。
## 2. 测试前提
### 2.1 基础环境
- Base URL`http://127.0.0.1:8080`
- OpenAPI`GET /v3/api-docs`
- Swagger UI`GET /swagger-ui.html`
- 默认返回体:`{ "code": "...", "message": "...", "data": ... }`
### 2.2 认证前提
- 若要执行认证、鉴权、越权、Token 失效类用例,建议运行时配置 `auth.enabled=true`
- 若当前运行环境仍为 `auth.enabled=false` 的 mock 模式,则以下用例中所有 `401/403` 校验需要在真实认证模式下执行。
### 2.3 种子数据建议
基于 `src/main/resources/sql/init.sql`,至少准备以下账号:
| 公司 | 用户名 | 密码 | 角色 |
|---|---|---|---|
| `DEMO` | `admin` | `admin123` | `ADMIN` |
| `DEMO` | `reviewer01` | `review123` | `REVIEWER` |
| `DEMO` | `annotator01` | `annot123` | `ANNOTATOR` |
| `DEMO` | `uploader01` | `upload123` | `UPLOADER` |
额外建议准备:
- 第二家公司 `TESTB`
- `TESTB` 下至少 1 个 `ADMIN` 账号
- `DEMO``TESTB` 各自的资料、任务、配置、导出批次、视频任务样本
### 2.4 通用 Header
- JSON 接口:`Content-Type: application/json`
- 文件上传:`multipart/form-data`
- 受保护接口:`Authorization: Bearer <token>`
- 视频回调启用密钥时:`X-Callback-Secret: <VIDEO_CALLBACK_SECRET>`
## 3. 通用黑盒用例
`POST /api/auth/login``POST /api/video/callback` 外,所有受保护接口均应复用以下通用用例。
| 用例ID | 适用范围 | 场景 | 操作 | 预期结果 |
|---|---|---|---|---|
| `G-AUTH-001` | 全部受保护接口 | 缺少 Token | 不传 `Authorization` | HTTP `401``code=UNAUTHORIZED` |
| `G-AUTH-002` | 全部受保护接口 | Token 格式错误 | 传 `Authorization: BearerX xxx``Basic xxx` | HTTP `401``code=UNAUTHORIZED` |
| `G-AUTH-003` | 全部受保护接口 | Token 无效或过期 | 传不存在/已失效 Token | HTTP `401``code=UNAUTHORIZED` |
| `G-ROLE-001` | 有角色要求的接口 | 角色不足 | 用低权限账号访问高权限接口 | HTTP `403``code=FORBIDDEN` |
| `G-TENANT-001` | 租户数据相关接口 | 跨租户访问数据 | 用 `TESTB` Token 访问 `DEMO` 数据 | 返回 `404`、空列表或业务拒绝;不能读到 `DEMO` 数据 |
| `G-PAGE-001` | 分页接口 | 大页码限制 | 传超大 `pageSize` | 返回成功,`pageSize` 被限制到系统上限,不出现异常 |
| `G-ERR-001` | 全部接口 | 非法请求不能打穿到 5xx | 传缺参/错参/非法状态 | 返回可解释的 `4xx` 或失败业务码,不应无意义 `500` |
## 4. 认证管理
### 4.1 `POST /api/auth/login`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `AUTH-LOGIN-001` | 正常登录 | `{"companyCode":"DEMO","username":"admin","password":"admin123"}` | HTTP `200``code=SUCCESS`,返回 `token/userId/username/role/expiresIn` |
| `AUTH-LOGIN-002` | 密码错误 | `password=wrong` | HTTP `401``code=USER_NOT_FOUND` |
| `AUTH-LOGIN-003` | 公司代码不存在 | `companyCode=NO_SUCH` | HTTP `401``code=USER_NOT_FOUND` |
| `AUTH-LOGIN-004` | 账号被禁用 | 先将用户禁用,再登录 | HTTP `403``code=USER_DISABLED` |
| `AUTH-LOGIN-005` | 缺少必填字段 | 缺 `companyCode``username``password` | 返回失败,不能生成有效 Token不应出现无提示 `500` |
### 4.2 `POST /api/auth/logout`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `AUTH-LOGOUT-001` | 正常退出 | 用有效 Token 调用退出 | HTTP `200``code=SUCCESS` |
| `AUTH-LOGOUT-002` | 退出后 Token 立即失效 | 退出后继续访问 `/api/auth/me` | HTTP `401``code=UNAUTHORIZED` |
| `AUTH-LOGOUT-003` | 无 Token 退出 | 复用 `G-AUTH-001` | HTTP `401` |
### 4.3 `GET /api/auth/me`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `AUTH-ME-001` | 查询当前用户信息 | 用 `admin` Token 调用 | HTTP `200`,返回 `id/username/realName/role/companyId/companyName` |
| `AUTH-ME-002` | 已退出 Token 查询自己 | 先登录再退出,再调 `/me` | HTTP `401``code=UNAUTHORIZED` |
| `AUTH-ME-003` | 被禁用账号的旧 Token 查询自己 | 先登录,管理员禁用该用户,再调 `/me` | HTTP `401``code=UNAUTHORIZED` |
## 5. 公司管理
说明:本组接口复用 `G-AUTH-001~003``G-ROLE-001`
### 5.1 `GET /api/companies`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `COMPANY-LIST-001` | 管理员分页查询公司 | `?page=1&pageSize=20` | HTTP `200`,返回分页结构 |
| `COMPANY-LIST-002` | 按状态筛选 | `?status=ACTIVE` | 仅返回对应状态公司 |
| `COMPANY-LIST-003` | 非管理员访问 | 用 `REVIEWER``UPLOADER` 调用 | HTTP `403``code=FORBIDDEN` |
### 5.2 `POST /api/companies`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `COMPANY-CREATE-001` | 创建公司成功 | `{"companyName":"测试公司B","companyCode":"TESTB"}` | HTTP `201`,创建成功,状态默认 `ACTIVE` |
| `COMPANY-CREATE-002` | 公司代码重复 | 使用已存在 `companyCode` | HTTP `409``code=DUPLICATE_COMPANY_CODE` |
| `COMPANY-CREATE-003` | 公司名称重复 | 使用已存在 `companyName` | HTTP `409``code=DUPLICATE_COMPANY_NAME` |
| `COMPANY-CREATE-004` | 公司名为空 | `companyName` 空或空白 | HTTP `400``code=INVALID_COMPANY_FIELD` |
| `COMPANY-CREATE-005` | 公司代码为空 | `companyCode` 空或空白 | HTTP `400``code=INVALID_COMPANY_FIELD` |
### 5.3 `PUT /api/companies/{id}`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `COMPANY-UPDATE-001` | 更新公司成功 | 修改 `companyName/companyCode` | HTTP `200`,字段更新成功 |
| `COMPANY-UPDATE-002` | 更新不存在公司 | `id` 不存在 | HTTP `404``code=NOT_FOUND` |
| `COMPANY-UPDATE-003` | 更新为重复代码 | `companyCode` 改成已存在值 | HTTP `409``code=DUPLICATE_COMPANY_CODE` |
| `COMPANY-UPDATE-004` | 更新为重复名称 | `companyName` 改成已存在值 | HTTP `409``code=DUPLICATE_COMPANY_NAME` |
### 5.4 `PUT /api/companies/{id}/status`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `COMPANY-STATUS-001` | 禁用公司成功 | `{"status":"DISABLED"}` | HTTP `200`,公司状态变为 `DISABLED` |
| `COMPANY-STATUS-002` | 恢复公司成功 | `{"status":"ACTIVE"}` | HTTP `200`,公司状态变为 `ACTIVE` |
| `COMPANY-STATUS-003` | 非法状态值 | `{"status":"UNKNOWN"}` | HTTP `400``code=INVALID_COMPANY_STATUS` |
| `COMPANY-STATUS-004` | 公司不存在 | 不存在 `id` | HTTP `404``code=NOT_FOUND` |
### 5.5 `DELETE /api/companies/{id}`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `COMPANY-DELETE-001` | 删除空公司成功 | 删除无用户的公司 | HTTP `200`,删除成功 |
| `COMPANY-DELETE-002` | 公司下仍有用户 | 删除已有用户公司 | HTTP `409``code=COMPANY_HAS_USERS` |
| `COMPANY-DELETE-003` | 删除不存在公司 | 不存在 `id` | HTTP `404``code=NOT_FOUND` |
## 6. 用户管理
说明:本组接口复用 `G-AUTH-001~003``G-ROLE-001``G-TENANT-001`
### 6.1 `GET /api/users`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `USER-LIST-001` | 管理员查看本公司用户 | `?page=1&pageSize=20` | HTTP `200`,仅返回当前公司用户 |
| `USER-LIST-002` | 跨租户隔离 | 用 `TESTB` 管理员查看列表 | 不出现 `DEMO` 用户 |
| `USER-LIST-003` | 非管理员访问 | 用 `REVIEWER` 调用 | HTTP `403` |
### 6.2 `POST /api/users`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `USER-CREATE-001` | 创建用户成功 | `{"username":"qa01","password":"qa123456","realName":"测试员","role":"ANNOTATOR"}` | HTTP `200`,创建成功,状态默认为 `ACTIVE` |
| `USER-CREATE-002` | 用户名重复 | 同公司创建同名用户 | HTTP `409``code=DUPLICATE_USERNAME` |
| `USER-CREATE-003` | 角色非法 | `role=VIEWER` 或其他未支持角色 | HTTP `400``code=INVALID_ROLE` |
| `USER-CREATE-004` | 跨租户污染校验 | `TESTB` 管理员创建用户后,`DEMO` 不可见 | 创建成功且仅属于当前公司 |
### 6.3 `PUT /api/users/{id}`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `USER-UPDATE-001` | 更新真实姓名成功 | `{"realName":"新名字"}` | HTTP `200`,真实姓名更新 |
| `USER-UPDATE-002` | 更新密码成功 | `{"password":"newpass123"}`,再重新登录 | 新密码可登录,旧密码失效 |
| `USER-UPDATE-003` | 更新不存在用户 | 不存在 `id` | HTTP `404``code=NOT_FOUND` |
| `USER-UPDATE-004` | 更新他租户用户 | 用 `TESTB` 管理员修改 `DEMO` 用户 | HTTP `404` 或不可见 |
### 6.4 `PUT /api/users/{id}/status`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `USER-STATUS-001` | 禁用用户成功 | `{"status":"DISABLED"}` | HTTP `200`,用户状态更新成功 |
| `USER-STATUS-002` | 禁用后旧 Token 失效 | 禁用后用该用户旧 Token 调 `/api/auth/me` | HTTP `401``code=UNAUTHORIZED` |
| `USER-STATUS-003` | 恢复用户成功 | `{"status":"ACTIVE"}` | HTTP `200` |
| `USER-STATUS-004` | 非法状态值 | `{"status":"LOCKED"}` | HTTP `400``code=INVALID_STATUS` |
### 6.5 `PUT /api/users/{id}/role`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `USER-ROLE-001` | 修改角色成功 | `{"role":"REVIEWER"}` | HTTP `200`,用户角色更新成功 |
| `USER-ROLE-002` | 角色变更立即生效 | 同一 Token 变更前访问 reviewer 接口 `403`,变更后重试 | 变更后无需重新登录即可访问成功 |
| `USER-ROLE-003` | 非法角色值 | `{"role":"VIEWER"}` | HTTP `400``code=INVALID_ROLE` |
| `USER-ROLE-004` | 修改不存在用户 | 不存在 `id` | HTTP `404``code=NOT_FOUND` |
## 7. 资料管理
说明:本组接口复用 `G-AUTH-001~003``G-ROLE-001``G-TENANT-001`
### 7.1 `POST /api/source/upload`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `SOURCE-UPLOAD-001` | 上传文本资料成功 | `multipart/form-data`,传 `file` + `dataType=TEXT` | HTTP `201`,返回资料 `id/fileName/dataType/status` |
| `SOURCE-UPLOAD-002` | 上传图片资料成功 | `dataType=IMAGE` | HTTP `201` |
| `SOURCE-UPLOAD-003` | 上传视频资料成功 | `dataType=VIDEO` | HTTP `201` |
| `SOURCE-UPLOAD-004` | 空文件上传 | 文件为空 | HTTP `400``code=FILE_EMPTY` |
| `SOURCE-UPLOAD-005` | 不支持的资料类型 | `dataType=PDF` | HTTP `400``code=INVALID_TYPE` |
| `SOURCE-UPLOAD-006` | Swagger 文案兼容性检查 | `dataType=text` 小写 | 应与文档约定一致;若失败需修正文档或实现 |
| `SOURCE-UPLOAD-007` | 低权限无权上传 | 用无上传权限账号访问 | HTTP `403` |
### 7.2 `GET /api/source/list`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `SOURCE-LIST-001` | 上传员查看自己资料 | 用 `uploader01` 调用 | 仅返回自己上传的数据 |
| `SOURCE-LIST-002` | 管理员查看公司全部资料 | 用 `admin` 调用 | 返回公司内全部资料 |
| `SOURCE-LIST-003` | 按类型筛选 | `?dataType=TEXT` | 仅返回对应类型 |
| `SOURCE-LIST-004` | 按状态筛选 | `?status=PENDING` | 仅返回对应状态 |
| `SOURCE-LIST-005` | 跨租户隔离 | `TESTB` 调用 | 看不到 `DEMO` 数据 |
### 7.3 `GET /api/source/{id}`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `SOURCE-DETAIL-001` | 查看资料详情成功 | 查询本公司资料 | HTTP `200`,返回详情及下载地址 |
| `SOURCE-DETAIL-002` | 查询不存在资料 | `id` 不存在 | HTTP `404``code=NOT_FOUND` |
| `SOURCE-DETAIL-003` | 跨租户查看详情 | 用 `TESTB` Token 查 `DEMO` 资料 | HTTP `404` 或不可见 |
### 7.4 `DELETE /api/source/{id}`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `SOURCE-DELETE-001` | 删除 `PENDING` 资料成功 | 删除未进入流水线资料 | HTTP `200` |
| `SOURCE-DELETE-002` | 删除不存在资料 | `id` 不存在 | HTTP `404``code=NOT_FOUND` |
| `SOURCE-DELETE-003` | 删除已进入流水线资料 | 状态为 `PREPROCESSING/EXTRACTING/QA_REVIEW/APPROVED` | HTTP `409``code=SOURCE_IN_PIPELINE` |
| `SOURCE-DELETE-004` | 非管理员删除 | 用 `UPLOADER` 删除资料 | HTTP `403` |
## 8. 任务管理
说明:本组接口复用 `G-AUTH-001~003``G-ROLE-001``G-TENANT-001`
### 8.1 `GET /api/tasks/pool`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `TASK-POOL-001` | 标注员查看任务池 | 用 `ANNOTATOR` 调用 | 仅返回 `EXTRACTION + UNCLAIMED` 任务 |
| `TASK-POOL-002` | Reviewer/Admin 调用任务池 | 用 `REVIEWER``ADMIN` 调用 | 返回 `SUBMITTED` 待审任务 |
| `TASK-POOL-003` | 跨租户隔离 | `TESTB` 调用 | 仅能看到本公司任务 |
### 8.2 `GET /api/tasks/mine`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `TASK-MINE-001` | 查询我的任务成功 | `?page=1&pageSize=20` | 返回当前用户的 `IN_PROGRESS/SUBMITTED/REJECTED` 任务 |
| `TASK-MINE-002` | 按状态过滤 | `?status=REJECTED` | 仅返回对应状态 |
| `TASK-MINE-003` | 未领取任务不应出现在 mine | 准备 `UNCLAIMED` 任务 | 列表中不出现该任务 |
### 8.3 `GET /api/tasks/pending-review`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `TASK-REVIEW-001` | Reviewer 查询待审批任务 | `?taskType=EXTRACTION` | 仅返回 `SUBMITTED` 且符合类型的任务 |
| `TASK-REVIEW-002` | 非 Reviewer 访问 | 用 `ANNOTATOR` 调用 | HTTP `403` |
| `TASK-REVIEW-003` | 跨租户隔离 | `TESTB` 调用 | 看不到 `DEMO` 待审任务 |
### 8.4 `GET /api/tasks`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `TASK-LIST-001` | 管理员查询全部任务 | `?status=SUBMITTED&taskType=QA_GENERATION` | 返回过滤后的本公司任务 |
| `TASK-LIST-002` | 非管理员访问 | 用 `REVIEWER` 调用 | HTTP `403` |
### 8.5 `POST /api/tasks`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `TASK-CREATE-001` | 创建提取任务成功 | `{"sourceId":<id>,"taskType":"EXTRACTION"}` | HTTP `200`,返回新任务,状态 `UNCLAIMED` |
| `TASK-CREATE-002` | 创建 QA 任务成功 | `{"sourceId":<id>,"taskType":"QA_GENERATION"}` | HTTP `200` |
| `TASK-CREATE-003` | 缺少 `sourceId` | 仅传 `taskType` | 应返回明确失败,不应出现裸 `500` |
| `TASK-CREATE-004` | 缺少 `taskType` | 仅传 `sourceId` | 应返回明确失败,不应出现裸 `500` |
### 8.6 `GET /api/tasks/{id}`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `TASK-DETAIL-001` | 查询任务详情成功 | 查询本公司任务 | HTTP `200`,返回任务详情 |
| `TASK-DETAIL-002` | 查询不存在任务 | 不存在 `id` | HTTP `404``code=NOT_FOUND` |
| `TASK-DETAIL-003` | 跨租户查看任务 | 用 `TESTB` 查询 `DEMO` 任务 | HTTP `404` 或不可见 |
### 8.7 `POST /api/tasks/{id}/claim`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `TASK-CLAIM-001` | 正常领取未领取任务 | `ANNOTATOR` 领取 `UNCLAIMED` 任务 | HTTP `200`,任务状态变为 `IN_PROGRESS` |
| `TASK-CLAIM-002` | 并发抢任务 | 10 并发领取同一任务 | 恰好 1 个成功,其余 HTTP `409``code=TASK_CLAIMED` |
| `TASK-CLAIM-003` | 重复领取已被占用任务 | 第二个用户再领取 | HTTP `409``code=TASK_CLAIMED` |
### 8.8 `POST /api/tasks/{id}/unclaim`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `TASK-UNCLAIM-001` | 放弃进行中任务成功 | 当前领取人放弃 `IN_PROGRESS` 任务 | HTTP `200`,状态回到 `UNCLAIMED` |
| `TASK-UNCLAIM-002` | 非法状态放弃 | 对 `SUBMITTED/REJECTED/APPROVED` 任务调用 | HTTP `409``code=INVALID_STATE_TRANSITION` |
| `TASK-UNCLAIM-003` | 非领取人放弃他人任务 | 用别的标注员调用 | 应被拒绝,不能让他人释放任务 |
### 8.9 `POST /api/tasks/{id}/reclaim`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `TASK-RECLAIM-001` | 原领取人重领被驳回任务 | 原领取人对 `REJECTED` 任务调用 | HTTP `200`,状态变为 `IN_PROGRESS` |
| `TASK-RECLAIM-002` | 非原领取人重领 | 其他用户调用 | HTTP `403``code=FORBIDDEN` |
| `TASK-RECLAIM-003` | 非驳回状态重领 | 对 `IN_PROGRESS` 任务调用 | HTTP `409``code=INVALID_STATE_TRANSITION` |
### 8.10 `PUT /api/tasks/{id}/reassign`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `TASK-REASSIGN-001` | 管理员强制指派成功 | `{"userId":<targetUserId>}` | HTTP `200`,任务归属变更到目标用户 |
| `TASK-REASSIGN-002` | 指派不存在任务 | 不存在 `id` | HTTP `404``code=NOT_FOUND` |
| `TASK-REASSIGN-003` | 缺少 `userId` | 空请求体或缺字段 | 应返回明确失败,不应裸 `500` |
| `TASK-REASSIGN-004` | 指派后状态一致性 | 指派后查询任务 | 任务应处于可执行状态,归属人与时间被更新 |
## 9. 提取标注
说明:本组接口复用 `G-AUTH-001~003``G-ROLE-001``G-TENANT-001`
### 9.1 `GET /api/extraction/{taskId}`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `EXT-GET-001` | 获取提取结果成功 | 查询本公司任务 | HTTP `200`,返回 `taskId/sourceType/sourceFilePath/isFinal/resultJson` |
| `EXT-GET-002` | 查询不存在任务 | 不存在 `taskId` | HTTP `404``code=NOT_FOUND` |
| `EXT-GET-003` | 跨租户查询结果 | 用 `TESTB` 查询 `DEMO` 任务 | HTTP `404` 或不可见 |
### 9.2 `PUT /api/extraction/{taskId}`
| 用例ID | 场景 | 请求体 | 预期结果 |
|---|---|---|---|
| `EXT-PUT-001` | 更新结果成功 | 传合法 JSON 字符串 | HTTP `200` |
| `EXT-PUT-002` | 非法 JSON | 传坏 JSON | HTTP `400``code=INVALID_JSON` |
| `EXT-PUT-003` | 任务不存在 | 不存在 `taskId` | HTTP `404``code=NOT_FOUND` |
### 9.3 `POST /api/extraction/{taskId}/submit`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `EXT-SUBMIT-001` | 提交成功 | `IN_PROGRESS` 任务提交 | HTTP `200`,任务变为 `SUBMITTED` |
| `EXT-SUBMIT-002` | 非法状态提交 | `UNCLAIMED/REJECTED/APPROVED` 时提交 | HTTP `409``code=INVALID_STATE_TRANSITION` |
### 9.4 `POST /api/extraction/{taskId}/approve`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `EXT-APPROVE-001` | 审批通过成功 | Reviewer 审批 `SUBMITTED` 任务 | HTTP `200`,原任务 `APPROVED``isFinal=true` |
| `EXT-APPROVE-002` | 审批通过触发后续链路 | 审批完成后查数据库/接口 | 自动创建 `QA_GENERATION` 任务,`source_data.status=QA_REVIEW`,创建 `training_dataset` |
| `EXT-APPROVE-003` | 自审拦截 | 提交人与审批人相同 | HTTP `403``code=SELF_REVIEW_FORBIDDEN` |
| `EXT-APPROVE-004` | 非法状态审批 | 未提交任务直接审批 | HTTP `409``code=INVALID_STATE_TRANSITION` |
### 9.5 `POST /api/extraction/{taskId}/reject`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `EXT-REJECT-001` | 驳回成功 | `{"reason":"实体识别有误"}` | HTTP `200`,任务变为 `REJECTED` |
| `EXT-REJECT-002` | 驳回原因为空 | 空 body 或空 reason | HTTP `400``code=REASON_REQUIRED` |
| `EXT-REJECT-003` | 自审驳回 | 提交人与驳回人相同 | HTTP `403``code=SELF_REVIEW_FORBIDDEN` |
| `EXT-REJECT-004` | 驳回后可重领重提 | 驳回后原领取人调 `/reclaim``/submit` | 可重领,任务恢复到 `SUBMITTED` |
## 10. 问答生成
说明:本组接口复用 `G-AUTH-001~003``G-ROLE-001``G-TENANT-001`
### 10.1 `GET /api/qa/{taskId}`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `QA-GET-001` | 获取候选问答对成功 | 查询本公司 QA 任务 | HTTP `200`,返回 `taskId/sourceType/items` |
| `QA-GET-002` | 查询不存在任务 | 不存在 `taskId` | HTTP `404``code=NOT_FOUND` |
| `QA-GET-003` | 跨租户查询 | 用 `TESTB` 查询 `DEMO` 任务 | HTTP `404` 或不可见 |
### 10.2 `PUT /api/qa/{taskId}`
| 用例ID | 场景 | 请求体 | 预期结果 |
|---|---|---|---|
| `QA-PUT-001` | 更新候选问答对成功 | `{"items":[...]}` | HTTP `200` |
| `QA-PUT-002` | 非法 JSON | 传坏 JSON | HTTP `400``code=INVALID_JSON` |
| `QA-PUT-003` | 缺失 items 字段 | 传空对象 `{}` | 不应报 5xx若允许则生成空 conversations |
### 10.3 `POST /api/qa/{taskId}/submit`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `QA-SUBMIT-001` | 提交成功 | `IN_PROGRESS` 任务提交 | HTTP `200`,任务变为 `SUBMITTED` |
| `QA-SUBMIT-002` | 非法状态提交 | 对 `UNCLAIMED/REJECTED/APPROVED` 任务提交 | HTTP `409``code=INVALID_STATE_TRANSITION` |
### 10.4 `POST /api/qa/{taskId}/approve`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `QA-APPROVE-001` | QA 审批通过成功 | Reviewer 审批 `SUBMITTED` QA 任务 | HTTP `200` |
| `QA-APPROVE-002` | 审批通过完成整条流水线 | 审批完成后查状态 | `training_dataset.status=APPROVED`,任务 `APPROVED``source_data.status=APPROVED` |
| `QA-APPROVE-003` | 自审拦截 | 提交人与审批人相同 | HTTP `403``code=SELF_REVIEW_FORBIDDEN` |
### 10.5 `POST /api/qa/{taskId}/reject`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `QA-REJECT-001` | 驳回成功 | `{"reason":"问题描述不准确"}` | HTTP `200`,任务变为 `REJECTED` |
| `QA-REJECT-002` | 驳回原因为空 | 空 body 或空 reason | HTTP `400``code=REASON_REQUIRED` |
| `QA-REJECT-003` | 驳回后候选数据删除 | 驳回后检查 `training_dataset` | 候选问答对被删除,`source_data` 维持 `QA_REVIEW` |
| `QA-REJECT-004` | 驳回后可重领重提 | 原领取人调 `/reclaim``/submit` | 可重领,重新提交成功 |
## 11. 导出与微调
说明:本组接口复用 `G-AUTH-001~003``G-ROLE-001``G-TENANT-001`
### 11.1 `GET /api/training/samples`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `EXPORT-SAMPLE-001` | 查询已审批样本成功 | `?page=1&pageSize=20` | HTTP `200`,返回 `APPROVED` 样本 |
| `EXPORT-SAMPLE-002` | 仅看未导出样本 | `?exported=false` | 只返回 `export_batch_id` 为空的数据 |
| `EXPORT-SAMPLE-003` | 按样本类型过滤 | `?sampleType=TEXT` | 仅返回对应类型 |
| `EXPORT-SAMPLE-004` | 非管理员访问 | 用 `ANNOTATOR` 调用 | HTTP `403` |
### 11.2 `POST /api/export/batch`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `EXPORT-BATCH-001` | 创建导出批次成功 | `{"sampleIds":[<approvedId1>,<approvedId2>]}` | HTTP `201`,返回批次信息 |
| `EXPORT-BATCH-002` | 样本列表为空 | `{"sampleIds":[]}` | HTTP `400``code=EMPTY_SAMPLES` |
| `EXPORT-BATCH-003` | 包含未审批样本 | 混入 `PENDING_REVIEW/REJECTED` 样本 | HTTP `400``code=INVALID_SAMPLES` |
| `EXPORT-BATCH-004` | 包含他租户样本 | 混入其他公司样本 ID | HTTP `400``code=INVALID_SAMPLES` |
| `EXPORT-BATCH-005` | 批次创建后回写样本导出信息 | 创建成功后查询样本 | `exportBatchId/exportedAt` 已写入 |
### 11.3 `POST /api/export/{batchId}/finetune`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `FINETUNE-TRIGGER-001` | 首次触发微调成功 | 对 `NOT_STARTED` 批次调用 | HTTP `200`,返回 `glmJobId/finetuneStatus=RUNNING` |
| `FINETUNE-TRIGGER-002` | 重复触发微调 | 对已启动批次再次调用 | HTTP `409``code=FINETUNE_ALREADY_STARTED` |
| `FINETUNE-TRIGGER-003` | 触发不存在批次 | 不存在 `batchId` | HTTP `404``code=NOT_FOUND` |
### 11.4 `GET /api/export/{batchId}/status`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `FINETUNE-STATUS-001` | 未启动批次查询状态 | `glmJobId` 为空批次 | HTTP `200`,返回 `NOT_STARTED/progress=0` |
| `FINETUNE-STATUS-002` | 已启动批次查询状态 | 对已提交微调任务批次调用 | HTTP `200`,返回 `batchId/glmJobId/finetuneStatus/progress/errorMessage` |
| `FINETUNE-STATUS-003` | 跨租户状态隔离 | 用 `TESTB``DEMO` 批次 | HTTP `404` 或不可见 |
### 11.5 `GET /api/export/list`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `EXPORT-LIST-001` | 分页查询导出批次成功 | `?page=1&pageSize=20` | HTTP `200`,仅返回当前公司批次 |
| `EXPORT-LIST-002` | 跨租户隔离 | `TESTB` 调用 | 看不到 `DEMO` 批次 |
## 12. 系统配置
说明:本组接口复用 `G-AUTH-001~003``G-ROLE-001``G-TENANT-001`
### 12.1 `GET /api/config`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `CONFIG-LIST-001` | 查询合并配置成功 | 管理员查询 | HTTP `200`,返回当前公司可见配置列表 |
| `CONFIG-LIST-002` | 公司配置覆盖全局默认 | 先写公司专属 `model_default`,再查询 | 返回 `scope=COMPANY` 且值为公司专属值 |
| `CONFIG-LIST-003` | 未配置公司专属时回退全局 | 清除公司专属后查询 | 返回 `scope=GLOBAL` |
| `CONFIG-LIST-004` | 跨租户隔离 | `DEMO` 配置不影响 `TESTB` | 另一公司仍看到自己的配置或全局默认 |
### 12.2 `PUT /api/config/{key}`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `CONFIG-UPSERT-001` | 新增公司专属配置成功 | `{"value":"glm-4-plus","description":"默认模型"}` | HTTP `200`,创建成功 |
| `CONFIG-UPSERT-002` | 更新同键配置成功 | 同一个 `key` 连续两次 `PUT` | HTTP `200`,第二次为更新而不是重复插入 |
| `CONFIG-UPSERT-003` | 未知配置键 | `PUT /api/config/unknown_key` | HTTP `400``code=UNKNOWN_CONFIG_KEY` |
| `CONFIG-UPSERT-004` | 空配置值 | `{"value":""}` | HTTP `400``code=INVALID_CONFIG_VALUE` |
## 13. 视频处理
说明:
- `POST /api/video/callback` 为公开接口,不复用 `G-AUTH-001~003`
- 其余视频接口复用 `G-AUTH-001~003``G-ROLE-001``G-TENANT-001`
### 13.1 `POST /api/video/process`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `VIDEO-CREATE-001` | 创建视频处理任务成功 | `{"sourceId":<videoSourceId>,"jobType":"FRAME_EXTRACT","params":"{\"frameInterval\":30}"}` | 创建成功,返回 job资料状态进入 `PREPROCESSING` |
| `VIDEO-CREATE-002` | 使用另一种任务类型成功 | `jobType=VIDEO_TO_TEXT` | 创建成功 |
| `VIDEO-CREATE-003` | 缺少必要字段 | 缺 `sourceId``jobType` | 返回失败,`code=INVALID_PARAMS`,且不能创建 job |
| `VIDEO-CREATE-004` | 非法任务类型 | `jobType=UNKNOWN` | HTTP `400``code=INVALID_JOB_TYPE` |
| `VIDEO-CREATE-005` | 非视频资料创建视频任务 | 对非 `VIDEO` 资料调用 | 应返回明确失败,不应产生错误状态迁移 |
| `VIDEO-CREATE-006` | 跨租户创建视频任务 | 用 `TESTB` 处理 `DEMO` 资料 | HTTP `404` 或不可见 |
### 13.2 `GET /api/video/jobs/{jobId}`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `VIDEO-GET-001` | 查询视频任务成功 | 查询本公司 `jobId` | HTTP `200`,返回任务状态、重试次数、输出路径等 |
| `VIDEO-GET-002` | 查询不存在任务 | 不存在 `jobId` | HTTP `404``code=NOT_FOUND` |
| `VIDEO-GET-003` | 跨租户查询 | 用 `TESTB``DEMO` 任务 | HTTP `404` 或不可见 |
### 13.3 `POST /api/video/jobs/{jobId}/reset`
| 用例ID | 场景 | 操作 | 预期结果 |
|---|---|---|---|
| `VIDEO-RESET-001` | 重置失败任务成功 | 对 `FAILED` 任务调用 | HTTP `200`job 状态变为 `PENDING``retryCount=0` |
| `VIDEO-RESET-002` | 非失败任务不允许重置 | 对 `PENDING/RETRYING/SUCCESS` 调用 | HTTP `400``code=INVALID_TRANSITION` |
| `VIDEO-RESET-003` | 跨租户重置 | 用 `TESTB``DEMO` 任务 | HTTP `404` 或不可见 |
### 13.4 `POST /api/video/callback`
| 用例ID | 场景 | 请求 | 预期结果 |
|---|---|---|---|
| `VIDEO-CALLBACK-001` | SUCCESS 回调成功 | `{"jobId":123,"status":"SUCCESS","outputPath":"processed/frames.zip"}` | HTTP `200`job 变 `SUCCESS``source_data.status=PENDING` |
| `VIDEO-CALLBACK-002` | SUCCESS 回调幂等 | 对同一 `jobId` 连续两次发送相同 SUCCESS 回调 | 两次都成功,第二次不重复改状态、不重复产出副作用 |
| `VIDEO-CALLBACK-003` | FAILED 回调触发重试 | 对未达重试上限 job 发 `FAILED` | job 变 `RETRYING``retryCount+1` |
| `VIDEO-CALLBACK-004` | 超过最大重试次数 | `retryCount=maxRetries-1` 时再发 `FAILED` | job 变 `FAILED``source_data.status=PENDING` |
| `VIDEO-CALLBACK-005` | 回调 job 不存在 | `jobId` 不存在 | 接口不应打穿服务;应安全返回,无脏数据写入 |
| `VIDEO-CALLBACK-006` | 启用共享密钥时密钥错误 | 不传或传错 `X-Callback-Secret` | 返回失败,`code=UNAUTHORIZED` |
| `VIDEO-CALLBACK-007` | 回调缺少 `jobId/status` | 缺关键字段 | 返回明确失败,不应裸 `500` |
## 14. 推荐执行顺序
建议按以下顺序执行,能更快定位问题来源:
1. 认证管理
2. 公司管理
3. 用户管理
4. 资料管理
5. 任务管理
6. 提取标注
7. 问答生成
8. 导出与微调
9. 系统配置
10. 视频处理
## 15. 高风险回归点
每次版本回归至少覆盖以下高风险用例:
| 优先级 | 用例ID | 说明 |
|---|---|---|
| P0 | `AUTH-LOGIN-001` | 登录主链路 |
| P0 | `AUTH-LOGOUT-002` | 退出后 Token 立即失效 |
| P0 | `USER-STATUS-002` | 禁用账号后旧 Token 失效 |
| P0 | `SOURCE-DELETE-003` | 资料进入流水线后不可删除 |
| P0 | `TASK-CLAIM-002` | 并发抢任务只允许一个成功 |
| P0 | `EXT-APPROVE-002` | 提取审批通过自动推进 QA 链路 |
| P0 | `QA-APPROVE-002` | QA 审批通过完成整条流水线 |
| P0 | `EXPORT-BATCH-003` | 非 APPROVED 样本不可导出 |
| P0 | `CONFIG-LIST-004` | 配置跨租户隔离 |
| P0 | `VIDEO-CALLBACK-002` | 视频回调幂等 |
## 16. 后续落地建议
本文档适合作为三类产物的源:
- Swagger 手工测试清单
- Postman/Apifox/Newman 自动化用例集
- `src/test/java/com/label/blackbox` 下的 API 黑盒自动化测试
若需要继续落地,建议优先把以下用例自动化:
- 认证与 Token 生命周期
- 任务领取并发
- 提取/问答审批状态流转
- 导出样本校验
- 视频回调幂等与重试