完整的前后端图片分析应用,包含: - 后端: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>
123 lines
4.0 KiB
TypeScript
123 lines
4.0 KiB
TypeScript
/**
|
||
* 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);
|
||
});
|
||
});
|
||
});
|