import { linkUserToProject, createTeamProject, getAllProjectRelations, } from '@n8n/backend-test-utils'; import { createWorkflow } from '@n8n/backend-test-utils'; import { testDb } from '@n8n/backend-test-utils'; import { SharedWorkflowRepository } from '@n8n/db'; import { Container } from '@n8n/di'; import { License } from '@/license'; import { ProjectService } from '@/services/project.service.ee'; import { LicenseMocker } from '@test-integration/license'; import { createUser } from './shared/db/users'; describe('ProjectService', () => { let projectService: ProjectService; let sharedWorkflowRepository: SharedWorkflowRepository; beforeAll(async () => { await testDb.init(); projectService = Container.get(ProjectService); sharedWorkflowRepository = Container.get(SharedWorkflowRepository); const license: LicenseMocker = new LicenseMocker(); license.mock(Container.get(License)); license.enable('feat:projectRole:editor'); }); afterEach(async () => { await testDb.truncate([ 'User', 'Project', 'ProjectRelation', 'WorkflowEntity', 'SharedWorkflow', ]); }); afterAll(async () => { await testDb.terminate(); }); describe('addUsersToProject', () => { it("don't throw a unique constraint violation error when adding a user that is already part of the project", async () => { // ARRANGE const user = await createUser(); const project = await createTeamProject('project', user); // ACT // add user again await projectService.addUsersToProject(project.id, [ { userId: user.id, role: 'project:admin' }, ]); // ASSERT const relations = await getAllProjectRelations({ projectId: project.id }); expect(relations).toHaveLength(1); expect(relations[0]).toMatchObject({ projectId: project.id, userId: user.id, role: 'project:admin', }); }); it('allows changing a users role', async () => { // ARRANGE const user = await createUser(); const project = await createTeamProject('project', user); // ACT // add user again await projectService.addUsersToProject(project.id, [ { userId: user.id, role: 'project:editor' }, ]); // ASSERT const relations = await getAllProjectRelations({ projectId: project.id }); expect(relations).toHaveLength(1); expect(relations[0]).toMatchObject({ projectId: project.id, userId: user.id, role: 'project:editor', }); }); }); describe('addUser', () => { it("don't throw a unique constraint violation error when adding a user that is already part of the project", async () => { // ARRANGE const user = await createUser(); const project = await createTeamProject('project', user); // ACT // add user again await projectService.addUser(project.id, { userId: user.id, role: 'project:admin' }); // ASSERT const relations = await getAllProjectRelations({ projectId: project.id }); expect(relations).toHaveLength(1); expect(relations[0]).toMatchObject({ projectId: project.id, userId: user.id, role: 'project:admin', }); }); }); describe('findRolesInProjects', () => { describe('when user has roles in projects where workflow is accessible', () => { it('should return roles and project IDs', async () => { const user = await createUser(); const firstProject = await createTeamProject('Project 1'); const secondProject = await createTeamProject('Project 2'); await linkUserToProject(user, firstProject, 'project:admin'); await linkUserToProject(user, secondProject, 'project:viewer'); const workflow = await createWorkflow(); await sharedWorkflowRepository.insert({ projectId: firstProject.id, workflowId: workflow.id, role: 'workflow:owner', }); await sharedWorkflowRepository.insert({ projectId: secondProject.id, workflowId: workflow.id, role: 'workflow:owner', }); const projectIds = await projectService.findProjectsWorkflowIsIn(workflow.id); expect(projectIds).toEqual(expect.arrayContaining([firstProject.id, secondProject.id])); }); }); describe('when user has no roles in projects where workflow is accessible', () => { it('should return project IDs but no roles', async () => { const firstProject = await createTeamProject('Project 1'); const secondProject = await createTeamProject('Project 2'); // workflow shared with projects, but user not added to any project const workflow = await createWorkflow(); await sharedWorkflowRepository.insert({ projectId: firstProject.id, workflowId: workflow.id, role: 'workflow:owner', }); await sharedWorkflowRepository.insert({ projectId: secondProject.id, workflowId: workflow.id, role: 'workflow:owner', }); const projectIds = await projectService.findProjectsWorkflowIsIn(workflow.id); expect(projectIds).toEqual(expect.arrayContaining([firstProject.id, secondProject.id])); }); }); describe('when user has roles in projects where workflow is inaccessible', () => { it('should return project IDs but no roles', async () => { const user = await createUser(); const firstProject = await createTeamProject('Project 1'); const secondProject = await createTeamProject('Project 2'); await linkUserToProject(user, firstProject, 'project:admin'); await linkUserToProject(user, secondProject, 'project:viewer'); const workflow = await createWorkflow(); // user added to projects, but workflow not shared with projects const projectIds = await projectService.findProjectsWorkflowIsIn(workflow.id); expect(projectIds).toHaveLength(0); }); }); }); });