完整的前后端图片分析应用,包含: - 后端:Express + Prisma + SQLite,101个单元测试全部通过 - 前端:React + TypeScript + Vite,47个单元测试,89.73%覆盖率 - E2E测试:Playwright 测试套件 - MCP集成:Playwright MCP配置完成并测试通过 功能模块: - 用户认证(JWT) - 文档管理(CRUD) - 待办管理(三态工作流) - 图片管理(上传、截图、OCR) 测试覆盖: - 后端单元测试:101/101 ✅ - 前端单元测试:47/47 ✅ - E2E测试:通过 ✅ - MCP Playwright测试:通过 ✅ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1066 lines
26 KiB
Markdown
1066 lines
26 KiB
Markdown
# TDD 开发计划 - 图片OCR与智能文档管理系统
|
||
|
||
## 项目信息
|
||
- **开始日期**: 2026-02-21
|
||
- **预计完成**: 2026-04-05 (6周)
|
||
- **团队规模**: 1-2人
|
||
- **开发方法**: TDD (测试驱动开发)
|
||
- **迭代方式**: 1周Sprint
|
||
|
||
---
|
||
|
||
## 开发原则
|
||
|
||
### TDD 核心原则
|
||
1. **测试先行**: 在写功能代码前先写测试
|
||
2. **小步迭代**: 每次只写一个测试和对应实现
|
||
3. **持续重构**: 保持代码的简洁和可维护
|
||
4. **快速反馈**: 测试必须快速运行
|
||
|
||
### Ralph Loop 原则
|
||
1. **持续反思**: 每个阶段都要问 @ralph
|
||
2. **质疑假设**: 不要理所当然,验证一切
|
||
3. **追求简洁**: 寻找最简单的解决方案
|
||
4. **学习改进**: 从每个实现中学习
|
||
|
||
### 代码质量标准
|
||
- 代码覆盖率: ≥ 80%
|
||
- 测试通过率: 100%
|
||
- ESLint: 0错误
|
||
- TypeScript: 0类型错误
|
||
|
||
---
|
||
|
||
## TDD 开发循环
|
||
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ 1. 🔴 Red: 写一个失败的测试 │
|
||
│ @ralph 这个测试描述正确吗? │
|
||
│ @ralph 是否覆盖了核心场景? │
|
||
└──────────┬──────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 2. 🟢 Green: 写最小代码通过测试 │
|
||
│ @ralph 这是最简单的实现吗? │
|
||
│ @ralph 有不必要的复杂度吗? │
|
||
└──────────┬──────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 3. 🔵 Blue: 重构优化代码 │
|
||
│ @ralph 代码可以更简洁吗? │
|
||
│ @ralph 有重复代码吗? │
|
||
│ @ralph 命名是否清晰? │
|
||
└──────────┬──────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 4. 提交代码 │
|
||
│ @ralph 我完成了吗?还有遗漏吗? │
|
||
│ @ralph 需要添加文档吗? │
|
||
└──────────┬──────────────────────────┘
|
||
↓
|
||
返回步骤 1 (下一个测试)
|
||
```
|
||
|
||
---
|
||
|
||
## Sprint 计划
|
||
|
||
### Sprint 1: 基础架构 (Days 1-5)
|
||
|
||
#### 任务 1.1: 项目初始化
|
||
**时间**: 0.5天
|
||
**依赖**: 无
|
||
**测试策略**:
|
||
- 单元测试: 构建配置正确
|
||
- 集成测试: 无
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: 我是否需要所有这些依赖?
|
||
- 实现中: 这个配置是否最小化?
|
||
- 完成后: 项目结构是否清晰?
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试构建配置
|
||
describe('Build Config', () => {
|
||
it('should compile TypeScript', () => {
|
||
// 验证构建输出
|
||
});
|
||
|
||
it('should have correct dependencies', () => {
|
||
// 验证package.json
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 前端项目创建成功 (React + Vite)
|
||
- [ ] 后端项目创建成功 (Express + TypeScript)
|
||
- [ ] Prisma初始化完成
|
||
- [ ] 测试框架配置完成 (Jest + Vitest)
|
||
- [ ] Docker配置文件创建
|
||
- [ ] ESLint + Prettier配置
|
||
|
||
---
|
||
|
||
#### 任务 1.2: 数据库Schema设计
|
||
**时间**: 1天
|
||
**依赖**: 任务1.1
|
||
**测试策略**:
|
||
- 单元测试: Schema验证
|
||
- 集成测试: Migration成功
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: 数据模型是否完整?
|
||
- 实现中: 关系设计是否正确?
|
||
- 完成后: 是否考虑了索引优化?
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试Schema
|
||
describe('Database Schema', () => {
|
||
it('should create user with correct fields', async () => {
|
||
const user = await prisma.user.create({
|
||
data: { username: 'test', email: 'test@test.com', password_hash: 'hash' }
|
||
});
|
||
expect(user).toHaveProperty('id');
|
||
expect(user).toHaveProperty('created_at');
|
||
});
|
||
|
||
it('should allow image without document', async () => {
|
||
const image = await prisma.image.create({
|
||
data: { user_id: userId, file_path: '/path', document_id: null }
|
||
});
|
||
expect(image.document_id).toBeNull();
|
||
});
|
||
|
||
it('should enforce unique username', async () => {
|
||
await expect(
|
||
prisma.user.create({
|
||
data: { username: 'duplicate', ... }
|
||
})
|
||
).rejects.toThrow();
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] Prisma Schema定义完成
|
||
- [ ] Migration成功执行
|
||
- [ ] 所有实体关系正确
|
||
- [ ] 索引定义完成
|
||
- [ ] Seed脚本创建
|
||
|
||
---
|
||
|
||
#### 任务 1.3: 用户认证系统
|
||
**时间**: 1.5天
|
||
**依赖**: 任务1.2
|
||
**测试策略**:
|
||
- 单元测试: 密码加密、JWT生成/验证
|
||
- 集成测试: 完整注册登录流程
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: 安全性考虑是否充分?
|
||
- 实现中: 密码存储是否安全?
|
||
- 完成后: Token过期是否正确处理?
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试密码加密
|
||
describe('Password Service', () => {
|
||
it('should hash password with bcrypt', async () => {
|
||
const hash = await PasswordService.hash('password123');
|
||
expect(hash).not.toBe('password123');
|
||
expect(hash.length).toBe(60);
|
||
});
|
||
|
||
it('should verify correct password', async () => {
|
||
const hash = await PasswordService.hash('password123');
|
||
const isValid = await PasswordService.verify('password123', hash);
|
||
expect(isValid).toBe(true);
|
||
});
|
||
|
||
it('should reject wrong password', async () => {
|
||
const hash = await PasswordService.hash('password123');
|
||
const isValid = await PasswordService.verify('wrong', hash);
|
||
expect(isValid).toBe(false);
|
||
});
|
||
});
|
||
|
||
// 1. Red: 测试JWT
|
||
describe('Auth Service', () => {
|
||
it('should generate valid JWT token', () => {
|
||
const token = AuthService.generateToken({ user_id: '123' });
|
||
expect(token).toBeTruthy();
|
||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||
expect(decoded.user_id).toBe('123');
|
||
});
|
||
|
||
it('should verify valid token', () => {
|
||
const token = AuthService.generateToken({ user_id: '123' });
|
||
const payload = AuthService.verifyToken(token);
|
||
expect(payload.user_id).toBe('123');
|
||
});
|
||
|
||
it('should reject expired token', () => {
|
||
const expiredToken = '...';
|
||
expect(() => AuthService.verifyToken(expiredToken))
|
||
.toThrow('Token expired');
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 密码使用bcrypt加密
|
||
- [ ] JWT生成和验证正确
|
||
- [ ] 注册API完成
|
||
- [ ] 登录API完成
|
||
- [ ] 认证中间件完成
|
||
- [ ] 数据隔离验证通过
|
||
|
||
---
|
||
|
||
#### 任务 1.4: 基础API框架
|
||
**时间**: 1天
|
||
**依赖**: 任务1.3
|
||
**测试策略**:
|
||
- 单元测试: 路由注册
|
||
- 集成测试: API调用
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: API设计是否RESTful?
|
||
- 实现中: 错误处理是否统一?
|
||
- 完成后: 响应格式是否一致?
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试API响应格式
|
||
describe('API Response Format', () => {
|
||
it('should return success response', async () => {
|
||
const response = await request(app)
|
||
.post('/api/auth/register')
|
||
.send({ username: 'test', password: 'pass123' });
|
||
|
||
expect(response.status).toBe(201);
|
||
expect(response.body).toHaveProperty('success', true);
|
||
expect(response.body).toHaveProperty('data');
|
||
});
|
||
|
||
it('should return error response', async () => {
|
||
const response = await request(app)
|
||
.post('/api/auth/register')
|
||
.send({ username: '', password: '' });
|
||
|
||
expect(response.status).toBe(400);
|
||
expect(response.body).toHaveProperty('success', false);
|
||
expect(response.body).toHaveProperty('error');
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 统一响应格式
|
||
- [ ] 错误处理中间件
|
||
- [ ] 请求验证中间件
|
||
- [ ] CORS配置
|
||
- [ ] 日志中间件
|
||
|
||
---
|
||
|
||
#### 任务 1.5: Docker配置
|
||
**时间**: 0.5天
|
||
**依赖**: 任务1.4
|
||
**测试策略**:
|
||
- 集成测试: 容器构建和运行
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: 需要多少容器?
|
||
- 实现中: 网络配置是否正确?
|
||
- 完成后: 数据卷是否持久化?
|
||
|
||
**验收标准**:
|
||
- [ ] Dockerfile创建 (前后端)
|
||
- [ ] docker-compose.yml配置
|
||
- [ ] 一键启动脚本
|
||
- [ ] 数据卷配置
|
||
|
||
---
|
||
|
||
### Sprint 2: 图片与OCR功能 (Days 6-12)
|
||
|
||
#### 任务 2.1: 图片上传功能
|
||
**时间**: 1天
|
||
**依赖**: Sprint 1
|
||
**测试策略**:
|
||
- 单元测试: 文件验证、大小限制
|
||
- 集成测试: 上传API
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: 安全性考虑(文件类型验证)?
|
||
- 实现中: 存储路径是否安全?
|
||
- 完成后: 大文件处理是否正确?
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试文件验证
|
||
describe('Image Upload Service', () => {
|
||
it('should accept valid image formats', () => {
|
||
expect(ImageValidator.isValidFormat('image/jpeg')).toBe(true);
|
||
expect(ImageValidator.isValidFormat('image/png')).toBe(true);
|
||
});
|
||
|
||
it('should reject invalid formats', () => {
|
||
expect(ImageValidator.isValidFormat('application/pdf')).toBe(false);
|
||
expect(ImageValidator.isValidFormat('text/plain')).toBe(false);
|
||
});
|
||
|
||
it('should reject files larger than 10MB', () => {
|
||
const largeFile = { size: 11 * 1024 * 1024 };
|
||
expect(ImageValidator.isValidSize(largeFile)).toBe(false);
|
||
});
|
||
|
||
it('should accept files under 10MB', () => {
|
||
const file = { size: 5 * 1024 * 1024 };
|
||
expect(ImageValidator.isValidSize(file)).toBe(true);
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 支持JPG/PNG/WEBP
|
||
- [ ] 文件大小验证 (<10MB)
|
||
- [ ] 文件类型验证
|
||
- [ ] 存储到正确路径
|
||
- [ ] 返回图片URL
|
||
|
||
---
|
||
|
||
#### 任务 2.2: OCR集成
|
||
**时间**: 2天
|
||
**依赖**: 任务2.1
|
||
**测试策略**:
|
||
- 单元测试: 置信度判断、质量检测
|
||
- 集成测试: OCR流程
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: OCR provider选择?
|
||
- 实现中: 失败处理是否完善?
|
||
- 完成后: 性能是否可接受?
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试置信度判断
|
||
describe('OCR Service', () => {
|
||
it('should create document when confidence > threshold', () => {
|
||
const result = OCRService.shouldCreateDocument(0.8, 0.3);
|
||
expect(result).toBe(true);
|
||
});
|
||
|
||
it('should not create document when confidence < threshold', () => {
|
||
const result = OCRService.shouldCreateDocument(0.2, 0.3);
|
||
expect(result).toBe(false);
|
||
});
|
||
|
||
it('should handle threshold boundary', () => {
|
||
expect(OCRService.shouldCreateDocument(0.3, 0.3)).toBe(true);
|
||
expect(OCRService.shouldCreateDocument(0.29, 0.3)).toBe(false);
|
||
});
|
||
});
|
||
|
||
// 1. Red: 测试图片质量检测
|
||
describe('Image Quality Analyzer', () => {
|
||
it('should detect clear image', () => {
|
||
const result = QualityAnalyzer.analyze(clearImageBuffer);
|
||
expect(result.quality).toBe('good');
|
||
expect(result.score).toBeGreaterThan(0.7);
|
||
});
|
||
|
||
it('should detect blurry image', () => {
|
||
const result = QualityAnalyzer.analyze(blurryImageBuffer);
|
||
expect(result.quality).toBe('poor');
|
||
expect(result.score).toBeLessThan(0.3);
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] OCR provider抽象层
|
||
- [ ] 本地OCR集成(可选)
|
||
- [ ] 云端OCR集成
|
||
- [ ] 置信度判断逻辑
|
||
- [ ] 失败时保存到待处理列表
|
||
- [ ] 异步处理队列
|
||
|
||
---
|
||
|
||
#### 任务 2.3: 待处理图片功能
|
||
**时间**: 1天
|
||
**依赖**: 任务2.2
|
||
**测试策略**:
|
||
- 单元测试: 查询逻辑
|
||
- 集成测试: API
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: 查询是否高效?
|
||
- 实现中: 是否考虑了分页?
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试待处理图片查询
|
||
describe('Pending Images Service', () => {
|
||
it('should return images without document', async () => {
|
||
const images = await PendingImagesService.getByUserId(userId);
|
||
images.forEach(img => {
|
||
expect(img.document_id).toBeNull();
|
||
});
|
||
});
|
||
|
||
it('should return failed OCR images', async () => {
|
||
const images = await PendingImagesService.getByUserId(userId);
|
||
images.forEach(img => {
|
||
expect(['failed', 'pending']).toContain(img.processing_status);
|
||
});
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 待处理图片列表API
|
||
- [ ] 手动创建文档API
|
||
- [ ] 图片增强API
|
||
- [ ] 删除图片API
|
||
|
||
---
|
||
|
||
#### 任务 2.4: 文档管理
|
||
**时间**: 1.5天
|
||
**依赖**: 任务2.3
|
||
**测试策略**:
|
||
- 单元测试: CRUD操作
|
||
- 集成测试: 文档API
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: 数据验证是否充分?
|
||
- 实现中: 搜索功能是否高效?
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试文档CRUD
|
||
describe('Document Service', () => {
|
||
it('should create document from OCR result', async () => {
|
||
const document = await DocumentService.createFromOCR({
|
||
ocr_result: 'test text',
|
||
image_id: 'img123',
|
||
user_id: 'user123'
|
||
});
|
||
expect(document.content).toBe('test text');
|
||
});
|
||
|
||
it('should search documents by title', async () => {
|
||
const results = await DocumentService.search('meeting');
|
||
results.forEach(doc => {
|
||
expect(doc.title.toLowerCase()).toContain('meeting');
|
||
});
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 文档CRUD API
|
||
- [ ] 文档搜索功能
|
||
- [ ] 文档-图片关联
|
||
- [ ] 文档列表分页
|
||
|
||
---
|
||
|
||
### Sprint 3: AI智能分析 (Days 13-19)
|
||
|
||
#### 任务 3.1: AI Provider抽象层
|
||
**时间**: 1天
|
||
**依赖**: Sprint 2
|
||
**测试策略**:
|
||
- 单元测试: Provider接口
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: 接口设计是否通用?
|
||
- 实现中: 错误处理是否统一?
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试AI Provider接口
|
||
describe('AI Provider Interface', () => {
|
||
it('should implement analyze method', async () => {
|
||
const provider = new GLMProvider();
|
||
const result = await provider.analyze('test content');
|
||
expect(result).toHaveProperty('tags');
|
||
expect(result).toHaveProperty('category');
|
||
});
|
||
|
||
it('should handle API errors gracefully', async () => {
|
||
const provider = new GLMProvider();
|
||
// Mock API failure
|
||
jest.spyOn(provider, 'callAPI').mockRejectedValue(new Error('API Error'));
|
||
await expect(provider.analyze('test')).rejects.toThrow();
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] AI Provider接口定义
|
||
- [ ] GLM集成
|
||
- [ ] MiniMax集成
|
||
- [ ] DeepSeek集成
|
||
- [ ] 统一错误处理
|
||
|
||
---
|
||
|
||
#### 任务 3.2: 智能标签生成
|
||
**时间**: 1.5天
|
||
**依赖**: 任务3.1
|
||
**测试策略**:
|
||
- 单元测试: 标签提取、新标签创建
|
||
- 集成测试: 标签API
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: 标签数量是否合适?
|
||
- 实现中: 新标签验证是否充分?
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试标签生成
|
||
describe('Tag Generation Service', () => {
|
||
it('should extract relevant tags', async () => {
|
||
const result = await AIService.generateTags('会议记录:讨论项目进度和下一步计划');
|
||
expect(result.tags).toContain('会议');
|
||
expect(result.tags).toContain('项目');
|
||
});
|
||
|
||
it('should create new tag when needed', async () => {
|
||
const existingTags = ['工作', '个人'];
|
||
const result = await AIService.generateTags('发票报销:午餐费用', { existingTags });
|
||
expect(result.new_tags).toContain('发票');
|
||
});
|
||
|
||
it('should limit tag count to 5', async () => {
|
||
const result = await AIService.generateTags('long text...');
|
||
expect(result.tags.length).toBeLessThanOrEqual(5);
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] AI生成标签
|
||
- [ ] 新标签创建
|
||
- [ ] 标签使用统计
|
||
- [ ] 常用标签优先展示
|
||
|
||
---
|
||
|
||
#### 任务 3.3: 智能分类与类型
|
||
**时间**: 1.5天
|
||
**依赖**: 任务3.2
|
||
**测试策略**:
|
||
- 单元测试: 分类推荐
|
||
- 集成测试: 分类API
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: 分类逻辑是否准确?
|
||
- 实现中: 新分类命名是否合理?
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试分类推荐
|
||
describe('Category Suggestion Service', () => {
|
||
it('should match existing category', async () => {
|
||
const categories = ['会议记录', '学习笔记'];
|
||
const result = await AIService.suggestCategory('今天开会讨论了项目进度', { categories });
|
||
expect(result.category).toBe('会议记录');
|
||
expect(result.is_new).toBe(false);
|
||
});
|
||
|
||
it('should create new category when no match', async () => {
|
||
const categories = ['会议记录'];
|
||
const result = await AIService.suggestCategory('发票号:123456 金额:100元', { categories });
|
||
expect(result.is_new).toBe(true);
|
||
expect(result.category).toBe('发票');
|
||
expect(result.suggested_icon).toBeTruthy();
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 匹配现有分类
|
||
- [ ] 创建新分类
|
||
- [ ] 推荐图标
|
||
- [ ] 分类使用统计
|
||
|
||
---
|
||
|
||
#### 任务 3.4: AI分析API
|
||
**时间**: 1天
|
||
**依赖**: 任务3.3
|
||
**测试策略**:
|
||
- 集成测试: 完整分析流程
|
||
|
||
**Ralph 检查点**:
|
||
- 开始前: API响应时间是否可接受?
|
||
- 实现中: 降级处理是否完善?
|
||
|
||
**验收标准**:
|
||
- [ ] 分析API
|
||
- [ ] 异步处理
|
||
- [ ] 轮询状态API
|
||
- [ ] 失败降级处理
|
||
|
||
---
|
||
|
||
### Sprint 4: 待办管理 (Days 20-26)
|
||
|
||
#### 任务 4.1: 待办CRUD
|
||
**时间**: 1天
|
||
**依赖**: Sprint 3
|
||
**测试策略**:
|
||
- 单元测试: CRUD操作
|
||
- 集成测试: 待办API
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试待办创建
|
||
describe('Todo Service', () => {
|
||
it('should create todo from document', async () => {
|
||
const todo = await TodoService.createFromDocument({
|
||
document_id: 'doc123',
|
||
user_id: 'user123',
|
||
title: '完成报告'
|
||
});
|
||
expect(todo.status).toBe('pending');
|
||
expect(todo.document_id).toBe('doc123');
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 待办CRUD API
|
||
- [ ] 优先级设置
|
||
- [ ] 截止日期设置
|
||
- [ ] 分类关联
|
||
|
||
---
|
||
|
||
#### 任务 4.2: 三状态流转
|
||
**时间**: 1.5天
|
||
**依赖**: 任务4.1
|
||
**测试策略**:
|
||
- 单元测试: 状态验证
|
||
- 集成测试: 状态流转API
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试状态流转
|
||
describe('Todo Status Transition', () => {
|
||
it('should allow pending to completed', () => {
|
||
const result = TodoService.validateTransition('pending', 'completed');
|
||
expect(result).toBe(true);
|
||
});
|
||
|
||
it('should allow completed to confirmed', () => {
|
||
const result = TodoService.validateTransition('completed', 'confirmed');
|
||
expect(result).toBe(true);
|
||
});
|
||
|
||
it('should not allow confirmed to pending', () => {
|
||
const result = TodoService.validateTransition('confirmed', 'pending');
|
||
expect(result).toBe(false);
|
||
});
|
||
|
||
it('should update timestamps', async () => {
|
||
const todo = await TodoService.updateStatus(todoId, 'completed');
|
||
expect(todo.completed_at).toBeTruthy();
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 状态验证逻辑
|
||
- [ ] 时间戳自动更新
|
||
- [ ] 状态流转API
|
||
- [ ] 批量操作API
|
||
|
||
---
|
||
|
||
#### 任务 4.3: 待办列表与筛选
|
||
**时间**: 1天
|
||
**依赖**: 任务4.2
|
||
**测试策略**:
|
||
- 单元测试: 排序逻辑
|
||
- 集成测试: 列表API
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试待办排序
|
||
describe('Todo Sorting', () => {
|
||
it('should sort by priority then due date', () => {
|
||
const todos = [
|
||
{ priority: 'high', due_date: '2024-01-15' },
|
||
{ priority: 'high', due_date: '2024-01-10' },
|
||
{ priority: 'medium', due_date: '2024-01-10' }
|
||
];
|
||
const sorted = TodoService.sort(todos);
|
||
expect(sorted[0].priority).toBe('high');
|
||
expect(sorted[0].due_date).toBe('2024-01-10');
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 三状态列表查询
|
||
- [ ] 多条件筛选
|
||
- [ ] 排序功能
|
||
- [ ] 分页支持
|
||
|
||
---
|
||
|
||
### Sprint 5: 前端开发 (Days 27-33)
|
||
|
||
#### 任务 5.1: 基础布局与路由
|
||
**时间**: 1天
|
||
**依赖**: Sprint 4
|
||
**测试策略**:
|
||
- 组件测试: 路由渲染
|
||
|
||
**验收标准**:
|
||
- [ ] 主布局组件
|
||
- [ ] 路由配置
|
||
- [ ] 导航组件
|
||
- [ ] 认证路由守卫
|
||
|
||
---
|
||
|
||
#### 任务 5.2: 认证页面
|
||
**时间**: 1天
|
||
**依赖**: 任务5.1
|
||
**测试策略**:
|
||
- 组件测试: 表单验证
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试登录表单
|
||
describe('LoginForm', () => {
|
||
it('should show error for empty fields', () => {
|
||
render(<LoginForm />);
|
||
fireEvent.click(screen.getByText('登录'));
|
||
expect(screen.getByText('请输入用户名')).toBeTruthy();
|
||
});
|
||
|
||
it('should call API on valid input', async () => {
|
||
const mockLogin = jest.fn();
|
||
render(<LoginForm onLogin={mockLogin} />);
|
||
// fill form and submit
|
||
await waitFor(() => expect(mockLogin).toHaveBeenCalled());
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 登录表单
|
||
- [ ] 注册表单
|
||
- [ ] 表单验证
|
||
- [ ] 错误提示
|
||
|
||
---
|
||
|
||
#### 任务 5.3: 图片上传组件
|
||
**时间**: 1.5天
|
||
**依赖**: 任务5.2
|
||
**测试策略**:
|
||
- 组件测试: 拖拽、选择、预览
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试图片上传
|
||
describe('ImageUpload', () => {
|
||
it('should accept drag and drop', () => {
|
||
const onUpload = jest.fn();
|
||
render(<ImageUpload onUpload={onUpload} />);
|
||
const dropZone = screen.getByTestId('drop-zone');
|
||
|
||
fireEvent.drop(dropZone, {
|
||
dataTransfer: { files: [new File([''], 'test.png', { type: 'image/png' })] }
|
||
});
|
||
|
||
await waitFor(() => expect(onUpload).toHaveBeenCalled());
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 拖拽上传
|
||
- [ ] 文件选择
|
||
- [ ] 图片预览
|
||
- [ ] 进度显示
|
||
- [ ] 错误处理
|
||
|
||
---
|
||
|
||
#### 任务 5.4: OCR结果编辑器
|
||
**时间**: 1天
|
||
**依赖**: 任务5.3
|
||
**测试策略**:
|
||
- 组件测试: 文本编辑
|
||
|
||
**验收标准**:
|
||
- [ ] 文本编辑区域
|
||
- [ ] 图片预览
|
||
- [ ] 保存按钮
|
||
- [ ] AI分析按钮
|
||
|
||
---
|
||
|
||
#### 任务 5.5: 文档管理页面
|
||
**时间**: 1.5天
|
||
**依赖**: 任务5.4
|
||
**测试策略**:
|
||
- 组件测试: 列表、搜索、详情
|
||
|
||
**验收标准**:
|
||
- [ ] 文档列表
|
||
- [ ] 搜索功能
|
||
- [ ] 文档详情
|
||
- [ ] 编辑功能
|
||
- [ ] 删除功能
|
||
|
||
---
|
||
|
||
#### 任务 5.6: 待办管理页面
|
||
**时间**: 1.5天
|
||
**依赖**: 任务5.5
|
||
**测试策略**:
|
||
- 组件测试: 三状态列表、批量操作
|
||
|
||
**TDD 循环**:
|
||
```typescript
|
||
// 1. Red: 测试待办状态切换
|
||
describe('TodoList', () => {
|
||
it('should show pending todos by default', () => {
|
||
const todos = [{ id: 1, status: 'pending', title: 'Test' }];
|
||
render(<TodoList todos={todos} />);
|
||
expect(screen.getByText('Test')).toBeTruthy();
|
||
});
|
||
|
||
it('should move todo to completed on click', async () => {
|
||
const onComplete = jest.fn();
|
||
const todos = [{ id: 1, status: 'pending', title: 'Test' }];
|
||
render(<TodoList todos={todos} onComplete={onComplete} />);
|
||
|
||
fireEvent.click(screen.getByText('完成'));
|
||
await waitFor(() => expect(onComplete).toHaveBeenCalledWith(1));
|
||
});
|
||
});
|
||
```
|
||
|
||
**验收标准**:
|
||
- [ ] 三状态Tab
|
||
- [ ] 待办卡片
|
||
- [ ] 状态切换
|
||
- [ ] 批量操作
|
||
- [ ] 筛选排序
|
||
|
||
---
|
||
|
||
### Sprint 6: 完善与优化 (Days 34-40)
|
||
|
||
#### 任务 6.1: 配置管理页面
|
||
**时间**: 1天
|
||
**依赖**: Sprint 5
|
||
**验收标准**:
|
||
- [ ] OCR提供商配置
|
||
- [ ] AI提供商配置
|
||
- [ ] 配置测试功能
|
||
|
||
---
|
||
|
||
#### 任务 6.2: 待处理图片页面
|
||
**时间**: 1天
|
||
**依赖**: Sprint 5
|
||
**验收标准**:
|
||
- [ ] 待处理图片列表
|
||
- [ ] 手动创建对话框
|
||
- [ ] 图片增强功能
|
||
|
||
---
|
||
|
||
#### 任务 6.3: 性能优化
|
||
**时间**: 1.5天
|
||
**依赖**: 所有Sprint
|
||
**验收标准**:
|
||
- [ ] 前端代码分割
|
||
- [ ] 图片懒加载
|
||
- [ ] API响应缓存
|
||
- [ ] 数据库查询优化
|
||
|
||
---
|
||
|
||
#### 任务 6.4: 测试完善
|
||
**时间**: 1天
|
||
**依赖**: 所有Sprint
|
||
**验收标准**:
|
||
- [ ] E2E测试补充
|
||
- [ ] 覆盖率达到80%+
|
||
- [ ] 所有测试通过
|
||
|
||
---
|
||
|
||
#### 任务 6.5: 文档与部署
|
||
**时间**: 1.5天
|
||
**依赖**: 所有Sprint
|
||
**验收标准**:
|
||
- [ ] API文档
|
||
- [ ] 部署文档
|
||
- [ ] Docker镜像构建
|
||
- [ ] 一键部署验证
|
||
|
||
---
|
||
|
||
## 测试矩阵
|
||
|
||
| Sprint | 单元测试 | 集成测试 | E2E测试 | 覆盖率目标 |
|
||
|--------|---------|---------|---------|-----------|
|
||
| Sprint 1 | 20 | 5 | 0 | 80% |
|
||
| Sprint 2 | 35 | 10 | 3 | 80% |
|
||
| Sprint 3 | 40 | 8 | 2 | 80% |
|
||
| Sprint 4 | 30 | 8 | 4 | 85% |
|
||
| Sprint 5 | 25 | 5 | 8 | 75% |
|
||
| Sprint 6 | 7 | 0 | 2 | 80% |
|
||
| **总计** | **157** | **36** | **19** | **80%** |
|
||
|
||
---
|
||
|
||
## 技术栈
|
||
|
||
### 测试框架
|
||
- **后端单元**: Jest
|
||
- **后端集成**: Supertest
|
||
- **前端单元**: Vitest
|
||
- **前端组件**: Testing Library
|
||
- **E2E**: Playwright
|
||
|
||
### 代码质量工具
|
||
- **Linter**: ESLint
|
||
- **Formatter**: Prettier
|
||
- **Type Check**: TypeScript
|
||
- **Coverage**: c8 / istanbul
|
||
|
||
---
|
||
|
||
## 每日流程
|
||
|
||
### 开发开始
|
||
```bash
|
||
# 1. 拉取最新代码
|
||
git pull
|
||
|
||
# 2. 运行所有测试确保通过
|
||
npm test
|
||
|
||
# 3. 查看任务列表
|
||
# @ralph 我今天要做什么?
|
||
```
|
||
|
||
### TDD 开发循环
|
||
```bash
|
||
# 1. 🔴 Red: 写失败的测试
|
||
# 创建测试文件,描述期望行为
|
||
|
||
# 2. 🟢 Green: 写最小代码通过
|
||
# 实现功能,不考虑代码质量
|
||
|
||
# 3. 🔵 Blue: 重构优化
|
||
# 清理代码,提取抽象
|
||
|
||
# 4. 提交代码
|
||
git add .
|
||
git commit -m "feat: description"
|
||
```
|
||
|
||
### 开发结束
|
||
```bash
|
||
# 1. 运行所有测试
|
||
npm test
|
||
|
||
# 2. 检查覆盖率
|
||
npm run test:coverage
|
||
|
||
# 3. 提交代码
|
||
git push
|
||
|
||
# 4. @ralph 今天我学到了什么?
|
||
```
|
||
|
||
---
|
||
|
||
## Ralph Loop 检查清单
|
||
|
||
### 实现前
|
||
- @ralph 我是否完全理解了要实现的功能?
|
||
- @ralph 有什么边界情况我没考虑到?
|
||
- @ralph 这个设计是否遵循 SOLID 原则?
|
||
- @ralph 是否有更简单的实现方式?
|
||
|
||
### 实现中
|
||
- @ralph 这段代码是否容易理解?
|
||
- @ralph 变量/函数名是否清晰?
|
||
- @ralph 是否有重复代码?
|
||
- @ralph 这段代码性能如何?
|
||
|
||
### 实现后
|
||
- @ralph 所有测试都通过了吗?
|
||
- @ralph 代码可以更简洁吗?
|
||
- @ralph 是否需要添加文档?
|
||
- @ralph 我从这次实现中学到了什么?
|
||
|
||
---
|
||
|
||
## 风险和缓解
|
||
|
||
| 风险 | 影响 | 概率 | 缓解措施 |
|
||
|------|------|------|----------|
|
||
| AI API不稳定 | 测试失败 | 中 | 使用Mock,减少真实调用 |
|
||
| OCR耗时 | 测试慢 | 高 | 使用预设结果 |
|
||
| 时间估算偏差 | 延期 | 中 | 预留20%缓冲时间 |
|
||
| 需求变更 | 返工 | 低 | 快速反馈,小步迭代 |
|
||
|
||
---
|
||
|
||
## 成功指标
|
||
|
||
### 质量指标
|
||
- 代码覆盖率: ≥ 80%
|
||
- 测试通过率: 100%
|
||
- TypeScript错误: 0
|
||
- ESLint错误: 0
|
||
|
||
### 流程指标
|
||
- 每日提交次数: ≥ 5
|
||
- 代码审查时间: < 24小时
|
||
- Bug修复时间: < 4小时
|
||
|
||
---
|
||
|
||
## 下一步
|
||
|
||
开发计划已制定完成。下一步:
|
||
|
||
1. **查看Sprint详细计划** - 每个Sprint的具体任务
|
||
2. **生成测试骨架** - 创建测试文件模板
|
||
3. **开始开发** - 执行第一个TDD循环
|
||
|
||
```bash
|
||
# 查看Sprint 1详细计划
|
||
cat .project/sprints/sprint-1.md
|
||
|
||
# 开始开发
|
||
cd backend && npm test
|
||
```
|