Files
erp/docs/superpowers/plans/2026-04-17-platform-maturity-q4-plan.md
iven 2bd274b39a docs: 添加 Q4 测试覆盖 + 插件生态实施计划
15 个 Task 覆盖:Testcontainers 集成测试框架、Playwright E2E、
进销存插件(6实体/6页面)、插件热更新、Wiki 文档更新
2026-04-17 17:30:29 +08:00

707 lines
19 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.

# Q4 测试覆盖 + 插件生态 实施计划
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 建立 Testcontainers 集成测试框架覆盖核心模块Playwright E2E 覆盖关键用户旅程;开发进销存插件验证插件系统扩展性;实现插件热更新能力。
**Architecture:** Testcontainers 启动真实 PostgreSQL 容器运行迁移后执行集成测试Playwright 驱动浏览器完成端到端验证;进销存插件复用 CRM 插件的 manifest + dynamic_table 模式;热更新通过版本对比 + 增量 DDL + 两阶段提交实现。
**Tech Stack:** Rust (testcontainers, testcontainers-modules), Playwright, WASM (wit-bindgen), SeaORM Migration
**Spec:** `docs/superpowers/specs/2026-04-17-platform-maturity-roadmap-design.md` §4
---
## File Structure
| 操作 | 文件 | 职责 |
|------|------|------|
| Create | `crates/erp-server/tests/integration/mod.rs` | 集成测试入口 |
| Create | `crates/erp-server/tests/integration/test_db.rs` | Testcontainers 测试基座 |
| Create | `crates/erp-server/tests/integration/auth_tests.rs` | Auth 模块集成测试 |
| Create | `crates/erp-server/tests/integration/plugin_tests.rs` | Plugin 模块集成测试 |
| Create | `crates/erp-server/tests/integration/workflow_tests.rs` | Workflow 模块集成测试 |
| Create | `crates/erp-server/tests/integration/event_tests.rs` | EventBus 端到端测试 |
| Create | `apps/web/e2e/login.spec.ts` | 登录流程 E2E |
| Create | `apps/web/e2e/users.spec.ts` | 用户管理 E2E |
| Create | `apps/web/e2e/plugins.spec.ts` | 插件安装 E2E |
| Create | `apps/web/e2e/tenant-isolation.spec.ts` | 多租户隔离 E2E |
| Create | `apps/web/playwright.config.ts` | Playwright 配置 |
| Create | `crates/erp-plugin-inventory/` | 进销存插件 crate |
| Modify | `Cargo.toml` | workspace 添加新 crate |
| Modify | `crates/erp-plugin/src/engine.rs` | 热更新 upgrade 端点支持 |
| Modify | `crates/erp-plugin/src/service.rs` | upgrade 生命周期 |
| Modify | `crates/erp-plugin/src/handler/plugin_handler.rs` | upgrade 路由 |
---
## Chunk 1: 集成测试框架
### Task 1: 添加 Testcontainers 依赖
**Files:**
- Modify: `crates/erp-server/Cargo.toml`dev-dependencies
- [ ] **Step 1: 在 `erp-server` 的 `[dev-dependencies]` 中添加**
```toml
[dev-dependencies]
testcontainers = "0.23"
testcontainers-modules = { version = "0.11", features = ["postgres"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
```
注意:版本号需与 workspace 已有依赖兼容。如果 workspace 已有 `testcontainers`,使用 workspace 引用。
- [ ] **Step 2: 验证编译**
```bash
cargo check -p erp-server
```
- [ ] **Step 3: Commit**
```bash
git add crates/erp-server/Cargo.toml Cargo.lock
git commit -m "chore(server): 添加 testcontainers 开发依赖"
```
---
### Task 2: 创建测试基座
**Files:**
- Create: `crates/erp-server/tests/integration/mod.rs`
- Create: `crates/erp-server/tests/integration/test_db.rs`
- [ ] **Step 1: 创建测试模块入口**
```rust
// crates/erp-server/tests/integration/mod.rs
mod test_db;
mod auth_tests;
mod plugin_tests;
```
注意:需要确保 `erp-server``Cargo.toml` 中有 `[[test]]` 配置或集成测试自动发现。
- [ ] **Step 2: 创建 Testcontainers 测试基座**
```rust
// crates/erp-server/tests/integration/test_db.rs
use testcontainers_modules::postgres::Postgres;
use testcontainers::runners::AsyncRunner;
use sea_orm::{Database, DatabaseConnection};
use std::sync::Arc;
/// 测试数据库容器 — 使用 once_cell 确保每进程一个容器
pub struct TestDb {
pub db: DatabaseConnection,
pub container: testcontainers::ContainerAsync<Postgres>,
}
impl TestDb {
pub async fn new() -> Self {
let postgres = Postgres::default()
.with_db_name("erp_test")
.with_user("test")
.with_password("test");
let container = postgres.start().await
.expect("Failed to start PostgreSQL container");
let host_port = container.get_host_port_ipv4(5432).await
.expect("Failed to get port");
let url = format!("postgres://test:test@localhost:{}/erp_test", host_port);
let db = Database::connect(&url).await
.expect("Failed to connect to test database");
// 运行所有迁移
run_migrations(&db).await;
Self { db, container }
}
}
async fn run_migrations(db: &DatabaseConnection) {
use migration::{Migrator, MigratorTrait};
Migrator::up(db, None).await.expect("Failed to run migrations");
}
```
注意:需要确保 `migration` crate 可被测试引用。可能需要调整 `Cargo.toml` 的依赖。
- [ ] **Step 3: 验证编译**
```bash
cargo check -p erp-server
```
- [ ] **Step 4: Commit**
```bash
git add crates/erp-server/tests/
git commit -m "test(server): 创建 Testcontainers 集成测试基座"
```
---
### Task 3: Auth 模块集成测试
**Files:**
- Create: `crates/erp-server/tests/integration/auth_tests.rs`
- [ ] **Step 1: 编写用户 CRUD 测试**
```rust
// crates/erp-server/tests/integration/auth_tests.rs
use super::test_db::TestDb;
#[tokio::test]
async fn test_user_crud() {
let test_db = TestDb::new().await;
let db = &test_db.db;
let tenant_id = uuid::Uuid::new_v4();
// 创建用户
let user = erp_auth::service::UserService::create(
tenant_id,
uuid::Uuid::new_v4(),
erp_auth::dto::CreateUserReq {
username: "testuser".to_string(),
password: "TestPass123".to_string(),
email: Some("test@example.com".to_string()),
phone: None,
display_name: Some("测试用户".to_string()),
},
db,
&erp_core::events::EventBus::new(100),
).await.expect("Failed to create user");
assert_eq!(user.username, "testuser");
// 查询用户
let found = erp_auth::service::UserService::get_by_id(user.id, tenant_id, db)
.await.expect("Failed to get user");
assert_eq!(found.username, "testuser");
// 列表查询
let (users, total) = erp_auth::service::UserService::list(
tenant_id, erp_core::types::Pagination { page: 1, page_size: 10 }, None, db,
).await.expect("Failed to list users");
assert_eq!(total, 1);
assert_eq!(users[0].username, "testuser");
}
```
- [ ] **Step 2: 编写多租户隔离测试**
```rust
#[tokio::test]
async fn test_tenant_isolation() {
let test_db = TestDb::new().await;
let db = &test_db.db;
let tenant_a = uuid::Uuid::new_v4();
let tenant_b = uuid::Uuid::new_v4();
// 租户 A 创建用户
let user_a = erp_auth::service::UserService::create(
tenant_a,
uuid::Uuid::new_v4(),
erp_auth::dto::CreateUserReq {
username: "user_a".to_string(),
password: "Pass123!".to_string(),
email: None, phone: None, display_name: None,
},
db,
&erp_core::events::EventBus::new(100),
).await.unwrap();
// 租户 B 查询不应看到租户 A 的用户
let (users_b, total_b) = erp_auth::service::UserService::list(
tenant_b, erp_core::types::Pagination { page: 1, page_size: 10 }, None, db,
).await.unwrap();
assert_eq!(total_b, 0);
assert!(users_b.is_empty());
// 租户 B 通过 ID 查询租户 A 的用户应返回 NotFound
let result = erp_auth::service::UserService::get_by_id(user_a.id, tenant_b, db).await;
assert!(result.is_err());
}
```
- [ ] **Step 3: 运行测试验证**
```bash
cargo test -p erp-server --test integration auth_tests
```
注意:需要 Docker 运行。Windows 上可能需要 WSL2。
- [ ] **Step 4: Commit**
```bash
git add crates/erp-server/tests/integration/auth_tests.rs
git commit -m "test(auth): 添加用户 CRUD 和多租户隔离集成测试"
```
---
### Task 4: Plugin 模块集成测试
**Files:**
- Create: `crates/erp-server/tests/integration/plugin_tests.rs`
- [ ] **Step 1: 编写插件生命周期测试**
测试 install → enable → data CRUD → disable → uninstall 完整流程。
- [ ] **Step 2: 编写 JSONB 查询测试**
验证 dynamic_table 的 generated column、pg_trgm 索引是否正确创建。
- [ ] **Step 3: Commit**
```bash
git add crates/erp-server/tests/integration/plugin_tests.rs
git commit -m "test(plugin): 添加插件生命周期和 JSONB 集成测试"
```
---
### Task 5: Workflow + EventBus 集成测试
**Files:**
- Create: `crates/erp-server/tests/integration/workflow_tests.rs`
- Create: `crates/erp-server/tests/integration/event_tests.rs`
- [ ] **Step 1: Workflow 测试 — 流程实例启动和任务完成**
- [ ] **Step 2: EventBus 测试 — 发布/订阅端到端 + outbox relay**
- [ ] **Step 3: Commit**
```bash
git add crates/erp-server/tests/integration/
git commit -m "test: 添加 workflow 和 EventBus 集成测试"
```
---
## Chunk 2: E2E 测试
### Task 6: Playwright 环境搭建
**Files:**
- Create: `apps/web/playwright.config.ts`
- Modify: `apps/web/package.json`
- [ ] **Step 1: 安装 Playwright**
```bash
cd apps/web && pnpm add -D @playwright/test && pnpm exec playwright install chromium
```
- [ ] **Step 2: 创建 Playwright 配置**
```ts
// apps/web/playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
timeout: 30000,
retries: 1,
use: {
baseURL: 'http://localhost:5173',
headless: true,
screenshot: 'only-on-failure',
trace: 'on-first-retry',
},
webServer: {
command: 'pnpm dev',
port: 5173,
reuseExistingServer: true,
},
});
```
- [ ] **Step 3: Commit**
```bash
git add apps/web/playwright.config.ts apps/web/package.json
git commit -m "test(web): 搭建 Playwright E2E 测试环境"
```
---
### Task 7: 登录流程 E2E 测试
**Files:**
- Create: `apps/web/e2e/login.spec.ts`
- [ ] **Step 1: 编写登录 E2E 测试**
```ts
// apps/web/e2e/login.spec.ts
import { test, expect } from '@playwright/test';
test('完整登录流程', async ({ page }) => {
await page.goto('/#/login');
await expect(page.locator('h2, .ant-card-head-title')).toContainText('登录');
// 输入凭据
await page.fill('input[placeholder*="用户名"]', 'admin');
await page.fill('input[placeholder*="密码"]', 'Admin@2026');
await page.click('button:has-text("登录")');
// 验证跳转到首页
await page.waitForURL('**/'),
await expect(page).toHaveURL(/\/$/);
});
```
注意:此测试需要后端服务运行。可在 CI 中使用 service container 或手动启动。
- [ ] **Step 2: Commit**
```bash
git add apps/web/e2e/login.spec.ts
git commit -m "test(web): 添加登录流程 E2E 测试"
```
---
### Task 8: 用户管理 E2E 测试
**Files:**
- Create: `apps/web/e2e/users.spec.ts`
- [ ] **Step 1: 编写用户管理闭环测试**
创建 → 搜索 → 编辑 → 软删除 → 验证列表不显示。
- [ ] **Step 2: Commit**
```bash
git add apps/web/e2e/users.spec.ts
git commit -m "test(web): 添加用户管理 E2E 测试"
```
---
### Task 9: 插件安装 + 多租户 E2E 测试
**Files:**
- Create: `apps/web/e2e/plugins.spec.ts`
- Create: `apps/web/e2e/tenant-isolation.spec.ts`
- [ ] **Step 1: 插件安装 E2E 测试**
上传 → 安装 → 验证菜单 → 数据 CRUD → 卸载。
- [ ] **Step 2: 多租户隔离 E2E 测试**
租户 A 创建数据 → 切换租户 B → 验证不可见。
- [ ] **Step 3: Commit**
```bash
git add apps/web/e2e/
git commit -m "test(web): 添加插件安装和多租户 E2E 测试"
```
---
## Chunk 3: 进销存插件
### Task 10: 创建插件 crate 骨架
**Files:**
- Create: `crates/erp-plugin-inventory/Cargo.toml`
- Create: `crates/erp-plugin-inventory/src/lib.rs`
- Create: `crates/erp-plugin-inventory/manifest.toml`
- Modify: `Cargo.toml`workspace members
- [ ] **Step 1: 创建 Cargo.toml**
```toml
[package]
name = "erp-plugin-inventory"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wit-bindgen = "0.38"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
```
- [ ] **Step 2: 创建 manifest.toml**
定义 6 个实体product, warehouse, stock, supplier, purchase_order, sales_order的完整 schema包括字段、关系、页面、权限声明。参考 CRM 插件的 `crates/erp-plugin-crm/manifest.toml`
- [ ] **Step 3: 创建 lib.rsGuest trait 实现)**
```rust
// crates/erp-plugin-inventory/src/lib.rs
wit_bindgen::generate!({
path: "../erp-plugin-prototype/wit",
world: "plugin-world",
});
struct InventoryPlugin;
impl Guest for InventoryPlugin {
fn init() -> Result<(), String> { Ok(()) }
fn on_tenant_created(_tenant_id: String) -> Result<(), String> { Ok(()) }
fn handle_event(_event_type: String, _event_data: String) -> Result<(), String> { Ok(()) }
}
export_plugin!(InventoryPlugin);
```
- [ ] **Step 4: 添加到 workspace**
在根 `Cargo.toml``members` 中添加 `"crates/erp-plugin-inventory"`
- [ ] **Step 5: 验证编译**
```bash
cargo check -p erp-plugin-inventory
```
- [ ] **Step 6: Commit**
```bash
git add crates/erp-plugin-inventory/ Cargo.toml
git commit -m "feat(inventory): 创建进销存插件 crate 骨架"
```
---
### Task 11: 定义实体 Schema
**Files:**
- Modify: `crates/erp-plugin-inventory/manifest.toml`
- [ ] **Step 1: 定义 6 个实体**
参考 CRM 插件 manifest 格式,定义:
| 实体 | 关键字段 | 关联 | 页面类型 |
|------|---------|------|---------|
| product | code, name, spec, unit, category, price, cost | — | CRUD |
| warehouse | code, name, address, manager, status | — | CRUD |
| stock | product_id, warehouse_id, qty, cost, alert_line | → product, warehouse | CRUD |
| supplier | code, name, contact, phone, address | — | CRUD |
| purchase_order | supplier_id, total_amount, status, date | → supplier, stock | CRUD + Dashboard |
| sales_order | customer_id, total_amount, status, date | → customer(CRM), stock | CRUD + Kanban |
- [ ] **Step 2: 定义 6 个页面**4 CRUD + 1 Dashboard 库存汇总 + 1 Kanban 销售看板)
- [ ] **Step 3: 定义 9 个权限**(每个实体 list/create/update/delete + 全局 manage
- [ ] **Step 4: Commit**
```bash
git add crates/erp-plugin-inventory/manifest.toml
git commit -m "feat(inventory): 定义 6 实体/6 页面/9 权限 manifest"
```
---
### Task 12: 编译 WASM 并测试安装
**Files:**
- Build output: `apps/web/public/inventory.wasm`
- [ ] **Step 1: 编译为 WASM Component**
```bash
cargo build -p erp-plugin-inventory --target wasm32-unknown-unknown --release
wasm-tools component new target/wasm32-unknown-unknown/release/erp_plugin_inventory.wasm -o target/erp_plugin_inventory.component.wasm
```
- [ ] **Step 2: 复制到前端 public 目录**
```bash
cp target/erp_plugin_inventory.component.wasm apps/web/public/inventory.wasm
```
- [ ] **Step 3: 通过 API 安装插件并验证**
使用 curl 或前端插件管理页面上传 `inventory.wasm`验证动态表创建成功CRUD 页面正常工作。
- [ ] **Step 4: Commit**
```bash
git add apps/web/public/inventory.wasm
git commit -m "feat(inventory): 编译并部署进销存插件 WASM"
```
---
## Chunk 4: 插件热更新
### Task 13: 添加 upgrade 端点
**Files:**
- Modify: `crates/erp-plugin/src/handler/plugin_handler.rs`
- Modify: `crates/erp-plugin/src/service.rs`
- [ ] **Step 1: 在 plugin_handler 中添加 upgrade 路由**
```rust
pub fn protected_routes() -> Router<AppState> {
Router::new()
// ... 现有路由 ...
.route("/admin/plugins/:plugin_id/upgrade", post(upgrade_plugin))
}
```
- [ ] **Step 2: 实现 upgrade handler**
接收新 WASM 文件,调用 service 层执行升级。
- [ ] **Step 3: 在 service 中实现升级逻辑**
```rust
pub async fn upgrade_plugin(
plugin_id: Uuid,
tenant_id: Uuid,
new_wasm_bytes: Vec<u8>,
db: &DatabaseConnection,
) -> PluginResult<()> {
// 1. 解析新 manifest
let new_manifest = parse_manifest_from_wasm(&new_wasm_bytes)?;
// 2. 获取当前插件信息
let current = find_by_id(plugin_id, tenant_id, db).await?;
// 3. 对比 schema 变更,生成增量 DDL
let schema_diff = compare_schemas(&current.manifest, &new_manifest)?;
// 4. 暂存新 WASM尝试验证初始化
// 5. 初始化成功后,在事务中执行 DDL + 状态更新
// 6. 失败时保持旧 WASM 继续运行
// 详见 spec §4.4 回滚策略
todo!()
}
```
- [ ] **Step 4: 验证编译**
```bash
cargo check -p erp-plugin
```
- [ ] **Step 5: Commit**
```bash
git add crates/erp-plugin/src/
git commit -m "feat(plugin): 添加插件热更新 upgrade 端点"
```
---
## Chunk 5: 文档更新与清理
### Task 14: 更新 Wiki 文档
**Files:**
- Modify: `wiki/frontend.md`
- Modify: `wiki/database.md`
- Modify: `wiki/testing.md`
- Modify: `wiki/index.md`
- [ ] **Step 1: 更新 `wiki/frontend.md`**
更新为反映当前 16 条路由、6 种插件页面类型、Zustand stores 等实际状态。
- [ ] **Step 2: 更新 `wiki/testing.md`**
更新测试数量、添加 Testcontainers 集成测试和 Playwright E2E 描述。
- [ ] **Step 3: 更新 `wiki/index.md`**
添加进销存插件到模块导航树,更新开发进度表。
- [ ] **Step 4: Commit**
```bash
git add wiki/
git commit -m "docs: 更新 Wiki 文档到当前状态"
```
---
### Task 15: CLAUDE.md 版本号修正 + 根目录清理
**Files:**
- Modify: `CLAUDE.md`
- Cleanup: 根目录未跟踪文件
- [ ] **Step 1: 修正 CLAUDE.md 版本号**
`React 18 + Ant Design 5` 改为 `React 19 + Ant Design 6`
- [ ] **Step 2: 清理根目录未跟踪文件**
删除开发临时文件截图、heap dump、perf trace、agent plan 文件。
```bash
rm -f current-page.png home-full.png home-improved.png docs/debug-*.png
rm -f docs/memory-snapshot-*.heapsnapshot docs/perf-trace-*.json
rm -f test_api_auth.py test_users.py
```
- [ ] **Step 3: 处理 integration-tests/ 目录**
验证 `integration-tests/` 中的测试是否能编译。若已失效则删除(新的集成测试在 `crates/erp-server/tests/integration/`)。若仍有效则添加到 workspace。
- [ ] **Step 4: Commit**
```bash
git add CLAUDE.md
git commit -m "docs: 修正 CLAUDE.md 版本号 (React 19 / AD 6) 并清理临时文件"
```
---
## 验证清单
- [ ] **V1: 全 workspace 编译和测试**
```bash
cargo check && cargo test --workspace
```
- [ ] **V2: 集成测试通过**
```bash
cargo test -p erp-server --test integration
```
注意:需要 Docker 运行
- [ ] **V3: 前端构建**
```bash
cd apps/web && pnpm build
```
- [ ] **V4: E2E 测试**
```bash
cd apps/web && pnpm exec playwright test
```
- [ ] **V5: 进销存插件安装验证**
通过 API 安装 inventory.wasm验证动态表和 CRUD 页面正常。
- [ ] **V6: Wiki 文档同步**
确认 Wiki 描述与代码实际状态一致。