From cfefe69a96227de20b90c6907b8788dd48357a50 Mon Sep 17 00:00:00 2001 From: "gru-agent[bot]" <185149714+gru-agent[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 13:30:10 +0800 Subject: [PATCH] Add unit tests for request utility functions in the API module. (#5254) Co-authored-by: gru-agent[bot] <185149714+gru-agent[bot]@users.noreply.github.com> --- projects/app/src/web/common/api/request.ts | 15 ++- test/cases/web/common/api/request.test.ts | 135 +++++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 test/cases/web/common/api/request.test.ts diff --git a/projects/app/src/web/common/api/request.ts b/projects/app/src/web/common/api/request.ts index cc3f8f083..388a92b74 100644 --- a/projects/app/src/web/common/api/request.ts +++ b/projects/app/src/web/common/api/request.ts @@ -35,7 +35,7 @@ const maxQuantityMap: Record< }[] > = {}; -/* +/* Every request generates a unique sign If the number of requests exceeds maxQuantity, cancel the earliest request and initiate a new request */ @@ -112,6 +112,7 @@ function responseError(err: any) { const isOutlinkPage = { '/chat/share': true, '/chat/team': true, + '/chat': true, '/login': true }[window.location.pathname]; @@ -223,3 +224,15 @@ export function PUT(url: string, data = {}, config: ConfigType = export function DELETE(url: string, data = {}, config: ConfigType = {}): Promise { return request(url, data, config, 'DELETE'); } + +export { + maxQuantityMap, + checkMaxQuantity, + requestFinish, + startInterceptors, + responseSuccess, + checkRes, + responseError, + instance, + request +}; diff --git a/test/cases/web/common/api/request.test.ts b/test/cases/web/common/api/request.test.ts new file mode 100644 index 000000000..49d52fa16 --- /dev/null +++ b/test/cases/web/common/api/request.test.ts @@ -0,0 +1,135 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import { + maxQuantityMap, + checkMaxQuantity, + requestFinish, + checkRes, + responseError +} from '../../../../../projects/app/src/web/common/api/request'; +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; +import { TOKEN_ERROR_CODE } from '@fastgpt/global/common/error/errorCode'; + +vi.mock('@fastgpt/web/common/system/utils', () => ({ + getWebReqUrl: vi.fn().mockReturnValue('http://test.com') +})); + +// Mock window.location +const mockLocation = { + pathname: '/test', + replace: vi.fn(), + search: '' +}; + +vi.stubGlobal('window', { + location: mockLocation +}); + +describe('request utils', () => { + beforeEach(() => { + vi.clearAllMocks(); + Object.keys(maxQuantityMap).forEach((key) => delete maxQuantityMap[key]); + mockLocation.pathname = '/test'; + }); + + describe('checkMaxQuantity', () => { + it('should return empty object when maxQuantity is not set', () => { + const result = checkMaxQuantity({ url: 'test', maxQuantity: undefined }); + expect(result).toEqual({}); + }); + + it('should handle first request', () => { + const result = checkMaxQuantity({ url: 'test', maxQuantity: 2 }); + expect(result.id).toBeDefined(); + expect(result.abortSignal).toBeDefined(); + expect(maxQuantityMap['test']?.length).toBe(1); + }); + + it('should cancel oldest request when maxQuantity exceeded', () => { + const result1 = checkMaxQuantity({ url: 'test', maxQuantity: 2 }); + const result2 = checkMaxQuantity({ url: 'test', maxQuantity: 2 }); + const result3 = checkMaxQuantity({ url: 'test', maxQuantity: 2 }); + + expect(maxQuantityMap['test']?.length).toBe(2); + expect(maxQuantityMap['test']?.find((item) => item.id === result1.id)).toBeUndefined(); + }); + }); + + describe('requestFinish', () => { + it('should remove finished request', () => { + const { id } = checkMaxQuantity({ url: 'test', maxQuantity: 2 }); + requestFinish({ signId: id, url: 'test' }); + expect(maxQuantityMap['test']).toBeUndefined(); + }); + + it('should handle non-existent request', () => { + requestFinish({ signId: 'non-existent', url: 'test' }); + expect(maxQuantityMap['test']).toBeUndefined(); + }); + }); + + describe('checkRes', () => { + it('should return data for successful response', () => { + const response = { code: 200, data: 'test data', message: 'success' }; + expect(checkRes(response)).toBe('test data'); + }); + + it('should reject for error response', async () => { + const response = { code: 400, data: null, message: 'error' }; + await expect(checkRes(response)).rejects.toEqual(response); + }); + + it('should reject for undefined response', async () => { + await expect(checkRes(undefined)).rejects.toBe('服务器异常'); + }); + }); + + describe('responseError', () => { + it('should handle token error for non-outlink page', async () => { + mockLocation.pathname = '/dashboard'; + const err = { + response: { + data: { + code: Object.values(TOKEN_ERROR_CODE)[0] + } + } + }; + await expect(responseError(err)).rejects.toEqual({ + code: Object.values(TOKEN_ERROR_CODE)[0] + }); + }); + + it('should handle token error for outlink page', async () => { + mockLocation.pathname = '/chat/share'; + const err = { + response: { + data: { + code: Object.values(TOKEN_ERROR_CODE)[0] + } + } + }; + await expect(responseError(err)).rejects.toEqual({ + code: Object.values(TOKEN_ERROR_CODE)[0] + }); + }); + + it('should handle team error', async () => { + const err = { response: { data: { statusText: TeamErrEnum.aiPointsNotEnough } } }; + await expect(responseError(err)).rejects.toEqual({ + statusText: TeamErrEnum.aiPointsNotEnough + }); + }); + + it('should handle string error', async () => { + await expect(responseError('error message')).rejects.toEqual({ message: 'error message' }); + }); + + it('should handle undefined error', async () => { + await expect(responseError(undefined)).rejects.toEqual({ message: '未知错误' }); + }); + + it('should handle string data error', async () => { + const err = { response: { data: 'error string' } }; + await expect(responseError(err)).rejects.toBe('error string'); + }); + }); +});