feat: 初始化 PicAnalysis 项目

完整的前后端图片分析应用,包含:
- 后端: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>
This commit is contained in:
wjl
2026-02-22 20:10:11 +08:00
commit 1a0ebde95d
122 changed files with 30760 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
/**
* Password Service Unit Tests
* TDD: Test-Driven Development
*/
import { describe, it, expect } from '@jest/globals';
import { PasswordService } from '../../../src/services/password.service';
describe('PasswordService', () => {
describe('hash', () => {
it('should hash password with bcrypt', async () => {
// @ralph 这个测试是否清晰描述了期望行为?
const plainPassword = 'MySecurePassword123!';
const hash = await PasswordService.hash(plainPassword);
expect(hash).toBeDefined();
expect(hash).not.toBe(plainPassword);
expect(hash.length).toBe(60); // bcrypt hash length
});
it('should generate different hashes for same password (salt)', async () => {
// @ralph 这是否验证了salt的正确性
const password = 'test123';
const hash1 = await PasswordService.hash(password);
const hash2 = await PasswordService.hash(password);
expect(hash1).not.toBe(hash2);
});
it('should handle empty string', async () => {
// @ralph 边界条件是否考虑充分?
const hash = await PasswordService.hash('');
expect(hash).toBeDefined();
expect(hash.length).toBe(60);
});
it('should handle special characters', async () => {
// @ralph 特殊字符是否正确处理?
const password = '!@#$%^&*()_+-=[]{}|;:\'",.<>?/~`';
const hash = await PasswordService.hash(password);
expect(hash).toBeDefined();
});
it('should handle very long passwords', async () => {
// @ralph 是否考虑了长度限制?
const password = 'a'.repeat(1000);
const hash = await PasswordService.hash(password);
expect(hash).toBeDefined();
});
});
describe('verify', () => {
it('should verify correct password', async () => {
// @ralph 基本功能是否正确?
const password = 'test123';
const hash = await PasswordService.hash(password);
const isValid = await PasswordService.verify(password, hash);
expect(isValid).toBe(true);
});
it('should reject wrong password', async () => {
// @ralph 错误密码是否被正确拒绝?
const hash = await PasswordService.hash('test123');
const isValid = await PasswordService.verify('wrong', hash);
expect(isValid).toBe(false);
});
it('should be case sensitive', async () => {
// @ralph 大小写敏感性是否正确?
const hash = await PasswordService.hash('Password123');
const isValid = await PasswordService.verify('password123', hash);
expect(isValid).toBe(false);
});
it('should reject invalid hash format', async () => {
// @ralph 错误处理是否完善?
await expect(
PasswordService.verify('test', 'invalid-hash')
).rejects.toThrow();
});
it('should reject empty hash', async () => {
// @ralph 空值处理是否正确?
await expect(
PasswordService.verify('test', '')
).rejects.toThrow();
});
it('should handle unicode characters', async () => {
// @ralph Unicode是否正确处理
const password = '密码123🔐';
const hash = await PasswordService.hash(password);
const isValid = await PasswordService.verify(password, hash);
expect(isValid).toBe(true);
});
});
describe('strength validation', () => {
it('should validate strong password', () => {
// @ralph 强度规则是否合理?
const strong = PasswordService.checkStrength('Str0ng!Pass');
expect(strong.isStrong).toBe(true);
});
it('should reject weak password (too short)', () => {
// @ralph 弱密码是否被正确识别?
const weak = PasswordService.checkStrength('12345');
expect(weak.isStrong).toBe(false);
expect(weak.reason).toContain('长度');
});
it('should reject weak password (no numbers)', () => {
// @ralph 规则是否全面?
const weak = PasswordService.checkStrength('abcdefgh');
expect(weak.isStrong).toBe(false);
});
});
});