/** * Document Service Unit Tests * TDD: Test-Driven Development */ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'; import { DocumentService } from '../../../src/services/document.service'; import { prisma } from '../../../src/lib/prisma'; describe('DocumentService', () => { // @ralph 我要测试什么? // - 创建文档 // - 更新文档内容 // - 删除文档 // - 获取用户文档列表 // - 按分类筛选文档 // - 边界情况:空内容、特殊字符、长文本 let userId: string; let categoryId: string; beforeEach(async () => { // Create test user and category const user = await prisma.user.create({ data: { username: `testuser_${Date.now()}`, email: `test_${Date.now()}@example.com`, password_hash: '$2a$10$test', }, }); userId = user.id; const category = await prisma.category.create({ data: { user_id: userId, name: 'Test Category', type: 'document', }, }); categoryId = category.id; }); afterEach(async () => { // Clean up await prisma.document.deleteMany({ where: { user_id: userId } }); await prisma.category.deleteMany({ where: { user_id: userId } }); await prisma.user.delete({ where: { id: userId } }); }); describe('create', () => { it('should create a new document', async () => { // @ralph 正常路径是否覆盖? const document = await DocumentService.create({ user_id: userId, content: 'Test document content', title: 'Test Document', category_id: categoryId, }); expect(document).toBeDefined(); expect(document.id).toBeDefined(); expect(document.content).toBe('Test document content'); expect(document.title).toBe('Test Document'); expect(document.user_id).toBe(userId); expect(document.category_id).toBe(categoryId); }); it('should create document with minimal required fields', async () => { // @ralph 最小输入是否处理? const document = await DocumentService.create({ user_id: userId, content: 'Minimal content', }); expect(document).toBeDefined(); expect(document.content).toBe('Minimal content'); expect(document.title).toBeNull(); expect(document.category_id).toBeNull(); }); it('should reject empty content', async () => { // @ralph 无效输入是否拒绝? await expect( DocumentService.create({ user_id: userId, content: '', }) ).rejects.toThrow('content'); }); it('should handle long content', async () => { // @ralph 边界条件是否测试? const longContent = 'A'.repeat(10000); const document = await DocumentService.create({ user_id: userId, content: longContent, }); expect(document.content).toBe(longContent); }); it('should handle special characters in content', async () => { // @ralph 特殊字符是否正确处理? const specialContent = 'Test with 中文 and émojis 🎉 and "quotes"'; const document = await DocumentService.create({ user_id: userId, content: specialContent, }); expect(document.content).toBe(specialContent); }); it('should reject non-existent category', async () => { // @ralph 外键约束是否验证? const fakeCategoryId = '00000000-0000-0000-0000-000000000000'; await expect( DocumentService.create({ user_id: userId, content: 'Test', category_id: fakeCategoryId, }) ).rejects.toThrow(); }); it('should reject non-existent user', async () => { // @ralph 用户验证是否正确? const fakeUserId = '00000000-0000-0000-0000-000000000000'; await expect( DocumentService.create({ user_id: fakeUserId, content: 'Test', }) ).rejects.toThrow(); }); }); describe('findById', () => { it('should find document by id', async () => { // @ralph 查找功能是否正常? const created = await DocumentService.create({ user_id: userId, content: 'Test content', }); const found = await DocumentService.findById(created.id, userId); expect(found).toBeDefined(); expect(found!.id).toBe(created.id); expect(found!.content).toBe('Test content'); }); it('should return null for non-existent document', async () => { // @ralph 未找到时是否返回null? const found = await DocumentService.findById('00000000-0000-0000-0000-000000000000', userId); expect(found).toBeNull(); }); it('should not return document from different user', async () => { // @ralph 数据隔离是否正确? const otherUser = await prisma.user.create({ data: { username: 'otheruser', password_hash: '$2a$10$test', }, }); const document = await DocumentService.create({ user_id: userId, content: 'Private document', }); const found = await DocumentService.findById(document.id, otherUser.id); expect(found).toBeNull(); await prisma.user.delete({ where: { id: otherUser.id } }); }); }); describe('update', () => { it('should update document content', async () => { // @ralph 更新功能是否正常? const document = await DocumentService.create({ user_id: userId, content: 'Original content', }); const updated = await DocumentService.update(document.id, userId, { content: 'Updated content', }); expect(updated.content).toBe('Updated content'); expect(updated.id).toBe(document.id); }); it('should update title', async () => { // @ralph 部分更新是否支持? const document = await DocumentService.create({ user_id: userId, content: 'Content', }); const updated = await DocumentService.update(document.id, userId, { title: 'New Title', }); expect(updated.title).toBe('New Title'); expect(updated.content).toBe('Content'); }); it('should update category', async () => { // @ralph 分类更新是否正确? const document = await DocumentService.create({ user_id: userId, content: 'Content', }); const updated = await DocumentService.update(document.id, userId, { category_id: categoryId, }); expect(updated.category_id).toBe(categoryId); }); it('should reject update for non-existent document', async () => { // @ralph 错误处理是否正确? await expect( DocumentService.update('00000000-0000-0000-0000-000000000000', userId, { content: 'Updated', }) ).rejects.toThrow(); }); it('should reject update from different user', async () => { // @ralph 权限控制是否正确? const otherUser = await prisma.user.create({ data: { username: 'otheruser', password_hash: '$2a$10$test', }, }); const document = await DocumentService.create({ user_id: userId, content: 'Content', }); await expect( DocumentService.update(document.id, otherUser.id, { content: 'Hacked', }) ).rejects.toThrow(); await prisma.user.delete({ where: { id: otherUser.id } }); }); }); describe('delete', () => { it('should delete document', async () => { // @ralph 删除功能是否正常? const document = await DocumentService.create({ user_id: userId, content: 'Content', }); await DocumentService.delete(document.id, userId); const found = await DocumentService.findById(document.id, userId); expect(found).toBeNull(); }); it('should reject delete for non-existent document', async () => { // @ralph 错误处理是否正确? await expect( DocumentService.delete('00000000-0000-0000-0000-000000000000', userId) ).rejects.toThrow(); }); it('should reject delete from different user', async () => { // @ralph 权限控制是否正确? const otherUser = await prisma.user.create({ data: { username: 'otheruser', password_hash: '$2a$10$test', }, }); const document = await DocumentService.create({ user_id: userId, content: 'Content', }); await expect( DocumentService.delete(document.id, otherUser.id) ).rejects.toThrow(); await prisma.user.delete({ where: { id: otherUser.id } }); }); }); describe('findByUser', () => { beforeEach(async () => { // Create multiple documents await DocumentService.create({ user_id: userId, content: 'Document 1', title: 'First', category_id: categoryId, }); await DocumentService.create({ user_id: userId, content: 'Document 2', title: 'Second', }); await DocumentService.create({ user_id: userId, content: 'Document 3', title: 'Third', category_id: categoryId, }); }); it('should return all user documents', async () => { // @ralph 列表查询是否正确? const documents = await DocumentService.findByUser(userId); expect(documents).toHaveLength(3); expect(documents.every(d => d.user_id === userId)).toBe(true); }); it('should filter by category', async () => { // @ralph 筛选功能是否正确? const documents = await DocumentService.findByUser(userId, { category_id: categoryId }); expect(documents).toHaveLength(2); expect(documents.every(d => d.category_id === categoryId)).toBe(true); }); it('should support pagination', async () => { // @ralph 分页功能是否支持? const page1 = await DocumentService.findByUser(userId, { page: 1, limit: 2 }); expect(page1).toHaveLength(2); const page2 = await DocumentService.findByUser(userId, { page: 2, limit: 2 }); expect(page2).toHaveLength(1); }); it('should return empty for user with no documents', async () => { // @ralph 空结果是否正确处理? const otherUser = await prisma.user.create({ data: { username: 'emptyuser', password_hash: '$2a$10$test', }, }); const documents = await DocumentService.findByUser(otherUser.id); expect(documents).toHaveLength(0); await prisma.user.delete({ where: { id: otherUser.id } }); }); }); describe('search', () => { beforeEach(async () => { await DocumentService.create({ user_id: userId, content: 'This is about programming with JavaScript', title: 'Programming Guide', }); await DocumentService.create({ user_id: userId, content: 'Cooking recipes for beginners', title: 'Cooking Book', }); await DocumentService.create({ user_id: userId, content: 'JavaScript best practices', title: 'Advanced Programming', }); }); it('should search in content', async () => { // @ralph 内容搜索是否正常? const results = await DocumentService.search(userId, 'JavaScript'); expect(results).toHaveLength(2); expect(results.every(d => d.content.includes('JavaScript') || d.title?.includes('JavaScript'))).toBe(true); }); it('should search in title', async () => { // @ralph 标题搜索是否正常? const results = await DocumentService.search(userId, 'Programming'); expect(results).toHaveLength(2); }); it('should return empty for no matches', async () => { // @ralph 无结果时是否正确? const results = await DocumentService.search(userId, 'NonExistentTerm'); expect(results).toHaveLength(0); }); it('should handle empty search term', async () => { // @ralph 空搜索词是否处理? const results = await DocumentService.search(userId, ''); expect(results).toHaveLength(3); }); }); });