From b4e60c3b47e5a5e86c55ff737ae9a63e2c8836fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 15 Mar 2023 14:24:09 +0100 Subject: [PATCH] refactor: Rewrite workflow public-api tests to avoid timeouts (no-changelog) (#5696) --- .../handlers/workflows/workflows.handler.ts | 34 +- .../handlers/workflows/workflows.service.ts | 10 +- .../integration/publicApi/workflows.test.ts | 2171 +++++++---------- .../cli/test/integration/shared/testDb.ts | 2 +- 4 files changed, 926 insertions(+), 1291 deletions(-) diff --git a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts index 4c7e0906a8..1dbfe72da2 100644 --- a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts +++ b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts @@ -1,6 +1,6 @@ import type express from 'express'; import { Container } from 'typedi'; -import type { FindManyOptions, FindOptionsWhere } from 'typeorm'; +import type { FindOptionsWhere } from 'typeorm'; import { In } from 'typeorm'; import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; @@ -20,12 +20,11 @@ import { updateWorkflow, hasStartNode, getStartNode, - getWorkflows, getSharedWorkflows, - getWorkflowsCount, createWorkflow, getWorkflowIdsViaTags, parseTagNames, + getWorkflowsAndCount, } from './workflows.service'; import { WorkflowsService } from '@/workflows/workflows.services'; import { InternalHooks } from '@/InternalHooks'; @@ -98,28 +97,15 @@ export = { async (req: WorkflowRequest.GetAll, res: express.Response): Promise => { const { offset = 0, limit = 100, active = undefined, tags = undefined } = req.query; - let workflows: WorkflowEntity[]; - let count: number; - const where: FindOptionsWhere = { ...(active !== undefined && { active }), }; - const query: FindManyOptions = { - skip: offset, - take: limit, - where, - ...(!config.getEnv('workflowTagsDisabled') && { relations: ['tags'] }), - }; if (isInstanceOwner(req.user)) { if (tags) { const workflowIds = await getWorkflowIdsViaTags(parseTagNames(tags)); - Object.assign(where, { id: In(workflowIds) }); + where.id = In(workflowIds); } - - workflows = await getWorkflows(query); - - count = await getWorkflowsCount(query); } else { const options: { workflowIds?: string[] } = {}; @@ -137,14 +123,16 @@ export = { } const workflowsIds = sharedWorkflows.map((shareWorkflow) => shareWorkflow.workflowId); - - Object.assign(where, { id: In(workflowsIds) }); - - workflows = await getWorkflows(query); - - count = await getWorkflowsCount(query); + where.id = In(workflowsIds); } + const [workflows, count] = await getWorkflowsAndCount({ + skip: offset, + take: limit, + where, + ...(!config.getEnv('workflowTagsDisabled') && { relations: ['tags'] }), + }); + void Container.get(InternalHooks).onUserRetrievedAllWorkflows({ user_id: req.user.id, public_api: true, diff --git a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.service.ts b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.service.ts index 62f6d74438..d727fd8805 100644 --- a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.service.ts +++ b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.service.ts @@ -109,14 +109,10 @@ export async function deleteWorkflow(workflow: WorkflowEntity): Promise, -): Promise { - return Db.collections.Workflow.find(options); -} - -export async function getWorkflowsCount(options: FindManyOptions): Promise { - return Db.collections.Workflow.count(options); +): Promise<[WorkflowEntity[], number]> { + return Db.collections.Workflow.findAndCount(options); } export async function updateWorkflow( diff --git a/packages/cli/test/integration/publicApi/workflows.test.ts b/packages/cli/test/integration/publicApi/workflows.test.ts index 8c418e0d44..4199b8276c 100644 --- a/packages/cli/test/integration/publicApi/workflows.test.ts +++ b/packages/cli/test/integration/publicApi/workflows.test.ts @@ -1,1273 +1,924 @@ -import express from 'express'; - +import type { Application } from 'express'; +import type { SuperAgentTest } from 'supertest'; import * as Db from '@/Db'; import config from '@/config'; import { Role } from '@db/entities/Role'; import { TagEntity } from '@db/entities/TagEntity'; +import type { User } from '@db/entities/User'; import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; import { randomApiKey } from '../shared/random'; import * as utils from '../shared/utils'; import * as testDb from '../shared/testDb'; -let app: express.Application; -let globalOwnerRole: Role; -let globalMemberRole: Role; -let workflowOwnerRole: Role; -let workflowRunner: ActiveWorkflowRunner; +describe('Workflows Public API', () => { + let app: Application; + let globalOwnerRole: Role; + let globalMemberRole: Role; + let workflowOwnerRole: Role; + let owner: User; + let member: User; + let authOwnerAgent: SuperAgentTest; + let authMemberAgent: SuperAgentTest; + let workflowRunner: ActiveWorkflowRunner; -beforeAll(async () => { - app = await utils.initTestServer({ - endpointGroups: ['publicApi'], - applyAuth: false, - enablePublicAPI: true, - }); - - const [fetchedGlobalOwnerRole, fetchedGlobalMemberRole, fetchedWorkflowOwnerRole] = - await testDb.getAllRoles(); - - globalOwnerRole = fetchedGlobalOwnerRole; - globalMemberRole = fetchedGlobalMemberRole; - workflowOwnerRole = fetchedWorkflowOwnerRole; - - utils.initConfigFile(); - await utils.initNodeTypes(); - workflowRunner = await utils.initActiveWorkflowRunner(); -}); - -beforeEach(async () => { - await testDb.truncate([ - 'SharedCredentials', - 'SharedWorkflow', - 'Tag', - 'User', - 'Workflow', - 'Credentials', - ]); - - config.set('userManagement.disabled', false); - config.set('userManagement.isInstanceOwnerSetUp', true); -}); - -afterEach(async () => { - await workflowRunner?.removeAll(); -}); - -afterAll(async () => { - await testDb.terminate(); -}); - -test('GET /workflows should fail due to missing API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.get('/workflows'); - - expect(response.statusCode).toBe(401); -}); - -test('GET /workflows should fail due to invalid API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - owner.apiKey = 'abcXYZ'; - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.get('/workflows'); - - expect(response.statusCode).toBe(401); -}); - -test('GET /workflows should return all owned workflows', async () => { - const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: member, - version: 1, - }); - - await Promise.all([ - testDb.createWorkflow({}, member), - testDb.createWorkflow({}, member), - testDb.createWorkflow({}, member), - ]); - - const response = await authAgent.get('/workflows'); - - expect(response.statusCode).toBe(200); - expect(response.body.data.length).toBe(3); - expect(response.body.nextCursor).toBeNull(); - - for (const workflow of response.body.data) { - const { - id, - connections, - active, - staticData, - nodes, - settings, - name, - createdAt, - updatedAt, - tags, - } = workflow; - - expect(id).toBeDefined(); - expect(name).toBeDefined(); - expect(connections).toBeDefined(); - expect(active).toBe(false); - expect(staticData).toBeDefined(); - expect(nodes).toBeDefined(); - expect(tags).toBeDefined(); - expect(settings).toBeDefined(); - expect(createdAt).toBeDefined(); - expect(updatedAt).toBeDefined(); - } -}); - -test('GET /workflows should return all owned workflows with pagination', async () => { - const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: member, - version: 1, - }); - - await Promise.all([ - testDb.createWorkflow({}, member), - testDb.createWorkflow({}, member), - testDb.createWorkflow({}, member), - ]); - - const response = await authAgent.get('/workflows?limit=1'); - - expect(response.statusCode).toBe(200); - expect(response.body.data.length).toBe(1); - expect(response.body.nextCursor).not.toBeNull(); - - const response2 = await authAgent.get(`/workflows?limit=1&cursor=${response.body.nextCursor}`); - - expect(response2.statusCode).toBe(200); - expect(response2.body.data.length).toBe(1); - expect(response2.body.nextCursor).not.toBeNull(); - expect(response2.body.nextCursor).not.toBe(response.body.nextCursor); - - const responses = [...response.body.data, ...response2.body.data]; - - for (const workflow of responses) { - const { - id, - connections, - active, - staticData, - nodes, - settings, - name, - createdAt, - updatedAt, - tags, - } = workflow; - - expect(id).toBeDefined(); - expect(name).toBeDefined(); - expect(connections).toBeDefined(); - expect(active).toBe(false); - expect(staticData).toBeDefined(); - expect(nodes).toBeDefined(); - expect(tags).toBeDefined(); - expect(settings).toBeDefined(); - expect(createdAt).toBeDefined(); - expect(updatedAt).toBeDefined(); - } - - // check that we really received a different result - expect(Number(response.body.data[0].id)).toBeLessThan(Number(response2.body.data[0].id)); -}); - -test('GET /workflows should return all owned workflows filtered by tag', async () => { - const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: member, - version: 1, - }); - - const tag = await testDb.createTag({}); - - const [workflow] = await Promise.all([ - testDb.createWorkflow({ tags: [tag] }, member), - testDb.createWorkflow({}, member), - ]); - - const response = await authAgent.get(`/workflows?tags=${tag.name}`); - - expect(response.statusCode).toBe(200); - expect(response.body.data.length).toBe(1); - - const { - id, - connections, - active, - staticData, - nodes, - settings, - name, - createdAt, - updatedAt, - tags: wfTags, - } = response.body.data[0]; - - expect(id).toBe(workflow.id); - expect(name).toBeDefined(); - expect(connections).toBeDefined(); - expect(active).toBe(false); - expect(staticData).toBeDefined(); - expect(nodes).toBeDefined(); - expect(settings).toBeDefined(); - expect(createdAt).toBeDefined(); - expect(updatedAt).toBeDefined(); - - expect(wfTags.length).toBe(1); - expect(wfTags[0].id).toBe(tag.id); -}); - -test('GET /workflows should return all owned workflows filtered by tags', async () => { - const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: member, - version: 1, - }); - - const tags = await Promise.all([await testDb.createTag({}), await testDb.createTag({})]); - const tagNames = tags.map((tag) => tag.name).join(','); - - const [workflow1, workflow2] = await Promise.all([ - testDb.createWorkflow({ tags }, member), - testDb.createWorkflow({ tags }, member), - testDb.createWorkflow({}, member), - testDb.createWorkflow({ tags: [tags[0]] }, member), - testDb.createWorkflow({ tags: [tags[1]] }, member), - ]); - - const response = await authAgent.get(`/workflows?tags=${tagNames}`); - - expect(response.statusCode).toBe(200); - expect(response.body.data.length).toBe(2); - - for (const workflow of response.body.data) { - const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = - workflow; - - expect(id).toBeDefined(); - expect([workflow1.id, workflow2.id].includes(id)).toBe(true); - - expect(name).toBeDefined(); - expect(connections).toBeDefined(); - expect(active).toBe(false); - expect(staticData).toBeDefined(); - expect(nodes).toBeDefined(); - expect(settings).toBeDefined(); - expect(createdAt).toBeDefined(); - expect(updatedAt).toBeDefined(); - - expect(workflow.tags.length).toBe(2); - workflow.tags.forEach((tag: TagEntity) => { - expect(tags.some((savedTag) => savedTag.id === tag.id)).toBe(true); + beforeAll(async () => { + app = await utils.initTestServer({ + endpointGroups: ['publicApi'], + applyAuth: false, + enablePublicAPI: true, }); - } -}); - -test('GET /workflows should return all workflows for owner', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - const member = await testDb.createUser({ globalRole: globalMemberRole }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - await Promise.all([ - testDb.createWorkflow({}, owner), - testDb.createWorkflow({}, member), - testDb.createWorkflow({}, owner), - testDb.createWorkflow({}, member), - testDb.createWorkflow({}, owner), - ]); - - const response = await authOwnerAgent.get('/workflows'); - - expect(response.statusCode).toBe(200); - expect(response.body.data.length).toBe(5); - expect(response.body.nextCursor).toBeNull(); - - for (const workflow of response.body.data) { - const { - id, - connections, - active, - staticData, - nodes, - settings, - name, - createdAt, - updatedAt, - tags, - } = workflow; - - expect(id).toBeDefined(); - expect(name).toBeDefined(); - expect(connections).toBeDefined(); - expect(active).toBe(false); - expect(staticData).toBeDefined(); - expect(nodes).toBeDefined(); - expect(tags).toBeDefined(); - expect(settings).toBeDefined(); - expect(createdAt).toBeDefined(); - expect(updatedAt).toBeDefined(); - } -}); - -test('GET /workflows/:id should fail due to missing API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole }); - - owner.apiKey = null; - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.get(`/workflows/2`); - - expect(response.statusCode).toBe(401); -}); - -test('GET /workflows/:id should fail due to invalid API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - owner.apiKey = 'abcXYZ'; - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.get(`/workflows/2`); - - expect(response.statusCode).toBe(401); -}); - -test('GET /workflows/:id should fail due to non-existing workflow', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.get(`/workflows/2`); - - expect(response.statusCode).toBe(404); -}); - -test('GET /workflows/:id should retrieve workflow', async () => { - const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: member, - version: 1, - }); - - // create and assign workflow to owner - const workflow = await testDb.createWorkflow({}, member); - - const response = await authAgent.get(`/workflows/${workflow.id}`); - - expect(response.statusCode).toBe(200); - - const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt, tags } = - response.body; - - expect(id).toEqual(workflow.id); - expect(name).toEqual(workflow.name); - expect(connections).toEqual(workflow.connections); - expect(active).toBe(false); - expect(staticData).toEqual(workflow.staticData); - expect(nodes).toEqual(workflow.nodes); - expect(tags).toEqual([]); - expect(settings).toEqual(workflow.settings); - expect(createdAt).toEqual(workflow.createdAt.toISOString()); - expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); -}); - -test('GET /workflows/:id should retrieve non-owned workflow for owner', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - const member = await testDb.createUser({ globalRole: globalMemberRole }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - // create and assign workflow to owner - const workflow = await testDb.createWorkflow({}, member); - - const response = await authOwnerAgent.get(`/workflows/${workflow.id}`); - - expect(response.statusCode).toBe(200); - - const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = - response.body; - - expect(id).toEqual(workflow.id); - expect(name).toEqual(workflow.name); - expect(connections).toEqual(workflow.connections); - expect(active).toBe(false); - expect(staticData).toEqual(workflow.staticData); - expect(nodes).toEqual(workflow.nodes); - expect(settings).toEqual(workflow.settings); - expect(createdAt).toEqual(workflow.createdAt.toISOString()); - expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); -}); - -test('DELETE /workflows/:id should fail due to missing API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.delete(`/workflows/2`); - - expect(response.statusCode).toBe(401); -}); - -test('DELETE /workflows/:id should fail due to invalid API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - owner.apiKey = 'abcXYZ'; - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.delete(`/workflows/2`); - - expect(response.statusCode).toBe(401); -}); - -test('DELETE /workflows/:id should fail due to non-existing workflow', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.delete(`/workflows/2`); - - expect(response.statusCode).toBe(404); -}); - -test('DELETE /workflows/:id should delete the workflow', async () => { - const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: member, - version: 1, - }); - - // create and assign workflow to owner - const workflow = await testDb.createWorkflow({}, member); - - const response = await authAgent.delete(`/workflows/${workflow.id}`); - - expect(response.statusCode).toBe(200); - - const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = - response.body; - - expect(id).toEqual(workflow.id); - expect(name).toEqual(workflow.name); - expect(connections).toEqual(workflow.connections); - expect(active).toBe(false); - expect(staticData).toEqual(workflow.staticData); - expect(nodes).toEqual(workflow.nodes); - expect(settings).toEqual(workflow.settings); - expect(createdAt).toEqual(workflow.createdAt.toISOString()); - expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); - - // make sure the workflow actually deleted from the db - const sharedWorkflow = await Db.collections.SharedWorkflow.findOneBy({ - workflowId: workflow.id, - }); - - expect(sharedWorkflow).toBeNull(); -}); - -test('DELETE /workflows/:id should delete non-owned workflow when owner', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - const member = await testDb.createUser({ globalRole: globalMemberRole }); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - // create and assign workflow to owner - const workflow = await testDb.createWorkflow({}, member); - - const response = await authAgent.delete(`/workflows/${workflow.id}`); - - expect(response.statusCode).toBe(200); - - const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = - response.body; - - expect(id).toEqual(workflow.id); - expect(name).toEqual(workflow.name); - expect(connections).toEqual(workflow.connections); - expect(active).toBe(false); - expect(staticData).toEqual(workflow.staticData); - expect(nodes).toEqual(workflow.nodes); - expect(settings).toEqual(workflow.settings); - expect(createdAt).toEqual(workflow.createdAt.toISOString()); - expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); - - // make sure the workflow actually deleted from the db - const sharedWorkflow = await Db.collections.SharedWorkflow.findOneBy({ - workflowId: workflow.id, - }); - - expect(sharedWorkflow).toBeNull(); -}); - -test('POST /workflows/:id/activate should fail due to missing API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.post(`/workflows/2/activate`); - - expect(response.statusCode).toBe(401); -}); - -test('POST /workflows/:id/activate should fail due to invalid API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - owner.apiKey = 'abcXYZ'; - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.post(`/workflows/2/activate`); - - expect(response.statusCode).toBe(401); -}); - -test('POST /workflows/:id/activate should fail due to non-existing workflow', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.post(`/workflows/2/activate`); - - expect(response.statusCode).toBe(404); -}); - -test('POST /workflows/:id/activate should fail due to trying to activate a workflow without a trigger', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const workflow = await testDb.createWorkflow({}, owner); - - const response = await authOwnerAgent.post(`/workflows/${workflow.id}/activate`); - - expect(response.statusCode).toBe(400); -}); - -test('POST /workflows/:id/activate should set workflow as active', async () => { - const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: member, - version: 1, - }); - - const workflow = await testDb.createWorkflowWithTrigger({}, member); - - const response = await authAgent.post(`/workflows/${workflow.id}/activate`); - - expect(response.statusCode).toBe(200); - - const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = - response.body; - - expect(id).toEqual(workflow.id); - expect(name).toEqual(workflow.name); - expect(connections).toEqual(workflow.connections); - expect(active).toBe(true); - expect(staticData).toEqual(workflow.staticData); - expect(nodes).toEqual(workflow.nodes); - expect(settings).toEqual(workflow.settings); - expect(createdAt).toEqual(workflow.createdAt.toISOString()); - expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); - - // check whether the workflow is on the database - const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ - where: { - userId: member.id, - workflowId: workflow.id, - }, - relations: ['workflow'], - }); - - expect(sharedWorkflow?.workflow.active).toBe(true); - - // check whether the workflow is on the active workflow runner - expect(await workflowRunner.isActive(workflow.id)).toBe(true); -}); - -test('POST /workflows/:id/activate should set non-owned workflow as active when owner', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - const member = await testDb.createUser({ globalRole: globalMemberRole }); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const workflow = await testDb.createWorkflowWithTrigger({}, member); - - const response = await authAgent.post(`/workflows/${workflow.id}/activate`); - - expect(response.statusCode).toBe(200); - - const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = - response.body; - - expect(id).toEqual(workflow.id); - expect(name).toEqual(workflow.name); - expect(connections).toEqual(workflow.connections); - expect(active).toBe(true); - expect(staticData).toEqual(workflow.staticData); - expect(nodes).toEqual(workflow.nodes); - expect(settings).toEqual(workflow.settings); - expect(createdAt).toEqual(workflow.createdAt.toISOString()); - expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); - - // check whether the workflow is on the database - const sharedOwnerWorkflow = await Db.collections.SharedWorkflow.findOne({ - where: { - userId: owner.id, - workflowId: workflow.id, - }, - }); - - expect(sharedOwnerWorkflow).toBeNull(); - - const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ - where: { - userId: member.id, - workflowId: workflow.id, - }, - relations: ['workflow'], - }); - - expect(sharedWorkflow?.workflow.active).toBe(true); - - // check whether the workflow is on the active workflow runner - expect(await workflowRunner.isActive(workflow.id)).toBe(true); -}); - -test('POST /workflows/:id/deactivate should fail due to missing API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.post(`/workflows/2/deactivate`); - - expect(response.statusCode).toBe(401); -}); - -test('POST /workflows/:id/deactivate should fail due to invalid API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - owner.apiKey = 'abcXYZ'; - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.post(`/workflows/2/deactivate`); - - expect(response.statusCode).toBe(401); -}); - -test('POST /workflows/:id/deactivate should fail due to non-existing workflow', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.post(`/workflows/2/deactivate`); - - expect(response.statusCode).toBe(404); -}); - -test('POST /workflows/:id/deactivate should deactivate workflow', async () => { - const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: member, - version: 1, - }); - - const workflow = await testDb.createWorkflowWithTrigger({}, member); - - await authAgent.post(`/workflows/${workflow.id}/activate`); - - const workflowDeactivationResponse = await authAgent.post(`/workflows/${workflow.id}/deactivate`); - - const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = - workflowDeactivationResponse.body; - - expect(id).toEqual(workflow.id); - expect(name).toEqual(workflow.name); - expect(connections).toEqual(workflow.connections); - expect(active).toBe(false); - expect(staticData).toEqual(workflow.staticData); - expect(nodes).toEqual(workflow.nodes); - expect(settings).toEqual(workflow.settings); - expect(createdAt).toBeDefined(); - expect(updatedAt).toBeDefined(); - - // get the workflow after it was deactivated - const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ - where: { - userId: member.id, - workflowId: workflow.id, - }, - relations: ['workflow'], - }); - - // check whether the workflow is deactivated in the database - expect(sharedWorkflow?.workflow.active).toBe(false); - - expect(await workflowRunner.isActive(workflow.id)).toBe(false); -}); - -test('POST /workflows/:id/deactivate should deactivate non-owned workflow when owner', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - const member = await testDb.createUser({ globalRole: globalMemberRole }); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const workflow = await testDb.createWorkflowWithTrigger({}, member); - - await authAgent.post(`/workflows/${workflow.id}/activate`); - - const workflowDeactivationResponse = await authAgent.post(`/workflows/${workflow.id}/deactivate`); - - const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = - workflowDeactivationResponse.body; - - expect(id).toEqual(workflow.id); - expect(name).toEqual(workflow.name); - expect(connections).toEqual(workflow.connections); - expect(active).toBe(false); - expect(staticData).toEqual(workflow.staticData); - expect(nodes).toEqual(workflow.nodes); - expect(settings).toEqual(workflow.settings); - expect(createdAt).toBeDefined(); - expect(updatedAt).toBeDefined(); - - // check whether the workflow is deactivated in the database - const sharedOwnerWorkflow = await Db.collections.SharedWorkflow.findOne({ - where: { - userId: owner.id, - workflowId: workflow.id, - }, - }); - - expect(sharedOwnerWorkflow).toBeNull(); - - const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ - where: { - userId: member.id, - workflowId: workflow.id, - }, - relations: ['workflow'], - }); - - expect(sharedWorkflow?.workflow.active).toBe(false); - - expect(await workflowRunner.isActive(workflow.id)).toBe(false); -}); - -test('POST /workflows should fail due to missing API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.post('/workflows'); - - expect(response.statusCode).toBe(401); -}); - -test('POST /workflows should fail due to invalid API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - owner.apiKey = 'abcXYZ'; - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.post('/workflows'); - - expect(response.statusCode).toBe(401); -}); - -test('POST /workflows should fail due to invalid body', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.post('/workflows').send({}); - - expect(response.statusCode).toBe(400); -}); - -test('POST /workflows should create workflow', async () => { - const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: member, - version: 1, - }); - - const payload = { - name: 'testing', - nodes: [ - { - id: 'uuid-1234', - parameters: {}, - name: 'Start', - type: 'n8n-nodes-base.start', - typeVersion: 1, - position: [240, 300], - }, - ], - connections: {}, - staticData: null, - settings: { - saveExecutionProgress: true, - saveManualExecutions: true, - saveDataErrorExecution: 'all', - saveDataSuccessExecution: 'all', - executionTimeout: 3600, - timezone: 'America/New_York', - }, - }; - - const response = await authAgent.post('/workflows').send(payload); - - expect(response.statusCode).toBe(200); - - const { id, name, nodes, connections, staticData, active, settings, createdAt, updatedAt } = - response.body; - - expect(id).toBeDefined(); - expect(name).toBe(payload.name); - expect(connections).toEqual(payload.connections); - expect(settings).toEqual(payload.settings); - expect(staticData).toEqual(payload.staticData); - expect(nodes).toEqual(payload.nodes); - expect(active).toBe(false); - expect(createdAt).toBeDefined(); - expect(updatedAt).toEqual(createdAt); - - // check if created workflow in DB - const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ - where: { - userId: member.id, - workflowId: response.body.id, - }, - relations: ['workflow', 'role'], - }); - - expect(sharedWorkflow?.workflow.name).toBe(name); - expect(sharedWorkflow?.workflow.createdAt.toISOString()).toBe(createdAt); - expect(sharedWorkflow?.role).toEqual(workflowOwnerRole); -}); - -test('PUT /workflows/:id should fail due to missing API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.put(`/workflows/1`); - - expect(response.statusCode).toBe(401); -}); - -test('PUT /workflows/:id should fail due to invalid API Key', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - owner.apiKey = 'abcXYZ'; - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.put(`/workflows/1`).send({}); - - expect(response.statusCode).toBe(401); -}); - -test('PUT /workflows/:id should fail due to non-existing workflow', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.put(`/workflows/1`).send({ - name: 'testing', - nodes: [ - { - id: 'uuid-1234', - parameters: {}, - name: 'Start', - type: 'n8n-nodes-base.start', - typeVersion: 1, - position: [240, 300], - }, - ], - connections: {}, - staticData: null, - settings: { - saveExecutionProgress: true, - saveManualExecutions: true, - saveDataErrorExecution: 'all', - saveDataSuccessExecution: 'all', - executionTimeout: 3600, - timezone: 'America/New_York', - }, - }); - - expect(response.statusCode).toBe(404); -}); - -test('PUT /workflows/:id should fail due to invalid body', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - const authOwnerAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const response = await authOwnerAgent.put(`/workflows/1`).send({ - nodes: [ - { - id: 'uuid-1234', - parameters: {}, - name: 'Start', - type: 'n8n-nodes-base.start', - typeVersion: 1, - position: [240, 300], - }, - ], - connections: {}, - staticData: null, - settings: { - saveExecutionProgress: true, - saveManualExecutions: true, - saveDataErrorExecution: 'all', - saveDataSuccessExecution: 'all', - executionTimeout: 3600, - timezone: 'America/New_York', - }, - }); - - expect(response.statusCode).toBe(400); -}); - -test('PUT /workflows/:id should update workflow', async () => { - const member = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - - const workflow = await testDb.createWorkflow({}, member); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: member, - version: 1, - }); - - const payload = { - name: 'name updated', - nodes: [ - { - id: 'uuid-1234', - parameters: {}, - name: 'Start', - type: 'n8n-nodes-base.start', - typeVersion: 1, - position: [240, 300], - }, - { - id: 'uuid-1234', - parameters: {}, - name: 'Cron', - type: 'n8n-nodes-base.cron', - typeVersion: 1, - position: [400, 300], - }, - ], - connections: {}, - staticData: '{"id":1}', - settings: { - saveExecutionProgress: false, - saveManualExecutions: false, - saveDataErrorExecution: 'all', - saveDataSuccessExecution: 'all', - executionTimeout: 3600, - timezone: 'America/New_York', - }, - }; - - const response = await authAgent.put(`/workflows/${workflow.id}`).send(payload); - - const { id, name, nodes, connections, staticData, active, settings, createdAt, updatedAt } = - response.body; - - expect(response.statusCode).toBe(200); - - expect(id).toBe(workflow.id); - expect(name).toBe(payload.name); - expect(connections).toEqual(payload.connections); - expect(settings).toEqual(payload.settings); - expect(staticData).toMatchObject(JSON.parse(payload.staticData)); - expect(nodes).toEqual(payload.nodes); - expect(active).toBe(false); - expect(createdAt).toBe(workflow.createdAt.toISOString()); - expect(updatedAt).not.toBe(workflow.updatedAt.toISOString()); - - // check updated workflow in DB - const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ - where: { - userId: member.id, - workflowId: response.body.id, - }, - relations: ['workflow'], - }); - - expect(sharedWorkflow?.workflow.name).toBe(payload.name); - expect(sharedWorkflow?.workflow.updatedAt.getTime()).toBeGreaterThan( - workflow.updatedAt.getTime(), - ); -}); - -test('PUT /workflows/:id should update non-owned workflow if owner', async () => { - const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); - const member = await testDb.createUser({ globalRole: globalMemberRole }); - - const workflow = await testDb.createWorkflow({}, member); - - const authAgent = utils.createAgent(app, { - apiPath: 'public', - auth: true, - user: owner, - version: 1, - }); - - const payload = { - name: 'name owner updated', - nodes: [ - { - id: 'uuid-1', - parameters: {}, - name: 'Start', - type: 'n8n-nodes-base.start', - typeVersion: 1, - position: [240, 300], - }, - { - id: 'uuid-2', - parameters: {}, - name: 'Cron', - type: 'n8n-nodes-base.cron', - typeVersion: 1, - position: [400, 300], - }, - ], - connections: {}, - staticData: '{"id":1}', - settings: { - saveExecutionProgress: false, - saveManualExecutions: false, - saveDataErrorExecution: 'all', - saveDataSuccessExecution: 'all', - executionTimeout: 3600, - timezone: 'America/New_York', - }, - }; - - const response = await authAgent.put(`/workflows/${workflow.id}`).send(payload); - - const { id, name, nodes, connections, staticData, active, settings, createdAt, updatedAt } = - response.body; - - expect(response.statusCode).toBe(200); - - expect(id).toBe(workflow.id); - expect(name).toBe(payload.name); - expect(connections).toEqual(payload.connections); - expect(settings).toEqual(payload.settings); - expect(staticData).toMatchObject(JSON.parse(payload.staticData)); - expect(nodes).toEqual(payload.nodes); - expect(active).toBe(false); - expect(createdAt).toBe(workflow.createdAt.toISOString()); - expect(updatedAt).not.toBe(workflow.updatedAt.toISOString()); - - // check updated workflow in DB - const sharedOwnerWorkflow = await Db.collections.SharedWorkflow.findOne({ - where: { - userId: owner.id, - workflowId: response.body.id, - }, - }); - - expect(sharedOwnerWorkflow).toBeNull(); - - const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ - where: { - userId: member.id, - workflowId: response.body.id, - }, - relations: ['workflow', 'role'], - }); - - expect(sharedWorkflow?.workflow.name).toBe(payload.name); - expect(sharedWorkflow?.workflow.updatedAt.getTime()).toBeGreaterThan( - workflow.updatedAt.getTime(), - ); - expect(sharedWorkflow?.role).toEqual(workflowOwnerRole); + + const [fetchedGlobalOwnerRole, fetchedGlobalMemberRole, fetchedWorkflowOwnerRole] = + await testDb.getAllRoles(); + + globalOwnerRole = fetchedGlobalOwnerRole; + globalMemberRole = fetchedGlobalMemberRole; + workflowOwnerRole = fetchedWorkflowOwnerRole; + + owner = await testDb.createUser({ + globalRole: globalOwnerRole, + apiKey: randomApiKey(), + }); + + member = await testDb.createUser({ + globalRole: globalMemberRole, + apiKey: randomApiKey(), + }); + + utils.initConfigFile(); + await utils.initNodeTypes(); + workflowRunner = await utils.initActiveWorkflowRunner(); + }); + + beforeEach(async () => { + await testDb.truncate([ + 'SharedCredentials', + 'SharedWorkflow', + 'Tag', + 'Workflow', + 'Credentials', + ]); + + authOwnerAgent = utils.createAgent(app, { + apiPath: 'public', + auth: true, + user: owner, + version: 1, + }); + + authMemberAgent = utils.createAgent(app, { + apiPath: 'public', + auth: true, + user: member, + version: 1, + }); + + config.set('userManagement.disabled', false); + config.set('userManagement.isInstanceOwnerSetUp', true); + }); + + afterEach(async () => { + await workflowRunner?.removeAll(); + }); + + afterAll(async () => { + await testDb.terminate(); + }); + + const testWithAPIKey = + (method: 'get' | 'post' | 'put' | 'delete', url: string, apiKey: string | null) => async () => { + authOwnerAgent.set({ 'X-N8N-API-KEY': apiKey }); + const response = await authOwnerAgent[method](url); + expect(response.statusCode).toBe(401); + }; + + describe('GET /workflows', () => { + test('should fail due to missing API Key', testWithAPIKey('get', '/workflows', null)); + + test('should fail due to invalid API Key', testWithAPIKey('get', '/workflows', 'abcXYZ')); + + test('should return all owned workflows', async () => { + await Promise.all([ + testDb.createWorkflow({}, member), + testDb.createWorkflow({}, member), + testDb.createWorkflow({}, member), + ]); + + const response = await authMemberAgent.get('/workflows'); + + expect(response.statusCode).toBe(200); + expect(response.body.data.length).toBe(3); + expect(response.body.nextCursor).toBeNull(); + + for (const workflow of response.body.data) { + const { + id, + connections, + active, + staticData, + nodes, + settings, + name, + createdAt, + updatedAt, + tags, + } = workflow; + + expect(id).toBeDefined(); + expect(name).toBeDefined(); + expect(connections).toBeDefined(); + expect(active).toBe(false); + expect(staticData).toBeDefined(); + expect(nodes).toBeDefined(); + expect(tags).toBeDefined(); + expect(settings).toBeDefined(); + expect(createdAt).toBeDefined(); + expect(updatedAt).toBeDefined(); + } + }); + + test('should return all owned workflows with pagination', async () => { + await Promise.all([ + testDb.createWorkflow({}, member), + testDb.createWorkflow({}, member), + testDb.createWorkflow({}, member), + ]); + + const response = await authMemberAgent.get('/workflows?limit=1'); + + expect(response.statusCode).toBe(200); + expect(response.body.data.length).toBe(1); + expect(response.body.nextCursor).not.toBeNull(); + + const response2 = await authMemberAgent.get( + `/workflows?limit=1&cursor=${response.body.nextCursor}`, + ); + + expect(response2.statusCode).toBe(200); + expect(response2.body.data.length).toBe(1); + expect(response2.body.nextCursor).not.toBeNull(); + expect(response2.body.nextCursor).not.toBe(response.body.nextCursor); + + const responses = [...response.body.data, ...response2.body.data]; + + for (const workflow of responses) { + const { + id, + connections, + active, + staticData, + nodes, + settings, + name, + createdAt, + updatedAt, + tags, + } = workflow; + + expect(id).toBeDefined(); + expect(name).toBeDefined(); + expect(connections).toBeDefined(); + expect(active).toBe(false); + expect(staticData).toBeDefined(); + expect(nodes).toBeDefined(); + expect(tags).toBeDefined(); + expect(settings).toBeDefined(); + expect(createdAt).toBeDefined(); + expect(updatedAt).toBeDefined(); + } + + // check that we really received a different result + expect(Number(response.body.data[0].id)).toBeLessThan(Number(response2.body.data[0].id)); + }); + + test('should return all owned workflows filtered by tag', async () => { + const tag = await testDb.createTag({}); + + const [workflow] = await Promise.all([ + testDb.createWorkflow({ tags: [tag] }, member), + testDb.createWorkflow({}, member), + ]); + + const response = await authMemberAgent.get(`/workflows?tags=${tag.name}`); + + expect(response.statusCode).toBe(200); + expect(response.body.data.length).toBe(1); + + const { + id, + connections, + active, + staticData, + nodes, + settings, + name, + createdAt, + updatedAt, + tags: wfTags, + } = response.body.data[0]; + + expect(id).toBe(workflow.id); + expect(name).toBeDefined(); + expect(connections).toBeDefined(); + expect(active).toBe(false); + expect(staticData).toBeDefined(); + expect(nodes).toBeDefined(); + expect(settings).toBeDefined(); + expect(createdAt).toBeDefined(); + expect(updatedAt).toBeDefined(); + + expect(wfTags.length).toBe(1); + expect(wfTags[0].id).toBe(tag.id); + }); + + test('should return all owned workflows filtered by tags', async () => { + const tags = await Promise.all([await testDb.createTag({}), await testDb.createTag({})]); + const tagNames = tags.map((tag) => tag.name).join(','); + + const [workflow1, workflow2] = await Promise.all([ + testDb.createWorkflow({ tags }, member), + testDb.createWorkflow({ tags }, member), + testDb.createWorkflow({}, member), + testDb.createWorkflow({ tags: [tags[0]] }, member), + testDb.createWorkflow({ tags: [tags[1]] }, member), + ]); + + const response = await authMemberAgent.get(`/workflows?tags=${tagNames}`); + + expect(response.statusCode).toBe(200); + expect(response.body.data.length).toBe(2); + + for (const workflow of response.body.data) { + const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = + workflow; + + expect(id).toBeDefined(); + expect([workflow1.id, workflow2.id].includes(id)).toBe(true); + + expect(name).toBeDefined(); + expect(connections).toBeDefined(); + expect(active).toBe(false); + expect(staticData).toBeDefined(); + expect(nodes).toBeDefined(); + expect(settings).toBeDefined(); + expect(createdAt).toBeDefined(); + expect(updatedAt).toBeDefined(); + + expect(workflow.tags.length).toBe(2); + workflow.tags.forEach((tag: TagEntity) => { + expect(tags.some((savedTag) => savedTag.id === tag.id)).toBe(true); + }); + } + }); + + test('should return all workflows for owner', async () => { + await Promise.all([ + testDb.createWorkflow({}, owner), + testDb.createWorkflow({}, member), + testDb.createWorkflow({}, owner), + testDb.createWorkflow({}, member), + testDb.createWorkflow({}, owner), + ]); + + const response = await authOwnerAgent.get('/workflows'); + + expect(response.statusCode).toBe(200); + expect(response.body.data.length).toBe(5); + expect(response.body.nextCursor).toBeNull(); + + for (const workflow of response.body.data) { + const { + id, + connections, + active, + staticData, + nodes, + settings, + name, + createdAt, + updatedAt, + tags, + } = workflow; + + expect(id).toBeDefined(); + expect(name).toBeDefined(); + expect(connections).toBeDefined(); + expect(active).toBe(false); + expect(staticData).toBeDefined(); + expect(nodes).toBeDefined(); + expect(tags).toBeDefined(); + expect(settings).toBeDefined(); + expect(createdAt).toBeDefined(); + expect(updatedAt).toBeDefined(); + } + }); + }); + + describe('GET /workflows/:id', () => { + test('should fail due to missing API Key', testWithAPIKey('get', '/workflows/2', null)); + + test('should fail due to invalid API Key', testWithAPIKey('get', '/workflows/2', 'abcXYZ')); + + test('should fail due to non-existing workflow', async () => { + const response = await authOwnerAgent.get(`/workflows/2`); + expect(response.statusCode).toBe(404); + }); + + test('should retrieve workflow', async () => { + // create and assign workflow to owner + const workflow = await testDb.createWorkflow({}, member); + + const response = await authMemberAgent.get(`/workflows/${workflow.id}`); + + expect(response.statusCode).toBe(200); + + const { + id, + connections, + active, + staticData, + nodes, + settings, + name, + createdAt, + updatedAt, + tags, + } = response.body; + + expect(id).toEqual(workflow.id); + expect(name).toEqual(workflow.name); + expect(connections).toEqual(workflow.connections); + expect(active).toBe(false); + expect(staticData).toEqual(workflow.staticData); + expect(nodes).toEqual(workflow.nodes); + expect(tags).toEqual([]); + expect(settings).toEqual(workflow.settings); + expect(createdAt).toEqual(workflow.createdAt.toISOString()); + expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); + }); + + test('should retrieve non-owned workflow for owner', async () => { + // create and assign workflow to owner + const workflow = await testDb.createWorkflow({}, member); + + const response = await authOwnerAgent.get(`/workflows/${workflow.id}`); + + expect(response.statusCode).toBe(200); + + const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = + response.body; + + expect(id).toEqual(workflow.id); + expect(name).toEqual(workflow.name); + expect(connections).toEqual(workflow.connections); + expect(active).toBe(false); + expect(staticData).toEqual(workflow.staticData); + expect(nodes).toEqual(workflow.nodes); + expect(settings).toEqual(workflow.settings); + expect(createdAt).toEqual(workflow.createdAt.toISOString()); + expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); + }); + }); + + describe('DELETE /workflows/:id', () => { + test('should fail due to missing API Key', testWithAPIKey('delete', '/workflows/2', null)); + + test('should fail due to invalid API Key', testWithAPIKey('delete', '/workflows/2', 'abcXYZ')); + + test('should fail due to non-existing workflow', async () => { + const response = await authOwnerAgent.delete(`/workflows/2`); + expect(response.statusCode).toBe(404); + }); + + test('should delete the workflow', async () => { + // create and assign workflow to owner + const workflow = await testDb.createWorkflow({}, member); + + const response = await authMemberAgent.delete(`/workflows/${workflow.id}`); + + expect(response.statusCode).toBe(200); + + const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = + response.body; + + expect(id).toEqual(workflow.id); + expect(name).toEqual(workflow.name); + expect(connections).toEqual(workflow.connections); + expect(active).toBe(false); + expect(staticData).toEqual(workflow.staticData); + expect(nodes).toEqual(workflow.nodes); + expect(settings).toEqual(workflow.settings); + expect(createdAt).toEqual(workflow.createdAt.toISOString()); + expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); + + // make sure the workflow actually deleted from the db + const sharedWorkflow = await Db.collections.SharedWorkflow.findOneBy({ + workflowId: workflow.id, + }); + + expect(sharedWorkflow).toBeNull(); + }); + + test('should delete non-owned workflow when owner', async () => { + // create and assign workflow to owner + const workflow = await testDb.createWorkflow({}, member); + + const response = await authMemberAgent.delete(`/workflows/${workflow.id}`); + + expect(response.statusCode).toBe(200); + + const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = + response.body; + + expect(id).toEqual(workflow.id); + expect(name).toEqual(workflow.name); + expect(connections).toEqual(workflow.connections); + expect(active).toBe(false); + expect(staticData).toEqual(workflow.staticData); + expect(nodes).toEqual(workflow.nodes); + expect(settings).toEqual(workflow.settings); + expect(createdAt).toEqual(workflow.createdAt.toISOString()); + expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); + + // make sure the workflow actually deleted from the db + const sharedWorkflow = await Db.collections.SharedWorkflow.findOneBy({ + workflowId: workflow.id, + }); + + expect(sharedWorkflow).toBeNull(); + }); + }); + + describe('POST /workflows/:id/activate', () => { + test( + 'should fail due to missing API Key', + testWithAPIKey('post', '/workflows/2/activate', null), + ); + + test( + 'should fail due to invalid API Key', + testWithAPIKey('post', '/workflows/2/activate', 'abcXYZ'), + ); + + test('should fail due to non-existing workflow', async () => { + const response = await authOwnerAgent.post(`/workflows/2/activate`); + expect(response.statusCode).toBe(404); + }); + + test('should fail due to trying to activate a workflow without a trigger', async () => { + const workflow = await testDb.createWorkflow({}, owner); + const response = await authOwnerAgent.post(`/workflows/${workflow.id}/activate`); + expect(response.statusCode).toBe(400); + }); + + test('should set workflow as active', async () => { + const workflow = await testDb.createWorkflowWithTrigger({}, member); + + const response = await authMemberAgent.post(`/workflows/${workflow.id}/activate`); + + expect(response.statusCode).toBe(200); + + const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = + response.body; + + expect(id).toEqual(workflow.id); + expect(name).toEqual(workflow.name); + expect(connections).toEqual(workflow.connections); + expect(active).toBe(true); + expect(staticData).toEqual(workflow.staticData); + expect(nodes).toEqual(workflow.nodes); + expect(settings).toEqual(workflow.settings); + expect(createdAt).toEqual(workflow.createdAt.toISOString()); + expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); + + // check whether the workflow is on the database + const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ + where: { + userId: member.id, + workflowId: workflow.id, + }, + relations: ['workflow'], + }); + + expect(sharedWorkflow?.workflow.active).toBe(true); + + // check whether the workflow is on the active workflow runner + expect(await workflowRunner.isActive(workflow.id)).toBe(true); + }); + + test('should set non-owned workflow as active when owner', async () => { + const workflow = await testDb.createWorkflowWithTrigger({}, member); + + const response = await authMemberAgent.post(`/workflows/${workflow.id}/activate`); + + expect(response.statusCode).toBe(200); + + const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = + response.body; + + expect(id).toEqual(workflow.id); + expect(name).toEqual(workflow.name); + expect(connections).toEqual(workflow.connections); + expect(active).toBe(true); + expect(staticData).toEqual(workflow.staticData); + expect(nodes).toEqual(workflow.nodes); + expect(settings).toEqual(workflow.settings); + expect(createdAt).toEqual(workflow.createdAt.toISOString()); + expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); + + // check whether the workflow is on the database + const sharedOwnerWorkflow = await Db.collections.SharedWorkflow.findOne({ + where: { + userId: owner.id, + workflowId: workflow.id, + }, + }); + + expect(sharedOwnerWorkflow).toBeNull(); + + const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ + where: { + userId: member.id, + workflowId: workflow.id, + }, + relations: ['workflow'], + }); + + expect(sharedWorkflow?.workflow.active).toBe(true); + + // check whether the workflow is on the active workflow runner + expect(await workflowRunner.isActive(workflow.id)).toBe(true); + }); + }); + + describe('POST /workflows/:id/deactivate', () => { + test( + 'should fail due to missing API Key', + testWithAPIKey('post', '/workflows/2/deactivate', null), + ); + + test( + 'should fail due to invalid API Key', + testWithAPIKey('post', '/workflows/2/deactivate', 'abcXYZ'), + ); + + test('should fail due to non-existing workflow', async () => { + const response = await authOwnerAgent.post(`/workflows/2/deactivate`); + expect(response.statusCode).toBe(404); + }); + + test('should deactivate workflow', async () => { + const workflow = await testDb.createWorkflowWithTrigger({}, member); + + await authMemberAgent.post(`/workflows/${workflow.id}/activate`); + + const workflowDeactivationResponse = await authMemberAgent.post( + `/workflows/${workflow.id}/deactivate`, + ); + + const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = + workflowDeactivationResponse.body; + + expect(id).toEqual(workflow.id); + expect(name).toEqual(workflow.name); + expect(connections).toEqual(workflow.connections); + expect(active).toBe(false); + expect(staticData).toEqual(workflow.staticData); + expect(nodes).toEqual(workflow.nodes); + expect(settings).toEqual(workflow.settings); + expect(createdAt).toBeDefined(); + expect(updatedAt).toBeDefined(); + + // get the workflow after it was deactivated + const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ + where: { + userId: member.id, + workflowId: workflow.id, + }, + relations: ['workflow'], + }); + + // check whether the workflow is deactivated in the database + expect(sharedWorkflow?.workflow.active).toBe(false); + + expect(await workflowRunner.isActive(workflow.id)).toBe(false); + }); + + test('should deactivate non-owned workflow when owner', async () => { + const workflow = await testDb.createWorkflowWithTrigger({}, member); + + await authMemberAgent.post(`/workflows/${workflow.id}/activate`); + + const workflowDeactivationResponse = await authMemberAgent.post( + `/workflows/${workflow.id}/deactivate`, + ); + + const { id, connections, active, staticData, nodes, settings, name, createdAt, updatedAt } = + workflowDeactivationResponse.body; + + expect(id).toEqual(workflow.id); + expect(name).toEqual(workflow.name); + expect(connections).toEqual(workflow.connections); + expect(active).toBe(false); + expect(staticData).toEqual(workflow.staticData); + expect(nodes).toEqual(workflow.nodes); + expect(settings).toEqual(workflow.settings); + expect(createdAt).toBeDefined(); + expect(updatedAt).toBeDefined(); + + // check whether the workflow is deactivated in the database + const sharedOwnerWorkflow = await Db.collections.SharedWorkflow.findOne({ + where: { + userId: owner.id, + workflowId: workflow.id, + }, + }); + + expect(sharedOwnerWorkflow).toBeNull(); + + const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ + where: { + userId: member.id, + workflowId: workflow.id, + }, + relations: ['workflow'], + }); + + expect(sharedWorkflow?.workflow.active).toBe(false); + + expect(await workflowRunner.isActive(workflow.id)).toBe(false); + }); + }); + + describe('POST /workflows', () => { + test('should fail due to missing API Key', testWithAPIKey('post', '/workflows', null)); + + test('should fail due to invalid API Key', testWithAPIKey('post', '/workflows', 'abcXYZ')); + + test('should fail due to invalid body', async () => { + const response = await authOwnerAgent.post('/workflows').send({}); + expect(response.statusCode).toBe(400); + }); + + test('should create workflow', async () => { + const payload = { + name: 'testing', + nodes: [ + { + id: 'uuid-1234', + parameters: {}, + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [240, 300], + }, + ], + connections: {}, + staticData: null, + settings: { + saveExecutionProgress: true, + saveManualExecutions: true, + saveDataErrorExecution: 'all', + saveDataSuccessExecution: 'all', + executionTimeout: 3600, + timezone: 'America/New_York', + }, + }; + + const response = await authMemberAgent.post('/workflows').send(payload); + + expect(response.statusCode).toBe(200); + + const { id, name, nodes, connections, staticData, active, settings, createdAt, updatedAt } = + response.body; + + expect(id).toBeDefined(); + expect(name).toBe(payload.name); + expect(connections).toEqual(payload.connections); + expect(settings).toEqual(payload.settings); + expect(staticData).toEqual(payload.staticData); + expect(nodes).toEqual(payload.nodes); + expect(active).toBe(false); + expect(createdAt).toBeDefined(); + expect(updatedAt).toEqual(createdAt); + + // check if created workflow in DB + const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ + where: { + userId: member.id, + workflowId: response.body.id, + }, + relations: ['workflow', 'role'], + }); + + expect(sharedWorkflow?.workflow.name).toBe(name); + expect(sharedWorkflow?.workflow.createdAt.toISOString()).toBe(createdAt); + expect(sharedWorkflow?.role).toEqual(workflowOwnerRole); + }); + }); + + describe('PUT /workflows/:id', () => { + test('should fail due to missing API Key', testWithAPIKey('put', '/workflows/1', null)); + + test('should fail due to invalid API Key', testWithAPIKey('put', '/workflows/1', 'abcXYZ')); + + test('should fail due to non-existing workflow', async () => { + const response = await authOwnerAgent.put(`/workflows/1`).send({ + name: 'testing', + nodes: [ + { + id: 'uuid-1234', + parameters: {}, + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [240, 300], + }, + ], + connections: {}, + staticData: null, + settings: { + saveExecutionProgress: true, + saveManualExecutions: true, + saveDataErrorExecution: 'all', + saveDataSuccessExecution: 'all', + executionTimeout: 3600, + timezone: 'America/New_York', + }, + }); + + expect(response.statusCode).toBe(404); + }); + + test('should fail due to invalid body', async () => { + const response = await authOwnerAgent.put(`/workflows/1`).send({ + nodes: [ + { + id: 'uuid-1234', + parameters: {}, + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [240, 300], + }, + ], + connections: {}, + staticData: null, + settings: { + saveExecutionProgress: true, + saveManualExecutions: true, + saveDataErrorExecution: 'all', + saveDataSuccessExecution: 'all', + executionTimeout: 3600, + timezone: 'America/New_York', + }, + }); + + expect(response.statusCode).toBe(400); + }); + + test('should update workflow', async () => { + const workflow = await testDb.createWorkflow({}, member); + const payload = { + name: 'name updated', + nodes: [ + { + id: 'uuid-1234', + parameters: {}, + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [240, 300], + }, + { + id: 'uuid-1234', + parameters: {}, + name: 'Cron', + type: 'n8n-nodes-base.cron', + typeVersion: 1, + position: [400, 300], + }, + ], + connections: {}, + staticData: '{"id":1}', + settings: { + saveExecutionProgress: false, + saveManualExecutions: false, + saveDataErrorExecution: 'all', + saveDataSuccessExecution: 'all', + executionTimeout: 3600, + timezone: 'America/New_York', + }, + }; + + const response = await authMemberAgent.put(`/workflows/${workflow.id}`).send(payload); + + const { id, name, nodes, connections, staticData, active, settings, createdAt, updatedAt } = + response.body; + + expect(response.statusCode).toBe(200); + + expect(id).toBe(workflow.id); + expect(name).toBe(payload.name); + expect(connections).toEqual(payload.connections); + expect(settings).toEqual(payload.settings); + expect(staticData).toMatchObject(JSON.parse(payload.staticData)); + expect(nodes).toEqual(payload.nodes); + expect(active).toBe(false); + expect(createdAt).toBe(workflow.createdAt.toISOString()); + expect(updatedAt).not.toBe(workflow.updatedAt.toISOString()); + + // check updated workflow in DB + const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ + where: { + userId: member.id, + workflowId: response.body.id, + }, + relations: ['workflow'], + }); + + expect(sharedWorkflow?.workflow.name).toBe(payload.name); + expect(sharedWorkflow?.workflow.updatedAt.getTime()).toBeGreaterThan( + workflow.updatedAt.getTime(), + ); + }); + + test('should update non-owned workflow if owner', async () => { + const workflow = await testDb.createWorkflow({}, member); + + const payload = { + name: 'name owner updated', + nodes: [ + { + id: 'uuid-1', + parameters: {}, + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [240, 300], + }, + { + id: 'uuid-2', + parameters: {}, + name: 'Cron', + type: 'n8n-nodes-base.cron', + typeVersion: 1, + position: [400, 300], + }, + ], + connections: {}, + staticData: '{"id":1}', + settings: { + saveExecutionProgress: false, + saveManualExecutions: false, + saveDataErrorExecution: 'all', + saveDataSuccessExecution: 'all', + executionTimeout: 3600, + timezone: 'America/New_York', + }, + }; + + const response = await authMemberAgent.put(`/workflows/${workflow.id}`).send(payload); + + const { id, name, nodes, connections, staticData, active, settings, createdAt, updatedAt } = + response.body; + + expect(response.statusCode).toBe(200); + + expect(id).toBe(workflow.id); + expect(name).toBe(payload.name); + expect(connections).toEqual(payload.connections); + expect(settings).toEqual(payload.settings); + expect(staticData).toMatchObject(JSON.parse(payload.staticData)); + expect(nodes).toEqual(payload.nodes); + expect(active).toBe(false); + expect(createdAt).toBe(workflow.createdAt.toISOString()); + expect(updatedAt).not.toBe(workflow.updatedAt.toISOString()); + + // check updated workflow in DB + const sharedOwnerWorkflow = await Db.collections.SharedWorkflow.findOne({ + where: { + userId: owner.id, + workflowId: response.body.id, + }, + }); + + expect(sharedOwnerWorkflow).toBeNull(); + + const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ + where: { + userId: member.id, + workflowId: response.body.id, + }, + relations: ['workflow', 'role'], + }); + + expect(sharedWorkflow?.workflow.name).toBe(payload.name); + expect(sharedWorkflow?.workflow.updatedAt.getTime()).toBeGreaterThan( + workflow.updatedAt.getTime(), + ); + expect(sharedWorkflow?.role).toEqual(workflowOwnerRole); + }); + }); }); diff --git a/packages/cli/test/integration/shared/testDb.ts b/packages/cli/test/integration/shared/testDb.ts index 4b26eb1625..c7fb165b3d 100644 --- a/packages/cli/test/integration/shared/testDb.ts +++ b/packages/cli/test/integration/shared/testDb.ts @@ -172,7 +172,7 @@ export async function createUser(attributes: Partial = {}): Promise password: await hashPassword(password ?? randomValidPassword()), firstName: firstName ?? randomName(), lastName: lastName ?? randomName(), - globalRole: globalRole ?? (await getGlobalMemberRole()), + globalRoleId: (globalRole ?? (await getGlobalMemberRole())).id, ...rest, };