# T00 — 系统基础设施与跨切面集成测试 > 类型: 系统级 | 前置条件: 后端 + 前端服务运行中 | 优先级: P0 > > 本文档覆盖角色测试计划(R01-R05)未涉及的跨切面关注点。 ## 1. 环境启动验证 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 1.1 | 后端启动 | `.\dev.ps1` 或 `cargo run -p erp-server` | 服务在 3000 端口启动,日志显示迁移自动执行 | ☐ | | 1.2 | 健康检查 | `curl http://localhost:3000/api/v1/health` | 返回 200,包含各模块状态 | ☐ | | 1.3 | 前端启动 | `cd apps/web && pnpm dev` | Vite 在 5174 端口启动,浏览器可访问 | ☐ | | 1.4 | OpenAPI 文档 | 浏览器打开 `http://localhost:3000/api/docs/openapi.json` | 返回完整 OpenAPI JSON | ☐ | | 1.5 | 数据库连接 | `psql -U postgres -h localhost -d erp -c "\dt"` | 显示所有表(30 基础 + 44 健康 + 3 AI) | ☐ | | 1.6 | Redis 连接 | 检查后端日志 | Redis 连接成功,无超时警告 | ☐ | | 1.7 | Ollama 可达 | `curl http://127.0.0.1:11434/api/tags` | 返回模型列表,包含 qwen3:4b | ☐ | ## 2. 多租户隔离验证 > **业务背景**: HMS 为 SaaS 平台,不同医疗机构(租户)的数据必须完全隔离。 ### 2.1 数据隔离 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 2.1.1 | 租户 A 查询 | 以 admin 登录(租户 A)→ 查询患者列表 | 只看到租户 A 的患者 | ☐ | | 2.1.2 | 跨租户 API | 用租户 A 的 token 直接请求租户 B 的患者 ID | 返回 404(不是 200+空数据) | ☐ | | 2.1.3 | 租户 ID 注入 | 检查后端日志中的 SQL 查询 | 所有 SELECT 都包含 `WHERE tenant_id = ?` | ☐ | | 2.1.4 | 新增数据隔离 | 创建患者 → 检查数据库 | 新记录的 `tenant_id` 自动注入为当前租户 | ☐ | ### 2.2 权限隔离 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 2.2.1 | 菜单隔离 | 不同租户的相同角色登录 | 菜单列表由 `menu_roles` 关联决定,可能不同 | ☐ | | 2.2.2 | 角色隔离 | 租户 A 的 doctor 无法访问租户 B 的数据 | API 返回空列表或 403 | ☐ | ## 3. 事件总线端到端 > **业务背景**: 模块间通过 EventBus 异步通信,事件必须可靠投递(Outbox 模式)。 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 3.1 | 患者创建事件 | 创建新患者 → 检查 `domain_events` 表 | 出现 `patient.created` 事件记录 | ☐ | | 3.2 | 事件消费 | 等待 5 秒 → 检查事件状态 | 事件被消费(状态 processed) | ☐ | | 3.3 | 随访完成事件 | 完成一条随访 → 检查 `domain_events` | 出现 `follow_up.completed` 或类似事件 | ☐ | | 3.4 | 死信队列 | 检查 dead-letter 存储 | 无消费失败的事件(或已知原因) | ☐ | | 3.5 | LISTEN/NOTIFY | 创建患者 → 检查 PostgreSQL NOTIFY 日志 | 通知已发出 | ☐ | ## 4. 权限码全量校验 > **业务背景**: 50 个声明权限码(health 39 + ai 6 + dialysis 5),前端 AuthButton 覆盖率仅 26%。已知 CRITICAL 问题:`health.alert.manage`(单数)vs `health.alerts.manage`(复数)。 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 4.1 | 权限码一致性 | 检查 `permissions` 表中的 50 个声明码 | 每个码格式为 `模块.实体.操作`(如 `health.patient.list`) | ☐ | | 4.2 | 告警权限码修复 | 登录有告警管理权限的角色 → 打开告警页面 | 告警管理按钮(确认/处理)正常显示 | ☐ | | 4.3 | AuthButton 覆盖 | 逐一检查各页面的操作按钮 | 新增/编辑/删除按钮使用 AuthButton 包裹,权限码匹配 | ☐ | | 4.4 | API 权限守卫 | 以无权限角色调用受保护 API | 返回 403,不是 500 | ☐ | | 4.5 | 菜单-权限关联 | 检查 `menu_roles` 表 | 每个角色关联的菜单与测试计划(R01-R05)一致 | ☐ | ## 5. 冻结模块路由拦截 > **业务背景**: 7 个模块已冻结(care_plan、shift 等),路由守卫应拦截访问。 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 5.1 | 冻结路由拦截 | 浏览器访问冻结模块路由(如 care_plan 相关路径) | 显示"模块已冻结"提示或重定向,不显示空白页 | ☐ | | 5.2 | 菜单不可见 | 检查左侧菜单 | 冻结模块不显示在菜单中 | ☐ | | 5.3 | API 拦截 | 调用冻结模块的 API | 返回明确错误(如 403 或 410),不是 500 | ☐ | ## 6. 并发冲突场景 > **业务背景**: 预约使用 CAS 乐观锁,排班满额时并发预约应被拒绝。 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 6.1 | 预约并发 | 同一时段两个请求同时预约 | 只有一个成功,另一个返回冲突错误 | ☐ | | 6.2 | 排班满额 | 预约数达到排班上限 → 再预约 | 返回"已满"错误,不超额 | ☐ | | 6.3 | 乐观锁冲突 | 两个请求同时编辑同一条患者 | 第二个请求返回版本冲突错误 | ☐ | | 6.4 | 软删除可见性 | 删除患者后 → 列表中不显示 | 列表排除 `deleted_at IS NOT NULL` 的记录 | ☐ | ## 7. PII 加密全链路 > **业务背景**: 患者敏感信息(姓名、身份证、手机号)使用 AES-256-GCM 加密存储,HMAC 盲索引支持搜索。 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 7.1 | 写入加密 | 创建患者 → 直接查询数据库 | 姓名/身份证/手机号字段为密文,非明文 | ☐ | | 7.2 | 读取解密 | 通过 API 查看患者详情 | 返回明文,可正常显示 | ☐ | | 7.3 | 盲索引搜索 | 按手机号搜索患者 | 搜索结果正确,命中目标患者 | ☐ | | 7.4 | 跨租户加密隔离 | 租户 A 的加密数据用租户 B 的密钥解密 | 解密失败或返回乱码,不泄漏明文 | ☐ | | 7.5 | HMAC 索引一致性 | 创建患者 → 检查 `blind_indexes` 表 | 对应字段有 HMAC 索引记录 | ☐ | ## 8. FHIR API 访问控制 > **业务背景**: 14 个公开 FHIR 路由需验证访问控制。 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 8.1 | FHIR 资源访问 | `GET /api/v1/fhir/Patient` | 返回 FHIR 格式的患者资源 | ☐ | | 8.2 | 无 token 访问 | 不带 Authorization 头访问 FHIR 端点 | 公开端点可访问 / 受保护端点返回 401 | ☐ | | 8.3 | 越权访问 | 用普通患者 token 访问其他患者的 FHIR 资源 | `allowed_patient_ids` 限制生效,返回 403 | ☐ | | 8.4 | FHIR 格式验证 | 检查返回的 JSON 结构 | 符合 FHIR R4 规范(`resourceType` 字段存在) | ☐ | ## 9. SSE / WebSocket 连通性 > **业务背景**: 消息中心使用 SSE 推送未读计数,AI 分析使用 SSE 流式返回结果。 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 9.1 | SSE 连接 | 浏览器打开 → 检查 Network 面板 SSE 连接 | `EventSource` 连接到 `/api/v1/messages/stream`,状态 200 | ☐ | | 9.2 | 消息推送 | 管理员发送消息 → 切换到另一个角色的浏览器 | 未读计数实时更新(SSE 推送) | ☐ | | 9.3 | SSE 断线重连 | 断开网络 → 恢复 | SSE 自动重连,消息不丢失 | ☐ | | 9.4 | AI SSE 分析 | 触发 AI 分析(需前端入口或直接 API 调用) | SSE 流式返回分析结果 | ☐ | ## 10. 错误恢复场景 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 10.1 | Token 过期刷新 | 登录后等待 token 接近过期(或手动修改 localStorage) | 前端自动刷新 token,无需重新登录 | ☐ | | 10.2 | 401 响应处理 | 后端返回 401 → 检查前端行为 | 跳转到登录页,不显示空白 | ☐ | | 10.3 | 403 响应处理 | 访问无权限页面 | 显示 403 提示或重定向,不显示空白 | ☐ | | 10.4 | 500 响应处理 | 触发后端错误(如发送异常数据) | 显示友好错误提示,不显示原始堆栈 | ☐ | | 10.5 | Redis 降级 | 停止 Redis → 发起 API 请求 | 限流降级为 fail-close(503),不是无限等待 | ☐ | | 10.6 | 数据库连接恢复 | 短暂断开数据库 → 恢复 | 连接池自动重建,后续请求正常 | ☐ | | 10.7 | 网络中断恢复 | 断开前端网络 → 恢复 | 页面恢复数据加载,SSE 重连 | ☐ | ## 11. 文件上传 > **业务背景**: 化验单和体检报告支持文件上传。 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 11.1 | 图片上传 | 患者详情 → 上传化验单图片 | 上传成功,图片可预览 | ☐ | | 11.2 | 文件大小限制 | 上传超大文件(>10MB) | 返回文件大小限制错误 | ☐ | | 11.3 | 文件类型限制 | 上传非允许类型文件(如 .exe) | 返回文件类型限制错误 | ☐ | | 11.4 | 图片预览 | 点击已上传的图片 | 全屏预览正常显示 | ☐ | ## 12. 安全边界 > **基于专家评审安全建议** | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 12.1 | SQL 注入 | 在搜索框输入 `' OR 1=1 --` | 不返回全量数据,搜索结果为空或正常过滤 | ☐ | | 12.2 | XSS 防护 | 在患者姓名中输入 `` | 存储后显示为转义文本,不执行脚本 | ☐ | | 12.3 | display_name XSS | 检查数据库中的 display_name 字段 | 不包含未转义的 HTML(P1 已知问题) | ☐ | | 12.4 | CORS 限制 | 从非白名单 Origin 发起 API 请求 | 被拒绝(生产环境 CORS 不含通配符) | ☐ | | 12.5 | JWT 伪造 | 使用篡改的 JWT 发起请求 | 返回 401 | ☐ | | 12.6 | 批量导出限制 | 尝试导出大量数据 | 有分页限制或超时保护 | ☐ | ## 13. 数据完整性 | # | 测试项 | 操作 | 预期结果 | 通过 | |---|--------|------|----------|------| | 13.1 | UUID v7 主键 | 检查任意表的主键格式 | 为 UUID v7 格式(时间排序) | ☐ | | 13.2 | 审计字段 | 创建任意记录 → 检查数据库 | `created_at`/`updated_at`/`created_by`/`updated_by` 自动填充 | ☐ | | 13.3 | 乐观锁版本 | 编辑记录两次 → 检查 `version` 字段 | version 递增 | ☐ | | 13.4 | 软删除 | 删除记录 → 直接查询数据库 | `deleted_at` 非空,记录仍存在 | ☐ | | 13.5 | 唯一约束 | 创建重复身份证号的患者 | 返回唯一约束错误 | ☐ | ## 测试结果 - 测试人: _________ - 测试日期: _________ - 通过数: ___ / 总数: ___ - 问题记录: