场次录像 API 测试报告¶
| 属性 | 值 |
|---|---|
| 文档类型 | 测试报告 |
| 测试对象 | 场次录像 API(POST https://record.pp-cdn.org/api/record/split,见 recorder-api.md) |
| 测试时间 | 2026-09-29(北京时间) |
| 测试方式 | 自动化集成测试(对生产端点)+ 按文档示例的手工请求校验 |
| 测试环境 | 生产:https://record.pp-cdn.org;专用测试 App(appId 已脱敏),simple 鉴权模式 |
| 结论 | 通过:文档描述的正确路径、鉴权、字段校验、状态码与静默 no-op 行为均与实现一致;因测试时段该 App 名下 table 无在线推流,成功路径(round-start → round-end 实际落盘)被跳过 |
1. 测试目标¶
按 recorder-api.md 逐条验证对外接口的行为是否符合文档约定:
- 服务地址与 HTTP 方法可用,返回统一的
{code,msg,data}信封; simple模式签名算法与时间容忍窗口;- 必填字段与非法字符的校验(HTTP 400);
- 签名错误 /
time过期(HTTP 401); - 「结束一个从未开始过的场次」为静默 no-op 成功(HTTP 200);
- 无在线流时的错误码语义(HTTP 500 +
code=50001)。
本报告只覆盖
simple模式。advance模式(HMAC-SHA256 + nonce)与每秒 500 次的限流(HTTP 429) 按文档说明需在部署侧显式开启、且对共享生产端点主动触发限流并不合适,故本次不覆盖。
2. 测试方法¶
- 自动化集成测试:对生产端点运行覆盖上述场景的测试用例,逐条断言 HTTP 状态码与信封
code。 - 手工请求校验:按文档 §7 的签名公式(
md5(appSecret + time + appId + tableId + [gameRound] + gameId)) 手工构造请求,核对实际返回正文,避免只依赖断言而看不到原始响应。
签名基线(与文档 §4.1 一致):
base = appSecret + time + appId + tableId
if gameRound: base += gameRound
base += gameId
token = md5(base).hexdigest()
3. 执行结果汇总¶
| 场景 | 期望 | 实测 | 结论 |
|---|---|---|---|
| 服务端点返回文档信封 | 2xx / 4xx / 5xx 且为 {code,msg,data} |
返回文档信封 | PASS |
| 场次开始(有在线流) | 200 | 无在线流 → 500 code=50001 |
SKIP |
| 场次结束(从未开始) | 200 静默 no-op | 200 {"code":200,"msg":"OK","data":null} |
PASS |
缺少 Authorization |
400 | 400 | PASS |
| 请求体非法 JSON | 400 | 400 | PASS |
缺少 tableId |
400 | 400 missing parameters. |
PASS |
tableId 含非法字符 |
400 | 400 ... contain invalid characters. |
PASS |
| 签名错误(错误 appSecret) | 401 | 401 invalid token! |
PASS |
time 超出容忍窗口 |
401 | 401 token expired! |
PASS |
无在线流的 tableId |
500 code=50001 |
500 code=50001 |
PASS |
| 冲突 owner 的 round-start | 500 | 无在线流,前置条件不成立 | SKIP |
| 完整 round-start → round-end | 200 → 200 | 无在线流,前置条件不成立 | SKIP |
合计:10 项执行,8 PASS,3 SKIP,0 FAIL。
4. 关键请求 / 响应实录¶
以下为按文档 §7 手工构造的请求的实际响应(time 为请求构造时刻的 unix 秒,Authorization 为
按上式算出的 32 位 md5):
A. 结束一个从未开始过的场次(预期静默 no-op 成功)
POST /api/record/split
Authorization: <md5>
{"time":"1759130000","appId":"app_***","tableId":"table","gameId":"itest-never-xxx","gameRound":"gc-itest-xxx"}
HTTP 200 {"code":200,"msg":"OK","data":null}
B. 场次开始但该 table 当前无在线流(预期 500 + 专用 code)
HTTP 500 {"code":50001,"msg":"stream for app \"app_***\" table \"table\" is not online","data":""}
C. 签名错误(预期 401)
HTTP 401 {"code":401,"msg":"invalid token!","data":""}
D. 缺少 tableId(预期 400)
HTTP 400 {"code":400,"msg":"missing parameters.","data":""}
E. tableId 含非法字符 table/../etc(预期 400)
HTTP 400 {"code":400,"msg":"appId/tableId/gameId/gameRound/appEnv contain invalid characters.","data":""}
F. time 取 1 小时前(超出 simple 模式 ±30s 窗口,预期 401)
HTTP 401 {"code":401,"msg":"token expired!","data":""}
实测确认:文档 §5 的两点关键约定成立——(1) 冲突/业务性拒绝与「无在线流」都使用 HTTP 500, 二者只能靠信封
code(50001vs 其它)或msg区分,不能只看 HTTP 状态码; (2) 结束一个从未开始过的场次是无条件成功,不会因为没有匹配的 round-start 而报错。
5. 未通过 / 跳过说明¶
以下用例的前置条件在测试时段不成立,因此跳过,不代表接口缺陷:
- 场次开始 / 完整 round-start → round-end:
tableId=table在当前时刻没有任何 view 在线推流, round-start 按文档预期返回code=50001。待该 table 实际开始推流后即可重跑成功路径。 - 冲突 owner 的 round-start:需要先有一个成功的 round-start 持有 table,前置条件不成立。
其余所有不依赖在线流的场景均已覆盖并通过。
6. 结论¶
recorder-api.md描述的服务地址、两阶段协议、simple鉴权、字段校验、状态码与 no-op 语义 与实际部署一致,本次重测全部通过。- 接入方在实现时请务必以信封
code/msg(而非 HTTP 状态码)区分「无在线流」 (50001)与其它业务性拒绝,并注意「开始后不要立即结束」的分段时长限制(文档 §8)。
7. 修订记录¶
| 日期 | 修订 | 说明 |
|---|---|---|
| 2026-09-29 | 初稿 | 对生产端点重测;成功路径因无在线流跳过,其余全部通过 |