From 61d9685b78ac66b26b8088ec9f73d3e7f894465e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E9=BE=99?= Date: Wed, 30 Jul 2025 16:09:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/service/test/Role.test.ts | 513 +++++++++++++++++++++++++ app/service/usecase/RoleEditUseCase.ts | 42 +- jest.config.js | 9 + 3 files changed, 562 insertions(+), 2 deletions(-) create mode 100644 app/service/test/Role.test.ts create mode 100644 jest.config.js diff --git a/app/service/test/Role.test.ts b/app/service/test/Role.test.ts new file mode 100644 index 0000000..4b5da97 --- /dev/null +++ b/app/service/test/Role.test.ts @@ -0,0 +1,513 @@ +import { getRoleList, getRoleData, updateText, updateTag, regenerateRole, getUserRoleLibrary, replaceRole, getRoleShots, applyRoleToShots } from '@/api/video_flow'; +import { RoleEditUseCase } from '../usecase/RoleEditUseCase'; +import { TextEditUseCase } from '../usecase/TextEditUseCase'; +import { TagEditUseCase } from '../usecase/TagEditUseCase'; +import { RoleItem, TextItem, TagItem } from '../domain/Item'; +import { RoleEntity, AITextEntity, TagEntity, ShotEntity } from '../domain/Entities'; + +// Mock API模块 +jest.mock('@/api/video_flow', () => ({ + getRoleList: jest.fn(), + getRoleData: jest.fn(), + updateText: jest.fn(), + updateTag: jest.fn(), + regenerateRole: jest.fn(), + getUserRoleLibrary: jest.fn(), + replaceRole: jest.fn(), + getRoleShots: jest.fn(), + applyRoleToShots: jest.fn(), +})); + +// Mock UseCase模块 +jest.mock('../usecase/RoleEditUseCase'); +jest.mock('../usecase/TextEditUseCase'); +jest.mock('../usecase/TagEditUseCase'); + +// Mock Domain模块 +jest.mock('../domain/Item', () => ({ + RoleItem: jest.fn(), + TextItem: jest.fn(), + TagItem: jest.fn(), +})); + +describe('RoleService 业务逻辑测试', () => { + let mockRoleEditUseCase: jest.Mocked; + let mockTextEditUseCase: jest.Mocked; + let mockTagEditUseCase: jest.Mocked; + + // 测试数据 + const mockRoleEntity: RoleEntity = { + id: 'role1', + name: '测试角色', + generateTextId: 'text1', + tagIds: ['tag1', 'tag2'], + imageUrl: 'http://example.com/role1.jpg', + updatedAt: Date.now(), + loadingProgress: 100, + disableEdit: false, + isStored: true, + }; + + const mockTextEntity: AITextEntity = { + id: 'text1', + content: '这是AI生成的文本内容', + updatedAt: Date.now(), + loadingProgress: 100, + disableEdit: false, + }; + + const mockTagEntity1: TagEntity = { + id: 'tag1', + name: '标签1', + content: '标签内容1', + updatedAt: Date.now(), + loadingProgress: 100, + disableEdit: false, + }; + + + const mockShotEntity: ShotEntity = { + id: 'shot1', + name: '分镜1', + sketchUrl: 'http://example.com/sketch1.jpg', + videoUrl: 'http://example.com/video1.mp4', + roleMap: [], + content: [], + shot: [], + updatedAt: Date.now(), + loadingProgress: 100, + disableEdit: false, + }; + + beforeEach(() => { + jest.clearAllMocks(); + + // 设置Mock UseCase实例 + mockRoleEditUseCase = { + AIgenerateRole: jest.fn(), + applyRole: jest.fn(), + } as any; + + mockTextEditUseCase = { + getOptimizedContent: jest.fn(), + updateText: jest.fn(), + } as any; + + mockTagEditUseCase = { + updateTag: jest.fn(), + } as any; + + // 设置Mock构造函数 + (RoleEditUseCase as jest.MockedClass).mockImplementation(() => mockRoleEditUseCase); + (TextEditUseCase as jest.MockedClass).mockImplementation(() => mockTextEditUseCase); + (TagEditUseCase as jest.MockedClass).mockImplementation(() => mockTagEditUseCase); + + // 设置Mock Item构造函数 + (RoleItem as jest.MockedClass).mockImplementation((entity) => ({ + entity, + metadata: {}, + disableEdit: entity.disableEdit, + type: 1, + } as any)); + + (TextItem as jest.MockedClass).mockImplementation((entity) => ({ + entity, + metadata: {}, + disableEdit: entity.disableEdit, + type: 0, + } as any)); + + (TagItem as jest.MockedClass).mockImplementation((entity) => ({ + entity, + metadata: {}, + disableEdit: entity.disableEdit, + type: 2, + } as any)); + }); + + describe('数据初始化测试', () => { + it('应该成功获取角色列表', async () => { + const mockRoles = [mockRoleEntity]; + (getRoleList as jest.Mock).mockResolvedValue({ + successful: true, + data: mockRoles, + message: 'success', + }); + + const result = await getRoleList({ projectId: 'project1' }); + + expect(getRoleList).toHaveBeenCalledWith({ projectId: 'project1' }); + expect(result.successful).toBe(true); + expect(result.data).toEqual(mockRoles); + }); + + it('获取角色列表失败时应该返回错误信息', async () => { + (getRoleList as jest.Mock).mockResolvedValue({ + successful: false, + message: '获取失败', + }); + + const result = await getRoleList({ projectId: 'project1' }); + + expect(result.successful).toBe(false); + expect(result.message).toBe('获取失败'); + }); + }); + + describe('修改文本和标签测试', () => { + it('应该成功修改AI文本', async () => { + const updatedTextEntity = { ...mockTextEntity, content: '更新后的文本' }; + (updateText as jest.Mock).mockResolvedValue({ + successful: true, + data: updatedTextEntity, + }); + + const result = await updateText({ + textId: 'text1', + content: '新的文本内容' + }); + + expect(updateText).toHaveBeenCalledWith({ + textId: 'text1', + content: '新的文本内容' + }); + expect(result.successful).toBe(true); + expect(result.data.content).toBe('更新后的文本'); + }); + + it('应该成功修改标签内容', async () => { + const updatedTagEntity = { ...mockTagEntity1, content: '更新后的标签' }; + (updateTag as jest.Mock).mockResolvedValue({ + successful: true, + data: updatedTagEntity, + }); + + const result = await updateTag({ + tagId: 'tag1', + content: '新的标签内容' + }); + + expect(updateTag).toHaveBeenCalledWith({ + tagId: 'tag1', + content: '新的标签内容' + }); + expect(result.successful).toBe(true); + expect(result.data.content).toBe('更新后的标签'); + }); + }); + + describe('文本AI优化测试', () => { + it('应该成功优化AI文本', async () => { + const optimizedContent = '优化后的文本内容'; + const updatedTextEntity = { ...mockTextEntity, content: optimizedContent }; + + mockTextEditUseCase.getOptimizedContent.mockResolvedValue(optimizedContent); + mockTextEditUseCase.updateText.mockResolvedValue({ + entity: updatedTextEntity, + metadata: {}, + disableEdit: false, + type: 0, + } as any); + + (updateText as jest.Mock).mockResolvedValue({ + successful: true, + data: updatedTextEntity, + }); + + // 模拟优化流程 + const optimizedContentResult = await mockTextEditUseCase.getOptimizedContent(); + const updateResult = await mockTextEditUseCase.updateText(optimizedContentResult); + + expect(mockTextEditUseCase.getOptimizedContent).toHaveBeenCalled(); + expect(mockTextEditUseCase.updateText).toHaveBeenCalledWith(optimizedContent); + expect(updateResult.entity.content).toBe(optimizedContent); + }); + }); + + describe('重新生成角色形象测试', () => { + it('应该成功重新生成角色', async () => { + const newRoleEntity = { ...mockRoleEntity, id: 'role2', name: '新角色' }; + (regenerateRole as jest.Mock).mockResolvedValue({ + successful: true, + data: newRoleEntity, + }); + + mockRoleEditUseCase.AIgenerateRole.mockResolvedValue(newRoleEntity); + + const result = await regenerateRole({ + prompt: '重新生成角色', + tagTypes: ['tag1', 'tag2'], + roleId: 'role1' + }); + + expect(regenerateRole).toHaveBeenCalledWith({ + prompt: '重新生成角色', + tagTypes: ['tag1', 'tag2'], + roleId: 'role1' + }); + expect(result.successful).toBe(true); + expect(result.data.id).toBe('role2'); + expect(result.data.name).toBe('新角色'); + }); + }); + + describe('重新获取角色数据测试', () => { + it('应该成功重新获取角色数据并更新实体', async () => { + const mockTextEntity = { + id: 'text1', + content: '更新后的AI文本', + updatedAt: Date.now(), + loadingProgress: 100, + disableEdit: false, + }; + + const mockTags = [ + { + id: 'tag1', + name: '更新标签1', + content: '更新内容1', + updatedAt: Date.now(), + loadingProgress: 100, + disableEdit: false, + }, + { + id: 'tag2', + name: '更新标签2', + content: '更新内容2', + updatedAt: Date.now(), + loadingProgress: 100, + disableEdit: false, + } + ]; + + (getRoleData as jest.Mock).mockResolvedValue({ + successful: true, + data: { + text: mockTextEntity, + tags: mockTags, + }, + }); + + // 模拟RoleItem的setEntity方法 + const mockSetEntity = jest.fn(); + (RoleItem as jest.MockedClass).mockImplementation((entity) => ({ + entity, + metadata: {}, + disableEdit: entity.disableEdit, + type: 1, + setEntity: mockSetEntity, + } as any)); + + const roleItem = new RoleItem(mockRoleEntity); + const useCase = new RoleEditUseCase(roleItem); + + const result = await useCase.refreshRoleData(); + + expect(getRoleData).toHaveBeenCalledWith({ roleId: 'role1' }); + expect(result.text).toEqual(mockTextEntity); + expect(result.tags).toEqual(mockTags); + expect(mockSetEntity).toHaveBeenCalledWith(expect.objectContaining({ + generateTextId: 'text1', + tagIds: ['tag1', 'tag2'], + updatedAt: expect.any(Number), + })); + }); + + it('角色ID不存在时应该抛出错误', async () => { + const emptyRoleEntity = { ...mockRoleEntity, id: '' }; + const roleItem = new RoleItem(emptyRoleEntity); + const useCase = new RoleEditUseCase(roleItem); + + await expect(useCase.refreshRoleData()).rejects.toThrow('角色ID不存在,无法获取角色数据'); + }); + + it('API调用失败时应该抛出错误', async () => { + (getRoleData as jest.Mock).mockResolvedValue({ + successful: false, + message: '获取失败', + }); + + const roleItem = new RoleItem(mockRoleEntity); + const useCase = new RoleEditUseCase(roleItem); + + await expect(useCase.refreshRoleData()).rejects.toThrow('获取角色数据失败: 获取失败'); + }); + }); + + describe('角色形象库选取使用测试', () => { + it('应该成功获取用户角色库', async () => { + const mockLibraryRoles = [mockRoleEntity]; + (getUserRoleLibrary as jest.Mock).mockResolvedValue({ + successful: true, + data: mockLibraryRoles, + }); + + const result = await getUserRoleLibrary(); + + expect(getUserRoleLibrary).toHaveBeenCalled(); + expect(result.successful).toBe(true); + expect(result.data).toEqual(mockLibraryRoles); + }); + + it('应该成功替换角色', async () => { + (replaceRole as jest.Mock).mockResolvedValue({ + successful: true, + data: { success: true }, + }); + + const result = await replaceRole({ + currentRoleId: 'role1', + replaceRoleId: 'newRoleId' + }); + + expect(replaceRole).toHaveBeenCalledWith({ + currentRoleId: 'role1', + replaceRoleId: 'newRoleId' + }); + expect(result.successful).toBe(true); + }); + + it('替换角色失败时应该返回错误信息', async () => { + (replaceRole as jest.Mock).mockResolvedValue({ + successful: false, + message: '替换失败', + }); + + const result = await replaceRole({ + currentRoleId: 'role1', + replaceRoleId: 'newRoleId' + }); + + expect(result.successful).toBe(false); + expect(result.message).toBe('替换失败'); + }); + }); + + describe('角色形象应用到多个分镜测试', () => { + it('应该成功获取角色分镜列表', async () => { + const mockShots = [mockShotEntity]; + (getRoleShots as jest.Mock).mockResolvedValue({ + successful: true, + data: { + shots: mockShots, + appliedShotIds: [], + }, + }); + + const result = await getRoleShots({ roleId: 'role1' }); + + expect(getRoleShots).toHaveBeenCalledWith({ roleId: 'role1' }); + expect(result.successful).toBe(true); + expect(result.data.shots).toEqual(mockShots); + expect(result.data.appliedShotIds).toEqual([]); + }); + + it('应该成功应用角色到选中的分镜', async () => { + (applyRoleToShots as jest.Mock).mockResolvedValue({ + successful: true, + data: { success: true }, + }); + + mockRoleEditUseCase.applyRole.mockResolvedValue({} as any); + + const result = await applyRoleToShots({ + roleId: 'role1', + shotIds: ['shot1', 'shot2'] + }); + + expect(applyRoleToShots).toHaveBeenCalledWith({ + roleId: 'role1', + shotIds: ['shot1', 'shot2'] + }); + expect(result.successful).toBe(true); + }); + + it('应用角色失败时应该返回错误信息', async () => { + (applyRoleToShots as jest.Mock).mockResolvedValue({ + successful: false, + message: '应用失败', + }); + + const result = await applyRoleToShots({ + roleId: 'role1', + shotIds: ['shot1'] + }); + + expect(result.successful).toBe(false); + expect(result.message).toBe('应用失败'); + }); + }); + + describe('UseCase业务逻辑测试', () => { + it('RoleEditUseCase应该正确初始化', () => { + const roleItem = new RoleItem(mockRoleEntity); + const useCase = new RoleEditUseCase(roleItem); + + expect(RoleEditUseCase).toHaveBeenCalledWith(roleItem); + expect(useCase).toBeDefined(); + }); + + it('TextEditUseCase应该正确初始化', () => { + const textItem = new TextItem(mockTextEntity); + const useCase = new TextEditUseCase(textItem); + + expect(TextEditUseCase).toHaveBeenCalledWith(textItem); + expect(useCase).toBeDefined(); + }); + + it('TagEditUseCase应该正确初始化', () => { + const tagItem = new TagItem(mockTagEntity1); + const useCase = new TagEditUseCase(tagItem); + + expect(TagEditUseCase).toHaveBeenCalledWith(tagItem); + expect(useCase).toBeDefined(); + }); + }); + + describe('Domain实体测试', () => { + it('RoleItem应该正确包装RoleEntity', () => { + const roleItem = new RoleItem(mockRoleEntity); + + expect(RoleItem).toHaveBeenCalledWith(mockRoleEntity); + expect(roleItem.entity).toEqual(mockRoleEntity); + expect(roleItem.disableEdit).toBe(false); + }); + + it('TextItem应该正确包装AITextEntity', () => { + const textItem = new TextItem(mockTextEntity); + + expect(TextItem).toHaveBeenCalledWith(mockTextEntity); + expect(textItem.entity).toEqual(mockTextEntity); + expect(textItem.disableEdit).toBe(false); + }); + + it('TagItem应该正确包装TagEntity', () => { + const tagItem = new TagItem(mockTagEntity1); + + expect(TagItem).toHaveBeenCalledWith(mockTagEntity1); + expect(tagItem.entity).toEqual(mockTagEntity1); + expect(tagItem.disableEdit).toBe(false); + }); + }); + + describe('错误处理测试', () => { + it('API调用失败时应该正确处理错误', async () => { + (getRoleList as jest.Mock).mockRejectedValue(new Error('网络错误')); + + await expect(getRoleList({ projectId: 'project1' })).rejects.toThrow('网络错误'); + }); + + it('API返回失败状态时应该正确处理', async () => { + (getRoleList as jest.Mock).mockResolvedValue({ + successful: false, + message: '服务器错误', + }); + + const result = await getRoleList({ projectId: 'project1' }); + + expect(result.successful).toBe(false); + expect(result.message).toBe('服务器错误'); + }); + }); +}); + diff --git a/app/service/usecase/RoleEditUseCase.ts b/app/service/usecase/RoleEditUseCase.ts index 9cd2ccf..15a8c37 100644 --- a/app/service/usecase/RoleEditUseCase.ts +++ b/app/service/usecase/RoleEditUseCase.ts @@ -1,6 +1,6 @@ -import { RoleEntity } from '../domain/Entities'; +import { RoleEntity, AITextEntity, TagEntity } from '../domain/Entities'; import { RoleItem, TagItem, TextItem } from '../domain/Item'; -import { regenerateRole, applyRoleToShots } from '@/api/video_flow'; +import { regenerateRole, applyRoleToShots, getRoleData } from '@/api/video_flow'; /** * 角色图编辑用例 @@ -49,4 +49,42 @@ export class RoleEditUseCase { shotIds, }); } + + /** + * 重新获取当前角色的数据 + * @description 从服务器重新获取当前角色的AI文本和标签数据,并更新当前实体 + * @returns Promise<{ text: AITextEntity; tags: TagEntity[] }> 角色相关的AI文本和标签数据 + * @throws {Error} 当API调用失败时抛出错误 + */ + async refreshRoleData(): Promise<{ text: AITextEntity; tags: TagEntity[] }> { + const roleId = this.roleItem.entity.id; + + if (!roleId) { + throw new Error('角色ID不存在,无法获取角色数据'); + } + + const response = await getRoleData({ + roleId: roleId + }); + + if (response.successful) { + // 更新当前角色的实体数据 + const { text, tags } = response.data; + + // 更新角色实体中的相关字段 + const updatedRoleEntity = { + ...this.roleItem.entity, + generateTextId: text.id, // 更新AI文本ID + tagIds: tags.map(tag => tag.id), // 更新标签ID列表 + updatedAt: Date.now(), // 更新时间戳 + }; + + // 更新当前UseCase中的实体 + this.roleItem.setEntity(updatedRoleEntity); + + return response.data; + } else { + throw new Error(`获取角色数据失败: ${response.message}`); + } + } } diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..028307e --- /dev/null +++ b/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + testEnvironment: 'node', // 使用 Node 环境,避免 DOM 相关依赖 + transform: { + '^.+\\.tsx?$': 'ts-jest', // 支持 TypeScript + }, + moduleNameMapper: { + '^@/(.*)$': '/$1', // 支持 Next.js 的 @ 别名 + }, + };