mirror of
https://github.com/labring/FastGPT.git
synced 2026-05-07 01:02:55 +08:00
fix: use upload control file config (#6858)
* fix: skip allowed extensions * fix: use upload control file config Made-with: Cursor * docs: v4.14.18 * chore: update pro submodule * docs
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: 'V4.14.18(处理中)'
|
||||
description: 'FastGPT V4.14.18 更新说明'
|
||||
---
|
||||
|
||||
## 升级指南
|
||||
|
||||
### 1. 更新镜像 tag
|
||||
|
||||
- 更新 fastgpt-app(fastgpt 主服务) 镜像 tag: v4.14.18
|
||||
- 更新 fastgpt-pro(fastgpt 商业版) 镜像 tag: v4.14.18
|
||||
|
||||
## 🐛 修复
|
||||
|
||||
1. 修复了部分`工作流工具`、`用户表单节点`无法正确根据文件类型过滤并上传文件的问题
|
||||
@@ -2,6 +2,7 @@
|
||||
"title": "4.14.x",
|
||||
"description": "",
|
||||
"pages": [
|
||||
"41418",
|
||||
"41417",
|
||||
"41416",
|
||||
"41415",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"title": "4.14.x",
|
||||
"description": "",
|
||||
"pages": [
|
||||
"41418",
|
||||
"41417",
|
||||
"41416",
|
||||
"41415",
|
||||
|
||||
@@ -120,6 +120,7 @@ description: FastGPT 文档目录
|
||||
- [/self-host/upgrading/4-14/41415](/self-host/upgrading/4-14/41415)
|
||||
- [/self-host/upgrading/4-14/41416](/self-host/upgrading/4-14/41416)
|
||||
- [/self-host/upgrading/4-14/41417](/self-host/upgrading/4-14/41417)
|
||||
- [/self-host/upgrading/4-14/41418](/self-host/upgrading/4-14/41418)
|
||||
- [/self-host/upgrading/4-14/4142](/self-host/upgrading/4-14/4142)
|
||||
- [/self-host/upgrading/4-14/4143](/self-host/upgrading/4-14/4143)
|
||||
- [/self-host/upgrading/4-14/4144](/self-host/upgrading/4-14/4144)
|
||||
|
||||
@@ -95,6 +95,8 @@
|
||||
"content/introduction/guide/knowledge_base/collection_tags.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/introduction/guide/knowledge_base/dataset_engine.en.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/introduction/guide/knowledge_base/dataset_engine.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/introduction/guide/knowledge_base/dingtalk_dataset.en.mdx": "2026-04-29T20:39:24+08:00",
|
||||
"content/introduction/guide/knowledge_base/dingtalk_dataset.mdx": "2026-04-29T20:39:24+08:00",
|
||||
"content/introduction/guide/knowledge_base/lark_dataset.en.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/introduction/guide/knowledge_base/lark_dataset.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/introduction/guide/knowledge_base/rag.en.mdx": "2026-04-26T21:08:47+08:00",
|
||||
@@ -232,6 +234,7 @@
|
||||
"content/self-host/upgrading/4-14/41416.en.mdx": "2026-04-26T21:28:27+08:00",
|
||||
"content/self-host/upgrading/4-14/41416.mdx": "2026-04-26T22:41:57+08:00",
|
||||
"content/self-host/upgrading/4-14/41417.mdx": "2026-04-28T18:03:38+08:00",
|
||||
"content/self-host/upgrading/4-14/41418.mdx": "2026-05-06T10:14:45+08:00",
|
||||
"content/self-host/upgrading/4-14/4142.en.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/self-host/upgrading/4-14/4142.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/self-host/upgrading/4-14/4143.en.mdx": "2026-04-26T21:08:47+08:00",
|
||||
@@ -252,7 +255,7 @@
|
||||
"content/self-host/upgrading/4-14/41481.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/self-host/upgrading/4-14/4149.en.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/self-host/upgrading/4-14/4149.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/self-host/upgrading/4-15/4150.mdx": "2026-04-29T14:53:45+08:00",
|
||||
"content/self-host/upgrading/4-15/4150.mdx": "2026-04-29T20:39:24+08:00",
|
||||
"content/self-host/upgrading/outdated/40.en.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/self-host/upgrading/outdated/40.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/self-host/upgrading/outdated/41.en.mdx": "2026-04-26T21:08:47+08:00",
|
||||
@@ -393,8 +396,8 @@
|
||||
"content/self-host/upgrading/outdated/499.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/self-host/upgrading/upgrade-intruction.en.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/self-host/upgrading/upgrade-intruction.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/toc.en.mdx": "2026-04-26T21:28:27+08:00",
|
||||
"content/toc.mdx": "2026-04-28T18:03:38+08:00",
|
||||
"content/toc.en.mdx": "2026-04-29T20:39:24+08:00",
|
||||
"content/toc.mdx": "2026-05-06T10:14:45+08:00",
|
||||
"content/use-cases/app-cases/dalle3.en.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/use-cases/app-cases/dalle3.mdx": "2026-04-26T21:08:47+08:00",
|
||||
"content/use-cases/app-cases/english_essay_correction_bot.en.mdx": "2026-04-26T21:08:47+08:00",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"dev": "next dev --turbo",
|
||||
"start": "next start",
|
||||
"postinstall": "fumadocs-mdx",
|
||||
"format-doc": "zhlint --dir ./ *.mdx --fix",
|
||||
"format-doc": "pnpx zhlint --dir ./ *.mdx --fix",
|
||||
"initDocTime": "node ./script/initDocTime.js",
|
||||
"initDocToc": "node ./script/generateToc.js",
|
||||
"checkDocRefs": "node ./script/checkDocRefs.js",
|
||||
|
||||
@@ -28,8 +28,7 @@ export const PresignChatFilePostUrlSchema = z
|
||||
filename: z.string().min(1).describe('文件名'),
|
||||
appId: ObjectIdSchema.describe('应用ID'),
|
||||
chatId: z.string().min(1).describe('对话ID'),
|
||||
fileSelectConfig:
|
||||
AppFileSelectConfigTypeSchema.optional().describe('调试态前端当前文件选择配置'),
|
||||
fileSelectConfig: AppFileSelectConfigTypeSchema.describe('本次上传控件的文件选择配置'),
|
||||
outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据')
|
||||
})
|
||||
.meta({
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { authDatasetByTmbId } from '../../support/permission/dataset/auth';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { S3Sources } from '../../common/s3/contracts/type';
|
||||
import { getS3DatasetSource, S3DatasetSource } from '../../common/s3/sources/dataset';
|
||||
import { getS3ChatSource } from '../../common/s3/sources/chat';
|
||||
import { jwtSignS3DownloadToken, isS3ObjectKey } from '../../common/s3/utils';
|
||||
import { getLogger, LogCategories } from '../../common/logger';
|
||||
import { S3Buckets } from '../../common/s3/config/constants';
|
||||
@@ -67,7 +65,10 @@ export function replaceS3KeyToPreviewUrl(documentQuoteText: string, expiredTime:
|
||||
for (const match of matches.slice().reverse()) {
|
||||
const [full, bang, alt, objectKey] = match;
|
||||
|
||||
if (isS3ObjectKey(objectKey, 'dataset') || isS3ObjectKey(objectKey, 'chat')) {
|
||||
const allowedKeys: (keyof typeof S3Sources)[] = ['dataset', 'chat', 'temp'];
|
||||
const allowedKeysGuard = allowedKeys.some((key) => isS3ObjectKey(objectKey, key));
|
||||
|
||||
if (allowedKeysGuard) {
|
||||
const url = jwtSignS3DownloadToken({
|
||||
objectKey,
|
||||
bucketName: S3Buckets.private,
|
||||
|
||||
@@ -134,10 +134,11 @@ describe('replaceS3KeyToPreviewUrl', () => {
|
||||
expect(result).toBe(text);
|
||||
});
|
||||
|
||||
it('temp 前缀不应被替换', () => {
|
||||
it('temp 前缀应被替换', () => {
|
||||
const text = '';
|
||||
const result = replaceS3KeyToPreviewUrl(text, expiredTime);
|
||||
expect(result).toBe(text);
|
||||
expect(result).toContain('https://example.com/api/system/file/download/mock-jwt-token-');
|
||||
expect(result).toContain('temp/team1/temp-file.png');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+1
-1
Submodule pro updated: 1a56dfc0d3...c8a75dd14a
@@ -62,10 +62,6 @@ const FileSelector = ({
|
||||
const appId = useContextSelector(WorkflowRuntimeContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(WorkflowRuntimeContext, (v) => v.chatId);
|
||||
const outLinkAuthData = useContextSelector(WorkflowRuntimeContext, (v) => v.outLinkAuthData);
|
||||
const runtimeFileSelectConfig = useContextSelector(
|
||||
WorkflowRuntimeContext,
|
||||
(v) => v.runtimeFileSelectConfig
|
||||
);
|
||||
const setFileUploadingCount = useContextSelector(
|
||||
WorkflowRuntimeContext,
|
||||
(v) => v.setFileUploadingCount
|
||||
@@ -95,6 +91,26 @@ const FileSelector = ({
|
||||
canSelectCustomFileExtension,
|
||||
customFileExtensionList
|
||||
]);
|
||||
const fileSelectConfig = useMemo<AppFileSelectConfigType>(
|
||||
() => ({
|
||||
maxFiles,
|
||||
canSelectFile,
|
||||
canSelectImg,
|
||||
canSelectVideo,
|
||||
canSelectAudio,
|
||||
canSelectCustomFileExtension,
|
||||
customFileExtensionList
|
||||
}),
|
||||
[
|
||||
maxFiles,
|
||||
canSelectFile,
|
||||
canSelectImg,
|
||||
canSelectVideo,
|
||||
canSelectAudio,
|
||||
canSelectCustomFileExtension,
|
||||
customFileExtensionList
|
||||
]
|
||||
);
|
||||
// 文件数量限制:组件参数 || 团队套餐 || 系统配置 || 默认值
|
||||
const maxSelectFiles =
|
||||
maxFiles ||
|
||||
@@ -131,7 +147,7 @@ const FileSelector = ({
|
||||
filename: file.rawFile.name,
|
||||
appId,
|
||||
chatId,
|
||||
fileSelectConfig: runtimeFileSelectConfig,
|
||||
fileSelectConfig,
|
||||
outLinkAuthData
|
||||
});
|
||||
|
||||
@@ -181,14 +197,7 @@ const FileSelector = ({
|
||||
})
|
||||
);
|
||||
},
|
||||
[
|
||||
handleChangeFiles,
|
||||
setFileUploadingCount,
|
||||
appId,
|
||||
chatId,
|
||||
runtimeFileSelectConfig,
|
||||
outLinkAuthData
|
||||
]
|
||||
[handleChangeFiles, setFileUploadingCount, appId, chatId, fileSelectConfig, outLinkAuthData]
|
||||
);
|
||||
|
||||
// Selector props
|
||||
|
||||
@@ -23,7 +23,6 @@ import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
import { useCreation } from 'ahooks';
|
||||
import type { ChatTypeEnum } from './constants';
|
||||
import { ChatTypeEnum as ChatTypeEnumValue } from './constants';
|
||||
import type { ChatQuickAppType } from '@fastgpt/global/core/chat/setting/type';
|
||||
import { WorkflowRuntimeContextProvider } from '@/components/core/chat/ChatContainer/context/workflowRuntimeContext';
|
||||
|
||||
@@ -254,22 +253,11 @@ const Provider = ({
|
||||
getHistoryResponseData,
|
||||
chatType
|
||||
};
|
||||
const runtimeFileSelectConfig = useMemo(() => {
|
||||
if (chatType === ChatTypeEnumValue.test) {
|
||||
return fileSelectConfig;
|
||||
}
|
||||
if (chatType === ChatTypeEnumValue.home && !props.currentQuickAppId) {
|
||||
return fileSelectConfig;
|
||||
}
|
||||
return undefined;
|
||||
}, [chatType, fileSelectConfig, props.currentQuickAppId]);
|
||||
|
||||
return (
|
||||
<WorkflowRuntimeContextProvider
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
outLinkAuthData={formatOutLinkAuth}
|
||||
runtimeFileSelectConfig={runtimeFileSelectConfig}
|
||||
>
|
||||
<ChatBoxContext.Provider value={value}>{children}</ChatBoxContext.Provider>
|
||||
</WorkflowRuntimeContextProvider>
|
||||
|
||||
@@ -17,8 +17,6 @@ import { type OutLinkChatAuthProps } from '@fastgpt/global/support/permission/ch
|
||||
import { getPresignedChatFileGetUrl, getUploadChatFilePresignedUrl } from '@/web/common/file/api';
|
||||
import { getUploadFileType } from '@fastgpt/global/core/app/constants';
|
||||
import { putFileToS3 } from '@fastgpt/web/common/file/utils';
|
||||
import { WorkflowRuntimeContext } from '../../context/workflowRuntimeContext';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
type UseFileUploadOptions = {
|
||||
fileSelectConfig: AppFileSelectConfigType;
|
||||
@@ -35,10 +33,6 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
const runtimeFileSelectConfig = useContextSelector(
|
||||
WorkflowRuntimeContext,
|
||||
(v) => v.runtimeFileSelectConfig
|
||||
);
|
||||
|
||||
const {
|
||||
update: updateFiles,
|
||||
@@ -196,7 +190,7 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
||||
filename: copyFile.rawFile.name,
|
||||
appId,
|
||||
chatId,
|
||||
fileSelectConfig: runtimeFileSelectConfig,
|
||||
fileSelectConfig,
|
||||
outLinkAuthData
|
||||
});
|
||||
|
||||
@@ -242,10 +236,10 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
||||
appId,
|
||||
chatId,
|
||||
fileList,
|
||||
fileSelectConfig,
|
||||
outLinkAuthData,
|
||||
removeFiles,
|
||||
replaceFiles,
|
||||
runtimeFileSelectConfig,
|
||||
t,
|
||||
toast,
|
||||
updateFiles
|
||||
|
||||
@@ -294,7 +294,6 @@ const PluginRunContextProvider = ({
|
||||
appId={props.appId}
|
||||
chatId={props.chatId}
|
||||
outLinkAuthData={props.outLinkAuthData || {}}
|
||||
runtimeFileSelectConfig={props.runtimeFileSelectConfig}
|
||||
>
|
||||
<PluginRunContext.Provider value={contextValue}>{children}</PluginRunContext.Provider>
|
||||
</WorkflowRuntimeContextProvider>
|
||||
|
||||
@@ -5,7 +5,6 @@ import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/ch
|
||||
import React from 'react';
|
||||
import type { onStartChatType } from '../type';
|
||||
import { ChatBoxInputFormType } from '../ChatBox/type';
|
||||
import type { AppFileSelectConfigType } from '@fastgpt/global/core/app/type/config.schema';
|
||||
|
||||
export type PluginRunBoxProps = {
|
||||
appId: string;
|
||||
@@ -15,5 +14,4 @@ export type PluginRunBoxProps = {
|
||||
onStartChat?: onStartChatType;
|
||||
onNewChat?: () => void;
|
||||
showTab?: PluginRunBoxTabEnum; // 如何设置了该字段,全局都 tab 不生效
|
||||
runtimeFileSelectConfig?: AppFileSelectConfigType;
|
||||
};
|
||||
|
||||
+1
-7
@@ -1,5 +1,4 @@
|
||||
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import type { AppFileSelectConfigType } from '@fastgpt/global/core/app/type/config.schema';
|
||||
import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance';
|
||||
import { useState } from 'react';
|
||||
import { createContext } from 'use-context-selector';
|
||||
@@ -8,7 +7,6 @@ type WorkflowRuntimeContextType = {
|
||||
outLinkAuthData: OutLinkChatAuthProps;
|
||||
appId: string;
|
||||
chatId: string;
|
||||
runtimeFileSelectConfig?: AppFileSelectConfigType;
|
||||
|
||||
fileUploading: boolean;
|
||||
setFileUploadingCount: React.Dispatch<React.SetStateAction<number>>;
|
||||
@@ -18,7 +16,6 @@ export const WorkflowRuntimeContext = createContext<WorkflowRuntimeContextType>(
|
||||
outLinkAuthData: {},
|
||||
appId: '',
|
||||
chatId: '',
|
||||
runtimeFileSelectConfig: undefined,
|
||||
fileUploading: false,
|
||||
setFileUploadingCount: () => {}
|
||||
});
|
||||
@@ -27,13 +24,11 @@ export const WorkflowRuntimeContextProvider = ({
|
||||
appId,
|
||||
chatId,
|
||||
outLinkAuthData,
|
||||
runtimeFileSelectConfig,
|
||||
children
|
||||
}: {
|
||||
appId: string;
|
||||
chatId: string;
|
||||
outLinkAuthData: OutLinkChatAuthProps;
|
||||
runtimeFileSelectConfig?: AppFileSelectConfigType;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const [fileUploadingCount, setFileUploadingCount] = useState<number>(0);
|
||||
@@ -44,11 +39,10 @@ export const WorkflowRuntimeContextProvider = ({
|
||||
outLinkAuthData,
|
||||
appId,
|
||||
chatId,
|
||||
runtimeFileSelectConfig,
|
||||
fileUploading,
|
||||
setFileUploadingCount
|
||||
}),
|
||||
[outLinkAuthData, appId, chatId, runtimeFileSelectConfig, fileUploading, setFileUploadingCount]
|
||||
[outLinkAuthData, appId, chatId, fileUploading, setFileUploadingCount]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -158,7 +158,6 @@ export const useChatTest = ({
|
||||
chatId={chatId}
|
||||
onNewChat={restartChat}
|
||||
onStartChat={startChat}
|
||||
runtimeFileSelectConfig={chatConfig.fileSelectConfig}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
|
||||
+6
-1
@@ -133,6 +133,11 @@ export const usePreviewFileUpload = ({ fileCtrl, appId, chatId }: UsePreviewFile
|
||||
filename: copyFile.rawFile.name,
|
||||
appId,
|
||||
chatId,
|
||||
fileSelectConfig: {
|
||||
maxFiles: maxSelectFiles,
|
||||
canSelectFile: true,
|
||||
canSelectImg: true
|
||||
},
|
||||
outLinkAuthData: undefined
|
||||
});
|
||||
|
||||
@@ -171,7 +176,7 @@ export const usePreviewFileUpload = ({ fileCtrl, appId, chatId }: UsePreviewFile
|
||||
);
|
||||
|
||||
removeFiles(errorIndexes);
|
||||
}, [appId, chatId, fileList, removeFiles, replaceFiles, t, toast, updateFiles]);
|
||||
}, [appId, chatId, fileList, maxSelectFiles, removeFiles, replaceFiles, t, toast, updateFiles]);
|
||||
|
||||
const sortFileList = useMemo(
|
||||
() =>
|
||||
|
||||
@@ -3,16 +3,12 @@ import { NextAPI } from '@/service/middleware/entry';
|
||||
import type { CreatePostPresignedUrlResponseType } from '@fastgpt/global/common/file/s3/type';
|
||||
import { getS3ChatSource } from '@fastgpt/service/common/s3/sources/chat';
|
||||
import { getAllowedExtensionsFromFileSelectConfig } from '@fastgpt/service/common/s3/utils/uploadConstraints';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { authFrequencyLimit } from '@fastgpt/service/common/system/frequencyLimit/utils';
|
||||
import { addSeconds } from 'date-fns';
|
||||
import { PresignChatFilePostUrlSchema } from '@fastgpt/global/openapi/core/chat/file/api';
|
||||
import { getTeamPlanStatus } from '@fastgpt/service/support/wallet/sub/utils';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { S3ErrEnum } from '@fastgpt/global/common/error/code/s3';
|
||||
import { MongoChatSetting } from '@fastgpt/service/core/chat/setting/schema';
|
||||
import { env } from '@fastgpt/service/env';
|
||||
|
||||
async function handler(req: ApiRequestProps): Promise<CreatePostPresignedUrlResponseType> {
|
||||
@@ -27,26 +23,8 @@ async function handler(req: ApiRequestProps): Promise<CreatePostPresignedUrlResp
|
||||
...outLinkAuthData
|
||||
});
|
||||
|
||||
const [planStatus, app] = await Promise.all([
|
||||
getTeamPlanStatus({ teamId }),
|
||||
MongoApp.findById(appId, 'chatConfig.fileSelectConfig').lean()
|
||||
]);
|
||||
const effectiveFileSelectConfig = fileSelectConfig
|
||||
? await (async () => {
|
||||
const isHomeApp = await MongoChatSetting.exists({ teamId, appId });
|
||||
|
||||
if (!isHomeApp) {
|
||||
await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: WritePermissionVal
|
||||
});
|
||||
}
|
||||
return fileSelectConfig;
|
||||
})()
|
||||
: app?.chatConfig?.fileSelectConfig;
|
||||
const allowedExtensions = getAllowedExtensionsFromFileSelectConfig(effectiveFileSelectConfig);
|
||||
const planStatus = await getTeamPlanStatus({ teamId });
|
||||
const allowedExtensions = getAllowedExtensionsFromFileSelectConfig(fileSelectConfig);
|
||||
|
||||
if (!env.SKIP_FILE_TYPE_CHECK && allowedExtensions.length === 0) {
|
||||
return Promise.reject(S3ErrEnum.fileUploadDisabled);
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import { S3ErrEnum } from '@fastgpt/global/common/error/code/s3';
|
||||
import type { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
authChatCrud: vi.fn(),
|
||||
getTeamPlanStatus: vi.fn(),
|
||||
authFrequencyLimit: vi.fn(),
|
||||
createUploadChatFileURL: vi.fn(),
|
||||
findAppById: vi.fn(),
|
||||
chatSettingExists: vi.fn(),
|
||||
authApp: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('@/service/middleware/entry', () => ({
|
||||
NextAPI: (handler: unknown) => handler
|
||||
}));
|
||||
|
||||
vi.mock('@/service/support/permission/auth/chat', () => ({
|
||||
authChatCrud: mocks.authChatCrud
|
||||
}));
|
||||
|
||||
vi.mock('@fastgpt/service/support/wallet/sub/utils', () => ({
|
||||
getTeamPlanStatus: mocks.getTeamPlanStatus
|
||||
}));
|
||||
|
||||
vi.mock('@fastgpt/service/common/system/frequencyLimit/utils', () => ({
|
||||
authFrequencyLimit: mocks.authFrequencyLimit
|
||||
}));
|
||||
|
||||
vi.mock('@fastgpt/service/common/s3/sources/chat', () => ({
|
||||
getS3ChatSource: () => ({
|
||||
createUploadChatFileURL: mocks.createUploadChatFileURL
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('@fastgpt/service/env', () => ({
|
||||
env: {
|
||||
SKIP_FILE_TYPE_CHECK: false
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@fastgpt/service/core/app/schema', () => ({
|
||||
MongoApp: {
|
||||
findById: mocks.findAppById
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@fastgpt/service/core/chat/setting/schema', () => ({
|
||||
MongoChatSetting: {
|
||||
exists: mocks.chatSettingExists
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@fastgpt/service/support/permission/app/auth', () => ({
|
||||
authApp: mocks.authApp
|
||||
}));
|
||||
|
||||
import handler from '@/pages/api/core/chat/file/presignChatFilePostUrl';
|
||||
|
||||
const appId = '507f1f77bcf86cd799439011';
|
||||
const chatId = 'chat-id';
|
||||
const filename = 'demo.png';
|
||||
const presignHandler = handler as unknown as (req: ApiRequestProps) => Promise<unknown>;
|
||||
|
||||
const callHandler = (body: Record<string, unknown>) =>
|
||||
presignHandler({
|
||||
body
|
||||
} as ApiRequestProps);
|
||||
|
||||
describe('presignChatFilePostUrl', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mocks.authChatCrud.mockResolvedValue({
|
||||
teamId: 'team-id',
|
||||
uid: 'user-id'
|
||||
});
|
||||
mocks.getTeamPlanStatus.mockResolvedValue({
|
||||
standard: {
|
||||
maxUploadFileCount: 20,
|
||||
maxUploadFileSize: 15
|
||||
}
|
||||
});
|
||||
mocks.createUploadChatFileURL.mockResolvedValue({
|
||||
url: 'https://example.com/upload-token',
|
||||
key: 'chat/app/user/chat/demo.png',
|
||||
headers: {
|
||||
'content-type': 'image/png'
|
||||
},
|
||||
maxSize: 15 * 1024 * 1024
|
||||
});
|
||||
});
|
||||
|
||||
it('uses request fileSelectConfig as upload constraints without reading app chatConfig', async () => {
|
||||
await expect(
|
||||
callHandler({
|
||||
filename,
|
||||
appId,
|
||||
chatId,
|
||||
fileSelectConfig: {
|
||||
canSelectImg: true
|
||||
}
|
||||
})
|
||||
).resolves.toMatchObject({
|
||||
url: 'https://example.com/upload-token'
|
||||
});
|
||||
|
||||
expect(mocks.findAppById).not.toHaveBeenCalled();
|
||||
expect(mocks.chatSettingExists).not.toHaveBeenCalled();
|
||||
expect(mocks.authApp).not.toHaveBeenCalled();
|
||||
expect(mocks.createUploadChatFileURL).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
appId,
|
||||
chatId,
|
||||
filename,
|
||||
uId: 'user-id',
|
||||
allowedExtensions: expect.arrayContaining([
|
||||
'.jpg',
|
||||
'.jpeg',
|
||||
'.png',
|
||||
'.gif',
|
||||
'.bmp',
|
||||
'.webp'
|
||||
])
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects upload when fileSelectConfig does not enable any file type', async () => {
|
||||
await expect(
|
||||
callHandler({
|
||||
filename,
|
||||
appId,
|
||||
chatId,
|
||||
fileSelectConfig: {}
|
||||
})
|
||||
).rejects.toBe(S3ErrEnum.fileUploadDisabled);
|
||||
|
||||
expect(mocks.createUploadChatFileURL).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user