mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
ci: Refactor cli tests to speed up CI (no-changelog) (#5718)
* ci: Refactor cli tests to speed up CI (no-changelog) * upgrade jest to address memory leaks
This commit is contained in:
parent
be172cb720
commit
6242cac53b
|
@ -28,8 +28,7 @@ const config = {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.CI === 'true') {
|
if (process.env.CI === 'true') {
|
||||||
config.maxWorkers = 2;
|
config.workerIdleMemoryLimit = 1024;
|
||||||
config.workerIdleMemoryLimit = 2048;
|
|
||||||
config.coverageReporters = ['cobertura'];
|
config.coverageReporters = ['cobertura'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
package.json
10
package.json
|
@ -39,15 +39,15 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@n8n_io/eslint-config": "workspace:*",
|
"@n8n_io/eslint-config": "workspace:*",
|
||||||
"@ngneat/falso": "^6.1.0",
|
"@ngneat/falso": "^6.1.0",
|
||||||
"@types/jest": "^29.2.2",
|
"@types/jest": "^29.5.0",
|
||||||
"@types/supertest": "^2.0.12",
|
"@types/supertest": "^2.0.12",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cypress": "^12.7.0",
|
"cypress": "^12.7.0",
|
||||||
"cypress-real-events": "^1.7.6",
|
"cypress-real-events": "^1.7.6",
|
||||||
"jest": "^29.4.2",
|
"jest": "^29.5.0",
|
||||||
"jest-environment-jsdom": "^29.4.2",
|
"jest-environment-jsdom": "^29.5.0",
|
||||||
"jest-mock": "^29.4.2",
|
"jest-mock": "^29.5.0",
|
||||||
"jest-mock-extended": "^3.0.1",
|
"jest-mock-extended": "^3.0.3",
|
||||||
"nock": "^13.2.9",
|
"nock": "^13.2.9",
|
||||||
"node-fetch": "^2.6.7",
|
"node-fetch": "^2.6.7",
|
||||||
"p-limit": "^3.1.0",
|
"p-limit": "^3.1.0",
|
||||||
|
|
|
@ -1,30 +1 @@
|
||||||
/* eslint-disable import/first */
|
export {};
|
||||||
export * from './CredentialsHelper';
|
|
||||||
export * from './CredentialTypes';
|
|
||||||
export * from './CredentialsOverwrites';
|
|
||||||
export * from './Interfaces';
|
|
||||||
export * from './WaitingWebhooks';
|
|
||||||
export * from './WorkflowCredentials';
|
|
||||||
export * from './WorkflowRunner';
|
|
||||||
|
|
||||||
import * as Db from './Db';
|
|
||||||
import * as GenericHelpers from './GenericHelpers';
|
|
||||||
import * as ResponseHelper from './ResponseHelper';
|
|
||||||
import * as Server from './Server';
|
|
||||||
import * as TestWebhooks from './TestWebhooks';
|
|
||||||
import * as WebhookHelpers from './WebhookHelpers';
|
|
||||||
import * as WebhookServer from './WebhookServer';
|
|
||||||
import * as WorkflowExecuteAdditionalData from './WorkflowExecuteAdditionalData';
|
|
||||||
import * as WorkflowHelpers from './WorkflowHelpers';
|
|
||||||
|
|
||||||
export {
|
|
||||||
Db,
|
|
||||||
GenericHelpers,
|
|
||||||
ResponseHelper,
|
|
||||||
Server,
|
|
||||||
TestWebhooks,
|
|
||||||
WebhookHelpers,
|
|
||||||
WebhookServer,
|
|
||||||
WorkflowExecuteAdditionalData,
|
|
||||||
WorkflowHelpers,
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,339 +1,338 @@
|
||||||
import express from 'express';
|
import type { Application } from 'express';
|
||||||
|
import type { SuperAgentTest } from 'supertest';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import { AUTH_COOKIE_NAME } from '@/constants';
|
import { AUTH_COOKIE_NAME } from '@/constants';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
|
import type { User } from '@db/entities/User';
|
||||||
import { LOGGED_OUT_RESPONSE_BODY } from './shared/constants';
|
import { LOGGED_OUT_RESPONSE_BODY } from './shared/constants';
|
||||||
import { randomValidPassword } from './shared/random';
|
import { randomValidPassword } from './shared/random';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import type { AuthAgent } from './shared/types';
|
import type { AuthAgent } from './shared/types';
|
||||||
import * as utils from './shared/utils';
|
import * as utils from './shared/utils';
|
||||||
|
|
||||||
let app: express.Application;
|
let app: Application;
|
||||||
let globalOwnerRole: Role;
|
let globalOwnerRole: Role;
|
||||||
let globalMemberRole: Role;
|
let globalMemberRole: Role;
|
||||||
|
let owner: User;
|
||||||
let authAgent: AuthAgent;
|
let authAgent: AuthAgent;
|
||||||
|
let authlessAgent: SuperAgentTest;
|
||||||
|
let authOwnerAgent: SuperAgentTest;
|
||||||
|
const ownerPassword = randomValidPassword();
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({ endpointGroups: ['auth'] });
|
app = await utils.initTestServer({ endpointGroups: ['auth'] });
|
||||||
|
authAgent = utils.createAuthAgent(app);
|
||||||
|
|
||||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||||
|
|
||||||
authAgent = utils.createAuthAgent(app);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await testDb.truncate(['User']);
|
await testDb.truncate(['User']);
|
||||||
|
authlessAgent = utils.createAgent(app);
|
||||||
config.set('ldap.disabled', true);
|
config.set('ldap.disabled', true);
|
||||||
|
await utils.setInstanceOwnerSetUp(true);
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
|
||||||
|
|
||||||
await Db.collections.Settings.update(
|
|
||||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
|
||||||
{ value: JSON.stringify(true) },
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await testDb.terminate();
|
await testDb.terminate();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /login should log user in', async () => {
|
describe('POST /login', () => {
|
||||||
const ownerPassword = randomValidPassword();
|
beforeEach(async () => {
|
||||||
const owner = await testDb.createUser({
|
owner = await testDb.createUser({
|
||||||
password: ownerPassword,
|
password: ownerPassword,
|
||||||
globalRole: globalOwnerRole,
|
globalRole: globalOwnerRole,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
test('should log user in', async () => {
|
||||||
|
const response = await authlessAgent.post('/login').send({
|
||||||
|
email: owner.email,
|
||||||
|
password: ownerPassword,
|
||||||
|
});
|
||||||
|
|
||||||
const response = await authlessAgent.post('/login').send({
|
expect(response.statusCode).toBe(200);
|
||||||
email: owner.email,
|
|
||||||
password: ownerPassword,
|
const {
|
||||||
|
id,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
password,
|
||||||
|
personalizationAnswers,
|
||||||
|
globalRole,
|
||||||
|
resetPasswordToken,
|
||||||
|
apiKey,
|
||||||
|
} = response.body.data;
|
||||||
|
|
||||||
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
|
expect(email).toBe(owner.email);
|
||||||
|
expect(firstName).toBe(owner.firstName);
|
||||||
|
expect(lastName).toBe(owner.lastName);
|
||||||
|
expect(password).toBeUndefined();
|
||||||
|
expect(personalizationAnswers).toBeNull();
|
||||||
|
expect(password).toBeUndefined();
|
||||||
|
expect(resetPasswordToken).toBeUndefined();
|
||||||
|
expect(globalRole).toBeDefined();
|
||||||
|
expect(globalRole.name).toBe('owner');
|
||||||
|
expect(globalRole.scope).toBe('global');
|
||||||
|
expect(apiKey).toBeUndefined();
|
||||||
|
|
||||||
|
const authToken = utils.getAuthToken(response);
|
||||||
|
expect(authToken).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /login', () => {
|
||||||
|
test('should return 401 Unauthorized if no cookie', async () => {
|
||||||
|
const response = await authlessAgent.get('/login');
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(401);
|
||||||
|
|
||||||
|
const authToken = utils.getAuthToken(response);
|
||||||
|
expect(authToken).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
test('should return cookie if UM is disabled and no cookie is already set', async () => {
|
||||||
|
await testDb.createUserShell(globalOwnerRole);
|
||||||
|
await utils.setInstanceOwnerSetUp(false);
|
||||||
|
|
||||||
const {
|
const response = await authlessAgent.get('/login');
|
||||||
id,
|
|
||||||
email,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
password,
|
|
||||||
personalizationAnswers,
|
|
||||||
globalRole,
|
|
||||||
resetPasswordToken,
|
|
||||||
apiKey,
|
|
||||||
} = response.body.data;
|
|
||||||
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(email).toBe(owner.email);
|
|
||||||
expect(firstName).toBe(owner.firstName);
|
|
||||||
expect(lastName).toBe(owner.lastName);
|
|
||||||
expect(password).toBeUndefined();
|
|
||||||
expect(personalizationAnswers).toBeNull();
|
|
||||||
expect(password).toBeUndefined();
|
|
||||||
expect(resetPasswordToken).toBeUndefined();
|
|
||||||
expect(globalRole).toBeDefined();
|
|
||||||
expect(globalRole.name).toBe('owner');
|
|
||||||
expect(globalRole.scope).toBe('global');
|
|
||||||
expect(apiKey).toBeUndefined();
|
|
||||||
|
|
||||||
const authToken = utils.getAuthToken(response);
|
const authToken = utils.getAuthToken(response);
|
||||||
expect(authToken).toBeDefined();
|
expect(authToken).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return 401 Unauthorized if invalid cookie', async () => {
|
||||||
|
authlessAgent.jar.setCookie(`${AUTH_COOKIE_NAME}=invalid`);
|
||||||
|
|
||||||
|
const response = await authlessAgent.get('/login');
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(401);
|
||||||
|
|
||||||
|
const authToken = utils.getAuthToken(response);
|
||||||
|
expect(authToken).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return logged-in owner shell', async () => {
|
||||||
|
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||||
|
|
||||||
|
const response = await authAgent(ownerShell).get('/login');
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
password,
|
||||||
|
personalizationAnswers,
|
||||||
|
globalRole,
|
||||||
|
resetPasswordToken,
|
||||||
|
apiKey,
|
||||||
|
} = response.body.data;
|
||||||
|
|
||||||
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
|
expect(email).toBeDefined();
|
||||||
|
expect(firstName).toBeNull();
|
||||||
|
expect(lastName).toBeNull();
|
||||||
|
expect(password).toBeUndefined();
|
||||||
|
expect(personalizationAnswers).toBeNull();
|
||||||
|
expect(password).toBeUndefined();
|
||||||
|
expect(resetPasswordToken).toBeUndefined();
|
||||||
|
expect(globalRole).toBeDefined();
|
||||||
|
expect(globalRole.name).toBe('owner');
|
||||||
|
expect(globalRole.scope).toBe('global');
|
||||||
|
expect(apiKey).toBeUndefined();
|
||||||
|
|
||||||
|
const authToken = utils.getAuthToken(response);
|
||||||
|
expect(authToken).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return logged-in member shell', async () => {
|
||||||
|
const memberShell = await testDb.createUserShell(globalMemberRole);
|
||||||
|
|
||||||
|
const response = await authAgent(memberShell).get('/login');
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
password,
|
||||||
|
personalizationAnswers,
|
||||||
|
globalRole,
|
||||||
|
resetPasswordToken,
|
||||||
|
apiKey,
|
||||||
|
} = response.body.data;
|
||||||
|
|
||||||
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
|
expect(email).toBeDefined();
|
||||||
|
expect(firstName).toBeNull();
|
||||||
|
expect(lastName).toBeNull();
|
||||||
|
expect(password).toBeUndefined();
|
||||||
|
expect(personalizationAnswers).toBeNull();
|
||||||
|
expect(password).toBeUndefined();
|
||||||
|
expect(resetPasswordToken).toBeUndefined();
|
||||||
|
expect(globalRole).toBeDefined();
|
||||||
|
expect(globalRole.name).toBe('member');
|
||||||
|
expect(globalRole.scope).toBe('global');
|
||||||
|
expect(apiKey).toBeUndefined();
|
||||||
|
|
||||||
|
const authToken = utils.getAuthToken(response);
|
||||||
|
expect(authToken).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return logged-in owner', async () => {
|
||||||
|
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||||
|
|
||||||
|
const response = await authAgent(owner).get('/login');
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
password,
|
||||||
|
personalizationAnswers,
|
||||||
|
globalRole,
|
||||||
|
resetPasswordToken,
|
||||||
|
apiKey,
|
||||||
|
} = response.body.data;
|
||||||
|
|
||||||
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
|
expect(email).toBe(owner.email);
|
||||||
|
expect(firstName).toBe(owner.firstName);
|
||||||
|
expect(lastName).toBe(owner.lastName);
|
||||||
|
expect(password).toBeUndefined();
|
||||||
|
expect(personalizationAnswers).toBeNull();
|
||||||
|
expect(password).toBeUndefined();
|
||||||
|
expect(resetPasswordToken).toBeUndefined();
|
||||||
|
expect(globalRole).toBeDefined();
|
||||||
|
expect(globalRole.name).toBe('owner');
|
||||||
|
expect(globalRole.scope).toBe('global');
|
||||||
|
expect(apiKey).toBeUndefined();
|
||||||
|
|
||||||
|
const authToken = utils.getAuthToken(response);
|
||||||
|
expect(authToken).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return logged-in member', async () => {
|
||||||
|
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
|
||||||
|
const response = await authAgent(member).get('/login');
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
password,
|
||||||
|
personalizationAnswers,
|
||||||
|
globalRole,
|
||||||
|
resetPasswordToken,
|
||||||
|
apiKey,
|
||||||
|
} = response.body.data;
|
||||||
|
|
||||||
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
|
expect(email).toBe(member.email);
|
||||||
|
expect(firstName).toBe(member.firstName);
|
||||||
|
expect(lastName).toBe(member.lastName);
|
||||||
|
expect(password).toBeUndefined();
|
||||||
|
expect(personalizationAnswers).toBeNull();
|
||||||
|
expect(password).toBeUndefined();
|
||||||
|
expect(resetPasswordToken).toBeUndefined();
|
||||||
|
expect(globalRole).toBeDefined();
|
||||||
|
expect(globalRole.name).toBe('member');
|
||||||
|
expect(globalRole.scope).toBe('global');
|
||||||
|
expect(apiKey).toBeUndefined();
|
||||||
|
|
||||||
|
const authToken = utils.getAuthToken(response);
|
||||||
|
expect(authToken).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /login should return 401 Unauthorized if no cookie', async () => {
|
describe('GET /resolve-signup-token', () => {
|
||||||
const authlessAgent = utils.createAgent(app);
|
beforeEach(async () => {
|
||||||
|
owner = await testDb.createUser({
|
||||||
|
password: ownerPassword,
|
||||||
|
globalRole: globalOwnerRole,
|
||||||
|
});
|
||||||
|
authOwnerAgent = authAgent(owner);
|
||||||
|
});
|
||||||
|
|
||||||
const response = await authlessAgent.get('/login');
|
test('should validate invite token', async () => {
|
||||||
|
const memberShell = await testDb.createUserShell(globalMemberRole);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(401);
|
const response = await authOwnerAgent
|
||||||
|
.get('/resolve-signup-token')
|
||||||
|
.query({ inviterId: owner.id })
|
||||||
|
.query({ inviteeId: memberShell.id });
|
||||||
|
|
||||||
const authToken = utils.getAuthToken(response);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(authToken).toBeUndefined();
|
expect(response.body).toEqual({
|
||||||
});
|
data: {
|
||||||
|
inviter: {
|
||||||
test('GET /login should return cookie if UM is disabled and no cookie is already set', async () => {
|
firstName: owner.firstName,
|
||||||
const authlessAgent = utils.createAgent(app);
|
lastName: owner.lastName,
|
||||||
await testDb.createUserShell(globalOwnerRole);
|
},
|
||||||
|
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
|
||||||
|
|
||||||
await Db.collections.Settings.update(
|
|
||||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
|
||||||
{ value: JSON.stringify(false) },
|
|
||||||
);
|
|
||||||
|
|
||||||
const response = await authlessAgent.get('/login');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const authToken = utils.getAuthToken(response);
|
|
||||||
expect(authToken).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /login should return 401 Unauthorized if invalid cookie', async () => {
|
|
||||||
const invalidAuthAgent = utils.createAgent(app);
|
|
||||||
invalidAuthAgent.jar.setCookie(`${AUTH_COOKIE_NAME}=invalid`);
|
|
||||||
|
|
||||||
const response = await invalidAuthAgent.get('/login');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(401);
|
|
||||||
|
|
||||||
const authToken = utils.getAuthToken(response);
|
|
||||||
expect(authToken).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /login should return logged-in owner shell', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).get('/login');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
email,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
password,
|
|
||||||
personalizationAnswers,
|
|
||||||
globalRole,
|
|
||||||
resetPasswordToken,
|
|
||||||
apiKey,
|
|
||||||
} = response.body.data;
|
|
||||||
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
|
||||||
expect(email).toBeDefined();
|
|
||||||
expect(firstName).toBeNull();
|
|
||||||
expect(lastName).toBeNull();
|
|
||||||
expect(password).toBeUndefined();
|
|
||||||
expect(personalizationAnswers).toBeNull();
|
|
||||||
expect(password).toBeUndefined();
|
|
||||||
expect(resetPasswordToken).toBeUndefined();
|
|
||||||
expect(globalRole).toBeDefined();
|
|
||||||
expect(globalRole.name).toBe('owner');
|
|
||||||
expect(globalRole.scope).toBe('global');
|
|
||||||
expect(apiKey).toBeUndefined();
|
|
||||||
|
|
||||||
const authToken = utils.getAuthToken(response);
|
|
||||||
expect(authToken).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /login should return logged-in member shell', async () => {
|
|
||||||
const memberShell = await testDb.createUserShell(globalMemberRole);
|
|
||||||
|
|
||||||
const response = await authAgent(memberShell).get('/login');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
email,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
password,
|
|
||||||
personalizationAnswers,
|
|
||||||
globalRole,
|
|
||||||
resetPasswordToken,
|
|
||||||
apiKey,
|
|
||||||
} = response.body.data;
|
|
||||||
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
|
||||||
expect(email).toBeDefined();
|
|
||||||
expect(firstName).toBeNull();
|
|
||||||
expect(lastName).toBeNull();
|
|
||||||
expect(password).toBeUndefined();
|
|
||||||
expect(personalizationAnswers).toBeNull();
|
|
||||||
expect(password).toBeUndefined();
|
|
||||||
expect(resetPasswordToken).toBeUndefined();
|
|
||||||
expect(globalRole).toBeDefined();
|
|
||||||
expect(globalRole.name).toBe('member');
|
|
||||||
expect(globalRole.scope).toBe('global');
|
|
||||||
expect(apiKey).toBeUndefined();
|
|
||||||
|
|
||||||
const authToken = utils.getAuthToken(response);
|
|
||||||
expect(authToken).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /login should return logged-in owner', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const response = await authAgent(owner).get('/login');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
email,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
password,
|
|
||||||
personalizationAnswers,
|
|
||||||
globalRole,
|
|
||||||
resetPasswordToken,
|
|
||||||
apiKey,
|
|
||||||
} = response.body.data;
|
|
||||||
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
|
||||||
expect(email).toBe(owner.email);
|
|
||||||
expect(firstName).toBe(owner.firstName);
|
|
||||||
expect(lastName).toBe(owner.lastName);
|
|
||||||
expect(password).toBeUndefined();
|
|
||||||
expect(personalizationAnswers).toBeNull();
|
|
||||||
expect(password).toBeUndefined();
|
|
||||||
expect(resetPasswordToken).toBeUndefined();
|
|
||||||
expect(globalRole).toBeDefined();
|
|
||||||
expect(globalRole.name).toBe('owner');
|
|
||||||
expect(globalRole.scope).toBe('global');
|
|
||||||
expect(apiKey).toBeUndefined();
|
|
||||||
|
|
||||||
const authToken = utils.getAuthToken(response);
|
|
||||||
expect(authToken).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /login should return logged-in member', async () => {
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
const response = await authAgent(member).get('/login');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
email,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
password,
|
|
||||||
personalizationAnswers,
|
|
||||||
globalRole,
|
|
||||||
resetPasswordToken,
|
|
||||||
apiKey,
|
|
||||||
} = response.body.data;
|
|
||||||
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
|
||||||
expect(email).toBe(member.email);
|
|
||||||
expect(firstName).toBe(member.firstName);
|
|
||||||
expect(lastName).toBe(member.lastName);
|
|
||||||
expect(password).toBeUndefined();
|
|
||||||
expect(personalizationAnswers).toBeNull();
|
|
||||||
expect(password).toBeUndefined();
|
|
||||||
expect(resetPasswordToken).toBeUndefined();
|
|
||||||
expect(globalRole).toBeDefined();
|
|
||||||
expect(globalRole.name).toBe('member');
|
|
||||||
expect(globalRole.scope).toBe('global');
|
|
||||||
expect(apiKey).toBeUndefined();
|
|
||||||
|
|
||||||
const authToken = utils.getAuthToken(response);
|
|
||||||
expect(authToken).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /resolve-signup-token should validate invite token', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const memberShell = await testDb.createUserShell(globalMemberRole);
|
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
|
||||||
.get('/resolve-signup-token')
|
|
||||||
.query({ inviterId: owner.id })
|
|
||||||
.query({ inviteeId: memberShell.id });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body).toEqual({
|
|
||||||
data: {
|
|
||||||
inviter: {
|
|
||||||
firstName: owner.firstName,
|
|
||||||
lastName: owner.lastName,
|
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail with invalid inputs', async () => {
|
||||||
|
const { id: inviteeId } = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
|
||||||
|
const first = await authOwnerAgent.get('/resolve-signup-token').query({ inviterId: owner.id });
|
||||||
|
|
||||||
|
const second = await authOwnerAgent.get('/resolve-signup-token').query({ inviteeId });
|
||||||
|
|
||||||
|
const third = await authOwnerAgent.get('/resolve-signup-token').query({
|
||||||
|
inviterId: '5531199e-b7ae-425b-a326-a95ef8cca59d',
|
||||||
|
inviteeId: 'cb133beb-7729-4c34-8cd1-a06be8834d9d',
|
||||||
|
});
|
||||||
|
|
||||||
|
// user is already set up, so call should error
|
||||||
|
const fourth = await authOwnerAgent
|
||||||
|
.get('/resolve-signup-token')
|
||||||
|
.query({ inviterId: owner.id })
|
||||||
|
.query({ inviteeId });
|
||||||
|
|
||||||
|
// cause inconsistent DB state
|
||||||
|
await Db.collections.User.update(owner.id, { email: '' });
|
||||||
|
const fifth = await authOwnerAgent
|
||||||
|
.get('/resolve-signup-token')
|
||||||
|
.query({ inviterId: owner.id })
|
||||||
|
.query({ inviteeId });
|
||||||
|
|
||||||
|
for (const response of [first, second, third, fourth, fifth]) {
|
||||||
|
expect(response.statusCode).toBe(400);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /resolve-signup-token should fail with invalid inputs', async () => {
|
describe('POST /logout', () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
test('should log user out', async () => {
|
||||||
const authOwnerAgent = authAgent(owner);
|
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||||
|
|
||||||
const { id: inviteeId } = await testDb.createUser({ globalRole: globalMemberRole });
|
const response = await authAgent(owner).post('/logout');
|
||||||
|
|
||||||
const first = await authOwnerAgent.get('/resolve-signup-token').query({ inviterId: owner.id });
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body).toEqual(LOGGED_OUT_RESPONSE_BODY);
|
||||||
|
|
||||||
const second = await authOwnerAgent.get('/resolve-signup-token').query({ inviteeId });
|
const authToken = utils.getAuthToken(response);
|
||||||
|
expect(authToken).toBeUndefined();
|
||||||
const third = await authOwnerAgent.get('/resolve-signup-token').query({
|
|
||||||
inviterId: '5531199e-b7ae-425b-a326-a95ef8cca59d',
|
|
||||||
inviteeId: 'cb133beb-7729-4c34-8cd1-a06be8834d9d',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// user is already set up, so call should error
|
|
||||||
const fourth = await authOwnerAgent
|
|
||||||
.get('/resolve-signup-token')
|
|
||||||
.query({ inviterId: owner.id })
|
|
||||||
.query({ inviteeId });
|
|
||||||
|
|
||||||
// cause inconsistent DB state
|
|
||||||
await Db.collections.User.update(owner.id, { email: '' });
|
|
||||||
const fifth = await authOwnerAgent
|
|
||||||
.get('/resolve-signup-token')
|
|
||||||
.query({ inviterId: owner.id })
|
|
||||||
.query({ inviteeId });
|
|
||||||
|
|
||||||
for (const response of [first, second, third, fourth, fifth]) {
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /logout should log user out', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const response = await authAgent(owner).post('/logout');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body).toEqual(LOGGED_OUT_RESPONSE_BODY);
|
|
||||||
|
|
||||||
const authToken = utils.getAuthToken(response);
|
|
||||||
expect(authToken).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,26 +1,21 @@
|
||||||
import express from 'express';
|
import type { SuperAgentTest } from 'supertest';
|
||||||
|
|
||||||
import request from 'supertest';
|
|
||||||
import type { Role } from '@db/entities/Role';
|
|
||||||
import {
|
import {
|
||||||
REST_PATH_SEGMENT,
|
|
||||||
ROUTES_REQUIRING_AUTHENTICATION,
|
ROUTES_REQUIRING_AUTHENTICATION,
|
||||||
ROUTES_REQUIRING_AUTHORIZATION,
|
ROUTES_REQUIRING_AUTHORIZATION,
|
||||||
} from './shared/constants';
|
} from './shared/constants';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import type { AuthAgent } from './shared/types';
|
|
||||||
import * as utils from './shared/utils';
|
import * as utils from './shared/utils';
|
||||||
|
|
||||||
let app: express.Application;
|
let authlessAgent: SuperAgentTest;
|
||||||
let globalMemberRole: Role;
|
let authMemberAgent: SuperAgentTest;
|
||||||
let authAgent: AuthAgent;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({ endpointGroups: ['me', 'auth', 'owner', 'users'] });
|
const app = await utils.initTestServer({ endpointGroups: ['me', 'auth', 'owner', 'users'] });
|
||||||
|
const globalMemberRole = await testDb.getGlobalMemberRole();
|
||||||
|
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
|
||||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
authlessAgent = utils.createAgent(app);
|
||||||
|
authMemberAgent = utils.createAuthAgent(app)(member);
|
||||||
authAgent = utils.createAuthAgent(app);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
@ -31,9 +26,8 @@ ROUTES_REQUIRING_AUTHENTICATION.concat(ROUTES_REQUIRING_AUTHORIZATION).forEach((
|
||||||
const [method, endpoint] = getMethodAndEndpoint(route);
|
const [method, endpoint] = getMethodAndEndpoint(route);
|
||||||
|
|
||||||
test(`${route} should return 401 Unauthorized if no cookie`, async () => {
|
test(`${route} should return 401 Unauthorized if no cookie`, async () => {
|
||||||
const response = await request(app)[method](endpoint).use(utils.prefix(REST_PATH_SEGMENT));
|
const { statusCode } = await authlessAgent[method](endpoint);
|
||||||
|
expect(statusCode).toBe(401);
|
||||||
expect(response.statusCode).toBe(401);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -41,10 +35,8 @@ ROUTES_REQUIRING_AUTHORIZATION.forEach(async (route) => {
|
||||||
const [method, endpoint] = getMethodAndEndpoint(route);
|
const [method, endpoint] = getMethodAndEndpoint(route);
|
||||||
|
|
||||||
test(`${route} should return 403 Forbidden for member`, async () => {
|
test(`${route} should return 403 Forbidden for member`, async () => {
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
const { statusCode } = await authMemberAgent[method](endpoint);
|
||||||
const response = await authAgent(member)[method](endpoint);
|
expect(statusCode).toBe(403);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(403);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,43 +1,48 @@
|
||||||
import express from 'express';
|
import type { SuperAgentTest } from 'supertest';
|
||||||
import { UserSettings } from 'n8n-core';
|
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
|
import { UserSettings } from 'n8n-core';
|
||||||
|
import type { IUser } from 'n8n-workflow';
|
||||||
|
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||||
import type { CredentialWithSharings } from '@/credentials/credentials.types';
|
import type { CredentialWithSharings } from '@/credentials/credentials.types';
|
||||||
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
|
import type { User } from '@db/entities/User';
|
||||||
import { randomCredentialPayload } from './shared/random';
|
import { randomCredentialPayload } from './shared/random';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import type { AuthAgent, SaveCredentialFunction } from './shared/types';
|
import type { AuthAgent, SaveCredentialFunction } from './shared/types';
|
||||||
import * as utils from './shared/utils';
|
import * as utils from './shared/utils';
|
||||||
import type { IUser } from 'n8n-workflow';
|
|
||||||
|
|
||||||
let app: express.Application;
|
|
||||||
let globalOwnerRole: Role;
|
|
||||||
let globalMemberRole: Role;
|
let globalMemberRole: Role;
|
||||||
let credentialOwnerRole: Role;
|
let owner: User;
|
||||||
let saveCredential: SaveCredentialFunction;
|
let member: User;
|
||||||
|
let authOwnerAgent: SuperAgentTest;
|
||||||
let authAgent: AuthAgent;
|
let authAgent: AuthAgent;
|
||||||
|
let saveCredential: SaveCredentialFunction;
|
||||||
let sharingSpy: jest.SpyInstance<boolean>;
|
let sharingSpy: jest.SpyInstance<boolean>;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({ endpointGroups: ['credentials'] });
|
const app = await utils.initTestServer({ endpointGroups: ['credentials'] });
|
||||||
|
|
||||||
utils.initConfigFile();
|
utils.initConfigFile();
|
||||||
|
|
||||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
const globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||||
credentialOwnerRole = await testDb.getCredentialOwnerRole();
|
const credentialOwnerRole = await testDb.getCredentialOwnerRole();
|
||||||
|
|
||||||
|
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||||
|
member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
|
||||||
|
authAgent = utils.createAuthAgent(app);
|
||||||
|
authOwnerAgent = authAgent(owner);
|
||||||
|
|
||||||
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
|
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
|
||||||
authAgent = utils.createAuthAgent(app);
|
|
||||||
|
|
||||||
sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
|
sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await testDb.truncate(['User', 'SharedCredentials', 'Credentials']);
|
await testDb.truncate(['SharedCredentials', 'Credentials']);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
@ -47,490 +52,452 @@ afterAll(async () => {
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// dynamic router switching
|
// dynamic router switching
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
describe('router should switch based on flag', () => {
|
||||||
|
let savedCredentialId: string;
|
||||||
|
|
||||||
test('router should switch based on flag', async () => {
|
beforeEach(async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
savedCredentialId = savedCredential.id;
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
});
|
||||||
|
|
||||||
// free router
|
test('when sharing is disabled', async () => {
|
||||||
sharingSpy.mockReturnValueOnce(false);
|
sharingSpy.mockReturnValueOnce(false);
|
||||||
|
|
||||||
const freeShareResponse = authAgent(owner)
|
await authOwnerAgent
|
||||||
.put(`/credentials/${savedCredential.id}/share`)
|
.put(`/credentials/${savedCredentialId}/share`)
|
||||||
.send({ shareWithIds: [member.id] });
|
.send({ shareWithIds: [member.id] })
|
||||||
|
.expect(404);
|
||||||
|
|
||||||
const freeGetResponse = authAgent(owner).get(`/credentials/${savedCredential.id}`).send();
|
await authOwnerAgent.get(`/credentials/${savedCredentialId}`).send().expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
const [{ statusCode: freeShareStatus }, { statusCode: freeGetStatus }] = await Promise.all([
|
test('when sharing is enabled', async () => {
|
||||||
freeShareResponse,
|
await authOwnerAgent
|
||||||
freeGetResponse,
|
.put(`/credentials/${savedCredentialId}/share`)
|
||||||
]);
|
.send({ shareWithIds: [member.id] })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
expect(freeShareStatus).toBe(404);
|
await authOwnerAgent.get(`/credentials/${savedCredentialId}`).send().expect(200);
|
||||||
expect(freeGetStatus).toBe(200);
|
});
|
||||||
|
|
||||||
// EE router
|
|
||||||
|
|
||||||
const eeShareResponse = authAgent(owner)
|
|
||||||
.put(`/credentials/${savedCredential.id}/share`)
|
|
||||||
.send({ shareWithIds: [member.id] });
|
|
||||||
|
|
||||||
const eeGetResponse = authAgent(owner).get(`/credentials/${savedCredential.id}`).send();
|
|
||||||
|
|
||||||
const [{ statusCode: eeShareStatus }, { statusCode: eeGetStatus }] = await Promise.all([
|
|
||||||
eeShareResponse,
|
|
||||||
eeGetResponse,
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(eeShareStatus).toBe(200);
|
|
||||||
expect(eeGetStatus).toBe(200);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// GET /credentials - fetch all credentials
|
// GET /credentials - fetch all credentials
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
describe('GET /credentials', () => {
|
||||||
test('GET /credentials should return all creds for owner', async () => {
|
test('should return all creds for owner', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
const [member1, member2, member3] = await testDb.createManyUsers(3, {
|
||||||
const [member1, member2, member3] = await testDb.createManyUsers(3, {
|
globalRole: globalMemberRole,
|
||||||
globalRole: globalMemberRole,
|
|
||||||
});
|
|
||||||
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
|
||||||
await saveCredential(randomCredentialPayload(), { user: member1 });
|
|
||||||
|
|
||||||
const sharedWith = [member1, member2, member3];
|
|
||||||
await testDb.shareCredentialWithUsers(savedCredential, sharedWith);
|
|
||||||
|
|
||||||
const response = await authAgent(owner).get('/credentials');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body.data).toHaveLength(2); // owner retrieved owner cred and member cred
|
|
||||||
|
|
||||||
const [ownerCredential, memberCredential] = response.body.data as CredentialWithSharings[];
|
|
||||||
|
|
||||||
validateMainCredentialData(ownerCredential);
|
|
||||||
expect(ownerCredential.data).toBeUndefined();
|
|
||||||
|
|
||||||
validateMainCredentialData(memberCredential);
|
|
||||||
expect(memberCredential.data).toBeUndefined();
|
|
||||||
|
|
||||||
expect(ownerCredential.ownedBy).toMatchObject({
|
|
||||||
id: owner.id,
|
|
||||||
email: owner.email,
|
|
||||||
firstName: owner.firstName,
|
|
||||||
lastName: owner.lastName,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(Array.isArray(ownerCredential.sharedWith)).toBe(true);
|
|
||||||
expect(ownerCredential.sharedWith).toHaveLength(3);
|
|
||||||
|
|
||||||
// Fix order issue (MySQL might return items in any order)
|
|
||||||
const ownerCredentialsSharedWithOrdered = [...ownerCredential.sharedWith!].sort(
|
|
||||||
(a: IUser, b: IUser) => (a.email < b.email ? -1 : 1),
|
|
||||||
);
|
|
||||||
const orderedSharedWith = [...sharedWith].sort((a, b) => (a.email < b.email ? -1 : 1));
|
|
||||||
|
|
||||||
ownerCredentialsSharedWithOrdered.forEach((sharee: IUser, idx: number) => {
|
|
||||||
expect(sharee).toMatchObject({
|
|
||||||
id: orderedSharedWith[idx].id,
|
|
||||||
email: orderedSharedWith[idx].email,
|
|
||||||
firstName: orderedSharedWith[idx].firstName,
|
|
||||||
lastName: orderedSharedWith[idx].lastName,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
await saveCredential(randomCredentialPayload(), { user: member1 });
|
||||||
|
|
||||||
|
const sharedWith = [member1, member2, member3];
|
||||||
|
await testDb.shareCredentialWithUsers(savedCredential, sharedWith);
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.get('/credentials');
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body.data).toHaveLength(2); // owner retrieved owner cred and member cred
|
||||||
|
|
||||||
|
const [ownerCredential, memberCredential] = response.body.data as CredentialWithSharings[];
|
||||||
|
|
||||||
|
validateMainCredentialData(ownerCredential);
|
||||||
|
expect(ownerCredential.data).toBeUndefined();
|
||||||
|
|
||||||
|
validateMainCredentialData(memberCredential);
|
||||||
|
expect(memberCredential.data).toBeUndefined();
|
||||||
|
|
||||||
|
expect(ownerCredential.ownedBy).toMatchObject({
|
||||||
|
id: owner.id,
|
||||||
|
email: owner.email,
|
||||||
|
firstName: owner.firstName,
|
||||||
|
lastName: owner.lastName,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(Array.isArray(ownerCredential.sharedWith)).toBe(true);
|
||||||
|
expect(ownerCredential.sharedWith).toHaveLength(3);
|
||||||
|
|
||||||
|
// Fix order issue (MySQL might return items in any order)
|
||||||
|
const ownerCredentialsSharedWithOrdered = [...ownerCredential.sharedWith!].sort(
|
||||||
|
(a: IUser, b: IUser) => (a.email < b.email ? -1 : 1),
|
||||||
|
);
|
||||||
|
const orderedSharedWith = [...sharedWith].sort((a, b) => (a.email < b.email ? -1 : 1));
|
||||||
|
|
||||||
|
ownerCredentialsSharedWithOrdered.forEach((sharee: IUser, idx: number) => {
|
||||||
|
expect(sharee).toMatchObject({
|
||||||
|
id: orderedSharedWith[idx].id,
|
||||||
|
email: orderedSharedWith[idx].email,
|
||||||
|
firstName: orderedSharedWith[idx].firstName,
|
||||||
|
lastName: orderedSharedWith[idx].lastName,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(memberCredential.ownedBy).toMatchObject({
|
||||||
|
id: member1.id,
|
||||||
|
email: member1.email,
|
||||||
|
firstName: member1.firstName,
|
||||||
|
lastName: member1.lastName,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(Array.isArray(memberCredential.sharedWith)).toBe(true);
|
||||||
|
expect(memberCredential.sharedWith).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(memberCredential.ownedBy).toMatchObject({
|
test('should return only relevant creds for member', async () => {
|
||||||
id: member1.id,
|
const [member1, member2] = await testDb.createManyUsers(2, {
|
||||||
email: member1.email,
|
globalRole: globalMemberRole,
|
||||||
firstName: member1.firstName,
|
});
|
||||||
lastName: member1.lastName,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(Array.isArray(memberCredential.sharedWith)).toBe(true);
|
await saveCredential(randomCredentialPayload(), { user: member2 });
|
||||||
expect(memberCredential.sharedWith).toHaveLength(0);
|
const savedMemberCredential = await saveCredential(randomCredentialPayload(), {
|
||||||
});
|
user: member1,
|
||||||
|
});
|
||||||
|
|
||||||
test('GET /credentials should return only relevant creds for member', async () => {
|
await testDb.shareCredentialWithUsers(savedMemberCredential, [member2]);
|
||||||
const [member1, member2] = await testDb.createManyUsers(2, {
|
|
||||||
globalRole: globalMemberRole,
|
|
||||||
});
|
|
||||||
|
|
||||||
await saveCredential(randomCredentialPayload(), { user: member2 });
|
const response = await authAgent(member1).get('/credentials');
|
||||||
const savedMemberCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
|
||||||
|
|
||||||
await testDb.shareCredentialWithUsers(savedMemberCredential, [member2]);
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body.data).toHaveLength(1); // member retrieved only member cred
|
||||||
|
|
||||||
const response = await authAgent(member1).get('/credentials');
|
const [member1Credential] = response.body.data;
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
validateMainCredentialData(member1Credential);
|
||||||
expect(response.body.data).toHaveLength(1); // member retrieved only member cred
|
expect(member1Credential.data).toBeUndefined();
|
||||||
|
|
||||||
const [member1Credential] = response.body.data;
|
expect(member1Credential.ownedBy).toMatchObject({
|
||||||
|
id: member1.id,
|
||||||
|
email: member1.email,
|
||||||
|
firstName: member1.firstName,
|
||||||
|
lastName: member1.lastName,
|
||||||
|
});
|
||||||
|
|
||||||
validateMainCredentialData(member1Credential);
|
expect(Array.isArray(member1Credential.sharedWith)).toBe(true);
|
||||||
expect(member1Credential.data).toBeUndefined();
|
expect(member1Credential.sharedWith).toHaveLength(1);
|
||||||
|
|
||||||
expect(member1Credential.ownedBy).toMatchObject({
|
const [sharee] = member1Credential.sharedWith;
|
||||||
id: member1.id,
|
|
||||||
email: member1.email,
|
|
||||||
firstName: member1.firstName,
|
|
||||||
lastName: member1.lastName,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(Array.isArray(member1Credential.sharedWith)).toBe(true);
|
expect(sharee).toMatchObject({
|
||||||
expect(member1Credential.sharedWith).toHaveLength(1);
|
id: member2.id,
|
||||||
|
email: member2.email,
|
||||||
const [sharee] = member1Credential.sharedWith;
|
firstName: member2.firstName,
|
||||||
|
lastName: member2.lastName,
|
||||||
expect(sharee).toMatchObject({
|
});
|
||||||
id: member2.id,
|
|
||||||
email: member2.email,
|
|
||||||
firstName: member2.firstName,
|
|
||||||
lastName: member2.lastName,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// GET /credentials/:id - fetch a certain credential
|
// GET /credentials/:id - fetch a certain credential
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
describe('GET /credentials/:id', () => {
|
||||||
|
test('should retrieve owned cred for owner', async () => {
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
|
||||||
test('GET /credentials/:id should retrieve owned cred for owner', async () => {
|
const firstResponse = await authOwnerAgent.get(`/credentials/${savedCredential.id}`);
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const authOwnerAgent = authAgent(ownerShell);
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
|
||||||
|
|
||||||
const firstResponse = await authOwnerAgent.get(`/credentials/${savedCredential.id}`);
|
expect(firstResponse.statusCode).toBe(200);
|
||||||
|
|
||||||
expect(firstResponse.statusCode).toBe(200);
|
const { data: firstCredential } = firstResponse.body;
|
||||||
|
validateMainCredentialData(firstCredential);
|
||||||
|
expect(firstCredential.data).toBeUndefined();
|
||||||
|
expect(firstCredential.ownedBy).toMatchObject({
|
||||||
|
id: owner.id,
|
||||||
|
email: owner.email,
|
||||||
|
firstName: owner.firstName,
|
||||||
|
lastName: owner.lastName,
|
||||||
|
});
|
||||||
|
expect(firstCredential.sharedWith).toHaveLength(0);
|
||||||
|
|
||||||
const { data: firstCredential } = firstResponse.body;
|
const secondResponse = await authOwnerAgent
|
||||||
validateMainCredentialData(firstCredential);
|
.get(`/credentials/${savedCredential.id}`)
|
||||||
expect(firstCredential.data).toBeUndefined();
|
.query({ includeData: true });
|
||||||
expect(firstCredential.ownedBy).toMatchObject({
|
|
||||||
id: ownerShell.id,
|
|
||||||
email: ownerShell.email,
|
|
||||||
firstName: ownerShell.firstName,
|
|
||||||
lastName: ownerShell.lastName,
|
|
||||||
});
|
|
||||||
expect(firstCredential.sharedWith).toHaveLength(0);
|
|
||||||
|
|
||||||
const secondResponse = await authOwnerAgent
|
expect(secondResponse.statusCode).toBe(200);
|
||||||
.get(`/credentials/${savedCredential.id}`)
|
|
||||||
.query({ includeData: true });
|
|
||||||
|
|
||||||
expect(secondResponse.statusCode).toBe(200);
|
const { data: secondCredential } = secondResponse.body;
|
||||||
|
validateMainCredentialData(secondCredential);
|
||||||
const { data: secondCredential } = secondResponse.body;
|
expect(secondCredential.data).toBeDefined();
|
||||||
validateMainCredentialData(secondCredential);
|
|
||||||
expect(secondCredential.data).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /credentials/:id should retrieve non-owned cred for owner', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const authOwnerAgent = authAgent(owner);
|
|
||||||
const [member1, member2] = await testDb.createManyUsers(2, {
|
|
||||||
globalRole: globalMemberRole,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
test('should retrieve non-owned cred for owner', async () => {
|
||||||
await testDb.shareCredentialWithUsers(savedCredential, [member2]);
|
const [member1, member2] = await testDb.createManyUsers(2, {
|
||||||
|
globalRole: globalMemberRole,
|
||||||
|
});
|
||||||
|
|
||||||
const response1 = await authOwnerAgent.get(`/credentials/${savedCredential.id}`);
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
||||||
|
await testDb.shareCredentialWithUsers(savedCredential, [member2]);
|
||||||
|
|
||||||
expect(response1.statusCode).toBe(200);
|
const response1 = await authOwnerAgent.get(`/credentials/${savedCredential.id}`);
|
||||||
|
|
||||||
validateMainCredentialData(response1.body.data);
|
expect(response1.statusCode).toBe(200);
|
||||||
expect(response1.body.data.data).toBeUndefined();
|
|
||||||
expect(response1.body.data.ownedBy).toMatchObject({
|
validateMainCredentialData(response1.body.data);
|
||||||
id: member1.id,
|
expect(response1.body.data.data).toBeUndefined();
|
||||||
email: member1.email,
|
expect(response1.body.data.ownedBy).toMatchObject({
|
||||||
firstName: member1.firstName,
|
id: member1.id,
|
||||||
lastName: member1.lastName,
|
email: member1.email,
|
||||||
});
|
firstName: member1.firstName,
|
||||||
expect(response1.body.data.sharedWith).toHaveLength(1);
|
lastName: member1.lastName,
|
||||||
expect(response1.body.data.sharedWith[0]).toMatchObject({
|
});
|
||||||
id: member2.id,
|
expect(response1.body.data.sharedWith).toHaveLength(1);
|
||||||
email: member2.email,
|
expect(response1.body.data.sharedWith[0]).toMatchObject({
|
||||||
firstName: member2.firstName,
|
id: member2.id,
|
||||||
lastName: member2.lastName,
|
email: member2.email,
|
||||||
|
firstName: member2.firstName,
|
||||||
|
lastName: member2.lastName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response2 = await authOwnerAgent
|
||||||
|
.get(`/credentials/${savedCredential.id}`)
|
||||||
|
.query({ includeData: true });
|
||||||
|
|
||||||
|
expect(response2.statusCode).toBe(200);
|
||||||
|
|
||||||
|
validateMainCredentialData(response2.body.data);
|
||||||
|
expect(response2.body.data.data).toBeUndefined();
|
||||||
|
expect(response2.body.data.sharedWith).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
const response2 = await authOwnerAgent
|
test('should retrieve owned cred for member', async () => {
|
||||||
.get(`/credentials/${savedCredential.id}`)
|
const [member1, member2, member3] = await testDb.createManyUsers(3, {
|
||||||
.query({ includeData: true });
|
globalRole: globalMemberRole,
|
||||||
|
});
|
||||||
|
const authMemberAgent = authAgent(member1);
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
||||||
|
await testDb.shareCredentialWithUsers(savedCredential, [member2, member3]);
|
||||||
|
|
||||||
expect(response2.statusCode).toBe(200);
|
const firstResponse = await authMemberAgent.get(`/credentials/${savedCredential.id}`);
|
||||||
|
|
||||||
validateMainCredentialData(response2.body.data);
|
expect(firstResponse.statusCode).toBe(200);
|
||||||
expect(response2.body.data.data).toBeUndefined();
|
|
||||||
expect(response2.body.data.sharedWith).toHaveLength(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /credentials/:id should retrieve owned cred for member', async () => {
|
const { data: firstCredential } = firstResponse.body;
|
||||||
const [member1, member2, member3] = await testDb.createManyUsers(3, {
|
validateMainCredentialData(firstCredential);
|
||||||
globalRole: globalMemberRole,
|
expect(firstCredential.data).toBeUndefined();
|
||||||
});
|
expect(firstCredential.ownedBy).toMatchObject({
|
||||||
const authMemberAgent = authAgent(member1);
|
id: member1.id,
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
email: member1.email,
|
||||||
await testDb.shareCredentialWithUsers(savedCredential, [member2, member3]);
|
firstName: member1.firstName,
|
||||||
|
lastName: member1.lastName,
|
||||||
|
});
|
||||||
|
expect(firstCredential.sharedWith).toHaveLength(2);
|
||||||
|
firstCredential.sharedWith.forEach((sharee: IUser, idx: number) => {
|
||||||
|
expect([member2.id, member3.id]).toContain(sharee.id);
|
||||||
|
});
|
||||||
|
|
||||||
const firstResponse = await authMemberAgent.get(`/credentials/${savedCredential.id}`);
|
const secondResponse = await authMemberAgent
|
||||||
|
.get(`/credentials/${savedCredential.id}`)
|
||||||
|
.query({ includeData: true });
|
||||||
|
|
||||||
expect(firstResponse.statusCode).toBe(200);
|
expect(secondResponse.statusCode).toBe(200);
|
||||||
|
|
||||||
const { data: firstCredential } = firstResponse.body;
|
const { data: secondCredential } = secondResponse.body;
|
||||||
validateMainCredentialData(firstCredential);
|
validateMainCredentialData(secondCredential);
|
||||||
expect(firstCredential.data).toBeUndefined();
|
expect(secondCredential.data).toBeDefined();
|
||||||
expect(firstCredential.ownedBy).toMatchObject({
|
expect(firstCredential.sharedWith).toHaveLength(2);
|
||||||
id: member1.id,
|
|
||||||
email: member1.email,
|
|
||||||
firstName: member1.firstName,
|
|
||||||
lastName: member1.lastName,
|
|
||||||
});
|
|
||||||
expect(firstCredential.sharedWith).toHaveLength(2);
|
|
||||||
firstCredential.sharedWith.forEach((sharee: IUser, idx: number) => {
|
|
||||||
expect([member2.id, member3.id]).toContain(sharee.id);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const secondResponse = await authMemberAgent
|
test('should not retrieve non-owned cred for member', async () => {
|
||||||
.get(`/credentials/${savedCredential.id}`)
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
.query({ includeData: true });
|
|
||||||
|
|
||||||
expect(secondResponse.statusCode).toBe(200);
|
const response = await authAgent(member).get(`/credentials/${savedCredential.id}`);
|
||||||
|
|
||||||
const { data: secondCredential } = secondResponse.body;
|
expect(response.statusCode).toBe(403);
|
||||||
validateMainCredentialData(secondCredential);
|
expect(response.body.data).toBeUndefined(); // owner's cred not returned
|
||||||
expect(secondCredential.data).toBeDefined();
|
|
||||||
expect(firstCredential.sharedWith).toHaveLength(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /credentials/:id should not retrieve non-owned cred for member', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
|
||||||
|
|
||||||
const response = await authAgent(member).get(`/credentials/${savedCredential.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(403);
|
|
||||||
expect(response.body.data).toBeUndefined(); // owner's cred not returned
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /credentials/:id should fail with missing encryption key', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
|
||||||
|
|
||||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
|
||||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell)
|
|
||||||
.get(`/credentials/${savedCredential.id}`)
|
|
||||||
.query({ includeData: true });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(500);
|
|
||||||
|
|
||||||
mock.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /credentials/:id should return 404 if cred not found', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).get('/credentials/789');
|
|
||||||
expect(response.statusCode).toBe(404);
|
|
||||||
|
|
||||||
const responseAbc = await authAgent(ownerShell).get('/credentials/abc');
|
|
||||||
expect(responseAbc.statusCode).toBe(404);
|
|
||||||
|
|
||||||
// because EE router has precedence, check if forwards this route
|
|
||||||
const responseNew = await authAgent(ownerShell).get('/credentials/new');
|
|
||||||
expect(responseNew.statusCode).toBe(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// indempotent share/unshare
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
test('PUT /credentials/:id/share should share the credential with the provided userIds and unshare it for missing ones', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
|
||||||
|
|
||||||
const [member1, member2, member3, member4, member5] = await testDb.createManyUsers(5, {
|
|
||||||
globalRole: globalMemberRole,
|
|
||||||
});
|
|
||||||
const shareWithIds = [member1.id, member2.id, member3.id];
|
|
||||||
|
|
||||||
await testDb.shareCredentialWithUsers(savedCredential, [member4, member5]);
|
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
|
||||||
.put(`/credentials/${savedCredential.id}/share`)
|
|
||||||
.send({ shareWithIds });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body.data).toBeUndefined();
|
|
||||||
|
|
||||||
const sharedCredentials = await Db.collections.SharedCredentials.find({
|
|
||||||
relations: ['role'],
|
|
||||||
where: { credentialsId: savedCredential.id },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// check that sharings have been removed/added correctly
|
test('should fail with missing encryption key', async () => {
|
||||||
expect(sharedCredentials.length).toBe(shareWithIds.length + 1); // +1 for the owner
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
|
||||||
sharedCredentials.forEach((sharedCredential) => {
|
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||||
if (sharedCredential.userId === owner.id) {
|
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||||
expect(sharedCredential.role.name).toBe('owner');
|
|
||||||
expect(sharedCredential.role.scope).toBe('credential');
|
const response = await authOwnerAgent
|
||||||
return;
|
.get(`/credentials/${savedCredential.id}`)
|
||||||
}
|
.query({ includeData: true });
|
||||||
expect(shareWithIds).toContain(sharedCredential.userId);
|
|
||||||
expect(sharedCredential.role.name).toBe('user');
|
expect(response.statusCode).toBe(500);
|
||||||
expect(sharedCredential.role.scope).toBe('credential');
|
|
||||||
|
mock.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return 404 if cred not found', async () => {
|
||||||
|
const response = await authOwnerAgent.get('/credentials/789');
|
||||||
|
expect(response.statusCode).toBe(404);
|
||||||
|
|
||||||
|
const responseAbc = await authOwnerAgent.get('/credentials/abc');
|
||||||
|
expect(responseAbc.statusCode).toBe(404);
|
||||||
|
|
||||||
|
// because EE router has precedence, check if forwards this route
|
||||||
|
const responseNew = await authOwnerAgent.get('/credentials/new');
|
||||||
|
expect(responseNew.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// share
|
// idempotent share/unshare
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
describe('PUT /credentials/:id/share', () => {
|
||||||
|
test('should share the credential with the provided userIds and unshare it for missing ones', async () => {
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
|
||||||
test('PUT /credentials/:id/share should share the credential with the provided userIds', async () => {
|
const [member1, member2, member3, member4, member5] = await testDb.createManyUsers(5, {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
globalRole: globalMemberRole,
|
||||||
const [member1, member2, member3] = await testDb.createManyUsers(3, {
|
});
|
||||||
globalRole: globalMemberRole,
|
const shareWithIds = [member1.id, member2.id, member3.id];
|
||||||
});
|
|
||||||
const memberIds = [member1.id, member2.id, member3.id];
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
await testDb.shareCredentialWithUsers(savedCredential, [member4, member5]);
|
||||||
.put(`/credentials/${savedCredential.id}/share`)
|
|
||||||
.send({ shareWithIds: memberIds });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
const response = await authOwnerAgent
|
||||||
expect(response.body.data).toBeUndefined();
|
|
||||||
|
|
||||||
// check that sharings got correctly set in DB
|
|
||||||
const sharedCredentials = await Db.collections.SharedCredentials.find({
|
|
||||||
relations: ['role'],
|
|
||||||
where: { credentialsId: savedCredential.id, userId: In([...memberIds]) },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(sharedCredentials.length).toBe(memberIds.length);
|
|
||||||
|
|
||||||
sharedCredentials.forEach((sharedCredential) => {
|
|
||||||
expect(sharedCredential.role.name).toBe('user');
|
|
||||||
expect(sharedCredential.role.scope).toBe('credential');
|
|
||||||
});
|
|
||||||
|
|
||||||
// check that owner still exists
|
|
||||||
const ownerSharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
|
||||||
relations: ['role'],
|
|
||||||
where: { credentialsId: savedCredential.id, userId: owner.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(ownerSharedCredential.role.name).toBe('owner');
|
|
||||||
expect(ownerSharedCredential.role.scope).toBe('credential');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PUT /credentials/:id/share should respond 403 for non-existing credentials', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
|
||||||
.put(`/credentials/1234567/share`)
|
|
||||||
.send({ shareWithIds: [member.id] });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(403);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PUT /credentials/:id/share should respond 403 for non-owned credentials', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
|
||||||
.put(`/credentials/${savedCredential.id}/share`)
|
|
||||||
.send({ shareWithIds: [member.id] });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(403);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PUT /credentials/:id/share should ignore pending sharee', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const memberShell = await testDb.createUserShell(globalMemberRole);
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
|
||||||
.put(`/credentials/${savedCredential.id}/share`)
|
|
||||||
.send({ shareWithIds: [memberShell.id] });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const sharedCredentials = await Db.collections.SharedCredentials.find({
|
|
||||||
where: { credentialsId: savedCredential.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(sharedCredentials).toHaveLength(1);
|
|
||||||
expect(sharedCredentials[0].userId).toBe(owner.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PUT /credentials/:id/share should ignore non-existing sharee', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
|
||||||
.put(`/credentials/${savedCredential.id}/share`)
|
|
||||||
.send({ shareWithIds: ['bce38a11-5e45-4d1c-a9ee-36e4a20ab0fc'] });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const sharedCredentials = await Db.collections.SharedCredentials.find({
|
|
||||||
where: { credentialsId: savedCredential.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(sharedCredentials).toHaveLength(1);
|
|
||||||
expect(sharedCredentials[0].userId).toBe(owner.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PUT /credentials/:id/share should respond 400 if invalid payload is provided', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
|
||||||
|
|
||||||
const responses = await Promise.all([
|
|
||||||
authAgent(owner).put(`/credentials/${savedCredential.id}/share`).send(),
|
|
||||||
authAgent(owner)
|
|
||||||
.put(`/credentials/${savedCredential.id}/share`)
|
.put(`/credentials/${savedCredential.id}/share`)
|
||||||
.send({ shareWithIds: [1] }),
|
.send({ shareWithIds });
|
||||||
]);
|
|
||||||
|
|
||||||
responses.forEach((response) => expect(response.statusCode).toBe(400));
|
expect(response.statusCode).toBe(200);
|
||||||
});
|
expect(response.body.data).toBeUndefined();
|
||||||
|
|
||||||
// ----------------------------------------
|
const sharedCredentials = await Db.collections.SharedCredentials.find({
|
||||||
// unshare
|
relations: ['role'],
|
||||||
// ----------------------------------------
|
where: { credentialsId: savedCredential.id },
|
||||||
|
});
|
||||||
|
|
||||||
test('PUT /credentials/:id/share should unshare the credential', async () => {
|
// check that sharings have been removed/added correctly
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
expect(sharedCredentials.length).toBe(shareWithIds.length + 1); // +1 for the owner
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
|
||||||
|
|
||||||
const [member1, member2] = await testDb.createManyUsers(2, {
|
sharedCredentials.forEach((sharedCredential) => {
|
||||||
globalRole: globalMemberRole,
|
if (sharedCredential.userId === owner.id) {
|
||||||
|
expect(sharedCredential.role.name).toBe('owner');
|
||||||
|
expect(sharedCredential.role.scope).toBe('credential');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(shareWithIds).toContain(sharedCredential.userId);
|
||||||
|
expect(sharedCredential.role.name).toBe('user');
|
||||||
|
expect(sharedCredential.role.scope).toBe('credential');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await testDb.shareCredentialWithUsers(savedCredential, [member1, member2]);
|
test('should share the credential with the provided userIds', async () => {
|
||||||
|
const [member1, member2, member3] = await testDb.createManyUsers(3, {
|
||||||
|
globalRole: globalMemberRole,
|
||||||
|
});
|
||||||
|
const memberIds = [member1.id, member2.id, member3.id];
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
const response = await authOwnerAgent
|
||||||
.put(`/credentials/${savedCredential.id}/share`)
|
.put(`/credentials/${savedCredential.id}/share`)
|
||||||
.send({ shareWithIds: [] });
|
.send({ shareWithIds: memberIds });
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body.data).toBeUndefined();
|
||||||
|
|
||||||
const sharedCredentials = await Db.collections.SharedCredentials.find({
|
// check that sharings got correctly set in DB
|
||||||
where: { credentialsId: savedCredential.id },
|
const sharedCredentials = await Db.collections.SharedCredentials.find({
|
||||||
|
relations: ['role'],
|
||||||
|
where: { credentialsId: savedCredential.id, userId: In([...memberIds]) },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sharedCredentials.length).toBe(memberIds.length);
|
||||||
|
|
||||||
|
sharedCredentials.forEach((sharedCredential) => {
|
||||||
|
expect(sharedCredential.role.name).toBe('user');
|
||||||
|
expect(sharedCredential.role.scope).toBe('credential');
|
||||||
|
});
|
||||||
|
|
||||||
|
// check that owner still exists
|
||||||
|
const ownerSharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||||
|
relations: ['role'],
|
||||||
|
where: { credentialsId: savedCredential.id, userId: owner.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ownerSharedCredential.role.name).toBe('owner');
|
||||||
|
expect(ownerSharedCredential.role.scope).toBe('credential');
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sharedCredentials).toHaveLength(1);
|
test('should respond 403 for non-existing credentials', async () => {
|
||||||
expect(sharedCredentials[0].userId).toBe(owner.id);
|
const response = await authOwnerAgent
|
||||||
|
.put(`/credentials/1234567/share`)
|
||||||
|
.send({ shareWithIds: [member.id] });
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respond 403 for non-owned credentials', async () => {
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
|
|
||||||
|
const response = await authOwnerAgent
|
||||||
|
.put(`/credentials/${savedCredential.id}/share`)
|
||||||
|
.send({ shareWithIds: [member.id] });
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should ignore pending sharee', async () => {
|
||||||
|
const memberShell = await testDb.createUserShell(globalMemberRole);
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
|
||||||
|
const response = await authOwnerAgent
|
||||||
|
.put(`/credentials/${savedCredential.id}/share`)
|
||||||
|
.send({ shareWithIds: [memberShell.id] });
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const sharedCredentials = await Db.collections.SharedCredentials.find({
|
||||||
|
where: { credentialsId: savedCredential.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sharedCredentials).toHaveLength(1);
|
||||||
|
expect(sharedCredentials[0].userId).toBe(owner.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should ignore non-existing sharee', async () => {
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
|
||||||
|
const response = await authOwnerAgent
|
||||||
|
.put(`/credentials/${savedCredential.id}/share`)
|
||||||
|
.send({ shareWithIds: ['bce38a11-5e45-4d1c-a9ee-36e4a20ab0fc'] });
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const sharedCredentials = await Db.collections.SharedCredentials.find({
|
||||||
|
where: { credentialsId: savedCredential.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sharedCredentials).toHaveLength(1);
|
||||||
|
expect(sharedCredentials[0].userId).toBe(owner.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respond 400 if invalid payload is provided', async () => {
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
|
||||||
|
const responses = await Promise.all([
|
||||||
|
authOwnerAgent.put(`/credentials/${savedCredential.id}/share`).send(),
|
||||||
|
authOwnerAgent.put(`/credentials/${savedCredential.id}/share`).send({ shareWithIds: [1] }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
responses.forEach((response) => expect(response.statusCode).toBe(400));
|
||||||
|
});
|
||||||
|
test('should unshare the credential', async () => {
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
|
||||||
|
const [member1, member2] = await testDb.createManyUsers(2, {
|
||||||
|
globalRole: globalMemberRole,
|
||||||
|
});
|
||||||
|
|
||||||
|
await testDb.shareCredentialWithUsers(savedCredential, [member1, member2]);
|
||||||
|
|
||||||
|
const response = await authOwnerAgent
|
||||||
|
.put(`/credentials/${savedCredential.id}/share`)
|
||||||
|
.send({ shareWithIds: [] });
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const sharedCredentials = await Db.collections.SharedCredentials.find({
|
||||||
|
where: { credentialsId: savedCredential.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sharedCredentials).toHaveLength(1);
|
||||||
|
expect(sharedCredentials[0].userId).toBe(owner.id);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function validateMainCredentialData(credential: CredentialWithSharings) {
|
function validateMainCredentialData(credential: CredentialWithSharings) {
|
||||||
|
|
|
@ -1,26 +1,31 @@
|
||||||
import express from 'express';
|
import type { Application } from 'express';
|
||||||
|
import type { SuperAgentTest } from 'supertest';
|
||||||
import { UserSettings } from 'n8n-core';
|
import { UserSettings } from 'n8n-core';
|
||||||
|
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
|
import config from '@/config';
|
||||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||||
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
||||||
|
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
|
import type { User } from '@db/entities/User';
|
||||||
import { randomCredentialPayload, randomName, randomString } from './shared/random';
|
import { randomCredentialPayload, randomName, randomString } from './shared/random';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import type { SaveCredentialFunction } from './shared/types';
|
import type { SaveCredentialFunction } from './shared/types';
|
||||||
import * as utils from './shared/utils';
|
import * as utils from './shared/utils';
|
||||||
|
|
||||||
import config from '@/config';
|
|
||||||
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
|
||||||
import type { AuthAgent } from './shared/types';
|
import type { AuthAgent } from './shared/types';
|
||||||
|
|
||||||
// mock that credentialsSharing is not enabled
|
// mock that credentialsSharing is not enabled
|
||||||
const mockIsCredentialsSharingEnabled = jest.spyOn(UserManagementHelpers, 'isSharingEnabled');
|
const mockIsCredentialsSharingEnabled = jest.spyOn(UserManagementHelpers, 'isSharingEnabled');
|
||||||
mockIsCredentialsSharingEnabled.mockReturnValue(false);
|
mockIsCredentialsSharingEnabled.mockReturnValue(false);
|
||||||
|
|
||||||
let app: express.Application;
|
let app: Application;
|
||||||
let globalOwnerRole: Role;
|
let globalOwnerRole: Role;
|
||||||
let globalMemberRole: Role;
|
let globalMemberRole: Role;
|
||||||
|
let owner: User;
|
||||||
|
let member: User;
|
||||||
|
let authOwnerAgent: SuperAgentTest;
|
||||||
|
let authMemberAgent: SuperAgentTest;
|
||||||
let saveCredential: SaveCredentialFunction;
|
let saveCredential: SaveCredentialFunction;
|
||||||
let authAgent: AuthAgent;
|
let authAgent: AuthAgent;
|
||||||
|
|
||||||
|
@ -33,13 +38,18 @@ beforeAll(async () => {
|
||||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||||
const credentialOwnerRole = await testDb.getCredentialOwnerRole();
|
const credentialOwnerRole = await testDb.getCredentialOwnerRole();
|
||||||
|
|
||||||
|
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||||
|
member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
|
||||||
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
|
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
|
||||||
|
|
||||||
authAgent = utils.createAuthAgent(app);
|
authAgent = utils.createAuthAgent(app);
|
||||||
|
authOwnerAgent = authAgent(owner);
|
||||||
|
authMemberAgent = authAgent(member);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await testDb.truncate(['User', 'SharedCredentials', 'Credentials']);
|
await testDb.truncate(['SharedCredentials', 'Credentials']);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
@ -49,526 +59,490 @@ afterAll(async () => {
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// GET /credentials - fetch all credentials
|
// GET /credentials - fetch all credentials
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
describe('GET /credentials', () => {
|
||||||
|
test('should return all creds for owner', async () => {
|
||||||
|
const [{ id: savedOwnerCredentialId }, { id: savedMemberCredentialId }] = await Promise.all([
|
||||||
|
saveCredential(randomCredentialPayload(), { user: owner }),
|
||||||
|
saveCredential(randomCredentialPayload(), { user: member }),
|
||||||
|
]);
|
||||||
|
|
||||||
test('GET /credentials should return all creds for owner', async () => {
|
const response = await authOwnerAgent.get('/credentials');
|
||||||
const [owner, member] = await Promise.all([
|
|
||||||
testDb.createUser({ globalRole: globalOwnerRole }),
|
|
||||||
testDb.createUser({ globalRole: globalMemberRole }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [{ id: savedOwnerCredentialId }, { id: savedMemberCredentialId }] = await Promise.all([
|
expect(response.statusCode).toBe(200);
|
||||||
saveCredential(randomCredentialPayload(), { user: owner }),
|
expect(response.body.data.length).toBe(2); // owner retrieved owner cred and member cred
|
||||||
saveCredential(randomCredentialPayload(), { user: member }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const response = await authAgent(owner).get('/credentials');
|
const savedCredentialsIds = [savedOwnerCredentialId, savedMemberCredentialId];
|
||||||
|
response.body.data.forEach((credential: CredentialsEntity) => {
|
||||||
|
validateMainCredentialData(credential);
|
||||||
|
expect(credential.data).toBeUndefined();
|
||||||
|
expect(savedCredentialsIds).toContain(credential.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
test('should return only own creds for member', async () => {
|
||||||
expect(response.body.data.length).toBe(2); // owner retrieved owner cred and member cred
|
const [member1, member2] = await testDb.createManyUsers(2, {
|
||||||
|
globalRole: globalMemberRole,
|
||||||
|
});
|
||||||
|
|
||||||
const savedCredentialsIds = [savedOwnerCredentialId, savedMemberCredentialId];
|
const [savedCredential1] = await Promise.all([
|
||||||
response.body.data.forEach((credential: CredentialsEntity) => {
|
saveCredential(randomCredentialPayload(), { user: member1 }),
|
||||||
validateMainCredentialData(credential);
|
saveCredential(randomCredentialPayload(), { user: member2 }),
|
||||||
expect(credential.data).toBeUndefined();
|
]);
|
||||||
expect(savedCredentialsIds).toContain(credential.id);
|
|
||||||
|
const response = await authAgent(member1).get('/credentials');
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body.data.length).toBe(1); // member retrieved only own cred
|
||||||
|
|
||||||
|
const [member1Credential] = response.body.data;
|
||||||
|
|
||||||
|
validateMainCredentialData(member1Credential);
|
||||||
|
expect(member1Credential.data).toBeUndefined();
|
||||||
|
expect(member1Credential.id).toBe(savedCredential1.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /credentials should return only own creds for member', async () => {
|
describe('POST /credentials', () => {
|
||||||
const [member1, member2] = await testDb.createManyUsers(2, {
|
test('should create cred', async () => {
|
||||||
globalRole: globalMemberRole,
|
const payload = randomCredentialPayload();
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.post('/credentials').send(payload);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const { id, name, type, nodesAccess, data: encryptedData } = response.body.data;
|
||||||
|
|
||||||
|
expect(name).toBe(payload.name);
|
||||||
|
expect(type).toBe(payload.type);
|
||||||
|
if (!payload.nodesAccess) {
|
||||||
|
fail('Payload did not contain a nodesAccess array');
|
||||||
|
}
|
||||||
|
expect(nodesAccess[0].nodeType).toBe(payload.nodesAccess[0].nodeType);
|
||||||
|
expect(encryptedData).not.toBe(payload.data);
|
||||||
|
|
||||||
|
const credential = await Db.collections.Credentials.findOneByOrFail({ id });
|
||||||
|
|
||||||
|
expect(credential.name).toBe(payload.name);
|
||||||
|
expect(credential.type).toBe(payload.type);
|
||||||
|
expect(credential.nodesAccess[0].nodeType).toBe(payload.nodesAccess![0].nodeType);
|
||||||
|
expect(credential.data).not.toBe(payload.data);
|
||||||
|
|
||||||
|
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||||
|
relations: ['user', 'credentials'],
|
||||||
|
where: { credentialsId: credential.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sharedCredential.user.id).toBe(owner.id);
|
||||||
|
expect(sharedCredential.credentials.name).toBe(payload.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
const [savedCredential1] = await Promise.all([
|
test('should fail with invalid inputs', async () => {
|
||||||
saveCredential(randomCredentialPayload(), { user: member1 }),
|
await Promise.all(
|
||||||
saveCredential(randomCredentialPayload(), { user: member2 }),
|
INVALID_PAYLOADS.map(async (invalidPayload) => {
|
||||||
]);
|
const response = await authOwnerAgent.post('/credentials').send(invalidPayload);
|
||||||
|
expect(response.statusCode).toBe(400);
|
||||||
const response = await authAgent(member1).get('/credentials');
|
}),
|
||||||
|
);
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body.data.length).toBe(1); // member retrieved only own cred
|
|
||||||
|
|
||||||
const [member1Credential] = response.body.data;
|
|
||||||
|
|
||||||
validateMainCredentialData(member1Credential);
|
|
||||||
expect(member1Credential.data).toBeUndefined();
|
|
||||||
expect(member1Credential.id).toBe(savedCredential1.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /credentials should create cred', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const payload = randomCredentialPayload();
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).post('/credentials').send(payload);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const { id, name, type, nodesAccess, data: encryptedData } = response.body.data;
|
|
||||||
|
|
||||||
expect(name).toBe(payload.name);
|
|
||||||
expect(type).toBe(payload.type);
|
|
||||||
if (!payload.nodesAccess) {
|
|
||||||
fail('Payload did not contain a nodesAccess array');
|
|
||||||
}
|
|
||||||
expect(nodesAccess[0].nodeType).toBe(payload.nodesAccess[0].nodeType);
|
|
||||||
expect(encryptedData).not.toBe(payload.data);
|
|
||||||
|
|
||||||
const credential = await Db.collections.Credentials.findOneByOrFail({ id });
|
|
||||||
|
|
||||||
expect(credential.name).toBe(payload.name);
|
|
||||||
expect(credential.type).toBe(payload.type);
|
|
||||||
expect(credential.nodesAccess[0].nodeType).toBe(payload.nodesAccess![0].nodeType);
|
|
||||||
expect(credential.data).not.toBe(payload.data);
|
|
||||||
|
|
||||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
|
||||||
relations: ['user', 'credentials'],
|
|
||||||
where: { credentialsId: credential.id },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sharedCredential.user.id).toBe(ownerShell.id);
|
test('should fail with missing encryption key', async () => {
|
||||||
expect(sharedCredential.credentials.name).toBe(payload.name);
|
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||||
});
|
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||||
|
|
||||||
test('POST /credentials should fail with invalid inputs', async () => {
|
const response = await authOwnerAgent.post('/credentials').send(randomCredentialPayload());
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const authOwnerAgent = authAgent(ownerShell);
|
|
||||||
|
|
||||||
await Promise.all(
|
expect(response.statusCode).toBe(500);
|
||||||
INVALID_PAYLOADS.map(async (invalidPayload) => {
|
|
||||||
const response = await authOwnerAgent.post('/credentials').send(invalidPayload);
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /credentials should fail with missing encryption key', async () => {
|
mock.mockRestore();
|
||||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
|
||||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
|
||||||
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).post('/credentials').send(randomCredentialPayload());
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(500);
|
|
||||||
|
|
||||||
mock.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /credentials should ignore ID in payload', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const authOwnerAgent = authAgent(ownerShell);
|
|
||||||
|
|
||||||
const firstResponse = await authOwnerAgent
|
|
||||||
.post('/credentials')
|
|
||||||
.send({ id: '8', ...randomCredentialPayload() });
|
|
||||||
|
|
||||||
expect(firstResponse.body.data.id).not.toBe('8');
|
|
||||||
|
|
||||||
const secondResponse = await authOwnerAgent
|
|
||||||
.post('/credentials')
|
|
||||||
.send({ id: 8, ...randomCredentialPayload() });
|
|
||||||
|
|
||||||
expect(secondResponse.body.data.id).not.toBe(8);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /credentials/:id should delete owned cred for owner', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).delete(`/credentials/${savedCredential.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body).toEqual({ data: true });
|
|
||||||
|
|
||||||
const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
|
|
||||||
|
|
||||||
expect(deletedCredential).toBeNull(); // deleted
|
|
||||||
|
|
||||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
|
||||||
|
|
||||||
expect(deletedSharedCredential).toBeNull(); // deleted
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /credentials/:id should delete non-owned cred for owner', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).delete(`/credentials/${savedCredential.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body).toEqual({ data: true });
|
|
||||||
|
|
||||||
const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
|
|
||||||
|
|
||||||
expect(deletedCredential).toBeNull(); // deleted
|
|
||||||
|
|
||||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
|
||||||
|
|
||||||
expect(deletedSharedCredential).toBeNull(); // deleted
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /credentials/:id should delete owned cred for member', async () => {
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
|
||||||
|
|
||||||
const response = await authAgent(member).delete(`/credentials/${savedCredential.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body).toEqual({ data: true });
|
|
||||||
|
|
||||||
const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
|
|
||||||
|
|
||||||
expect(deletedCredential).toBeNull(); // deleted
|
|
||||||
|
|
||||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
|
||||||
|
|
||||||
expect(deletedSharedCredential).toBeNull(); // deleted
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /credentials/:id should not delete non-owned cred for member', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
|
||||||
|
|
||||||
const response = await authAgent(member).delete(`/credentials/${savedCredential.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
|
||||||
|
|
||||||
const shellCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
|
|
||||||
|
|
||||||
expect(shellCredential).toBeDefined(); // not deleted
|
|
||||||
|
|
||||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
|
||||||
|
|
||||||
expect(deletedSharedCredential).toBeDefined(); // not deleted
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /credentials/:id should fail if cred not found', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).delete('/credentials/123');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PATCH /credentials/:id should update owned cred for owner', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
|
||||||
const patchPayload = randomCredentialPayload();
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell)
|
|
||||||
.patch(`/credentials/${savedCredential.id}`)
|
|
||||||
.send(patchPayload);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const { id, name, type, nodesAccess, data: encryptedData } = response.body.data;
|
|
||||||
|
|
||||||
expect(name).toBe(patchPayload.name);
|
|
||||||
expect(type).toBe(patchPayload.type);
|
|
||||||
if (!patchPayload.nodesAccess) {
|
|
||||||
fail('Payload did not contain a nodesAccess array');
|
|
||||||
}
|
|
||||||
expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
|
||||||
|
|
||||||
expect(encryptedData).not.toBe(patchPayload.data);
|
|
||||||
|
|
||||||
const credential = await Db.collections.Credentials.findOneByOrFail({ id });
|
|
||||||
|
|
||||||
expect(credential.name).toBe(patchPayload.name);
|
|
||||||
expect(credential.type).toBe(patchPayload.type);
|
|
||||||
expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess![0].nodeType);
|
|
||||||
expect(credential.data).not.toBe(patchPayload.data);
|
|
||||||
|
|
||||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
|
||||||
relations: ['credentials'],
|
|
||||||
where: { credentialsId: credential.id },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sharedCredential.credentials.name).toBe(patchPayload.name); // updated
|
test('should ignore ID in payload', async () => {
|
||||||
|
const firstResponse = await authOwnerAgent
|
||||||
|
.post('/credentials')
|
||||||
|
.send({ id: '8', ...randomCredentialPayload() });
|
||||||
|
|
||||||
|
expect(firstResponse.body.data.id).not.toBe('8');
|
||||||
|
|
||||||
|
const secondResponse = await authOwnerAgent
|
||||||
|
.post('/credentials')
|
||||||
|
.send({ id: 8, ...randomCredentialPayload() });
|
||||||
|
|
||||||
|
expect(secondResponse.body.data.id).not.toBe(8);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PATCH /credentials/:id should update non-owned cred for owner', async () => {
|
describe('DELETE /credentials/:id', () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
test('should delete owned cred for owner', async () => {
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
|
||||||
const patchPayload = randomCredentialPayload();
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell)
|
const response = await authOwnerAgent.delete(`/credentials/${savedCredential.id}`);
|
||||||
.patch(`/credentials/${savedCredential.id}`)
|
|
||||||
.send(patchPayload);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body).toEqual({ data: true });
|
||||||
|
|
||||||
const { id, name, type, nodesAccess, data: encryptedData } = response.body.data;
|
const deletedCredential = await Db.collections.Credentials.findOneBy({
|
||||||
|
id: savedCredential.id,
|
||||||
|
});
|
||||||
|
|
||||||
expect(name).toBe(patchPayload.name);
|
expect(deletedCredential).toBeNull(); // deleted
|
||||||
expect(type).toBe(patchPayload.type);
|
|
||||||
|
|
||||||
if (!patchPayload.nodesAccess) {
|
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
||||||
fail('Payload did not contain a nodesAccess array');
|
|
||||||
}
|
|
||||||
expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
|
||||||
|
|
||||||
expect(encryptedData).not.toBe(patchPayload.data);
|
expect(deletedSharedCredential).toBeNull(); // deleted
|
||||||
|
|
||||||
const credential = await Db.collections.Credentials.findOneByOrFail({ id });
|
|
||||||
|
|
||||||
expect(credential.name).toBe(patchPayload.name);
|
|
||||||
expect(credential.type).toBe(patchPayload.type);
|
|
||||||
expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess![0].nodeType);
|
|
||||||
expect(credential.data).not.toBe(patchPayload.data);
|
|
||||||
|
|
||||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
|
||||||
relations: ['credentials'],
|
|
||||||
where: { credentialsId: credential.id },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sharedCredential.credentials.name).toBe(patchPayload.name); // updated
|
test('should delete non-owned cred for owner', async () => {
|
||||||
});
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
|
|
||||||
test('PATCH /credentials/:id should update owned cred for member', async () => {
|
const response = await authOwnerAgent.delete(`/credentials/${savedCredential.id}`);
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
|
||||||
const patchPayload = randomCredentialPayload();
|
|
||||||
|
|
||||||
const response = await authAgent(member)
|
expect(response.statusCode).toBe(200);
|
||||||
.patch(`/credentials/${savedCredential.id}`)
|
expect(response.body).toEqual({ data: true });
|
||||||
.send(patchPayload);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
const deletedCredential = await Db.collections.Credentials.findOneBy({
|
||||||
|
id: savedCredential.id,
|
||||||
|
});
|
||||||
|
|
||||||
const { id, name, type, nodesAccess, data: encryptedData } = response.body.data;
|
expect(deletedCredential).toBeNull(); // deleted
|
||||||
|
|
||||||
expect(name).toBe(patchPayload.name);
|
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
||||||
expect(type).toBe(patchPayload.type);
|
|
||||||
|
|
||||||
if (!patchPayload.nodesAccess) {
|
expect(deletedSharedCredential).toBeNull(); // deleted
|
||||||
fail('Payload did not contain a nodesAccess array');
|
|
||||||
}
|
|
||||||
expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
|
||||||
|
|
||||||
expect(encryptedData).not.toBe(patchPayload.data);
|
|
||||||
|
|
||||||
const credential = await Db.collections.Credentials.findOneByOrFail({ id });
|
|
||||||
|
|
||||||
expect(credential.name).toBe(patchPayload.name);
|
|
||||||
expect(credential.type).toBe(patchPayload.type);
|
|
||||||
expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess![0].nodeType);
|
|
||||||
expect(credential.data).not.toBe(patchPayload.data);
|
|
||||||
|
|
||||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
|
||||||
relations: ['credentials'],
|
|
||||||
where: { credentialsId: credential.id },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sharedCredential.credentials.name).toBe(patchPayload.name); // updated
|
test('should delete owned cred for member', async () => {
|
||||||
});
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
|
|
||||||
test('PATCH /credentials/:id should not update non-owned cred for member', async () => {
|
const response = await authMemberAgent.delete(`/credentials/${savedCredential.id}`);
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
|
||||||
const patchPayload = randomCredentialPayload();
|
|
||||||
|
|
||||||
const response = await authAgent(member)
|
expect(response.statusCode).toBe(200);
|
||||||
.patch(`/credentials/${savedCredential.id}`)
|
expect(response.body).toEqual({ data: true });
|
||||||
.send(patchPayload);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
const deletedCredential = await Db.collections.Credentials.findOneBy({
|
||||||
|
id: savedCredential.id,
|
||||||
|
});
|
||||||
|
|
||||||
const shellCredential = await Db.collections.Credentials.findOneByOrFail({
|
expect(deletedCredential).toBeNull(); // deleted
|
||||||
id: savedCredential.id,
|
|
||||||
|
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
||||||
|
|
||||||
|
expect(deletedSharedCredential).toBeNull(); // deleted
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(shellCredential.name).not.toBe(patchPayload.name); // not updated
|
test('should not delete non-owned cred for member', async () => {
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
|
||||||
|
const response = await authMemberAgent.delete(`/credentials/${savedCredential.id}`);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(404);
|
||||||
|
|
||||||
|
const shellCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
|
||||||
|
|
||||||
|
expect(shellCredential).toBeDefined(); // not deleted
|
||||||
|
|
||||||
|
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
||||||
|
|
||||||
|
expect(deletedSharedCredential).toBeDefined(); // not deleted
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail if cred not found', async () => {
|
||||||
|
const response = await authOwnerAgent.delete('/credentials/123');
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(404);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PATCH /credentials/:id should fail with invalid inputs', async () => {
|
describe('PATCH /credentials/:id', () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
test('should update owned cred for owner', async () => {
|
||||||
const authOwnerAgent = authAgent(ownerShell);
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
const patchPayload = randomCredentialPayload();
|
||||||
|
|
||||||
await Promise.all(
|
const response = await authOwnerAgent
|
||||||
INVALID_PAYLOADS.map(async (invalidPayload) => {
|
.patch(`/credentials/${savedCredential.id}`)
|
||||||
const response = await authOwnerAgent
|
.send(patchPayload);
|
||||||
.patch(`/credentials/${savedCredential.id}`)
|
|
||||||
.send(invalidPayload);
|
|
||||||
|
|
||||||
if (response.statusCode === 500) {
|
expect(response.statusCode).toBe(200);
|
||||||
console.log(response.statusCode, response.body);
|
|
||||||
|
const { id, name, type, nodesAccess, data: encryptedData } = response.body.data;
|
||||||
|
|
||||||
|
expect(name).toBe(patchPayload.name);
|
||||||
|
expect(type).toBe(patchPayload.type);
|
||||||
|
if (!patchPayload.nodesAccess) {
|
||||||
|
fail('Payload did not contain a nodesAccess array');
|
||||||
|
}
|
||||||
|
expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
||||||
|
|
||||||
|
expect(encryptedData).not.toBe(patchPayload.data);
|
||||||
|
|
||||||
|
const credential = await Db.collections.Credentials.findOneByOrFail({ id });
|
||||||
|
|
||||||
|
expect(credential.name).toBe(patchPayload.name);
|
||||||
|
expect(credential.type).toBe(patchPayload.type);
|
||||||
|
expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess![0].nodeType);
|
||||||
|
expect(credential.data).not.toBe(patchPayload.data);
|
||||||
|
|
||||||
|
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||||
|
relations: ['credentials'],
|
||||||
|
where: { credentialsId: credential.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sharedCredential.credentials.name).toBe(patchPayload.name); // updated
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should update non-owned cred for owner', async () => {
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
|
const patchPayload = randomCredentialPayload();
|
||||||
|
|
||||||
|
const response = await authOwnerAgent
|
||||||
|
.patch(`/credentials/${savedCredential.id}`)
|
||||||
|
.send(patchPayload);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const { id, name, type, nodesAccess, data: encryptedData } = response.body.data;
|
||||||
|
|
||||||
|
expect(name).toBe(patchPayload.name);
|
||||||
|
expect(type).toBe(patchPayload.type);
|
||||||
|
|
||||||
|
if (!patchPayload.nodesAccess) {
|
||||||
|
fail('Payload did not contain a nodesAccess array');
|
||||||
|
}
|
||||||
|
expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
||||||
|
|
||||||
|
expect(encryptedData).not.toBe(patchPayload.data);
|
||||||
|
|
||||||
|
const credential = await Db.collections.Credentials.findOneByOrFail({ id });
|
||||||
|
|
||||||
|
expect(credential.name).toBe(patchPayload.name);
|
||||||
|
expect(credential.type).toBe(patchPayload.type);
|
||||||
|
expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess![0].nodeType);
|
||||||
|
expect(credential.data).not.toBe(patchPayload.data);
|
||||||
|
|
||||||
|
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||||
|
relations: ['credentials'],
|
||||||
|
where: { credentialsId: credential.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sharedCredential.credentials.name).toBe(patchPayload.name); // updated
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should update owned cred for member', async () => {
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
|
const patchPayload = randomCredentialPayload();
|
||||||
|
|
||||||
|
const response = await authMemberAgent
|
||||||
|
.patch(`/credentials/${savedCredential.id}`)
|
||||||
|
.send(patchPayload);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const { id, name, type, nodesAccess, data: encryptedData } = response.body.data;
|
||||||
|
|
||||||
|
expect(name).toBe(patchPayload.name);
|
||||||
|
expect(type).toBe(patchPayload.type);
|
||||||
|
|
||||||
|
if (!patchPayload.nodesAccess) {
|
||||||
|
fail('Payload did not contain a nodesAccess array');
|
||||||
|
}
|
||||||
|
expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
||||||
|
|
||||||
|
expect(encryptedData).not.toBe(patchPayload.data);
|
||||||
|
|
||||||
|
const credential = await Db.collections.Credentials.findOneByOrFail({ id });
|
||||||
|
|
||||||
|
expect(credential.name).toBe(patchPayload.name);
|
||||||
|
expect(credential.type).toBe(patchPayload.type);
|
||||||
|
expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess![0].nodeType);
|
||||||
|
expect(credential.data).not.toBe(patchPayload.data);
|
||||||
|
|
||||||
|
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||||
|
relations: ['credentials'],
|
||||||
|
where: { credentialsId: credential.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sharedCredential.credentials.name).toBe(patchPayload.name); // updated
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not update non-owned cred for member', async () => {
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
const patchPayload = randomCredentialPayload();
|
||||||
|
|
||||||
|
const response = await authMemberAgent
|
||||||
|
.patch(`/credentials/${savedCredential.id}`)
|
||||||
|
.send(patchPayload);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(404);
|
||||||
|
|
||||||
|
const shellCredential = await Db.collections.Credentials.findOneByOrFail({
|
||||||
|
id: savedCredential.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(shellCredential.name).not.toBe(patchPayload.name); // not updated
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail with invalid inputs', async () => {
|
||||||
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
INVALID_PAYLOADS.map(async (invalidPayload) => {
|
||||||
|
const response = await authOwnerAgent
|
||||||
|
.patch(`/credentials/${savedCredential.id}`)
|
||||||
|
.send(invalidPayload);
|
||||||
|
|
||||||
|
if (response.statusCode === 500) {
|
||||||
|
console.log(response.statusCode, response.body);
|
||||||
|
}
|
||||||
|
expect(response.statusCode).toBe(400);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail if cred not found', async () => {
|
||||||
|
const response = await authOwnerAgent.patch('/credentials/123').send(randomCredentialPayload());
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail with missing encryption key', async () => {
|
||||||
|
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||||
|
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.post('/credentials').send(randomCredentialPayload());
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(500);
|
||||||
|
|
||||||
|
mock.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /credentials/new', () => {
|
||||||
|
test('should return default name for new credential or its increment', async () => {
|
||||||
|
const name = config.getEnv('credentials.defaultName');
|
||||||
|
let tempName = name;
|
||||||
|
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const response = await authOwnerAgent.get(`/credentials/new?name=${name}`);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
if (i === 0) {
|
||||||
|
expect(response.body.data.name).toBe(name);
|
||||||
|
} else {
|
||||||
|
tempName = name + ' ' + (i + 1);
|
||||||
|
expect(response.body.data.name).toBe(tempName);
|
||||||
}
|
}
|
||||||
expect(response.statusCode).toBe(400);
|
await saveCredential({ ...randomCredentialPayload(), name: tempName }, { user: owner });
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PATCH /credentials/:id should fail if cred not found', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell)
|
|
||||||
.patch('/credentials/123')
|
|
||||||
.send(randomCredentialPayload());
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PATCH /credentials/:id should fail with missing encryption key', async () => {
|
|
||||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
|
||||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
|
||||||
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).post('/credentials').send(randomCredentialPayload());
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(500);
|
|
||||||
|
|
||||||
mock.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /credentials/new should return default name for new credential or its increment', async () => {
|
|
||||||
const ownerShell = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const authOwnerAgent = authAgent(ownerShell);
|
|
||||||
const name = config.getEnv('credentials.defaultName');
|
|
||||||
let tempName = name;
|
|
||||||
|
|
||||||
for (let i = 0; i < 4; i++) {
|
|
||||||
const response = await authOwnerAgent.get(`/credentials/new?name=${name}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
if (i === 0) {
|
|
||||||
expect(response.body.data.name).toBe(name);
|
|
||||||
} else {
|
|
||||||
tempName = name + ' ' + (i + 1);
|
|
||||||
expect(response.body.data.name).toBe(tempName);
|
|
||||||
}
|
}
|
||||||
await saveCredential({ ...randomCredentialPayload(), name: tempName }, { user: ownerShell });
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /credentials/new should return name from query for new credential or its increment', async () => {
|
test('should return name from query for new credential or its increment', async () => {
|
||||||
const ownerShell = await testDb.createUser({ globalRole: globalOwnerRole });
|
const name = 'special credential name';
|
||||||
const authOwnerAgent = authAgent(ownerShell);
|
let tempName = name;
|
||||||
const name = 'special credential name';
|
|
||||||
let tempName = name;
|
|
||||||
|
|
||||||
for (let i = 0; i < 4; i++) {
|
for (let i = 0; i < 4; i++) {
|
||||||
const response = await authOwnerAgent.get(`/credentials/new?name=${name}`);
|
const response = await authOwnerAgent.get(`/credentials/new?name=${name}`);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
expect(response.body.data.name).toBe(name);
|
expect(response.body.data.name).toBe(name);
|
||||||
} else {
|
} else {
|
||||||
tempName = name + ' ' + (i + 1);
|
tempName = name + ' ' + (i + 1);
|
||||||
expect(response.body.data.name).toBe(tempName);
|
expect(response.body.data.name).toBe(tempName);
|
||||||
|
}
|
||||||
|
await saveCredential({ ...randomCredentialPayload(), name: tempName }, { user: owner });
|
||||||
}
|
}
|
||||||
await saveCredential({ ...randomCredentialPayload(), name: tempName }, { user: ownerShell });
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /credentials/:id should retrieve owned cred for owner', async () => {
|
describe('GET /credentials/:id', () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
test('should retrieve owned cred for owner', async () => {
|
||||||
const authOwnerAgent = authAgent(ownerShell);
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
|
||||||
|
|
||||||
const firstResponse = await authOwnerAgent.get(`/credentials/${savedCredential.id}`);
|
const firstResponse = await authOwnerAgent.get(`/credentials/${savedCredential.id}`);
|
||||||
|
|
||||||
expect(firstResponse.statusCode).toBe(200);
|
expect(firstResponse.statusCode).toBe(200);
|
||||||
|
|
||||||
validateMainCredentialData(firstResponse.body.data);
|
validateMainCredentialData(firstResponse.body.data);
|
||||||
expect(firstResponse.body.data.data).toBeUndefined();
|
expect(firstResponse.body.data.data).toBeUndefined();
|
||||||
|
|
||||||
const secondResponse = await authOwnerAgent
|
const secondResponse = await authOwnerAgent
|
||||||
.get(`/credentials/${savedCredential.id}`)
|
.get(`/credentials/${savedCredential.id}`)
|
||||||
.query({ includeData: true });
|
.query({ includeData: true });
|
||||||
|
|
||||||
validateMainCredentialData(secondResponse.body.data);
|
validateMainCredentialData(secondResponse.body.data);
|
||||||
expect(secondResponse.body.data.data).toBeDefined();
|
expect(secondResponse.body.data.data).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /credentials/:id should retrieve owned cred for member', async () => {
|
test('should retrieve owned cred for member', async () => {
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
const authMemberAgent = authAgent(member);
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
|
||||||
|
|
||||||
const firstResponse = await authMemberAgent.get(`/credentials/${savedCredential.id}`);
|
const firstResponse = await authMemberAgent.get(`/credentials/${savedCredential.id}`);
|
||||||
|
|
||||||
expect(firstResponse.statusCode).toBe(200);
|
expect(firstResponse.statusCode).toBe(200);
|
||||||
|
|
||||||
validateMainCredentialData(firstResponse.body.data);
|
validateMainCredentialData(firstResponse.body.data);
|
||||||
expect(firstResponse.body.data.data).toBeUndefined();
|
expect(firstResponse.body.data.data).toBeUndefined();
|
||||||
|
|
||||||
const secondResponse = await authMemberAgent
|
const secondResponse = await authMemberAgent
|
||||||
.get(`/credentials/${savedCredential.id}`)
|
.get(`/credentials/${savedCredential.id}`)
|
||||||
.query({ includeData: true });
|
.query({ includeData: true });
|
||||||
|
|
||||||
expect(secondResponse.statusCode).toBe(200);
|
expect(secondResponse.statusCode).toBe(200);
|
||||||
|
|
||||||
validateMainCredentialData(secondResponse.body.data);
|
validateMainCredentialData(secondResponse.body.data);
|
||||||
expect(secondResponse.body.data.data).toBeDefined();
|
expect(secondResponse.body.data.data).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /credentials/:id should retrieve non-owned cred for owner', async () => {
|
test('should retrieve non-owned cred for owner', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
const authOwnerAgent = authAgent(owner);
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
const response1 = await authOwnerAgent.get(`/credentials/${savedCredential.id}`);
|
||||||
|
|
||||||
const response1 = await authOwnerAgent.get(`/credentials/${savedCredential.id}`);
|
expect(response1.statusCode).toBe(200);
|
||||||
|
|
||||||
expect(response1.statusCode).toBe(200);
|
validateMainCredentialData(response1.body.data);
|
||||||
|
expect(response1.body.data.data).toBeUndefined();
|
||||||
|
|
||||||
validateMainCredentialData(response1.body.data);
|
const response2 = await authOwnerAgent
|
||||||
expect(response1.body.data.data).toBeUndefined();
|
.get(`/credentials/${savedCredential.id}`)
|
||||||
|
.query({ includeData: true });
|
||||||
|
|
||||||
const response2 = await authOwnerAgent
|
expect(response2.statusCode).toBe(200);
|
||||||
.get(`/credentials/${savedCredential.id}`)
|
|
||||||
.query({ includeData: true });
|
|
||||||
|
|
||||||
expect(response2.statusCode).toBe(200);
|
validateMainCredentialData(response2.body.data);
|
||||||
|
expect(response2.body.data.data).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
validateMainCredentialData(response2.body.data);
|
test('should not retrieve non-owned cred for member', async () => {
|
||||||
expect(response2.body.data.data).toBeDefined();
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /credentials/:id should not retrieve non-owned cred for member', async () => {
|
const response = await authMemberAgent.get(`/credentials/${savedCredential.id}`);
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
|
||||||
|
|
||||||
const response = await authAgent(member).get(`/credentials/${savedCredential.id}`);
|
expect(response.statusCode).toBe(404);
|
||||||
|
expect(response.body.data).toBeUndefined(); // owner's cred not returned
|
||||||
|
});
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
test('should fail with missing encryption key', async () => {
|
||||||
expect(response.body.data).toBeUndefined(); // owner's cred not returned
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /credentials/:id should fail with missing encryption key', async () => {
|
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
|
||||||
|
|
||||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
const response = await authOwnerAgent
|
||||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
.get(`/credentials/${savedCredential.id}`)
|
||||||
|
.query({ includeData: true });
|
||||||
|
|
||||||
const response = await authAgent(ownerShell)
|
expect(response.statusCode).toBe(500);
|
||||||
.get(`/credentials/${savedCredential.id}`)
|
|
||||||
.query({ includeData: true });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(500);
|
mock.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
mock.mockRestore();
|
test('should return 404 if cred not found', async () => {
|
||||||
});
|
const response = await authOwnerAgent.get('/credentials/789');
|
||||||
|
expect(response.statusCode).toBe(404);
|
||||||
|
|
||||||
test('GET /credentials/:id should return 404 if cred not found', async () => {
|
const responseAbc = await authOwnerAgent.get('/credentials/abc');
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
expect(responseAbc.statusCode).toBe(404);
|
||||||
const response = await authAgent(ownerShell).get('/credentials/789');
|
});
|
||||||
expect(response.statusCode).toBe(404);
|
|
||||||
|
|
||||||
const responseAbc = await authAgent(ownerShell).get('/credentials/abc');
|
|
||||||
expect(responseAbc.statusCode).toBe(404);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function validateMainCredentialData(credential: CredentialsEntity) {
|
function validateMainCredentialData(credential: CredentialsEntity) {
|
||||||
|
|
|
@ -2,6 +2,8 @@ import express from 'express';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import syslog from 'syslog-client';
|
import syslog from 'syslog-client';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import type { SuperAgentTest } from 'supertest';
|
||||||
import * as utils from './shared/utils';
|
import * as utils from './shared/utils';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import { Role } from '@db/entities/Role';
|
import { Role } from '@db/entities/Role';
|
||||||
|
@ -15,14 +17,12 @@ import {
|
||||||
MessageEventBusDestinationWebhookOptions,
|
MessageEventBusDestinationWebhookOptions,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { eventBus } from '@/eventbus';
|
import { eventBus } from '@/eventbus';
|
||||||
import { SuperAgentTest } from 'supertest';
|
|
||||||
import { EventMessageGeneric } from '@/eventbus/EventMessageClasses/EventMessageGeneric';
|
import { EventMessageGeneric } from '@/eventbus/EventMessageClasses/EventMessageGeneric';
|
||||||
import { MessageEventBusDestinationSyslog } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee';
|
import { MessageEventBusDestinationSyslog } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee';
|
||||||
import { MessageEventBusDestinationWebhook } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee';
|
import { MessageEventBusDestinationWebhook } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee';
|
||||||
import { MessageEventBusDestinationSentry } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee';
|
import { MessageEventBusDestinationSentry } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee';
|
||||||
import { EventMessageAudit } from '@/eventbus/EventMessageClasses/EventMessageAudit';
|
import { EventMessageAudit } from '@/eventbus/EventMessageClasses/EventMessageAudit';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { EventNamesTypes } from '@/eventbus/EventMessageClasses';
|
||||||
import { EventNamesTypes } from '../../src/eventbus/EventMessageClasses';
|
|
||||||
|
|
||||||
jest.unmock('@/eventbus/MessageEventBus/MessageEventBus');
|
jest.unmock('@/eventbus/MessageEventBus/MessageEventBus');
|
||||||
jest.mock('axios');
|
jest.mock('axios');
|
||||||
|
@ -54,6 +54,7 @@ const testWebhookDestination: MessageEventBusDestinationWebhookOptions = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
subscribedEvents: ['n8n.test.message', 'n8n.audit.user.updated'],
|
subscribedEvents: ['n8n.test.message', 'n8n.audit.user.updated'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const testSentryDestination: MessageEventBusDestinationSentryOptions = {
|
const testSentryDestination: MessageEventBusDestinationSentryOptions = {
|
||||||
...defaultMessageEventBusDestinationSentryOptions,
|
...defaultMessageEventBusDestinationSentryOptions,
|
||||||
id: '450ca04b-87dd-4837-a052-ab3a347a00e9',
|
id: '450ca04b-87dd-4837-a052-ab3a347a00e9',
|
||||||
|
@ -101,13 +102,10 @@ beforeAll(async () => {
|
||||||
config.set('eventBus.logWriter.logBaseName', 'n8n-test-logwriter');
|
config.set('eventBus.logWriter.logBaseName', 'n8n-test-logwriter');
|
||||||
config.set('eventBus.logWriter.keepLogCount', '1');
|
config.set('eventBus.logWriter.keepLogCount', '1');
|
||||||
config.set('enterprise.features.logStreaming', true);
|
config.set('enterprise.features.logStreaming', true);
|
||||||
|
|
||||||
await eventBus.initialize();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
config.set('userManagement.disabled', false);
|
config.set('userManagement.disabled', false);
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||||
|
|
||||||
|
await eventBus.initialize();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import type { Entry as LdapUser } from 'ldapts';
|
import type { Entry as LdapUser } from 'ldapts';
|
||||||
|
import { Not } from 'typeorm';
|
||||||
import { jsonParse } from 'n8n-workflow';
|
import { jsonParse } from 'n8n-workflow';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { LDAP_DEFAULT_CONFIGURATION, LDAP_ENABLED, LDAP_FEATURE_NAME } from '@/Ldap/constants';
|
import { LDAP_DEFAULT_CONFIGURATION, LDAP_FEATURE_NAME } from '@/Ldap/constants';
|
||||||
import { LdapManager } from '@/Ldap/LdapManager.ee';
|
import { LdapManager } from '@/Ldap/LdapManager.ee';
|
||||||
import { LdapService } from '@/Ldap/LdapService.ee';
|
import { LdapService } from '@/Ldap/LdapService.ee';
|
||||||
import { encryptPassword, saveLdapSynchronization } from '@/Ldap/helpers';
|
import { encryptPassword, saveLdapSynchronization } from '@/Ldap/helpers';
|
||||||
|
@ -21,7 +22,6 @@ jest.mock('@/UserManagement/email/NodeMailer');
|
||||||
|
|
||||||
let app: express.Application;
|
let app: express.Application;
|
||||||
let globalMemberRole: Role;
|
let globalMemberRole: Role;
|
||||||
let globalOwnerRole: Role;
|
|
||||||
let owner: User;
|
let owner: User;
|
||||||
let authAgent: AuthAgent;
|
let authAgent: AuthAgent;
|
||||||
|
|
||||||
|
@ -42,11 +42,12 @@ const defaultLdapConfig = {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({ endpointGroups: ['auth', 'ldap'] });
|
app = await utils.initTestServer({ endpointGroups: ['auth', 'ldap'] });
|
||||||
|
|
||||||
const [fetchedGlobalOwnerRole, fetchedGlobalMemberRole] = await testDb.getAllRoles();
|
const [globalOwnerRole, fetchedGlobalMemberRole] = await testDb.getAllRoles();
|
||||||
|
|
||||||
globalOwnerRole = fetchedGlobalOwnerRole;
|
|
||||||
globalMemberRole = fetchedGlobalMemberRole;
|
globalMemberRole = fetchedGlobalMemberRole;
|
||||||
|
|
||||||
|
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||||
|
|
||||||
authAgent = utils.createAuthAgent(app);
|
authAgent = utils.createAuthAgent(app);
|
||||||
|
|
||||||
defaultLdapConfig.bindingAdminPassword = await encryptPassword(
|
defaultLdapConfig.bindingAdminPassword = await encryptPassword(
|
||||||
|
@ -64,10 +65,9 @@ beforeEach(async () => {
|
||||||
'Credentials',
|
'Credentials',
|
||||||
'SharedWorkflow',
|
'SharedWorkflow',
|
||||||
'Workflow',
|
'Workflow',
|
||||||
'User',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
await Db.collections.User.delete({ id: Not(owner.id) });
|
||||||
|
|
||||||
jest.mock('@/telemetry');
|
jest.mock('@/telemetry');
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,36 @@
|
||||||
import express from 'express';
|
import type { SuperAgentTest } from 'supertest';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { User } from '@db/entities/User';
|
||||||
import * as testDb from './shared/testDb';
|
|
||||||
import type { AuthAgent } from './shared/types';
|
|
||||||
import * as utils from './shared/utils';
|
|
||||||
import { ILicensePostResponse, ILicenseReadResponse } from '@/Interfaces';
|
import { ILicensePostResponse, ILicenseReadResponse } from '@/Interfaces';
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
|
import * as testDb from './shared/testDb';
|
||||||
|
import * as utils from './shared/utils';
|
||||||
|
|
||||||
const MOCK_SERVER_URL = 'https://server.com/v1';
|
const MOCK_SERVER_URL = 'https://server.com/v1';
|
||||||
const MOCK_RENEW_OFFSET = 259200;
|
const MOCK_RENEW_OFFSET = 259200;
|
||||||
const MOCK_INSTANCE_ID = 'instance-id';
|
|
||||||
|
|
||||||
let app: express.Application;
|
let owner: User;
|
||||||
let globalOwnerRole: Role;
|
let member: User;
|
||||||
let globalMemberRole: Role;
|
let authOwnerAgent: SuperAgentTest;
|
||||||
let authAgent: AuthAgent;
|
let authMemberAgent: SuperAgentTest;
|
||||||
let license: License;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({ endpointGroups: ['license'] });
|
const app = await utils.initTestServer({ endpointGroups: ['license'] });
|
||||||
|
|
||||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
const globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
const globalMemberRole = await testDb.getGlobalMemberRole();
|
||||||
|
owner = await testDb.createUserShell(globalOwnerRole);
|
||||||
|
member = await testDb.createUserShell(globalMemberRole);
|
||||||
|
|
||||||
authAgent = utils.createAuthAgent(app);
|
const authAgent = utils.createAuthAgent(app);
|
||||||
|
authOwnerAgent = authAgent(owner);
|
||||||
|
authMemberAgent = authAgent(member);
|
||||||
|
|
||||||
config.set('license.serverUrl', MOCK_SERVER_URL);
|
config.set('license.serverUrl', MOCK_SERVER_URL);
|
||||||
config.set('license.autoRenewEnabled', true);
|
config.set('license.autoRenewEnabled', true);
|
||||||
config.set('license.autoRenewOffset', MOCK_RENEW_OFFSET);
|
config.set('license.autoRenewOffset', MOCK_RENEW_OFFSET);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
license = new License();
|
|
||||||
await license.init(MOCK_INSTANCE_ID);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await testDb.truncate(['Settings']);
|
await testDb.truncate(['Settings']);
|
||||||
});
|
});
|
||||||
|
@ -44,98 +39,66 @@ afterAll(async () => {
|
||||||
await testDb.terminate();
|
await testDb.terminate();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /license should return license information to the instance owner', async () => {
|
describe('GET /license', () => {
|
||||||
const userShell = await testDb.createUserShell(globalOwnerRole);
|
test('should return license information to the instance owner', async () => {
|
||||||
|
// No license defined so we just expect the result to be the defaults
|
||||||
const response = await authAgent(userShell).get('/license');
|
await authOwnerAgent.get('/license').expect(200, DEFAULT_LICENSE_RESPONSE);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
// No license defined so we just expect the result to be the defaults
|
|
||||||
expect(response.body).toStrictEqual(DEFAULT_LICENSE_RESPONSE);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /license should return license information to a regular user', async () => {
|
|
||||||
const userShell = await testDb.createUserShell(globalMemberRole);
|
|
||||||
|
|
||||||
const response = await authAgent(userShell).get('/license');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
// No license defined so we just expect the result to be the defaults
|
|
||||||
expect(response.body).toStrictEqual(DEFAULT_LICENSE_RESPONSE);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /license/activate should work for instance owner', async () => {
|
|
||||||
const userShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const response = await authAgent(userShell)
|
|
||||||
.post('/license/activate')
|
|
||||||
.send({ activationKey: 'abcde' });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
// No license defined so we just expect the result to be the defaults
|
|
||||||
expect(response.body).toMatchObject(DEFAULT_POST_RESPONSE);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /license/activate does not work for regular users', async () => {
|
|
||||||
const userShell = await testDb.createUserShell(globalMemberRole);
|
|
||||||
|
|
||||||
const response = await authAgent(userShell)
|
|
||||||
.post('/license/activate')
|
|
||||||
.send({ activationKey: 'abcde' });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(403);
|
|
||||||
expect(response.body.message).toBe(NON_OWNER_ACTIVATE_RENEW_MESSAGE);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /license/activate errors out properly', async () => {
|
|
||||||
License.prototype.activate = jest.fn().mockImplementation(() => {
|
|
||||||
throw new Error(INVALID_ACIVATION_KEY_MESSAGE);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const userShell = await testDb.createUserShell(globalOwnerRole);
|
test('should return license information to a regular user', async () => {
|
||||||
|
// No license defined so we just expect the result to be the defaults
|
||||||
const response = await authAgent(userShell)
|
await authMemberAgent.get('/license').expect(200, DEFAULT_LICENSE_RESPONSE);
|
||||||
.post('/license/activate')
|
});
|
||||||
.send({ activationKey: 'abcde' });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
expect(response.body.message).toBe(INVALID_ACIVATION_KEY_MESSAGE);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /license/renew should work for instance owner', async () => {
|
describe('POST /license/activate', () => {
|
||||||
const userShell = await testDb.createUserShell(globalOwnerRole);
|
test('should work for instance owner', async () => {
|
||||||
|
await authOwnerAgent
|
||||||
const response = await authAgent(userShell).post('/license/renew');
|
.post('/license/activate')
|
||||||
|
.send({ activationKey: 'abcde' })
|
||||||
expect(response.statusCode).toBe(200);
|
.expect(200, DEFAULT_POST_RESPONSE);
|
||||||
|
|
||||||
// No license defined so we just expect the result to be the defaults
|
|
||||||
expect(response.body).toMatchObject(DEFAULT_POST_RESPONSE);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /license/renew does not work for regular users', async () => {
|
|
||||||
const userShell = await testDb.createUserShell(globalMemberRole);
|
|
||||||
|
|
||||||
const response = await authAgent(userShell).post('/license/renew');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(403);
|
|
||||||
expect(response.body.message).toBe(NON_OWNER_ACTIVATE_RENEW_MESSAGE);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /license/renew errors out properly', async () => {
|
|
||||||
License.prototype.renew = jest.fn().mockImplementation(() => {
|
|
||||||
throw new Error(RENEW_ERROR_MESSAGE);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const userShell = await testDb.createUserShell(globalOwnerRole);
|
test('does not work for regular users', async () => {
|
||||||
|
await authMemberAgent
|
||||||
|
.post('/license/activate')
|
||||||
|
.send({ activationKey: 'abcde' })
|
||||||
|
.expect(403, { code: 403, message: NON_OWNER_ACTIVATE_RENEW_MESSAGE });
|
||||||
|
});
|
||||||
|
|
||||||
const response = await authAgent(userShell).post('/license/renew');
|
test('errors out properly', async () => {
|
||||||
|
License.prototype.activate = jest.fn().mockImplementation(() => {
|
||||||
|
throw new Error(INVALID_ACIVATION_KEY_MESSAGE);
|
||||||
|
});
|
||||||
|
|
||||||
expect(response.statusCode).toBe(400);
|
await authOwnerAgent
|
||||||
expect(response.body.message).toBe(RENEW_ERROR_MESSAGE);
|
.post('/license/activate')
|
||||||
|
.send({ activationKey: 'abcde' })
|
||||||
|
.expect(400, { code: 400, message: INVALID_ACIVATION_KEY_MESSAGE });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /license/renew', () => {
|
||||||
|
test('should work for instance owner', async () => {
|
||||||
|
// No license defined so we just expect the result to be the defaults
|
||||||
|
await authOwnerAgent.post('/license/renew').expect(200, DEFAULT_POST_RESPONSE);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not work for regular users', async () => {
|
||||||
|
await authMemberAgent
|
||||||
|
.post('/license/renew')
|
||||||
|
.expect(403, { code: 403, message: NON_OWNER_ACTIVATE_RENEW_MESSAGE });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('errors out properly', async () => {
|
||||||
|
License.prototype.renew = jest.fn().mockImplementation(() => {
|
||||||
|
throw new Error(RENEW_ERROR_MESSAGE);
|
||||||
|
});
|
||||||
|
|
||||||
|
await authOwnerAgent
|
||||||
|
.post('/license/renew')
|
||||||
|
.expect(400, { code: 400, message: RENEW_ERROR_MESSAGE });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const DEFAULT_LICENSE_RESPONSE: { data: ILicenseReadResponse } = {
|
const DEFAULT_LICENSE_RESPONSE: { data: ILicenseReadResponse } = {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import express from 'express';
|
import type { Application } from 'express';
|
||||||
|
import type { SuperAgentTest } from 'supertest';
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
|
|
||||||
import config from '@/config';
|
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
|
import type { User } from '@db/entities/User';
|
||||||
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
||||||
import {
|
import {
|
||||||
randomApiKey,
|
randomApiKey,
|
||||||
|
@ -17,7 +17,7 @@ import * as testDb from './shared/testDb';
|
||||||
import type { AuthAgent } from './shared/types';
|
import type { AuthAgent } from './shared/types';
|
||||||
import * as utils from './shared/utils';
|
import * as utils from './shared/utils';
|
||||||
|
|
||||||
let app: express.Application;
|
let app: Application;
|
||||||
let globalOwnerRole: Role;
|
let globalOwnerRole: Role;
|
||||||
let globalMemberRole: Role;
|
let globalMemberRole: Role;
|
||||||
let authAgent: AuthAgent;
|
let authAgent: AuthAgent;
|
||||||
|
@ -40,10 +40,16 @@ afterAll(async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Owner shell', () => {
|
describe('Owner shell', () => {
|
||||||
test('PATCH /me should succeed with valid inputs', async () => {
|
let ownerShell: User;
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
let authOwnerShellAgent: SuperAgentTest;
|
||||||
const authOwnerShellAgent = authAgent(ownerShell);
|
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||||
|
await testDb.addApiKey(ownerShell);
|
||||||
|
authOwnerShellAgent = authAgent(ownerShell);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('PATCH /me should succeed with valid inputs', async () => {
|
||||||
for (const validPayload of VALID_PATCH_ME_PAYLOADS) {
|
for (const validPayload of VALID_PATCH_ME_PAYLOADS) {
|
||||||
const response = await authOwnerShellAgent.patch('/me').send(validPayload);
|
const response = await authOwnerShellAgent.patch('/me').send(validPayload);
|
||||||
|
|
||||||
|
@ -83,9 +89,6 @@ describe('Owner shell', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PATCH /me should fail with invalid inputs', async () => {
|
test('PATCH /me should fail with invalid inputs', async () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const authOwnerShellAgent = authAgent(ownerShell);
|
|
||||||
|
|
||||||
for (const invalidPayload of INVALID_PATCH_ME_PAYLOADS) {
|
for (const invalidPayload of INVALID_PATCH_ME_PAYLOADS) {
|
||||||
const response = await authOwnerShellAgent.patch('/me').send(invalidPayload);
|
const response = await authOwnerShellAgent.patch('/me').send(invalidPayload);
|
||||||
expect(response.statusCode).toBe(400);
|
expect(response.statusCode).toBe(400);
|
||||||
|
@ -98,9 +101,6 @@ describe('Owner shell', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PATCH /me/password should fail for shell', async () => {
|
test('PATCH /me/password should fail for shell', async () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const authOwnerShellAgent = authAgent(ownerShell);
|
|
||||||
|
|
||||||
const validPasswordPayload = {
|
const validPasswordPayload = {
|
||||||
currentPassword: randomValidPassword(),
|
currentPassword: randomValidPassword(),
|
||||||
newPassword: randomValidPassword(),
|
newPassword: randomValidPassword(),
|
||||||
|
@ -130,9 +130,6 @@ describe('Owner shell', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /me/survey should succeed with valid inputs', async () => {
|
test('POST /me/survey should succeed with valid inputs', async () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const authOwnerShellAgent = authAgent(ownerShell);
|
|
||||||
|
|
||||||
const validPayloads = [SURVEY, {}];
|
const validPayloads = [SURVEY, {}];
|
||||||
|
|
||||||
for (const validPayload of validPayloads) {
|
for (const validPayload of validPayloads) {
|
||||||
|
@ -150,9 +147,7 @@ describe('Owner shell', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /me/api-key should create an api key', async () => {
|
test('POST /me/api-key should create an api key', async () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
const response = await authOwnerShellAgent.post('/me/api-key');
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).post('/me/api-key');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.body.data.apiKey).toBeDefined();
|
expect(response.body.data.apiKey).toBeDefined();
|
||||||
|
@ -166,20 +161,14 @@ describe('Owner shell', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /me/api-key should fetch the api key', async () => {
|
test('GET /me/api-key should fetch the api key', async () => {
|
||||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
const response = await authOwnerShellAgent.get('/me/api-key');
|
||||||
ownerShell = await testDb.addApiKey(ownerShell);
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).get('/me/api-key');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.body.data.apiKey).toEqual(ownerShell.apiKey);
|
expect(response.body.data.apiKey).toEqual(ownerShell.apiKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('DELETE /me/api-key should fetch the api key', async () => {
|
test('DELETE /me/api-key should fetch the api key', async () => {
|
||||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
const response = await authOwnerShellAgent.delete('/me/api-key');
|
||||||
ownerShell = await testDb.addApiKey(ownerShell);
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).delete('/me/api-key');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
@ -192,19 +181,22 @@ describe('Owner shell', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Member', () => {
|
describe('Member', () => {
|
||||||
beforeEach(async () => {
|
const memberPassword = randomValidPassword();
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
let member: User;
|
||||||
|
let authMemberAgent: SuperAgentTest;
|
||||||
|
|
||||||
await Db.collections.Settings.update(
|
beforeEach(async () => {
|
||||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
member = await testDb.createUser({
|
||||||
{ value: JSON.stringify(true) },
|
password: memberPassword,
|
||||||
);
|
globalRole: globalMemberRole,
|
||||||
|
apiKey: randomApiKey(),
|
||||||
|
});
|
||||||
|
authMemberAgent = authAgent(member);
|
||||||
|
|
||||||
|
await utils.setInstanceOwnerSetUp(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PATCH /me should succeed with valid inputs', async () => {
|
test('PATCH /me should succeed with valid inputs', async () => {
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const authMemberAgent = authAgent(member);
|
|
||||||
|
|
||||||
for (const validPayload of VALID_PATCH_ME_PAYLOADS) {
|
for (const validPayload of VALID_PATCH_ME_PAYLOADS) {
|
||||||
const response = await authMemberAgent.patch('/me').send(validPayload);
|
const response = await authMemberAgent.patch('/me').send(validPayload);
|
||||||
|
|
||||||
|
@ -244,9 +236,6 @@ describe('Member', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PATCH /me should fail with invalid inputs', async () => {
|
test('PATCH /me should fail with invalid inputs', async () => {
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const authMemberAgent = authAgent(member);
|
|
||||||
|
|
||||||
for (const invalidPayload of INVALID_PATCH_ME_PAYLOADS) {
|
for (const invalidPayload of INVALID_PATCH_ME_PAYLOADS) {
|
||||||
const response = await authMemberAgent.patch('/me').send(invalidPayload);
|
const response = await authMemberAgent.patch('/me').send(invalidPayload);
|
||||||
expect(response.statusCode).toBe(400);
|
expect(response.statusCode).toBe(400);
|
||||||
|
@ -259,18 +248,12 @@ describe('Member', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PATCH /me/password should succeed with valid inputs', async () => {
|
test('PATCH /me/password should succeed with valid inputs', async () => {
|
||||||
const memberPassword = randomValidPassword();
|
|
||||||
const member = await testDb.createUser({
|
|
||||||
password: memberPassword,
|
|
||||||
globalRole: globalMemberRole,
|
|
||||||
});
|
|
||||||
|
|
||||||
const validPayload = {
|
const validPayload = {
|
||||||
currentPassword: memberPassword,
|
currentPassword: memberPassword,
|
||||||
newPassword: randomValidPassword(),
|
newPassword: randomValidPassword(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await authAgent(member).patch('/me/password').send(validPayload);
|
const response = await authMemberAgent.patch('/me/password').send(validPayload);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.body).toEqual(SUCCESS_RESPONSE_BODY);
|
expect(response.body).toEqual(SUCCESS_RESPONSE_BODY);
|
||||||
|
|
||||||
|
@ -280,9 +263,6 @@ describe('Member', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PATCH /me/password should fail with invalid inputs', async () => {
|
test('PATCH /me/password should fail with invalid inputs', async () => {
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const authMemberAgent = authAgent(member);
|
|
||||||
|
|
||||||
for (const payload of INVALID_PASSWORD_PAYLOADS) {
|
for (const payload of INVALID_PASSWORD_PAYLOADS) {
|
||||||
const response = await authMemberAgent.patch('/me/password').send(payload);
|
const response = await authMemberAgent.patch('/me/password').send(payload);
|
||||||
expect([400, 500].includes(response.statusCode)).toBe(true);
|
expect([400, 500].includes(response.statusCode)).toBe(true);
|
||||||
|
@ -299,9 +279,6 @@ describe('Member', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /me/survey should succeed with valid inputs', async () => {
|
test('POST /me/survey should succeed with valid inputs', async () => {
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const authMemberAgent = authAgent(member);
|
|
||||||
|
|
||||||
const validPayloads = [SURVEY, {}];
|
const validPayloads = [SURVEY, {}];
|
||||||
|
|
||||||
for (const validPayload of validPayloads) {
|
for (const validPayload of validPayloads) {
|
||||||
|
@ -318,11 +295,6 @@ describe('Member', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /me/api-key should create an api key', async () => {
|
test('POST /me/api-key should create an api key', async () => {
|
||||||
const member = await testDb.createUser({
|
|
||||||
globalRole: globalMemberRole,
|
|
||||||
apiKey: randomApiKey(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await authAgent(member).post('/me/api-key');
|
const response = await authAgent(member).post('/me/api-key');
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
|
@ -335,11 +307,6 @@ describe('Member', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /me/api-key should fetch the api key', async () => {
|
test('GET /me/api-key should fetch the api key', async () => {
|
||||||
const member = await testDb.createUser({
|
|
||||||
globalRole: globalMemberRole,
|
|
||||||
apiKey: randomApiKey(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await authAgent(member).get('/me/api-key');
|
const response = await authAgent(member).get('/me/api-key');
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
|
@ -347,11 +314,6 @@ describe('Member', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('DELETE /me/api-key should fetch the api key', async () => {
|
test('DELETE /me/api-key should fetch the api key', async () => {
|
||||||
const member = await testDb.createUser({
|
|
||||||
globalRole: globalMemberRole,
|
|
||||||
apiKey: randomApiKey(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await authAgent(member).delete('/me/api-key');
|
const response = await authAgent(member).delete('/me/api-key');
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
|
@ -364,7 +326,7 @@ describe('Member', () => {
|
||||||
|
|
||||||
describe('Owner', () => {
|
describe('Owner', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
await utils.setInstanceOwnerSetUp(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PATCH /me should succeed with valid inputs', async () => {
|
test('PATCH /me should succeed with valid inputs', async () => {
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import express from 'express';
|
|
||||||
import { mocked } from 'jest-mock';
|
import { mocked } from 'jest-mock';
|
||||||
|
import type { SuperAgentTest } from 'supertest';
|
||||||
import * as utils from './shared/utils';
|
|
||||||
import * as testDb from './shared/testDb';
|
|
||||||
import {
|
import {
|
||||||
executeCommand,
|
executeCommand,
|
||||||
checkNpmPackageStatus,
|
checkNpmPackageStatus,
|
||||||
|
@ -15,13 +11,13 @@ import {
|
||||||
import { findInstalledPackage, isPackageInstalled } from '@/CommunityNodes/packageModel';
|
import { findInstalledPackage, isPackageInstalled } from '@/CommunityNodes/packageModel';
|
||||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||||
import { InstalledPackages } from '@db/entities/InstalledPackages';
|
import { InstalledPackages } from '@db/entities/InstalledPackages';
|
||||||
|
import type { User } from '@db/entities/User';
|
||||||
import type { Role } from '@db/entities/Role';
|
|
||||||
import type { AuthAgent } from './shared/types';
|
|
||||||
import type { InstalledNodes } from '@db/entities/InstalledNodes';
|
import type { InstalledNodes } from '@db/entities/InstalledNodes';
|
||||||
import { COMMUNITY_PACKAGE_VERSION } from './shared/constants';
|
|
||||||
import { NodeTypes } from '@/NodeTypes';
|
import { NodeTypes } from '@/NodeTypes';
|
||||||
import { Push } from '@/push';
|
import { Push } from '@/push';
|
||||||
|
import { COMMUNITY_PACKAGE_VERSION } from './shared/constants';
|
||||||
|
import * as utils from './shared/utils';
|
||||||
|
import * as testDb from './shared/testDb';
|
||||||
|
|
||||||
const mockLoadNodesAndCredentials = utils.mockInstance(LoadNodesAndCredentials);
|
const mockLoadNodesAndCredentials = utils.mockInstance(LoadNodesAndCredentials);
|
||||||
utils.mockInstance(NodeTypes);
|
utils.mockInstance(NodeTypes);
|
||||||
|
@ -48,22 +44,21 @@ jest.mock('@/CommunityNodes/packageModel', () => {
|
||||||
|
|
||||||
const mockedEmptyPackage = mocked(utils.emptyPackage);
|
const mockedEmptyPackage = mocked(utils.emptyPackage);
|
||||||
|
|
||||||
let app: express.Application;
|
let ownerShell: User;
|
||||||
let globalOwnerRole: Role;
|
let authOwnerShellAgent: SuperAgentTest;
|
||||||
let authAgent: AuthAgent;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({ endpointGroups: ['nodes'] });
|
const app = await utils.initTestServer({ endpointGroups: ['nodes'] });
|
||||||
|
|
||||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
const globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||||
|
ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||||
authAgent = utils.createAuthAgent(app);
|
authOwnerShellAgent = utils.createAuthAgent(app)(ownerShell);
|
||||||
|
|
||||||
utils.initConfigFile();
|
utils.initConfigFile();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await testDb.truncate(['InstalledNodes', 'InstalledPackages', 'User']);
|
await testDb.truncate(['InstalledNodes', 'InstalledPackages']);
|
||||||
|
|
||||||
mocked(executeCommand).mockReset();
|
mocked(executeCommand).mockReset();
|
||||||
mocked(findInstalledPackage).mockReset();
|
mocked(findInstalledPackage).mockReset();
|
||||||
|
@ -73,255 +68,216 @@ afterAll(async () => {
|
||||||
await testDb.terminate();
|
await testDb.terminate();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
describe('GET /nodes', () => {
|
||||||
* GET /nodes
|
test('should respond 200 if no nodes are installed', async () => {
|
||||||
*/
|
const {
|
||||||
|
statusCode,
|
||||||
|
body: { data },
|
||||||
|
} = await authOwnerShellAgent.get('/nodes');
|
||||||
|
|
||||||
test('GET /nodes should respond 200 if no nodes are installed', async () => {
|
expect(statusCode).toBe(200);
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
expect(data).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
test('should return list of one installed package and node', async () => {
|
||||||
statusCode,
|
const { packageName } = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||||
body: { data },
|
await testDb.saveInstalledNode(utils.installedNodePayload(packageName));
|
||||||
} = await authAgent(ownerShell).get('/nodes');
|
|
||||||
|
|
||||||
expect(statusCode).toBe(200);
|
const {
|
||||||
expect(data).toHaveLength(0);
|
statusCode,
|
||||||
});
|
body: { data },
|
||||||
|
} = await authOwnerShellAgent.get('/nodes');
|
||||||
|
|
||||||
test('GET /nodes should return list of one installed package and node', async () => {
|
expect(statusCode).toBe(200);
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
expect(data).toHaveLength(1);
|
||||||
|
expect(data[0].installedNodes).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
const { packageName } = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
test('should return list of multiple installed packages and nodes', async () => {
|
||||||
await testDb.saveInstalledNode(utils.installedNodePayload(packageName));
|
const first = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||||
|
await testDb.saveInstalledNode(utils.installedNodePayload(first.packageName));
|
||||||
|
|
||||||
const {
|
const second = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||||
statusCode,
|
await testDb.saveInstalledNode(utils.installedNodePayload(second.packageName));
|
||||||
body: { data },
|
await testDb.saveInstalledNode(utils.installedNodePayload(second.packageName));
|
||||||
} = await authAgent(ownerShell).get('/nodes');
|
|
||||||
|
|
||||||
expect(statusCode).toBe(200);
|
const {
|
||||||
expect(data).toHaveLength(1);
|
statusCode,
|
||||||
expect(data[0].installedNodes).toHaveLength(1);
|
body: { data },
|
||||||
});
|
} = await authOwnerShellAgent.get('/nodes');
|
||||||
|
|
||||||
test('GET /nodes should return list of multiple installed packages and nodes', async () => {
|
expect(statusCode).toBe(200);
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
expect(data).toHaveLength(2);
|
||||||
|
|
||||||
const first = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
const allNodes = data.reduce(
|
||||||
await testDb.saveInstalledNode(utils.installedNodePayload(first.packageName));
|
(acc: InstalledNodes[], cur: InstalledPackages) => acc.concat(cur.installedNodes),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const second = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
expect(allNodes).toHaveLength(3);
|
||||||
await testDb.saveInstalledNode(utils.installedNodePayload(second.packageName));
|
});
|
||||||
await testDb.saveInstalledNode(utils.installedNodePayload(second.packageName));
|
|
||||||
|
|
||||||
const {
|
test('should not check for updates if no packages installed', async () => {
|
||||||
statusCode,
|
await authOwnerShellAgent.get('/nodes');
|
||||||
body: { data },
|
|
||||||
} = await authAgent(ownerShell).get('/nodes');
|
|
||||||
|
|
||||||
expect(statusCode).toBe(200);
|
expect(mocked(executeCommand)).toHaveBeenCalledTimes(0);
|
||||||
expect(data).toHaveLength(2);
|
});
|
||||||
|
|
||||||
const allNodes = data.reduce(
|
test('should check for updates if packages installed', async () => {
|
||||||
(acc: InstalledNodes[], cur: InstalledPackages) => acc.concat(cur.installedNodes),
|
const { packageName } = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||||
[],
|
await testDb.saveInstalledNode(utils.installedNodePayload(packageName));
|
||||||
);
|
|
||||||
|
|
||||||
expect(allNodes).toHaveLength(3);
|
await authOwnerShellAgent.get('/nodes');
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /nodes should not check for updates if no packages installed', async () => {
|
expect(mocked(executeCommand)).toHaveBeenCalledWith('npm outdated --json', {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
doNotHandleError: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await authAgent(ownerShell).get('/nodes');
|
test('should report package updates if available', async () => {
|
||||||
|
const { packageName } = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||||
|
await testDb.saveInstalledNode(utils.installedNodePayload(packageName));
|
||||||
|
|
||||||
expect(mocked(executeCommand)).toHaveBeenCalledTimes(0);
|
mocked(executeCommand).mockImplementationOnce(() => {
|
||||||
});
|
throw {
|
||||||
|
code: 1,
|
||||||
|
stdout: JSON.stringify({
|
||||||
|
[packageName]: {
|
||||||
|
current: COMMUNITY_PACKAGE_VERSION.CURRENT,
|
||||||
|
wanted: COMMUNITY_PACKAGE_VERSION.CURRENT,
|
||||||
|
latest: COMMUNITY_PACKAGE_VERSION.UPDATED,
|
||||||
|
location: path.join('node_modules', packageName),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
test('GET /nodes should check for updates if packages installed', async () => {
|
mocked(isNpmError).mockReturnValueOnce(true);
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const { packageName } = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
const {
|
||||||
await testDb.saveInstalledNode(utils.installedNodePayload(packageName));
|
body: { data },
|
||||||
|
} = await authOwnerShellAgent.get('/nodes');
|
||||||
|
|
||||||
await authAgent(ownerShell).get('/nodes');
|
expect(data[0].installedVersion).toBe(COMMUNITY_PACKAGE_VERSION.CURRENT);
|
||||||
|
expect(data[0].updateAvailable).toBe(COMMUNITY_PACKAGE_VERSION.UPDATED);
|
||||||
expect(mocked(executeCommand)).toHaveBeenCalledWith('npm outdated --json', {
|
|
||||||
doNotHandleError: true,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /nodes should report package updates if available', async () => {
|
describe('POST /nodes', () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
test('should reject if package name is missing', async () => {
|
||||||
|
const { statusCode } = await authOwnerShellAgent.post('/nodes');
|
||||||
|
|
||||||
const { packageName } = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
expect(statusCode).toBe(400);
|
||||||
await testDb.saveInstalledNode(utils.installedNodePayload(packageName));
|
|
||||||
|
|
||||||
mocked(executeCommand).mockImplementationOnce(() => {
|
|
||||||
throw {
|
|
||||||
code: 1,
|
|
||||||
stdout: JSON.stringify({
|
|
||||||
[packageName]: {
|
|
||||||
current: COMMUNITY_PACKAGE_VERSION.CURRENT,
|
|
||||||
wanted: COMMUNITY_PACKAGE_VERSION.CURRENT,
|
|
||||||
latest: COMMUNITY_PACKAGE_VERSION.UPDATED,
|
|
||||||
location: path.join('node_modules', packageName),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
mocked(isNpmError).mockReturnValueOnce(true);
|
test('should reject if package is duplicate', async () => {
|
||||||
|
mocked(findInstalledPackage).mockResolvedValueOnce(new InstalledPackages());
|
||||||
|
mocked(isPackageInstalled).mockResolvedValueOnce(true);
|
||||||
|
mocked(hasPackageLoaded).mockReturnValueOnce(true);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
body: { data },
|
statusCode,
|
||||||
} = await authAgent(ownerShell).get('/nodes');
|
body: { message },
|
||||||
|
} = await authOwnerShellAgent.post('/nodes').send({
|
||||||
|
name: utils.installedPackagePayload().packageName,
|
||||||
|
});
|
||||||
|
|
||||||
expect(data[0].installedVersion).toBe(COMMUNITY_PACKAGE_VERSION.CURRENT);
|
expect(statusCode).toBe(400);
|
||||||
expect(data[0].updateAvailable).toBe(COMMUNITY_PACKAGE_VERSION.UPDATED);
|
expect(message).toContain('already installed');
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /nodes
|
|
||||||
*/
|
|
||||||
|
|
||||||
test('POST /nodes should reject if package name is missing', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const { statusCode } = await authAgent(ownerShell).post('/nodes');
|
|
||||||
|
|
||||||
expect(statusCode).toBe(400);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /nodes should reject if package is duplicate', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
mocked(findInstalledPackage).mockResolvedValueOnce(new InstalledPackages());
|
|
||||||
mocked(isPackageInstalled).mockResolvedValueOnce(true);
|
|
||||||
mocked(hasPackageLoaded).mockReturnValueOnce(true);
|
|
||||||
|
|
||||||
const {
|
|
||||||
statusCode,
|
|
||||||
body: { message },
|
|
||||||
} = await authAgent(ownerShell).post('/nodes').send({
|
|
||||||
name: utils.installedPackagePayload().packageName,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(statusCode).toBe(400);
|
test('should allow installing packages that could not be loaded', async () => {
|
||||||
expect(message).toContain('already installed');
|
mocked(findInstalledPackage).mockResolvedValueOnce(new InstalledPackages());
|
||||||
});
|
mocked(hasPackageLoaded).mockReturnValueOnce(false);
|
||||||
|
mocked(checkNpmPackageStatus).mockResolvedValueOnce({ status: 'OK' });
|
||||||
|
|
||||||
test('POST /nodes should allow installing packages that could not be loaded', async () => {
|
mockLoadNodesAndCredentials.loadNpmModule.mockImplementationOnce(mockedEmptyPackage);
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
mocked(findInstalledPackage).mockResolvedValueOnce(new InstalledPackages());
|
const { statusCode } = await authOwnerShellAgent.post('/nodes').send({
|
||||||
mocked(hasPackageLoaded).mockReturnValueOnce(false);
|
name: utils.installedPackagePayload().packageName,
|
||||||
mocked(checkNpmPackageStatus).mockResolvedValueOnce({ status: 'OK' });
|
});
|
||||||
|
|
||||||
mockLoadNodesAndCredentials.loadNpmModule.mockImplementationOnce(mockedEmptyPackage);
|
expect(statusCode).toBe(200);
|
||||||
|
expect(mocked(removePackageFromMissingList)).toHaveBeenCalled();
|
||||||
const { statusCode } = await authAgent(ownerShell).post('/nodes').send({
|
|
||||||
name: utils.installedPackagePayload().packageName,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(statusCode).toBe(200);
|
test('should not install a banned package', async () => {
|
||||||
expect(mocked(removePackageFromMissingList)).toHaveBeenCalled();
|
mocked(checkNpmPackageStatus).mockResolvedValueOnce({ status: 'Banned' });
|
||||||
|
|
||||||
|
const {
|
||||||
|
statusCode,
|
||||||
|
body: { message },
|
||||||
|
} = await authOwnerShellAgent.post('/nodes').send({
|
||||||
|
name: utils.installedPackagePayload().packageName,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(statusCode).toBe(400);
|
||||||
|
expect(message).toContain('banned');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /nodes should not install a banned package', async () => {
|
describe('DELETE /nodes', () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
test('should not delete if package name is empty', async () => {
|
||||||
mocked(checkNpmPackageStatus).mockResolvedValueOnce({ status: 'Banned' });
|
const response = await authOwnerShellAgent.delete('/nodes');
|
||||||
|
|
||||||
const {
|
expect(response.statusCode).toBe(400);
|
||||||
statusCode,
|
|
||||||
body: { message },
|
|
||||||
} = await authAgent(ownerShell).post('/nodes').send({
|
|
||||||
name: utils.installedPackagePayload().packageName,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(statusCode).toBe(400);
|
test('should reject if package is not installed', async () => {
|
||||||
expect(message).toContain('banned');
|
const {
|
||||||
});
|
statusCode,
|
||||||
|
body: { message },
|
||||||
|
} = await authOwnerShellAgent.delete('/nodes').query({
|
||||||
|
name: utils.installedPackagePayload().packageName,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
expect(statusCode).toBe(400);
|
||||||
* DELETE /nodes
|
expect(message).toContain('not installed');
|
||||||
*/
|
|
||||||
|
|
||||||
test('DELETE /nodes should not delete if package name is empty', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).delete('/nodes');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /nodes should reject if package is not installed', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const {
|
|
||||||
statusCode,
|
|
||||||
body: { message },
|
|
||||||
} = await authAgent(ownerShell).delete('/nodes').query({
|
|
||||||
name: utils.installedPackagePayload().packageName,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(statusCode).toBe(400);
|
test('should uninstall package', async () => {
|
||||||
expect(message).toContain('not installed');
|
const removeSpy = mockLoadNodesAndCredentials.removeNpmModule.mockImplementationOnce(jest.fn());
|
||||||
|
|
||||||
|
mocked(findInstalledPackage).mockImplementationOnce(mockedEmptyPackage);
|
||||||
|
|
||||||
|
const { statusCode } = await authOwnerShellAgent.delete('/nodes').query({
|
||||||
|
name: utils.installedPackagePayload().packageName,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(statusCode).toBe(200);
|
||||||
|
expect(removeSpy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('DELETE /nodes should uninstall package', async () => {
|
describe('PATCH /nodes', () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
test('should reject if package name is empty', async () => {
|
||||||
|
const response = await authOwnerShellAgent.patch('/nodes');
|
||||||
|
|
||||||
const removeSpy = mockLoadNodesAndCredentials.removeNpmModule.mockImplementationOnce(jest.fn());
|
expect(response.statusCode).toBe(400);
|
||||||
|
|
||||||
mocked(findInstalledPackage).mockImplementationOnce(mockedEmptyPackage);
|
|
||||||
|
|
||||||
const { statusCode } = await authAgent(ownerShell).delete('/nodes').query({
|
|
||||||
name: utils.installedPackagePayload().packageName,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(statusCode).toBe(200);
|
test('reject if package is not installed', async () => {
|
||||||
expect(removeSpy).toHaveBeenCalledTimes(1);
|
const {
|
||||||
});
|
statusCode,
|
||||||
|
body: { message },
|
||||||
|
} = await authOwnerShellAgent.patch('/nodes').send({
|
||||||
|
name: utils.installedPackagePayload().packageName,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
expect(statusCode).toBe(400);
|
||||||
* PATCH /nodes
|
expect(message).toContain('not installed');
|
||||||
*/
|
|
||||||
|
|
||||||
test('PATCH /nodes should reject if package name is empty', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).patch('/nodes');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PATCH /nodes reject if package is not installed', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const {
|
|
||||||
statusCode,
|
|
||||||
body: { message },
|
|
||||||
} = await authAgent(ownerShell).patch('/nodes').send({
|
|
||||||
name: utils.installedPackagePayload().packageName,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(statusCode).toBe(400);
|
test('should update a package', async () => {
|
||||||
expect(message).toContain('not installed');
|
const updateSpy =
|
||||||
});
|
mockLoadNodesAndCredentials.updateNpmModule.mockImplementationOnce(mockedEmptyPackage);
|
||||||
|
|
||||||
test('PATCH /nodes should update a package', async () => {
|
mocked(findInstalledPackage).mockImplementationOnce(mockedEmptyPackage);
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const updateSpy =
|
await authOwnerShellAgent.patch('/nodes').send({
|
||||||
mockLoadNodesAndCredentials.updateNpmModule.mockImplementationOnce(mockedEmptyPackage);
|
name: utils.installedPackagePayload().packageName,
|
||||||
|
});
|
||||||
|
|
||||||
mocked(findInstalledPackage).mockImplementationOnce(mockedEmptyPackage);
|
expect(updateSpy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
await authAgent(ownerShell).patch('/nodes').send({
|
|
||||||
name: utils.installedPackagePayload().packageName,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(updateSpy).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import express from 'express';
|
import type { Application } from 'express';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
|
import type { SuperAgentTest } from 'supertest';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
|
import type { User } from '@db/entities/User';
|
||||||
import {
|
import {
|
||||||
randomEmail,
|
randomEmail,
|
||||||
randomInvalidPassword,
|
randomInvalidPassword,
|
||||||
|
@ -11,23 +13,22 @@ import {
|
||||||
randomValidPassword,
|
randomValidPassword,
|
||||||
} from './shared/random';
|
} from './shared/random';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import type { AuthAgent } from './shared/types';
|
|
||||||
import * as utils from './shared/utils';
|
import * as utils from './shared/utils';
|
||||||
|
|
||||||
let app: express.Application;
|
let app: Application;
|
||||||
let globalOwnerRole: Role;
|
let globalOwnerRole: Role;
|
||||||
let authAgent: AuthAgent;
|
let ownerShell: User;
|
||||||
|
let authOwnerShellAgent: SuperAgentTest;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({ endpointGroups: ['owner'] });
|
app = await utils.initTestServer({ endpointGroups: ['owner'] });
|
||||||
|
|
||||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||||
|
|
||||||
authAgent = utils.createAuthAgent(app);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
config.set('userManagement.isInstanceOwnerSetUp', false);
|
||||||
|
ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||||
|
authOwnerShellAgent = utils.createAuthAgent(app)(ownerShell);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
@ -38,152 +39,149 @@ afterAll(async () => {
|
||||||
await testDb.terminate();
|
await testDb.terminate();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /owner/setup should create owner and enable isInstanceOwnerSetUp', async () => {
|
describe('POST /owner/setup', () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
test('should create owner and enable isInstanceOwnerSetUp', async () => {
|
||||||
|
const newOwnerData = {
|
||||||
|
email: randomEmail(),
|
||||||
|
firstName: randomName(),
|
||||||
|
lastName: randomName(),
|
||||||
|
password: randomValidPassword(),
|
||||||
|
};
|
||||||
|
|
||||||
const newOwnerData = {
|
const response = await authOwnerShellAgent.post('/owner/setup').send(newOwnerData);
|
||||||
email: randomEmail(),
|
|
||||||
firstName: randomName(),
|
|
||||||
lastName: randomName(),
|
|
||||||
password: randomValidPassword(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).post('/owner/setup').send(newOwnerData);
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
const {
|
||||||
|
id,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
personalizationAnswers,
|
||||||
|
globalRole,
|
||||||
|
password,
|
||||||
|
resetPasswordToken,
|
||||||
|
isPending,
|
||||||
|
apiKey,
|
||||||
|
} = response.body.data;
|
||||||
|
|
||||||
const {
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
id,
|
expect(email).toBe(newOwnerData.email);
|
||||||
email,
|
expect(firstName).toBe(newOwnerData.firstName);
|
||||||
firstName,
|
expect(lastName).toBe(newOwnerData.lastName);
|
||||||
lastName,
|
expect(personalizationAnswers).toBeNull();
|
||||||
personalizationAnswers,
|
expect(password).toBeUndefined();
|
||||||
globalRole,
|
expect(isPending).toBe(false);
|
||||||
password,
|
expect(resetPasswordToken).toBeUndefined();
|
||||||
resetPasswordToken,
|
expect(globalRole.name).toBe('owner');
|
||||||
isPending,
|
expect(globalRole.scope).toBe('global');
|
||||||
apiKey,
|
expect(apiKey).toBeUndefined();
|
||||||
} = response.body.data;
|
|
||||||
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
const storedOwner = await Db.collections.User.findOneByOrFail({ id });
|
||||||
expect(email).toBe(newOwnerData.email);
|
expect(storedOwner.password).not.toBe(newOwnerData.password);
|
||||||
expect(firstName).toBe(newOwnerData.firstName);
|
expect(storedOwner.email).toBe(newOwnerData.email);
|
||||||
expect(lastName).toBe(newOwnerData.lastName);
|
expect(storedOwner.firstName).toBe(newOwnerData.firstName);
|
||||||
expect(personalizationAnswers).toBeNull();
|
expect(storedOwner.lastName).toBe(newOwnerData.lastName);
|
||||||
expect(password).toBeUndefined();
|
|
||||||
expect(isPending).toBe(false);
|
|
||||||
expect(resetPasswordToken).toBeUndefined();
|
|
||||||
expect(globalRole.name).toBe('owner');
|
|
||||||
expect(globalRole.scope).toBe('global');
|
|
||||||
expect(apiKey).toBeUndefined();
|
|
||||||
|
|
||||||
const storedOwner = await Db.collections.User.findOneByOrFail({ id });
|
const isInstanceOwnerSetUpConfig = config.getEnv('userManagement.isInstanceOwnerSetUp');
|
||||||
expect(storedOwner.password).not.toBe(newOwnerData.password);
|
expect(isInstanceOwnerSetUpConfig).toBe(true);
|
||||||
expect(storedOwner.email).toBe(newOwnerData.email);
|
|
||||||
expect(storedOwner.firstName).toBe(newOwnerData.firstName);
|
|
||||||
expect(storedOwner.lastName).toBe(newOwnerData.lastName);
|
|
||||||
|
|
||||||
const isInstanceOwnerSetUpConfig = config.getEnv('userManagement.isInstanceOwnerSetUp');
|
const isInstanceOwnerSetUpSetting = await utils.isInstanceOwnerSetUp();
|
||||||
expect(isInstanceOwnerSetUpConfig).toBe(true);
|
expect(isInstanceOwnerSetUpSetting).toBe(true);
|
||||||
|
});
|
||||||
const isInstanceOwnerSetUpSetting = await utils.isInstanceOwnerSetUp();
|
|
||||||
expect(isInstanceOwnerSetUpSetting).toBe(true);
|
test('should create owner with lowercased email', async () => {
|
||||||
});
|
const newOwnerData = {
|
||||||
|
email: randomEmail().toUpperCase(),
|
||||||
test('POST /owner/setup should create owner with lowercased email', async () => {
|
firstName: randomName(),
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
lastName: randomName(),
|
||||||
|
password: randomValidPassword(),
|
||||||
const newOwnerData = {
|
};
|
||||||
email: randomEmail().toUpperCase(),
|
|
||||||
firstName: randomName(),
|
const response = await authOwnerShellAgent.post('/owner/setup').send(newOwnerData);
|
||||||
lastName: randomName(),
|
|
||||||
password: randomValidPassword(),
|
expect(response.statusCode).toBe(200);
|
||||||
};
|
|
||||||
|
const { id, email } = response.body.data;
|
||||||
const response = await authAgent(ownerShell).post('/owner/setup').send(newOwnerData);
|
|
||||||
|
expect(id).toBe(ownerShell.id);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(email).toBe(newOwnerData.email.toLowerCase());
|
||||||
|
|
||||||
const { id, email } = response.body.data;
|
const storedOwner = await Db.collections.User.findOneByOrFail({ id });
|
||||||
|
expect(storedOwner.email).toBe(newOwnerData.email.toLowerCase());
|
||||||
expect(id).toBe(ownerShell.id);
|
});
|
||||||
expect(email).toBe(newOwnerData.email.toLowerCase());
|
|
||||||
|
const INVALID_POST_OWNER_PAYLOADS = [
|
||||||
const storedOwner = await Db.collections.User.findOneByOrFail({ id });
|
{
|
||||||
expect(storedOwner.email).toBe(newOwnerData.email.toLowerCase());
|
email: '',
|
||||||
});
|
firstName: randomName(),
|
||||||
|
lastName: randomName(),
|
||||||
test('POST /owner/setup should fail with invalid inputs', async () => {
|
password: randomValidPassword(),
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
},
|
||||||
const authOwnerAgent = authAgent(ownerShell);
|
{
|
||||||
|
email: randomEmail(),
|
||||||
await Promise.all(
|
firstName: '',
|
||||||
INVALID_POST_OWNER_PAYLOADS.map(async (invalidPayload) => {
|
lastName: randomName(),
|
||||||
const response = await authOwnerAgent.post('/owner/setup').send(invalidPayload);
|
password: randomValidPassword(),
|
||||||
expect(response.statusCode).toBe(400);
|
},
|
||||||
}),
|
{
|
||||||
);
|
email: randomEmail(),
|
||||||
});
|
firstName: randomName(),
|
||||||
|
lastName: '',
|
||||||
test('POST /owner/skip-setup should persist skipping setup to the DB', async () => {
|
password: randomValidPassword(),
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
},
|
||||||
|
{
|
||||||
const response = await authAgent(ownerShell).post('/owner/skip-setup').send();
|
email: randomEmail(),
|
||||||
|
firstName: randomName(),
|
||||||
expect(response.statusCode).toBe(200);
|
lastName: randomName(),
|
||||||
|
password: randomInvalidPassword(),
|
||||||
const skipConfig = config.getEnv('userManagement.skipInstanceOwnerSetup');
|
},
|
||||||
expect(skipConfig).toBe(true);
|
{
|
||||||
|
firstName: randomName(),
|
||||||
const { value } = await Db.collections.Settings.findOneByOrFail({
|
lastName: randomName(),
|
||||||
key: 'userManagement.skipInstanceOwnerSetup',
|
},
|
||||||
|
{
|
||||||
|
firstName: randomName(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lastName: randomName(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: randomEmail(),
|
||||||
|
firstName: 'John <script',
|
||||||
|
lastName: randomName(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: randomEmail(),
|
||||||
|
firstName: 'John <a',
|
||||||
|
lastName: randomName(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
test('should fail with invalid inputs', async () => {
|
||||||
|
const authOwnerAgent = authOwnerShellAgent;
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
INVALID_POST_OWNER_PAYLOADS.map(async (invalidPayload) => {
|
||||||
|
const response = await authOwnerAgent.post('/owner/setup').send(invalidPayload);
|
||||||
|
expect(response.statusCode).toBe(400);
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
expect(value).toBe('true');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const INVALID_POST_OWNER_PAYLOADS = [
|
describe('POST /owner/skip-setup', () => {
|
||||||
{
|
test('should persist skipping setup to the DB', async () => {
|
||||||
email: '',
|
const response = await authOwnerShellAgent.post('/owner/skip-setup').send();
|
||||||
firstName: randomName(),
|
|
||||||
lastName: randomName(),
|
expect(response.statusCode).toBe(200);
|
||||||
password: randomValidPassword(),
|
|
||||||
},
|
const skipConfig = config.getEnv('userManagement.skipInstanceOwnerSetup');
|
||||||
{
|
expect(skipConfig).toBe(true);
|
||||||
email: randomEmail(),
|
|
||||||
firstName: '',
|
const { value } = await Db.collections.Settings.findOneByOrFail({
|
||||||
lastName: randomName(),
|
key: 'userManagement.skipInstanceOwnerSetup',
|
||||||
password: randomValidPassword(),
|
});
|
||||||
},
|
expect(value).toBe('true');
|
||||||
{
|
});
|
||||||
email: randomEmail(),
|
});
|
||||||
firstName: randomName(),
|
|
||||||
lastName: '',
|
|
||||||
password: randomValidPassword(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
email: randomEmail(),
|
|
||||||
firstName: randomName(),
|
|
||||||
lastName: randomName(),
|
|
||||||
password: randomInvalidPassword(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
firstName: randomName(),
|
|
||||||
lastName: randomName(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
firstName: randomName(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lastName: randomName(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
email: randomEmail(),
|
|
||||||
firstName: 'John <script',
|
|
||||||
lastName: randomName(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
email: randomEmail(),
|
|
||||||
firstName: 'John <a',
|
|
||||||
lastName: randomName(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import express from 'express';
|
import type { SuperAgentTest } from 'supertest';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { compare } from 'bcryptjs';
|
||||||
|
|
||||||
import * as utils from './shared/utils';
|
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { compare } from 'bcryptjs';
|
import type { Role } from '@db/entities/Role';
|
||||||
|
import type { User } from '@db/entities/User';
|
||||||
|
import * as utils from './shared/utils';
|
||||||
import {
|
import {
|
||||||
randomEmail,
|
randomEmail,
|
||||||
randomInvalidPassword,
|
randomInvalidPassword,
|
||||||
|
@ -12,276 +14,237 @@ import {
|
||||||
randomValidPassword,
|
randomValidPassword,
|
||||||
} from './shared/random';
|
} from './shared/random';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import type { Role } from '@db/entities/Role';
|
|
||||||
|
|
||||||
jest.mock('@/UserManagement/email/NodeMailer');
|
jest.mock('@/UserManagement/email/NodeMailer');
|
||||||
|
|
||||||
let app: express.Application;
|
|
||||||
let globalOwnerRole: Role;
|
let globalOwnerRole: Role;
|
||||||
let globalMemberRole: Role;
|
let globalMemberRole: Role;
|
||||||
|
let owner: User;
|
||||||
|
let authlessAgent: SuperAgentTest;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({ endpointGroups: ['passwordReset'] });
|
const app = await utils.initTestServer({ endpointGroups: ['passwordReset'] });
|
||||||
|
|
||||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||||
|
|
||||||
|
authlessAgent = utils.createAgent(app);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await testDb.truncate(['User']);
|
await testDb.truncate(['User']);
|
||||||
|
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||||
jest.mock('@/config');
|
|
||||||
|
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||||
config.set('userManagement.emails.mode', '');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await testDb.terminate();
|
await testDb.terminate();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /forgot-password should send password reset email', async () => {
|
describe('POST /forgot-password', () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
test('should send password reset email', async () => {
|
||||||
|
const member = await testDb.createUser({
|
||||||
|
email: 'test@test.com',
|
||||||
|
globalRole: globalMemberRole,
|
||||||
|
});
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
config.set('userManagement.emails.mode', 'smtp');
|
||||||
const member = await testDb.createUser({
|
|
||||||
email: 'test@test.com',
|
await Promise.all(
|
||||||
globalRole: globalMemberRole,
|
[{ email: owner.email }, { email: member.email.toUpperCase() }].map(async (payload) => {
|
||||||
|
const response = await authlessAgent.post('/forgot-password').send(payload);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body).toEqual({});
|
||||||
|
|
||||||
|
const user = await Db.collections.User.findOneByOrFail({ email: payload.email });
|
||||||
|
expect(user.resetPasswordToken).toBeDefined();
|
||||||
|
expect(user.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000));
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
config.set('userManagement.emails.mode', 'smtp');
|
test('should fail if emailing is not set up', async () => {
|
||||||
|
config.set('userManagement.emails.mode', '');
|
||||||
|
|
||||||
await Promise.all(
|
await authlessAgent.post('/forgot-password').send({ email: owner.email }).expect(500);
|
||||||
[{ email: owner.email }, { email: member.email.toUpperCase() }].map(async (payload) => {
|
|
||||||
const response = await authlessAgent.post('/forgot-password').send(payload);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
const storedOwner = await Db.collections.User.findOneByOrFail({ email: owner.email });
|
||||||
expect(response.body).toEqual({});
|
expect(storedOwner.resetPasswordToken).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
const user = await Db.collections.User.findOneByOrFail({ email: payload.email });
|
test('should fail with invalid inputs', async () => {
|
||||||
expect(user.resetPasswordToken).toBeDefined();
|
config.set('userManagement.emails.mode', 'smtp');
|
||||||
expect(user.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000));
|
|
||||||
}),
|
const invalidPayloads = [
|
||||||
);
|
randomEmail(),
|
||||||
|
[randomEmail()],
|
||||||
|
{},
|
||||||
|
[{ name: randomName() }],
|
||||||
|
[{ email: randomName() }],
|
||||||
|
];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
invalidPayloads.map(async (invalidPayload) => {
|
||||||
|
const response = await authlessAgent.post('/forgot-password').send(invalidPayload);
|
||||||
|
expect(response.statusCode).toBe(400);
|
||||||
|
|
||||||
|
const storedOwner = await Db.collections.User.findOneByOrFail({ email: owner.email });
|
||||||
|
expect(storedOwner.resetPasswordToken).toBeNull();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail if user is not found', async () => {
|
||||||
|
config.set('userManagement.emails.mode', 'smtp');
|
||||||
|
|
||||||
|
const response = await authlessAgent.post('/forgot-password').send({ email: randomEmail() });
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200); // expect 200 to remain vague
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /forgot-password should fail if emailing is not set up', async () => {
|
describe('GET /resolve-password-token', () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
beforeEach(() => {
|
||||||
|
config.set('userManagement.emails.mode', 'smtp');
|
||||||
|
});
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
test('should succeed with valid inputs', async () => {
|
||||||
|
const resetPasswordToken = uuid();
|
||||||
|
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
||||||
|
|
||||||
const response = await authlessAgent.post('/forgot-password').send({ email: owner.email });
|
await Db.collections.User.update(owner.id, {
|
||||||
|
resetPasswordToken,
|
||||||
|
resetPasswordTokenExpiration,
|
||||||
|
});
|
||||||
|
|
||||||
expect(response.statusCode).toBe(500);
|
const response = await authlessAgent
|
||||||
|
.get('/resolve-password-token')
|
||||||
|
.query({ userId: owner.id, token: resetPasswordToken });
|
||||||
|
|
||||||
const storedOwner = await Db.collections.User.findOneByOrFail({ email: owner.email });
|
expect(response.statusCode).toBe(200);
|
||||||
expect(storedOwner.resetPasswordToken).toBeNull();
|
});
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /forgot-password should fail with invalid inputs', async () => {
|
test('should fail with invalid inputs', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
const first = await authlessAgent.get('/resolve-password-token').query({ token: uuid() });
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
const second = await authlessAgent.get('/resolve-password-token').query({ userId: owner.id });
|
||||||
|
|
||||||
config.set('userManagement.emails.mode', 'smtp');
|
for (const response of [first, second]) {
|
||||||
|
|
||||||
const invalidPayloads = [
|
|
||||||
randomEmail(),
|
|
||||||
[randomEmail()],
|
|
||||||
{},
|
|
||||||
[{ name: randomName() }],
|
|
||||||
[{ email: randomName() }],
|
|
||||||
];
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
invalidPayloads.map(async (invalidPayload) => {
|
|
||||||
const response = await authlessAgent.post('/forgot-password').send(invalidPayload);
|
|
||||||
expect(response.statusCode).toBe(400);
|
expect(response.statusCode).toBe(400);
|
||||||
|
}
|
||||||
const storedOwner = await Db.collections.User.findOneByOrFail({ email: owner.email });
|
|
||||||
expect(storedOwner.resetPasswordToken).toBeNull();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /forgot-password should fail if user is not found', async () => {
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
|
||||||
|
|
||||||
config.set('userManagement.emails.mode', 'smtp');
|
|
||||||
|
|
||||||
const response = await authlessAgent.post('/forgot-password').send({ email: randomEmail() });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200); // expect 200 to remain vague
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /resolve-password-token should succeed with valid inputs', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
|
||||||
|
|
||||||
const resetPasswordToken = uuid();
|
|
||||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
|
||||||
|
|
||||||
await Db.collections.User.update(owner.id, {
|
|
||||||
resetPasswordToken,
|
|
||||||
resetPasswordTokenExpiration,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await authlessAgent
|
test('should fail if user is not found', async () => {
|
||||||
.get('/resolve-password-token')
|
const response = await authlessAgent
|
||||||
.query({ userId: owner.id, token: resetPasswordToken });
|
.get('/resolve-password-token')
|
||||||
|
.query({ userId: owner.id, token: uuid() });
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(404);
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /resolve-password-token should fail with invalid inputs', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
|
||||||
|
|
||||||
config.set('userManagement.emails.mode', 'smtp');
|
|
||||||
|
|
||||||
const first = await authlessAgent.get('/resolve-password-token').query({ token: uuid() });
|
|
||||||
|
|
||||||
const second = await authlessAgent.get('/resolve-password-token').query({ userId: owner.id });
|
|
||||||
|
|
||||||
for (const response of [first, second]) {
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /resolve-password-token should fail if user is not found', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
|
||||||
|
|
||||||
config.set('userManagement.emails.mode', 'smtp');
|
|
||||||
|
|
||||||
const response = await authlessAgent
|
|
||||||
.get('/resolve-password-token')
|
|
||||||
.query({ userId: owner.id, token: uuid() });
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /resolve-password-token should fail if token is expired', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
|
||||||
|
|
||||||
const resetPasswordToken = uuid();
|
|
||||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) - 1;
|
|
||||||
|
|
||||||
await Db.collections.User.update(owner.id, {
|
|
||||||
resetPasswordToken,
|
|
||||||
resetPasswordTokenExpiration,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
config.set('userManagement.emails.mode', 'smtp');
|
test('should fail if token is expired', async () => {
|
||||||
|
const resetPasswordToken = uuid();
|
||||||
|
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) - 1;
|
||||||
|
|
||||||
const response = await authlessAgent
|
await Db.collections.User.update(owner.id, {
|
||||||
.get('/resolve-password-token')
|
resetPasswordToken,
|
||||||
.query({ userId: owner.id, token: resetPasswordToken });
|
resetPasswordTokenExpiration,
|
||||||
|
});
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
const response = await authlessAgent
|
||||||
|
.get('/resolve-password-token')
|
||||||
|
.query({ userId: owner.id, token: resetPasswordToken });
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(404);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /change-password should succeed with valid inputs', async () => {
|
describe('POST /change-password', () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
|
||||||
|
|
||||||
const resetPasswordToken = uuid();
|
const resetPasswordToken = uuid();
|
||||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
|
||||||
|
|
||||||
await Db.collections.User.update(owner.id, {
|
|
||||||
resetPasswordToken,
|
|
||||||
resetPasswordTokenExpiration,
|
|
||||||
});
|
|
||||||
|
|
||||||
const passwordToStore = randomValidPassword();
|
const passwordToStore = randomValidPassword();
|
||||||
|
|
||||||
const response = await authlessAgent.post('/change-password').send({
|
test('should succeed with valid inputs', async () => {
|
||||||
token: resetPasswordToken,
|
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
||||||
userId: owner.id,
|
|
||||||
password: passwordToStore,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
await Db.collections.User.update(owner.id, {
|
||||||
|
resetPasswordToken,
|
||||||
|
resetPasswordTokenExpiration,
|
||||||
|
});
|
||||||
|
|
||||||
const authToken = utils.getAuthToken(response);
|
const response = await authlessAgent.post('/change-password').send({
|
||||||
expect(authToken).toBeDefined();
|
|
||||||
|
|
||||||
const { password: storedPassword } = await Db.collections.User.findOneByOrFail({ id: owner.id });
|
|
||||||
|
|
||||||
const comparisonResult = await compare(passwordToStore, storedPassword);
|
|
||||||
expect(comparisonResult).toBe(true);
|
|
||||||
expect(storedPassword).not.toBe(passwordToStore);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /change-password should fail with invalid inputs', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
|
||||||
|
|
||||||
const resetPasswordToken = uuid();
|
|
||||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
|
||||||
|
|
||||||
await Db.collections.User.update(owner.id, {
|
|
||||||
resetPasswordToken,
|
|
||||||
resetPasswordTokenExpiration,
|
|
||||||
});
|
|
||||||
|
|
||||||
const invalidPayloads = [
|
|
||||||
{ token: uuid() },
|
|
||||||
{ id: owner.id },
|
|
||||||
{ password: randomValidPassword() },
|
|
||||||
{ token: uuid(), id: owner.id },
|
|
||||||
{ token: uuid(), password: randomValidPassword() },
|
|
||||||
{ id: owner.id, password: randomValidPassword() },
|
|
||||||
{
|
|
||||||
id: owner.id,
|
|
||||||
password: randomInvalidPassword(),
|
|
||||||
token: resetPasswordToken,
|
token: resetPasswordToken,
|
||||||
},
|
userId: owner.id,
|
||||||
{
|
password: passwordToStore,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const authToken = utils.getAuthToken(response);
|
||||||
|
expect(authToken).toBeDefined();
|
||||||
|
|
||||||
|
const { password: storedPassword } = await Db.collections.User.findOneByOrFail({
|
||||||
id: owner.id,
|
id: owner.id,
|
||||||
password: randomValidPassword(),
|
});
|
||||||
token: uuid(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
await Promise.all(
|
const comparisonResult = await compare(passwordToStore, storedPassword);
|
||||||
invalidPayloads.map(async (invalidPayload) => {
|
expect(comparisonResult).toBe(true);
|
||||||
const response = await authlessAgent.post('/change-password').query(invalidPayload);
|
expect(storedPassword).not.toBe(passwordToStore);
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
|
|
||||||
const { password: storedPassword } = await Db.collections.User.findOneByOrFail({});
|
|
||||||
expect(owner.password).toBe(storedPassword);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /change-password should fail when token has expired', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
|
||||||
|
|
||||||
const resetPasswordToken = uuid();
|
|
||||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) - 1;
|
|
||||||
|
|
||||||
await Db.collections.User.update(owner.id, {
|
|
||||||
resetPasswordToken,
|
|
||||||
resetPasswordTokenExpiration,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const passwordToStore = randomValidPassword();
|
test('should fail with invalid inputs', async () => {
|
||||||
|
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
||||||
|
|
||||||
const response = await authlessAgent.post('/change-password').send({
|
await Db.collections.User.update(owner.id, {
|
||||||
token: resetPasswordToken,
|
resetPasswordToken,
|
||||||
userId: owner.id,
|
resetPasswordTokenExpiration,
|
||||||
password: passwordToStore,
|
});
|
||||||
|
|
||||||
|
const invalidPayloads = [
|
||||||
|
{ token: uuid() },
|
||||||
|
{ id: owner.id },
|
||||||
|
{ password: randomValidPassword() },
|
||||||
|
{ token: uuid(), id: owner.id },
|
||||||
|
{ token: uuid(), password: randomValidPassword() },
|
||||||
|
{ id: owner.id, password: randomValidPassword() },
|
||||||
|
{
|
||||||
|
id: owner.id,
|
||||||
|
password: randomInvalidPassword(),
|
||||||
|
token: resetPasswordToken,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: owner.id,
|
||||||
|
password: randomValidPassword(),
|
||||||
|
token: uuid(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
invalidPayloads.map(async (invalidPayload) => {
|
||||||
|
const response = await authlessAgent.post('/change-password').query(invalidPayload);
|
||||||
|
expect(response.statusCode).toBe(400);
|
||||||
|
|
||||||
|
const { password: storedPassword } = await Db.collections.User.findOneByOrFail({});
|
||||||
|
expect(owner.password).toBe(storedPassword);
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
test('should fail when token has expired', async () => {
|
||||||
|
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) - 1;
|
||||||
|
|
||||||
|
await Db.collections.User.update(owner.id, {
|
||||||
|
resetPasswordToken,
|
||||||
|
resetPasswordTokenExpiration,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await authlessAgent.post('/change-password').send({
|
||||||
|
token: resetPasswordToken,
|
||||||
|
userId: owner.id,
|
||||||
|
password: passwordToStore,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(404);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
import express from 'express';
|
import type { SuperAgentTest } from 'supertest';
|
||||||
|
|
||||||
import { UserSettings } from 'n8n-core';
|
import { UserSettings } from 'n8n-core';
|
||||||
|
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
|
import type { User } from '@db/entities/User';
|
||||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||||
import { randomApiKey, randomName, randomString } from '../shared/random';
|
import { randomApiKey, randomName, randomString } from '../shared/random';
|
||||||
import * as utils from '../shared/utils';
|
import * as utils from '../shared/utils';
|
||||||
import type { CredentialPayload, SaveCredentialFunction } from '../shared/types';
|
import type { CredentialPayload, SaveCredentialFunction } from '../shared/types';
|
||||||
import * as testDb from '../shared/testDb';
|
import * as testDb from '../shared/testDb';
|
||||||
|
|
||||||
let app: express.Application;
|
|
||||||
let globalOwnerRole: Role;
|
|
||||||
let globalMemberRole: Role;
|
let globalMemberRole: Role;
|
||||||
let credentialOwnerRole: Role;
|
let credentialOwnerRole: Role;
|
||||||
|
let owner: User;
|
||||||
|
let member: User;
|
||||||
|
let authOwnerAgent: SuperAgentTest;
|
||||||
|
let authMemberAgent: SuperAgentTest;
|
||||||
|
|
||||||
let saveCredential: SaveCredentialFunction;
|
let saveCredential: SaveCredentialFunction;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({
|
const app = await utils.initTestServer({
|
||||||
endpointGroups: ['publicApi'],
|
endpointGroups: ['publicApi'],
|
||||||
applyAuth: false,
|
applyAuth: false,
|
||||||
enablePublicAPI: true,
|
enablePublicAPI: true,
|
||||||
|
@ -26,334 +27,265 @@ beforeAll(async () => {
|
||||||
|
|
||||||
utils.initConfigFile();
|
utils.initConfigFile();
|
||||||
|
|
||||||
const [fetchedGlobalOwnerRole, fetchedGlobalMemberRole, _, fetchedCredentialOwnerRole] =
|
const [globalOwnerRole, fetchedGlobalMemberRole, _, fetchedCredentialOwnerRole] =
|
||||||
await testDb.getAllRoles();
|
await testDb.getAllRoles();
|
||||||
|
|
||||||
globalOwnerRole = fetchedGlobalOwnerRole;
|
|
||||||
globalMemberRole = fetchedGlobalMemberRole;
|
globalMemberRole = fetchedGlobalMemberRole;
|
||||||
credentialOwnerRole = fetchedCredentialOwnerRole;
|
credentialOwnerRole = fetchedCredentialOwnerRole;
|
||||||
|
|
||||||
|
owner = await testDb.addApiKey(await testDb.createUserShell(globalOwnerRole));
|
||||||
|
member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() });
|
||||||
|
|
||||||
|
authOwnerAgent = utils.createAgent(app, {
|
||||||
|
apiPath: 'public',
|
||||||
|
version: 1,
|
||||||
|
auth: true,
|
||||||
|
user: owner,
|
||||||
|
});
|
||||||
|
authMemberAgent = utils.createAgent(app, {
|
||||||
|
apiPath: 'public',
|
||||||
|
version: 1,
|
||||||
|
auth: true,
|
||||||
|
user: member,
|
||||||
|
});
|
||||||
|
|
||||||
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
|
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
|
||||||
|
|
||||||
utils.initCredentialsTypes();
|
utils.initCredentialsTypes();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await testDb.truncate(['User', 'SharedCredentials', 'Credentials']);
|
await testDb.truncate(['SharedCredentials', 'Credentials']);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await testDb.terminate();
|
await testDb.terminate();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /credentials should create credentials', async () => {
|
describe('POST /credentials', () => {
|
||||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
test('should create credentials', async () => {
|
||||||
ownerShell = await testDb.addApiKey(ownerShell);
|
const payload = {
|
||||||
|
name: 'test credential',
|
||||||
|
type: 'githubApi',
|
||||||
|
data: {
|
||||||
|
accessToken: 'abcdefghijklmnopqrstuvwxyz',
|
||||||
|
user: 'test',
|
||||||
|
server: 'testServer',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const authOwnerAgent = utils.createAgent(app, {
|
const response = await authOwnerAgent.post('/credentials').send(payload);
|
||||||
apiPath: 'public',
|
|
||||||
version: 1,
|
|
||||||
auth: true,
|
|
||||||
user: ownerShell,
|
|
||||||
});
|
|
||||||
const payload = {
|
|
||||||
name: 'test credential',
|
|
||||||
type: 'githubApi',
|
|
||||||
data: {
|
|
||||||
accessToken: 'abcdefghijklmnopqrstuvwxyz',
|
|
||||||
user: 'test',
|
|
||||||
server: 'testServer',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.post('/credentials').send(payload);
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
const { id, name, type } = response.body;
|
||||||
|
|
||||||
const { id, name, type } = response.body;
|
expect(name).toBe(payload.name);
|
||||||
|
expect(type).toBe(payload.type);
|
||||||
|
|
||||||
expect(name).toBe(payload.name);
|
const credential = await Db.collections.Credentials.findOneByOrFail({ id });
|
||||||
expect(type).toBe(payload.type);
|
|
||||||
|
|
||||||
const credential = await Db.collections.Credentials.findOneByOrFail({ id });
|
expect(credential.name).toBe(payload.name);
|
||||||
|
expect(credential.type).toBe(payload.type);
|
||||||
|
expect(credential.data).not.toBe(payload.data);
|
||||||
|
|
||||||
expect(credential.name).toBe(payload.name);
|
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||||
expect(credential.type).toBe(payload.type);
|
relations: ['user', 'credentials', 'role'],
|
||||||
expect(credential.data).not.toBe(payload.data);
|
where: { credentialsId: credential.id, userId: owner.id },
|
||||||
|
});
|
||||||
|
|
||||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
expect(sharedCredential.role).toEqual(credentialOwnerRole);
|
||||||
relations: ['user', 'credentials', 'role'],
|
expect(sharedCredential.credentials.name).toBe(payload.name);
|
||||||
where: { credentialsId: credential.id, userId: ownerShell.id },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sharedCredential.role).toEqual(credentialOwnerRole);
|
test('should fail with invalid inputs', async () => {
|
||||||
expect(sharedCredential.credentials.name).toBe(payload.name);
|
await Promise.all(
|
||||||
|
INVALID_PAYLOADS.map(async (invalidPayload) => {
|
||||||
|
const response = await authOwnerAgent.post('/credentials').send(invalidPayload);
|
||||||
|
expect(response.statusCode === 400 || response.statusCode === 415).toBe(true);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail with missing encryption key', async () => {
|
||||||
|
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||||
|
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.post('/credentials').send(credentialPayload());
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(500);
|
||||||
|
|
||||||
|
mock.mockRestore();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /credentials should fail with invalid inputs', async () => {
|
describe('DELETE /credentials/:id', () => {
|
||||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
test('should delete owned cred for owner', async () => {
|
||||||
ownerShell = await testDb.addApiKey(ownerShell);
|
const savedCredential = await saveCredential(dbCredential(), { user: owner });
|
||||||
|
|
||||||
const authOwnerAgent = utils.createAgent(app, {
|
const response = await authOwnerAgent.delete(`/credentials/${savedCredential.id}`);
|
||||||
apiPath: 'public',
|
|
||||||
version: 1,
|
expect(response.statusCode).toBe(200);
|
||||||
auth: true,
|
|
||||||
user: ownerShell,
|
const { name, type } = response.body;
|
||||||
|
|
||||||
|
expect(name).toBe(savedCredential.name);
|
||||||
|
expect(type).toBe(savedCredential.type);
|
||||||
|
|
||||||
|
const deletedCredential = await Db.collections.Credentials.findOneBy({
|
||||||
|
id: savedCredential.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(deletedCredential).toBeNull(); // deleted
|
||||||
|
|
||||||
|
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
||||||
|
|
||||||
|
expect(deletedSharedCredential).toBeNull(); // deleted
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(
|
test('should delete non-owned cred for owner', async () => {
|
||||||
INVALID_PAYLOADS.map(async (invalidPayload) => {
|
const savedCredential = await saveCredential(dbCredential(), { user: member });
|
||||||
const response = await authOwnerAgent.post('/credentials').send(invalidPayload);
|
|
||||||
expect(response.statusCode === 400 || response.statusCode === 415).toBe(true);
|
const response = await authOwnerAgent.delete(`/credentials/${savedCredential.id}`);
|
||||||
}),
|
|
||||||
);
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const deletedCredential = await Db.collections.Credentials.findOneBy({
|
||||||
|
id: savedCredential.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(deletedCredential).toBeNull(); // deleted
|
||||||
|
|
||||||
|
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
||||||
|
|
||||||
|
expect(deletedSharedCredential).toBeNull(); // deleted
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should delete owned cred for member', async () => {
|
||||||
|
const savedCredential = await saveCredential(dbCredential(), { user: member });
|
||||||
|
|
||||||
|
const response = await authMemberAgent.delete(`/credentials/${savedCredential.id}`);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const { name, type } = response.body;
|
||||||
|
|
||||||
|
expect(name).toBe(savedCredential.name);
|
||||||
|
expect(type).toBe(savedCredential.type);
|
||||||
|
|
||||||
|
const deletedCredential = await Db.collections.Credentials.findOneBy({
|
||||||
|
id: savedCredential.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(deletedCredential).toBeNull(); // deleted
|
||||||
|
|
||||||
|
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
||||||
|
|
||||||
|
expect(deletedSharedCredential).toBeNull(); // deleted
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should delete owned cred for member but leave others untouched', async () => {
|
||||||
|
const anotherMember = await testDb.createUser({
|
||||||
|
globalRole: globalMemberRole,
|
||||||
|
apiKey: randomApiKey(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const savedCredential = await saveCredential(dbCredential(), { user: member });
|
||||||
|
const notToBeChangedCredential = await saveCredential(dbCredential(), { user: member });
|
||||||
|
const notToBeChangedCredential2 = await saveCredential(dbCredential(), {
|
||||||
|
user: anotherMember,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await authMemberAgent.delete(`/credentials/${savedCredential.id}`);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const { name, type } = response.body;
|
||||||
|
|
||||||
|
expect(name).toBe(savedCredential.name);
|
||||||
|
expect(type).toBe(savedCredential.type);
|
||||||
|
|
||||||
|
const deletedCredential = await Db.collections.Credentials.findOneBy({
|
||||||
|
id: savedCredential.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(deletedCredential).toBeNull(); // deleted
|
||||||
|
|
||||||
|
const deletedSharedCredential = await Db.collections.SharedCredentials.findOne({
|
||||||
|
where: {
|
||||||
|
credentialsId: savedCredential.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(deletedSharedCredential).toBeNull(); // deleted
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
[notToBeChangedCredential, notToBeChangedCredential2].map(async (credential) => {
|
||||||
|
const untouchedCredential = await Db.collections.Credentials.findOneBy({
|
||||||
|
id: credential.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(untouchedCredential).toEqual(credential); // not deleted
|
||||||
|
|
||||||
|
const untouchedSharedCredential = await Db.collections.SharedCredentials.findOne({
|
||||||
|
where: {
|
||||||
|
credentialsId: credential.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(untouchedSharedCredential).toBeDefined(); // not deleted
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not delete non-owned cred for member', async () => {
|
||||||
|
const savedCredential = await saveCredential(dbCredential(), { user: owner });
|
||||||
|
|
||||||
|
const response = await authMemberAgent.delete(`/credentials/${savedCredential.id}`);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(404);
|
||||||
|
|
||||||
|
const shellCredential = await Db.collections.Credentials.findOneBy({
|
||||||
|
id: savedCredential.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(shellCredential).toBeDefined(); // not deleted
|
||||||
|
|
||||||
|
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
||||||
|
|
||||||
|
expect(deletedSharedCredential).toBeDefined(); // not deleted
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail if cred not found', async () => {
|
||||||
|
const response = await authOwnerAgent.delete('/credentials/123');
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(404);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /credentials should fail with missing encryption key', async () => {
|
describe('GET /credentials/schema/:credentialType', () => {
|
||||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
test('should fail due to not found type', async () => {
|
||||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
const response = await authOwnerAgent.get('/credentials/schema/testing');
|
||||||
|
|
||||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
expect(response.statusCode).toBe(404);
|
||||||
ownerShell = await testDb.addApiKey(ownerShell);
|
|
||||||
|
|
||||||
const authOwnerAgent = utils.createAgent(app, {
|
|
||||||
apiPath: 'public',
|
|
||||||
version: 1,
|
|
||||||
auth: true,
|
|
||||||
user: ownerShell,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await authOwnerAgent.post('/credentials').send(credentialPayload());
|
test('should retrieve credential type', async () => {
|
||||||
|
const response = await authOwnerAgent.get('/credentials/schema/githubApi');
|
||||||
|
|
||||||
expect(response.statusCode).toBe(500);
|
const { additionalProperties, type, properties, required } = response.body;
|
||||||
|
|
||||||
mock.mockRestore();
|
expect(additionalProperties).toBe(false);
|
||||||
});
|
expect(type).toBe('object');
|
||||||
|
expect(properties.server).toBeDefined();
|
||||||
test('DELETE /credentials/:id should delete owned cred for owner', async () => {
|
expect(properties.server.type).toBe('string');
|
||||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
expect(properties.user.type).toBeDefined();
|
||||||
ownerShell = await testDb.addApiKey(ownerShell);
|
expect(properties.user.type).toBe('string');
|
||||||
|
expect(properties.accessToken.type).toBeDefined();
|
||||||
const authOwnerAgent = utils.createAgent(app, {
|
expect(properties.accessToken.type).toBe('string');
|
||||||
apiPath: 'public',
|
expect(required).toEqual(expect.arrayContaining(['server', 'user', 'accessToken']));
|
||||||
version: 1,
|
expect(response.statusCode).toBe(200);
|
||||||
auth: true,
|
|
||||||
user: ownerShell,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const savedCredential = await saveCredential(dbCredential(), { user: ownerShell });
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.delete(`/credentials/${savedCredential.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const { name, type } = response.body;
|
|
||||||
|
|
||||||
expect(name).toBe(savedCredential.name);
|
|
||||||
expect(type).toBe(savedCredential.type);
|
|
||||||
|
|
||||||
const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
|
|
||||||
|
|
||||||
expect(deletedCredential).toBeNull(); // deleted
|
|
||||||
|
|
||||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
|
||||||
|
|
||||||
expect(deletedSharedCredential).toBeNull(); // deleted
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /credentials/:id should delete non-owned cred for owner', async () => {
|
|
||||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
ownerShell = await testDb.addApiKey(ownerShell);
|
|
||||||
|
|
||||||
const authOwnerAgent = utils.createAgent(app, {
|
|
||||||
apiPath: 'public',
|
|
||||||
version: 1,
|
|
||||||
auth: true,
|
|
||||||
user: ownerShell,
|
|
||||||
});
|
|
||||||
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
const savedCredential = await saveCredential(dbCredential(), { user: member });
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.delete(`/credentials/${savedCredential.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
|
|
||||||
|
|
||||||
expect(deletedCredential).toBeNull(); // deleted
|
|
||||||
|
|
||||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
|
||||||
|
|
||||||
expect(deletedSharedCredential).toBeNull(); // deleted
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /credentials/:id should delete owned cred for member', async () => {
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() });
|
|
||||||
|
|
||||||
const authMemberAgent = utils.createAgent(app, {
|
|
||||||
apiPath: 'public',
|
|
||||||
version: 1,
|
|
||||||
auth: true,
|
|
||||||
user: member,
|
|
||||||
});
|
|
||||||
|
|
||||||
const savedCredential = await saveCredential(dbCredential(), { user: member });
|
|
||||||
|
|
||||||
const response = await authMemberAgent.delete(`/credentials/${savedCredential.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const { name, type } = response.body;
|
|
||||||
|
|
||||||
expect(name).toBe(savedCredential.name);
|
|
||||||
expect(type).toBe(savedCredential.type);
|
|
||||||
|
|
||||||
const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
|
|
||||||
|
|
||||||
expect(deletedCredential).toBeNull(); // deleted
|
|
||||||
|
|
||||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
|
||||||
|
|
||||||
expect(deletedSharedCredential).toBeNull(); // deleted
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /credentials/:id should delete owned cred for member but leave others untouched', async () => {
|
|
||||||
const member1 = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() });
|
|
||||||
const member2 = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() });
|
|
||||||
|
|
||||||
const savedCredential = await saveCredential(dbCredential(), { user: member1 });
|
|
||||||
const notToBeChangedCredential = await saveCredential(dbCredential(), { user: member1 });
|
|
||||||
const notToBeChangedCredential2 = await saveCredential(dbCredential(), { user: member2 });
|
|
||||||
|
|
||||||
const authMemberAgent = utils.createAgent(app, {
|
|
||||||
apiPath: 'public',
|
|
||||||
version: 1,
|
|
||||||
auth: true,
|
|
||||||
user: member1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await authMemberAgent.delete(`/credentials/${savedCredential.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const { name, type } = response.body;
|
|
||||||
|
|
||||||
expect(name).toBe(savedCredential.name);
|
|
||||||
expect(type).toBe(savedCredential.type);
|
|
||||||
|
|
||||||
const deletedCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
|
|
||||||
|
|
||||||
expect(deletedCredential).toBeNull(); // deleted
|
|
||||||
|
|
||||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOne({
|
|
||||||
where: {
|
|
||||||
credentialsId: savedCredential.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(deletedSharedCredential).toBeNull(); // deleted
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
[notToBeChangedCredential, notToBeChangedCredential2].map(async (credential) => {
|
|
||||||
const untouchedCredential = await Db.collections.Credentials.findOneBy({ id: credential.id });
|
|
||||||
|
|
||||||
expect(untouchedCredential).toEqual(credential); // not deleted
|
|
||||||
|
|
||||||
const untouchedSharedCredential = await Db.collections.SharedCredentials.findOne({
|
|
||||||
where: {
|
|
||||||
credentialsId: credential.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(untouchedSharedCredential).toBeDefined(); // not deleted
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /credentials/:id should not delete non-owned cred for member', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() });
|
|
||||||
|
|
||||||
const authMemberAgent = utils.createAgent(app, {
|
|
||||||
apiPath: 'public',
|
|
||||||
version: 1,
|
|
||||||
auth: true,
|
|
||||||
user: member,
|
|
||||||
});
|
|
||||||
const savedCredential = await saveCredential(dbCredential(), { user: ownerShell });
|
|
||||||
|
|
||||||
const response = await authMemberAgent.delete(`/credentials/${savedCredential.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
|
||||||
|
|
||||||
const shellCredential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
|
|
||||||
|
|
||||||
expect(shellCredential).toBeDefined(); // not deleted
|
|
||||||
|
|
||||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
|
||||||
|
|
||||||
expect(deletedSharedCredential).toBeDefined(); // not deleted
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /credentials/:id should fail if cred not found', async () => {
|
|
||||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
ownerShell = await testDb.addApiKey(ownerShell);
|
|
||||||
|
|
||||||
const authOwnerAgent = utils.createAgent(app, {
|
|
||||||
apiPath: 'public',
|
|
||||||
version: 1,
|
|
||||||
auth: true,
|
|
||||||
user: ownerShell,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.delete('/credentials/123');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /credentials/schema/:credentialType should fail due to not found type', async () => {
|
|
||||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
ownerShell = await testDb.addApiKey(ownerShell);
|
|
||||||
|
|
||||||
const authOwnerAgent = utils.createAgent(app, {
|
|
||||||
apiPath: 'public',
|
|
||||||
version: 1,
|
|
||||||
auth: true,
|
|
||||||
user: ownerShell,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.get('/credentials/schema/testing');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /credentials/schema/:credentialType should retrieve credential type', async () => {
|
|
||||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
ownerShell = await testDb.addApiKey(ownerShell);
|
|
||||||
|
|
||||||
const authOwnerAgent = utils.createAgent(app, {
|
|
||||||
apiPath: 'public',
|
|
||||||
version: 1,
|
|
||||||
auth: true,
|
|
||||||
user: ownerShell,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.get('/credentials/schema/githubApi');
|
|
||||||
|
|
||||||
const { additionalProperties, type, properties, required } = response.body;
|
|
||||||
|
|
||||||
expect(additionalProperties).toBe(false);
|
|
||||||
expect(type).toBe('object');
|
|
||||||
expect(properties.server).toBeDefined();
|
|
||||||
expect(properties.server.type).toBe('string');
|
|
||||||
expect(properties.user.type).toBeDefined();
|
|
||||||
expect(properties.user.type).toBe('string');
|
|
||||||
expect(properties.accessToken.type).toBeDefined();
|
|
||||||
expect(properties.accessToken.type).toBe('string');
|
|
||||||
expect(required).toEqual(expect.arrayContaining(['server', 'user', 'accessToken']));
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const credentialPayload = (): CredentialPayload => ({
|
const credentialPayload = (): CredentialPayload => ({
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import express from 'express';
|
import type { Application } from 'express';
|
||||||
|
import type { SuperAgentTest } from 'supertest';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { Role } from '@db/entities/Role';
|
import type { User } from '@db/entities/User';
|
||||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
import type { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||||
|
|
||||||
import { randomApiKey } from '../shared/random';
|
import { randomApiKey } from '../shared/random';
|
||||||
import * as utils from '../shared/utils';
|
import * as utils from '../shared/utils';
|
||||||
import * as testDb from '../shared/testDb';
|
import * as testDb from '../shared/testDb';
|
||||||
|
|
||||||
let app: express.Application;
|
let app: Application;
|
||||||
let globalOwnerRole: Role;
|
let owner: User;
|
||||||
|
let authOwnerAgent: SuperAgentTest;
|
||||||
let workflowRunner: ActiveWorkflowRunner;
|
let workflowRunner: ActiveWorkflowRunner;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
@ -19,7 +20,8 @@ beforeAll(async () => {
|
||||||
enablePublicAPI: true,
|
enablePublicAPI: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
const globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||||
|
owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
||||||
|
|
||||||
await utils.initBinaryManager();
|
await utils.initBinaryManager();
|
||||||
await utils.initNodeTypes();
|
await utils.initNodeTypes();
|
||||||
|
@ -31,13 +33,19 @@ beforeEach(async () => {
|
||||||
await testDb.truncate([
|
await testDb.truncate([
|
||||||
'SharedCredentials',
|
'SharedCredentials',
|
||||||
'SharedWorkflow',
|
'SharedWorkflow',
|
||||||
'User',
|
|
||||||
'Workflow',
|
'Workflow',
|
||||||
'Credentials',
|
'Credentials',
|
||||||
'Execution',
|
'Execution',
|
||||||
'Settings',
|
'Settings',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
authOwnerAgent = utils.createAgent(app, {
|
||||||
|
apiPath: 'public',
|
||||||
|
auth: true,
|
||||||
|
user: owner,
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
|
||||||
config.set('userManagement.disabled', false);
|
config.set('userManagement.disabled', false);
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||||
});
|
});
|
||||||
|
@ -50,270 +58,27 @@ afterAll(async () => {
|
||||||
await testDb.terminate();
|
await testDb.terminate();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /executions/:id should fail due to missing API Key', async () => {
|
const testWithAPIKey =
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
(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);
|
||||||
|
};
|
||||||
|
|
||||||
const authOwnerAgent = utils.createAgent(app, {
|
describe('GET /executions/:id', () => {
|
||||||
apiPath: 'public',
|
test('should fail due to missing API Key', testWithAPIKey('get', '/executions/1', null));
|
||||||
auth: true,
|
|
||||||
user: owner,
|
|
||||||
version: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.get('/executions/1');
|
test('should fail due to invalid API Key', testWithAPIKey('get', '/executions/1', 'abcXYZ'));
|
||||||
|
|
||||||
expect(response.statusCode).toBe(401);
|
test('should get an execution', async () => {
|
||||||
});
|
const workflow = await testDb.createWorkflow({}, owner);
|
||||||
|
|
||||||
test('GET /executions/:id should fail due to invalid API Key', async () => {
|
const execution = await testDb.createSuccessfulExecution(workflow);
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
|
||||||
owner.apiKey = 'abcXYZ';
|
|
||||||
|
|
||||||
const authOwnerAgent = utils.createAgent(app, {
|
const response = await authOwnerAgent.get(`/executions/${execution.id}`);
|
||||||
apiPath: 'public',
|
|
||||||
auth: true,
|
|
||||||
user: owner,
|
|
||||||
version: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.get('/executions/1');
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(401);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /executions/:id should get an execution', 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 execution = await testDb.createSuccessfulExecution(workflow);
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.get(`/executions/${execution.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
finished,
|
|
||||||
mode,
|
|
||||||
retryOf,
|
|
||||||
retrySuccessId,
|
|
||||||
startedAt,
|
|
||||||
stoppedAt,
|
|
||||||
workflowId,
|
|
||||||
waitTill,
|
|
||||||
} = response.body;
|
|
||||||
|
|
||||||
expect(id).toBeDefined();
|
|
||||||
expect(finished).toBe(true);
|
|
||||||
expect(mode).toEqual(execution.mode);
|
|
||||||
expect(retrySuccessId).toBeNull();
|
|
||||||
expect(retryOf).toBeNull();
|
|
||||||
expect(startedAt).not.toBeNull();
|
|
||||||
expect(stoppedAt).not.toBeNull();
|
|
||||||
expect(workflowId).toBe(execution.workflowId);
|
|
||||||
expect(waitTill).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /executions/: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('/executions/1');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(401);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /executions/: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('/executions/1');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(401);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /executions/:id should delete an execution', 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 execution = await testDb.createSuccessfulExecution(workflow);
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.delete(`/executions/${execution.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
finished,
|
|
||||||
mode,
|
|
||||||
retryOf,
|
|
||||||
retrySuccessId,
|
|
||||||
startedAt,
|
|
||||||
stoppedAt,
|
|
||||||
workflowId,
|
|
||||||
waitTill,
|
|
||||||
} = response.body;
|
|
||||||
|
|
||||||
expect(id).toBeDefined();
|
|
||||||
expect(finished).toBe(true);
|
|
||||||
expect(mode).toEqual(execution.mode);
|
|
||||||
expect(retrySuccessId).toBeNull();
|
|
||||||
expect(retryOf).toBeNull();
|
|
||||||
expect(startedAt).not.toBeNull();
|
|
||||||
expect(stoppedAt).not.toBeNull();
|
|
||||||
expect(workflowId).toBe(execution.workflowId);
|
|
||||||
expect(waitTill).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /executions 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('/executions');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(401);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /executions 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('/executions');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(401);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /executions should retrieve all successful executions', 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 successfullExecution = await testDb.createSuccessfulExecution(workflow);
|
|
||||||
|
|
||||||
await testDb.createErrorExecution(workflow);
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.get(`/executions`).query({
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body.data.length).toBe(1);
|
|
||||||
expect(response.body.nextCursor).toBe(null);
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
finished,
|
|
||||||
mode,
|
|
||||||
retryOf,
|
|
||||||
retrySuccessId,
|
|
||||||
startedAt,
|
|
||||||
stoppedAt,
|
|
||||||
workflowId,
|
|
||||||
waitTill,
|
|
||||||
} = response.body.data[0];
|
|
||||||
|
|
||||||
expect(id).toBeDefined();
|
|
||||||
expect(finished).toBe(true);
|
|
||||||
expect(mode).toEqual(successfullExecution.mode);
|
|
||||||
expect(retrySuccessId).toBeNull();
|
|
||||||
expect(retryOf).toBeNull();
|
|
||||||
expect(startedAt).not.toBeNull();
|
|
||||||
expect(stoppedAt).not.toBeNull();
|
|
||||||
expect(workflowId).toBe(successfullExecution.workflowId);
|
|
||||||
expect(waitTill).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
// failing on Postgres and MySQL - ref: https://github.com/n8n-io/n8n/pull/3834
|
|
||||||
test.skip('GET /executions should paginate two executions', 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 firstSuccessfulExecution = await testDb.createSuccessfulExecution(workflow);
|
|
||||||
|
|
||||||
const secondSuccessfulExecution = await testDb.createSuccessfulExecution(workflow);
|
|
||||||
|
|
||||||
await testDb.createErrorExecution(workflow);
|
|
||||||
|
|
||||||
const firstExecutionResponse = await authOwnerAgent.get(`/executions`).query({
|
|
||||||
status: 'success',
|
|
||||||
limit: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(firstExecutionResponse.statusCode).toBe(200);
|
|
||||||
expect(firstExecutionResponse.body.data.length).toBe(1);
|
|
||||||
expect(firstExecutionResponse.body.nextCursor).toBeDefined();
|
|
||||||
|
|
||||||
const secondExecutionResponse = await authOwnerAgent.get(`/executions`).query({
|
|
||||||
status: 'success',
|
|
||||||
limit: 1,
|
|
||||||
cursor: firstExecutionResponse.body.nextCursor,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(secondExecutionResponse.statusCode).toBe(200);
|
|
||||||
expect(secondExecutionResponse.body.data.length).toBe(1);
|
|
||||||
expect(secondExecutionResponse.body.nextCursor).toBeNull();
|
|
||||||
|
|
||||||
const successfulExecutions = [firstSuccessfulExecution, secondSuccessfulExecution];
|
|
||||||
const executions = [...firstExecutionResponse.body.data, ...secondExecutionResponse.body.data];
|
|
||||||
|
|
||||||
for (let i = 0; i < executions.length; i++) {
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
finished,
|
finished,
|
||||||
|
@ -324,146 +89,33 @@ test.skip('GET /executions should paginate two executions', async () => {
|
||||||
stoppedAt,
|
stoppedAt,
|
||||||
workflowId,
|
workflowId,
|
||||||
waitTill,
|
waitTill,
|
||||||
} = executions[i];
|
} = response.body;
|
||||||
|
|
||||||
expect(id).toBeDefined();
|
expect(id).toBeDefined();
|
||||||
expect(finished).toBe(true);
|
expect(finished).toBe(true);
|
||||||
expect(mode).toEqual(successfulExecutions[i].mode);
|
expect(mode).toEqual(execution.mode);
|
||||||
expect(retrySuccessId).toBeNull();
|
expect(retrySuccessId).toBeNull();
|
||||||
expect(retryOf).toBeNull();
|
expect(retryOf).toBeNull();
|
||||||
expect(startedAt).not.toBeNull();
|
expect(startedAt).not.toBeNull();
|
||||||
expect(stoppedAt).not.toBeNull();
|
expect(stoppedAt).not.toBeNull();
|
||||||
expect(workflowId).toBe(successfulExecutions[i].workflowId);
|
expect(workflowId).toBe(execution.workflowId);
|
||||||
expect(waitTill).toBeNull();
|
expect(waitTill).toBeNull();
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /executions should retrieve all error executions', async () => {
|
describe('DELETE /executions/:id', () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
test('should fail due to missing API Key', testWithAPIKey('delete', '/executions/1', null));
|
||||||
|
|
||||||
const authOwnerAgent = utils.createAgent(app, {
|
test('should fail due to invalid API Key', testWithAPIKey('delete', '/executions/1', 'abcXYZ'));
|
||||||
apiPath: 'public',
|
|
||||||
auth: true,
|
|
||||||
user: owner,
|
|
||||||
version: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const workflow = await testDb.createWorkflow({}, owner);
|
test('should delete an execution', async () => {
|
||||||
|
const workflow = await testDb.createWorkflow({}, owner);
|
||||||
|
const execution = await testDb.createSuccessfulExecution(workflow);
|
||||||
|
|
||||||
await testDb.createSuccessfulExecution(workflow);
|
const response = await authOwnerAgent.delete(`/executions/${execution.id}`);
|
||||||
|
|
||||||
const errorExecution = await testDb.createErrorExecution(workflow);
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
const response = await authOwnerAgent.get(`/executions`).query({
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body.data.length).toBe(1);
|
|
||||||
expect(response.body.nextCursor).toBe(null);
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
finished,
|
|
||||||
mode,
|
|
||||||
retryOf,
|
|
||||||
retrySuccessId,
|
|
||||||
startedAt,
|
|
||||||
stoppedAt,
|
|
||||||
workflowId,
|
|
||||||
waitTill,
|
|
||||||
} = response.body.data[0];
|
|
||||||
|
|
||||||
expect(id).toBeDefined();
|
|
||||||
expect(finished).toBe(false);
|
|
||||||
expect(mode).toEqual(errorExecution.mode);
|
|
||||||
expect(retrySuccessId).toBeNull();
|
|
||||||
expect(retryOf).toBeNull();
|
|
||||||
expect(startedAt).not.toBeNull();
|
|
||||||
expect(stoppedAt).not.toBeNull();
|
|
||||||
expect(workflowId).toBe(errorExecution.workflowId);
|
|
||||||
expect(waitTill).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /executions should return all waiting executions', 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);
|
|
||||||
|
|
||||||
await testDb.createSuccessfulExecution(workflow);
|
|
||||||
|
|
||||||
await testDb.createErrorExecution(workflow);
|
|
||||||
|
|
||||||
const waitingExecution = await testDb.createWaitingExecution(workflow);
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.get(`/executions`).query({
|
|
||||||
status: 'waiting',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body.data.length).toBe(1);
|
|
||||||
expect(response.body.nextCursor).toBe(null);
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
finished,
|
|
||||||
mode,
|
|
||||||
retryOf,
|
|
||||||
retrySuccessId,
|
|
||||||
startedAt,
|
|
||||||
stoppedAt,
|
|
||||||
workflowId,
|
|
||||||
waitTill,
|
|
||||||
} = response.body.data[0];
|
|
||||||
|
|
||||||
expect(id).toBeDefined();
|
|
||||||
expect(finished).toBe(false);
|
|
||||||
expect(mode).toEqual(waitingExecution.mode);
|
|
||||||
expect(retrySuccessId).toBeNull();
|
|
||||||
expect(retryOf).toBeNull();
|
|
||||||
expect(startedAt).not.toBeNull();
|
|
||||||
expect(stoppedAt).not.toBeNull();
|
|
||||||
expect(workflowId).toBe(waitingExecution.workflowId);
|
|
||||||
expect(new Date(waitTill).getTime()).toBeGreaterThan(Date.now() - 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /executions should retrieve all executions of specific 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 [workflow, workflow2] = await testDb.createManyWorkflows(2, {}, owner);
|
|
||||||
|
|
||||||
const savedExecutions = await testDb.createManyExecutions(
|
|
||||||
2,
|
|
||||||
workflow,
|
|
||||||
// @ts-ignore
|
|
||||||
testDb.createSuccessfulExecution,
|
|
||||||
);
|
|
||||||
// @ts-ignore
|
|
||||||
await testDb.createManyExecutions(2, workflow2, testDb.createSuccessfulExecution);
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.get(`/executions`).query({
|
|
||||||
workflowId: workflow.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body.data.length).toBe(2);
|
|
||||||
expect(response.body.nextCursor).toBe(null);
|
|
||||||
|
|
||||||
for (const execution of response.body.data) {
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
finished,
|
finished,
|
||||||
|
@ -474,16 +126,240 @@ test('GET /executions should retrieve all executions of specific workflow', asyn
|
||||||
stoppedAt,
|
stoppedAt,
|
||||||
workflowId,
|
workflowId,
|
||||||
waitTill,
|
waitTill,
|
||||||
} = execution;
|
} = response.body;
|
||||||
|
|
||||||
expect(savedExecutions.some((exec) => exec.id === id)).toBe(true);
|
expect(id).toBeDefined();
|
||||||
expect(finished).toBe(true);
|
expect(finished).toBe(true);
|
||||||
expect(mode).toBeDefined();
|
expect(mode).toEqual(execution.mode);
|
||||||
expect(retrySuccessId).toBeNull();
|
expect(retrySuccessId).toBeNull();
|
||||||
expect(retryOf).toBeNull();
|
expect(retryOf).toBeNull();
|
||||||
expect(startedAt).not.toBeNull();
|
expect(startedAt).not.toBeNull();
|
||||||
expect(stoppedAt).not.toBeNull();
|
expect(stoppedAt).not.toBeNull();
|
||||||
expect(workflowId).toBe(workflow.id);
|
expect(workflowId).toBe(execution.workflowId);
|
||||||
expect(waitTill).toBeNull();
|
expect(waitTill).toBeNull();
|
||||||
}
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /executions', () => {
|
||||||
|
test('should fail due to missing API Key', testWithAPIKey('get', '/executions', null));
|
||||||
|
|
||||||
|
test('should fail due to invalid API Key', testWithAPIKey('get', '/executions', 'abcXYZ'));
|
||||||
|
|
||||||
|
test('should retrieve all successful executions', async () => {
|
||||||
|
const workflow = await testDb.createWorkflow({}, owner);
|
||||||
|
|
||||||
|
const successfulExecution = await testDb.createSuccessfulExecution(workflow);
|
||||||
|
|
||||||
|
await testDb.createErrorExecution(workflow);
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.get(`/executions`).query({
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body.data.length).toBe(1);
|
||||||
|
expect(response.body.nextCursor).toBe(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
finished,
|
||||||
|
mode,
|
||||||
|
retryOf,
|
||||||
|
retrySuccessId,
|
||||||
|
startedAt,
|
||||||
|
stoppedAt,
|
||||||
|
workflowId,
|
||||||
|
waitTill,
|
||||||
|
} = response.body.data[0];
|
||||||
|
|
||||||
|
expect(id).toBeDefined();
|
||||||
|
expect(finished).toBe(true);
|
||||||
|
expect(mode).toEqual(successfulExecution.mode);
|
||||||
|
expect(retrySuccessId).toBeNull();
|
||||||
|
expect(retryOf).toBeNull();
|
||||||
|
expect(startedAt).not.toBeNull();
|
||||||
|
expect(stoppedAt).not.toBeNull();
|
||||||
|
expect(workflowId).toBe(successfulExecution.workflowId);
|
||||||
|
expect(waitTill).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
// failing on Postgres and MySQL - ref: https://github.com/n8n-io/n8n/pull/3834
|
||||||
|
test.skip('should paginate two executions', async () => {
|
||||||
|
const workflow = await testDb.createWorkflow({}, owner);
|
||||||
|
|
||||||
|
const firstSuccessfulExecution = await testDb.createSuccessfulExecution(workflow);
|
||||||
|
|
||||||
|
const secondSuccessfulExecution = await testDb.createSuccessfulExecution(workflow);
|
||||||
|
|
||||||
|
await testDb.createErrorExecution(workflow);
|
||||||
|
|
||||||
|
const firstExecutionResponse = await authOwnerAgent.get(`/executions`).query({
|
||||||
|
status: 'success',
|
||||||
|
limit: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(firstExecutionResponse.statusCode).toBe(200);
|
||||||
|
expect(firstExecutionResponse.body.data.length).toBe(1);
|
||||||
|
expect(firstExecutionResponse.body.nextCursor).toBeDefined();
|
||||||
|
|
||||||
|
const secondExecutionResponse = await authOwnerAgent.get(`/executions`).query({
|
||||||
|
status: 'success',
|
||||||
|
limit: 1,
|
||||||
|
cursor: firstExecutionResponse.body.nextCursor,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(secondExecutionResponse.statusCode).toBe(200);
|
||||||
|
expect(secondExecutionResponse.body.data.length).toBe(1);
|
||||||
|
expect(secondExecutionResponse.body.nextCursor).toBeNull();
|
||||||
|
|
||||||
|
const successfulExecutions = [firstSuccessfulExecution, secondSuccessfulExecution];
|
||||||
|
const executions = [...firstExecutionResponse.body.data, ...secondExecutionResponse.body.data];
|
||||||
|
|
||||||
|
for (let i = 0; i < executions.length; i++) {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
finished,
|
||||||
|
mode,
|
||||||
|
retryOf,
|
||||||
|
retrySuccessId,
|
||||||
|
startedAt,
|
||||||
|
stoppedAt,
|
||||||
|
workflowId,
|
||||||
|
waitTill,
|
||||||
|
} = executions[i];
|
||||||
|
|
||||||
|
expect(id).toBeDefined();
|
||||||
|
expect(finished).toBe(true);
|
||||||
|
expect(mode).toEqual(successfulExecutions[i].mode);
|
||||||
|
expect(retrySuccessId).toBeNull();
|
||||||
|
expect(retryOf).toBeNull();
|
||||||
|
expect(startedAt).not.toBeNull();
|
||||||
|
expect(stoppedAt).not.toBeNull();
|
||||||
|
expect(workflowId).toBe(successfulExecutions[i].workflowId);
|
||||||
|
expect(waitTill).toBeNull();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should retrieve all error executions', async () => {
|
||||||
|
const workflow = await testDb.createWorkflow({}, owner);
|
||||||
|
|
||||||
|
await testDb.createSuccessfulExecution(workflow);
|
||||||
|
|
||||||
|
const errorExecution = await testDb.createErrorExecution(workflow);
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.get(`/executions`).query({
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body.data.length).toBe(1);
|
||||||
|
expect(response.body.nextCursor).toBe(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
finished,
|
||||||
|
mode,
|
||||||
|
retryOf,
|
||||||
|
retrySuccessId,
|
||||||
|
startedAt,
|
||||||
|
stoppedAt,
|
||||||
|
workflowId,
|
||||||
|
waitTill,
|
||||||
|
} = response.body.data[0];
|
||||||
|
|
||||||
|
expect(id).toBeDefined();
|
||||||
|
expect(finished).toBe(false);
|
||||||
|
expect(mode).toEqual(errorExecution.mode);
|
||||||
|
expect(retrySuccessId).toBeNull();
|
||||||
|
expect(retryOf).toBeNull();
|
||||||
|
expect(startedAt).not.toBeNull();
|
||||||
|
expect(stoppedAt).not.toBeNull();
|
||||||
|
expect(workflowId).toBe(errorExecution.workflowId);
|
||||||
|
expect(waitTill).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return all waiting executions', async () => {
|
||||||
|
const workflow = await testDb.createWorkflow({}, owner);
|
||||||
|
|
||||||
|
await testDb.createSuccessfulExecution(workflow);
|
||||||
|
|
||||||
|
await testDb.createErrorExecution(workflow);
|
||||||
|
|
||||||
|
const waitingExecution = await testDb.createWaitingExecution(workflow);
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.get(`/executions`).query({
|
||||||
|
status: 'waiting',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body.data.length).toBe(1);
|
||||||
|
expect(response.body.nextCursor).toBe(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
finished,
|
||||||
|
mode,
|
||||||
|
retryOf,
|
||||||
|
retrySuccessId,
|
||||||
|
startedAt,
|
||||||
|
stoppedAt,
|
||||||
|
workflowId,
|
||||||
|
waitTill,
|
||||||
|
} = response.body.data[0];
|
||||||
|
|
||||||
|
expect(id).toBeDefined();
|
||||||
|
expect(finished).toBe(false);
|
||||||
|
expect(mode).toEqual(waitingExecution.mode);
|
||||||
|
expect(retrySuccessId).toBeNull();
|
||||||
|
expect(retryOf).toBeNull();
|
||||||
|
expect(startedAt).not.toBeNull();
|
||||||
|
expect(stoppedAt).not.toBeNull();
|
||||||
|
expect(workflowId).toBe(waitingExecution.workflowId);
|
||||||
|
expect(new Date(waitTill).getTime()).toBeGreaterThan(Date.now() - 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should retrieve all executions of specific workflow', async () => {
|
||||||
|
const [workflow, workflow2] = await testDb.createManyWorkflows(2, {}, owner);
|
||||||
|
|
||||||
|
const savedExecutions = await testDb.createManyExecutions(
|
||||||
|
2,
|
||||||
|
workflow,
|
||||||
|
// @ts-ignore
|
||||||
|
testDb.createSuccessfulExecution,
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
await testDb.createManyExecutions(2, workflow2, testDb.createSuccessfulExecution);
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.get(`/executions`).query({
|
||||||
|
workflowId: workflow.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body.data.length).toBe(2);
|
||||||
|
expect(response.body.nextCursor).toBe(null);
|
||||||
|
|
||||||
|
for (const execution of response.body.data) {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
finished,
|
||||||
|
mode,
|
||||||
|
retryOf,
|
||||||
|
retrySuccessId,
|
||||||
|
startedAt,
|
||||||
|
stoppedAt,
|
||||||
|
workflowId,
|
||||||
|
waitTill,
|
||||||
|
} = execution;
|
||||||
|
|
||||||
|
expect(savedExecutions.some((exec) => exec.id === id)).toBe(true);
|
||||||
|
expect(finished).toBe(true);
|
||||||
|
expect(mode).toBeDefined();
|
||||||
|
expect(retrySuccessId).toBeNull();
|
||||||
|
expect(retryOf).toBeNull();
|
||||||
|
expect(startedAt).not.toBeNull();
|
||||||
|
expect(stoppedAt).not.toBeNull();
|
||||||
|
expect(workflowId).toBe(workflow.id);
|
||||||
|
expect(waitTill).toBeNull();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,18 +1,14 @@
|
||||||
import express from 'express';
|
import type { SuperAgentTest } from 'supertest';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { User } from '@db/entities/User';
|
||||||
|
import { setSamlLoginEnabled } from '@/sso/saml/samlHelpers';
|
||||||
|
import { setCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
|
||||||
import { randomEmail, randomName, randomValidPassword } from '../shared/random';
|
import { randomEmail, randomName, randomValidPassword } from '../shared/random';
|
||||||
import * as testDb from '../shared/testDb';
|
import * as testDb from '../shared/testDb';
|
||||||
import type { AuthAgent } from '../shared/types';
|
|
||||||
import * as utils from '../shared/utils';
|
import * as utils from '../shared/utils';
|
||||||
import { setSamlLoginEnabled } from '../../../src/sso/saml/samlHelpers';
|
|
||||||
import { setCurrentAuthenticationMethod } from '../../../src/sso/ssoHelpers';
|
|
||||||
|
|
||||||
let app: express.Application;
|
let owner: User;
|
||||||
let globalOwnerRole: Role;
|
let authOwnerAgent: SuperAgentTest;
|
||||||
let globalMemberRole: Role;
|
|
||||||
let authAgent: AuthAgent;
|
|
||||||
|
|
||||||
function enableSaml(enable: boolean) {
|
function enableSaml(enable: boolean) {
|
||||||
setSamlLoginEnabled(enable);
|
setSamlLoginEnabled(enable);
|
||||||
|
@ -21,58 +17,59 @@ function enableSaml(enable: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({ endpointGroups: ['me'] });
|
const app = await utils.initTestServer({ endpointGroups: ['me'] });
|
||||||
|
owner = await testDb.createOwner();
|
||||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
authOwnerAgent = utils.createAuthAgent(app)(owner);
|
||||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
|
||||||
|
|
||||||
authAgent = utils.createAuthAgent(app);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
// beforeEach(async () => {
|
||||||
await testDb.truncate(['User']);
|
// await testDb.truncate(['User']);
|
||||||
});
|
// });
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await testDb.terminate();
|
await testDb.terminate();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Instance owner', () => {
|
describe('Instance owner', () => {
|
||||||
test('PATCH /me should succeed with valid inputs', async () => {
|
describe('PATCH /me', () => {
|
||||||
const owner = await testDb.createOwner();
|
test('should succeed with valid inputs', async () => {
|
||||||
const authOwnerAgent = authAgent(owner);
|
enableSaml(false);
|
||||||
const response = await authOwnerAgent.patch('/me').send({
|
await authOwnerAgent
|
||||||
email: randomEmail(),
|
.patch('/me')
|
||||||
firstName: randomName(),
|
.send({
|
||||||
lastName: randomName(),
|
email: randomEmail(),
|
||||||
password: randomValidPassword(),
|
firstName: randomName(),
|
||||||
|
lastName: randomName(),
|
||||||
|
password: randomValidPassword(),
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw BadRequestError if email is changed when SAML is enabled', async () => {
|
||||||
|
enableSaml(true);
|
||||||
|
await authOwnerAgent
|
||||||
|
.patch('/me')
|
||||||
|
.send({
|
||||||
|
email: randomEmail(),
|
||||||
|
firstName: randomName(),
|
||||||
|
lastName: randomName(),
|
||||||
|
})
|
||||||
|
.expect(400, { code: 400, message: 'SAML user may not change their email' });
|
||||||
});
|
});
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PATCH /me should throw BadRequestError if email is changed when SAML is enabled', async () => {
|
describe('PATCH /password', () => {
|
||||||
enableSaml(true);
|
test('should throw BadRequestError if password is changed when SAML is enabled', async () => {
|
||||||
const owner = await testDb.createOwner();
|
enableSaml(true);
|
||||||
const authOwnerAgent = authAgent(owner);
|
await authOwnerAgent
|
||||||
const response = await authOwnerAgent.patch('/me').send({
|
.patch('/me/password')
|
||||||
email: randomEmail(),
|
.send({
|
||||||
firstName: randomName(),
|
password: randomValidPassword(),
|
||||||
lastName: randomName(),
|
})
|
||||||
|
.expect(400, {
|
||||||
|
code: 400,
|
||||||
|
message: 'With SAML enabled, users need to use their SAML provider to change passwords',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
expect(response.body.message).toContain('SAML');
|
|
||||||
enableSaml(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PATCH /password should throw BadRequestError if password is changed when SAML is enabled', async () => {
|
|
||||||
enableSaml(true);
|
|
||||||
const owner = await testDb.createOwner();
|
|
||||||
const authOwnerAgent = authAgent(owner);
|
|
||||||
const response = await authOwnerAgent.patch('/me/password').send({
|
|
||||||
password: randomValidPassword(),
|
|
||||||
});
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
expect(response.body.message).toContain('SAML');
|
|
||||||
enableSaml(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import superagent = require('superagent');
|
import superagent = require('superagent');
|
||||||
import type { ObjectLiteral } from 'typeorm';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make `SuperTest<T>` string-indexable.
|
* Make `SuperTest<T>` string-indexable.
|
||||||
|
|
|
@ -9,13 +9,11 @@ import set from 'lodash.set';
|
||||||
import { BinaryDataManager, UserSettings } from 'n8n-core';
|
import { BinaryDataManager, UserSettings } from 'n8n-core';
|
||||||
import {
|
import {
|
||||||
ICredentialType,
|
ICredentialType,
|
||||||
ICredentialTypes,
|
|
||||||
IDataObject,
|
IDataObject,
|
||||||
IExecuteFunctions,
|
IExecuteFunctions,
|
||||||
INode,
|
INode,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodesAndCredentials,
|
|
||||||
ITriggerFunctions,
|
ITriggerFunctions,
|
||||||
ITriggerResponse,
|
ITriggerResponse,
|
||||||
LoggerProxy,
|
LoggerProxy,
|
||||||
|
@ -90,13 +88,6 @@ export const mockInstance = <T>(
|
||||||
return instance;
|
return instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadNodesAndCredentials: INodesAndCredentials = {
|
|
||||||
loaded: { nodes: {}, credentials: {} },
|
|
||||||
known: { nodes: {}, credentials: {} },
|
|
||||||
credentialTypes: {} as ICredentialTypes,
|
|
||||||
};
|
|
||||||
Container.set(LoadNodesAndCredentials, loadNodesAndCredentials);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a test server.
|
* Initialize a test server.
|
||||||
*/
|
*/
|
||||||
|
@ -740,6 +731,15 @@ export async function isInstanceOwnerSetUp() {
|
||||||
return Boolean(value);
|
return Boolean(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const setInstanceOwnerSetUp = async (value: boolean) => {
|
||||||
|
config.set('userManagement.isInstanceOwnerSetUp', value);
|
||||||
|
|
||||||
|
await Db.collections.Settings.update(
|
||||||
|
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||||
|
{ value: JSON.stringify(value) },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// misc
|
// misc
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import express from 'express';
|
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
|
import { Not } from 'typeorm';
|
||||||
|
import type { SuperAgentTest } from 'supertest';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
|
@ -8,6 +9,9 @@ import type { Role } from '@db/entities/Role';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import { compareHash } from '@/UserManagement/UserManagementHelper';
|
import { compareHash } from '@/UserManagement/UserManagementHelper';
|
||||||
|
import { UserManagementMailer } from '@/UserManagement/email/UserManagementMailer';
|
||||||
|
import { NodeMailer } from '@/UserManagement/email/NodeMailer';
|
||||||
|
|
||||||
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
||||||
import {
|
import {
|
||||||
randomCredentialPayload,
|
randomCredentialPayload,
|
||||||
|
@ -17,41 +21,40 @@ import {
|
||||||
randomValidPassword,
|
randomValidPassword,
|
||||||
} from './shared/random';
|
} from './shared/random';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import type { AuthAgent } from './shared/types';
|
|
||||||
import * as utils from './shared/utils';
|
import * as utils from './shared/utils';
|
||||||
|
|
||||||
import { UserManagementMailer } from '@/UserManagement/email/UserManagementMailer';
|
|
||||||
import { NodeMailer } from '@/UserManagement/email/NodeMailer';
|
|
||||||
|
|
||||||
jest.mock('@/UserManagement/email/NodeMailer');
|
jest.mock('@/UserManagement/email/NodeMailer');
|
||||||
|
|
||||||
let app: express.Application;
|
|
||||||
let globalMemberRole: Role;
|
let globalMemberRole: Role;
|
||||||
let globalOwnerRole: Role;
|
|
||||||
let workflowOwnerRole: Role;
|
let workflowOwnerRole: Role;
|
||||||
let credentialOwnerRole: Role;
|
let credentialOwnerRole: Role;
|
||||||
let authAgent: AuthAgent;
|
let owner: User;
|
||||||
|
let authlessAgent: SuperAgentTest;
|
||||||
|
let authOwnerAgent: SuperAgentTest;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({ endpointGroups: ['users'] });
|
const app = await utils.initTestServer({ endpointGroups: ['users'] });
|
||||||
|
|
||||||
const [
|
const [
|
||||||
fetchedGlobalOwnerRole,
|
globalOwnerRole,
|
||||||
fetchedGlobalMemberRole,
|
fetchedGlobalMemberRole,
|
||||||
fetchedWorkflowOwnerRole,
|
fetchedWorkflowOwnerRole,
|
||||||
fetchedCredentialOwnerRole,
|
fetchedCredentialOwnerRole,
|
||||||
] = await testDb.getAllRoles();
|
] = await testDb.getAllRoles();
|
||||||
|
|
||||||
globalOwnerRole = fetchedGlobalOwnerRole;
|
|
||||||
globalMemberRole = fetchedGlobalMemberRole;
|
globalMemberRole = fetchedGlobalMemberRole;
|
||||||
workflowOwnerRole = fetchedWorkflowOwnerRole;
|
workflowOwnerRole = fetchedWorkflowOwnerRole;
|
||||||
credentialOwnerRole = fetchedCredentialOwnerRole;
|
credentialOwnerRole = fetchedCredentialOwnerRole;
|
||||||
|
|
||||||
authAgent = utils.createAuthAgent(app);
|
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||||
|
|
||||||
|
authlessAgent = utils.createAgent(app);
|
||||||
|
authOwnerAgent = utils.createAuthAgent(app)(owner);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await testDb.truncate(['User', 'SharedCredentials', 'SharedWorkflow', 'Workflow', 'Credentials']);
|
await testDb.truncate(['SharedCredentials', 'SharedWorkflow', 'Workflow', 'Credentials']);
|
||||||
|
await Db.collections.User.delete({ id: Not(owner.id) });
|
||||||
|
|
||||||
jest.mock('@/config');
|
jest.mock('@/config');
|
||||||
|
|
||||||
|
@ -65,18 +68,16 @@ afterAll(async () => {
|
||||||
await testDb.terminate();
|
await testDb.terminate();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /users should return all users', async () => {
|
describe('GET /users', () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
test('should return all users', async () => {
|
||||||
|
await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
|
||||||
await testDb.createUser({ globalRole: globalMemberRole });
|
const response = await authOwnerAgent.get('/users');
|
||||||
|
|
||||||
const response = await authAgent(owner).get('/users');
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body.data.length).toBe(2);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
response.body.data.map((user: User) => {
|
||||||
expect(response.body.data.length).toBe(2);
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
response.body.data.map(async (user: User) => {
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
email,
|
email,
|
||||||
|
@ -100,442 +101,421 @@ test('GET /users should return all users', async () => {
|
||||||
expect(isPending).toBe(false);
|
expect(isPending).toBe(false);
|
||||||
expect(globalRole).toBeDefined();
|
expect(globalRole).toBeDefined();
|
||||||
expect(apiKey).not.toBeDefined();
|
expect(apiKey).not.toBeDefined();
|
||||||
}),
|
});
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('DELETE /users/:id should delete the user', async () => {
|
describe('DELETE /users/:id', () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
test('should delete the user', async () => {
|
||||||
|
const userToDelete = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
|
||||||
const userToDelete = await testDb.createUser({ globalRole: globalMemberRole });
|
const newWorkflow = new WorkflowEntity();
|
||||||
|
|
||||||
const newWorkflow = new WorkflowEntity();
|
Object.assign(newWorkflow, {
|
||||||
|
name: randomName(),
|
||||||
|
active: false,
|
||||||
|
connections: {},
|
||||||
|
nodes: [],
|
||||||
|
});
|
||||||
|
|
||||||
Object.assign(newWorkflow, {
|
const savedWorkflow = await Db.collections.Workflow.save(newWorkflow);
|
||||||
name: randomName(),
|
|
||||||
active: false,
|
await Db.collections.SharedWorkflow.save({
|
||||||
connections: {},
|
role: workflowOwnerRole,
|
||||||
nodes: [],
|
user: userToDelete,
|
||||||
|
workflow: savedWorkflow,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newCredential = new CredentialsEntity();
|
||||||
|
|
||||||
|
Object.assign(newCredential, {
|
||||||
|
name: randomName(),
|
||||||
|
data: '',
|
||||||
|
type: '',
|
||||||
|
nodesAccess: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const savedCredential = await Db.collections.Credentials.save(newCredential);
|
||||||
|
|
||||||
|
await Db.collections.SharedCredentials.save({
|
||||||
|
role: credentialOwnerRole,
|
||||||
|
user: userToDelete,
|
||||||
|
credentials: savedCredential,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.delete(`/users/${userToDelete.id}`);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.body).toEqual(SUCCESS_RESPONSE_BODY);
|
||||||
|
|
||||||
|
const user = await Db.collections.User.findOneBy({ id: userToDelete.id });
|
||||||
|
expect(user).toBeNull(); // deleted
|
||||||
|
|
||||||
|
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({
|
||||||
|
relations: ['user'],
|
||||||
|
where: { userId: userToDelete.id, roleId: workflowOwnerRole.id },
|
||||||
|
});
|
||||||
|
expect(sharedWorkflow).toBeNull(); // deleted
|
||||||
|
|
||||||
|
const sharedCredential = await Db.collections.SharedCredentials.findOne({
|
||||||
|
relations: ['user'],
|
||||||
|
where: { userId: userToDelete.id, roleId: credentialOwnerRole.id },
|
||||||
|
});
|
||||||
|
expect(sharedCredential).toBeNull(); // deleted
|
||||||
|
|
||||||
|
const workflow = await Db.collections.Workflow.findOneBy({ id: savedWorkflow.id });
|
||||||
|
expect(workflow).toBeNull(); // deleted
|
||||||
|
|
||||||
|
// TODO: Include active workflow and check whether webhook has been removed
|
||||||
|
|
||||||
|
const credential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
|
||||||
|
expect(credential).toBeNull(); // deleted
|
||||||
});
|
});
|
||||||
|
|
||||||
const savedWorkflow = await Db.collections.Workflow.save(newWorkflow);
|
test('should fail to delete self', async () => {
|
||||||
|
const response = await authOwnerAgent.delete(`/users/${owner.id}`);
|
||||||
|
|
||||||
await Db.collections.SharedWorkflow.save({
|
expect(response.statusCode).toBe(400);
|
||||||
role: workflowOwnerRole,
|
|
||||||
user: userToDelete,
|
const user = await Db.collections.User.findOneBy({ id: owner.id });
|
||||||
workflow: savedWorkflow,
|
expect(user).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
const newCredential = new CredentialsEntity();
|
test('should fail if user to delete is transferee', async () => {
|
||||||
|
const { id: idToDelete } = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
|
||||||
Object.assign(newCredential, {
|
const response = await authOwnerAgent.delete(`/users/${idToDelete}`).query({
|
||||||
name: randomName(),
|
transferId: idToDelete,
|
||||||
data: '',
|
});
|
||||||
type: '',
|
|
||||||
nodesAccess: [],
|
expect(response.statusCode).toBe(400);
|
||||||
|
|
||||||
|
const user = await Db.collections.User.findOneBy({ id: idToDelete });
|
||||||
|
expect(user).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
const savedCredential = await Db.collections.Credentials.save(newCredential);
|
test('with transferId should perform transfer', async () => {
|
||||||
|
const userToDelete = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
|
||||||
await Db.collections.SharedCredentials.save({
|
const savedWorkflow = await testDb.createWorkflow(undefined, userToDelete);
|
||||||
role: credentialOwnerRole,
|
|
||||||
user: userToDelete,
|
const savedCredential = await testDb.saveCredential(randomCredentialPayload(), {
|
||||||
credentials: savedCredential,
|
user: userToDelete,
|
||||||
|
role: credentialOwnerRole,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.delete(`/users/${userToDelete.id}`).query({
|
||||||
|
transferId: owner.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const sharedWorkflow = await Db.collections.SharedWorkflow.findOneOrFail({
|
||||||
|
relations: ['workflow'],
|
||||||
|
where: { userId: owner.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sharedWorkflow.workflow).toBeDefined();
|
||||||
|
expect(sharedWorkflow.workflow.id).toBe(savedWorkflow.id);
|
||||||
|
|
||||||
|
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||||
|
relations: ['credentials'],
|
||||||
|
where: { userId: owner.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sharedCredential.credentials).toBeDefined();
|
||||||
|
expect(sharedCredential.credentials.id).toBe(savedCredential.id);
|
||||||
|
|
||||||
|
const deletedUser = await Db.collections.User.findOneBy({ id: userToDelete.id });
|
||||||
|
|
||||||
|
expect(deletedUser).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await authAgent(owner).delete(`/users/${userToDelete.id}`);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body).toEqual(SUCCESS_RESPONSE_BODY);
|
|
||||||
|
|
||||||
const user = await Db.collections.User.findOneBy({ id: userToDelete.id });
|
|
||||||
expect(user).toBeNull(); // deleted
|
|
||||||
|
|
||||||
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({
|
|
||||||
relations: ['user'],
|
|
||||||
where: { userId: userToDelete.id, roleId: workflowOwnerRole.id },
|
|
||||||
});
|
|
||||||
expect(sharedWorkflow).toBeNull(); // deleted
|
|
||||||
|
|
||||||
const sharedCredential = await Db.collections.SharedCredentials.findOne({
|
|
||||||
relations: ['user'],
|
|
||||||
where: { userId: userToDelete.id, roleId: credentialOwnerRole.id },
|
|
||||||
});
|
|
||||||
expect(sharedCredential).toBeNull(); // deleted
|
|
||||||
|
|
||||||
const workflow = await Db.collections.Workflow.findOneBy({ id: savedWorkflow.id });
|
|
||||||
expect(workflow).toBeNull(); // deleted
|
|
||||||
|
|
||||||
// TODO: Include active workflow and check whether webhook has been removed
|
|
||||||
|
|
||||||
const credential = await Db.collections.Credentials.findOneBy({ id: savedCredential.id });
|
|
||||||
expect(credential).toBeNull(); // deleted
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('DELETE /users/:id should fail to delete self', async () => {
|
describe('POST /users/:id', () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
test('should fill out a user shell', async () => {
|
||||||
|
const memberShell = await testDb.createUserShell(globalMemberRole);
|
||||||
|
|
||||||
const response = await authAgent(owner).delete(`/users/${owner.id}`);
|
const memberData = {
|
||||||
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
|
|
||||||
const user = await Db.collections.User.findOneBy({ id: owner.id });
|
|
||||||
expect(user).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /users/:id should fail if user to delete is transferee', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const { id: idToDelete } = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
const response = await authAgent(owner).delete(`/users/${idToDelete}`).query({
|
|
||||||
transferId: idToDelete,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
|
|
||||||
const user = await Db.collections.User.findOneBy({ id: idToDelete });
|
|
||||||
expect(user).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DELETE /users/:id with transferId should perform transfer', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const userToDelete = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
const savedWorkflow = await testDb.createWorkflow(undefined, userToDelete);
|
|
||||||
|
|
||||||
const savedCredential = await testDb.saveCredential(randomCredentialPayload(), {
|
|
||||||
user: userToDelete,
|
|
||||||
role: credentialOwnerRole,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await authAgent(owner).delete(`/users/${userToDelete.id}`).query({
|
|
||||||
transferId: owner.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const sharedWorkflow = await Db.collections.SharedWorkflow.findOneOrFail({
|
|
||||||
relations: ['workflow'],
|
|
||||||
where: { userId: owner.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(sharedWorkflow.workflow).toBeDefined();
|
|
||||||
expect(sharedWorkflow.workflow.id).toBe(savedWorkflow.id);
|
|
||||||
|
|
||||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
|
||||||
relations: ['credentials'],
|
|
||||||
where: { userId: owner.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(sharedCredential.credentials).toBeDefined();
|
|
||||||
expect(sharedCredential.credentials.id).toBe(savedCredential.id);
|
|
||||||
|
|
||||||
const deletedUser = await Db.collections.User.findOneBy({ id: userToDelete.id });
|
|
||||||
|
|
||||||
expect(deletedUser).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /users/:id should fill out a user shell', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const memberShell = await testDb.createUserShell(globalMemberRole);
|
|
||||||
|
|
||||||
const memberData = {
|
|
||||||
inviterId: owner.id,
|
|
||||||
firstName: randomName(),
|
|
||||||
lastName: randomName(),
|
|
||||||
password: randomValidPassword(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
|
||||||
|
|
||||||
const response = await authlessAgent.post(`/users/${memberShell.id}`).send(memberData);
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
email,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
personalizationAnswers,
|
|
||||||
password,
|
|
||||||
resetPasswordToken,
|
|
||||||
globalRole,
|
|
||||||
isPending,
|
|
||||||
apiKey,
|
|
||||||
} = response.body.data;
|
|
||||||
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
|
||||||
expect(email).toBeDefined();
|
|
||||||
expect(firstName).toBe(memberData.firstName);
|
|
||||||
expect(lastName).toBe(memberData.lastName);
|
|
||||||
expect(personalizationAnswers).toBeNull();
|
|
||||||
expect(password).toBeUndefined();
|
|
||||||
expect(resetPasswordToken).toBeUndefined();
|
|
||||||
expect(isPending).toBe(false);
|
|
||||||
expect(globalRole).toBeDefined();
|
|
||||||
expect(apiKey).not.toBeDefined();
|
|
||||||
|
|
||||||
const authToken = utils.getAuthToken(response);
|
|
||||||
expect(authToken).toBeDefined();
|
|
||||||
|
|
||||||
const member = await Db.collections.User.findOneByOrFail({ id: memberShell.id });
|
|
||||||
expect(member.firstName).toBe(memberData.firstName);
|
|
||||||
expect(member.lastName).toBe(memberData.lastName);
|
|
||||||
expect(member.password).not.toBe(memberData.password);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /users/:id should fail with invalid inputs', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
|
||||||
|
|
||||||
const memberShellEmail = randomEmail();
|
|
||||||
|
|
||||||
const memberShell = await Db.collections.User.save({
|
|
||||||
email: memberShellEmail,
|
|
||||||
globalRole: globalMemberRole,
|
|
||||||
});
|
|
||||||
|
|
||||||
const invalidPayloads = [
|
|
||||||
{
|
|
||||||
firstName: randomName(),
|
|
||||||
lastName: randomName(),
|
|
||||||
password: randomValidPassword(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
inviterId: owner.id,
|
|
||||||
firstName: randomName(),
|
|
||||||
password: randomValidPassword(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
inviterId: owner.id,
|
|
||||||
firstName: randomName(),
|
|
||||||
password: randomValidPassword(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
inviterId: owner.id,
|
inviterId: owner.id,
|
||||||
firstName: randomName(),
|
firstName: randomName(),
|
||||||
lastName: randomName(),
|
lastName: randomName(),
|
||||||
},
|
password: randomValidPassword(),
|
||||||
{
|
};
|
||||||
inviterId: owner.id,
|
|
||||||
firstName: randomName(),
|
|
||||||
lastName: randomName(),
|
|
||||||
password: randomInvalidPassword(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
await Promise.all(
|
const response = await authlessAgent.post(`/users/${memberShell.id}`).send(memberData);
|
||||||
invalidPayloads.map(async (invalidPayload) => {
|
|
||||||
const response = await authlessAgent.post(`/users/${memberShell.id}`).send(invalidPayload);
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
|
|
||||||
const storedUser = await Db.collections.User.findOneOrFail({
|
const {
|
||||||
where: { email: memberShellEmail },
|
id,
|
||||||
});
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
personalizationAnswers,
|
||||||
|
password,
|
||||||
|
resetPasswordToken,
|
||||||
|
globalRole,
|
||||||
|
isPending,
|
||||||
|
apiKey,
|
||||||
|
} = response.body.data;
|
||||||
|
|
||||||
expect(storedUser.firstName).toBeNull();
|
|
||||||
expect(storedUser.lastName).toBeNull();
|
|
||||||
expect(storedUser.password).toBeNull();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /users/:id should fail with already accepted invite', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
const newMemberData = {
|
|
||||||
inviterId: owner.id,
|
|
||||||
firstName: randomName(),
|
|
||||||
lastName: randomName(),
|
|
||||||
password: randomValidPassword(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
|
||||||
|
|
||||||
const response = await authlessAgent.post(`/users/${member.id}`).send(newMemberData);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
|
|
||||||
const storedMember = await Db.collections.User.findOneOrFail({
|
|
||||||
where: { email: member.email },
|
|
||||||
});
|
|
||||||
expect(storedMember.firstName).not.toBe(newMemberData.firstName);
|
|
||||||
expect(storedMember.lastName).not.toBe(newMemberData.lastName);
|
|
||||||
|
|
||||||
const comparisonResult = await compareHash(member.password, storedMember.password);
|
|
||||||
expect(comparisonResult).toBe(false);
|
|
||||||
expect(storedMember.password).not.toBe(newMemberData.password);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /users should succeed if emailing is not set up', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
|
||||||
.post('/users')
|
|
||||||
.send([{ email: randomEmail() }]);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
expect(response.body.data[0].user.inviteAcceptUrl).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /users should fail if user management is disabled', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
config.set('userManagement.disabled', true);
|
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
|
||||||
.post('/users')
|
|
||||||
.send([{ email: randomEmail() }]);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /users should email invites and create user shells but ignore existing', async () => {
|
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const memberShell = await testDb.createUserShell(globalMemberRole);
|
|
||||||
|
|
||||||
config.set('userManagement.emails.mode', 'smtp');
|
|
||||||
|
|
||||||
const testEmails = [randomEmail(), randomEmail().toUpperCase(), memberShell.email, member.email];
|
|
||||||
|
|
||||||
const payload = testEmails.map((e) => ({ email: e }));
|
|
||||||
|
|
||||||
const response = await authAgent(owner).post('/users').send(payload);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
for (const {
|
|
||||||
user: { id, email: receivedEmail },
|
|
||||||
error,
|
|
||||||
} of response.body.data) {
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
expect(id).not.toBe(member.id);
|
expect(email).toBeDefined();
|
||||||
|
expect(firstName).toBe(memberData.firstName);
|
||||||
const lowerCasedEmail = receivedEmail.toLowerCase();
|
expect(lastName).toBe(memberData.lastName);
|
||||||
expect(receivedEmail).toBe(lowerCasedEmail);
|
|
||||||
expect(payload.some(({ email }) => email.toLowerCase() === lowerCasedEmail)).toBe(true);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
expect(error).toBe('Email could not be sent');
|
|
||||||
}
|
|
||||||
|
|
||||||
const storedUser = await Db.collections.User.findOneByOrFail({ id });
|
|
||||||
const { firstName, lastName, personalizationAnswers, password, resetPasswordToken } =
|
|
||||||
storedUser;
|
|
||||||
|
|
||||||
expect(firstName).toBeNull();
|
|
||||||
expect(lastName).toBeNull();
|
|
||||||
expect(personalizationAnswers).toBeNull();
|
expect(personalizationAnswers).toBeNull();
|
||||||
expect(password).toBeNull();
|
expect(password).toBeUndefined();
|
||||||
expect(resetPasswordToken).toBeNull();
|
expect(resetPasswordToken).toBeUndefined();
|
||||||
}
|
expect(isPending).toBe(false);
|
||||||
|
expect(globalRole).toBeDefined();
|
||||||
|
expect(apiKey).not.toBeDefined();
|
||||||
|
|
||||||
|
const authToken = utils.getAuthToken(response);
|
||||||
|
expect(authToken).toBeDefined();
|
||||||
|
|
||||||
|
const member = await Db.collections.User.findOneByOrFail({ id: memberShell.id });
|
||||||
|
expect(member.firstName).toBe(memberData.firstName);
|
||||||
|
expect(member.lastName).toBe(memberData.lastName);
|
||||||
|
expect(member.password).not.toBe(memberData.password);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail with invalid inputs', async () => {
|
||||||
|
const memberShellEmail = randomEmail();
|
||||||
|
|
||||||
|
const memberShell = await Db.collections.User.save({
|
||||||
|
email: memberShellEmail,
|
||||||
|
globalRole: globalMemberRole,
|
||||||
|
});
|
||||||
|
|
||||||
|
const invalidPayloads = [
|
||||||
|
{
|
||||||
|
firstName: randomName(),
|
||||||
|
lastName: randomName(),
|
||||||
|
password: randomValidPassword(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inviterId: owner.id,
|
||||||
|
firstName: randomName(),
|
||||||
|
password: randomValidPassword(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inviterId: owner.id,
|
||||||
|
firstName: randomName(),
|
||||||
|
password: randomValidPassword(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inviterId: owner.id,
|
||||||
|
firstName: randomName(),
|
||||||
|
lastName: randomName(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inviterId: owner.id,
|
||||||
|
firstName: randomName(),
|
||||||
|
lastName: randomName(),
|
||||||
|
password: randomInvalidPassword(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
invalidPayloads.map(async (invalidPayload) => {
|
||||||
|
const response = await authlessAgent.post(`/users/${memberShell.id}`).send(invalidPayload);
|
||||||
|
expect(response.statusCode).toBe(400);
|
||||||
|
|
||||||
|
const storedUser = await Db.collections.User.findOneOrFail({
|
||||||
|
where: { email: memberShellEmail },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(storedUser.firstName).toBeNull();
|
||||||
|
expect(storedUser.lastName).toBeNull();
|
||||||
|
expect(storedUser.password).toBeNull();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail with already accepted invite', async () => {
|
||||||
|
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
|
||||||
|
const newMemberData = {
|
||||||
|
inviterId: owner.id,
|
||||||
|
firstName: randomName(),
|
||||||
|
lastName: randomName(),
|
||||||
|
password: randomValidPassword(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await authlessAgent.post(`/users/${member.id}`).send(newMemberData);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(400);
|
||||||
|
|
||||||
|
const storedMember = await Db.collections.User.findOneOrFail({
|
||||||
|
where: { email: member.email },
|
||||||
|
});
|
||||||
|
expect(storedMember.firstName).not.toBe(newMemberData.firstName);
|
||||||
|
expect(storedMember.lastName).not.toBe(newMemberData.lastName);
|
||||||
|
|
||||||
|
const comparisonResult = await compareHash(member.password, storedMember.password);
|
||||||
|
expect(comparisonResult).toBe(false);
|
||||||
|
expect(storedMember.password).not.toBe(newMemberData.password);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /users should fail with invalid inputs', async () => {
|
describe('POST /users', () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
beforeEach(() => {
|
||||||
const authOwnerAgent = authAgent(owner);
|
config.set('userManagement.emails.mode', 'smtp');
|
||||||
|
});
|
||||||
|
|
||||||
config.set('userManagement.emails.mode', 'smtp');
|
test('should succeed if emailing is not set up', async () => {
|
||||||
|
const response = await authOwnerAgent.post('/users').send([{ email: randomEmail() }]);
|
||||||
|
|
||||||
const invalidPayloads = [
|
expect(response.statusCode).toBe(200);
|
||||||
randomEmail(),
|
expect(response.body.data[0].user.inviteAcceptUrl).toBeDefined();
|
||||||
[randomEmail()],
|
});
|
||||||
{},
|
|
||||||
[{ name: randomName() }],
|
|
||||||
[{ email: randomName() }],
|
|
||||||
];
|
|
||||||
|
|
||||||
await Promise.all(
|
test('should fail if user management is disabled', async () => {
|
||||||
invalidPayloads.map(async (invalidPayload) => {
|
config.set('userManagement.disabled', true);
|
||||||
const response = await authOwnerAgent.post('/users').send(invalidPayload);
|
config.set('userManagement.isInstanceOwnerSetUp', false);
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
|
|
||||||
const users = await Db.collections.User.find();
|
const response = await authOwnerAgent.post('/users').send([{ email: randomEmail() }]);
|
||||||
expect(users.length).toBe(1); // DB unaffected
|
|
||||||
}),
|
expect(response.statusCode).toBe(400);
|
||||||
);
|
});
|
||||||
|
|
||||||
|
test('should email invites and create user shells but ignore existing', async () => {
|
||||||
|
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
const memberShell = await testDb.createUserShell(globalMemberRole);
|
||||||
|
|
||||||
|
const testEmails = [
|
||||||
|
randomEmail(),
|
||||||
|
randomEmail().toUpperCase(),
|
||||||
|
memberShell.email,
|
||||||
|
member.email,
|
||||||
|
];
|
||||||
|
|
||||||
|
const payload = testEmails.map((e) => ({ email: e }));
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.post('/users').send(payload);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
for (const {
|
||||||
|
user: { id, email: receivedEmail },
|
||||||
|
error,
|
||||||
|
} of response.body.data) {
|
||||||
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
|
expect(id).not.toBe(member.id);
|
||||||
|
|
||||||
|
const lowerCasedEmail = receivedEmail.toLowerCase();
|
||||||
|
expect(receivedEmail).toBe(lowerCasedEmail);
|
||||||
|
expect(payload.some(({ email }) => email.toLowerCase() === lowerCasedEmail)).toBe(true);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
expect(error).toBe('Email could not be sent');
|
||||||
|
}
|
||||||
|
|
||||||
|
const storedUser = await Db.collections.User.findOneByOrFail({ id });
|
||||||
|
const { firstName, lastName, personalizationAnswers, password, resetPasswordToken } =
|
||||||
|
storedUser;
|
||||||
|
|
||||||
|
expect(firstName).toBeNull();
|
||||||
|
expect(lastName).toBeNull();
|
||||||
|
expect(personalizationAnswers).toBeNull();
|
||||||
|
expect(password).toBeNull();
|
||||||
|
expect(resetPasswordToken).toBeNull();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail with invalid inputs', async () => {
|
||||||
|
const invalidPayloads = [
|
||||||
|
randomEmail(),
|
||||||
|
[randomEmail()],
|
||||||
|
{},
|
||||||
|
[{ name: randomName() }],
|
||||||
|
[{ email: randomName() }],
|
||||||
|
];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
invalidPayloads.map(async (invalidPayload) => {
|
||||||
|
const response = await authOwnerAgent.post('/users').send(invalidPayload);
|
||||||
|
expect(response.statusCode).toBe(400);
|
||||||
|
|
||||||
|
const users = await Db.collections.User.find();
|
||||||
|
expect(users.length).toBe(1); // DB unaffected
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should ignore an empty payload', async () => {
|
||||||
|
const response = await authOwnerAgent.post('/users').send([]);
|
||||||
|
|
||||||
|
const { data } = response.body;
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(Array.isArray(data)).toBe(true);
|
||||||
|
expect(data.length).toBe(0);
|
||||||
|
|
||||||
|
const users = await Db.collections.User.find();
|
||||||
|
expect(users.length).toBe(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /users should ignore an empty payload', async () => {
|
describe('POST /users/:id/reinvite', () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
beforeEach(() => {
|
||||||
|
config.set('userManagement.emails.mode', 'smtp');
|
||||||
|
// those configs are needed to make sure the reinvite email is sent,because of this check isEmailSetUp()
|
||||||
|
config.set('userManagement.emails.smtp.host', 'host');
|
||||||
|
config.set('userManagement.emails.smtp.auth.user', 'user');
|
||||||
|
config.set('userManagement.emails.smtp.auth.pass', 'pass');
|
||||||
|
});
|
||||||
|
|
||||||
config.set('userManagement.emails.mode', 'smtp');
|
test('should send reinvite, but fail if user already accepted invite', async () => {
|
||||||
|
const email = randomEmail();
|
||||||
|
const payload = [{ email }];
|
||||||
|
const response = await authOwnerAgent.post('/users').send(payload);
|
||||||
|
|
||||||
const response = await authAgent(owner).post('/users').send([]);
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
const { data } = response.body;
|
const { data } = response.body;
|
||||||
|
const invitedUserId = data[0].user.id;
|
||||||
|
const reinviteResponse = await authOwnerAgent.post(`/users/${invitedUserId}/reinvite`);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(reinviteResponse.statusCode).toBe(200);
|
||||||
expect(Array.isArray(data)).toBe(true);
|
|
||||||
expect(data.length).toBe(0);
|
|
||||||
|
|
||||||
const users = await Db.collections.User.find();
|
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
expect(users.length).toBe(1);
|
const reinviteMemberResponse = await authOwnerAgent.post(`/users/${member.id}/reinvite`);
|
||||||
|
|
||||||
|
expect(reinviteMemberResponse.statusCode).toBe(400);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /users/:id/reinvite should send reinvite, but fail if user already accepted invite', async () => {
|
describe('UserManagementMailer expect NodeMailer.verifyConnection', () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
let mockInit: jest.SpyInstance<Promise<void>, []>;
|
||||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
let mockVerifyConnection: jest.SpyInstance<Promise<void>, []>;
|
||||||
|
|
||||||
config.set('userManagement.emails.mode', 'smtp');
|
beforeAll(() => {
|
||||||
|
mockVerifyConnection = jest
|
||||||
|
.spyOn(NodeMailer.prototype, 'verifyConnection')
|
||||||
|
.mockImplementation(async () => {});
|
||||||
|
mockInit = jest.spyOn(NodeMailer.prototype, 'init').mockImplementation(async () => {});
|
||||||
|
});
|
||||||
|
|
||||||
// those configs are needed to make sure the reinvite email is sent,because of this check isEmailSetUp()
|
afterAll(() => {
|
||||||
config.set('userManagement.emails.smtp.host', 'host');
|
mockVerifyConnection.mockRestore();
|
||||||
config.set('userManagement.emails.smtp.auth.user', 'user');
|
mockInit.mockRestore();
|
||||||
config.set('userManagement.emails.smtp.auth.pass', 'pass');
|
});
|
||||||
|
|
||||||
const email = randomEmail();
|
test('not be called when SMTP not set up', async () => {
|
||||||
const payload = [{ email }];
|
const userManagementMailer = new UserManagementMailer();
|
||||||
const response = await authOwnerAgent.post('/users').send(payload);
|
// NodeMailer.verifyConnection gets called only explicitly
|
||||||
|
expect(async () => await userManagementMailer.verifyConnection()).rejects.toThrow();
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(NodeMailer.prototype.verifyConnection).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
const { data } = response.body;
|
test('to be called when SMTP set up', async () => {
|
||||||
const invitedUserId = data[0].user.id;
|
// host needs to be set, otherwise smtp is skipped
|
||||||
const reinviteResponse = await authOwnerAgent.post(`/users/${invitedUserId}/reinvite`);
|
config.set('userManagement.emails.smtp.host', 'host');
|
||||||
|
config.set('userManagement.emails.mode', 'smtp');
|
||||||
|
|
||||||
expect(reinviteResponse.statusCode).toBe(200);
|
const userManagementMailer = new UserManagementMailer();
|
||||||
|
// NodeMailer.verifyConnection gets called only explicitly
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
expect(async () => await userManagementMailer.verifyConnection()).not.toThrow();
|
||||||
const reinviteMemberResponse = await authOwnerAgent.post(`/users/${member.id}/reinvite`);
|
});
|
||||||
|
|
||||||
expect(reinviteMemberResponse.statusCode).toBe(400);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('UserManagementMailer expect NodeMailer.verifyConnection not be called when SMTP not set up', async () => {
|
|
||||||
const mockVerifyConnection = jest.spyOn(NodeMailer.prototype, 'verifyConnection');
|
|
||||||
mockVerifyConnection.mockImplementation(async () => {});
|
|
||||||
|
|
||||||
const userManagementMailer = new UserManagementMailer();
|
|
||||||
// NodeMailer.verifyConnection gets called only explicitly
|
|
||||||
expect(async () => await userManagementMailer.verifyConnection()).rejects.toThrow();
|
|
||||||
|
|
||||||
expect(NodeMailer.prototype.verifyConnection).toHaveBeenCalledTimes(0);
|
|
||||||
|
|
||||||
mockVerifyConnection.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('UserManagementMailer expect NodeMailer.verifyConnection to be called when SMTP set up', async () => {
|
|
||||||
const mockVerifyConnection = jest.spyOn(NodeMailer.prototype, 'verifyConnection');
|
|
||||||
mockVerifyConnection.mockImplementation(async () => {});
|
|
||||||
const mockInit = jest.spyOn(NodeMailer.prototype, 'init');
|
|
||||||
mockInit.mockImplementation(async () => {});
|
|
||||||
|
|
||||||
// host needs to be set, otherwise smtp is skipped
|
|
||||||
config.set('userManagement.emails.smtp.host', 'host');
|
|
||||||
config.set('userManagement.emails.mode', 'smtp');
|
|
||||||
|
|
||||||
const userManagementMailer = new UserManagementMailer();
|
|
||||||
// NodeMailer.verifyConnection gets called only explicitly
|
|
||||||
expect(async () => await userManagementMailer.verifyConnection()).not.toThrow();
|
|
||||||
|
|
||||||
// expect(NodeMailer.prototype.verifyConnection).toHaveBeenCalledTimes(1);
|
|
||||||
mockVerifyConnection.mockRestore();
|
|
||||||
mockInit.mockRestore();
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,89 +1,89 @@
|
||||||
import express from 'express';
|
import type { SuperAgentTest } from 'supertest';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { INode } from 'n8n-workflow';
|
import type { INode } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
||||||
|
import type { User } from '@db/entities/User';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
import * as utils from './shared/utils';
|
import * as utils from './shared/utils';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import { createWorkflow } from './shared/testDb';
|
import { createWorkflow } from './shared/testDb';
|
||||||
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
import type { SaveCredentialFunction } from './shared/types';
|
||||||
import type { Role } from '@db/entities/Role';
|
|
||||||
import config from '@/config';
|
|
||||||
import type { AuthAgent, SaveCredentialFunction } from './shared/types';
|
|
||||||
import { makeWorkflow } from './shared/utils';
|
import { makeWorkflow } from './shared/utils';
|
||||||
import { randomCredentialPayload } from './shared/random';
|
import { randomCredentialPayload } from './shared/random';
|
||||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
|
||||||
|
|
||||||
let app: express.Application;
|
let owner: User;
|
||||||
let globalOwnerRole: Role;
|
let member: User;
|
||||||
let globalMemberRole: Role;
|
let anotherMember: User;
|
||||||
let credentialOwnerRole: Role;
|
let authOwnerAgent: SuperAgentTest;
|
||||||
let authAgent: AuthAgent;
|
let authMemberAgent: SuperAgentTest;
|
||||||
|
let authAnotherMemberAgent: SuperAgentTest;
|
||||||
let saveCredential: SaveCredentialFunction;
|
let saveCredential: SaveCredentialFunction;
|
||||||
let isSharingEnabled: jest.SpyInstance<boolean>;
|
|
||||||
let workflowRunner: ActiveWorkflowRunner;
|
|
||||||
let sharingSpy: jest.SpyInstance<boolean>;
|
let sharingSpy: jest.SpyInstance<boolean>;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({ endpointGroups: ['workflows'] });
|
const app = await utils.initTestServer({ endpointGroups: ['workflows'] });
|
||||||
|
|
||||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
const globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
const globalMemberRole = await testDb.getGlobalMemberRole();
|
||||||
credentialOwnerRole = await testDb.getCredentialOwnerRole();
|
const credentialOwnerRole = await testDb.getCredentialOwnerRole();
|
||||||
|
|
||||||
|
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||||
|
member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
anotherMember = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
|
|
||||||
|
const authAgent = utils.createAuthAgent(app);
|
||||||
|
authOwnerAgent = authAgent(owner);
|
||||||
|
authMemberAgent = authAgent(member);
|
||||||
|
authAnotherMemberAgent = authAgent(anotherMember);
|
||||||
|
|
||||||
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
|
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
|
||||||
|
sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
|
||||||
authAgent = utils.createAuthAgent(app);
|
|
||||||
|
|
||||||
isSharingEnabled = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
|
|
||||||
|
|
||||||
await utils.initNodeTypes();
|
await utils.initNodeTypes();
|
||||||
workflowRunner = await utils.initActiveWorkflowRunner();
|
|
||||||
|
|
||||||
config.set('enterprise.features.sharing', true);
|
config.set('enterprise.features.sharing', true);
|
||||||
sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true); // @TODO: Remove on release
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await testDb.truncate(['User', 'Workflow', 'SharedWorkflow']);
|
await testDb.truncate(['Workflow', 'SharedWorkflow']);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await testDb.terminate();
|
await testDb.terminate();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Router should switch dynamically', async () => {
|
describe('router should switch based on flag', () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
let savedWorkflowId: string;
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
const createWorkflowResponse = await authAgent(owner).post('/workflows').send(makeWorkflow());
|
beforeEach(async () => {
|
||||||
const { id } = createWorkflowResponse.body.data;
|
const createWorkflowResponse = await authOwnerAgent.post('/workflows').send(makeWorkflow());
|
||||||
|
savedWorkflowId = createWorkflowResponse.body.data.id;
|
||||||
|
});
|
||||||
|
|
||||||
// free router
|
test('when sharing is disabled', async () => {
|
||||||
|
sharingSpy.mockReturnValueOnce(false);
|
||||||
|
|
||||||
isSharingEnabled.mockReturnValueOnce(false);
|
await authOwnerAgent
|
||||||
|
.put(`/workflows/${savedWorkflowId}/share`)
|
||||||
|
.send({ shareWithIds: [member.id] })
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
|
||||||
const freeShareResponse = await authAgent(owner)
|
test('when sharing is enabled', async () => {
|
||||||
.put(`/workflows/${id}/share`)
|
await authOwnerAgent
|
||||||
.send({ shareWithIds: [member.id] });
|
.put(`/workflows/${savedWorkflowId}/share`)
|
||||||
|
.send({ shareWithIds: [member.id] })
|
||||||
expect(freeShareResponse.status).toBe(404);
|
.expect(200);
|
||||||
|
});
|
||||||
// EE router
|
|
||||||
|
|
||||||
const paidShareResponse = await authAgent(owner)
|
|
||||||
.put(`/workflows/${id}/share`)
|
|
||||||
.send({ shareWithIds: [member.id] });
|
|
||||||
|
|
||||||
expect(paidShareResponse.status).toBe(200);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('PUT /workflows/:id', () => {
|
describe('PUT /workflows/:id', () => {
|
||||||
test('PUT /workflows/:id/share should save sharing with new users', async () => {
|
test('PUT /workflows/:id/share should save sharing with new users', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const workflow = await createWorkflow({}, owner);
|
const workflow = await createWorkflow({}, owner);
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
const response = await authOwnerAgent
|
||||||
.put(`/workflows/${workflow.id}/share`)
|
.put(`/workflows/${workflow.id}/share`)
|
||||||
.send({ shareWithIds: [member.id] });
|
.send({ shareWithIds: [member.id] });
|
||||||
|
|
||||||
|
@ -94,10 +94,9 @@ describe('PUT /workflows/:id', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PUT /workflows/:id/share should succeed when sharing with invalid user-id', async () => {
|
test('PUT /workflows/:id/share should succeed when sharing with invalid user-id', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const workflow = await createWorkflow({}, owner);
|
const workflow = await createWorkflow({}, owner);
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
const response = await authOwnerAgent
|
||||||
.put(`/workflows/${workflow.id}/share`)
|
.put(`/workflows/${workflow.id}/share`)
|
||||||
.send({ shareWithIds: [uuid()] });
|
.send({ shareWithIds: [uuid()] });
|
||||||
|
|
||||||
|
@ -108,12 +107,9 @@ describe('PUT /workflows/:id', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PUT /workflows/:id/share should allow sharing with multiple users', async () => {
|
test('PUT /workflows/:id/share should allow sharing with multiple users', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const anotherMember = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const workflow = await createWorkflow({}, owner);
|
const workflow = await createWorkflow({}, owner);
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
const response = await authOwnerAgent
|
||||||
.put(`/workflows/${workflow.id}/share`)
|
.put(`/workflows/${workflow.id}/share`)
|
||||||
.send({ shareWithIds: [member.id, anotherMember.id] });
|
.send({ shareWithIds: [member.id, anotherMember.id] });
|
||||||
|
|
||||||
|
@ -124,13 +120,8 @@ describe('PUT /workflows/:id', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PUT /workflows/:id/share should override sharing', async () => {
|
test('PUT /workflows/:id/share should override sharing', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const anotherMember = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const workflow = await createWorkflow({}, owner);
|
const workflow = await createWorkflow({}, owner);
|
||||||
|
|
||||||
const authOwnerAgent = authAgent(owner);
|
|
||||||
|
|
||||||
const response = await authOwnerAgent
|
const response = await authOwnerAgent
|
||||||
.put(`/workflows/${workflow.id}/share`)
|
.put(`/workflows/${workflow.id}/share`)
|
||||||
.send({ shareWithIds: [member.id, anotherMember.id] });
|
.send({ shareWithIds: [member.id, anotherMember.id] });
|
||||||
|
@ -152,8 +143,6 @@ describe('PUT /workflows/:id', () => {
|
||||||
|
|
||||||
describe('GET /workflows', () => {
|
describe('GET /workflows', () => {
|
||||||
test('should return workflows without nodes, sharing and credential usage details', async () => {
|
test('should return workflows without nodes, sharing and credential usage details', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const tag = await testDb.createTag({ name: 'test' });
|
const tag = await testDb.createTag({ name: 'test' });
|
||||||
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
@ -183,7 +172,7 @@ describe('GET /workflows', () => {
|
||||||
|
|
||||||
await testDb.shareWorkflowWithUsers(workflow, [member]);
|
await testDb.shareWorkflowWithUsers(workflow, [member]);
|
||||||
|
|
||||||
const response = await authAgent(owner).get('/workflows');
|
const response = await authOwnerAgent.get('/workflows');
|
||||||
|
|
||||||
const [fetchedWorkflow] = response.body.data;
|
const [fetchedWorkflow] = response.body.data;
|
||||||
|
|
||||||
|
@ -192,42 +181,37 @@ describe('GET /workflows', () => {
|
||||||
id: owner.id,
|
id: owner.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(fetchedWorkflow.sharedWith).not.toBeDefined()
|
expect(fetchedWorkflow.sharedWith).not.toBeDefined();
|
||||||
expect(fetchedWorkflow.usedCredentials).not.toBeDefined()
|
expect(fetchedWorkflow.usedCredentials).not.toBeDefined();
|
||||||
expect(fetchedWorkflow.nodes).not.toBeDefined()
|
expect(fetchedWorkflow.nodes).not.toBeDefined();
|
||||||
expect(fetchedWorkflow.tags).toEqual(
|
expect(fetchedWorkflow.tags).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
name: expect.any(String)
|
name: expect.any(String),
|
||||||
})
|
}),
|
||||||
])
|
]),
|
||||||
)
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /workflows/:id', () => {
|
describe('GET /workflows/:id', () => {
|
||||||
test('GET should fail with invalid id due to route rule', async () => {
|
test('GET should fail with invalid id due to route rule', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
const response = await authOwnerAgent.get('/workflows/potatoes');
|
||||||
|
|
||||||
const response = await authAgent(owner).get('/workflows/potatoes');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
expect(response.statusCode).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET should return 404 for non existing workflow', async () => {
|
test('GET should return 404 for non existing workflow', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
const response = await authOwnerAgent.get('/workflows/9001');
|
||||||
|
|
||||||
const response = await authAgent(owner).get('/workflows/9001');
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(404);
|
expect(response.statusCode).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET should return a workflow with owner', async () => {
|
test('GET should return a workflow with owner', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const workflow = await createWorkflow({}, owner);
|
const workflow = await createWorkflow({}, owner);
|
||||||
|
|
||||||
const response = await authAgent(owner).get(`/workflows/${workflow.id}`);
|
const response = await authOwnerAgent.get(`/workflows/${workflow.id}`);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.body.data.ownedBy).toMatchObject({
|
expect(response.body.data.ownedBy).toMatchObject({
|
||||||
|
@ -241,12 +225,10 @@ describe('GET /workflows/:id', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET should return shared workflow with user data', async () => {
|
test('GET should return shared workflow with user data', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const workflow = await createWorkflow({}, owner);
|
const workflow = await createWorkflow({}, owner);
|
||||||
await testDb.shareWorkflowWithUsers(workflow, [member]);
|
await testDb.shareWorkflowWithUsers(workflow, [member]);
|
||||||
|
|
||||||
const response = await authAgent(owner).get(`/workflows/${workflow.id}`);
|
const response = await authOwnerAgent.get(`/workflows/${workflow.id}`);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.body.data.ownedBy).toMatchObject({
|
expect(response.body.data.ownedBy).toMatchObject({
|
||||||
|
@ -266,13 +248,10 @@ describe('GET /workflows/:id', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET should return all sharees', async () => {
|
test('GET should return all sharees', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member1 = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const member2 = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const workflow = await createWorkflow({}, owner);
|
const workflow = await createWorkflow({}, owner);
|
||||||
await testDb.shareWorkflowWithUsers(workflow, [member1, member2]);
|
await testDb.shareWorkflowWithUsers(workflow, [member, anotherMember]);
|
||||||
|
|
||||||
const response = await authAgent(owner).get(`/workflows/${workflow.id}`);
|
const response = await authOwnerAgent.get(`/workflows/${workflow.id}`);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.body.data.ownedBy).toMatchObject({
|
expect(response.body.data.ownedBy).toMatchObject({
|
||||||
|
@ -286,7 +265,6 @@ describe('GET /workflows/:id', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET should return workflow with credentials owned by user', async () => {
|
test('GET should return workflow with credentials owned by user', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
|
||||||
const workflowPayload = makeWorkflow({
|
const workflowPayload = makeWorkflow({
|
||||||
|
@ -295,7 +273,7 @@ describe('GET /workflows/:id', () => {
|
||||||
});
|
});
|
||||||
const workflow = await createWorkflow(workflowPayload, owner);
|
const workflow = await createWorkflow(workflowPayload, owner);
|
||||||
|
|
||||||
const response = await authAgent(owner).get(`/workflows/${workflow.id}`);
|
const response = await authOwnerAgent.get(`/workflows/${workflow.id}`);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.body.data.usedCredentials).toMatchObject([
|
expect(response.body.data.usedCredentials).toMatchObject([
|
||||||
|
@ -310,8 +288,6 @@ describe('GET /workflows/:id', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET should return workflow with credentials saying owner does not have access when not shared', async () => {
|
test('GET should return workflow with credentials saying owner does not have access when not shared', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
|
|
||||||
const workflowPayload = makeWorkflow({
|
const workflowPayload = makeWorkflow({
|
||||||
|
@ -320,7 +296,7 @@ describe('GET /workflows/:id', () => {
|
||||||
});
|
});
|
||||||
const workflow = await createWorkflow(workflowPayload, owner);
|
const workflow = await createWorkflow(workflowPayload, owner);
|
||||||
|
|
||||||
const response = await authAgent(owner).get(`/workflows/${workflow.id}`);
|
const response = await authOwnerAgent.get(`/workflows/${workflow.id}`);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.body.data.usedCredentials).toMatchObject([
|
expect(response.body.data.usedCredentials).toMatchObject([
|
||||||
|
@ -335,18 +311,16 @@ describe('GET /workflows/:id', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET should return workflow with credentials for all users with or without access', async () => {
|
test('GET should return workflow with credentials for all users with or without access', async () => {
|
||||||
const member1 = await testDb.createUser({ globalRole: globalMemberRole });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
const member2 = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
|
||||||
|
|
||||||
const workflowPayload = makeWorkflow({
|
const workflowPayload = makeWorkflow({
|
||||||
withPinData: false,
|
withPinData: false,
|
||||||
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
const workflow = await createWorkflow(workflowPayload, member1);
|
const workflow = await createWorkflow(workflowPayload, member);
|
||||||
await testDb.shareWorkflowWithUsers(workflow, [member2]);
|
await testDb.shareWorkflowWithUsers(workflow, [anotherMember]);
|
||||||
|
|
||||||
const responseMember1 = await authAgent(member1).get(`/workflows/${workflow.id}`);
|
const responseMember1 = await authMemberAgent.get(`/workflows/${workflow.id}`);
|
||||||
expect(responseMember1.statusCode).toBe(200);
|
expect(responseMember1.statusCode).toBe(200);
|
||||||
expect(responseMember1.body.data.usedCredentials).toMatchObject([
|
expect(responseMember1.body.data.usedCredentials).toMatchObject([
|
||||||
{
|
{
|
||||||
|
@ -357,7 +331,7 @@ describe('GET /workflows/:id', () => {
|
||||||
]);
|
]);
|
||||||
expect(responseMember1.body.data.sharedWith).toHaveLength(1);
|
expect(responseMember1.body.data.sharedWith).toHaveLength(1);
|
||||||
|
|
||||||
const responseMember2 = await authAgent(member2).get(`/workflows/${workflow.id}`);
|
const responseMember2 = await authAnotherMemberAgent.get(`/workflows/${workflow.id}`);
|
||||||
expect(responseMember2.statusCode).toBe(200);
|
expect(responseMember2.statusCode).toBe(200);
|
||||||
expect(responseMember2.body.data.usedCredentials).toMatchObject([
|
expect(responseMember2.body.data.usedCredentials).toMatchObject([
|
||||||
{
|
{
|
||||||
|
@ -370,20 +344,18 @@ describe('GET /workflows/:id', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET should return workflow with credentials for all users with access', async () => {
|
test('GET should return workflow with credentials for all users with access', async () => {
|
||||||
const member1 = await testDb.createUser({ globalRole: globalMemberRole });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
const member2 = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
|
||||||
// Both users have access to the credential (none is owner)
|
// Both users have access to the credential (none is owner)
|
||||||
await testDb.shareCredentialWithUsers(savedCredential, [member2]);
|
await testDb.shareCredentialWithUsers(savedCredential, [anotherMember]);
|
||||||
|
|
||||||
const workflowPayload = makeWorkflow({
|
const workflowPayload = makeWorkflow({
|
||||||
withPinData: false,
|
withPinData: false,
|
||||||
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
const workflow = await createWorkflow(workflowPayload, member1);
|
const workflow = await createWorkflow(workflowPayload, member);
|
||||||
await testDb.shareWorkflowWithUsers(workflow, [member2]);
|
await testDb.shareWorkflowWithUsers(workflow, [anotherMember]);
|
||||||
|
|
||||||
const responseMember1 = await authAgent(member1).get(`/workflows/${workflow.id}`);
|
const responseMember1 = await authMemberAgent.get(`/workflows/${workflow.id}`);
|
||||||
expect(responseMember1.statusCode).toBe(200);
|
expect(responseMember1.statusCode).toBe(200);
|
||||||
expect(responseMember1.body.data.usedCredentials).toMatchObject([
|
expect(responseMember1.body.data.usedCredentials).toMatchObject([
|
||||||
{
|
{
|
||||||
|
@ -394,7 +366,7 @@ describe('GET /workflows/:id', () => {
|
||||||
]);
|
]);
|
||||||
expect(responseMember1.body.data.sharedWith).toHaveLength(1);
|
expect(responseMember1.body.data.sharedWith).toHaveLength(1);
|
||||||
|
|
||||||
const responseMember2 = await authAgent(member2).get(`/workflows/${workflow.id}`);
|
const responseMember2 = await authAnotherMemberAgent.get(`/workflows/${workflow.id}`);
|
||||||
expect(responseMember2.statusCode).toBe(200);
|
expect(responseMember2.statusCode).toBe(200);
|
||||||
expect(responseMember2.body.data.usedCredentials).toMatchObject([
|
expect(responseMember2.body.data.usedCredentials).toMatchObject([
|
||||||
{
|
{
|
||||||
|
@ -409,33 +381,26 @@ describe('GET /workflows/:id', () => {
|
||||||
|
|
||||||
describe('POST /workflows', () => {
|
describe('POST /workflows', () => {
|
||||||
it('Should create a workflow that uses no credential', async () => {
|
it('Should create a workflow that uses no credential', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const workflow = makeWorkflow({ withPinData: false });
|
const workflow = makeWorkflow({ withPinData: false });
|
||||||
|
|
||||||
const response = await authAgent(owner).post('/workflows').send(workflow);
|
const response = await authOwnerAgent.post('/workflows').send(workflow);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should save a new workflow with credentials', async () => {
|
it('Should save a new workflow with credentials', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
const workflow = makeWorkflow({
|
const workflow = makeWorkflow({
|
||||||
withPinData: false,
|
withPinData: false,
|
||||||
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await authAgent(owner).post('/workflows').send(workflow);
|
const response = await authOwnerAgent.post('/workflows').send(workflow);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should not allow saving a workflow using credential you have no access', async () => {
|
it('Should not allow saving a workflow using credential you have no access', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
// Credential belongs to owner, member cannot use it.
|
// Credential belongs to owner, member cannot use it.
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
const workflow = makeWorkflow({
|
const workflow = makeWorkflow({
|
||||||
|
@ -443,7 +408,7 @@ describe('POST /workflows', () => {
|
||||||
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await authAgent(member).post('/workflows').send(workflow);
|
const response = await authMemberAgent.post('/workflows').send(workflow);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(400);
|
expect(response.statusCode).toBe(400);
|
||||||
expect(response.body.message).toBe(
|
expect(response.body.message).toBe(
|
||||||
|
@ -452,9 +417,6 @@ describe('POST /workflows', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should allow owner to save a workflow using credential owned by others', async () => {
|
it('Should allow owner to save a workflow using credential owned by others', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
// Credential belongs to owner, member cannot use it.
|
// Credential belongs to owner, member cannot use it.
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
const workflow = makeWorkflow({
|
const workflow = makeWorkflow({
|
||||||
|
@ -462,32 +424,27 @@ describe('POST /workflows', () => {
|
||||||
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await authAgent(owner).post('/workflows').send(workflow);
|
const response = await authOwnerAgent.post('/workflows').send(workflow);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should allow saving a workflow using a credential owned by others and shared with you', async () => {
|
it('Should allow saving a workflow using a credential owned by others and shared with you', async () => {
|
||||||
const member1 = await testDb.createUser({ globalRole: globalMemberRole });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
const member2 = await testDb.createUser({ globalRole: globalMemberRole });
|
await testDb.shareCredentialWithUsers(savedCredential, [anotherMember]);
|
||||||
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
|
||||||
await testDb.shareCredentialWithUsers(savedCredential, [member2]);
|
|
||||||
|
|
||||||
const workflow = makeWorkflow({
|
const workflow = makeWorkflow({
|
||||||
withPinData: false,
|
withPinData: false,
|
||||||
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await authAgent(member2).post('/workflows').send(workflow);
|
const response = await authAnotherMemberAgent.post('/workflows').send(workflow);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('PATCH /workflows/:id - validate credential permissions to user', () => {
|
describe('PATCH /workflows/:id - validate credential permissions to user', () => {
|
||||||
it('Should succeed when saving unchanged workflow nodes', async () => {
|
it('Should succeed when saving unchanged workflow nodes', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
const workflow = {
|
const workflow = {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
@ -511,10 +468,10 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const createResponse = await authAgent(owner).post('/workflows').send(workflow);
|
const createResponse = await authOwnerAgent.post('/workflows').send(workflow);
|
||||||
const { id, versionId } = createResponse.body.data;
|
const { id, versionId } = createResponse.body.data;
|
||||||
|
|
||||||
const response = await authAgent(owner).patch(`/workflows/${id}`).send({
|
const response = await authOwnerAgent.patch(`/workflows/${id}`).send({
|
||||||
name: 'new name',
|
name: 'new name',
|
||||||
versionId,
|
versionId,
|
||||||
});
|
});
|
||||||
|
@ -523,9 +480,6 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should allow owner to add node containing credential not shared with the owner', async () => {
|
it('Should allow owner to add node containing credential not shared with the owner', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
const workflow = {
|
const workflow = {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
@ -549,38 +503,33 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const createResponse = await authAgent(owner).post('/workflows').send(workflow);
|
const createResponse = await authOwnerAgent.post('/workflows').send(workflow);
|
||||||
const { id, versionId } = createResponse.body.data;
|
const { id, versionId } = createResponse.body.data;
|
||||||
|
|
||||||
const response = await authAgent(owner)
|
const response = await authOwnerAgent.patch(`/workflows/${id}`).send({
|
||||||
.patch(`/workflows/${id}`)
|
versionId,
|
||||||
.send({
|
nodes: [
|
||||||
versionId,
|
{
|
||||||
nodes: [
|
id: 'uuid-1234',
|
||||||
{
|
name: 'Start',
|
||||||
id: 'uuid-1234',
|
parameters: {},
|
||||||
name: 'Start',
|
position: [-20, 260],
|
||||||
parameters: {},
|
type: 'n8n-nodes-base.start',
|
||||||
position: [-20, 260],
|
typeVersion: 1,
|
||||||
type: 'n8n-nodes-base.start',
|
credentials: {
|
||||||
typeVersion: 1,
|
default: {
|
||||||
credentials: {
|
id: savedCredential.id,
|
||||||
default: {
|
name: savedCredential.name,
|
||||||
id: savedCredential.id,
|
|
||||||
name: savedCredential.name,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
});
|
],
|
||||||
|
});
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should prevent member from adding node containing credential inaccessible to member', async () => {
|
it('Should prevent member from adding node containing credential inaccessible to member', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
|
|
||||||
const workflow = {
|
const workflow = {
|
||||||
|
@ -605,48 +554,43 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const createResponse = await authAgent(owner).post('/workflows').send(workflow);
|
const createResponse = await authOwnerAgent.post('/workflows').send(workflow);
|
||||||
const { id, versionId } = createResponse.body.data;
|
const { id, versionId } = createResponse.body.data;
|
||||||
|
|
||||||
const response = await authAgent(member)
|
const response = await authMemberAgent.patch(`/workflows/${id}`).send({
|
||||||
.patch(`/workflows/${id}`)
|
versionId,
|
||||||
.send({
|
nodes: [
|
||||||
versionId,
|
{
|
||||||
nodes: [
|
id: 'uuid-1234',
|
||||||
{
|
name: 'Start',
|
||||||
id: 'uuid-1234',
|
parameters: {},
|
||||||
name: 'Start',
|
position: [-20, 260],
|
||||||
parameters: {},
|
type: 'n8n-nodes-base.start',
|
||||||
position: [-20, 260],
|
typeVersion: 1,
|
||||||
type: 'n8n-nodes-base.start',
|
credentials: {},
|
||||||
typeVersion: 1,
|
},
|
||||||
credentials: {},
|
{
|
||||||
},
|
id: 'uuid-12345',
|
||||||
{
|
name: 'Start',
|
||||||
id: 'uuid-12345',
|
parameters: {},
|
||||||
name: 'Start',
|
position: [-20, 260],
|
||||||
parameters: {},
|
type: 'n8n-nodes-base.start',
|
||||||
position: [-20, 260],
|
typeVersion: 1,
|
||||||
type: 'n8n-nodes-base.start',
|
credentials: {
|
||||||
typeVersion: 1,
|
default: {
|
||||||
credentials: {
|
id: savedCredential.id,
|
||||||
default: {
|
name: savedCredential.name,
|
||||||
id: savedCredential.id,
|
|
||||||
name: savedCredential.name,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
});
|
],
|
||||||
|
});
|
||||||
|
|
||||||
expect(response.statusCode).toBe(400);
|
expect(response.statusCode).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should succeed but prevent modifying node attributes other than position, name and disabled', async () => {
|
it('Should succeed but prevent modifying node attributes other than position, name and disabled', async () => {
|
||||||
const member1 = await testDb.createUser({ globalRole: globalMemberRole });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
const member2 = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
|
||||||
|
|
||||||
const originalNodes: INode[] = [
|
const originalNodes: INode[] = [
|
||||||
{
|
{
|
||||||
|
@ -714,14 +658,12 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
nodes: originalNodes,
|
nodes: originalNodes,
|
||||||
};
|
};
|
||||||
|
|
||||||
const createResponse = await authAgent(member1).post('/workflows').send(workflow);
|
const createResponse = await authMemberAgent.post('/workflows').send(workflow);
|
||||||
const { id, versionId } = createResponse.body.data;
|
const { id, versionId } = createResponse.body.data;
|
||||||
|
|
||||||
await authAgent(member1)
|
await authMemberAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [anotherMember.id] });
|
||||||
.put(`/workflows/${id}/share`)
|
|
||||||
.send({ shareWithIds: [member2.id] });
|
|
||||||
|
|
||||||
const response = await authAgent(member2).patch(`/workflows/${id}`).send({
|
const response = await authAnotherMemberAgent.patch(`/workflows/${id}`).send({
|
||||||
versionId,
|
versionId,
|
||||||
nodes: changedNodes,
|
nodes: changedNodes,
|
||||||
});
|
});
|
||||||
|
@ -733,29 +675,24 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
|
|
||||||
describe('PATCH /workflows/:id - validate interim updates', () => {
|
describe('PATCH /workflows/:id - validate interim updates', () => {
|
||||||
it('should block owner updating workflow nodes on interim update by member', async () => {
|
it('should block owner updating workflow nodes on interim update by member', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
// owner creates and shares workflow
|
// owner creates and shares workflow
|
||||||
|
|
||||||
const createResponse = await authAgent(owner).post('/workflows').send(makeWorkflow());
|
const createResponse = await authOwnerAgent.post('/workflows').send(makeWorkflow());
|
||||||
const { id, versionId: ownerVersionId } = createResponse.body.data;
|
const { id, versionId: ownerVersionId } = createResponse.body.data;
|
||||||
await authAgent(owner)
|
await authOwnerAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [member.id] });
|
||||||
.put(`/workflows/${id}/share`)
|
|
||||||
.send({ shareWithIds: [member.id] });
|
|
||||||
|
|
||||||
// member accesses and updates workflow name
|
// member accesses and updates workflow name
|
||||||
|
|
||||||
const memberGetResponse = await authAgent(member).get(`/workflows/${id}`);
|
const memberGetResponse = await authMemberAgent.get(`/workflows/${id}`);
|
||||||
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
||||||
|
|
||||||
await authAgent(member)
|
await authMemberAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ name: 'Update by member', versionId: memberVersionId });
|
.send({ name: 'Update by member', versionId: memberVersionId });
|
||||||
|
|
||||||
// owner blocked from updating workflow nodes
|
// owner blocked from updating workflow nodes
|
||||||
|
|
||||||
const updateAttemptResponse = await authAgent(owner)
|
const updateAttemptResponse = await authOwnerAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ nodes: [], versionId: ownerVersionId });
|
.send({ nodes: [], versionId: ownerVersionId });
|
||||||
|
|
||||||
|
@ -764,38 +701,33 @@ describe('PATCH /workflows/:id - validate interim updates', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should block member updating workflow nodes on interim update by owner', async () => {
|
it('should block member updating workflow nodes on interim update by owner', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
// owner creates, updates and shares workflow
|
// owner creates, updates and shares workflow
|
||||||
|
|
||||||
const createResponse = await authAgent(owner).post('/workflows').send(makeWorkflow());
|
const createResponse = await authOwnerAgent.post('/workflows').send(makeWorkflow());
|
||||||
const { id, versionId: ownerFirstVersionId } = createResponse.body.data;
|
const { id, versionId: ownerFirstVersionId } = createResponse.body.data;
|
||||||
|
|
||||||
const updateResponse = await authAgent(owner)
|
const updateResponse = await authOwnerAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ name: 'Update by owner', versionId: ownerFirstVersionId });
|
.send({ name: 'Update by owner', versionId: ownerFirstVersionId });
|
||||||
|
|
||||||
const { versionId: ownerSecondVersionId } = updateResponse.body.data;
|
const { versionId: ownerSecondVersionId } = updateResponse.body.data;
|
||||||
|
|
||||||
await authAgent(owner)
|
await authOwnerAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [member.id] });
|
||||||
.put(`/workflows/${id}/share`)
|
|
||||||
.send({ shareWithIds: [member.id] });
|
|
||||||
|
|
||||||
// member accesses workflow
|
// member accesses workflow
|
||||||
|
|
||||||
const memberGetResponse = await authAgent(member).get(`/workflows/${id}`);
|
const memberGetResponse = await authMemberAgent.get(`/workflows/${id}`);
|
||||||
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
||||||
|
|
||||||
// owner re-updates workflow
|
// owner re-updates workflow
|
||||||
|
|
||||||
await authAgent(owner)
|
await authOwnerAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ name: 'Owner update again', versionId: ownerSecondVersionId });
|
.send({ name: 'Owner update again', versionId: ownerSecondVersionId });
|
||||||
|
|
||||||
// member blocked from updating workflow
|
// member blocked from updating workflow
|
||||||
|
|
||||||
const updateAttemptResponse = await authAgent(member)
|
const updateAttemptResponse = await authMemberAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ nodes: [], versionId: memberVersionId });
|
.send({ nodes: [], versionId: memberVersionId });
|
||||||
|
|
||||||
|
@ -804,28 +736,23 @@ describe('PATCH /workflows/:id - validate interim updates', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should block owner activation on interim activation by member', async () => {
|
it('should block owner activation on interim activation by member', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
// owner creates and shares workflow
|
// owner creates and shares workflow
|
||||||
|
|
||||||
const createResponse = await authAgent(owner).post('/workflows').send(makeWorkflow());
|
const createResponse = await authOwnerAgent.post('/workflows').send(makeWorkflow());
|
||||||
const { id, versionId: ownerVersionId } = createResponse.body.data;
|
const { id, versionId: ownerVersionId } = createResponse.body.data;
|
||||||
await authAgent(owner)
|
await authOwnerAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [member.id] });
|
||||||
.put(`/workflows/${id}/share`)
|
|
||||||
.send({ shareWithIds: [member.id] });
|
|
||||||
|
|
||||||
// member accesses and activates workflow
|
// member accesses and activates workflow
|
||||||
|
|
||||||
const memberGetResponse = await authAgent(member).get(`/workflows/${id}`);
|
const memberGetResponse = await authMemberAgent.get(`/workflows/${id}`);
|
||||||
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
||||||
await authAgent(member)
|
await authMemberAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ active: true, versionId: memberVersionId });
|
.send({ active: true, versionId: memberVersionId });
|
||||||
|
|
||||||
// owner blocked from activating workflow
|
// owner blocked from activating workflow
|
||||||
|
|
||||||
const activationAttemptResponse = await authAgent(owner)
|
const activationAttemptResponse = await authOwnerAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ active: true, versionId: ownerVersionId });
|
.send({ active: true, versionId: ownerVersionId });
|
||||||
|
|
||||||
|
@ -834,37 +761,32 @@ describe('PATCH /workflows/:id - validate interim updates', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should block member activation on interim activation by owner', async () => {
|
it('should block member activation on interim activation by owner', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
// owner creates, updates and shares workflow
|
// owner creates, updates and shares workflow
|
||||||
|
|
||||||
const createResponse = await authAgent(owner).post('/workflows').send(makeWorkflow());
|
const createResponse = await authOwnerAgent.post('/workflows').send(makeWorkflow());
|
||||||
const { id, versionId: ownerFirstVersionId } = createResponse.body.data;
|
const { id, versionId: ownerFirstVersionId } = createResponse.body.data;
|
||||||
|
|
||||||
const updateResponse = await authAgent(owner)
|
const updateResponse = await authOwnerAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ name: 'Update by owner', versionId: ownerFirstVersionId });
|
.send({ name: 'Update by owner', versionId: ownerFirstVersionId });
|
||||||
const { versionId: ownerSecondVersionId } = updateResponse.body.data;
|
const { versionId: ownerSecondVersionId } = updateResponse.body.data;
|
||||||
|
|
||||||
await authAgent(owner)
|
await authOwnerAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [member.id] });
|
||||||
.put(`/workflows/${id}/share`)
|
|
||||||
.send({ shareWithIds: [member.id] });
|
|
||||||
|
|
||||||
// member accesses workflow
|
// member accesses workflow
|
||||||
|
|
||||||
const memberGetResponse = await authAgent(member).get(`/workflows/${id}`);
|
const memberGetResponse = await authMemberAgent.get(`/workflows/${id}`);
|
||||||
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
||||||
|
|
||||||
// owner activates workflow
|
// owner activates workflow
|
||||||
|
|
||||||
await authAgent(owner)
|
await authOwnerAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ active: true, versionId: ownerSecondVersionId });
|
.send({ active: true, versionId: ownerSecondVersionId });
|
||||||
|
|
||||||
// member blocked from activating workflow
|
// member blocked from activating workflow
|
||||||
|
|
||||||
const updateAttemptResponse = await authAgent(member)
|
const updateAttemptResponse = await authMemberAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ active: true, versionId: memberVersionId });
|
.send({ active: true, versionId: memberVersionId });
|
||||||
|
|
||||||
|
@ -873,31 +795,26 @@ describe('PATCH /workflows/:id - validate interim updates', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should block member updating workflow settings on interim update by owner', async () => {
|
it('should block member updating workflow settings on interim update by owner', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
// owner creates and shares workflow
|
// owner creates and shares workflow
|
||||||
|
|
||||||
const createResponse = await authAgent(owner).post('/workflows').send(makeWorkflow());
|
const createResponse = await authOwnerAgent.post('/workflows').send(makeWorkflow());
|
||||||
const { id, versionId: ownerVersionId } = createResponse.body.data;
|
const { id, versionId: ownerVersionId } = createResponse.body.data;
|
||||||
await authAgent(owner)
|
await authOwnerAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [member.id] });
|
||||||
.put(`/workflows/${id}/share`)
|
|
||||||
.send({ shareWithIds: [member.id] });
|
|
||||||
|
|
||||||
// member accesses workflow
|
// member accesses workflow
|
||||||
|
|
||||||
const memberGetResponse = await authAgent(member).get(`/workflows/${id}`);
|
const memberGetResponse = await authMemberAgent.get(`/workflows/${id}`);
|
||||||
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
||||||
|
|
||||||
// owner updates workflow name
|
// owner updates workflow name
|
||||||
|
|
||||||
await authAgent(owner)
|
await authOwnerAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ name: 'Another name', versionId: ownerVersionId });
|
.send({ name: 'Another name', versionId: ownerVersionId });
|
||||||
|
|
||||||
// member blocked from updating workflow settings
|
// member blocked from updating workflow settings
|
||||||
|
|
||||||
const updateAttemptResponse = await authAgent(member)
|
const updateAttemptResponse = await authMemberAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ settings: { saveManualExecutions: true }, versionId: memberVersionId });
|
.send({ settings: { saveManualExecutions: true }, versionId: memberVersionId });
|
||||||
|
|
||||||
|
@ -906,31 +823,26 @@ describe('PATCH /workflows/:id - validate interim updates', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should block member updating workflow name on interim update by owner', async () => {
|
it('should block member updating workflow name on interim update by owner', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
|
||||||
|
|
||||||
// owner creates and shares workflow
|
// owner creates and shares workflow
|
||||||
|
|
||||||
const createResponse = await authAgent(owner).post('/workflows').send(makeWorkflow());
|
const createResponse = await authOwnerAgent.post('/workflows').send(makeWorkflow());
|
||||||
const { id, versionId: ownerVersionId } = createResponse.body.data;
|
const { id, versionId: ownerVersionId } = createResponse.body.data;
|
||||||
await authAgent(owner)
|
await authOwnerAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [member.id] });
|
||||||
.put(`/workflows/${id}/share`)
|
|
||||||
.send({ shareWithIds: [member.id] });
|
|
||||||
|
|
||||||
// member accesses workflow
|
// member accesses workflow
|
||||||
|
|
||||||
const memberGetResponse = await authAgent(member).get(`/workflows/${id}`);
|
const memberGetResponse = await authMemberAgent.get(`/workflows/${id}`);
|
||||||
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
||||||
|
|
||||||
// owner updates workflow settings
|
// owner updates workflow settings
|
||||||
|
|
||||||
await authAgent(owner)
|
await authOwnerAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ settings: { saveManualExecutions: true }, versionId: ownerVersionId });
|
.send({ settings: { saveManualExecutions: true }, versionId: ownerVersionId });
|
||||||
|
|
||||||
// member blocked from updating workflow name
|
// member blocked from updating workflow name
|
||||||
|
|
||||||
const updateAttemptResponse = await authAgent(member)
|
const updateAttemptResponse = await authMemberAgent
|
||||||
.patch(`/workflows/${id}`)
|
.patch(`/workflows/${id}`)
|
||||||
.send({ settings: { saveManualExecutions: true }, versionId: memberVersionId });
|
.send({ settings: { saveManualExecutions: true }, versionId: memberVersionId });
|
||||||
|
|
||||||
|
|
|
@ -1,78 +1,74 @@
|
||||||
import express from 'express';
|
import { SuperAgentTest } from 'supertest';
|
||||||
|
import type { IPinData } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import type { User } from '@db/entities/User';
|
||||||
|
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
||||||
|
|
||||||
import * as utils from './shared/utils';
|
import * as utils from './shared/utils';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
|
||||||
|
|
||||||
import type { Role } from '@db/entities/Role';
|
|
||||||
import type { IPinData } from 'n8n-workflow';
|
|
||||||
import { makeWorkflow, MOCK_PINDATA } from './shared/utils';
|
import { makeWorkflow, MOCK_PINDATA } from './shared/utils';
|
||||||
|
|
||||||
let app: express.Application;
|
let ownerShell: User;
|
||||||
let globalOwnerRole: Role;
|
let authOwnerAgent: SuperAgentTest;
|
||||||
|
|
||||||
// mock whether sharing is enabled or not
|
|
||||||
jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(false);
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await utils.initTestServer({ endpointGroups: ['workflows'] });
|
const app = await utils.initTestServer({ endpointGroups: ['workflows'] });
|
||||||
|
const globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||||
|
ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||||
|
authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||||
|
|
||||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
// mock whether sharing is enabled or not
|
||||||
|
jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await testDb.truncate(['User', 'Workflow', 'SharedWorkflow']);
|
await testDb.truncate(['Workflow', 'SharedWorkflow']);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await testDb.terminate();
|
await testDb.terminate();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /workflows should store pin data for node in workflow', async () => {
|
describe('POST /workflows', () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
test('should store pin data for node in workflow', async () => {
|
||||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
const workflow = makeWorkflow({ withPinData: true });
|
||||||
|
|
||||||
const workflow = makeWorkflow({ withPinData: true });
|
const response = await authOwnerAgent.post('/workflows').send(workflow);
|
||||||
|
|
||||||
const response = await authOwnerAgent.post('/workflows').send(workflow);
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
const { pinData } = response.body.data as { pinData: IPinData };
|
||||||
|
|
||||||
const { pinData } = response.body.data as { pinData: IPinData };
|
expect(pinData).toMatchObject(MOCK_PINDATA);
|
||||||
|
});
|
||||||
|
|
||||||
expect(pinData).toMatchObject(MOCK_PINDATA);
|
test('should set pin data to null if no pin data', async () => {
|
||||||
|
const workflow = makeWorkflow({ withPinData: false });
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.post('/workflows').send(workflow);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const { pinData } = response.body.data as { pinData: IPinData };
|
||||||
|
|
||||||
|
expect(pinData).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /workflows should set pin data to null if no pin data', async () => {
|
describe('GET /workflows/:id', () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
test('should return pin data', async () => {
|
||||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
const workflow = makeWorkflow({ withPinData: true });
|
||||||
|
|
||||||
const workflow = makeWorkflow({ withPinData: false });
|
const workflowCreationResponse = await authOwnerAgent.post('/workflows').send(workflow);
|
||||||
|
|
||||||
const response = await authOwnerAgent.post('/workflows').send(workflow);
|
const { id } = workflowCreationResponse.body.data as { id: string };
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
const workflowRetrievalResponse = await authOwnerAgent.get(`/workflows/${id}`);
|
||||||
|
|
||||||
const { pinData } = response.body.data as { pinData: IPinData };
|
expect(workflowRetrievalResponse.statusCode).toBe(200);
|
||||||
|
|
||||||
expect(pinData).toBeNull();
|
const { pinData } = workflowRetrievalResponse.body.data as { pinData: IPinData };
|
||||||
});
|
|
||||||
|
expect(pinData).toMatchObject(MOCK_PINDATA);
|
||||||
test('GET /workflows/:id should return pin data', async () => {
|
});
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
|
||||||
|
|
||||||
const workflow = makeWorkflow({ withPinData: true });
|
|
||||||
|
|
||||||
const workflowCreationResponse = await authOwnerAgent.post('/workflows').send(workflow);
|
|
||||||
|
|
||||||
const { id } = workflowCreationResponse.body.data as { id: string };
|
|
||||||
|
|
||||||
const workflowRetrievalResponse = await authOwnerAgent.get(`/workflows/${id}`);
|
|
||||||
|
|
||||||
expect(workflowRetrievalResponse.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const { pinData } = workflowRetrievalResponse.body.data as { pinData: IPinData };
|
|
||||||
|
|
||||||
expect(pinData).toMatchObject(MOCK_PINDATA);
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { User } from '@/databases/entities/User';
|
||||||
import { getLogger } from '@/Logger';
|
import { getLogger } from '@/Logger';
|
||||||
import { randomEmail, randomName } from '../integration/shared/random';
|
import { randomEmail, randomName } from '../integration/shared/random';
|
||||||
import * as Helpers from './Helpers';
|
import * as Helpers from './Helpers';
|
||||||
import { WorkflowExecuteAdditionalData } from '@/index';
|
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
||||||
|
|
||||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
import { WorkflowRunner } from '@/WorkflowRunner';
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
|
|
|
@ -11,20 +11,59 @@ import {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { CredentialsHelper } from '@/CredentialsHelper';
|
import { CredentialsHelper } from '@/CredentialsHelper';
|
||||||
import { CredentialTypes } from '@/CredentialTypes';
|
import { CredentialTypes } from '@/CredentialTypes';
|
||||||
import * as Helpers from './Helpers';
|
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { NodeTypes } from '@/NodeTypes';
|
import { NodeTypes } from '@/NodeTypes';
|
||||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||||
|
|
||||||
const TEST_ENCRYPTION_KEY = 'test';
|
|
||||||
const mockNodesAndCredentials: INodesAndCredentials = {
|
|
||||||
loaded: { nodes: {}, credentials: {} },
|
|
||||||
known: { nodes: {}, credentials: {} },
|
|
||||||
credentialTypes: {} as ICredentialTypes,
|
|
||||||
};
|
|
||||||
Container.set(LoadNodesAndCredentials, mockNodesAndCredentials);
|
|
||||||
|
|
||||||
describe('CredentialsHelper', () => {
|
describe('CredentialsHelper', () => {
|
||||||
|
const TEST_ENCRYPTION_KEY = 'test';
|
||||||
|
|
||||||
|
const mockNodesAndCredentials: INodesAndCredentials = {
|
||||||
|
loaded: {
|
||||||
|
nodes: {
|
||||||
|
'test.set': {
|
||||||
|
sourcePath: '',
|
||||||
|
type: {
|
||||||
|
description: {
|
||||||
|
displayName: 'Set',
|
||||||
|
name: 'set',
|
||||||
|
group: ['input'],
|
||||||
|
version: 1,
|
||||||
|
description: 'Sets a value',
|
||||||
|
defaults: {
|
||||||
|
name: 'Set',
|
||||||
|
color: '#0000FF',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Value1',
|
||||||
|
name: 'value1',
|
||||||
|
type: 'string',
|
||||||
|
default: 'default-value1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value2',
|
||||||
|
name: 'value2',
|
||||||
|
type: 'string',
|
||||||
|
default: 'default-value2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
credentials: {},
|
||||||
|
},
|
||||||
|
known: { nodes: {}, credentials: {} },
|
||||||
|
credentialTypes: {} as ICredentialTypes,
|
||||||
|
};
|
||||||
|
|
||||||
|
Container.set(LoadNodesAndCredentials, mockNodesAndCredentials);
|
||||||
|
|
||||||
|
const nodeTypes = Container.get(NodeTypes);
|
||||||
|
|
||||||
describe('authenticate', () => {
|
describe('authenticate', () => {
|
||||||
const tests: Array<{
|
const tests: Array<{
|
||||||
description: string;
|
description: string;
|
||||||
|
@ -219,8 +258,6 @@ describe('CredentialsHelper', () => {
|
||||||
qs: {},
|
qs: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodeTypes = Helpers.NodeTypes() as unknown as NodeTypes;
|
|
||||||
|
|
||||||
const workflow = new Workflow({
|
const workflow = new Workflow({
|
||||||
nodes: [node],
|
nodes: [node],
|
||||||
connections: {},
|
connections: {},
|
||||||
|
|
|
@ -1,32 +1,37 @@
|
||||||
import { LoggerProxy, WorkflowExecuteMode } from 'n8n-workflow';
|
import { IRun, LoggerProxy, WorkflowExecuteMode } from 'n8n-workflow';
|
||||||
import { QueryFailedError } from 'typeorm';
|
import { QueryFailedError, Repository } from 'typeorm';
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { Db } from '@/index';
|
import * as Db from '@/Db';
|
||||||
|
import { User } from '@db/entities/User';
|
||||||
|
import { WorkflowStatistics } from '@db/entities/WorkflowStatistics';
|
||||||
import { nodeFetchedData, workflowExecutionCompleted } from '@/events/WorkflowStatistics';
|
import { nodeFetchedData, workflowExecutionCompleted } from '@/events/WorkflowStatistics';
|
||||||
import { getLogger } from '@/Logger';
|
|
||||||
import * as UserManagementHelper from '@/UserManagement/UserManagementHelper';
|
import * as UserManagementHelper from '@/UserManagement/UserManagementHelper';
|
||||||
|
import { getLogger } from '@/Logger';
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
|
|
||||||
import { mockInstance } from '../integration/shared/utils';
|
import { mockInstance } from '../integration/shared/utils';
|
||||||
|
|
||||||
const FAKE_USER_ID = 'abcde-fghij';
|
type WorkflowStatisticsRepository = Repository<WorkflowStatistics>;
|
||||||
|
|
||||||
jest.mock('@/Db', () => {
|
jest.mock('@/Db', () => {
|
||||||
return {
|
return {
|
||||||
collections: {
|
collections: {
|
||||||
WorkflowStatistics: {
|
WorkflowStatistics: mock<WorkflowStatisticsRepository>(),
|
||||||
insert: jest.fn((...args) => {}),
|
|
||||||
update: jest.fn((...args) => {}),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
jest.spyOn(UserManagementHelper, 'getWorkflowOwner').mockImplementation(async (_workflowId) => {
|
|
||||||
return { id: FAKE_USER_ID };
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Events', () => {
|
describe('Events', () => {
|
||||||
|
const fakeUser = Object.assign(new User(), { id: 'abcde-fghij' });
|
||||||
const internalHooks = mockInstance(InternalHooks);
|
const internalHooks = mockInstance(InternalHooks);
|
||||||
|
|
||||||
|
jest.spyOn(UserManagementHelper, 'getWorkflowOwner').mockResolvedValue(fakeUser);
|
||||||
|
|
||||||
|
const workflowStatisticsRepository = Db.collections.WorkflowStatistics as ReturnType<
|
||||||
|
typeof mock<WorkflowStatisticsRepository>
|
||||||
|
>;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
config.set('diagnostics.enabled', true);
|
config.set('diagnostics.enabled', true);
|
||||||
config.set('deployment.type', 'n8n-testing');
|
config.set('deployment.type', 'n8n-testing');
|
||||||
|
@ -57,8 +62,9 @@ describe('Events', () => {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
connections: {},
|
connections: {},
|
||||||
};
|
};
|
||||||
const runData = {
|
const runData: IRun = {
|
||||||
finished: true,
|
finished: true,
|
||||||
|
status: 'success',
|
||||||
data: { resultData: { runData: {} } },
|
data: { resultData: { runData: {} } },
|
||||||
mode: 'internal' as WorkflowExecuteMode,
|
mode: 'internal' as WorkflowExecuteMode,
|
||||||
startedAt: new Date(),
|
startedAt: new Date(),
|
||||||
|
@ -66,7 +72,7 @@ describe('Events', () => {
|
||||||
await workflowExecutionCompleted(workflow, runData);
|
await workflowExecutionCompleted(workflow, runData);
|
||||||
expect(internalHooks.onFirstProductionWorkflowSuccess).toBeCalledTimes(1);
|
expect(internalHooks.onFirstProductionWorkflowSuccess).toBeCalledTimes(1);
|
||||||
expect(internalHooks.onFirstProductionWorkflowSuccess).toHaveBeenNthCalledWith(1, {
|
expect(internalHooks.onFirstProductionWorkflowSuccess).toHaveBeenNthCalledWith(1, {
|
||||||
user_id: FAKE_USER_ID,
|
user_id: fakeUser.id,
|
||||||
workflow_id: workflow.id,
|
workflow_id: workflow.id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -82,8 +88,9 @@ describe('Events', () => {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
connections: {},
|
connections: {},
|
||||||
};
|
};
|
||||||
const runData = {
|
const runData: IRun = {
|
||||||
finished: false,
|
finished: false,
|
||||||
|
status: 'failed',
|
||||||
data: { resultData: { runData: {} } },
|
data: { resultData: { runData: {} } },
|
||||||
mode: 'internal' as WorkflowExecuteMode,
|
mode: 'internal' as WorkflowExecuteMode,
|
||||||
startedAt: new Date(),
|
startedAt: new Date(),
|
||||||
|
@ -94,7 +101,7 @@ describe('Events', () => {
|
||||||
|
|
||||||
test('should not send metrics for updated entries', async () => {
|
test('should not send metrics for updated entries', async () => {
|
||||||
// Call the function with a fail insert, ensure update is called *and* metrics aren't sent
|
// Call the function with a fail insert, ensure update is called *and* metrics aren't sent
|
||||||
Db.collections.WorkflowStatistics.insert.mockImplementationOnce(() => {
|
workflowStatisticsRepository.insert.mockImplementationOnce(() => {
|
||||||
throw new QueryFailedError('invalid insert', [], '');
|
throw new QueryFailedError('invalid insert', [], '');
|
||||||
});
|
});
|
||||||
const workflow = {
|
const workflow = {
|
||||||
|
@ -106,8 +113,9 @@ describe('Events', () => {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
connections: {},
|
connections: {},
|
||||||
};
|
};
|
||||||
const runData = {
|
const runData: IRun = {
|
||||||
finished: true,
|
finished: true,
|
||||||
|
status: 'success',
|
||||||
data: { resultData: { runData: {} } },
|
data: { resultData: { runData: {} } },
|
||||||
mode: 'internal' as WorkflowExecuteMode,
|
mode: 'internal' as WorkflowExecuteMode,
|
||||||
startedAt: new Date(),
|
startedAt: new Date(),
|
||||||
|
@ -132,7 +140,7 @@ describe('Events', () => {
|
||||||
await nodeFetchedData(workflowId, node);
|
await nodeFetchedData(workflowId, node);
|
||||||
expect(internalHooks.onFirstWorkflowDataLoad).toBeCalledTimes(1);
|
expect(internalHooks.onFirstWorkflowDataLoad).toBeCalledTimes(1);
|
||||||
expect(internalHooks.onFirstWorkflowDataLoad).toHaveBeenNthCalledWith(1, {
|
expect(internalHooks.onFirstWorkflowDataLoad).toHaveBeenNthCalledWith(1, {
|
||||||
user_id: FAKE_USER_ID,
|
user_id: fakeUser.id,
|
||||||
workflow_id: workflowId,
|
workflow_id: workflowId,
|
||||||
node_type: node.type,
|
node_type: node.type,
|
||||||
node_id: node.id,
|
node_id: node.id,
|
||||||
|
@ -159,7 +167,7 @@ describe('Events', () => {
|
||||||
await nodeFetchedData(workflowId, node);
|
await nodeFetchedData(workflowId, node);
|
||||||
expect(internalHooks.onFirstWorkflowDataLoad).toBeCalledTimes(1);
|
expect(internalHooks.onFirstWorkflowDataLoad).toBeCalledTimes(1);
|
||||||
expect(internalHooks.onFirstWorkflowDataLoad).toHaveBeenNthCalledWith(1, {
|
expect(internalHooks.onFirstWorkflowDataLoad).toHaveBeenNthCalledWith(1, {
|
||||||
user_id: FAKE_USER_ID,
|
user_id: fakeUser.id,
|
||||||
workflow_id: workflowId,
|
workflow_id: workflowId,
|
||||||
node_type: node.type,
|
node_type: node.type,
|
||||||
node_id: node.id,
|
node_id: node.id,
|
||||||
|
@ -170,7 +178,7 @@ describe('Events', () => {
|
||||||
|
|
||||||
test('should not send metrics for entries that already have the flag set', async () => {
|
test('should not send metrics for entries that already have the flag set', async () => {
|
||||||
// Fetch data for workflow 2 which is set up to not be altered in the mocks
|
// Fetch data for workflow 2 which is set up to not be altered in the mocks
|
||||||
Db.collections.WorkflowStatistics.insert.mockImplementationOnce(() => {
|
workflowStatisticsRepository.insert.mockImplementationOnce(() => {
|
||||||
throw new QueryFailedError('invalid insert', [], '');
|
throw new QueryFailedError('invalid insert', [], '');
|
||||||
});
|
});
|
||||||
const workflowId = '1';
|
const workflowId = '1';
|
||||||
|
|
|
@ -1,108 +1,4 @@
|
||||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
import { INodeTypeData } from 'n8n-workflow';
|
||||||
import {
|
|
||||||
INodeType,
|
|
||||||
INodeTypeData,
|
|
||||||
INodeTypes,
|
|
||||||
IVersionedNodeType,
|
|
||||||
NodeHelpers,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
// TODO: delete this
|
|
||||||
class NodeTypesClass implements INodeTypes {
|
|
||||||
nodeTypes: INodeTypeData = {
|
|
||||||
'test.set': {
|
|
||||||
sourcePath: '',
|
|
||||||
type: {
|
|
||||||
description: {
|
|
||||||
displayName: 'Set',
|
|
||||||
name: 'set',
|
|
||||||
group: ['input'],
|
|
||||||
version: 1,
|
|
||||||
description: 'Sets a value',
|
|
||||||
defaults: {
|
|
||||||
name: 'Set',
|
|
||||||
color: '#0000FF',
|
|
||||||
},
|
|
||||||
inputs: ['main'],
|
|
||||||
outputs: ['main'],
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
displayName: 'Value1',
|
|
||||||
name: 'value1',
|
|
||||||
type: 'string',
|
|
||||||
default: 'default-value1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Value2',
|
|
||||||
name: 'value2',
|
|
||||||
type: 'string',
|
|
||||||
default: 'default-value2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'fake-scheduler': {
|
|
||||||
sourcePath: '',
|
|
||||||
type: {
|
|
||||||
description: {
|
|
||||||
displayName: 'Schedule',
|
|
||||||
name: 'set',
|
|
||||||
group: ['input'],
|
|
||||||
version: 1,
|
|
||||||
description: 'Schedules execuitons',
|
|
||||||
defaults: {
|
|
||||||
name: 'Set',
|
|
||||||
color: '#0000FF',
|
|
||||||
},
|
|
||||||
inputs: ['main'],
|
|
||||||
outputs: ['main'],
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
displayName: 'Value1',
|
|
||||||
name: 'value1',
|
|
||||||
type: 'string',
|
|
||||||
default: 'default-value1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Value2',
|
|
||||||
name: 'value2',
|
|
||||||
type: 'string',
|
|
||||||
default: 'default-value2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
trigger: () => {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(nodesAndCredentials?: LoadNodesAndCredentials) {
|
|
||||||
if (nodesAndCredentials?.loaded?.nodes) {
|
|
||||||
this.nodeTypes = nodesAndCredentials?.loaded?.nodes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getByName(nodeType: string): INodeType | IVersionedNodeType {
|
|
||||||
return this.nodeTypes[nodeType].type;
|
|
||||||
}
|
|
||||||
|
|
||||||
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
|
||||||
return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let nodeTypesInstance: NodeTypesClass | undefined;
|
|
||||||
|
|
||||||
export function NodeTypes(nodesAndCredentials?: LoadNodesAndCredentials): NodeTypesClass {
|
|
||||||
if (nodeTypesInstance === undefined) {
|
|
||||||
nodeTypesInstance = new NodeTypesClass(nodesAndCredentials);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodeTypesInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure all pending promises settle. The promise's `resolve` is placed in
|
* Ensure all pending promises settle. The promise's `resolve` is placed in
|
||||||
|
|
|
@ -1,46 +1,47 @@
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import {
|
import { Container } from 'typedi';
|
||||||
ICredentialTypes,
|
import { ICredentialTypes, INodeTypes, SubworkflowOperationError, Workflow } from 'n8n-workflow';
|
||||||
INodeTypeData,
|
|
||||||
INodeTypes,
|
|
||||||
SubworkflowOperationError,
|
|
||||||
Workflow,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import * as testDb from '../integration/shared/testDb';
|
import { Role } from '@db/entities/Role';
|
||||||
import { mockNodeTypesData, NodeTypes as MockNodeTypes } from './Helpers';
|
import { User } from '@db/entities/User';
|
||||||
|
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||||
|
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||||
|
import { NodeTypes } from '@/NodeTypes';
|
||||||
import { UserService } from '@/user/user.service';
|
import { UserService } from '@/user/user.service';
|
||||||
import { PermissionChecker } from '@/UserManagement/PermissionChecker';
|
import { PermissionChecker } from '@/UserManagement/PermissionChecker';
|
||||||
import * as UserManagementHelper from '@/UserManagement/UserManagementHelper';
|
import * as UserManagementHelper from '@/UserManagement/UserManagementHelper';
|
||||||
import { WorkflowsService } from '@/workflows/workflows.services';
|
import { WorkflowsService } from '@/workflows/workflows.services';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
randomCredentialPayload as randomCred,
|
randomCredentialPayload as randomCred,
|
||||||
randomPositiveDigit,
|
randomPositiveDigit,
|
||||||
} from '../integration/shared/random';
|
} from '../integration/shared/random';
|
||||||
|
import * as testDb from '../integration/shared/testDb';
|
||||||
import { Role } from '@db/entities/Role';
|
import { mockNodeTypesData } from './Helpers';
|
||||||
import type { SaveCredentialFunction } from '../integration/shared/types';
|
import type { SaveCredentialFunction } from '../integration/shared/types';
|
||||||
import { User } from '@db/entities/User';
|
import { mockInstance } from '../integration/shared/utils';
|
||||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
|
||||||
|
|
||||||
let mockNodeTypes: INodeTypes;
|
let mockNodeTypes: INodeTypes;
|
||||||
let credentialOwnerRole: Role;
|
let credentialOwnerRole: Role;
|
||||||
let workflowOwnerRole: Role;
|
let workflowOwnerRole: Role;
|
||||||
let saveCredential: SaveCredentialFunction;
|
let saveCredential: SaveCredentialFunction;
|
||||||
|
|
||||||
|
const MOCK_NODE_TYPES_DATA = mockNodeTypesData(['start', 'actionNetwork']);
|
||||||
|
mockInstance(LoadNodesAndCredentials, {
|
||||||
|
loaded: {
|
||||||
|
nodes: MOCK_NODE_TYPES_DATA,
|
||||||
|
credentials: {},
|
||||||
|
},
|
||||||
|
known: { nodes: {}, credentials: {} },
|
||||||
|
credentialTypes: {} as ICredentialTypes,
|
||||||
|
});
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await testDb.init();
|
await testDb.init();
|
||||||
|
|
||||||
mockNodeTypes = MockNodeTypes({
|
mockNodeTypes = Container.get(NodeTypes);
|
||||||
loaded: {
|
|
||||||
nodes: MOCK_NODE_TYPES_DATA,
|
|
||||||
credentials: {},
|
|
||||||
},
|
|
||||||
known: { nodes: {}, credentials: {} },
|
|
||||||
credentialTypes: {} as ICredentialTypes,
|
|
||||||
});
|
|
||||||
|
|
||||||
credentialOwnerRole = await testDb.getCredentialOwnerRole();
|
credentialOwnerRole = await testDb.getCredentialOwnerRole();
|
||||||
workflowOwnerRole = await testDb.getWorkflowOwnerRole();
|
workflowOwnerRole = await testDb.getWorkflowOwnerRole();
|
||||||
|
@ -241,7 +242,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
connections: {},
|
connections: {},
|
||||||
active: false,
|
active: false,
|
||||||
nodeTypes: MockNodeTypes(),
|
nodeTypes: mockNodeTypes,
|
||||||
id: '2',
|
id: '2',
|
||||||
});
|
});
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -263,7 +264,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
connections: {},
|
connections: {},
|
||||||
active: false,
|
active: false,
|
||||||
nodeTypes: MockNodeTypes(),
|
nodeTypes: mockNodeTypes,
|
||||||
id: '2',
|
id: '2',
|
||||||
});
|
});
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -301,7 +302,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
connections: {},
|
connections: {},
|
||||||
active: false,
|
active: false,
|
||||||
nodeTypes: MockNodeTypes(),
|
nodeTypes: mockNodeTypes,
|
||||||
id: '2',
|
id: '2',
|
||||||
settings: {
|
settings: {
|
||||||
callerPolicy: 'workflowsFromAList',
|
callerPolicy: 'workflowsFromAList',
|
||||||
|
@ -327,7 +328,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
connections: {},
|
connections: {},
|
||||||
active: false,
|
active: false,
|
||||||
nodeTypes: MockNodeTypes(),
|
nodeTypes: mockNodeTypes,
|
||||||
id: '2',
|
id: '2',
|
||||||
});
|
});
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -350,7 +351,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
connections: {},
|
connections: {},
|
||||||
active: false,
|
active: false,
|
||||||
nodeTypes: MockNodeTypes(),
|
nodeTypes: mockNodeTypes,
|
||||||
id: '2',
|
id: '2',
|
||||||
settings: {
|
settings: {
|
||||||
callerPolicy: 'workflowsFromAList',
|
callerPolicy: 'workflowsFromAList',
|
||||||
|
@ -376,7 +377,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
connections: {},
|
connections: {},
|
||||||
active: false,
|
active: false,
|
||||||
nodeTypes: MockNodeTypes(),
|
nodeTypes: mockNodeTypes,
|
||||||
id: '2',
|
id: '2',
|
||||||
settings: {
|
settings: {
|
||||||
callerPolicy: 'any',
|
callerPolicy: 'any',
|
||||||
|
@ -387,5 +388,3 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
||||||
).resolves.not.toThrow();
|
).resolves.not.toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const MOCK_NODE_TYPES_DATA = mockNodeTypesData(['start', 'actionNetwork']);
|
|
||||||
|
|
697
pnpm-lock.yaml
697
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue