diff --git a/packages/cli/src/LoadNodesAndCredentials.ts b/packages/cli/src/LoadNodesAndCredentials.ts index c1178c9058..10d85f8834 100644 --- a/packages/cli/src/LoadNodesAndCredentials.ts +++ b/packages/cli/src/LoadNodesAndCredentials.ts @@ -58,6 +58,7 @@ class LoadNodesAndCredentialsClass { // In case "n8n" package is the root and the packages are // in the "node_modules" folder underneath it. path.join(__dirname, '..', '..', 'node_modules', 'n8n-workflow'), + path.join(__dirname, '..', 'node_modules', 'n8n-workflow'), // for test run ]; for (const checkPath of checkPaths) { try { diff --git a/packages/cli/test/integration/oauth2.api.test.ts b/packages/cli/test/integration/oauth2.api.test.ts new file mode 100644 index 0000000000..2cf6eb9d01 --- /dev/null +++ b/packages/cli/test/integration/oauth2.api.test.ts @@ -0,0 +1,125 @@ +import express from 'express'; + +import * as utils from './shared/utils'; +import * as testDb from './shared/testDb'; +import { RESPONSE_ERROR_MESSAGES } from '../../src/constants'; + +import type { Role } from '../../src/databases/entities/Role'; +import { CredentialTypes, LoadNodesAndCredentials } from '../../src'; + +const SCOPES_ENDPOINT = '/oauth2-credential/scopes'; + +let app: express.Application; +let testDbName = ''; +let globalOwnerRole: Role; + +beforeAll(async () => { + app = utils.initTestServer({ endpointGroups: ['oauth2-credential'], applyAuth: true }); + const initResult = await testDb.init(); + testDbName = initResult.testDbName; + + utils.initConfigFile(); + + globalOwnerRole = await testDb.getGlobalOwnerRole(); + utils.initTestLogger(); + + const loadNodesAndCredentials = LoadNodesAndCredentials(); + await loadNodesAndCredentials.init(); + + const credentialTypes = CredentialTypes(); + await credentialTypes.init(loadNodesAndCredentials.credentialTypes); +}); + +afterAll(async () => { + await testDb.terminate(testDbName); +}); + +describe('OAuth2 scopes', () => { + beforeEach(async () => { + await testDb.truncate(['User'], testDbName); + }); + + test(`GET ${SCOPES_ENDPOINT} should return scopes - comma-delimited`, async () => { + const ownerShell = await testDb.createUserShell(globalOwnerRole); + const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell }); + + const response = await authOwnerShellAgent + .get(SCOPES_ENDPOINT) + .query({ credentialType: 'twistOAuth2Api' }); + + expect(response.statusCode).toBe(200); + + const scopes = response.body.data; + const TWIST_OAUTH2_API_SCOPES_TOTAL = 6; + + expect(Array.isArray(scopes)).toBe(true); + expect(scopes.length).toBe(TWIST_OAUTH2_API_SCOPES_TOTAL); + }); + + test(`GET ${SCOPES_ENDPOINT} should return scopes - whitespace-delimited`, async () => { + const ownerShell = await testDb.createUserShell(globalOwnerRole); + const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell }); + + const response = await authOwnerShellAgent + .get(SCOPES_ENDPOINT) + .query({ credentialType: 'dropboxOAuth2Api' }); + + expect(response.statusCode).toBe(200); + + const scopes = response.body.data; + const DROPBOX_OAUTH2_API_SCOPES_TOTAL = 4; + + expect(Array.isArray(scopes)).toBe(true); + expect(scopes.length).toBe(DROPBOX_OAUTH2_API_SCOPES_TOTAL); + }); + + test(`GET ${SCOPES_ENDPOINT} should return scope - non-delimited`, async () => { + const ownerShell = await testDb.createUserShell(globalOwnerRole); + const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell }); + + const response = await authOwnerShellAgent + .get(SCOPES_ENDPOINT) + .query({ credentialType: 'harvestOAuth2Api' }); + + expect(response.statusCode).toBe(200); + + const scopes = response.body.data; + + expect(Array.isArray(scopes)).toBe(true); + expect(scopes.length).toBe(1); + }); + + test(`GET ${SCOPES_ENDPOINT} should fail with missing credential type`, async () => { + const ownerShell = await testDb.createUserShell(globalOwnerRole); + const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell }); + + const response = await authOwnerShellAgent.get(SCOPES_ENDPOINT); + + expect(response.statusCode).toBe(400); + expect(response.body.message).toBe(RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL_TYPE); + }); + + test(`GET ${SCOPES_ENDPOINT} should fail with non-OAuth2 credential type`, async () => { + const ownerShell = await testDb.createUserShell(globalOwnerRole); + const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell }); + + const response = await authOwnerShellAgent + .get(SCOPES_ENDPOINT) + .query({ credentialType: 'disqusApi' }); + + expect(response.statusCode).toBe(400); + expect(response.body.message).toBe(RESPONSE_ERROR_MESSAGES.CREDENTIAL_TYPE_NOT_OAUTH2); + }); + + test(`GET ${SCOPES_ENDPOINT} should fail with wrong OAuth2 credential type`, async () => { + const ownerShell = await testDb.createUserShell(globalOwnerRole); + const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell }); + + const response = await authOwnerShellAgent + .get(SCOPES_ENDPOINT) + .query({ credentialType: 'wrongOAuth2Api' }); + + expect(response.statusCode).toBe(500); + expect(response.body.message).toBe(`${RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL}: wrongOAuth2Api`); + }); +}); diff --git a/packages/cli/test/integration/shared/types.d.ts b/packages/cli/test/integration/shared/types.d.ts index bca1c86fbc..732d925d84 100644 --- a/packages/cli/test/integration/shared/types.d.ts +++ b/packages/cli/test/integration/shared/types.d.ts @@ -15,7 +15,7 @@ export type SmtpTestAccount = { }; }; -type EndpointGroup = 'me' | 'users' | 'auth' | 'owner' | 'passwordReset' | 'credentials'; +type EndpointGroup = 'me' | 'users' | 'auth' | 'owner' | 'passwordReset' | 'credentials' | 'oauth2-credential'; export type CredentialPayload = { name: string; diff --git a/packages/cli/test/integration/shared/utils.ts b/packages/cli/test/integration/shared/utils.ts index 4127eb98db..369f7f1dfa 100644 --- a/packages/cli/test/integration/shared/utils.ts +++ b/packages/cli/test/integration/shared/utils.ts @@ -26,6 +26,7 @@ import { credentialsController } from '../../../src/api/credentials.api'; import type { User } from '../../../src/databases/entities/User'; import type { EndpointGroup, SmtpTestAccount } from './types'; import type { N8nApp } from '../../../src/UserManagement/Interfaces'; +import { oauth2CredentialController } from '../../../src/api/oauth2Credential.api'; /** * Initialize a test server. @@ -63,6 +64,7 @@ export function initTestServer({ if (routerEndpoints.length) { const map: Record = { credentials: credentialsController, + 'oauth2-credential': oauth2CredentialController, }; for (const group of routerEndpoints) { @@ -105,7 +107,10 @@ const classifyEndpointGroups = (endpointGroups: string[]) => { const functionEndpoints: string[] = []; endpointGroups.forEach((group) => - (group === 'credentials' ? routerEndpoints : functionEndpoints).push(group), + (['credentials', 'oauth2-credential'].includes(group) + ? routerEndpoints + : functionEndpoints + ).push(group), ); return [routerEndpoints, functionEndpoints];