Skip to content

Commit

Permalink
Add endpoint to create folders
Browse files Browse the repository at this point in the history
  • Loading branch information
RicardoE105 committed Feb 23, 2025
1 parent 92a4550 commit 32f5194
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 1 deletion.
3 changes: 3 additions & 0 deletions packages/cli/src/permissions.ee/project-roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const REGULAR_PROJECT_ADMIN_SCOPES: Scope[] = [
'project:read',
'project:update',
'project:delete',
'folder:create',
];

export const PERSONAL_PROJECT_OWNER_SCOPES: Scope[] = [
Expand All @@ -45,6 +46,7 @@ export const PERSONAL_PROJECT_OWNER_SCOPES: Scope[] = [
'credential:move',
'project:list',
'project:read',
'folder:create',
];

export const PROJECT_EDITOR_SCOPES: Scope[] = [
Expand All @@ -61,6 +63,7 @@ export const PROJECT_EDITOR_SCOPES: Scope[] = [
'credential:list',
'project:list',
'project:read',
'folder:create',
];

export const PROJECT_VIEWER_SCOPES: Scope[] = [
Expand Down
188 changes: 188 additions & 0 deletions packages/cli/test/integration/folder/folder.controller.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { Container } from '@n8n/di';

import type { User } from '@/databases/entities/user';
import { FolderRepository } from '@/databases/repositories/folder.repository';
import { ProjectRepository } from '@/databases/repositories/project.repository';
import { createFolder } from '@test-integration/db/folders';

import { createTeamProject, linkUserToProject } from '../shared/db/projects';
import { createOwner, createMember } from '../shared/db/users';
import * as testDb from '../shared/test-db';
import type { SuperAgentTest } from '../shared/types';
import * as utils from '../shared/utils/';

let owner: User;
let member: User;
let authOwnerAgent: SuperAgentTest;
let authMemberAgent: SuperAgentTest;

const testServer = utils.setupTestServer({
endpointGroups: ['folder'],
});

let projectRepository: ProjectRepository;
let folderRepository: FolderRepository;

beforeEach(async () => {
await testDb.truncate(['Folder', 'SharedWorkflow', 'Tag', 'Project', 'ProjectRelation']);

projectRepository = Container.get(ProjectRepository);
folderRepository = Container.get(FolderRepository);

owner = await createOwner();
member = await createMember();
authOwnerAgent = testServer.authAgentFor(owner);
authMemberAgent = testServer.authAgentFor(member);
});

describe('POST /projects/:projectId/folders', () => {
test('should not create folder when project does not exist', async () => {
const payload = {
name: 'Test Folder',
};

await authOwnerAgent.post('/projects/non-existing-id/folders').send(payload).expect(403);
});

test('should not create folder when name is empty', async () => {
const project = await createTeamProject(undefined, owner);
const payload = {
name: '',
};

await authOwnerAgent.post(`/projects/${project.id}/folders`).send(payload).expect(400);
});

test('should not create folder if user has project:viewer role in team project', async () => {
const project = await createTeamProject(undefined, owner);
await linkUserToProject(member, project, 'project:viewer');

const payload = {
name: 'Test Folder',
};

await authMemberAgent.post(`/projects/${project.id}/folders`).send(payload).expect(403);

const foldersInDb = await folderRepository.find();
expect(foldersInDb).toHaveLength(0);
});

test("should not allow creating folder in another user's personal project", async () => {
const ownerPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id);
const payload = {
name: 'Test Folder',
};

await authMemberAgent
.post(`/projects/${ownerPersonalProject.id}/folders`)
.send(payload)
.expect(403);
});

test('should create folder if user has project:editor role in team project', async () => {
const project = await createTeamProject(undefined, owner);
await linkUserToProject(member, project, 'project:editor');

const payload = {
name: 'Test Folder',
};

await authMemberAgent.post(`/projects/${project.id}/folders`).send(payload).expect(200);

const foldersInDb = await folderRepository.find();
expect(foldersInDb).toHaveLength(1);
});

test('should create folder if user has project:admin role in team project', async () => {
const project = await createTeamProject(undefined, owner);

const payload = {
name: 'Test Folder',
};

await authOwnerAgent.post(`/projects/${project.id}/folders`).send(payload).expect(200);

const foldersInDb = await folderRepository.find();
expect(foldersInDb).toHaveLength(1);
});

test('should create folder in root of specified project', async () => {
const project = await createTeamProject('test', owner);
const payload = {
name: 'Test Folder',
};

const response = await authOwnerAgent.post(`/projects/${project.id}/folders`).send(payload);

expect(response.body.data).toEqual(
expect.objectContaining({
id: expect.any(String),
name: payload.name,
parentFolder: null,
createdAt: expect.any(String),
updatedAt: expect.any(String),
}),
);

const folderInDb = await folderRepository.findOneBy({ id: response.body.id });
expect(folderInDb).toBeDefined();
expect(folderInDb?.name).toBe(payload.name);
});

test('should create folder in specified project within another folder', async () => {
const project = await createTeamProject('test', owner);
const folder = await createFolder(project);

const payload = {
name: 'Test Folder',
parentFolderId: folder.id,
};

const response = await authOwnerAgent.post(`/projects/${project.id}/folders`).send(payload);

expect(response.body.data).toEqual(
expect.objectContaining({
id: expect.any(String),
name: payload.name,
parentFolder: expect.objectContaining({
id: folder.id,
name: folder.name,
createdAt: expect.any(String),
updatedAt: expect.any(String),
}),
createdAt: expect.any(String),
updatedAt: expect.any(String),
}),
);

const folderInDb = await folderRepository.findOneBy({ id: response.body.data.id });

expect(folderInDb).toBeDefined();
expect(folderInDb?.name).toBe(payload.name);
});

test('should create folder in personal project', async () => {
const personalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id);
const payload = {
name: 'Personal Folder',
};

const response = await authOwnerAgent
.post(`/projects/${personalProject.id}/folders`)
.send(payload)
.expect(200);

expect(response.body.data).toEqual(
expect.objectContaining({
id: expect.any(String),
name: payload.name,
createdAt: expect.any(String),
updatedAt: expect.any(String),
}),
);

const folderInDb = await folderRepository.findOneBy({ id: response.body.id });
expect(folderInDb).toBeDefined();
expect(folderInDb?.name).toBe(payload.name);
});
});
3 changes: 2 additions & 1 deletion packages/cli/test/integration/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ type EndpointGroup =
| 'dynamic-node-parameters'
| 'apiKeys'
| 'evaluation'
| 'ai';
| 'ai'
| 'folder';

export interface SetupProps {
endpointGroups?: EndpointGroup[];
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/test/integration/shared/utils/test-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ export const setupTestServer = ({

case 'ai':
await import('@/controllers/ai.controller');

case 'folder':
await import('@/controllers/folder.controller');
}
}

Expand Down

0 comments on commit 32f5194

Please sign in to comment.