traceability-p94-p95-c26.spec.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. /**
  2. * 需求↔SC↔TC 定向三类流执行证据(2026-11-10,execute 档)
  3. * ================================================================
  4. * 对应补写用例:TC-P-94*、TC-P-95*(tests/test-cases/parent.md「成就与成长历史专项」)
  5. * TC-C-26*、TC-A-27-S*(child.md / admin.md 睡眠配置专项)
  6. * 主表:tests/TRACEABILITY-MATRIX.md
  7. *
  8. * 运行:
  9. * cd d:/cfc/tests && npx playwright test e2e/api/traceability-p94-p95-c26.spec.js \
  10. * -c playwright.config.api.js --project=desktop
  11. *
  12. * 环境事实(2026-11-10 实测):
  13. * - 远程 http://cfc.iwintrue.com 仅部署后端 API,未托管前端 SPA;
  14. * 故 SC-P-96(五列布局)等纯 UI 断言无法在此规格执行,标「未实测」。
  15. * - 账号由 /api/auth/direct-register 自注册,不依赖任何硬编码凭据;
  16. * 全程不打印 token / 手机号,不写业务数据(仅只读查询类 POST)。
  17. * - 接口实际契约已探活确认,断言均基于实测可判定行为,未臆造字段。
  18. */
  19. const { test, expect } = require('@playwright/test');
  20. const { registerRole, apiPost } = require('../helpers/api');
  21. let ctx;
  22. let memberId;
  23. test.beforeAll(async ({ request }) => {
  24. ctx = await registerRole(request, 'parent', 'QA-TRACE');
  25. const r = await apiPost(request, '/api/family/member/list', {}, ctx.token);
  26. expect(r.code, '成员列表应可用').toBe(200);
  27. expect(Array.isArray(r.data) && r.data.length > 0, '应至少有一个家庭成员').toBeTruthy();
  28. memberId = r.data[0].memberId;
  29. });
  30. // ---------------------------------------------------------------
  31. // SC-P-94 · 我的成就「完成任务」→ 历史完成任务页面
  32. // 接口:POST /api/tasks/history(MyBatis-Plus Page)
  33. // ---------------------------------------------------------------
  34. test.describe('SC-P-94 历史完成任务三类流', () => {
  35. test('TC-P-94 正常流:分页结构完整且仅返回已完成记录', async ({ request }) => {
  36. const r = await apiPost(request, '/api/tasks/history', { memberId, page: 1, size: 10 }, ctx.token);
  37. expect(r.code).toBe(200);
  38. expect(r.data, '应返回分页对象').toBeTruthy();
  39. expect(Array.isArray(r.data.records), 'records 应为数组').toBeTruthy();
  40. expect(r.data).toHaveProperty('total');
  41. expect(r.data).toHaveProperty('pages');
  42. expect(r.data).toHaveProperty('current');
  43. // 新注册账号无历史任务,空态为合法结果(对应文档用例「暂无已完成任务」)
  44. expect(r.data.total).toBeGreaterThanOrEqual(r.data.records.length);
  45. });
  46. test('TC-P-94-E 异常流:非法分页/非法 category 不 5xx 且结构稳定', async ({ request }) => {
  47. const bad = [
  48. { memberId, page: -1, size: 10 },
  49. { memberId, page: 999999, size: 10 },
  50. { memberId, page: 1, size: 10, category: '__INVALID__' },
  51. ];
  52. for (const body of bad) {
  53. const r = await apiPost(request, '/api/tasks/history', body, ctx.token);
  54. expect(r.code, `非法参数 ${JSON.stringify(body)} 不应返回 5xx`).toBe(200);
  55. expect(Array.isArray(r.data.records), '分页结构应完整').toBeTruthy();
  56. }
  57. // 实测:实现未对非法分页/category 返回 400,而是归一处理返回空页。
  58. // 文档用例允许「400 或归一到首页」,故按「不 5xx + 结构稳定」判定通过;
  59. // 参数校验强度是否需加强 → 标「需核实」,见主矩阵 G-7。
  60. });
  61. test('TC-P-94-P 权限流:传非本家庭 memberId 不返回他人任务数据', async ({ request }) => {
  62. const r = await apiPost(request, '/api/tasks/history', { memberId: 999999999, page: 1, size: 10 }, ctx.token);
  63. expect([200, 403, 401]).toContain(r.code);
  64. if (r.code === 200) {
  65. expect(r.data.records, '不得返回非本家庭任务').toHaveLength(0);
  66. }
  67. // 实测:实现按当前登录态过滤,返回 200 + 空列表(未返回 403)。
  68. // 断言按「不泄露他人数据」落地;「必须 403」契约需在造跨家庭夹具后复测 → 主矩阵 G-7。
  69. });
  70. });
  71. // ---------------------------------------------------------------
  72. // SC-P-95 · 我的成就「成长记录」→ 成长历史记录页
  73. // 接口:POST /api/health/checkin/list(今日成长打卡记录流)
  74. // ---------------------------------------------------------------
  75. test.describe('SC-P-95 成长历史记录三类流', () => {
  76. test('TC-P-95 正常流:打卡记录流返回可遍历数组', async ({ request }) => {
  77. const r = await apiPost(request, '/api/health/checkin/list', { memberId, page: 1, size: 10 }, ctx.token);
  78. expect(r.code).toBe(200);
  79. expect(Array.isArray(r.data), '打卡记录流应为数组(空数组为合法空态)').toBeTruthy();
  80. });
  81. test('TC-P-95-E 异常流:非法筛选与非法分页不 5xx', async ({ request }) => {
  82. const bad = [
  83. { memberId, page: -1, size: 10 },
  84. { memberId, page: 1, size: 10, type: '__INVALID__' },
  85. { memberId, startTime: '2099-01-01', endTime: '2020-01-01' },
  86. ];
  87. for (const body of bad) {
  88. const r = await apiPost(request, '/api/health/checkin/list', body, ctx.token);
  89. expect(r.code, `非法参数 ${JSON.stringify(body)} 不应返回 5xx`).toBe(200);
  90. expect(Array.isArray(r.data)).toBeTruthy();
  91. }
  92. });
  93. test('TC-P-95-P 权限流:非本家庭 memberId 不返回他人打卡记录', async ({ request }) => {
  94. const r = await apiPost(request, '/api/health/checkin/list', { memberId: 999999999, page: 1, size: 10 }, ctx.token);
  95. expect([200, 403, 401]).toContain(r.code);
  96. if (r.code === 200) {
  97. expect(r.data, '不得返回非本家庭打卡记录').toHaveLength(0);
  98. }
  99. });
  100. });
  101. // ---------------------------------------------------------------
  102. // 契约缺口(真实探活结果,登记为 fixme,不伪造通过)
  103. // ---------------------------------------------------------------
  104. test.describe('契约缺口待修复(2026-11-10 实测)', () => {
  105. test.fixme('TC-P-95:/api/growth/record/list 带/不带 memberId 均返回 code=500「服务器内部错误」,成长档案记录聚合口径未落地', async () => {});
  106. test.fixme('TC-C-26:睡眠选项配置读取契约未落地 —— /api/health/sleep/tags 返回 404;/api/config/public/value 带 key=sleep.quality.tags 返回 code=500', async () => {});
  107. test.fixme('TC-A-27-S:后台维护契约未落地 —— /api/admin/sleep/tags 返回 404;需确认是否改由 /api/admin/config/* 通用配置承载', async () => {});
  108. test.fixme('SC-P-96 / 前端页面类断言:远程环境未托管前端 SPA,纯 UI 与页面跳转断言需本地/真机环境执行', async () => {});
  109. });