Files
hms/docs/qa/T00-system-integration.md
iven df1d85bfde docs: T40 UI 审计报告 + wiki 更新 + Docker 配置
- T40 UI 审计计划和结果文档(docs/qa/)
- wiki 更新:miniprogram 设计系统合规审计记录 + index 关键数字更新
- 审计 V2 完整报告(docs/audits/v2/)
- 讨论记录文档(docs/discussions/)
- 设计规格和实施计划(docs/superpowers/)
- 角色测试计划和结果(docs/qa/role-test-*)
- Docker 生产部署配置
2026-05-13 23:29:42 +08:00

170 lines
10 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.
# 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-close503不是无限等待 | ☐ |
| 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 防护 | 在患者姓名中输入 `<script>alert(1)</script>` | 存储后显示为转义文本,不执行脚本 | ☐ |
| 12.3 | display_name XSS | 检查数据库中的 display_name 字段 | 不包含未转义的 HTMLP1 已知问题) | ☐ |
| 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 | 唯一约束 | 创建重复身份证号的患者 | 返回唯一约束错误 | ☐ |
## 测试结果
- 测试人: _________
- 测试日期: _________
- 通过数: ___ / 总数: ___
- 问题记录: