From b0297d29153c6698efd4e54e3c0d3bb2e3b82ada Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Tue, 29 Apr 2025 12:15:07 +0800 Subject: [PATCH] Add test (#4721) * Add unit tests for Markdown utility functions and CodeClassNameEnum. (#4716) Co-authored-by: gru-agent[bot] <185149714+gru-agent[bot]@users.noreply.github.com> * Add unit tests for authChatCrud and authCollectionInChat functions in chat service. (#4718) Co-authored-by: gru-agent[bot] <185149714+gru-agent[bot]@users.noreply.github.com> --------- Co-authored-by: gru-agent[bot] <185149714+gru-agent[bot]@users.noreply.github.com> --- .../service/support/permission/auth/chat.ts | 2 + test/cases/components/Markdown/utils.test.ts | 59 ++++ .../support/permission/auth/chat.test.ts | 254 ++++++++++++++++++ 3 files changed, 315 insertions(+) create mode 100644 test/cases/components/Markdown/utils.test.ts create mode 100644 test/cases/service/support/permission/auth/chat.test.ts diff --git a/projects/app/src/service/support/permission/auth/chat.ts b/projects/app/src/service/support/permission/auth/chat.ts index e42b862e2..dd9d0beb6 100644 --- a/projects/app/src/service/support/permission/auth/chat.ts +++ b/projects/app/src/service/support/permission/auth/chat.ts @@ -244,3 +244,5 @@ export const authCollectionInChat = async ({ } catch (error) {} return Promise.reject(DatasetErrEnum.unAuthDatasetFile); }; + +export { defaultResponseShow }; diff --git a/test/cases/components/Markdown/utils.test.ts b/test/cases/components/Markdown/utils.test.ts new file mode 100644 index 000000000..3d9737924 --- /dev/null +++ b/test/cases/components/Markdown/utils.test.ts @@ -0,0 +1,59 @@ +import { describe, it, expect } from 'vitest'; +import { mdTextFormat, CodeClassNameEnum } from '@/components/Markdown/utils'; + +describe('Markdown utils', () => { + describe('mdTextFormat', () => { + it('should format latex expressions correctly', () => { + const input = 'Here is some math: \\[x^2 + y^2 = z^2\\] and inline \\(a+b=c\\)'; + const expected = 'Here is some math: $$x^2 + y^2 = z^2$$ and inline $a+b=c$'; + expect(mdTextFormat(input)).toBe(expected); + }); + + it('should not format latex expressions inside code blocks', () => { + const input = '```math\n\\[x^2\\]\n```\n`\\[y^2\\]`'; + expect(mdTextFormat(input)).toBe(input); + }); + + it('should convert quote references to proper markdown links', () => { + const input = '[123456789012345678901234]'; + const expected = '[123456789012345678901234](QUOTE)'; + expect(mdTextFormat(input)).toBe(expected); + }); + + it('should not convert invalid quote references', () => { + const input = '[12345] [abcdef] [123456789012345678901234](test)'; + expect(mdTextFormat(input)).toBe(input); + }); + + it('should add spaces between URLs and Chinese punctuation', () => { + const input = 'Check https://example.com,here。'; + const expected = 'Check https://example.com ,here。'; + expect(mdTextFormat(input)).toBe(expected); + }); + + it('should handle complex text with multiple patterns', () => { + const input = + 'Math \\[x^2\\] with link https://test.com,and quote [123456789012345678901234]'; + const expected = + 'Math $$x^2$$ with link https://test.com ,and quote [123456789012345678901234](QUOTE)'; + expect(mdTextFormat(input)).toBe(expected); + }); + }); + + describe('CodeClassNameEnum', () => { + it('should have correct enum values', () => { + expect(CodeClassNameEnum.guide).toBe('guide'); + expect(CodeClassNameEnum.questionguide).toBe('questionguide'); + expect(CodeClassNameEnum.mermaid).toBe('mermaid'); + expect(CodeClassNameEnum.echarts).toBe('echarts'); + expect(CodeClassNameEnum.quote).toBe('quote'); + expect(CodeClassNameEnum.files).toBe('files'); + expect(CodeClassNameEnum.latex).toBe('latex'); + expect(CodeClassNameEnum.iframe).toBe('iframe'); + expect(CodeClassNameEnum.html).toBe('html'); + expect(CodeClassNameEnum.svg).toBe('svg'); + expect(CodeClassNameEnum.video).toBe('video'); + expect(CodeClassNameEnum.audio).toBe('audio'); + }); + }); +}); diff --git a/test/cases/service/support/permission/auth/chat.test.ts b/test/cases/service/support/permission/auth/chat.test.ts new file mode 100644 index 000000000..a30ee43de --- /dev/null +++ b/test/cases/service/support/permission/auth/chat.test.ts @@ -0,0 +1,254 @@ +import { describe, expect, it, vi } from 'vitest'; +import { authChatCrud, authCollectionInChat } from '@/service/support/permission/auth/chat'; +import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; +import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; +import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; +import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset'; +import { authApp } from '@fastgpt/service/support/permission/app/auth'; +import { authOutLink } from '@/service/support/permission/auth/outLink'; + +vi.mock('@fastgpt/service/core/chat/chatSchema', () => ({ + MongoChat: { + findOne: vi.fn() + } +})); + +vi.mock('@fastgpt/service/core/chat/chatItemSchema', () => ({ + MongoChatItem: { + findOne: vi.fn() + } +})); + +vi.mock('@fastgpt/service/support/permission/app/auth'); +vi.mock('@/service/support/permission/auth/outLink'); + +describe('authChatCrud', () => { + it('should reject if no appId provided', async () => { + await expect(authChatCrud({ appId: '' })).rejects.toBe(ChatErrEnum.unAuthChat); + }); + + it('should auth outLink without chatId', async () => { + vi.mocked(authOutLink).mockResolvedValue({ + outLinkConfig: { + teamId: 'team1', + tmbId: 'tmb1', + responseDetail: true, + showNodeStatus: true, + showRawSource: true + }, + uid: 'user1', + appId: 'app1' + }); + + const result = await authChatCrud({ + appId: 'app1', + shareId: 'share1', + outLinkUid: 'user1' + }); + + expect(result).toMatchObject({ + teamId: 'team1', + tmbId: 'tmb1', + uid: 'user1', + responseDetail: true, + showNodeStatus: true, + showRawSource: true, + authType: AuthUserTypeEnum.outLink + }); + }); + + it('should auth outLink with chatId', async () => { + const mockChat = { + appId: 'app1', + outLinkUid: 'user1' + }; + + vi.mocked(authOutLink).mockResolvedValue({ + outLinkConfig: { + teamId: 'team1', + tmbId: 'tmb1', + responseDetail: true, + showNodeStatus: true, + showRawSource: true + }, + uid: 'user1', + appId: 'app1' + }); + + vi.mocked(MongoChat.findOne).mockReturnValue({ + lean: () => mockChat + } as any); + + const result = await authChatCrud({ + appId: 'app1', + chatId: 'chat1', + shareId: 'share1', + outLinkUid: 'user1' + }); + + expect(result).toMatchObject({ + teamId: 'team1', + tmbId: 'tmb1', + uid: 'user1', + chat: mockChat, + responseDetail: true, + showNodeStatus: true, + showRawSource: true, + authType: AuthUserTypeEnum.outLink + }); + }); + + it('should reject if outLink appId does not match', async () => { + vi.mocked(authOutLink).mockResolvedValue({ + outLinkConfig: { + teamId: 'team1', + tmbId: 'tmb1' + }, + uid: 'user1', + appId: 'different-app' + }); + + await expect( + authChatCrud({ + appId: 'app1', + shareId: 'share1', + outLinkUid: 'user1' + }) + ).rejects.toBe(ChatErrEnum.unAuthChat); + }); + + it('should auth with cookie', async () => { + vi.mocked(authApp).mockResolvedValue({ + teamId: 'team1', + tmbId: 'tmb1', + permission: { + hasManagePer: true + }, + authType: AuthUserTypeEnum.team + }); + + const result = await authChatCrud({ + appId: 'app1', + req: {} as any + }); + + expect(result).toEqual({ + teamId: 'team1', + tmbId: 'tmb1', + uid: 'tmb1', + responseDetail: true, + showNodeStatus: true, + showRawSource: true, + authType: AuthUserTypeEnum.team + }); + }); +}); + +describe('authCollectionInChat', () => { + it('should reject if chat item not found', async () => { + vi.mocked(MongoChatItem.findOne).mockReturnValue({ + lean: () => null + } as any); + + await expect( + authCollectionInChat({ + collectionIds: ['col1'], + appId: 'app1', + chatId: 'chat1', + chatItemDataId: 'item1' + }) + ).rejects.toBe(DatasetErrEnum.unAuthDatasetCollection); + }); + + it('should auth collection ids in chat item', async () => { + const mockChatItem = { + time: new Date(), + responseData: [ + { + quoteList: [{ collectionId: 'col1' }, { collectionId: 'col2' }] + } + ] + }; + + vi.mocked(MongoChatItem.findOne).mockReturnValue({ + lean: () => mockChatItem + } as any); + + const result = await authCollectionInChat({ + collectionIds: ['col1', 'col2'], + appId: 'app1', + chatId: 'chat1', + chatItemDataId: 'item1' + }); + + expect(result).toEqual({ + chatItem: mockChatItem + }); + }); + + it('should handle plugin, tool and loop details in response data', async () => { + const mockChatItem = { + time: new Date(), + responseData: [ + { + quoteList: [{ collectionId: 'col1' }], + pluginDetail: [ + { + quoteList: [{ collectionId: 'col2' }] + } + ], + toolDetail: [ + { + quoteList: [{ collectionId: 'col3' }] + } + ], + loopDetail: [ + { + quoteList: [{ collectionId: 'col4' }] + } + ] + } + ] + }; + + vi.mocked(MongoChatItem.findOne).mockReturnValue({ + lean: () => mockChatItem + } as any); + + const result = await authCollectionInChat({ + collectionIds: ['col1', 'col2', 'col3', 'col4'], + appId: 'app1', + chatId: 'chat1', + chatItemDataId: 'item1' + }); + + expect(result).toEqual({ + chatItem: mockChatItem + }); + }); + + it('should reject if collection ids not found in quotes', async () => { + const mockChatItem = { + time: new Date(), + responseData: [ + { + quoteList: [{ collectionId: 'col1' }] + } + ] + }; + + vi.mocked(MongoChatItem.findOne).mockReturnValue({ + lean: () => mockChatItem + } as any); + + await expect( + authCollectionInChat({ + collectionIds: ['col2'], + appId: 'app1', + chatId: 'chat1', + chatItemDataId: 'item1' + }) + ).rejects.toBe(DatasetErrEnum.unAuthDatasetFile); + }); +});