diff --git a/package.json b/package.json index 86e4c11a456a..0989eeb24016 100644 --- a/package.json +++ b/package.json @@ -18,15 +18,15 @@ }, "devDependencies": { "@chakra-ui/cli": "^2.4.1", - "@vitest/coverage-v8": "^3.0.2", + "@vitest/coverage-v8": "^3.0.9", "husky": "^8.0.3", "i18next": "23.16.8", "lint-staged": "^13.3.0", "next-i18next": "15.4.2", "prettier": "3.2.4", "react-i18next": "14.1.2", - "vitest": "^3.0.2", - "vitest-mongodb": "^1.0.1", + "vitest": "^3.0.9", + "mongodb-memory-server": "^10.1.4", "zhlint": "^0.7.4" }, "lint-staged": { diff --git a/packages/global/support/permission/memberGroup/type.d.ts b/packages/global/support/permission/memberGroup/type.d.ts index 5011d74f616f..a615bef4c1c6 100644 --- a/packages/global/support/permission/memberGroup/type.d.ts +++ b/packages/global/support/permission/memberGroup/type.d.ts @@ -17,23 +17,23 @@ type GroupMemberSchemaType = { role: `${GroupMemberRole}`; }; -type MemberGroupListItemType = MemberGroupSchemaType & { - members: T extends true +type MemberGroupListItemType = MemberGroupSchemaType & { + members: WithMembers extends true ? { tmbId: string; name: string; avatar: string; }[] : undefined; - count: T extends true ? number : undefined; - owner?: T extends true + count: WithMembers extends true ? number : undefined; + owner?: WithMembers extends true ? { tmbId: string; name: string; avatar: string; } : undefined; - permission: T extends true ? Permission : undefined; + permission: WithMembers extends true ? Permission : undefined; }; type GroupMemberItemType = { diff --git a/packages/global/support/permission/user/constant.ts b/packages/global/support/permission/user/constant.ts index 743753cbdf5e..b9a95da25404 100644 --- a/packages/global/support/permission/user/constant.ts +++ b/packages/global/support/permission/user/constant.ts @@ -1,22 +1,50 @@ import { PermissionKeyEnum } from '../constant'; import { PermissionListType } from '../type'; import { PermissionList } from '../constant'; -export const TeamPermissionList: PermissionListType = { +import { i18nT } from '../../../../web/i18n/utils'; +export enum TeamPermissionKeyEnum { + appCreate = 'appCreate', + datasetCreate = 'datasetCreate', + apikeyCreate = 'apikeyCreate' +} + +export const TeamPermissionList: PermissionListType = { [PermissionKeyEnum.read]: { ...PermissionList[PermissionKeyEnum.read], - value: 0b100 + value: 0b000100 }, [PermissionKeyEnum.write]: { ...PermissionList[PermissionKeyEnum.write], - value: 0b010 + value: 0b000110 }, [PermissionKeyEnum.manage]: { ...PermissionList[PermissionKeyEnum.manage], - value: 0b001 + value: 0b000101 + }, + [TeamPermissionKeyEnum.appCreate]: { + checkBoxType: 'multiple', + description: '', + name: i18nT('account_team:permission_appCreate'), + value: 0b001100 + }, + [TeamPermissionKeyEnum.datasetCreate]: { + checkBoxType: 'multiple', + description: '', + name: i18nT('account_team:permission_datasetCreate'), + value: 0b010100 + }, + [TeamPermissionKeyEnum.apikeyCreate]: { + checkBoxType: 'multiple', + description: '', + name: i18nT('account_team:permission_apikeyCreate'), + value: 0b100100 } }; export const TeamReadPermissionVal = TeamPermissionList['read'].value; export const TeamWritePermissionVal = TeamPermissionList['write'].value; export const TeamManagePermissionVal = TeamPermissionList['manage'].value; +export const TeamAppCreatePermissionVal = TeamPermissionList['appCreate'].value; +export const TeamDatasetCreatePermissionVal = TeamPermissionList['datasetCreate'].value; +export const TeamApikeyCreatePermissionVal = TeamPermissionList['apikeyCreate'].value; export const TeamDefaultPermissionVal = TeamReadPermissionVal; diff --git a/packages/service/common/mongo/index.ts b/packages/service/common/mongo/index.ts index 02b4213e6b85..3d4bb5d55a88 100644 --- a/packages/service/common/mongo/index.ts +++ b/packages/service/common/mongo/index.ts @@ -60,7 +60,7 @@ const addCommonMiddleware = (schema: mongoose.Schema) => { export const getMongoModel = (name: string, schema: mongoose.Schema) => { if (connectionMongo.models[name]) return connectionMongo.models[name] as Model; - console.log('Load model======', name); + if (process.env.NODE_ENV !== 'test') console.log('Load model======', name); addCommonMiddleware(schema); const model = connectionMongo.model(name, schema); diff --git a/packages/service/support/permission/auth/org.ts b/packages/service/support/permission/auth/org.ts index a569a18ae28f..fe5ba7cb7a31 100644 --- a/packages/service/support/permission/auth/org.ts +++ b/packages/service/support/permission/auth/org.ts @@ -2,7 +2,7 @@ import { TeamPermission } from '@fastgpt/global/support/permission/user/controll import { AuthModeType, AuthResponseType } from '../type'; import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; import { authUserPer } from '../user/auth'; -import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant'; +import { TeamManagePermissionVal } from '@fastgpt/global/support/permission/user/constant'; /* Team manager can control org @@ -15,7 +15,7 @@ export const authOrgMember = async ({ } & AuthModeType): Promise => { const result = await authUserPer({ ...props, - per: ManagePermissionVal + per: TeamManagePermissionVal }); const { teamId, tmbId, isRoot, tmb } = result; diff --git a/packages/web/i18n/en/account_team.json b/packages/web/i18n/en/account_team.json index 2a50f6cb00b0..135e72a98d78 100644 --- a/packages/web/i18n/en/account_team.json +++ b/packages/web/i18n/en/account_team.json @@ -61,5 +61,13 @@ "user_team_invite_member": "Invite members", "user_team_leave_team": "Leave the team", "user_team_leave_team_failed": "Failure to leave the team", - "waiting": "To be accepted" + "waiting": "To be accepted", + "permission_appCreate": "Create Application", + "permission_datasetCreate": "Create Knowledge Base", + "permission_apikeyCreate": "Create API Key", + "permission_appCreate_tip": "Can create applications in the root directory (creation permissions in folders are controlled by the folder)", + "permission_datasetCreate_Tip": "Can create knowledge bases in the root directory (creation permissions in folders are controlled by the folder)", + "permission_apikeyCreate_Tip": "Can create global APIKeys", + "permission_manage": "Admin", + "permission_manage_tip": "Can manage members, create groups, manage all groups, and assign permissions to groups and members" } diff --git a/packages/web/i18n/en/user.json b/packages/web/i18n/en/user.json index ff5051d31db4..e9eb68f65680 100644 --- a/packages/web/i18n/en/user.json +++ b/packages/web/i18n/en/user.json @@ -100,7 +100,6 @@ "team.group.manage_tip": "Can manage members, create groups, manage all groups, assign permissions to groups and members", "team.group.members": "member", "team.group.name": "Group name", - "team.group.permission.manage": "administrator", "team.group.permission.write": "Workbench/knowledge base creation", "team.group.permission_tip": "Members with individually configured permissions will follow the individual permission configuration and will no longer be affected by group permissions.\n\nIf a member is in multiple permission groups, the member's permissions are combined.", "team.group.role.admin": "administrator", @@ -112,5 +111,5 @@ "team.manage_collaborators": "Manage Collaborators", "team.no_collaborators": "No Collaborators", "team.org.org": "Organization", - "team.write_role_member": "" + "team.write_role_member": "Write Permission" } diff --git a/packages/web/i18n/zh-CN/account_team.json b/packages/web/i18n/zh-CN/account_team.json index 2659db5b4a16..c612cbd4ff59 100644 --- a/packages/web/i18n/zh-CN/account_team.json +++ b/packages/web/i18n/zh-CN/account_team.json @@ -77,5 +77,13 @@ "user_team_invite_member": "邀请成员", "user_team_leave_team": "离开团队", "user_team_leave_team_failed": "离开团队失败", - "waiting": "待接受" + "waiting": "待接受", + "permission_appCreate": "创建应用", + "permission_datasetCreate": "创建知识库", + "permission_apikeyCreate": "创建 API 密钥", + "permission_appCreate_tip": "可以在根目录创建应用,(文件夹下的创建权限由文件夹控制)", + "permission_datasetCreate_Tip": "可以在根目录创建知识库,(文件夹下的创建权限由文件夹控制)", + "permission_apikeyCreate_Tip": "可以创建全局的 APIKey", + "permission_manage": "管理员", + "permission_manage_tip": "可以管理成员、创建群组、管理所有群组、为群组和成员分配权限" } diff --git a/packages/web/i18n/zh-CN/user.json b/packages/web/i18n/zh-CN/user.json index 6d451357aa39..78bde1a1cfab 100644 --- a/packages/web/i18n/zh-CN/user.json +++ b/packages/web/i18n/zh-CN/user.json @@ -98,11 +98,9 @@ "team.group.keep_admin": "保留管理员权限", "team.group.manage_member": "管理成员", "team.group.manage_tip": "可以管理成员、创建群组、管理所有群组、为群组和成员分配权限", + "team.group.permission_tip": "单独配置权限的成员,将遵循个人权限配置,不再受群组权限影响。\n若成员在多个权限组,则该成员的权限取并集。", "team.group.members": "成员", "team.group.name": "群组名称", - "team.group.permission.manage": "管理员", - "team.group.permission.write": "工作台/知识库创建", - "team.group.permission_tip": "单独配置权限的成员,将遵循个人权限配置,不再受群组权限影响。\n若成员在多个权限组,则该成员的权限取并集。", "team.group.role.admin": "管理员", "team.group.role.member": "成员", "team.group.role.owner": "所有者", diff --git a/packages/web/i18n/zh-Hant/account_team.json b/packages/web/i18n/zh-Hant/account_team.json index 09dc87868c16..ed6d9aac227f 100644 --- a/packages/web/i18n/zh-Hant/account_team.json +++ b/packages/web/i18n/zh-Hant/account_team.json @@ -61,5 +61,13 @@ "user_team_invite_member": "邀請成員", "user_team_leave_team": "離開團隊", "user_team_leave_team_failed": "離開團隊失敗", - "waiting": "待接受" + "waiting": "待接受", + "permission_appCreate": "建立應用", + "permission_datasetCreate": "建立知識庫", + "permission_apikeyCreate": "建立 API 密鑰", + "permission_appCreate_tip": "可以在根目錄建立應用,(資料夾下的建立權限由資料夾控制)", + "permission_datasetCreate_Tip": "可以在根目錄建立知識庫,(資料夾下的建立權限由資料夾控制)", + "permission_apikeyCreate_Tip": "可以建立全域的 APIKey", + "permission_manage": "管理員", + "permission_manage_tip": "可以管理成員、建立群組、管理所有群組、為群組和成員分配權限" } diff --git a/packages/web/i18n/zh-Hant/user.json b/packages/web/i18n/zh-Hant/user.json index 40a883d78bd4..2ec80eee6b03 100644 --- a/packages/web/i18n/zh-Hant/user.json +++ b/packages/web/i18n/zh-Hant/user.json @@ -100,7 +100,6 @@ "team.group.manage_tip": "可以管理成員、創建群組、管理所有群組、為群組和成員分配權限", "team.group.members": "成員", "team.group.name": "群組名稱", - "team.group.permission.manage": "管理員", "team.group.permission.write": "工作臺/知識庫建立", "team.group.permission_tip": "單獨設定權限的成員,將依照個人權限設定,不再受群組權限影響。\n若成員屬於多個權限群組,該成員的權限將會合併。", "team.group.role.admin": "管理員", diff --git a/projects/app/src/components/support/permission/MemberManager/context.tsx b/projects/app/src/components/support/permission/MemberManager/context.tsx index 683830b1538b..ddf0db2246ec 100644 --- a/projects/app/src/components/support/permission/MemberManager/context.tsx +++ b/projects/app/src/components/support/permission/MemberManager/context.tsx @@ -110,7 +110,15 @@ const CollaboratorContextProvider = ({ } = useRequest2( async () => { if (feConfigs.isPlus) { - return onGetCollaboratorList(); + const data = await onGetCollaboratorList(); + return data.map((item) => { + return { + ...item, + permission: new Permission({ + per: item.permission.value + }) + }; + }); } return []; }, diff --git a/projects/app/src/pageComponents/account/team/PermissionManage/index.tsx b/projects/app/src/pageComponents/account/team/PermissionManage/index.tsx index e04fa2e54bd7..27013743f67d 100644 --- a/projects/app/src/pageComponents/account/team/PermissionManage/index.tsx +++ b/projects/app/src/pageComponents/account/team/PermissionManage/index.tsx @@ -27,6 +27,9 @@ import Avatar from '@fastgpt/web/components/common/Avatar'; import MemberTag from '../../../../components/support/user/team/Info/MemberTag'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import { + TeamApikeyCreatePermissionVal, + TeamAppCreatePermissionVal, + TeamDatasetCreatePermissionVal, TeamManagePermissionVal, TeamPermissionList, TeamWritePermissionVal @@ -42,6 +45,8 @@ import MyIcon from '@fastgpt/web/components/common/Icon'; import { useContextSelector } from 'use-context-selector'; import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; import { GetSearchUserGroupOrg } from '@/web/support/user/api'; +import { PermissionValueType } from '@fastgpt/global/support/permission/type'; +import { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator'; function PermissionManage({ Tabs, @@ -104,19 +109,18 @@ function PermissionManage({ }, [collaboratorList, searchResult, searchKey]); const { runAsync: onUpdatePermission, loading: addLoading } = useRequest2( - async ({ id, type, per }: { id: string; type: 'add' | 'remove'; per: 'write' | 'manage' }) => { + async ({ id, type, per }: { id: string; type: 'add' | 'remove'; per: PermissionValueType }) => { const clb = collaboratorList.find( (clb) => clb.tmbId === id || clb.groupId === id || clb.orgId === id ); if (!clb) return; - const updatePer = per === 'write' ? TeamWritePermissionVal : TeamManagePermissionVal; const permission = new TeamPermission({ per: clb.permission.value }); if (type === 'add') { - permission.addPer(updatePer); + permission.addPer(per); } else { - permission.removePer(updatePer); + permission.removePer(per); } return onUpdateCollaborators({ @@ -138,6 +142,42 @@ function PermissionManage({ return false; }; + function PermissionCheckBox({ + isDisabled, + per, + clbPer, + id + }: { + isDisabled: boolean; + per: PermissionValueType; + clbPer: TeamPermission; + id: string; + }) { + return ( + + + + e.target.checked + ? onUpdatePermission({ + id, + type: 'add', + per + }) + : onUpdatePermission({ + id, + type: 'remove', + per + }) + } + /> + + + ); + } + return ( <> @@ -174,13 +214,26 @@ function PermissionManage({ - {t('user:team.group.permission.write')} + {t('account_team:permission_appCreate')} + - {t('user:team.group.permission.manage')} - + {t('account_team:permission_datasetCreate')} + + + + + + {t('account_team:permission_apikeyCreate')} + + + + + + {t('account_team:permission_manage')} + @@ -210,48 +263,30 @@ function PermissionManage({ {member.name} - - - - e.target.checked - ? onUpdatePermission({ - id: member.tmbId!, - type: 'add', - per: 'write' - }) - : onUpdatePermission({ - id: member.tmbId!, - type: 'remove', - per: 'write' - }) - } - /> - - - - - - e.target.checked - ? onUpdatePermission({ - id: member.tmbId!, - type: 'add', - per: 'manage' - }) - : onUpdatePermission({ - id: member.tmbId!, - type: 'remove', - per: 'manage' - }) - } - /> - - + + + + {hasDeletePer(member.permission) && userInfo?.team.tmbId !== member.tmbId && ( @@ -268,7 +303,6 @@ function PermissionManage({ ))} - <> @@ -286,40 +320,30 @@ function PermissionManage({ - - - - e.target.checked - ? onUpdatePermission({ id: org.orgId!, type: 'add', per: 'write' }) - : onUpdatePermission({ - id: org.orgId!, - type: 'remove', - per: 'write' - }) - } - /> - - - - - - e.target.checked - ? onUpdatePermission({ id: org.orgId!, type: 'add', per: 'manage' }) - : onUpdatePermission({ - id: org.orgId!, - type: 'remove', - per: 'manage' - }) - } - /> - - + + + + {hasDeletePer(org.permission) && ( @@ -358,48 +382,30 @@ function PermissionManage({ avatar={group.avatar} /> - - - - e.target.checked - ? onUpdatePermission({ - id: group.groupId!, - type: 'add', - per: 'write' - }) - : onUpdatePermission({ - id: group.groupId!, - type: 'remove', - per: 'write' - }) - } - /> - - - - - - e.target.checked - ? onUpdatePermission({ - id: group.groupId!, - type: 'add', - per: 'manage' - }) - : onUpdatePermission({ - id: group.groupId!, - type: 'remove', - per: 'manage' - }) - } - /> - - + + + + {hasDeletePer(group.permission) && ( diff --git a/projects/app/src/pages/api/admin/initv493.ts b/projects/app/src/pages/api/admin/initv493.ts new file mode 100644 index 000000000000..0e0684144740 --- /dev/null +++ b/projects/app/src/pages/api/admin/initv493.ts @@ -0,0 +1,39 @@ +import { NextAPI } from '@/service/middleware/entry'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { NextApiRequest, NextApiResponse } from 'next'; +import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema'; +import { TeamPermission } from '@fastgpt/global/support/permission/user/controller'; +import { + TeamApikeyCreatePermissionVal, + TeamAppCreatePermissionVal, + TeamDatasetCreatePermissionVal +} from '@fastgpt/global/support/permission/user/constant'; + +async function handler(req: NextApiRequest, _res: NextApiResponse) { + await authCert({ req, authRoot: true }); + // 更新团队权限: + // 目前所有有 TeamWritePermission 的,都需要添加三个新的权限。 + + const rps = await MongoResourcePermission.find({ + resourceType: 'team', + teamId: { $exists: true }, + resourceId: null + }); + + for (const rp of rps) { + const per = new TeamPermission({ per: rp.permission }); + if (per.hasWritePer) { + const newPer = per.addPer( + TeamAppCreatePermissionVal, + TeamDatasetCreatePermissionVal, + TeamApikeyCreatePermissionVal + ); + rp.permission = newPer.value; + rp.save(); + } + } + + return { success: true }; +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/copy.ts b/projects/app/src/pages/api/core/app/copy.ts index 2f7e51d541fc..10b7f61ba518 100644 --- a/projects/app/src/pages/api/core/app/copy.ts +++ b/projects/app/src/pages/api/core/app/copy.ts @@ -4,6 +4,7 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; import { onCreateApp } from './create'; +import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant'; export type copyAppQuery = {}; @@ -17,19 +18,16 @@ async function handler( req: ApiRequestProps, res: ApiResponseType ): Promise { - const [{ app, tmbId }] = await Promise.all([ - authApp({ - req, - authToken: true, - per: WritePermissionVal, - appId: req.body.appId - }), - authUserPer({ - req, - authToken: true, - per: WritePermissionVal - }) - ]); + const { app } = await authApp({ + req, + authToken: true, + per: WritePermissionVal, + appId: req.body.appId + }); + + const { tmbId } = app.parentId + ? await authApp({ req, appId: app.parentId, per: TeamAppCreatePermissionVal, authToken: true }) + : await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal }); const appId = await onCreateApp({ parentId: app.parentId, diff --git a/projects/app/src/pages/api/core/app/create.ts b/projects/app/src/pages/api/core/app/create.ts index 1eb64b8402ee..70ab57aed62c 100644 --- a/projects/app/src/pages/api/core/app/create.ts +++ b/projects/app/src/pages/api/core/app/create.ts @@ -17,6 +17,7 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema'; import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils'; import { refreshSourceAvatar } from '@fastgpt/service/common/file/image/controller'; +import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant'; export type CreateAppBody = { parentId?: ParentIdType; @@ -36,18 +37,15 @@ async function handler(req: ApiRequestProps) { } // 凭证校验 - const [{ teamId, tmbId, userId }] = await Promise.all([ - authUserPer({ req, authToken: true, per: WritePermissionVal }), - ...(parentId - ? [authApp({ req, appId: parentId, per: WritePermissionVal, authToken: true })] - : []) - ]); + const { teamId, tmbId, userId } = parentId + ? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true }) + : await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal }); // 上限校验 await checkTeamAppLimit(teamId); const tmb = await MongoTeamMember.findById({ _id: tmbId }, 'userId').populate<{ - user: { avatar: string; username: string }; - }>('user', 'avatar username'); + user: { username: string }; + }>('user', 'username'); // 创建app const appId = await onCreateApp({ @@ -60,7 +58,7 @@ async function handler(req: ApiRequestProps) { chatConfig, teamId, tmbId, - userAvatar: tmb?.user?.avatar, + userAvatar: tmb?.avatar, username: tmb?.user?.username }); diff --git a/projects/app/src/pages/api/core/app/folder/create.ts b/projects/app/src/pages/api/core/app/folder/create.ts index c27b5269d602..3afbf313f58d 100644 --- a/projects/app/src/pages/api/core/app/folder/create.ts +++ b/projects/app/src/pages/api/core/app/folder/create.ts @@ -16,7 +16,7 @@ import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { syncCollaborators } from '@fastgpt/service/support/permission/inheritPermission'; import { getResourceClbsAndGroups } from '@fastgpt/service/support/permission/controller'; -import { TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant'; +import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant'; import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema'; export type CreateAppFolderBody = { @@ -33,21 +33,9 @@ async function handler(req: ApiRequestProps) { } // 凭证校验 - const { teamId, tmbId } = await authUserPer({ - req, - authToken: true, - per: TeamWritePermissionVal - }); - - if (parentId) { - // if it is not a root folder - await authApp({ - req, - appId: parentId, - per: WritePermissionVal, - authToken: true - }); - } + const { teamId, tmbId } = parentId + ? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true }) + : await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal }); // Create app await mongoSessionRun(async (session) => { diff --git a/projects/app/src/pages/api/core/app/httpPlugin/create.ts b/projects/app/src/pages/api/core/app/httpPlugin/create.ts index 8e2346c693e1..ec4be89f4239 100644 --- a/projects/app/src/pages/api/core/app/httpPlugin/create.ts +++ b/projects/app/src/pages/api/core/app/httpPlugin/create.ts @@ -9,6 +9,8 @@ import { onCreateApp, type CreateAppBody } from '../create'; import { AppSchema } from '@fastgpt/global/core/app/type'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils'; +import { authApp } from '@fastgpt/service/support/permission/app/auth'; +import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant'; export type createHttpPluginQuery = {}; @@ -29,11 +31,9 @@ async function handler( return Promise.reject('缺少参数'); } - const { teamId, tmbId, userId } = await authUserPer({ - req, - authToken: true, - per: WritePermissionVal - }); + const { teamId, tmbId, userId } = parentId + ? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true }) + : await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal }); await mongoSessionRun(async (session) => { // create http plugin folder diff --git a/projects/app/src/pages/api/core/app/update.ts b/projects/app/src/pages/api/core/app/update.ts index 9f7ccb4d9bc7..97a96328cc44 100644 --- a/projects/app/src/pages/api/core/app/update.ts +++ b/projects/app/src/pages/api/core/app/update.ts @@ -20,7 +20,7 @@ import { ClientSession } from 'mongoose'; import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; import { getResourceClbsAndGroups } from '@fastgpt/service/support/permission/controller'; import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; -import { TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant'; +import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant'; import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; import { refreshSourceAvatar } from '@fastgpt/service/common/file/image/controller'; import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema'; @@ -79,7 +79,7 @@ async function handler(req: ApiRequestProps) { await authUserPer({ req, authToken: true, - per: TeamWritePermissionVal + per: TeamAppCreatePermissionVal }); } } else { diff --git a/projects/app/src/pages/api/core/dataset/create.ts b/projects/app/src/pages/api/core/dataset/create.ts index 40e4b5ef2077..dba740ad9993 100644 --- a/projects/app/src/pages/api/core/dataset/create.ts +++ b/projects/app/src/pages/api/core/dataset/create.ts @@ -6,11 +6,9 @@ import { getLLMModel, getEmbeddingModel, getDatasetModel, - getDefaultEmbeddingModel, - getVlmModel + getDefaultEmbeddingModel } from '@fastgpt/service/core/ai/model'; import { checkTeamDatasetLimit } from '@fastgpt/service/support/permission/teamLimit'; -import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { NextAPI } from '@/service/middleware/entry'; import type { ApiRequestProps } from '@fastgpt/service/type/next'; import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils'; @@ -18,6 +16,7 @@ import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils'; import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; import { refreshSourceAvatar } from '@fastgpt/service/common/file/image/controller'; +import { TeamDatasetCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant'; export type DatasetCreateQuery = {}; export type DatasetCreateBody = CreateDatasetParams; @@ -41,25 +40,20 @@ async function handler( } = req.body; // auth - const [{ teamId, tmbId, userId }] = await Promise.all([ - authUserPer({ - req, - authToken: true, - authApiKey: true, - per: WritePermissionVal - }), - ...(parentId - ? [ - authDataset({ - req, - datasetId: parentId, - authToken: true, - authApiKey: true, - per: WritePermissionVal - }) - ] - : []) - ]); + const { teamId, tmbId, userId } = parentId + ? await authDataset({ + req, + datasetId: parentId, + authToken: true, + authApiKey: true, + per: TeamDatasetCreatePermissionVal + }) + : await authUserPer({ + req, + authToken: true, + authApiKey: true, + per: TeamDatasetCreatePermissionVal + }); // check model valid const vectorModelStore = getEmbeddingModel(vectorModel); diff --git a/projects/app/src/pages/api/core/dataset/folder/create.ts b/projects/app/src/pages/api/core/dataset/folder/create.ts index 1173671312ee..8564af6c4021 100644 --- a/projects/app/src/pages/api/core/dataset/folder/create.ts +++ b/projects/app/src/pages/api/core/dataset/folder/create.ts @@ -5,8 +5,7 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; import { OwnerPermissionVal, - PerResourceTypeEnum, - WritePermissionVal + PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; @@ -16,6 +15,7 @@ import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; import { getResourceClbsAndGroups } from '@fastgpt/service/support/permission/controller'; import { syncCollaborators } from '@fastgpt/service/support/permission/inheritPermission'; import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema'; +import { TeamDatasetCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant'; export type DatasetFolderCreateQuery = {}; export type DatasetFolderCreateBody = { parentId?: string; @@ -33,20 +33,20 @@ async function handler( return Promise.reject(CommonErrEnum.missingParams); } - const { tmbId, teamId } = await authUserPer({ - req, - per: WritePermissionVal, - authToken: true - }); - - if (parentId) { - await authDataset({ - datasetId: parentId, - per: WritePermissionVal, - req, - authToken: true - }); - } + const { teamId, tmbId } = parentId + ? await authDataset({ + req, + datasetId: parentId, + authToken: true, + authApiKey: true, + per: TeamDatasetCreatePermissionVal + }) + : await authUserPer({ + req, + authToken: true, + authApiKey: true, + per: TeamDatasetCreatePermissionVal + }); await mongoSessionRun(async (session) => { const dataset = await MongoDataset.create({ diff --git a/projects/app/src/pages/api/core/dataset/update.ts b/projects/app/src/pages/api/core/dataset/update.ts index 2ebff1121378..1d2b46f750e3 100644 --- a/projects/app/src/pages/api/core/dataset/update.ts +++ b/projects/app/src/pages/api/core/dataset/update.ts @@ -23,7 +23,7 @@ import { syncCollaborators } from '@fastgpt/service/support/permission/inheritPermission'; import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; -import { TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant'; +import { TeamDatasetCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant'; import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; @@ -97,7 +97,7 @@ async function handler( await authUserPer({ req, authToken: true, - per: TeamWritePermissionVal + per: TeamDatasetCreatePermissionVal }); } } else { diff --git a/projects/app/src/pages/api/support/openapi/create.ts b/projects/app/src/pages/api/support/openapi/create.ts index 720122578a45..7684e72e8882 100644 --- a/projects/app/src/pages/api/support/openapi/create.ts +++ b/projects/app/src/pages/api/support/openapi/create.ts @@ -4,12 +4,10 @@ import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import type { ApiRequestProps } from '@fastgpt/service/type/next'; import { NextAPI } from '@/service/middleware/entry'; -import { - ManagePermissionVal, - WritePermissionVal -} from '@fastgpt/global/support/permission/constant'; +import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant'; import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { OpenApiErrEnum } from '@fastgpt/global/common/error/code/openapi'; +import { TeamApikeyCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant'; async function handler(req: ApiRequestProps): Promise { const { appId, name, limit } = req.body; @@ -19,7 +17,7 @@ async function handler(req: ApiRequestProps): Promise { const { teamId, tmbId } = await authUserPer({ req, authToken: true, - per: WritePermissionVal + per: TeamApikeyCreatePermissionVal }); return { teamId, tmbId }; } else { diff --git a/projects/app/test/api/core/app/create.test.ts b/projects/app/test/api/core/app/create.test.ts new file mode 100644 index 000000000000..79cb3f34ad40 --- /dev/null +++ b/projects/app/test/api/core/app/create.test.ts @@ -0,0 +1,57 @@ +import * as createapi from '@/pages/api/core/app/create'; +import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; +import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant'; +import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema'; +import { getFakeUsers } from '@test/datas/users'; +import { Call } from '@test/utils/request'; +import { expect, it, describe } from 'vitest'; + +describe('create api', () => { + it('should return 200 when create app success', async () => { + const users = await getFakeUsers(2); + await MongoResourcePermission.create({ + resourceType: 'team', + teamId: users.members[0].teamId, + resourceId: null, + tmbId: users.members[0].tmbId, + permission: TeamAppCreatePermissionVal + }); + const res = await Call(createapi.default, { + auth: users.members[0], + body: { + modules: [], + name: 'testfolder', + type: AppTypeEnum.folder + } + }); + expect(res.error).toBeUndefined(); + expect(res.code).toBe(200); + const folderId = res.data as string; + + const res2 = await Call(createapi.default, { + auth: users.members[0], + body: { + modules: [], + name: 'testapp', + type: AppTypeEnum.simple, + parentId: String(folderId) + } + }); + expect(res2.error).toBeUndefined(); + expect(res2.code).toBe(200); + expect(res2.data).toBeDefined(); + + const res3 = await Call(createapi.default, { + auth: users.members[1], + body: { + modules: [], + name: 'testapp', + type: AppTypeEnum.simple, + parentId: String(folderId) + } + }); + expect(res3.error).toBe(AppErrEnum.unAuthApp); + expect(res3.code).toBe(500); + }); +}); diff --git a/test/cases/api/core/app/version/list.test.ts b/projects/app/test/api/core/app/version/list.test.ts similarity index 100% rename from test/cases/api/core/app/version/list.test.ts rename to projects/app/test/api/core/app/version/list.test.ts diff --git a/test/cases/pages/api/core/dataset/collection/paths.test.ts b/projects/app/test/api/core/dataset/collection/paths.test.ts similarity index 100% rename from test/cases/pages/api/core/dataset/collection/paths.test.ts rename to projects/app/test/api/core/dataset/collection/paths.test.ts diff --git a/projects/app/test/api/core/dataset/create.test.ts b/projects/app/test/api/core/dataset/create.test.ts new file mode 100644 index 000000000000..5863a0bbbd15 --- /dev/null +++ b/projects/app/test/api/core/dataset/create.test.ts @@ -0,0 +1,54 @@ +import * as createapi from '@/pages/api/core/dataset/create'; +import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; +import { TeamDatasetCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant'; +import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema'; +import { getFakeUsers } from '@test/datas/users'; +import { Call } from '@test/utils/request'; +import { vi, describe, it, expect } from 'vitest'; + +describe('create dataset', () => { + it('should return 200 when create dataset success', async () => { + const users = await getFakeUsers(2); + await MongoResourcePermission.create({ + resourceType: 'team', + teamId: users.members[0].teamId, + resourceId: null, + tmbId: users.members[0].tmbId, + permission: TeamDatasetCreatePermissionVal + }); + const res = await Call< + createapi.DatasetCreateBody, + createapi.DatasetCreateQuery, + createapi.DatasetCreateResponse + >(createapi.default, { + auth: users.members[0], + body: { + name: 'folder', + intro: 'intro', + avatar: 'avatar', + type: DatasetTypeEnum.folder + } + }); + expect(res.error).toBeUndefined(); + expect(res.code).toBe(200); + const folderId = res.data as string; + + const res2 = await Call< + createapi.DatasetCreateBody, + createapi.DatasetCreateQuery, + createapi.DatasetCreateResponse + >(createapi.default, { + auth: users.members[0], + body: { + name: 'test', + intro: 'intro', + avatar: 'avatar', + type: DatasetTypeEnum.dataset, + parentId: folderId + } + }); + + expect(res2.error).toBeUndefined(); + expect(res2.code).toBe(200); + }); +}); diff --git a/test/cases/pages/api/core/dataset/paths.test.ts b/projects/app/test/api/core/dataset/paths.test.ts similarity index 100% rename from test/cases/pages/api/core/dataset/paths.test.ts rename to projects/app/test/api/core/dataset/paths.test.ts diff --git a/projects/app/test/api/support/openapi/create.test.ts b/projects/app/test/api/support/openapi/create.test.ts new file mode 100644 index 000000000000..8674e87f09eb --- /dev/null +++ b/projects/app/test/api/support/openapi/create.test.ts @@ -0,0 +1,64 @@ +import { EditApiKeyProps } from '@/global/support/openapi/api'; +import * as createapi from '@/pages/api/support/openapi/create'; +import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; +import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant'; +import { + TeamApikeyCreatePermissionVal, + TeamDatasetCreatePermissionVal +} from '@fastgpt/global/support/permission/user/constant'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; +import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema'; +import { getFakeUsers } from '@test/datas/users'; +import { Call } from '@test/utils/request'; +import { describe, it, expect } from 'vitest'; + +describe('create dataset', () => { + it('should return 200 when create dataset success', async () => { + const users = await getFakeUsers(2); + await MongoResourcePermission.create({ + resourceType: 'team', + teamId: users.members[0].teamId, + resourceId: null, + tmbId: users.members[0].tmbId, + permission: TeamApikeyCreatePermissionVal + }); + const res = await Call(createapi.default, { + auth: users.members[0], + body: { + name: 'test', + limit: { + maxUsagePoints: 1000 + } + } + }); + expect(res.error).toBeUndefined(); + expect(res.code).toBe(200); + + await MongoResourcePermission.create({ + resourceType: 'app', + teamId: users.members[1].teamId, + resourceId: null, + tmbId: users.members[1].tmbId, + permission: ManagePermissionVal + }); + + const app = await MongoApp.create({ + name: 'a', + type: 'simple', + tmbId: users.members[1].tmbId, + teamId: users.members[1].teamId + }); + const res2 = await Call(createapi.default, { + auth: users.members[1], + body: { + appId: app._id, + name: 'test', + limit: { + maxUsagePoints: 1000 + } + } + }); + expect(res2.error).toBeUndefined(); + expect(res2.code).toBe(200); + }); +}); diff --git a/projects/app/test/tsconfig.json b/projects/app/test/tsconfig.json new file mode 100644 index 000000000000..e663f79f518f --- /dev/null +++ b/projects/app/test/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es2022", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "baseUrl": ".", + "paths": { + "@/*": ["../src/*"], + "@fastgpt/*": ["../../../packages/*"], + "@test/*": ["../../../test/*"] + } + }, + "include": ["**/*.test.ts"], + "exclude": ["**/node_modules"] +} diff --git a/test/datas/users.ts b/test/datas/users.ts index 0315d4fb7051..663f8e35cb63 100644 --- a/test/datas/users.ts +++ b/test/datas/users.ts @@ -1,4 +1,12 @@ -import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { AuthUserTypeEnum, PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type'; +import { PermissionValueType } from '@fastgpt/global/support/permission/type'; +import { TeamManagePermissionVal } from '@fastgpt/global/support/permission/user/constant'; +import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; +import { OrgSchemaType, OrgType } from '@fastgpt/global/support/user/team/org/type'; +import { MongoMemberGroupModel } from '@fastgpt/service/support/permission/memberGroup/memberGroupSchema'; +import { MongoOrgModel } from '@fastgpt/service/support/permission/org/orgSchema'; +import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema'; import { MongoUser } from '@fastgpt/service/support/user/schema'; import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema'; import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema'; @@ -33,22 +41,40 @@ export async function getRootUser(): Promise { }; } -export async function getUser(username: string): Promise { +export async function getUser(username: string, teamId?: string): Promise { const user = await MongoUser.create({ username, password: '123456' }); - const team = await MongoTeam.create({ - name: 'test team', - ownerId: user._id - }); + const tmb = await (async () => { + if (!teamId) { + const team = await MongoTeam.create({ + name: username, + ownerId: user._id + }); + const tmb = await MongoTeamMember.create({ + name: username, + teamId: team._id, + userId: user._id, + status: 'active', + role: 'owner' + }); - const tmb = await MongoTeamMember.create({ - teamId: team._id, - userId: user._id, - status: 'active' - }); + await MongoMemberGroupModel.create({ + teamId: team._id, + name: DefaultGroupName, + avatar: team.avatar + }); + + return tmb; + } + return MongoTeamMember.create({ + teamId, + userId: user._id, + status: 'active' + }); + })(); return { userId: user._id, @@ -61,3 +87,90 @@ export async function getUser(username: string): Promise { tmbId: tmb?._id }; } + +let fakeUsers: Record = {}; + +async function getFakeUser(username: string) { + if (username === 'Owner') { + if (!fakeUsers[username]) { + fakeUsers[username] = await getUser(username); + } + return fakeUsers[username]; + } + const owner = await getFakeUser('Owner'); + const ownerTeamId = owner.teamId; + if (!fakeUsers[username]) { + fakeUsers[username] = await getUser(username, ownerTeamId); + } + return fakeUsers[username]; +} + +async function addPermission({ + user, + permission +}: { + user: parseHeaderCertRet; + permission: PermissionValueType; +}) { + const { teamId, tmbId } = user; + await MongoResourcePermission.updateOne({ + resourceType: PerResourceTypeEnum.team, + teamId, + resourceId: null, + tmbId, + permission + }); +} + +export async function getFakeUsers(num: number = 10) { + const owner = await getFakeUser('Owner'); + const manager = await getFakeUser('Manager'); + await MongoResourcePermission.create({ + resourceType: PerResourceTypeEnum.team, + teamId: owner.teamId, + resourceId: null, + tmbId: manager.tmbId, + permission: TeamManagePermissionVal + }); + const members = (await Promise.all( + Array.from({ length: num }, (_, i) => `member${i + 1}`) // 团队 member1, member2, ..., member10 + .map((username) => getFakeUser(username)) + )) as parseHeaderCertRet[]; + return { + owner, + manager, + members + }; +} + +export async function getFakeGroups(num: number = 5) { + // create 5 groups + const teamId = (await getFakeUser('Owner')).teamId; + return MongoMemberGroupModel.create([ + ...Array(num) + .keys() + .map((i) => ({ + name: `group${i + 1}`, + teamId + })) + ]) as Promise; +} + +export async function getFakeOrgs() { + // create 5 orgs + const pathIds = ['root', 'org1', 'org2', 'org3', 'org4', 'org5']; + const paths = ['', '/root', '/root', '/root', '/root/org1', '/root/org1/org4']; + const teamId = (await getFakeUser('Owner')).teamId; + return MongoOrgModel.create( + pathIds.map((pathId, i) => ({ + pathId, + name: pathId, + path: paths[i], + teamId + })) + ) as Promise; +} + +export async function clean() { + fakeUsers = {}; +} diff --git a/test/globalSetup.ts b/test/globalSetup.ts new file mode 100644 index 000000000000..9f5d8fa55b50 --- /dev/null +++ b/test/globalSetup.ts @@ -0,0 +1,18 @@ +import { MongoMemoryReplSet } from 'mongodb-memory-server'; +import type { TestProject } from 'vitest/node'; + +export default async function setup(project: TestProject) { + const replset = await MongoMemoryReplSet.create({ replSet: { count: 1 } }); + const uri = replset.getUri(); + project.provide('MONGODB_URI', uri); + + return async () => { + await replset.stop(); + }; +} + +declare module 'vitest' { + export interface ProvidedContext { + MONGODB_URI: string; + } +} diff --git a/test/mocks/request.ts b/test/mocks/request.ts index 5a576018a0a3..650c081961f5 100644 --- a/test/mocks/request.ts +++ b/test/mocks/request.ts @@ -1,4 +1,8 @@ +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { TeamPermission } from '@fastgpt/global/support/permission/user/controller'; +import { MongoGroupMemberModel } from '@fastgpt/service/support/permission/memberGroup/groupMemberSchema'; +import { getTmbInfoByTmbId } from '@fastgpt/service/support/user/team/controller'; import { vi } from 'vitest'; // vi.mock(import('@/service/middleware/entry'), async () => { @@ -87,3 +91,62 @@ vi.mock(import('@fastgpt/service/support/permission/controller'), async (importO parseHeaderCert }; }); + +vi.mock( + import('@fastgpt/service/support/permission/memberGroup/controllers'), + async (importOriginal) => { + const mod = await importOriginal(); + const parseHeaderCert = vi.fn( + ({ + req, + authToken = false, + authRoot = false, + authApiKey = false + }: { + req: MockReqType; + authToken?: boolean; + authRoot?: boolean; + authApiKey?: boolean; + }) => { + const { auth } = req; + if (!auth) { + return Promise.reject(Error('unAuthorization(mock)')); + } + return Promise.resolve(auth); + } + ); + const authGroupMemberRole = vi.fn(async ({ groupId, role, ...props }: any) => { + const result = await parseHeaderCert(props); + const { teamId, tmbId, isRoot } = result; + if (isRoot) { + return { + ...result, + permission: new TeamPermission({ + isOwner: true + }), + teamId, + tmbId + }; + } + const [groupMember, tmb] = await Promise.all([ + MongoGroupMemberModel.findOne({ groupId, tmbId }), + getTmbInfoByTmbId({ tmbId }) + ]); + + // Team admin or role check + if (tmb.permission.hasManagePer || (groupMember && role.includes(groupMember.role))) { + return { + ...result, + permission: tmb.permission, + teamId, + tmbId + }; + } + return Promise.reject(TeamErrEnum.unAuthTeam); + }); + return { + ...mod, + authGroupMemberRole + }; + } +); diff --git a/test/setup.ts b/test/setup.ts index 5e636ddcf802..caf13e29ef82 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -1,25 +1,43 @@ +import './mocks'; import { existsSync, readFileSync } from 'fs'; -import mongoose from '@fastgpt/service/common/mongo'; import { connectMongo } from '@fastgpt/service/common/mongo/init'; import { initGlobalVariables } from '@/service/common/system'; -import { afterAll, beforeAll, vi } from 'vitest'; -import { setup, teardown } from 'vitest-mongodb'; +import { afterAll, afterEach, beforeAll, beforeEach, inject, vi } from 'vitest'; import setupModels from './setupModels'; -import './mocks'; +import { clean } from './datas/users'; +import { connectionMongo } from '@fastgpt/service/common/mongo'; +import { randomUUID } from 'crypto'; +import { delay } from '@fastgpt/global/common/system/utils'; vi.stubEnv('NODE_ENV', 'test'); -vi.mock(import('@fastgpt/service/common/mongo'), async (importOriginal) => { +// vi.mock(import('@fastgpt/service/common/mongo'), async (importOriginal) => { +// const mod = await importOriginal(); +// return { +// ...mod, +// connectionMongo: await (async () => { +// if (!global.mongodb) { +// global.mongodb = mongoose; +// await global.mongodb.connect((globalThis as any).__MONGO_URI__ as string, { +// timeoutMS: 3000 +// }); +// } + +// return global.mongodb; +// })() +// }; +// }); + +vi.mock(import('@fastgpt/service/common/mongo/init'), async (importOriginal: any) => { const mod = await importOriginal(); + const uri = inject('MONGODB_URI'); return { ...mod, - connectionMongo: await (async () => { - if (!global.mongodb) { - global.mongodb = mongoose; - await global.mongodb.connect((globalThis as any).__MONGO_URI__ as string); - } - - return global.mongodb; - })() + connectMongo: async () => { + (await connectionMongo.connect(uri)).connection.useDb(randomUUID()); + await delay(500); + // (connectionMongo as typeof mongoose) + // .connection.useDb(randomUUID()); + } }; }); @@ -53,18 +71,11 @@ vi.mock(import('@/service/common/system'), async (importOriginal) => { }); beforeAll(async () => { - await setup({ - type: 'replSet', - serverOptions: { - replSet: { - count: 1 - } - } - }); - vi.stubEnv('MONGODB_URI', (globalThis as any).__MONGO_URI__); - initGlobalVariables(); + vi.stubEnv('MONGODB_URI', inject('MONGODB_URI')); await connectMongo(); + initGlobalVariables(); + // await getInitConfig(); if (existsSync('projects/app/.env.local')) { const str = readFileSync('projects/app/.env.local', 'utf-8'); @@ -79,10 +90,21 @@ beforeAll(async () => { global.systemEnv = {} as any; global.systemEnv.oneapiUrl = systemEnv['OPENAI_BASE_URL']; global.systemEnv.chatApiKey = systemEnv['CHAT_API_KEY']; + global.feConfigs = { + isPlus: false + } as any; await setupModels(); } }); afterAll(async () => { - await teardown(); + if (global?.mongodb?.connection) global.mongodb?.connection.close(); +}); + +beforeEach(async () => { + await connectMongo(); + return async () => { + clean(); + await global.mongodb?.connection.db?.dropDatabase(); + }; }); diff --git a/test/setupModels.ts b/test/setupModels.ts index 4bc02f73beb6..91e6fd10c8de 100644 --- a/test/setupModels.ts +++ b/test/setupModels.ts @@ -3,6 +3,7 @@ import { ModelProviderIdType } from 'packages/global/core/ai/provider'; export default async function setupModels() { global.llmModelMap = new Map(); + global.embeddingModelMap = new Map(); global.llmModelMap.set('gpt-4o-mini', { type: ModelTypeEnum.llm, model: 'gpt-4o-mini', @@ -47,6 +48,22 @@ export default async function setupModels() { maxContext: 4096, maxResponse: 4096, quoteMaxToken: 2048 + }, + embedding: { + type: ModelTypeEnum.embedding, + model: 'text-embedding-ada-002', + name: 'text-embedding-ada-002', + avatar: 'text-embedding-ada-002', + isActive: true, + isDefault: true, + isCustom: false, + requestUrl: undefined, + requestAuth: undefined, + defaultConfig: undefined, + defaultToken: 1, + maxToken: 100, + provider: 'OpenAI', + weight: 1 } }; } diff --git a/test/utils/request.ts b/test/utils/request.ts index 6e94afa207df..c81ac8842a89 100644 --- a/test/utils/request.ts +++ b/test/utils/request.ts @@ -1,21 +1,35 @@ import { NextApiHandler } from '@fastgpt/service/common/middle/entry'; import { MockReqType } from '../mocks/request'; +import { vi } from 'vitest'; export async function Call( handler: NextApiHandler, props?: MockReqType ) { const { body = {}, query = {}, ...rest } = props || {}; - return (await handler( + let raw; + const res: any = { + setHeader: vi.fn(), + write: vi.fn((data: any) => { + raw = data; + }), + end: vi.fn() + }; + const response = (await handler( { - body: body, - query: query, + body: JSON.parse(JSON.stringify(body)), + query: JSON.parse(JSON.stringify(query)), ...(rest as any) }, - {} as any - )) as Promise<{ + res + )) as any; + return { + ...response, + raw + } as { code: number; data: R; error?: any; - }>; + raw?: any; + }; } diff --git a/vitest.config.mts b/vitest.config.mts index 8a5325984f64..57a8529ea92d 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -5,13 +5,18 @@ export default defineConfig({ coverage: { enabled: true, reporter: ['html', 'json-summary', 'json'], - all: false, - reportOnFailure: true + reportOnFailure: true, + include: ['projects/**/*.ts', 'packages/**/*.ts'], + cleanOnRerun: false }, outputFile: 'test-results.json', - setupFiles: ['./test/setup.ts'], - include: ['./test/test.ts', './test/cases/**/*.test.ts'], - testTimeout: 5000 + setupFiles: 'test/setup.ts', + globalSetup: 'test/globalSetup.ts', + fileParallelism: false, + pool: 'threads', + include: ['test/test.ts', 'test/cases/**/*.test.ts', 'projects/app/test/**/*.test.ts'], + testTimeout: 5000, + reporters: ['github-actions', 'default'] }, resolve: { alias: {