diff --git a/apps/portal-shell/README.md b/apps/portal-shell/README.md index 53bb2f5..1966269 100644 --- a/apps/portal-shell/README.md +++ b/apps/portal-shell/README.md @@ -2,7 +2,7 @@ > 版本:2.0 > 日期:2026-07-17 -> 状态:已落地(v2.1 M8-M12 完成 + v1.1 数据抽象与 GraphQL 加固完成 + v2.0 shadcn 标准化 + 三层安全边界 + 流式渲染 + 三级错误处理完成 + P0 全部验证通过:typecheck 0 错误 / lint 0 错误 / build 6 路由生成成功) +> 状态:已落地(v2.1 M8-M12 完成 + v1.1 数据抽象与 GraphQL 加固完成 + v2.0 shadcn 标准化 + 三层安全边界 + 流式渲染 + 三级错误处理完成 + P0-P4 全部验证通过:typecheck 0 错误 / lint 0 错误 / build 6 路由生成成功 / 206 测试全部通过) > 架构范式:Modular Monolith + Micro-kernel(单 Next.js App Router 容器 + 插件化仪表盘) > 关联文档: > @@ -1146,52 +1146,58 @@ portal-shell 的关键架构决策记录在 004 文档的 ADR 章节,此处为 - [x] `pnpm run lint` + `pnpm run typecheck` 零错误(v2.0:含 2 个 auto-generated 文件警告,可忽略) - [x] `pnpm run build` 通过(v2.0:Next.js 16 Turbopack,6 路由生成成功:`/` / `/_not-found` / `/api/health` / `/api/log` / `/api/ready` / `/shell/[[...route]]`) -- [x] `pnpm run test` 95/95 通过(lib/api 7 domain 55 用例 + 安全栈 10 用例 + Shell/Lifecycle/Context 30 用例) +- [x] `pnpm run test` 206/206 通过(lib/api 7 domain 55 用例 + 安全栈 10 用例 + Shell/Lifecycle/Context 30 用例 + v2.0 新增组件单元测试 81 用例 + v2.0 E2E 测试 30 用例) - [x] 0 处 widget 内联 gql 字面量(强制,arch:scan 违规检测) - [x] 所有新代码遵守 shadcn 标准令牌(v2.0:ESLint 强制,无硬编码颜色/字体/任意值) +- [x] 31 个 widget 旧纸感令牌全部迁移到 shadcn 标准(P1:1104 次替换,arch:scan 零违规) - [x] apollo-router 启用 APQ + PQ Manifest + 深度/成本/批量限制 - [x] 50 个 GraphQL resolver @auth 审计完成(35 TS + 15 Python),19 个 TS resolver 补齐 @RequirePermission - [x] arch.db 更新,004 文档同步 - [x] **v2.0 README 同步**:本文件 v2.0,004 同步更新 ADR-044/045/046/047 - [x] **v2.0 流式渲染验证**:首屏 HTML 直出骨架,Config resolve 后流式注入(本地 Docker 验证) +- [x] **v2.0 P2 单元测试**:5 文件 81 用例(权限位图 24 + 路由权限 26 + notify 12 + useErrorReport 9 + PluginBoundary 10) +- [x] **v2.0 P3 生产端点**:api-gateway POST /api/v1/log + useErrorReport 环境感知端点切换 +- [x] **v2.0 P4 E2E 测试**:3 文件 30 用例(流式渲染 4 + 三级错误边界 6 + 三层安全边界 20) - [ ] Shell 首屏 LCP < 2s(需真实环境压测验证) - [ ] 插件加载耗时 < 500ms(dynamic import 缓存命中后,需真实环境验证) -- [ ] 单元测试覆盖率 ≥ 80%(当前覆盖核心纯函数 + lib/api 全量,admin domain 仅 4 用例待补,插件组件测试待补,v2.0 新增组件测试待补) -- [ ] E2E 测试(tests/e2e/portal-shell.spec.ts,待补,含 v2.0 流式渲染 + 三级错误边界场景) +- [ ] 单元测试覆盖率 ≥ 80%(当前覆盖核心纯函数 + lib/api 全量 + v2.0 新增组件全量,admin domain 仅 4 用例待补,插件组件渲染测试待补) +- [ ] E2E 测试(tests/e2e/ 真实浏览器场景,含 admin 改配置 → 用户刷新生效、apollo-router 深度限制) - [ ] 视觉回归测试(5 种 layout 截图,待补,含 v2.0 shadcn 标准化对比) - [ ] 生产部署前 APOLLO_REQUIRE_PQ_MANIFEST=true + APOLLO_ROUTER_INTROSPECTION=false 写入部署 env -- [ ] 31 个 widget 旧纸感令牌批量迁移到 shadcn 标准(P1 阶段,arch:scan 违规检测) ### 11.3 测试矩阵 -| 测试类型 | 范围 | 文件 | 状态 | -| -------- | --------------------------------------------- | ---------------------------------------------------------- | ----------------- | -| 单元测试 | PluginLifecycle 纯函数 | `src/shell/__tests__/PluginLifecycle.test.ts` | ✅ 12 用例 | -| 单元测试 | Registry 插件注册 | `src/shell/__tests__/Registry.test.ts` | ✅ 6 用例 | -| 单元测试 | plugin-context URL 上下文 | `src/lib/__tests__/plugin-context.test.ts` | ✅ 12 用例 | -| 单元测试 | lib/api universal domain | `src/lib/api/__tests__/universal.test.ts` | ✅ 6 用例 | -| 单元测试 | lib/api sidebar domain | `src/lib/api/__tests__/sidebar.test.tsx` | ✅ 9 用例 | -| 单元测试 | lib/api topbar domain | `src/lib/api/__tests__/topbar.test.tsx` | ✅ 9 用例 | -| 单元测试 | lib/api teacher domain | `src/lib/api/__tests__/teacher.test.tsx` | ✅ 7 用例 | -| 单元测试 | lib/api student domain | `src/lib/api/__tests__/student.test.tsx` | ✅ 9 用例 | -| 单元测试 | lib/api parent domain | `src/lib/api/__tests__/parent.test.tsx` | ✅ 11 用例 | -| 单元测试 | lib/api admin domain | `src/lib/api/__tests__/admin.test.tsx` | ✅ 4 用例(待补) | -| 单元测试 | PQ Manifest + APQ + 深度限制 | `src/lib/api/__tests__/security.test.ts` | ✅ 10 用例 | -| 单元测试 | 权限位图 base36 编解码(v2.0) | `packages/shared-ts/__tests__/permission-bitmap.test.ts` | 🚧 待补 | -| 单元测试 | 路由权限配置表 + checkRoutePermission(v2.0) | `src/shared/lib/__tests__/route-permissions.test.ts` | 🚧 待补 | -| 单元测试 | notify 统一封装(v2.0) | `src/shared/lib/__tests__/notify.test.ts` | 🚧 待补 | -| 单元测试 | useErrorReport 节流逻辑(v2.0) | `packages/hooks/__tests__/use-error-report.test.ts` | 🚧 待补 | -| 单元测试 | PluginBoundary 三件套(v2.0) | `src/shared/components/__tests__/plugin-boundary.test.tsx` | 🚧 待补 | -| 单元测试 | PropsMerger 三层合并 | 待补 | 🚧 | -| 单元测试 | 各插件组件渲染 | 待补 | 🚧 | -| E2E | 登录 → 加载 layout → 渲染插件 → 切换 layout | `tests/e2e/portal-shell.spec.ts` | ⏳ | -| E2E | admin 改配置 → 用户刷新生效 | `tests/e2e/plugin-config.spec.ts` | ⏳ | -| E2E | apollo-router 拒绝 11 层嵌套查询 | `tests/e2e/graphql-depth-limit.spec.ts` | ⏳ | -| E2E | apollo-router 拒绝未知 PQ hash | `tests/e2e/graphql-pq-manifest.spec.ts` | ⏳ | -| E2E | 流式渲染 + 三级错误边界(v2.0) | `tests/e2e/streaming-and-error-boundary.spec.ts` | ⏳(v2.0) | -| E2E | 三层安全边界 + 权限位图(v2.0) | `tests/e2e/security-boundary.spec.ts` | ⏳(v2.0) | -| 视觉回归 | 5 种 layout 截图对比 | `tests/visual/portal-shell.spec.ts` | ⏳ | -| 视觉回归 | shadcn 标准化对比(v2.0) | `tests/visual/shadcn-migration.spec.ts` | ⏳(v2.0) | +| 测试类型 | 范围 | 文件 | 状态 | +| -------- | --------------------------------------------------- | ---------------------------------------------------------- | ----------------- | +| 单元测试 | PluginLifecycle 纯函数 | `src/shell/__tests__/PluginLifecycle.test.ts` | ✅ 12 用例 | +| 单元测试 | Registry 插件注册 | `src/shell/__tests__/Registry.test.ts` | ✅ 6 用例 | +| 单元测试 | plugin-context URL 上下文 | `src/lib/__tests__/plugin-context.test.ts` | ✅ 12 用例 | +| 单元测试 | lib/api universal domain | `src/lib/api/__tests__/universal.test.ts` | ✅ 6 用例 | +| 单元测试 | lib/api sidebar domain | `src/lib/api/__tests__/sidebar.test.tsx` | ✅ 9 用例 | +| 单元测试 | lib/api topbar domain | `src/lib/api/__tests__/topbar.test.tsx` | ✅ 9 用例 | +| 单元测试 | lib/api teacher domain | `src/lib/api/__tests__/teacher.test.tsx` | ✅ 7 用例 | +| 单元测试 | lib/api student domain | `src/lib/api/__tests__/student.test.tsx` | ✅ 9 用例 | +| 单元测试 | lib/api parent domain | `src/lib/api/__tests__/parent.test.tsx` | ✅ 11 用例 | +| 单元测试 | lib/api admin domain | `src/lib/api/__tests__/admin.test.tsx` | ✅ 4 用例(待补) | +| 单元测试 | PQ Manifest + APQ + 深度限制 | `src/lib/api/__tests__/security.test.ts` | ✅ 10 用例 | +| 单元测试 | 权限位图 base36 编解码(v2.0) | `src/shared/lib/__tests__/permission-bitmap.test.ts` | ✅ 24 用例 | +| 单元测试 | 路由权限配置表 + checkRoutePermission(v2.0) | `src/shared/lib/__tests__/route-permissions.test.ts` | ✅ 26 用例 | +| 单元测试 | notify 统一封装(v2.0) | `src/shared/lib/__tests__/notify.test.ts` | ✅ 12 用例 | +| 单元测试 | useErrorReport 节流逻辑(v2.0) | `src/shared/lib/__tests__/use-error-report.test.ts` | ✅ 9 用例 | +| 单元测试 | PluginBoundary 三件套(v2.0) | `src/shared/components/__tests__/plugin-boundary.test.tsx` | ✅ 10 用例 | +| E2E | 流式渲染(React 19 use() + Suspense)(v2.0) | `src/__tests__/e2e/streaming.test.tsx` | ✅ 4 用例 | +| E2E | 三级错误边界层级(v2.0) | `src/__tests__/e2e/error-boundaries.test.tsx` | ✅ 6 用例 | +| E2E | 三层安全边界(L1 角色 + L2 权限 + L3 范围)(v2.0) | `src/__tests__/e2e/security-boundaries.test.ts` | ✅ 20 用例 | +| 单元测试 | PropsMerger 三层合并 | 待补 | 🚧 | +| 单元测试 | 各插件组件渲染 | 待补 | 🚧 | +| E2E | 登录 → 加载 layout → 渲染插件 → 切换 layout | `tests/e2e/portal-shell.spec.ts` | ⏳ | +| E2E | admin 改配置 → 用户刷新生效 | `tests/e2e/plugin-config.spec.ts` | ⏳ | +| E2E | apollo-router 拒绝 11 层嵌套查询 | `tests/e2e/graphql-depth-limit.spec.ts` | ⏳ | +| E2E | apollo-router 拒绝未知 PQ hash | `tests/e2e/graphql-pq-manifest.spec.ts` | ⏳ | +| E2E | 流式渲染 + 三级错误边界(v2.0) | `tests/e2e/streaming-and-error-boundary.spec.ts` | ⏳(v2.0) | +| E2E | 三层安全边界 + 权限位图(v2.0) | `tests/e2e/security-boundary.spec.ts` | ⏳(v2.0) | +| 视觉回归 | 5 种 layout 截图对比 | `tests/visual/portal-shell.spec.ts` | ⏳ | +| 视觉回归 | shadcn 标准化对比(v2.0) | `tests/visual/shadcn-migration.spec.ts` | ⏳(v2.0) | --- @@ -1248,10 +1254,10 @@ portal-shell 的关键架构决策记录在 004 文档的 ADR 章节,此处为 | v1.1 FU-1 | 4 个 TS 子图 AuthMiddleware 覆盖 /graphql 路径 | ⏳ Follow-up | | v1.1 FU-2 | Python 子图(data-ana/ai)补 @RequirePermission 基础设施 | ⏳ Follow-up | | v1.1 FU-3 | admin domain 测试用例补齐(当前仅 4 用例) | ⏳ Follow-up | -| **v2.0 P1** | **31 widget 旧纸感令牌批量迁移到 shadcn 标准** | ⏳ 规划 | -| **v2.0 P2** | **v2.0 新增组件单元测试补齐**(权限位图 / 路由权限 / notify / useErrorReport / PluginBoundary) | ⏳ 规划 | -| **v2.0 P3** | **错误上报端点生产替换**(后端 /api/v1/log) | ⏳ 规划 | -| **v2.0 P4** | **E2E 测试**(流式渲染 + 三级错误边界 + 三层安全边界) | ⏳ 规划 | +| **v2.0 P1** | **31 widget 旧纸感令牌批量迁移到 shadcn 标准** | ✅ 完成(2026-07-17) | +| **v2.0 P2** | **v2.0 新增组件单元测试补齐**(权限位图 / 路由权限 / notify / useErrorReport / PluginBoundary) | ✅ 完成(2026-07-17) | +| **v2.0 P3** | **错误上报端点生产替换**(后端 /api/v1/log) | ✅ 完成(2026-07-17) | +| **v2.0 P4** | **E2E 测试**(流式渲染 + 三级错误边界 + 三层安全边界) | ✅ 完成(2026-07-17) | | P5(二期) | 第三方插件上传 + iframe 沙箱 | ⏳ 规划 | | P6(二期) | 插件市场在线商店 | ⏳ 规划 | | P7(二期) | canvas 拖拽编辑器 | ⏳ 规划 | diff --git a/apps/portal-shell/src/__tests__/e2e/error-boundaries.test.tsx b/apps/portal-shell/src/__tests__/e2e/error-boundaries.test.tsx new file mode 100644 index 0000000..3e63f71 --- /dev/null +++ b/apps/portal-shell/src/__tests__/e2e/error-boundaries.test.tsx @@ -0,0 +1,153 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen, fireEvent } from "@testing-library/react"; +import type { ReactNode } from "react"; + +/** + * E2E 集成测试:三级错误边界 + * + * 模拟 portal-shell 三级错误边界层级: + * 1. Route 级(error.tsx)→ 捕获整个路由的渲染错误 + * 2. Section 级(DashboardSection)→ 捕获单个区块的错误 + * 3. Widget 级(PluginBoundary)→ 捕获单个插件的错误 + * + * 验证:低级错误不冒泡到高级边界,高级边界兜底未捕获的低级错误 + * + * 关联:portal-shell README v2.0 §5.4 三级错误处理 + */ + +// mock useErrorReport +const reportErrorMock = vi.fn(); +vi.mock("@edu/hooks", () => ({ + useErrorReport: () => reportErrorMock, +})); + +import { ErrorBoundary } from "@edu/ui-components"; +import { PluginBoundary } from "@/shared/components/plugin-boundary"; + +/** 制造抛错组件 */ +function ThrowOnRender({ message }: { message: string }): ReactNode { + throw new Error(message); +} + +function GoodComponent({ label }: { label: string }): ReactNode { + return
暂无审计日志
+暂无审计日志
) : (| 时间 | 用户 | 操作 | @@ -142,18 +142,20 @@ export default function AuditLogs(props: PluginProps): React.ReactElement {|||||||
|---|---|---|---|---|---|---|---|---|---|
| {log.timestamp} | -{log.userName} | -{log.action} | -+ | + {log.timestamp} + | +{log.userName} | +{log.action} | +{log.resource} {log.resourceId ? ` / ${log.resourceId}` : ""} | -{log.ip} | +{log.ip} |