From 2d02c73fbda0b1728b3b3494aee5bea31c9627eb 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: Tue, 18 Jun 2024 10:50:39 +0200 Subject: [PATCH] refactor(core): Refactor cli command tests (no-changelog) (#9731) --- packages/cli/src/commands/worker.ts | 4 +- .../commands/credentials.cmd.test.ts | 45 +++----- .../integration/commands/import.cmd.test.ts | 48 +++------ .../integration/commands/ldap/reset.test.ts | 84 ++++++--------- .../integration/commands/license.cmd.test.ts | 23 ++-- .../integration/commands/reset.cmd.test.ts | 32 +++--- .../commands/update/workflow.test.ts | 39 ++----- .../integration/commands/worker.cmd.test.ts | 102 ++++++------------ .../integration/shared/utils/testCommand.ts | 37 +++++++ 9 files changed, 162 insertions(+), 252 deletions(-) create mode 100644 packages/cli/test/integration/shared/utils/testCommand.ts diff --git a/packages/cli/src/commands/worker.ts b/packages/cli/src/commands/worker.ts index ad7987471b9f5..c60b7b4301362 100644 --- a/packages/cli/src/commands/worker.ts +++ b/packages/cli/src/commands/worker.ts @@ -14,7 +14,7 @@ import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData' import config from '@/config'; import type { Job, JobId, JobResponse, WebhookResponse } from '@/Queue'; import { Queue } from '@/Queue'; -import { N8N_VERSION } from '@/constants'; +import { N8N_VERSION, inTest } from '@/constants'; import { ExecutionRepository } from '@db/repositories/execution.repository'; import { WorkflowRepository } from '@db/repositories/workflow.repository'; import type { ICredentialsOverwrite } from '@/Interfaces'; @@ -498,7 +498,7 @@ export class Worker extends BaseCommand { } // Make sure that the process does not close - await new Promise(() => {}); + if (!inTest) await new Promise(() => {}); } async catch(error: Error) { diff --git a/packages/cli/test/integration/commands/credentials.cmd.test.ts b/packages/cli/test/integration/commands/credentials.cmd.test.ts index 812549c5b5ef2..386f3a41b20de 100644 --- a/packages/cli/test/integration/commands/credentials.cmd.test.ts +++ b/packages/cli/test/integration/commands/credentials.cmd.test.ts @@ -1,39 +1,24 @@ -import { Config } from '@oclif/core'; +import { nanoid } from 'nanoid'; import { InternalHooks } from '@/InternalHooks'; import { ImportCredentialsCommand } from '@/commands/import/credentials'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; +import { setupTestCommand } from '@test-integration/utils/testCommand'; import { mockInstance } from '../../shared/mocking'; import * as testDb from '../shared/testDb'; import { getAllCredentials, getAllSharedCredentials } from '../shared/db/credentials'; import { createMember, createOwner } from '../shared/db/users'; import { getPersonalProject } from '../shared/db/projects'; -import { nanoid } from 'nanoid'; - -const oclifConfig = new Config({ root: __dirname }); -async function importCredential(argv: string[]) { - const importer = new ImportCredentialsCommand(argv, oclifConfig); - await importer.init(); - await importer.run(); -} - -beforeAll(async () => { - mockInstance(InternalHooks); - mockInstance(LoadNodesAndCredentials); - await testDb.init(); - await oclifConfig.load(); -}); +mockInstance(InternalHooks); +mockInstance(LoadNodesAndCredentials); +const command = setupTestCommand(ImportCredentialsCommand); beforeEach(async () => { await testDb.truncate(['Credentials', 'SharedCredentials', 'User']); }); -afterAll(async () => { - await testDb.terminate(); -}); - test('import:credentials should import a credential', async () => { // // ARRANGE @@ -44,9 +29,7 @@ test('import:credentials should import a credential', async () => { // // ACT // - await importCredential([ - '--input=./test/integration/commands/importCredentials/credentials.json', - ]); + await command.run(['--input=./test/integration/commands/importCredentials/credentials.json']); // // ASSERT @@ -78,7 +61,7 @@ test('import:credentials should import a credential from separated files', async // ACT // // import credential the first time, assigning it to the owner - await importCredential([ + await command.run([ '--separate', '--input=./test/integration/commands/importCredentials/separate', ]); @@ -117,7 +100,7 @@ test('`import:credentials --userId ...` should fail if the credential exists alr const member = await createMember(); // import credential the first time, assigning it to the owner - await importCredential([ + await command.run([ '--input=./test/integration/commands/importCredentials/credentials.json', `--userId=${owner.id}`, ]); @@ -145,7 +128,7 @@ test('`import:credentials --userId ...` should fail if the credential exists alr // Import again while updating the name we try to assign the // credential to another user. await expect( - importCredential([ + command.run([ '--input=./test/integration/commands/importCredentials/credentials-updated.json', `--userId=${member.id}`, ]), @@ -188,7 +171,7 @@ test("only update credential, don't create or update owner if neither `--userId` const memberProject = await getPersonalProject(member); // import credential the first time, assigning it to a member - await importCredential([ + await command.run([ '--input=./test/integration/commands/importCredentials/credentials.json', `--userId=${member.id}`, ]); @@ -213,7 +196,7 @@ test("only update credential, don't create or update owner if neither `--userId` // ACT // // Import again only updating the name and omitting `--userId` - await importCredential([ + await command.run([ '--input=./test/integration/commands/importCredentials/credentials-updated.json', ]); @@ -253,7 +236,7 @@ test('`import:credential --projectId ...` should fail if the credential already const memberProject = await getPersonalProject(member); // import credential the first time, assigning it to the owner - await importCredential([ + await command.run([ '--input=./test/integration/commands/importCredentials/credentials.json', `--userId=${owner.id}`, ]); @@ -281,7 +264,7 @@ test('`import:credential --projectId ...` should fail if the credential already // Import again while updating the name we try to assign the // credential to another user. await expect( - importCredential([ + command.run([ '--input=./test/integration/commands/importCredentials/credentials-updated.json', `--projectId=${memberProject.id}`, ]), @@ -317,7 +300,7 @@ test('`import:credential --projectId ...` should fail if the credential already test('`import:credential --projectId ... --userId ...` fails explaining that only one of the options can be used at a time', async () => { await expect( - importCredential([ + command.run([ '--input=./test/integration/commands/importCredentials/credentials-updated.json', `--projectId=${nanoid()}`, `--userId=${nanoid()}`, diff --git a/packages/cli/test/integration/commands/import.cmd.test.ts b/packages/cli/test/integration/commands/import.cmd.test.ts index 1b3cde31664b0..3295f0de27b1c 100644 --- a/packages/cli/test/integration/commands/import.cmd.test.ts +++ b/packages/cli/test/integration/commands/import.cmd.test.ts @@ -1,39 +1,24 @@ -import { Config } from '@oclif/core'; +import { nanoid } from 'nanoid'; import { InternalHooks } from '@/InternalHooks'; import { ImportWorkflowsCommand } from '@/commands/import/workflow'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; +import { setupTestCommand } from '@test-integration/utils/testCommand'; import { mockInstance } from '../../shared/mocking'; import * as testDb from '../shared/testDb'; import { getAllSharedWorkflows, getAllWorkflows } from '../shared/db/workflows'; import { createMember, createOwner } from '../shared/db/users'; import { getPersonalProject } from '../shared/db/projects'; -import { nanoid } from 'nanoid'; - -const oclifConfig = new Config({ root: __dirname }); -async function importWorkflow(argv: string[]) { - const importer = new ImportWorkflowsCommand(argv, oclifConfig); - await importer.init(); - await importer.run(); -} - -beforeAll(async () => { - mockInstance(InternalHooks); - mockInstance(LoadNodesAndCredentials); - await testDb.init(); - await oclifConfig.load(); -}); +mockInstance(InternalHooks); +mockInstance(LoadNodesAndCredentials); +const command = setupTestCommand(ImportWorkflowsCommand); beforeEach(async () => { await testDb.truncate(['Workflow', 'SharedWorkflow', 'User']); }); -afterAll(async () => { - await testDb.terminate(); -}); - test('import:workflow should import active workflow and deactivate it', async () => { // // ARRANGE @@ -44,10 +29,7 @@ test('import:workflow should import active workflow and deactivate it', async () // // ACT // - await importWorkflow([ - '--separate', - '--input=./test/integration/commands/importWorkflows/separate', - ]); + await command.run(['--separate', '--input=./test/integration/commands/importWorkflows/separate']); // // ASSERT @@ -86,9 +68,7 @@ test('import:workflow should import active workflow from combined file and deact // // ACT // - await importWorkflow([ - '--input=./test/integration/commands/importWorkflows/combined/combined.json', - ]); + await command.run(['--input=./test/integration/commands/importWorkflows/combined/combined.json']); // // ASSERT @@ -126,7 +106,7 @@ test('`import:workflow --userId ...` should fail if the workflow exists already const member = await createMember(); // Import workflow the first time, assigning it to a member. - await importWorkflow([ + await command.run([ '--input=./test/integration/commands/importWorkflows/combined-with-update/original.json', `--userId=${owner.id}`, ]); @@ -153,7 +133,7 @@ test('`import:workflow --userId ...` should fail if the workflow exists already // Import the same workflow again, with another name but the same ID, and try // to assign it to the member. await expect( - importWorkflow([ + command.run([ '--input=./test/integration/commands/importWorkflows/combined-with-update/updated.json', `--userId=${member.id}`, ]), @@ -190,7 +170,7 @@ test("only update the workflow, don't create or update the owner if `--userId` i const memberProject = await getPersonalProject(member); // Import workflow the first time, assigning it to a member. - await importWorkflow([ + await command.run([ '--input=./test/integration/commands/importWorkflows/combined-with-update/original.json', `--userId=${member.id}`, ]); @@ -215,7 +195,7 @@ test("only update the workflow, don't create or update the owner if `--userId` i // ACT // // Import the same workflow again, with another name but the same ID. - await importWorkflow([ + await command.run([ '--input=./test/integration/commands/importWorkflows/combined-with-update/updated.json', ]); @@ -249,7 +229,7 @@ test('`import:workflow --projectId ...` should fail if the credential already ex const memberProject = await getPersonalProject(member); // Import workflow the first time, assigning it to a member. - await importWorkflow([ + await command.run([ '--input=./test/integration/commands/importWorkflows/combined-with-update/original.json', `--userId=${owner.id}`, ]); @@ -276,7 +256,7 @@ test('`import:workflow --projectId ...` should fail if the credential already ex // Import the same workflow again, with another name but the same ID, and try // to assign it to the member. await expect( - importWorkflow([ + command.run([ '--input=./test/integration/commands/importWorkflows/combined-with-update/updated.json', `--projectId=${memberProject.id}`, ]), @@ -306,7 +286,7 @@ test('`import:workflow --projectId ...` should fail if the credential already ex test('`import:workflow --projectId ... --userId ...` fails explaining that only one of the options can be used at a time', async () => { await expect( - importWorkflow([ + command.run([ '--input=./test/integration/commands/importWorkflows/combined-with-update/updated.json', `--userId=${nanoid()}`, `--projectId=${nanoid()}`, diff --git a/packages/cli/test/integration/commands/ldap/reset.test.ts b/packages/cli/test/integration/commands/ldap/reset.test.ts index 24efed477c0dd..1464b56728e69 100644 --- a/packages/cli/test/integration/commands/ldap/reset.test.ts +++ b/packages/cli/test/integration/commands/ldap/reset.test.ts @@ -1,57 +1,37 @@ -import { Reset } from '@/commands/ldap/reset'; -import { Config } from '@oclif/core'; +import { Container } from 'typedi'; +import { v4 as uuid } from 'uuid'; +import { EntityNotFoundError } from '@n8n/typeorm'; -import * as testDb from '../../shared/testDb'; +import { Reset } from '@/commands/ldap/reset'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; -import { mockInstance } from '../../../shared/mocking'; import { InternalHooks } from '@/InternalHooks'; +import { WorkflowRepository } from '@db/repositories/workflow.repository'; +import { CredentialsRepository } from '@db/repositories/credentials.repository'; +import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; +import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; +import { getLdapSynchronizations, saveLdapSynchronization } from '@/Ldap/helpers'; +import { LdapService } from '@/Ldap/ldap.service'; +import { Push } from '@/push'; +import { Telemetry } from '@/telemetry'; + +import { setupTestCommand } from '@test-integration/utils/testCommand'; +import { mockInstance } from '../../../shared/mocking'; import { createLdapUser, createMember, getUserById } from '../../shared/db/users'; import { createWorkflow } from '../../shared/db/workflows'; import { randomCredentialPayload } from '../../shared/random'; import { saveCredential } from '../../shared/db/credentials'; -import Container from 'typedi'; -import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; -import { CredentialsRepository } from '@/databases/repositories/credentials.repository'; -import { EntityNotFoundError } from '@n8n/typeorm'; -import { Push } from '@/push'; -import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflow.repository'; -import { SharedCredentialsRepository } from '@/databases/repositories/sharedCredentials.repository'; -import { createTeamProject, findProject, getPersonalProject } from '../../shared/db/projects'; -import { getLdapSynchronizations, saveLdapSynchronization } from '@/Ldap/helpers'; import { createLdapConfig } from '../../shared/ldap'; -import { LdapService } from '@/Ldap/ldap.service'; -import { v4 as uuid } from 'uuid'; -import { Telemetry } from '@/telemetry'; +import { createTeamProject, findProject, getPersonalProject } from '../../shared/db/projects'; mockInstance(Telemetry); -const oclifConfig = new Config({ root: __dirname }); - -async function resetLDAP(argv: string[]) { - const cmd = new Reset(argv, oclifConfig); - try { - await cmd.init(); - } catch (error) { - console.error(error); - throw error; - } - await cmd.run(); -} - -beforeAll(async () => { - mockInstance(Push); - mockInstance(InternalHooks); - mockInstance(LoadNodesAndCredentials); - await testDb.init(); - await oclifConfig.load(); -}); - -afterAll(async () => { - await testDb.terminate(); -}); +mockInstance(Push); +mockInstance(InternalHooks); +mockInstance(LoadNodesAndCredentials); +const command = setupTestCommand(Reset); test('fails if neither `--userId` nor `--projectId` nor `--deleteWorkflowsAndCredentials` is passed', async () => { - await expect(resetLDAP([])).rejects.toThrowError( + await expect(command.run()).rejects.toThrowError( 'You must use exactly one of `--userId`, `--projectId` or `--deleteWorkflowsAndCredentials`.', ); }); @@ -66,7 +46,7 @@ test.each([ ])( 'fails if more than one of `--userId`, `--projectId`, `--deleteWorkflowsAndCredentials` are passed', async (...argv) => { - await expect(resetLDAP(argv)).rejects.toThrowError( + await expect(command.run(argv)).rejects.toThrowError( 'You must use exactly one of `--userId`, `--projectId` or `--deleteWorkflowsAndCredentials`.', ); }, @@ -95,7 +75,7 @@ describe('--deleteWorkflowsAndCredentials', () => { // // ACT // - await resetLDAP(['--deleteWorkflowsAndCredentials']); + await command.run(['--deleteWorkflowsAndCredentials']); // // ASSERT @@ -139,7 +119,7 @@ describe('--deleteWorkflowsAndCredentials', () => { // // ACT // - await resetLDAP(['--deleteWorkflowsAndCredentials']); + await command.run(['--deleteWorkflowsAndCredentials']); // // ASSERT @@ -159,7 +139,7 @@ describe('--deleteWorkflowsAndCredentials', () => { // // ACT // - await resetLDAP(['--deleteWorkflowsAndCredentials']); + await command.run(['--deleteWorkflowsAndCredentials']); // // ASSERT @@ -173,7 +153,7 @@ describe('--deleteWorkflowsAndCredentials', () => { describe('--userId', () => { test('fails if the user does not exist', async () => { const userId = uuid(); - await expect(resetLDAP([`--userId=${userId}`])).rejects.toThrowError( + await expect(command.run([`--userId=${userId}`])).rejects.toThrowError( `Could not find the user with the ID ${userId} or their personalProject.`, ); }); @@ -184,7 +164,7 @@ describe('--userId', () => { // const member = await createLdapUser({ role: 'global:member' }, uuid()); - await expect(resetLDAP([`--userId=${member.id}`])).rejects.toThrowError( + await expect(command.run([`--userId=${member.id}`])).rejects.toThrowError( `Can't migrate workflows and credentials to the user with the ID ${member.id}. That user was created via LDAP and will be deleted as well.`, ); }); @@ -212,7 +192,7 @@ describe('--userId', () => { // // ACT // - await resetLDAP([`--userId=${normalMember.id}`]); + await command.run([`--userId=${normalMember.id}`]); // // ASSERT @@ -249,7 +229,7 @@ describe('--userId', () => { describe('--projectId', () => { test('fails if the project does not exist', async () => { const projectId = uuid(); - await expect(resetLDAP([`--projectId=${projectId}`])).rejects.toThrowError( + await expect(command.run([`--projectId=${projectId}`])).rejects.toThrowError( `Could not find the project with the ID ${projectId}.`, ); }); @@ -261,7 +241,7 @@ describe('--projectId', () => { const member = await createLdapUser({ role: 'global:member' }, uuid()); const memberProject = await getPersonalProject(member); - await expect(resetLDAP([`--projectId=${memberProject.id}`])).rejects.toThrowError( + await expect(command.run([`--projectId=${memberProject.id}`])).rejects.toThrowError( `Can't migrate workflows and credentials to the project with the ID ${memberProject.id}. That project is a personal project belonging to a user that was created via LDAP and will be deleted as well.`, ); }); @@ -289,7 +269,7 @@ describe('--projectId', () => { // // ACT // - await resetLDAP([`--projectId=${normalMemberProject.id}`]); + await command.run([`--projectId=${normalMemberProject.id}`]); // // ASSERT @@ -346,7 +326,7 @@ describe('--projectId', () => { // // ACT // - await resetLDAP([`--projectId=${teamProject.id}`]); + await command.run([`--projectId=${teamProject.id}`]); // // ASSERT diff --git a/packages/cli/test/integration/commands/license.cmd.test.ts b/packages/cli/test/integration/commands/license.cmd.test.ts index c36fdc253c7fc..8637d2d02017d 100644 --- a/packages/cli/test/integration/commands/license.cmd.test.ts +++ b/packages/cli/test/integration/commands/license.cmd.test.ts @@ -2,27 +2,18 @@ import { InternalHooks } from '@/InternalHooks'; import { License } from '@/License'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; import { ClearLicenseCommand } from '@/commands/license/clear'; -import { Config } from '@oclif/core'; -import { mockInstance } from '../../shared/mocking'; -const oclifConfig = new Config({ root: __dirname }); +import { setupTestCommand } from '@test-integration/utils/testCommand'; +import { mockInstance } from '../../shared/mocking'; -beforeAll(async () => { - mockInstance(InternalHooks); - mockInstance(LoadNodesAndCredentials); - await oclifConfig.load(); -}); +mockInstance(InternalHooks); +mockInstance(LoadNodesAndCredentials); +const license = mockInstance(License); +const command = setupTestCommand(ClearLicenseCommand); test('license:clear invokes shutdown() to release any floating entitlements', async () => { - const cmd = new ClearLicenseCommand([], oclifConfig); - await cmd.init(); - - const license = mockInstance(License); - - await cmd.run(); + await command.run(); expect(license.init).toHaveBeenCalledTimes(1); expect(license.shutdown).toHaveBeenCalledTimes(1); - - jest.restoreAllMocks(); }); diff --git a/packages/cli/test/integration/commands/reset.cmd.test.ts b/packages/cli/test/integration/commands/reset.cmd.test.ts index 04e92dbdf25a3..769bd249294f7 100644 --- a/packages/cli/test/integration/commands/reset.cmd.test.ts +++ b/packages/cli/test/integration/commands/reset.cmd.test.ts @@ -1,38 +1,34 @@ +import { Container } from 'typedi'; + import { Reset } from '@/commands/user-management/reset'; import { InternalHooks } from '@/InternalHooks'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; import { NodeTypes } from '@/NodeTypes'; -import Container from 'typedi'; +import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; +import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; +import { CredentialsRepository } from '@db/repositories/credentials.repository'; +import { CredentialsEntity } from '@db/entities/CredentialsEntity'; +import { SettingsRepository } from '@db/repositories/settings.repository'; import { UserRepository } from '@db/repositories/user.repository'; +import { setupTestCommand } from '@test-integration/utils/testCommand'; import { mockInstance } from '../../shared/mocking'; import * as testDb from '../shared/testDb'; import { createMember, createUser } from '../shared/db/users'; import { createWorkflow } from '../shared/db/workflows'; -import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflow.repository'; import { getPersonalProject } from '../shared/db/projects'; import { encryptCredentialData, saveCredential } from '../shared/db/credentials'; import { randomCredentialPayload } from '../shared/random'; -import { SharedCredentialsRepository } from '@/databases/repositories/sharedCredentials.repository'; -import { CredentialsRepository } from '@/databases/repositories/credentials.repository'; -import { CredentialsEntity } from '@/databases/entities/CredentialsEntity'; -import { SettingsRepository } from '@/databases/repositories/settings.repository'; - -beforeAll(async () => { - mockInstance(InternalHooks); - mockInstance(LoadNodesAndCredentials); - mockInstance(NodeTypes); - await testDb.init(); -}); + +mockInstance(InternalHooks); +mockInstance(LoadNodesAndCredentials); +mockInstance(NodeTypes); +const command = setupTestCommand(Reset); beforeEach(async () => { await testDb.truncate(['User']); }); -afterAll(async () => { - await testDb.terminate(); -}); - // eslint-disable-next-line n8n-local-rules/no-skipped-tests test('user-management:reset should reset DB to default user state', async () => { // @@ -65,7 +61,7 @@ test('user-management:reset should reset DB to default user state', async () => // // ACT // - await Reset.run(); + await command.run(); // // ASSERT diff --git a/packages/cli/test/integration/commands/update/workflow.test.ts b/packages/cli/test/integration/commands/update/workflow.test.ts index 917f49120fa96..315e64c5263af 100644 --- a/packages/cli/test/integration/commands/update/workflow.test.ts +++ b/packages/cli/test/integration/commands/update/workflow.test.ts @@ -1,29 +1,20 @@ -import { Config } from '@oclif/core'; import { InternalHooks } from '@/InternalHooks'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; import { UpdateWorkflowCommand } from '@/commands/update/workflow'; +import { setupTestCommand } from '@test-integration/utils/testCommand'; import * as testDb from '../../shared/testDb'; import { createWorkflowWithTrigger, getAllWorkflows } from '../../shared/db/workflows'; import { mockInstance } from '../../../shared/mocking'; -const oclifConfig = new Config({ root: __dirname }); - -beforeAll(async () => { - mockInstance(InternalHooks); - mockInstance(LoadNodesAndCredentials); - await testDb.init(); - await oclifConfig.load(); -}); +mockInstance(InternalHooks); +mockInstance(LoadNodesAndCredentials); +const command = setupTestCommand(UpdateWorkflowCommand); beforeEach(async () => { await testDb.truncate(['Workflow']); }); -afterAll(async () => { - await testDb.terminate(); -}); - test('update:workflow can activate all workflows', async () => { // // ARRANGE @@ -37,9 +28,7 @@ test('update:workflow can activate all workflows', async () => { // // ACT // - const updater = new UpdateWorkflowCommand(['--all', '--active=true'], oclifConfig); - await updater.init(); - await updater.run(); + await command.run(['--all', '--active=true']); // // ASSERT @@ -61,9 +50,7 @@ test('update:workflow can deactivate all workflows', async () => { // // ACT // - const updater = new UpdateWorkflowCommand(['--all', '--active=false'], oclifConfig); - await updater.init(); - await updater.run(); + await command.run(['--all', '--active=false']); // // ASSERT @@ -87,12 +74,7 @@ test('update:workflow can activate a specific workflow', async () => { // // ACT // - const updater = new UpdateWorkflowCommand( - [`--id=${workflows[0].id}`, '--active=true'], - oclifConfig, - ); - await updater.init(); - await updater.run(); + await command.run([`--id=${workflows[0].id}`, '--active=true']); // // ASSERT @@ -116,12 +98,7 @@ test('update:workflow can deactivate a specific workflow', async () => { // // ACT // - const updater = new UpdateWorkflowCommand( - [`--id=${workflows[0].id}`, '--active=false'], - oclifConfig, - ); - await updater.init(); - await updater.run(); + await command.run([`--id=${workflows[0].id}`, '--active=false']); // // ASSERT diff --git a/packages/cli/test/integration/commands/worker.cmd.test.ts b/packages/cli/test/integration/commands/worker.cmd.test.ts index da2a1fd5b255e..d3483098fe880 100644 --- a/packages/cli/test/integration/commands/worker.cmd.test.ts +++ b/packages/cli/test/integration/commands/worker.cmd.test.ts @@ -1,85 +1,51 @@ -import { Config } from '@oclif/core'; +import { BinaryDataService } from 'n8n-core'; +import { mock } from 'jest-mock-extended'; + import { Worker } from '@/commands/worker'; import config from '@/config'; -import { Telemetry } from '@/telemetry'; import { ExternalSecretsManager } from '@/ExternalSecrets/ExternalSecretsManager.ee'; -import { BinaryDataService } from 'n8n-core'; -import { CacheService } from '@/services/cache/cache.service'; -import { RedisServicePubSubPublisher } from '@/services/redis/RedisServicePubSubPublisher'; -import { RedisServicePubSubSubscriber } from '@/services/redis/RedisServicePubSubSubscriber'; import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; -import { CredentialTypes } from '@/CredentialTypes'; -import { NodeTypes } from '@/NodeTypes'; import { InternalHooks } from '@/InternalHooks'; -import { PostHogClient } from '@/posthog'; -import { RedisService } from '@/services/redis.service'; import { OrchestrationHandlerWorkerService } from '@/services/orchestration/worker/orchestration.handler.worker.service'; -import { OrchestrationService } from '@/services/orchestration.service'; +import { OrchestrationWorkerService } from '@/services/orchestration/worker/orchestration.worker.service'; +import { License } from '@/License'; +import { ExternalHooks } from '@/ExternalHooks'; +import { type JobQueue, Queue } from '@/Queue'; -import * as testDb from '../shared/testDb'; +import { setupTestCommand } from '@test-integration/utils/testCommand'; import { mockInstance } from '../../shared/mocking'; -const oclifConfig = new Config({ root: __dirname }); - -let eventBus: MessageEventBus; - -beforeAll(async () => { - config.set('executions.mode', 'queue'); - config.set('binaryDataManager.availableModes', 'filesystem'); - mockInstance(Telemetry); - mockInstance(PostHogClient); - mockInstance(InternalHooks); - mockInstance(CacheService); - mockInstance(ExternalSecretsManager); - mockInstance(BinaryDataService); - eventBus = mockInstance(MessageEventBus); - mockInstance(LoadNodesAndCredentials); - mockInstance(CredentialTypes); - mockInstance(NodeTypes); - mockInstance(RedisService); - mockInstance(RedisServicePubSubPublisher); - mockInstance(RedisServicePubSubSubscriber); - mockInstance(OrchestrationService); - await testDb.init(); - await oclifConfig.load(); -}); - -afterAll(async () => { - await testDb.terminate(); -}); +config.set('executions.mode', 'queue'); +config.set('binaryDataManager.availableModes', 'filesystem'); +mockInstance(InternalHooks); +mockInstance(LoadNodesAndCredentials); +const binaryDataService = mockInstance(BinaryDataService); +const externalHooks = mockInstance(ExternalHooks); +const externalSecretsManager = mockInstance(ExternalSecretsManager); +const license = mockInstance(License); +const messageEventBus = mockInstance(MessageEventBus); +const orchestrationHandlerWorkerService = mockInstance(OrchestrationHandlerWorkerService); +const queue = mockInstance(Queue); +const orchestrationWorkerService = mockInstance(OrchestrationWorkerService); +const command = setupTestCommand(Worker); + +queue.getBullObjectInstance.mockReturnValue(mock({ on: jest.fn() })); test('worker initializes all its components', async () => { - const worker = new Worker([], oclifConfig); - - jest.spyOn(worker, 'init'); - jest.spyOn(worker, 'initLicense').mockImplementation(async () => {}); - jest.spyOn(worker, 'initBinaryDataService').mockImplementation(async () => {}); - jest.spyOn(worker, 'initExternalHooks').mockImplementation(async () => {}); - jest.spyOn(worker, 'initExternalSecrets').mockImplementation(async () => {}); - jest.spyOn(worker, 'initEventBus').mockImplementation(async () => {}); - jest.spyOn(worker, 'initOrchestration'); - // jest.spyOn(MessageEventBus.prototype, 'send').mockImplementation(async () => {}); - jest - .spyOn(OrchestrationHandlerWorkerService.prototype, 'initSubscriber') - .mockImplementation(async () => {}); - jest.spyOn(RedisServicePubSubPublisher.prototype, 'init').mockImplementation(async () => {}); - jest.spyOn(worker, 'initQueue').mockImplementation(async () => {}); - - await worker.init(); + const worker = await command.run(); expect(worker.queueModeId).toBeDefined(); expect(worker.queueModeId).toContain('worker'); expect(worker.queueModeId.length).toBeGreaterThan(15); - expect(worker.initLicense).toHaveBeenCalledTimes(1); - expect(worker.initBinaryDataService).toHaveBeenCalledTimes(1); - expect(worker.initExternalHooks).toHaveBeenCalledTimes(1); - expect(worker.initExternalSecrets).toHaveBeenCalledTimes(1); - expect(worker.initEventBus).toHaveBeenCalledTimes(1); - expect(worker.initOrchestration).toHaveBeenCalledTimes(1); - expect(OrchestrationHandlerWorkerService.prototype.initSubscriber).toHaveBeenCalledTimes(1); - expect(eventBus.send).toHaveBeenCalledTimes(1); - expect(worker.initQueue).toHaveBeenCalledTimes(1); - - jest.restoreAllMocks(); + expect(license.init).toHaveBeenCalledTimes(1); + expect(binaryDataService.init).toHaveBeenCalledTimes(1); + expect(externalHooks.init).toHaveBeenCalledTimes(1); + expect(externalSecretsManager.init).toHaveBeenCalledTimes(1); + expect(messageEventBus.initialize).toHaveBeenCalledTimes(1); + expect(queue.init).toHaveBeenCalledTimes(1); + expect(queue.process).toHaveBeenCalledTimes(1); + expect(orchestrationWorkerService.init).toHaveBeenCalledTimes(1); + expect(orchestrationHandlerWorkerService.initWithOptions).toHaveBeenCalledTimes(1); + expect(messageEventBus.send).toHaveBeenCalledTimes(1); }); diff --git a/packages/cli/test/integration/shared/utils/testCommand.ts b/packages/cli/test/integration/shared/utils/testCommand.ts new file mode 100644 index 0000000000000..3ec203408a197 --- /dev/null +++ b/packages/cli/test/integration/shared/utils/testCommand.ts @@ -0,0 +1,37 @@ +import type { Config } from '@oclif/core'; +import type { Class } from 'n8n-core'; +import { mock } from 'jest-mock-extended'; + +import type { BaseCommand } from '@/commands/BaseCommand'; +import * as testDb from '../testDb'; + +export const setupTestCommand = (Command: Class) => { + const config = mock(); + config.runHook.mockResolvedValue({ successes: [], failures: [] }); + + // mock SIGINT/SIGTERM registration + process.once = jest.fn(); + + beforeAll(async () => { + await testDb.init(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(async () => { + await testDb.terminate(); + + jest.restoreAllMocks(); + }); + + const run = async (argv: string[] = []) => { + const command = new Command(argv, config); + await command.init(); + await command.run(); + return command; + }; + + return { run }; +};