mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 21:13:50 +00:00
* feat: team permission refine (#4402) * chore: team permission extend * feat: manage team permission * chore: api auth * fix: i18n * feat: add initv493 * fix: test, org auth manager * test: app test for refined permission * update init sh * fix: add/remove manage permission (#4427) * fix: add/remove manage permission * fix: github action fastgpt-test * fix: mock create model * fix: team write permission * fix: ts * account permission --------- Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
57
projects/app/test/api/core/app/create.test.ts
Normal file
57
projects/app/test/api/core/app/create.test.ts
Normal file
@@ -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.CreateAppBody, {}, {}>(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.CreateAppBody, {}, {}>(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.CreateAppBody, {}, {}>(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);
|
||||
});
|
||||
});
|
38
projects/app/test/api/core/app/version/list.test.ts
Normal file
38
projects/app/test/api/core/app/version/list.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||
import { getRootUser } from '@test/datas/users';
|
||||
import { Call } from '@test/utils/request';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import handler, {
|
||||
type versionListBody,
|
||||
type versionListResponse
|
||||
} from '@/pages/api/core/app/version/list';
|
||||
|
||||
describe('app version list test', () => {
|
||||
it('should return app version list', async () => {
|
||||
const root = await getRootUser();
|
||||
const app = await MongoApp.create({
|
||||
name: 'test',
|
||||
tmbId: root.tmbId,
|
||||
teamId: root.teamId
|
||||
});
|
||||
await MongoAppVersion.create(
|
||||
[...Array(10).keys()].map((i) => ({
|
||||
tmbId: root.tmbId,
|
||||
appId: app._id,
|
||||
versionName: `v${i}`
|
||||
}))
|
||||
);
|
||||
const res = await Call<versionListBody, {}, versionListResponse>(handler, {
|
||||
auth: root,
|
||||
body: {
|
||||
pageSize: 10,
|
||||
offset: 0,
|
||||
appId: app._id
|
||||
}
|
||||
});
|
||||
expect(res.code).toBe(200);
|
||||
expect(res.data.total).toBe(10);
|
||||
expect(res.data.list.length).toBe(10);
|
||||
});
|
||||
});
|
87
projects/app/test/api/core/dataset/collection/paths.test.ts
Normal file
87
projects/app/test/api/core/dataset/collection/paths.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { getDatasetCollectionPaths } from '@/pages/api/core/dataset/collection/paths';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
|
||||
vi.mock('@fastgpt/service/core/dataset/collection/schema', () => ({
|
||||
MongoDatasetCollection: {
|
||||
findOne: vi.fn()
|
||||
},
|
||||
DatasetColCollectionName: 'dataset_collections'
|
||||
}));
|
||||
|
||||
describe('getDatasetCollectionPaths', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return empty array for empty parentId', async () => {
|
||||
const result = await getDatasetCollectionPaths({});
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array if collection not found', async () => {
|
||||
vi.mocked(MongoDatasetCollection.findOne).mockResolvedValueOnce(null);
|
||||
|
||||
const result = await getDatasetCollectionPaths({ parentId: 'nonexistent-id' });
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return single path for collection without parent', async () => {
|
||||
vi.mocked(MongoDatasetCollection.findOne).mockResolvedValueOnce({
|
||||
_id: 'col1',
|
||||
name: 'Collection 1',
|
||||
parentId: ''
|
||||
});
|
||||
|
||||
const result = await getDatasetCollectionPaths({ parentId: 'col1' });
|
||||
expect(result).toEqual([{ parentId: 'col1', parentName: 'Collection 1' }]);
|
||||
});
|
||||
|
||||
it('should return full path for nested collections', async () => {
|
||||
vi.mocked(MongoDatasetCollection.findOne)
|
||||
.mockResolvedValueOnce({
|
||||
_id: 'col3',
|
||||
name: 'Collection 3',
|
||||
parentId: 'col2'
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
_id: 'col2',
|
||||
name: 'Collection 2',
|
||||
parentId: 'col1'
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
_id: 'col1',
|
||||
name: 'Collection 1',
|
||||
parentId: ''
|
||||
});
|
||||
|
||||
const result = await getDatasetCollectionPaths({ parentId: 'col3' });
|
||||
|
||||
expect(result).toEqual([
|
||||
{ parentId: 'col1', parentName: 'Collection 1' },
|
||||
{ parentId: 'col2', parentName: 'Collection 2' },
|
||||
{ parentId: 'col3', parentName: 'Collection 3' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle circular references gracefully', async () => {
|
||||
vi.mocked(MongoDatasetCollection.findOne)
|
||||
.mockResolvedValueOnce({
|
||||
_id: 'col1',
|
||||
name: 'Collection 1',
|
||||
parentId: 'col2'
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
_id: 'col2',
|
||||
name: 'Collection 2',
|
||||
parentId: 'col1'
|
||||
});
|
||||
|
||||
const result = await getDatasetCollectionPaths({ parentId: 'col1' });
|
||||
|
||||
expect(result).toEqual([
|
||||
{ parentId: 'col2', parentName: 'Collection 2' },
|
||||
{ parentId: 'col1', parentName: 'Collection 1' }
|
||||
]);
|
||||
});
|
||||
});
|
54
projects/app/test/api/core/dataset/create.test.ts
Normal file
54
projects/app/test/api/core/dataset/create.test.ts
Normal file
@@ -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);
|
||||
});
|
||||
});
|
83
projects/app/test/api/core/dataset/paths.test.ts
Normal file
83
projects/app/test/api/core/dataset/paths.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { getParents } from '@/pages/api/core/dataset/paths';
|
||||
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
|
||||
|
||||
vi.mock('@fastgpt/service/core/dataset/schema', () => ({
|
||||
MongoDataset: {
|
||||
findById: vi.fn()
|
||||
},
|
||||
ChunkSettings: {},
|
||||
DatasetCollectionName: 'datasets'
|
||||
}));
|
||||
|
||||
describe('getParents', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return empty array if parentId is undefined', async () => {
|
||||
const result = await getParents(undefined);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array if parent not found', async () => {
|
||||
vi.mocked(MongoDataset.findById).mockResolvedValueOnce(null);
|
||||
|
||||
const result = await getParents('non-existent-id');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return single parent path if no further parents', async () => {
|
||||
vi.mocked(MongoDataset.findById).mockResolvedValueOnce({
|
||||
name: 'Parent1',
|
||||
parentId: undefined
|
||||
});
|
||||
|
||||
const result = await getParents('parent1-id');
|
||||
|
||||
expect(result).toEqual([{ parentId: 'parent1-id', parentName: 'Parent1' }]);
|
||||
});
|
||||
|
||||
it('should return full parent path for nested parents', async () => {
|
||||
vi.mocked(MongoDataset.findById)
|
||||
.mockResolvedValueOnce({
|
||||
name: 'Child',
|
||||
parentId: 'parent1-id'
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
name: 'Parent1',
|
||||
parentId: 'parent2-id'
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
name: 'Parent2',
|
||||
parentId: undefined
|
||||
});
|
||||
|
||||
const result = await getParents('child-id');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ parentId: 'parent2-id', parentName: 'Parent2' },
|
||||
{ parentId: 'parent1-id', parentName: 'Parent1' },
|
||||
{ parentId: 'child-id', parentName: 'Child' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle circular references gracefully', async () => {
|
||||
vi.mocked(MongoDataset.findById)
|
||||
.mockResolvedValueOnce({
|
||||
name: 'Node1',
|
||||
parentId: 'node2-id'
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
name: 'Node2',
|
||||
parentId: 'node1-id' // Circular reference
|
||||
});
|
||||
|
||||
const result = await getParents('node1-id');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ parentId: 'node2-id', parentName: 'Node2' },
|
||||
{ parentId: 'node1-id', parentName: 'Node1' }
|
||||
]);
|
||||
});
|
||||
});
|
64
projects/app/test/api/support/openapi/create.test.ts
Normal file
64
projects/app/test/api/support/openapi/create.test.ts
Normal file
@@ -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<EditApiKeyProps>(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<EditApiKeyProps>(createapi.default, {
|
||||
auth: users.members[1],
|
||||
body: {
|
||||
appId: app._id,
|
||||
name: 'test',
|
||||
limit: {
|
||||
maxUsagePoints: 1000
|
||||
}
|
||||
}
|
||||
});
|
||||
expect(res2.error).toBeUndefined();
|
||||
expect(res2.code).toBe(200);
|
||||
});
|
||||
});
|
26
projects/app/test/tsconfig.json
Normal file
26
projects/app/test/tsconfig.json
Normal file
@@ -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"]
|
||||
}
|
Reference in New Issue
Block a user