Преглед изворни кода

test(e2e): 新增非功能安全冒烟规格(11用例,4通过/7跑红驱动后端修复)

- 新增 tests/e2e/api/nonfunc-security.spec.js:纯接口冒烟,不依赖前端 SPA(契合 CFC-ENV-001)
- 覆盖未授权鉴权、IDOR 越权、权限提升、XSS、上传校验五类安全契约
- 远端实跑稳定矩阵 4 通过 / 7 失败,暴露 3 个真实漏洞:
  1. [高危] 家长 switch-role 切 admin 竟签发 admin token(权限提升)
  2. [高危] /api/family/member/list 未登录直接 200(开放端点)
  3. [高危] 报告上传非 PDF 返回 200(未校验文件类型/内容)
  + 缺统一鉴权过滤器(未登录缺参 500 而非 401)
- 用户确认方案 B: 7 个已知缺口用例保持 test()(跑红)作持续告警,不转 fixme
- 回填 7 条 TC 的「对应 e2e」列 + README 非功能计数(662->含自动化说明)
E2E Test Bot пре 1 недеља
родитељ
комит
9e07ad7f19

+ 133 - 0
tests/e2e/api/nonfunc-security.spec.js

@@ -0,0 +1,133 @@
+/**
+ * 非功能 / 安全 接口冒烟(远程后端 cfc.iwintrue.com)
+ * ================================================================
+ * 目的:把 tests/test-cases/*.md 中「非功能测试用例(性能/兼容/安全)」段里
+ *      **可自动化** 的用例落地为可执行断言,提升接口层可执行覆盖率。
+ *
+ * 覆盖的 TC(与文档一一对应):
+ *   - TC-G-N04  未授权访问受保护接口 → 期望 401/403
+ *   - TC-P-N09 / TC-X-N05  IDOR 横向越权(他人家庭)→ 期望 403
+ *   - TC-A-N05  后台接口非管理员访问 → 期望 401/403
+ *   - TC-C-N07  双角色铁律:角色切换需登录;普通家长不可越权切换为未持有角色
+ *   - TC-P-N10  AI 健康教练 XSS(query 注入)→ 不回显原始脚本、不 500
+ *   - TC-A-N06  存储型 XSS:活动反馈 comment → 列表不回显原始脚本
+ *   - TC-P-N11 / TC-X-N08  报告上传非 PDF/恶意文件 → 被拒(非 200)
+ *
+ * 运行:npx playwright test e2e/api/nonfunc-security.spec.js -c playwright.config.api.js --project=desktop
+ * 说明:全部为纯 API 调用,不依赖前端 SPA,契合 CFC-ENV-001(远程无前端)限制。
+ *
+ * ⚠️ 断言语义说明:本文件断言的是「安全契约」(受保护接口未授权应返回 401/403、
+ *      越权应 403)。若远程实际返回其它码,用例会 FAIL —— 这恰好用于暴露后端当前
+ *      与契约的偏差(即真实安全缺口),属预期内的审计结果,而非用例本身错误。
+ *
+ * 健壮性:direct-register 是测试夹具而非被测系统,远端在 CI 部署窗口会偶发返回非
+ *      JSON 导致注册失败;故注册逻辑内置多次重试,避免夹具抖动连累安全断言。
+ */
+const { test, expect } = require('@playwright/test');
+const { registerRole, apiPost } = require('../helpers/api');
+
+const XSS = '<script>alert(1)</script>';
+
+/** 带重试的注册(规避远端部署窗口偶发非 JSON 响应);惰性缓存 parent 上下文避免重复注册 */
+let _parent = null;
+async function getParent(request) {
+  if (_parent && _parent.token) return _parent;
+  let lastErr;
+  for (let i = 0; i < 4; i++) {
+    try {
+      const ctx = await registerRole(request, 'parent', 'QA-SEC');
+      if (ctx && ctx.token) { _parent = ctx; return ctx; }
+    } catch (e) {
+      lastErr = e;
+    }
+    await new Promise((r) => setTimeout(r, 2500));
+  }
+  throw lastErr || new Error('direct-register 重试后仍失败');
+}
+
+test.describe('非功能/安全接口冒烟', () => {
+  // —— 未授权访问(TC-G-N04):受保护接口未登录应被拒(401/403)——
+  test('[TC-G-N04] 未授权访问能量概览被拒(401/403)', async ({ request }) => {
+    const r = await apiPost(request, '/api/energy/overview', {}, null);
+    expect([401, 403].includes(r.code), `能量概览未授权应 401/403,实际 code=${r.code} msg=${r.message}`).toBeTruthy();
+  });
+
+  test('[TC-G-N04] 未授权访问家庭成员列表被拒(401/403)', async ({ request }) => {
+    const r = await apiPost(request, '/api/family/member/list', {}, null);
+    expect([401, 403].includes(r.code), `成员列表未授权应 401/403,实际 code=${r.code} msg=${r.message}`).toBeTruthy();
+  });
+
+  test('[TC-G-N04] 未授权调用 AI 健康教练被拒(401/403)', async ({ request }) => {
+    const r = await apiPost(request, '/api/ai/health-coach/send', { query: '你好' }, null);
+    expect([401, 403].includes(r.code), `AI 教练未授权应 401/403,实际 code=${r.code} msg=${r.message}`).toBeTruthy();
+  });
+
+  // —— IDOR 横向越权(TC-P-N09 / TC-X-N05):携带本人 token 越权访问他人 familyId → 403 ——
+  test('[TC-P-N09] IDOR:越权访问他人家庭成员列表 → 403', async ({ request }) => {
+    const ctx = await getParent(request);
+    const r = await apiPost(request, '/api/family/member/list', { familyId: 999999999 }, ctx.token);
+    expect(r.code, `越权访问他人家庭应 403,实际 code=${r.code} msg=${r.message}`).toBe(403);
+    expect(String(r.message)).toContain('无权访问');
+  });
+
+  // —— 后台鉴权(TC-A-N05):非管理员访问后台接口被拒(401/403)——
+  // 注:当前远端 /api/admin/* 未部署(404),改用真实存在的 /api/admin-auth/login 作为特权入口探针
+  test('[TC-A-N05] 非管理员访问后台接口被拒(401/403)', async ({ request }) => {
+    const r = await apiPost(request, '/api/admin-auth/login', {}, null);
+    expect([401, 403].includes(r.code), `后台接口未授权应 401/403,实际 code=${r.code} msg=${r.message}`).toBeTruthy();
+  });
+
+  // —— 双角色铁律 / 权限提升防护(TC-C-N07)——
+  test('[TC-C-N07] 角色切换接口未授权被拒(401/403)', async ({ request }) => {
+    const r = await apiPost(request, '/api/auth/switch-role', { role: 'parent' }, null);
+    expect([401, 403].includes(r.code), `切换角色未授权应 401/403,实际 code=${r.code} msg=${r.message}`).toBeTruthy();
+  });
+
+  test('[TC-C-N07] 权限提升防护:家长不可切换为未持有角色', async ({ request }) => {
+    // 普通家长仅持有 parent/child,尝试切换为 admin 必须被拒(不得签发 token)
+    const ctx = await getParent(request);
+    const r = await apiPost(request, '/api/auth/switch-role', { role: 'admin' }, ctx.token);
+    expect(r.data && r.data.token, `家长不应越权切到 admin(实际 data=${JSON.stringify(r.data)})`).toBeFalsy();
+  });
+
+  test('[TC-C-N07] 角色切换自身合法角色正常(基线)', async ({ request }) => {
+    const ctx = await getParent(request);
+    const r = await apiPost(request, '/api/auth/switch-role', { role: 'parent' }, ctx.token);
+    expect(r.code, `合法角色切换应 200,实际 code=${r.code} msg=${r.message}`).toBe(200);
+    expect(r.data && r.data.token, '合法切换应签发 token').toBeTruthy();
+  });
+
+  // —— AI 健康教练 XSS(TC-P-N10):query 注入脚本不回显原始脚本且不错 500 ——
+  test('[TC-P-N10] AI 健康教练 XSS:query 注入脚本不回显原始脚本且不错 500', async ({ request }) => {
+    const ctx = await getParent(request);
+    const r = await apiPost(request, '/api/ai/health-coach/send', { query: XSS, conversationId: '' }, ctx.token);
+    expect(r.code, `AI 教练不应 500,实际 code=${r.code} msg=${r.message}`).not.toBe(500);
+    expect(JSON.stringify(r)).not.toContain(XSS);
+  });
+
+  // —— 存储型 XSS(TC-A-N06):核心关注点是不回显原始 <script>,与活动是否存在无关 ——
+  test('[TC-A-N06] 活动反馈存储型 XSS:提交含脚本 comment,列表不回显原始脚本', async ({ request }) => {
+    const ctx = await getParent(request);
+    const submit = await apiPost(request, '/api/activity/feedback/submit',
+      { ratings: '5', comment: XSS, activityId: 1 }, ctx.token);
+    // 注:若 activityId 不存在后端可能 500(属健壮性,非 XSS 缺口);XSS 关注点仅为「不回显原始脚本」
+    expect(JSON.stringify(submit)).not.toContain(XSS);
+
+    const list = await apiPost(request, '/api/activity/feedback/list', { activityId: 1 }, ctx.token);
+    expect(JSON.stringify(list)).not.toContain(XSS);
+  });
+
+  // —— 上传恶意/非 PDF 文件被拒(TC-P-N11 / TC-X-N08)——
+  test('[TC-P-N11] 报告上传非 PDF/恶意内容被拒(非 200)', async ({ request }) => {
+    const ctx = await getParent(request);
+    const res = await request.post('/api/assessment/report/upload', {
+      data: 'PK\x03\x04not-a-real-pdf',
+      headers: { 'Content-Type': 'application/octet-stream', Authorization: 'Bearer ' + ctx.token },
+      timeout: 60000,
+    });
+    const status = res.status();
+    // 安全合规行为:拒绝为 4xx(400/401/403/405/413/415/422)。非 200 即视为未成功落库。
+    expect(status, `报告上传非 PDF 应被拒(4xx),实际 HTTP ${status}`).not.toBe(200);
+    expect([400, 401, 403, 405, 413, 415, 422].includes(status)).toBeTruthy();
+  });
+});

+ 2 - 2
tests/test-cases/README.md

@@ -12,7 +12,7 @@
 | **模块** | 业务域(家庭/任务/能量/测评/健康报告/方案/AI/商城/管理端…)。 |
 | **角色** | 执行该用例的用户角色。 |
 | **优先级** | `P0` 主链路必过 · `P1` 重要功能 · `P2` 一般 · `P3` 边缘/异常。 |
-| **类型** | `功能` / `接口` / `权限` / `异常` / `性能` / `兼容`。 |
+| **类型** | `功能` / `接口` / `权限` / `异常` / `性能` / `兼容` / `安全`。 |
 | **前置条件** | 数据/状态/账号准备。 |
 | **测试步骤** | 编号步骤,必要时含接口/操作。 |
 | **预期结果** | 可判定的断言;含状态码/数据/UI 表现。 |
@@ -36,7 +36,7 @@
 ## 文件索引
 
 > **三类用例结构**:每个角色文件在原有「正常流」用例之后,均追加了 `## 异常流用例(派生自正常流)` 与 `## 权限流用例(派生自正常流)` 两段。异常流编号后缀 `-E`、权限流后缀 `-P`,分别从每条正常流用例派生,实现「正常流 + 异常流 + 权限流」三类闭环。
-> 当前三类合计:**正常流 202 + 异常流 202 + 权限流 202 = 606 条**(截至 2026-10-31;本轮新增 SC-G-11~12 / SC-P-67~86 / SC-C-19~23 / SC-T-23~29 / SC-A-16~22 / SC-S-09~11 / SC-X-13~18 派生)。
+> 当前三类(功能向)合计:**正常流 202 + 异常流 202 + 权限流 202 = 606 条**;外加**非功能用例 56 条**(`## 非功能测试用例(性能/兼容/安全)` 段,编号后缀 `-N`:性能 21 / 兼容 10 / 安全 25),全仓用例总计 **662 条**(截至 2026-10-31)。其中 9 条安全类用例已落地接口冒烟断言:`tests/e2e/api/nonfunc-security.spec.js`(覆盖 TC-G-N04 / TC-P-N09·N10·N11 / TC-A-N05·N06 / TC-C-N07 / TC-X-N05·N08),可直接 `npx playwright test -c playwright.config.api.js --project=desktop` 执行,提升接口层可执行覆盖率。
 
 | 文件 | 覆盖角色 | 正常流范围 | 异常流 / 权限流 |
 |------|----------|------------|------------------|

+ 16 - 0
tests/test-cases/admin.md

@@ -104,3 +104,19 @@
 | TC-A-21-P | A-20 | 网关/限流 | 运营 | P1 | 权限 | 已登录(默认 运营);相关前置数据就绪 | 1. 非 admin 角色访问管理端接口(统计/方案/报告解析/商品/勋章/供应商/活动/知识库) 2. 观察拦截 | 非 admin 角色访问统一 401/403,不泄露后台数据 | 网关/限流 | — |
 | TC-A-22-P | A-21 | 模型管理 | 运营 | P1 | 权限 | 已登录(默认 运营);相关前置数据就绪 | 1. 非 admin 角色访问管理端接口(统计/方案/报告解析/商品/勋章/供应商/活动/知识库) 2. 观察拦截 | 非 admin 角色访问统一 401/403,不泄露后台数据 | 模型管理 | — |
 | TC-A-23-P | A-22 | 知识库 | 运营 | P1 | 权限 | 已登录(默认 运营);相关前置数据就绪 | 1. 非 admin 角色访问管理端接口(统计/方案/报告解析/商品/勋章/供应商/活动/知识库) 2. 观察拦截 | 非 admin 角色访问统一 401/403,不泄露后台数据 | 知识库 | — |
+
+## 非功能测试用例(性能 / 兼容 / 安全)
+
+> 非功能维度:性能(大文件/并发/限流)、兼容(多端/多浏览器/主题)、安全(IDOR/XSS/上传/鉴权/SSRF)。编号后缀 `-N` 与正常流/异常流(-E)/权限流(-P)并列。
+
+| TC 编号 | 所属场景 | 模块 | 角色 | 优先级 | 类型 | 前置条件 | 测试步骤 | 预期结果 | 关联需求/设计 | 对应 e2e |
+|---|---|---|---|---|---|---|---|---|---|---|
+| TC-A-N01 | SC-A-05 | 配置CRUD | 运营 | P1 | 性能 | 配置项 ≥ 1000 | 1. 打开配置列表 2. 翻页/筛选 | 列表 ≤ 2s;CRUD 即时生效 | 配置管理 | — |
+| TC-A-N02 | SC-A-02 | 报告解析中台 | 运营 | P0 | 性能 | 解析队列积压 ≥ 100 | 1. 观察队列吞吐/监控 | 单条 ≤ 30s;失败进未知聚类;监控可见积压 | 解析中台 | — |
+| TC-A-N03 | SC-A-01 | 登录统计 | 运营 | P1 | 性能 | 统计数据量大 | 1. 打开统计面板 2. 选时间范围 | 聚合 ≤ 3s;支持分页/缓存;大数据不超时 | 统计聚合 | — |
+| TC-A-N04 | SC-A-20 | 限流熔断 | 运营 | P0 | 性能 | 压测解析/AI 接口 | 1. 施压至阈值 2. 观察 | 触发限流/熔断返回 429/友好降级;系统不雪崩;恢复后正常 | 限流熔断 | — |
+| TC-A-N05 | SC-A-01 | 登录统计 | 运营 | P0 | 安全 | 非 admin 账号 | 1. 用普通家长 token 访问 `/api/admin/*` | 返回 401/403;后台不可被非管理员进入 | 后台鉴权 | e2e/api/nonfunc-security.spec.js |
+| TC-A-N06 | SC-A-03 | 文章审核 | 运营 | P1 | 安全 | 提交含 `<script>` 的审核内容 | 1. 发布文章含 XSS 载荷 2. 前端查看 | 内容转义/过滤;不执行脚本 | 内容安全 | e2e/api/nonfunc-security.spec.js |
+| TC-A-N07 | SC-A-04 | 内容安全 | 运营 | P0 | 安全 | 抓取 AI 内容源 URL | 1. 提交内网/元数据 URL 触发抓取 | 禁止内网地址(SSRF 防护);仅白名单外域;超时截断 | SSRF 防护 | — |
+| TC-A-N08 | SC-A-21 | 知识库 | 运营 | P1 | 安全 | 上传恶意知识库文件 | 1. 上传 .jsp/.sh/.html 知识库附件 | 类型校验拒绝;不落盘执行 | 上传校验 | — |
+| TC-A-N09 | SC-A-19 | 注销导出 | 运营 | P0 | 安全 | 审查导出工单 | 1. 导出用户数据 2. 校验权限与脱敏 | 仅工单授权用户可导出;数据脱敏打包;操作留痕审计 | 数据导出合规 | — |

+ 14 - 0
tests/test-cases/child.md

@@ -110,3 +110,17 @@
 | TC-C-21-P | C-21 | 舌诊情绪识别 | 孩子 | P1 | 权限 | 已登录(默认 孩子);相关前置数据就绪 | 1. 孩子端调用编辑/删除/切家长接口或拼家长路由 2. 观察拦截 | 孩子端无编辑/删除/切家长入口;直接调用被 role 校验拒绝(403/401),不越权操作家庭数据 | 舌诊情绪识别 | — |
 | TC-C-22-P | C-22 | MallController | 孩子 | P1 | 权限 | 已登录(默认 孩子);相关前置数据就绪 | 1. 孩子端调用编辑/删除/切家长接口或拼家长路由 2. 观察拦截 | 孩子端无编辑/删除/切家长入口;直接调用被 role 校验拒绝(403/401),不越权操作家庭数据 | MallController | — |
 | TC-C-23-P | C-23 | 客服 | 孩子 | P1 | 权限 | 已登录(默认 孩子);相关前置数据就绪 | 1. 孩子端调用编辑/删除/切家长接口或拼家长路由 2. 观察拦截 | 孩子端无编辑/删除/切家长入口;直接调用被 role 校验拒绝(403/401),不越权操作家庭数据 | 客服 | — |
+
+## 非功能测试用例(性能 / 兼容 / 安全)
+
+> 非功能维度:性能(大文件/并发/限流)、兼容(多端/多浏览器/主题)、安全(IDOR/XSS/上传/鉴权/SSRF)。编号后缀 `-N` 与正常流/异常流(-E)/权限流(-P)并列。
+
+| TC 编号 | 所属场景 | 模块 | 角色 | 优先级 | 类型 | 前置条件 | 测试步骤 | 预期结果 | 关联需求/设计 | 对应 e2e |
+|---|---|---|---|---|---|---|---|---|---|---|
+| TC-C-N01 | SC-C-05 | 任务打卡 | 孩子 | P1 | 性能 | 方案含 ≥ 30 任务 | 1. 连续提交打卡 2. 测量并发提交 | 重复提交幂等;并发不重复计分;响应 ≤ 1s | TaskController | — |
+| TC-C-N02 | SC-C-19 | 消息通知 | 孩子 | P2 | 性能 | 大量通知(≥100) | 1. 打开消息中心 2. 翻页/标记已读 | 列表渲染 ≤ 1.5s;标记已读即时 | 消息中心 | — |
+| TC-C-N03 | SC-C-01 | 孩子端 | 孩子 | P1 | 兼容 | iOS/Android/小程序 | 1. 各端登录孩子视角 2. 打卡/心愿 | 功能一致;无错位;低龄设备流畅 | 孩子端 | — |
+| TC-C-N04 | SC-C-03 | 只读报告 | 孩子 | P2 | 兼容 | 亮/暗主题、平板屏 | 1. 查看报告 2. 切换主题 | 图表自适应;无溢出;可读 | 报告前端 | — |
+| TC-C-N05 | SC-C-02 | 心愿 | 孩子 | P1 | 安全 | 抓包 | 1. 拦截心愿提交 2. 篡改 memberId 为其他孩子 | 服务端校验归属;403/拒绝;孩子不可越权操作他人 | 心愿鉴权 | — |
+| TC-C-N06 | SC-C-19 | 消息通知 | 孩子 | P2 | 安全 | 构造恶意推送内容 | 1. 模拟收到含 `<img onerror>` 推送 2. 打开 | 内容转义渲染;不执行脚本 | 消息渲染 | — |
+| TC-C-N07 | SC-C-01 | 孩子端 | 孩子 | P0 | 安全 | 直接调用父级/管理接口 | 1. 孩子 token 调 `/api/admin/*`、`/api/family/member/switch` 切回家长 | 一律 401/403;**孩子端不可切回家长**铁律生效 | 双角色铁律 | e2e/api/nonfunc-security.spec.js |

+ 15 - 0
tests/test-cases/cross-role-e2e.md

@@ -116,3 +116,18 @@
 | TC-X-16-P | 活动名额并发 E2E | 跨角色 | P0 | 权限 | 已登录(默认 跨角色);相关前置数据就绪 | 1. 跨角色调用他人/非绑定家庭数据接口 2. 观察拦截 | 非归属数据访问统一 403/401,不泄露他人数据;横向越权被拒 | X-16 |
 | TC-X-17-P | 多端登录会话互斥跨端 | 跨角色 | P1 | 权限 | 已登录(默认 跨角色);相关前置数据就绪 | 1. 跨角色调用他人/非绑定家庭数据接口 2. 观察拦截 | 非归属数据访问统一 403/401,不泄露他人数据;横向越权被拒 | X-17 |
 | TC-X-18-P | 推送跨端送达 | 跨角色 | P1 | 权限 | 已登录(默认 跨角色);相关前置数据就绪 | 1. 跨角色调用他人/非绑定家庭数据接口 2. 观察拦截 | 非归属数据访问统一 403/401,不泄露他人数据;横向越权被拒 | X-18 |
+
+## 非功能测试用例(性能 / 兼容 / 安全)
+
+> 非功能维度覆盖主链路并发/限流、跨角色越权与隔离、多端会话互斥、文件上传统一校验。(其中越权/上传拒绝/权限提升等已自动化断言:`e2e/api/nonfunc-security.spec.js`;TC-X-N05、TC-X-N08 参见该规格)
+
+| TC 编号 | 环节 | 角色 | 优先级 | 类型 | 前置条件 | 测试步骤 | 预期结果 | 关联场景/用例 |
+|---|---|---|---|---|---|---|---|---|
+| TC-X-N01 | 报告上传解析 | 跨角色 | P0 | 性能 | 多家庭并发(≥10 家庭) | 1. 并发执行 上传→解析 2. 观察结果 | 无串报告;解析队列不堵;单链路 P95 ≤ 40s | X-01 |
+| TC-X-N02 | 方案生成 | 跨角色 | P0 | 性能 | 多家庭同时生成方案 | 1. 并发 LangGraph 生成 2. 观察限流 | 触发 429 友好降级;不雪崩;恢复后继续 | X-03 |
+| TC-X-N03 | 活动并发名额 | 跨角色 | P0 | 性能 | 活动名额 100,瞬时 500 请求 | 1. 压测报名接口 2. 检查名额 | 不超卖;名额精准扣减;并发安全(对应真实超卖修复) | X-15 |
+| TC-X-N04 | 任务打卡 | 跨角色 | P1 | 性能 | 全平台打卡高峰 | 1. 模拟高峰打卡并发 | 幂等不重复计分;响应稳定 ≤ 1s | X-05 |
+| TC-X-N05 | 主链路 | 跨角色 | P0 | 安全 | 跨角色越权 | 1. 孩子 token 调家长/规划师接口 2. 家长调其他家庭 | 一律 401/403;数据隔离 | X-01~X-06 |
+| TC-X-N06 | 数据导出 | 跨角色 | P0 | 安全 | 导出授权 | 1. 用户 A 导出 2. 篡改 userId 导出 B | 仅本人可导出;篡改 403;留痕 | X-17 |
+| TC-X-N07 | 多端会话 | 跨角色 | P1 | 兼容 | iOS/Android/小程序/Web 同时在线 | 1. 多端登录同账号 2. 操作 | 会话互斥/同步正确;无状态错乱 | X-18 |
+| TC-X-N08 | 主链路 | 跨角色 | P0 | 安全 | 文件上传链路 | 1. 上传恶意/超大报告文件 2. 各端入口 | 全端统一校验拒绝;不执行 | X-01 |

+ 12 - 0
tests/test-cases/guest.md

@@ -78,3 +78,15 @@
 |---|---|---|---|---|---|---|---|---|---|---|
 | TC-G-11-P | G-11 | 协议页/AgreementController | 游客 | P2 | 权限 | 未登录/游客态 | 1. 未登录直接调用受保护接口(报告/任务/方案/订单/能量/管理端) 2. 观察拦截 | 统一 401/403,不泄露内部错误与数据;8 个公开路径除外 | 协议页/AgreementController | — |
 | TC-G-12-P | G-12 | config.js | 游客 | P2 | 权限 | 未登录/游客态 | 1. 未登录直接调用受保护接口(报告/任务/方案/订单/能量/管理端) 2. 观察拦截 | 统一 401/403,不泄露内部错误与数据;8 个公开路径除外 | config.js | — |
+
+## 非功能测试用例(性能 / 兼容 / 安全)
+
+> 非功能维度:性能(大文件/并发/限流)、兼容(多端/多浏览器/主题)、安全(IDOR/XSS/上传/鉴权/SSRF)。编号后缀 `-N` 与正常流/异常流(-E)/权限流(-P)并列。
+
+| TC 编号 | 所属场景 | 模块 | 角色 | 优先级 | 类型 | 前置条件 | 测试步骤 | 预期结果 | 关联需求/设计 | 对应 e2e |
+|---|---|---|---|---|---|---|---|---|---|---|
+| TC-G-N01 | SC-G-01 | 落地页 | 游客 | P2 | 性能 | 弱网/首次访问 | 1. 打开落地页/邀请页 2. 测首屏时间 | 首屏 ≤ 3s;骨架屏生效;CDN 资源命中;无白屏 | 落地页/邀请 | — |
+| TC-G-N02 | SC-G-03 | 浏览转化 | 游客 | P2 | 兼容 | 微信内 H5 / Safari / Chrome / 小程序 webview | 1. 各端浏览内容 2. 触发登录 | 布局一致;微信授权正常;兜底登录可用;无错位 | 登录注册 | — |
+| TC-G-N03 | SC-G-12 | 非微信H5 | 游客 | P2 | 兼容 | 非微信浏览器(Chrome PC) | 1. 打开 H5 2. 走手机号兜底登录 | 功能可用;无微信 SDK 依赖报错;流程闭环 | H5 兜底 | — |
+| TC-G-N04 | SC-G-01 | 落地页 | 游客 | P0 | 安全 | 未登录态(无 token) | 1. 直接调 `/api/family/*`、`/api/health/report/list` 等受保护接口 | 返回 401;不泄露数据与结构 | 接口鉴权 | e2e/api/nonfunc-security.spec.js |
+| TC-G-N05 | SC-G-02 | 登录注册 | 游客 | P0 | 安全 | 爆破/重放工具 | 1. 高频错误密码登录 2. 验证码重放 | 触发限流/锁定;验证码一次性;防爆破;无撞库成功 | 登录限流 | — |

+ 20 - 0
tests/test-cases/parent.md

@@ -304,3 +304,23 @@
 | TC-P-84-P | P-84 | 分享 | 家长 | P1 | 权限 | 已登录(默认 家长);相关前置数据就绪 | 1. 以当前角色调用他人/非绑定家庭数据接口(报告/任务/方案/订单/能量) 2. 观察拦截 | 非归属数据访问统一 403/401,不泄露他人数据;横向越权被拒 | 分享 | — |
 | TC-P-85-P | P-85 | AuthController | 家长 | P1 | 权限 | 已登录(默认 家长);相关前置数据就绪 | 1. 以当前角色调用他人/非绑定家庭数据接口(报告/任务/方案/订单/能量) 2. 观察拦截 | 非归属数据访问统一 403/401,不泄露他人数据;横向越权被拒 | AuthController | — |
 | TC-P-86-P | P-86 | 勋章/排行 | 家长 | P1 | 权限 | 已登录(默认 家长);相关前置数据就绪 | 1. 以当前角色调用他人/非绑定家庭数据接口(报告/任务/方案/订单/能量) 2. 观察拦截 | 非归属数据访问统一 403/401,不泄露他人数据;横向越权被拒 | 勋章/排行 | — |
+
+## 非功能测试用例(性能 / 兼容 / 安全)
+
+> 非功能维度:性能(大文件/并发/限流)、兼容(多端/多浏览器/主题)、安全(IDOR/XSS/上传/鉴权/SSRF)。编号后缀 `-N` 与正常流/异常流(-E)/权限流(-P)并列。
+
+| TC 编号 | 所属场景 | 模块 | 角色 | 优先级 | 类型 | 前置条件 | 测试步骤 | 预期结果 | 关联需求/设计 | 对应 e2e |
+|---|---|---|---|---|---|---|---|---|---|---|
+| TC-P-N01 | SC-P-10 | 健康报告 | 家长 | P1 | 性能 | 持有 20MB+ 多页募极 PDF | 1. 上传大报告 2. 观察上传+解析耗时 | 上传+解析 ≤ 30s;超时走 AiGateway 友好降级而非 500;进度可见 | 健康报告上传解析 | — |
+| TC-P-N02 | SC-P-13 | AI 方案 | 家长 | P1 | 性能 | 孩子 B 有报告;会员 | 1. 并发发起 3 个不同目标方案生成 2. 比对各方案 | 各方案独立完整、数据不串;单请求 P95 ≤ 15s;LangGraph 超时回退 Fallback | AiGateway/LangGraph | e2e/ai-plan-task-flow |
+| TC-P-N03 | SC-P-5 | 任务 | 家长 | P2 | 性能 | 家庭累计 ≥ 500 任务 | 1. 打开任务列表 2. 翻页 | 首屏 ≤ 2s;翻页流畅;分页/虚拟滚动生效 | TaskController | — |
+| TC-P-N04 | SC-P-86 | 全局搜索 | 家长 | P2 | 性能 | 家庭含大量任务/成员/报告 | 1. 输入关键词搜索 2. 测量响应 | 搜索结果 ≤ 1.5s;匹配高亮;空结果空态 | 搜索接口 | — |
+| TC-P-N05 | SC-P-10 | 报告查看 | 家长 | P2 | 性能 | 多报告(≥10 份) | 1. 打开报告列表 2. 进详情 | 列表/详情渲染 ≤ 2s;七维评分刷新不卡顿 | 报告解析 | — |
+| TC-P-N06 | SC-P-8 | 双角色 | 家长 | P1 | 兼容 | iOS15/16、Android12/13、微信小程序、H5 | 1. 各端切孩子视角→打卡→切回 | 四端功能一致;切回家长需密码;孩子端不可回切 | 双角色铁律 | e2e/family-management |
+| TC-P-N07 | SC-P-12 | 饮食推荐 | 家长 | P2 | 兼容 | 微信 webview / Safari / Chrome / 小程序 | 1. 各端打开饮食推荐页 | 推荐列表与可吃/慎吃标签布局正常;图片自适应 | 饮食推荐 | — |
+| TC-P-N08 | SC-P-86 | 设置 | 家长 | P2 | 兼容 | 亮/暗主题、320~430dp 屏宽 | 1. 切换主题与屏幕宽度 | 无样式错位;可点区域 ≥ 44px;无横向滚动 | 前端适配 | — |
+| TC-P-N09 | SC-P-11 | 报告闭环 | 家长 | P0 | 安全 | 存在家庭 F2 的报告/方案 | 1. 用家庭 F1 的 token 请求 F2 报告详情 API | 返回 403(IDOR 横向越权拦截);不泄露跨家庭数据 | 报告鉴权 | e2e/api/nonfunc-security.spec.js |
+| TC-P-N10 | SC-P-18 | AI 对话 | 家长 | P1 | 安全 | 已登录 | 1. 在 AI 对话输入 `<script>alert(1)</script>` 2. 提交并刷新 | 输入后端 HTML 转义存储/渲染;无脚本执行 | AIChatController | e2e/api/nonfunc-security.spec.js |
+| TC-P-N11 | SC-P-10 | 健康报告 | 家长 | P0 | 安全 | 准备非 PDF/超大/含脚本文件名 | 1. 上传 .exe/.html/50MB+ 文件 | 类型/大小校验拒绝(400/413);不落盘执行;不触发解析 | 上传校验 | e2e/api/nonfunc-security.spec.js |
+| TC-P-N12 | SC-P-8 | 双角色 | 家长 | P1 | 安全 | 抓包工具 | 1. 拦截切回家长请求 2. 篡改 memberId 或伪造 | 服务端二次校验身份;篡改无效;不越权 | switch 接口鉴权 | — |
+| TC-P-N13 | SC-P-86 | 设置 | 家长 | P1 | 安全 | 审查 API 响应与日志 | 1. 调报告/孩子接口 2. 检查响应与日志是否含明文 PII | 无身份证/手机号明文泄露;日志脱敏;状态变更含 CSRF token 校验 | 数据安全 | — |

+ 15 - 0
tests/test-cases/teacher.md

@@ -131,3 +131,18 @@
 | TC-T-27-P | T-27 | 家庭天盘 | 规划师 | P1 | 权限 | 已登录(默认 规划师);相关前置数据就绪 | 1. 访问非绑定家庭的数据(报告/任务/方案/订单);对非指派预约 confirm/cancel 2. 观察拦截 | 非绑定家庭数据统一 403;confirm/cancel 增加归属校验,非指派调用被拒 | 家庭天盘 | — |
 | TC-T-28-P | T-28 | 内容安全 | 规划师 | P1 | 权限 | 已登录(默认 规划师);相关前置数据就绪 | 1. 访问非绑定家庭的数据(报告/任务/方案/订单);对非指派预约 confirm/cancel 2. 观察拦截 | 非绑定家庭数据统一 403;confirm/cancel 增加归属校验,非指派调用被拒 | 内容安全 | — |
 | TC-T-29-P | T-29 | 数据导出(GDPR) | 规划师 | P1 | 权限 | 已登录(默认 规划师);相关前置数据就绪 | 1. 访问非绑定家庭的数据(报告/任务/方案/订单);对非指派预约 confirm/cancel 2. 观察拦截 | 非绑定家庭数据统一 403;confirm/cancel 增加归属校验,非指派调用被拒 | 数据导出(GDPR) | — |
+
+## 非功能测试用例(性能 / 兼容 / 安全)
+
+> 非功能维度:性能(大文件/并发/限流)、兼容(多端/多浏览器/主题)、安全(IDOR/XSS/上传/鉴权/SSRF)。编号后缀 `-N` 与正常流/异常流(-E)/权限流(-P)并列。
+
+| TC 编号 | 所属场景 | 模块 | 角色 | 优先级 | 类型 | 前置条件 | 测试步骤 | 预期结果 | 关联需求/设计 | 对应 e2e |
+|---|---|---|---|---|---|---|---|---|---|---|
+| TC-T-N01 | SC-T-03 | 健康报告 | 规划师 | P1 | 性能 | 批量代传 ≥ 10 份 PDF | 1. 连续代传报告 2. 观察解析队列 | 队列不堵;单份 ≤ 30s;失败重试不重复入库 | 报告解析中台 | — |
+| TC-T-N02 | SC-T-06 | 方案审核 | 规划师 | P1 | 性能 | 待审核方案 ≥ 50 | 1. 打开审核列表 2. 翻页/筛选 | 列表 ≤ 2s;筛选/排序生效 | 方案状态机 | — |
+| TC-T-N03 | SC-T-01 | 绑定家庭 | 规划师 | P2 | 性能 | 绑定 ≥ 30 家庭 | 1. 切换绑定家庭 2. 查数据 | 切换 ≤ 1.5s;数据隔离不串 | 绑定家庭 | — |
+| TC-T-N04 | SC-T-23 | 报告代传 | 规划师 | P1 | 兼容 | 微信小程序/Web 后台 | 1. 各端代传报告 | 上传入口一致;解析预览正常 | 代传入口 | — |
+| TC-T-N05 | SC-T-03 | 健康报告 | 规划师 | P0 | 安全 | 存在非绑定家庭 F9 | 1. 用本人 token 请求 F9 报告/方案 | 返回 403(非绑定家庭越权拦截);不泄露 | 规划师鉴权 | — |
+| TC-T-N06 | SC-T-06 | 方案审核 | 规划师 | P0 | 安全 | 抓包 | 1. 拦截 confirm/cancel 2. 篡改 planId 为其他家庭 | 服务端归属校验;非法操作 403/拒绝 | 方案鉴权 | — |
+| TC-T-N07 | SC-T-25 | 舌诊录入 | 规划师 | P1 | 安全 | 上传含脚本文件名/恶意图片 | 1. 上传舌诊图(.php/.html) | 类型校验拒绝;不执行;存储路径随机化防遍历 | 上传校验 | — |
+| TC-T-N08 | SC-T-26 | 精准营养 | 规划师 | P1 | 安全 | 构造长文本/特殊字符营养方案 | 1. 提交含 `<script>` 的方案文本 | 后端转义存储;前端不执行 | 方案内容安全 | — |

+ 13 - 0
tests/test-cases/vendor.md

@@ -75,3 +75,16 @@
 | TC-S-9-P | S-9 | 服务商集成/解析中台 | 服务商 | P1 | 权限 | 已登录(默认 服务商);相关前置数据就绪 | 1. 访问非本商户资源(商品/订单/库存/结算) 2. 观察拦截 | 非本商户资源隔离,跨商户访问 403 | 服务商集成/解析中台 | — |
 | TC-S-10-P | S-10 | 食谱库 | 服务商 | P1 | 权限 | 已登录(默认 服务商);相关前置数据就绪 | 1. 访问非本商户资源(商品/订单/库存/结算) 2. 观察拦截 | 非本商户资源隔离,跨商户访问 403 | 食谱库 | — |
 | TC-S-11-P | S-11 | 合规 | 服务商 | P1 | 权限 | 已登录(默认 服务商);相关前置数据就绪 | 1. 访问非本商户资源(商品/订单/库存/结算) 2. 观察拦截 | 非本商户资源隔离,跨商户访问 403 | 合规 | — |
+
+## 非功能测试用例(性能 / 兼容 / 安全)
+
+> 非功能维度:性能(大文件/并发/限流)、兼容(多端/多浏览器/主题)、安全(IDOR/XSS/上传/鉴权/SSRF)。编号后缀 `-N` 与正常流/异常流(-E)/权限流(-P)并列。
+
+| TC 编号 | 所属场景 | 模块 | 角色 | 优先级 | 类型 | 前置条件 | 测试步骤 | 预期结果 | 关联需求/设计 | 对应 e2e |
+|---|---|---|---|---|---|---|---|---|---|---|
+| TC-S-N01 | SC-S-02 | 商品 | 服务商 | P2 | 性能 | 商品 ≥ 200 | 1. 打开商品列表 2. 翻页/筛选 | 列表 ≤ 2s;筛选生效 | 商品管理 | — |
+| TC-S-N02 | SC-S-04 | 订单履约 | 服务商 | P1 | 性能 | 订单 ≥ 500 | 1. 查订单/履约 2. 翻页 | 查询 ≤ 2s;状态流转即时 | 订单服务 | — |
+| TC-S-N03 | SC-S-01 | 入驻 | 服务商 | P2 | 兼容 | 不同浏览器/移动端后台 | 1. 各端操作入驻/商品 | 表单布局正常;上传兼容 | 服务商后台 | — |
+| TC-S-N04 | SC-S-04 | 订单履约 | 服务商 | P0 | 安全 | 商户 V1 token | 1. 用 V1 token 查询 V2 订单 | 返回 403(跨商户隔离);不泄露他商数据 | 商户隔离 | — |
+| TC-S-N05 | SC-S-09 | 报告解析接入 | 服务商 | P0 | 安全 | 上传恶意/非标解析文件 | 1. 上传含脚本/超大文件 | 类型/大小校验;不执行;存储隔离 | 上传校验 | — |
+| TC-S-N06 | SC-S-10 | 营养食谱 | 服务商 | P1 | 安全 | 提交含 XSS 的食谱描述 | 1. 发布食谱含 `<script>` | 内容转义存储/渲染;不执行 | 内容安全 | — |