mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -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') {
|
||||
config.maxWorkers = 2;
|
||||
config.workerIdleMemoryLimit = 2048;
|
||||
config.workerIdleMemoryLimit = 1024;
|
||||
config.coverageReporters = ['cobertura'];
|
||||
}
|
||||
|
||||
|
|
10
package.json
10
package.json
|
@ -39,15 +39,15 @@
|
|||
"devDependencies": {
|
||||
"@n8n_io/eslint-config": "workspace:*",
|
||||
"@ngneat/falso": "^6.1.0",
|
||||
"@types/jest": "^29.2.2",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^12.7.0",
|
||||
"cypress-real-events": "^1.7.6",
|
||||
"jest": "^29.4.2",
|
||||
"jest-environment-jsdom": "^29.4.2",
|
||||
"jest-mock": "^29.4.2",
|
||||
"jest-mock-extended": "^3.0.1",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"jest-mock": "^29.5.0",
|
||||
"jest-mock-extended": "^3.0.3",
|
||||
"nock": "^13.2.9",
|
||||
"node-fetch": "^2.6.7",
|
||||
"p-limit": "^3.1.0",
|
||||
|
|
|
@ -1,30 +1 @@
|
|||
/* eslint-disable import/first */
|
||||
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,
|
||||
};
|
||||
export {};
|
||||
|
|
|
@ -1,339 +1,338 @@
|
|||
import express from 'express';
|
||||
import type { Application } from 'express';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import validator from 'validator';
|
||||
import config from '@/config';
|
||||
import * as Db from '@/Db';
|
||||
import { AUTH_COOKIE_NAME } from '@/constants';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { LOGGED_OUT_RESPONSE_BODY } from './shared/constants';
|
||||
import { randomValidPassword } from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { AuthAgent } from './shared/types';
|
||||
import * as utils from './shared/utils';
|
||||
|
||||
let app: express.Application;
|
||||
let app: Application;
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let owner: User;
|
||||
let authAgent: AuthAgent;
|
||||
let authlessAgent: SuperAgentTest;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
const ownerPassword = randomValidPassword();
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({ endpointGroups: ['auth'] });
|
||||
authAgent = utils.createAuthAgent(app);
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
|
||||
authAgent = utils.createAuthAgent(app);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['User']);
|
||||
|
||||
authlessAgent = utils.createAgent(app);
|
||||
config.set('ldap.disabled', true);
|
||||
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
|
||||
await Db.collections.Settings.update(
|
||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||
{ value: JSON.stringify(true) },
|
||||
);
|
||||
await utils.setInstanceOwnerSetUp(true);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.terminate();
|
||||
});
|
||||
|
||||
test('POST /login should log user in', async () => {
|
||||
const ownerPassword = randomValidPassword();
|
||||
const owner = await testDb.createUser({
|
||||
password: ownerPassword,
|
||||
globalRole: globalOwnerRole,
|
||||
describe('POST /login', () => {
|
||||
beforeEach(async () => {
|
||||
owner = await testDb.createUser({
|
||||
password: ownerPassword,
|
||||
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({
|
||||
email: owner.email,
|
||||
password: ownerPassword,
|
||||
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).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 {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
resetPasswordToken,
|
||||
apiKey,
|
||||
} = response.body.data;
|
||||
const response = await authlessAgent.get('/login');
|
||||
|
||||
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();
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeDefined();
|
||||
const authToken = utils.getAuthToken(response);
|
||||
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 () => {
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
describe('GET /resolve-signup-token', () => {
|
||||
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(authToken).toBeUndefined();
|
||||
});
|
||||
|
||||
test('GET /login should return cookie if UM is disabled and no cookie is already set', async () => {
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
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,
|
||||
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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = authAgent(owner);
|
||||
describe('POST /logout', () => {
|
||||
test('should log user out', async () => {
|
||||
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 third = await authOwnerAgent.get('/resolve-signup-token').query({
|
||||
inviterId: '5531199e-b7ae-425b-a326-a95ef8cca59d',
|
||||
inviteeId: 'cb133beb-7729-4c34-8cd1-a06be8834d9d',
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeUndefined();
|
||||
});
|
||||
|
||||
// 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 request from 'supertest';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import {
|
||||
REST_PATH_SEGMENT,
|
||||
ROUTES_REQUIRING_AUTHENTICATION,
|
||||
ROUTES_REQUIRING_AUTHORIZATION,
|
||||
} from './shared/constants';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { AuthAgent } from './shared/types';
|
||||
import * as utils from './shared/utils';
|
||||
|
||||
let app: express.Application;
|
||||
let globalMemberRole: Role;
|
||||
let authAgent: AuthAgent;
|
||||
let authlessAgent: SuperAgentTest;
|
||||
let authMemberAgent: SuperAgentTest;
|
||||
|
||||
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();
|
||||
|
||||
authAgent = utils.createAuthAgent(app);
|
||||
authlessAgent = utils.createAgent(app);
|
||||
authMemberAgent = utils.createAuthAgent(app)(member);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -31,9 +26,8 @@ ROUTES_REQUIRING_AUTHENTICATION.concat(ROUTES_REQUIRING_AUTHORIZATION).forEach((
|
|||
const [method, endpoint] = getMethodAndEndpoint(route);
|
||||
|
||||
test(`${route} should return 401 Unauthorized if no cookie`, async () => {
|
||||
const response = await request(app)[method](endpoint).use(utils.prefix(REST_PATH_SEGMENT));
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
const { statusCode } = await authlessAgent[method](endpoint);
|
||||
expect(statusCode).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -41,10 +35,8 @@ ROUTES_REQUIRING_AUTHORIZATION.forEach(async (route) => {
|
|||
const [method, endpoint] = getMethodAndEndpoint(route);
|
||||
|
||||
test(`${route} should return 403 Forbidden for member`, async () => {
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const response = await authAgent(member)[method](endpoint);
|
||||
|
||||
expect(response.statusCode).toBe(403);
|
||||
const { statusCode } = await authMemberAgent[method](endpoint);
|
||||
expect(statusCode).toBe(403);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,43 +1,48 @@
|
|||
import express from 'express';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import { In } from 'typeorm';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
import * as Db from '@/Db';
|
||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
import type { CredentialWithSharings } from '@/credentials/credentials.types';
|
||||
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { randomCredentialPayload } from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { AuthAgent, SaveCredentialFunction } from './shared/types';
|
||||
import * as utils from './shared/utils';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
let app: express.Application;
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let credentialOwnerRole: Role;
|
||||
let saveCredential: SaveCredentialFunction;
|
||||
let owner: User;
|
||||
let member: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
let authAgent: AuthAgent;
|
||||
let saveCredential: SaveCredentialFunction;
|
||||
let sharingSpy: jest.SpyInstance<boolean>;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({ endpointGroups: ['credentials'] });
|
||||
const app = await utils.initTestServer({ endpointGroups: ['credentials'] });
|
||||
|
||||
utils.initConfigFile();
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
const globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
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);
|
||||
authAgent = utils.createAuthAgent(app);
|
||||
|
||||
sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['User', 'SharedCredentials', 'Credentials']);
|
||||
await testDb.truncate(['SharedCredentials', 'Credentials']);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -47,490 +52,452 @@ afterAll(async () => {
|
|||
// ----------------------------------------
|
||||
// dynamic router switching
|
||||
// ----------------------------------------
|
||||
describe('router should switch based on flag', () => {
|
||||
let savedCredentialId: string;
|
||||
|
||||
test('router should switch based on flag', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
beforeEach(async () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
savedCredentialId = savedCredential.id;
|
||||
});
|
||||
|
||||
// free router
|
||||
sharingSpy.mockReturnValueOnce(false);
|
||||
test('when sharing is disabled', async () => {
|
||||
sharingSpy.mockReturnValueOnce(false);
|
||||
|
||||
const freeShareResponse = authAgent(owner)
|
||||
.put(`/credentials/${savedCredential.id}/share`)
|
||||
.send({ shareWithIds: [member.id] });
|
||||
await authOwnerAgent
|
||||
.put(`/credentials/${savedCredentialId}/share`)
|
||||
.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([
|
||||
freeShareResponse,
|
||||
freeGetResponse,
|
||||
]);
|
||||
test('when sharing is enabled', async () => {
|
||||
await authOwnerAgent
|
||||
.put(`/credentials/${savedCredentialId}/share`)
|
||||
.send({ shareWithIds: [member.id] })
|
||||
.expect(200);
|
||||
|
||||
expect(freeShareStatus).toBe(404);
|
||||
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);
|
||||
await authOwnerAgent.get(`/credentials/${savedCredentialId}`).send().expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
// ----------------------------------------
|
||||
// GET /credentials - fetch all credentials
|
||||
// ----------------------------------------
|
||||
|
||||
test('GET /credentials should return all creds for owner', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const [member1, member2, member3] = await testDb.createManyUsers(3, {
|
||||
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,
|
||||
describe('GET /credentials', () => {
|
||||
test('should return all creds for owner', async () => {
|
||||
const [member1, member2, member3] = await testDb.createManyUsers(3, {
|
||||
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 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({
|
||||
id: member1.id,
|
||||
email: member1.email,
|
||||
firstName: member1.firstName,
|
||||
lastName: member1.lastName,
|
||||
});
|
||||
test('should return only relevant creds for member', async () => {
|
||||
const [member1, member2] = await testDb.createManyUsers(2, {
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
|
||||
expect(Array.isArray(memberCredential.sharedWith)).toBe(true);
|
||||
expect(memberCredential.sharedWith).toHaveLength(0);
|
||||
});
|
||||
await saveCredential(randomCredentialPayload(), { user: member2 });
|
||||
const savedMemberCredential = await saveCredential(randomCredentialPayload(), {
|
||||
user: member1,
|
||||
});
|
||||
|
||||
test('GET /credentials should return only relevant creds for member', async () => {
|
||||
const [member1, member2] = await testDb.createManyUsers(2, {
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
await testDb.shareCredentialWithUsers(savedMemberCredential, [member2]);
|
||||
|
||||
await saveCredential(randomCredentialPayload(), { user: member2 });
|
||||
const savedMemberCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
||||
const response = await authAgent(member1).get('/credentials');
|
||||
|
||||
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);
|
||||
expect(response.body.data).toHaveLength(1); // member retrieved only member cred
|
||||
validateMainCredentialData(member1Credential);
|
||||
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(member1Credential.data).toBeUndefined();
|
||||
expect(Array.isArray(member1Credential.sharedWith)).toBe(true);
|
||||
expect(member1Credential.sharedWith).toHaveLength(1);
|
||||
|
||||
expect(member1Credential.ownedBy).toMatchObject({
|
||||
id: member1.id,
|
||||
email: member1.email,
|
||||
firstName: member1.firstName,
|
||||
lastName: member1.lastName,
|
||||
});
|
||||
const [sharee] = member1Credential.sharedWith;
|
||||
|
||||
expect(Array.isArray(member1Credential.sharedWith)).toBe(true);
|
||||
expect(member1Credential.sharedWith).toHaveLength(1);
|
||||
|
||||
const [sharee] = member1Credential.sharedWith;
|
||||
|
||||
expect(sharee).toMatchObject({
|
||||
id: member2.id,
|
||||
email: member2.email,
|
||||
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
|
||||
// ----------------------------------------
|
||||
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 ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = authAgent(ownerShell);
|
||||
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);
|
||||
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;
|
||||
validateMainCredentialData(firstCredential);
|
||||
expect(firstCredential.data).toBeUndefined();
|
||||
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
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
|
||||
const secondResponse = await authOwnerAgent
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
expect(secondResponse.statusCode).toBe(200);
|
||||
|
||||
expect(secondResponse.statusCode).toBe(200);
|
||||
|
||||
const { data: secondCredential } = secondResponse.body;
|
||||
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 { data: secondCredential } = secondResponse.body;
|
||||
validateMainCredentialData(secondCredential);
|
||||
expect(secondCredential.data).toBeDefined();
|
||||
});
|
||||
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
||||
await testDb.shareCredentialWithUsers(savedCredential, [member2]);
|
||||
test('should retrieve non-owned cred for owner', async () => {
|
||||
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.body.data.data).toBeUndefined();
|
||||
expect(response1.body.data.ownedBy).toMatchObject({
|
||||
id: member1.id,
|
||||
email: member1.email,
|
||||
firstName: member1.firstName,
|
||||
lastName: member1.lastName,
|
||||
});
|
||||
expect(response1.body.data.sharedWith).toHaveLength(1);
|
||||
expect(response1.body.data.sharedWith[0]).toMatchObject({
|
||||
id: member2.id,
|
||||
email: member2.email,
|
||||
firstName: member2.firstName,
|
||||
lastName: member2.lastName,
|
||||
expect(response1.statusCode).toBe(200);
|
||||
|
||||
validateMainCredentialData(response1.body.data);
|
||||
expect(response1.body.data.data).toBeUndefined();
|
||||
expect(response1.body.data.ownedBy).toMatchObject({
|
||||
id: member1.id,
|
||||
email: member1.email,
|
||||
firstName: member1.firstName,
|
||||
lastName: member1.lastName,
|
||||
});
|
||||
expect(response1.body.data.sharedWith).toHaveLength(1);
|
||||
expect(response1.body.data.sharedWith[0]).toMatchObject({
|
||||
id: member2.id,
|
||||
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
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
test('should retrieve owned cred for member', async () => {
|
||||
const [member1, member2, member3] = await testDb.createManyUsers(3, {
|
||||
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(response2.body.data.data).toBeUndefined();
|
||||
expect(response2.body.data.sharedWith).toHaveLength(1);
|
||||
});
|
||||
expect(firstResponse.statusCode).toBe(200);
|
||||
|
||||
test('GET /credentials/:id should retrieve owned cred for member', async () => {
|
||||
const [member1, member2, member3] = await testDb.createManyUsers(3, {
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
const authMemberAgent = authAgent(member1);
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
||||
await testDb.shareCredentialWithUsers(savedCredential, [member2, member3]);
|
||||
const { data: firstCredential } = firstResponse.body;
|
||||
validateMainCredentialData(firstCredential);
|
||||
expect(firstCredential.data).toBeUndefined();
|
||||
expect(firstCredential.ownedBy).toMatchObject({
|
||||
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 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;
|
||||
validateMainCredentialData(firstCredential);
|
||||
expect(firstCredential.data).toBeUndefined();
|
||||
expect(firstCredential.ownedBy).toMatchObject({
|
||||
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 { data: secondCredential } = secondResponse.body;
|
||||
validateMainCredentialData(secondCredential);
|
||||
expect(secondCredential.data).toBeDefined();
|
||||
expect(firstCredential.sharedWith).toHaveLength(2);
|
||||
});
|
||||
|
||||
const secondResponse = await authMemberAgent
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
test('should not retrieve non-owned cred for member', async () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
expect(secondResponse.statusCode).toBe(200);
|
||||
const response = await authAgent(member).get(`/credentials/${savedCredential.id}`);
|
||||
|
||||
const { data: secondCredential } = secondResponse.body;
|
||||
validateMainCredentialData(secondCredential);
|
||||
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 },
|
||||
expect(response.statusCode).toBe(403);
|
||||
expect(response.body.data).toBeUndefined(); // owner's cred not returned
|
||||
});
|
||||
|
||||
// check that sharings have been removed/added correctly
|
||||
expect(sharedCredentials.length).toBe(shareWithIds.length + 1); // +1 for the owner
|
||||
test('should fail with missing encryption key', async () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
sharedCredentials.forEach((sharedCredential) => {
|
||||
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');
|
||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
|
||||
expect(response.statusCode).toBe(500);
|
||||
|
||||
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 owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
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 [member1, member2, member3, member4, member5] = await testDb.createManyUsers(5, {
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
const shareWithIds = [member1.id, member2.id, member3.id];
|
||||
|
||||
const response = await authAgent(owner)
|
||||
.put(`/credentials/${savedCredential.id}/share`)
|
||||
.send({ shareWithIds: memberIds });
|
||||
await testDb.shareCredentialWithUsers(savedCredential, [member4, member5]);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
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)
|
||||
const response = await authOwnerAgent
|
||||
.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();
|
||||
|
||||
// ----------------------------------------
|
||||
// unshare
|
||||
// ----------------------------------------
|
||||
const sharedCredentials = await Db.collections.SharedCredentials.find({
|
||||
relations: ['role'],
|
||||
where: { credentialsId: savedCredential.id },
|
||||
});
|
||||
|
||||
test('PUT /credentials/:id/share should unshare the credential', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
// check that sharings have been removed/added correctly
|
||||
expect(sharedCredentials.length).toBe(shareWithIds.length + 1); // +1 for the owner
|
||||
|
||||
const [member1, member2] = await testDb.createManyUsers(2, {
|
||||
globalRole: globalMemberRole,
|
||||
sharedCredentials.forEach((sharedCredential) => {
|
||||
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)
|
||||
.put(`/credentials/${savedCredential.id}/share`)
|
||||
.send({ shareWithIds: [] });
|
||||
const response = await authOwnerAgent
|
||||
.put(`/credentials/${savedCredential.id}/share`)
|
||||
.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({
|
||||
where: { credentialsId: savedCredential.id },
|
||||
// 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');
|
||||
});
|
||||
|
||||
expect(sharedCredentials).toHaveLength(1);
|
||||
expect(sharedCredentials[0].userId).toBe(owner.id);
|
||||
test('should respond 403 for non-existing credentials', async () => {
|
||||
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) {
|
||||
|
|
|
@ -1,26 +1,31 @@
|
|||
import express from 'express';
|
||||
import type { Application } from 'express';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
|
||||
import * as Db from '@/Db';
|
||||
import config from '@/config';
|
||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
||||
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { randomCredentialPayload, randomName, randomString } from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { SaveCredentialFunction } from './shared/types';
|
||||
import * as utils from './shared/utils';
|
||||
|
||||
import config from '@/config';
|
||||
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||
import type { AuthAgent } from './shared/types';
|
||||
|
||||
// mock that credentialsSharing is not enabled
|
||||
const mockIsCredentialsSharingEnabled = jest.spyOn(UserManagementHelpers, 'isSharingEnabled');
|
||||
mockIsCredentialsSharingEnabled.mockReturnValue(false);
|
||||
|
||||
let app: express.Application;
|
||||
let app: Application;
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let owner: User;
|
||||
let member: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
let authMemberAgent: SuperAgentTest;
|
||||
let saveCredential: SaveCredentialFunction;
|
||||
let authAgent: AuthAgent;
|
||||
|
||||
|
@ -33,13 +38,18 @@ beforeAll(async () => {
|
|||
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
const credentialOwnerRole = await testDb.getCredentialOwnerRole();
|
||||
|
||||
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
|
||||
|
||||
authAgent = utils.createAuthAgent(app);
|
||||
authOwnerAgent = authAgent(owner);
|
||||
authMemberAgent = authAgent(member);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['User', 'SharedCredentials', 'Credentials']);
|
||||
await testDb.truncate(['SharedCredentials', 'Credentials']);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -49,526 +59,490 @@ afterAll(async () => {
|
|||
// ----------------------------------------
|
||||
// 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 [owner, member] = await Promise.all([
|
||||
testDb.createUser({ globalRole: globalOwnerRole }),
|
||||
testDb.createUser({ globalRole: globalMemberRole }),
|
||||
]);
|
||||
const response = await authOwnerAgent.get('/credentials');
|
||||
|
||||
const [{ id: savedOwnerCredentialId }, { id: savedMemberCredentialId }] = await Promise.all([
|
||||
saveCredential(randomCredentialPayload(), { user: owner }),
|
||||
saveCredential(randomCredentialPayload(), { user: member }),
|
||||
]);
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body.data.length).toBe(2); // owner retrieved owner cred and member cred
|
||||
|
||||
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);
|
||||
expect(response.body.data.length).toBe(2); // owner retrieved owner cred and member cred
|
||||
test('should return only own creds for member', async () => {
|
||||
const [member1, member2] = await testDb.createManyUsers(2, {
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
|
||||
const savedCredentialsIds = [savedOwnerCredentialId, savedMemberCredentialId];
|
||||
response.body.data.forEach((credential: CredentialsEntity) => {
|
||||
validateMainCredentialData(credential);
|
||||
expect(credential.data).toBeUndefined();
|
||||
expect(savedCredentialsIds).toContain(credential.id);
|
||||
const [savedCredential1] = await Promise.all([
|
||||
saveCredential(randomCredentialPayload(), { user: member1 }),
|
||||
saveCredential(randomCredentialPayload(), { user: member2 }),
|
||||
]);
|
||||
|
||||
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 () => {
|
||||
const [member1, member2] = await testDb.createManyUsers(2, {
|
||||
globalRole: globalMemberRole,
|
||||
describe('POST /credentials', () => {
|
||||
test('should create cred', async () => {
|
||||
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([
|
||||
saveCredential(randomCredentialPayload(), { user: member1 }),
|
||||
saveCredential(randomCredentialPayload(), { user: member2 }),
|
||||
]);
|
||||
|
||||
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 },
|
||||
test('should fail with invalid inputs', async () => {
|
||||
await Promise.all(
|
||||
INVALID_PAYLOADS.map(async (invalidPayload) => {
|
||||
const response = await authOwnerAgent.post('/credentials').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(sharedCredential.user.id).toBe(ownerShell.id);
|
||||
expect(sharedCredential.credentials.name).toBe(payload.name);
|
||||
});
|
||||
test('should fail with missing encryption key', async () => {
|
||||
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 ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = authAgent(ownerShell);
|
||||
const response = await authOwnerAgent.post('/credentials').send(randomCredentialPayload());
|
||||
|
||||
await Promise.all(
|
||||
INVALID_PAYLOADS.map(async (invalidPayload) => {
|
||||
const response = await authOwnerAgent.post('/credentials').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(response.statusCode).toBe(500);
|
||||
|
||||
test('POST /credentials 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('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 },
|
||||
mock.mockRestore();
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||
const patchPayload = randomCredentialPayload();
|
||||
describe('DELETE /credentials/:id', () => {
|
||||
test('should delete owned cred for owner', async () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
const response = await authAgent(ownerShell)
|
||||
.patch(`/credentials/${savedCredential.id}`)
|
||||
.send(patchPayload);
|
||||
const response = await authOwnerAgent.delete(`/credentials/${savedCredential.id}`);
|
||||
|
||||
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(type).toBe(patchPayload.type);
|
||||
expect(deletedCredential).toBeNull(); // deleted
|
||||
|
||||
if (!patchPayload.nodesAccess) {
|
||||
fail('Payload did not contain a nodesAccess array');
|
||||
}
|
||||
expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
||||
|
||||
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(deletedSharedCredential).toBeNull(); // deleted
|
||||
});
|
||||
|
||||
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 member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||
const patchPayload = randomCredentialPayload();
|
||||
const response = await authOwnerAgent.delete(`/credentials/${savedCredential.id}`);
|
||||
|
||||
const response = await authAgent(member)
|
||||
.patch(`/credentials/${savedCredential.id}`)
|
||||
.send(patchPayload);
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({ data: true });
|
||||
|
||||
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);
|
||||
expect(type).toBe(patchPayload.type);
|
||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOneBy({});
|
||||
|
||||
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(deletedSharedCredential).toBeNull(); // deleted
|
||||
});
|
||||
|
||||
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 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 authMemberAgent.delete(`/credentials/${savedCredential.id}`);
|
||||
|
||||
const response = await authAgent(member)
|
||||
.patch(`/credentials/${savedCredential.id}`)
|
||||
.send(patchPayload);
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({ data: true });
|
||||
|
||||
expect(response.statusCode).toBe(404);
|
||||
const deletedCredential = await Db.collections.Credentials.findOneBy({
|
||||
id: savedCredential.id,
|
||||
});
|
||||
|
||||
const shellCredential = await Db.collections.Credentials.findOneByOrFail({
|
||||
id: savedCredential.id,
|
||||
expect(deletedCredential).toBeNull(); // deleted
|
||||
|
||||
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 () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = authAgent(ownerShell);
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
||||
describe('PATCH /credentials/:id', () => {
|
||||
test('should update owned cred for owner', async () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
const patchPayload = randomCredentialPayload();
|
||||
|
||||
await Promise.all(
|
||||
INVALID_PAYLOADS.map(async (invalidPayload) => {
|
||||
const response = await authOwnerAgent
|
||||
.patch(`/credentials/${savedCredential.id}`)
|
||||
.send(invalidPayload);
|
||||
const response = await authOwnerAgent
|
||||
.patch(`/credentials/${savedCredential.id}`)
|
||||
.send(patchPayload);
|
||||
|
||||
if (response.statusCode === 500) {
|
||||
console.log(response.statusCode, response.body);
|
||||
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 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);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
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: owner });
|
||||
}
|
||||
await saveCredential({ ...randomCredentialPayload(), name: tempName }, { user: ownerShell });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('GET /credentials/new should return name from query for new credential or its increment', async () => {
|
||||
const ownerShell = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = authAgent(ownerShell);
|
||||
const name = 'special credential name';
|
||||
let tempName = name;
|
||||
test('should return name from query for new credential or its increment', async () => {
|
||||
const name = 'special credential name';
|
||||
let tempName = name;
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const response = await authOwnerAgent.get(`/credentials/new?name=${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(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: owner });
|
||||
}
|
||||
await saveCredential({ ...randomCredentialPayload(), name: tempName }, { user: ownerShell });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('GET /credentials/:id should retrieve owned cred for owner', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = authAgent(ownerShell);
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: ownerShell });
|
||||
describe('GET /credentials/:id', () => {
|
||||
test('should retrieve owned cred for owner', async () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
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);
|
||||
expect(firstResponse.body.data.data).toBeUndefined();
|
||||
validateMainCredentialData(firstResponse.body.data);
|
||||
expect(firstResponse.body.data.data).toBeUndefined();
|
||||
|
||||
const secondResponse = await authOwnerAgent
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
const secondResponse = await authOwnerAgent
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
|
||||
validateMainCredentialData(secondResponse.body.data);
|
||||
expect(secondResponse.body.data.data).toBeDefined();
|
||||
});
|
||||
validateMainCredentialData(secondResponse.body.data);
|
||||
expect(secondResponse.body.data.data).toBeDefined();
|
||||
});
|
||||
|
||||
test('GET /credentials/:id should retrieve owned cred for member', async () => {
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = authAgent(member);
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||
test('should retrieve owned cred for member', async () => {
|
||||
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);
|
||||
expect(firstResponse.body.data.data).toBeUndefined();
|
||||
validateMainCredentialData(firstResponse.body.data);
|
||||
expect(firstResponse.body.data.data).toBeUndefined();
|
||||
|
||||
const secondResponse = await authMemberAgent
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
const secondResponse = await authMemberAgent
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
|
||||
expect(secondResponse.statusCode).toBe(200);
|
||||
expect(secondResponse.statusCode).toBe(200);
|
||||
|
||||
validateMainCredentialData(secondResponse.body.data);
|
||||
expect(secondResponse.body.data.data).toBeDefined();
|
||||
});
|
||||
validateMainCredentialData(secondResponse.body.data);
|
||||
expect(secondResponse.body.data.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 member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
test('should retrieve non-owned cred for owner', async () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||
|
||||
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);
|
||||
expect(response1.body.data.data).toBeUndefined();
|
||||
const response2 = await authOwnerAgent
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
|
||||
const response2 = await authOwnerAgent
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
expect(response2.statusCode).toBe(200);
|
||||
|
||||
expect(response2.statusCode).toBe(200);
|
||||
validateMainCredentialData(response2.body.data);
|
||||
expect(response2.body.data.data).toBeDefined();
|
||||
});
|
||||
|
||||
validateMainCredentialData(response2.body.data);
|
||||
expect(response2.body.data.data).toBeDefined();
|
||||
});
|
||||
test('should not retrieve non-owned cred for member', async () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
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 authMemberAgent.get(`/credentials/${savedCredential.id}`);
|
||||
|
||||
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);
|
||||
expect(response.body.data).toBeUndefined(); // owner's cred not returned
|
||||
});
|
||||
test('should fail with missing encryption key', async () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
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 mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||
const response = await authOwnerAgent
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
|
||||
const response = await authAgent(ownerShell)
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
expect(response.statusCode).toBe(500);
|
||||
|
||||
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 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);
|
||||
const responseAbc = await authOwnerAgent.get('/credentials/abc');
|
||||
expect(responseAbc.statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
function validateMainCredentialData(credential: CredentialsEntity) {
|
||||
|
|
|
@ -2,6 +2,8 @@ import express from 'express';
|
|||
import config from '@/config';
|
||||
import axios from 'axios';
|
||||
import syslog from 'syslog-client';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import * as utils from './shared/utils';
|
||||
import * as testDb from './shared/testDb';
|
||||
import { Role } from '@db/entities/Role';
|
||||
|
@ -15,14 +17,12 @@ import {
|
|||
MessageEventBusDestinationWebhookOptions,
|
||||
} from 'n8n-workflow';
|
||||
import { eventBus } from '@/eventbus';
|
||||
import { SuperAgentTest } from 'supertest';
|
||||
import { EventMessageGeneric } from '@/eventbus/EventMessageClasses/EventMessageGeneric';
|
||||
import { MessageEventBusDestinationSyslog } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee';
|
||||
import { MessageEventBusDestinationWebhook } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee';
|
||||
import { MessageEventBusDestinationSentry } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee';
|
||||
import { EventMessageAudit } from '@/eventbus/EventMessageClasses/EventMessageAudit';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { EventNamesTypes } from '../../src/eventbus/EventMessageClasses';
|
||||
import { EventNamesTypes } from '@/eventbus/EventMessageClasses';
|
||||
|
||||
jest.unmock('@/eventbus/MessageEventBus/MessageEventBus');
|
||||
jest.mock('axios');
|
||||
|
@ -54,6 +54,7 @@ const testWebhookDestination: MessageEventBusDestinationWebhookOptions = {
|
|||
enabled: false,
|
||||
subscribedEvents: ['n8n.test.message', 'n8n.audit.user.updated'],
|
||||
};
|
||||
|
||||
const testSentryDestination: MessageEventBusDestinationSentryOptions = {
|
||||
...defaultMessageEventBusDestinationSentryOptions,
|
||||
id: '450ca04b-87dd-4837-a052-ab3a347a00e9',
|
||||
|
@ -101,13 +102,10 @@ beforeAll(async () => {
|
|||
config.set('eventBus.logWriter.logBaseName', 'n8n-test-logwriter');
|
||||
config.set('eventBus.logWriter.keepLogCount', '1');
|
||||
config.set('enterprise.features.logStreaming', true);
|
||||
|
||||
await eventBus.initialize();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
config.set('userManagement.disabled', false);
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
|
||||
await eventBus.initialize();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import express from 'express';
|
||||
import type { Entry as LdapUser } from 'ldapts';
|
||||
import { Not } from 'typeorm';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import config from '@/config';
|
||||
import * as Db from '@/Db';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
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 { LdapService } from '@/Ldap/LdapService.ee';
|
||||
import { encryptPassword, saveLdapSynchronization } from '@/Ldap/helpers';
|
||||
|
@ -21,7 +22,6 @@ jest.mock('@/UserManagement/email/NodeMailer');
|
|||
|
||||
let app: express.Application;
|
||||
let globalMemberRole: Role;
|
||||
let globalOwnerRole: Role;
|
||||
let owner: User;
|
||||
let authAgent: AuthAgent;
|
||||
|
||||
|
@ -42,11 +42,12 @@ const defaultLdapConfig = {
|
|||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({ endpointGroups: ['auth', 'ldap'] });
|
||||
|
||||
const [fetchedGlobalOwnerRole, fetchedGlobalMemberRole] = await testDb.getAllRoles();
|
||||
const [globalOwnerRole, fetchedGlobalMemberRole] = await testDb.getAllRoles();
|
||||
|
||||
globalOwnerRole = fetchedGlobalOwnerRole;
|
||||
globalMemberRole = fetchedGlobalMemberRole;
|
||||
|
||||
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
authAgent = utils.createAuthAgent(app);
|
||||
|
||||
defaultLdapConfig.bindingAdminPassword = await encryptPassword(
|
||||
|
@ -64,10 +65,9 @@ beforeEach(async () => {
|
|||
'Credentials',
|
||||
'SharedWorkflow',
|
||||
'Workflow',
|
||||
'User',
|
||||
]);
|
||||
|
||||
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
await Db.collections.User.delete({ id: Not(owner.id) });
|
||||
|
||||
jest.mock('@/telemetry');
|
||||
|
||||
|
|
|
@ -1,41 +1,36 @@
|
|||
import express from 'express';
|
||||
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import config from '@/config';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { AuthAgent } from './shared/types';
|
||||
import * as utils from './shared/utils';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { ILicensePostResponse, ILicenseReadResponse } from '@/Interfaces';
|
||||
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_RENEW_OFFSET = 259200;
|
||||
const MOCK_INSTANCE_ID = 'instance-id';
|
||||
|
||||
let app: express.Application;
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let authAgent: AuthAgent;
|
||||
let license: License;
|
||||
let owner: User;
|
||||
let member: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
let authMemberAgent: SuperAgentTest;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({ endpointGroups: ['license'] });
|
||||
const app = await utils.initTestServer({ endpointGroups: ['license'] });
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
const globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
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.autoRenewEnabled', true);
|
||||
config.set('license.autoRenewOffset', MOCK_RENEW_OFFSET);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
license = new License();
|
||||
await license.init(MOCK_INSTANCE_ID);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await testDb.truncate(['Settings']);
|
||||
});
|
||||
|
@ -44,98 +39,66 @@ afterAll(async () => {
|
|||
await testDb.terminate();
|
||||
});
|
||||
|
||||
test('GET /license should return license information to the instance owner', async () => {
|
||||
const userShell = await testDb.createUserShell(globalOwnerRole);
|
||||
|
||||
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('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);
|
||||
describe('GET /license', () => {
|
||||
test('should return license information to the instance owner', async () => {
|
||||
// No license defined so we just expect the result to be the defaults
|
||||
await authOwnerAgent.get('/license').expect(200, DEFAULT_LICENSE_RESPONSE);
|
||||
});
|
||||
|
||||
const userShell = await testDb.createUserShell(globalOwnerRole);
|
||||
|
||||
const response = await authAgent(userShell)
|
||||
.post('/license/activate')
|
||||
.send({ activationKey: 'abcde' });
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.body.message).toBe(INVALID_ACIVATION_KEY_MESSAGE);
|
||||
test('should return license information to a regular user', async () => {
|
||||
// No license defined so we just expect the result to be the defaults
|
||||
await authMemberAgent.get('/license').expect(200, DEFAULT_LICENSE_RESPONSE);
|
||||
});
|
||||
});
|
||||
|
||||
test('POST /license/renew should work for instance owner', async () => {
|
||||
const userShell = await testDb.createUserShell(globalOwnerRole);
|
||||
|
||||
const response = await authAgent(userShell).post('/license/renew');
|
||||
|
||||
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/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);
|
||||
describe('POST /license/activate', () => {
|
||||
test('should work for instance owner', async () => {
|
||||
await authOwnerAgent
|
||||
.post('/license/activate')
|
||||
.send({ activationKey: 'abcde' })
|
||||
.expect(200, DEFAULT_POST_RESPONSE);
|
||||
});
|
||||
|
||||
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);
|
||||
expect(response.body.message).toBe(RENEW_ERROR_MESSAGE);
|
||||
await authOwnerAgent
|
||||
.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 } = {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import express from 'express';
|
||||
import type { Application } from 'express';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import { IsNull } from 'typeorm';
|
||||
import validator from 'validator';
|
||||
|
||||
import config from '@/config';
|
||||
import * as Db from '@/Db';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
||||
import {
|
||||
randomApiKey,
|
||||
|
@ -17,7 +17,7 @@ import * as testDb from './shared/testDb';
|
|||
import type { AuthAgent } from './shared/types';
|
||||
import * as utils from './shared/utils';
|
||||
|
||||
let app: express.Application;
|
||||
let app: Application;
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let authAgent: AuthAgent;
|
||||
|
@ -40,10 +40,16 @@ afterAll(async () => {
|
|||
});
|
||||
|
||||
describe('Owner shell', () => {
|
||||
test('PATCH /me should succeed with valid inputs', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerShellAgent = authAgent(ownerShell);
|
||||
let ownerShell: User;
|
||||
let authOwnerShellAgent: SuperAgentTest;
|
||||
|
||||
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) {
|
||||
const response = await authOwnerShellAgent.patch('/me').send(validPayload);
|
||||
|
||||
|
@ -83,9 +89,6 @@ describe('Owner shell', () => {
|
|||
});
|
||||
|
||||
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) {
|
||||
const response = await authOwnerShellAgent.patch('/me').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
@ -98,9 +101,6 @@ describe('Owner shell', () => {
|
|||
});
|
||||
|
||||
test('PATCH /me/password should fail for shell', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerShellAgent = authAgent(ownerShell);
|
||||
|
||||
const validPasswordPayload = {
|
||||
currentPassword: randomValidPassword(),
|
||||
newPassword: randomValidPassword(),
|
||||
|
@ -130,9 +130,6 @@ describe('Owner shell', () => {
|
|||
});
|
||||
|
||||
test('POST /me/survey should succeed with valid inputs', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerShellAgent = authAgent(ownerShell);
|
||||
|
||||
const validPayloads = [SURVEY, {}];
|
||||
|
||||
for (const validPayload of validPayloads) {
|
||||
|
@ -150,9 +147,7 @@ describe('Owner shell', () => {
|
|||
});
|
||||
|
||||
test('POST /me/api-key should create an api key', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
|
||||
const response = await authAgent(ownerShell).post('/me/api-key');
|
||||
const response = await authOwnerShellAgent.post('/me/api-key');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body.data.apiKey).toBeDefined();
|
||||
|
@ -166,20 +161,14 @@ describe('Owner shell', () => {
|
|||
});
|
||||
|
||||
test('GET /me/api-key should fetch the api key', async () => {
|
||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
ownerShell = await testDb.addApiKey(ownerShell);
|
||||
|
||||
const response = await authAgent(ownerShell).get('/me/api-key');
|
||||
const response = await authOwnerShellAgent.get('/me/api-key');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body.data.apiKey).toEqual(ownerShell.apiKey);
|
||||
});
|
||||
|
||||
test('DELETE /me/api-key should fetch the api key', async () => {
|
||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
ownerShell = await testDb.addApiKey(ownerShell);
|
||||
|
||||
const response = await authAgent(ownerShell).delete('/me/api-key');
|
||||
const response = await authOwnerShellAgent.delete('/me/api-key');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
|
@ -192,19 +181,22 @@ describe('Owner shell', () => {
|
|||
});
|
||||
|
||||
describe('Member', () => {
|
||||
beforeEach(async () => {
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
const memberPassword = randomValidPassword();
|
||||
let member: User;
|
||||
let authMemberAgent: SuperAgentTest;
|
||||
|
||||
await Db.collections.Settings.update(
|
||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||
{ value: JSON.stringify(true) },
|
||||
);
|
||||
beforeEach(async () => {
|
||||
member = await testDb.createUser({
|
||||
password: memberPassword,
|
||||
globalRole: globalMemberRole,
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
authMemberAgent = authAgent(member);
|
||||
|
||||
await utils.setInstanceOwnerSetUp(true);
|
||||
});
|
||||
|
||||
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) {
|
||||
const response = await authMemberAgent.patch('/me').send(validPayload);
|
||||
|
||||
|
@ -244,9 +236,6 @@ describe('Member', () => {
|
|||
});
|
||||
|
||||
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) {
|
||||
const response = await authMemberAgent.patch('/me').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
@ -259,18 +248,12 @@ describe('Member', () => {
|
|||
});
|
||||
|
||||
test('PATCH /me/password should succeed with valid inputs', async () => {
|
||||
const memberPassword = randomValidPassword();
|
||||
const member = await testDb.createUser({
|
||||
password: memberPassword,
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
|
||||
const validPayload = {
|
||||
currentPassword: memberPassword,
|
||||
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.body).toEqual(SUCCESS_RESPONSE_BODY);
|
||||
|
||||
|
@ -280,9 +263,6 @@ describe('Member', () => {
|
|||
});
|
||||
|
||||
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) {
|
||||
const response = await authMemberAgent.patch('/me/password').send(payload);
|
||||
expect([400, 500].includes(response.statusCode)).toBe(true);
|
||||
|
@ -299,9 +279,6 @@ describe('Member', () => {
|
|||
});
|
||||
|
||||
test('POST /me/survey should succeed with valid inputs', async () => {
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = authAgent(member);
|
||||
|
||||
const validPayloads = [SURVEY, {}];
|
||||
|
||||
for (const validPayload of validPayloads) {
|
||||
|
@ -318,11 +295,6 @@ describe('Member', () => {
|
|||
});
|
||||
|
||||
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');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
@ -335,11 +307,6 @@ describe('Member', () => {
|
|||
});
|
||||
|
||||
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');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
@ -347,11 +314,6 @@ describe('Member', () => {
|
|||
});
|
||||
|
||||
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');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
@ -364,7 +326,7 @@ describe('Member', () => {
|
|||
|
||||
describe('Owner', () => {
|
||||
beforeEach(async () => {
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
await utils.setInstanceOwnerSetUp(true);
|
||||
});
|
||||
|
||||
test('PATCH /me should succeed with valid inputs', async () => {
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import path from 'path';
|
||||
|
||||
import express from 'express';
|
||||
import { mocked } from 'jest-mock';
|
||||
|
||||
import * as utils from './shared/utils';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import {
|
||||
executeCommand,
|
||||
checkNpmPackageStatus,
|
||||
|
@ -15,13 +11,13 @@ import {
|
|||
import { findInstalledPackage, isPackageInstalled } from '@/CommunityNodes/packageModel';
|
||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||
import { InstalledPackages } from '@db/entities/InstalledPackages';
|
||||
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { AuthAgent } from './shared/types';
|
||||
import type { User } from '@db/entities/User';
|
||||
import type { InstalledNodes } from '@db/entities/InstalledNodes';
|
||||
import { COMMUNITY_PACKAGE_VERSION } from './shared/constants';
|
||||
import { NodeTypes } from '@/NodeTypes';
|
||||
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);
|
||||
utils.mockInstance(NodeTypes);
|
||||
|
@ -48,22 +44,21 @@ jest.mock('@/CommunityNodes/packageModel', () => {
|
|||
|
||||
const mockedEmptyPackage = mocked(utils.emptyPackage);
|
||||
|
||||
let app: express.Application;
|
||||
let globalOwnerRole: Role;
|
||||
let authAgent: AuthAgent;
|
||||
let ownerShell: User;
|
||||
let authOwnerShellAgent: SuperAgentTest;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({ endpointGroups: ['nodes'] });
|
||||
const app = await utils.initTestServer({ endpointGroups: ['nodes'] });
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
|
||||
authAgent = utils.createAuthAgent(app);
|
||||
const globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
authOwnerShellAgent = utils.createAuthAgent(app)(ownerShell);
|
||||
|
||||
utils.initConfigFile();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['InstalledNodes', 'InstalledPackages', 'User']);
|
||||
await testDb.truncate(['InstalledNodes', 'InstalledPackages']);
|
||||
|
||||
mocked(executeCommand).mockReset();
|
||||
mocked(findInstalledPackage).mockReset();
|
||||
|
@ -73,255 +68,216 @@ afterAll(async () => {
|
|||
await testDb.terminate();
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /nodes
|
||||
*/
|
||||
describe('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 () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
expect(statusCode).toBe(200);
|
||||
expect(data).toHaveLength(0);
|
||||
});
|
||||
|
||||
const {
|
||||
statusCode,
|
||||
body: { data },
|
||||
} = await authAgent(ownerShell).get('/nodes');
|
||||
test('should return list of one installed package and node', async () => {
|
||||
const { packageName } = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||
await testDb.saveInstalledNode(utils.installedNodePayload(packageName));
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(data).toHaveLength(0);
|
||||
});
|
||||
const {
|
||||
statusCode,
|
||||
body: { data },
|
||||
} = await authOwnerShellAgent.get('/nodes');
|
||||
|
||||
test('GET /nodes should return list of one installed package and node', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
expect(statusCode).toBe(200);
|
||||
expect(data).toHaveLength(1);
|
||||
expect(data[0].installedNodes).toHaveLength(1);
|
||||
});
|
||||
|
||||
const { packageName } = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||
await testDb.saveInstalledNode(utils.installedNodePayload(packageName));
|
||||
test('should return list of multiple installed packages and nodes', async () => {
|
||||
const first = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||
await testDb.saveInstalledNode(utils.installedNodePayload(first.packageName));
|
||||
|
||||
const {
|
||||
statusCode,
|
||||
body: { data },
|
||||
} = await authAgent(ownerShell).get('/nodes');
|
||||
const second = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||
await testDb.saveInstalledNode(utils.installedNodePayload(second.packageName));
|
||||
await testDb.saveInstalledNode(utils.installedNodePayload(second.packageName));
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(data).toHaveLength(1);
|
||||
expect(data[0].installedNodes).toHaveLength(1);
|
||||
});
|
||||
const {
|
||||
statusCode,
|
||||
body: { data },
|
||||
} = await authOwnerShellAgent.get('/nodes');
|
||||
|
||||
test('GET /nodes should return list of multiple installed packages and nodes', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
expect(statusCode).toBe(200);
|
||||
expect(data).toHaveLength(2);
|
||||
|
||||
const first = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||
await testDb.saveInstalledNode(utils.installedNodePayload(first.packageName));
|
||||
const allNodes = data.reduce(
|
||||
(acc: InstalledNodes[], cur: InstalledPackages) => acc.concat(cur.installedNodes),
|
||||
[],
|
||||
);
|
||||
|
||||
const second = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||
await testDb.saveInstalledNode(utils.installedNodePayload(second.packageName));
|
||||
await testDb.saveInstalledNode(utils.installedNodePayload(second.packageName));
|
||||
expect(allNodes).toHaveLength(3);
|
||||
});
|
||||
|
||||
const {
|
||||
statusCode,
|
||||
body: { data },
|
||||
} = await authAgent(ownerShell).get('/nodes');
|
||||
test('should not check for updates if no packages installed', async () => {
|
||||
await authOwnerShellAgent.get('/nodes');
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(data).toHaveLength(2);
|
||||
expect(mocked(executeCommand)).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
const allNodes = data.reduce(
|
||||
(acc: InstalledNodes[], cur: InstalledPackages) => acc.concat(cur.installedNodes),
|
||||
[],
|
||||
);
|
||||
test('should check for updates if packages installed', async () => {
|
||||
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 () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
expect(mocked(executeCommand)).toHaveBeenCalledWith('npm outdated --json', {
|
||||
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 () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
mocked(isNpmError).mockReturnValueOnce(true);
|
||||
|
||||
const { packageName } = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||
await testDb.saveInstalledNode(utils.installedNodePayload(packageName));
|
||||
const {
|
||||
body: { data },
|
||||
} = await authOwnerShellAgent.get('/nodes');
|
||||
|
||||
await authAgent(ownerShell).get('/nodes');
|
||||
|
||||
expect(mocked(executeCommand)).toHaveBeenCalledWith('npm outdated --json', {
|
||||
doNotHandleError: true,
|
||||
expect(data[0].installedVersion).toBe(COMMUNITY_PACKAGE_VERSION.CURRENT);
|
||||
expect(data[0].updateAvailable).toBe(COMMUNITY_PACKAGE_VERSION.UPDATED);
|
||||
});
|
||||
});
|
||||
|
||||
test('GET /nodes should report package updates if available', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
describe('POST /nodes', () => {
|
||||
test('should reject if package name is missing', async () => {
|
||||
const { statusCode } = await authOwnerShellAgent.post('/nodes');
|
||||
|
||||
const { packageName } = await testDb.saveInstalledPackage(utils.installedPackagePayload());
|
||||
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),
|
||||
},
|
||||
}),
|
||||
};
|
||||
expect(statusCode).toBe(400);
|
||||
});
|
||||
|
||||
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 {
|
||||
body: { data },
|
||||
} = await authAgent(ownerShell).get('/nodes');
|
||||
const {
|
||||
statusCode,
|
||||
body: { message },
|
||||
} = await authOwnerShellAgent.post('/nodes').send({
|
||||
name: utils.installedPackagePayload().packageName,
|
||||
});
|
||||
|
||||
expect(data[0].installedVersion).toBe(COMMUNITY_PACKAGE_VERSION.CURRENT);
|
||||
expect(data[0].updateAvailable).toBe(COMMUNITY_PACKAGE_VERSION.UPDATED);
|
||||
});
|
||||
|
||||
/**
|
||||
* 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);
|
||||
expect(message).toContain('already installed');
|
||||
});
|
||||
|
||||
expect(statusCode).toBe(400);
|
||||
expect(message).toContain('already installed');
|
||||
});
|
||||
test('should allow installing packages that could not be loaded', async () => {
|
||||
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 () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
mockLoadNodesAndCredentials.loadNpmModule.mockImplementationOnce(mockedEmptyPackage);
|
||||
|
||||
mocked(findInstalledPackage).mockResolvedValueOnce(new InstalledPackages());
|
||||
mocked(hasPackageLoaded).mockReturnValueOnce(false);
|
||||
mocked(checkNpmPackageStatus).mockResolvedValueOnce({ status: 'OK' });
|
||||
const { statusCode } = await authOwnerShellAgent.post('/nodes').send({
|
||||
name: utils.installedPackagePayload().packageName,
|
||||
});
|
||||
|
||||
mockLoadNodesAndCredentials.loadNpmModule.mockImplementationOnce(mockedEmptyPackage);
|
||||
|
||||
const { statusCode } = await authAgent(ownerShell).post('/nodes').send({
|
||||
name: utils.installedPackagePayload().packageName,
|
||||
expect(statusCode).toBe(200);
|
||||
expect(mocked(removePackageFromMissingList)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(mocked(removePackageFromMissingList)).toHaveBeenCalled();
|
||||
test('should not install a banned package', async () => {
|
||||
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 () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
mocked(checkNpmPackageStatus).mockResolvedValueOnce({ status: 'Banned' });
|
||||
describe('DELETE /nodes', () => {
|
||||
test('should not delete if package name is empty', async () => {
|
||||
const response = await authOwnerShellAgent.delete('/nodes');
|
||||
|
||||
const {
|
||||
statusCode,
|
||||
body: { message },
|
||||
} = await authAgent(ownerShell).post('/nodes').send({
|
||||
name: utils.installedPackagePayload().packageName,
|
||||
expect(response.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
expect(statusCode).toBe(400);
|
||||
expect(message).toContain('banned');
|
||||
});
|
||||
test('should reject if package is not installed', async () => {
|
||||
const {
|
||||
statusCode,
|
||||
body: { message },
|
||||
} = await authOwnerShellAgent.delete('/nodes').query({
|
||||
name: utils.installedPackagePayload().packageName,
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /nodes
|
||||
*/
|
||||
|
||||
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);
|
||||
expect(message).toContain('not installed');
|
||||
});
|
||||
|
||||
expect(statusCode).toBe(400);
|
||||
expect(message).toContain('not installed');
|
||||
test('should uninstall package', async () => {
|
||||
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 () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
describe('PATCH /nodes', () => {
|
||||
test('should reject if package name is empty', async () => {
|
||||
const response = await authOwnerShellAgent.patch('/nodes');
|
||||
|
||||
const removeSpy = mockLoadNodesAndCredentials.removeNpmModule.mockImplementationOnce(jest.fn());
|
||||
|
||||
mocked(findInstalledPackage).mockImplementationOnce(mockedEmptyPackage);
|
||||
|
||||
const { statusCode } = await authAgent(ownerShell).delete('/nodes').query({
|
||||
name: utils.installedPackagePayload().packageName,
|
||||
expect(response.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(removeSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
test('reject if package is not installed', async () => {
|
||||
const {
|
||||
statusCode,
|
||||
body: { message },
|
||||
} = await authOwnerShellAgent.patch('/nodes').send({
|
||||
name: utils.installedPackagePayload().packageName,
|
||||
});
|
||||
|
||||
/**
|
||||
* PATCH /nodes
|
||||
*/
|
||||
|
||||
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);
|
||||
expect(message).toContain('not installed');
|
||||
});
|
||||
|
||||
expect(statusCode).toBe(400);
|
||||
expect(message).toContain('not installed');
|
||||
});
|
||||
test('should update a package', async () => {
|
||||
const updateSpy =
|
||||
mockLoadNodesAndCredentials.updateNpmModule.mockImplementationOnce(mockedEmptyPackage);
|
||||
|
||||
test('PATCH /nodes should update a package', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
mocked(findInstalledPackage).mockImplementationOnce(mockedEmptyPackage);
|
||||
|
||||
const updateSpy =
|
||||
mockLoadNodesAndCredentials.updateNpmModule.mockImplementationOnce(mockedEmptyPackage);
|
||||
await authOwnerShellAgent.patch('/nodes').send({
|
||||
name: utils.installedPackagePayload().packageName,
|
||||
});
|
||||
|
||||
mocked(findInstalledPackage).mockImplementationOnce(mockedEmptyPackage);
|
||||
|
||||
await authAgent(ownerShell).patch('/nodes').send({
|
||||
name: utils.installedPackagePayload().packageName,
|
||||
expect(updateSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
expect(updateSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import express from 'express';
|
||||
import type { Application } from 'express';
|
||||
import validator from 'validator';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
|
||||
import config from '@/config';
|
||||
import * as Db from '@/Db';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import {
|
||||
randomEmail,
|
||||
randomInvalidPassword,
|
||||
|
@ -11,23 +13,22 @@ import {
|
|||
randomValidPassword,
|
||||
} from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { AuthAgent } from './shared/types';
|
||||
import * as utils from './shared/utils';
|
||||
|
||||
let app: express.Application;
|
||||
let app: Application;
|
||||
let globalOwnerRole: Role;
|
||||
let authAgent: AuthAgent;
|
||||
let ownerShell: User;
|
||||
let authOwnerShellAgent: SuperAgentTest;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({ endpointGroups: ['owner'] });
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
|
||||
authAgent = utils.createAuthAgent(app);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
||||
ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
authOwnerShellAgent = utils.createAuthAgent(app)(ownerShell);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
@ -38,152 +39,149 @@ afterAll(async () => {
|
|||
await testDb.terminate();
|
||||
});
|
||||
|
||||
test('POST /owner/setup should create owner and enable isInstanceOwnerSetUp', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
describe('POST /owner/setup', () => {
|
||||
test('should create owner and enable isInstanceOwnerSetUp', async () => {
|
||||
const newOwnerData = {
|
||||
email: randomEmail(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
};
|
||||
|
||||
const newOwnerData = {
|
||||
email: randomEmail(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
};
|
||||
const response = await authOwnerShellAgent.post('/owner/setup').send(newOwnerData);
|
||||
|
||||
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 {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
password,
|
||||
resetPasswordToken,
|
||||
isPending,
|
||||
apiKey,
|
||||
} = response.body.data;
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(newOwnerData.email);
|
||||
expect(firstName).toBe(newOwnerData.firstName);
|
||||
expect(lastName).toBe(newOwnerData.lastName);
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(apiKey).toBeUndefined();
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(newOwnerData.email);
|
||||
expect(firstName).toBe(newOwnerData.firstName);
|
||||
expect(lastName).toBe(newOwnerData.lastName);
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
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 });
|
||||
expect(storedOwner.password).not.toBe(newOwnerData.password);
|
||||
expect(storedOwner.email).toBe(newOwnerData.email);
|
||||
expect(storedOwner.firstName).toBe(newOwnerData.firstName);
|
||||
expect(storedOwner.lastName).toBe(newOwnerData.lastName);
|
||||
|
||||
const storedOwner = await Db.collections.User.findOneByOrFail({ id });
|
||||
expect(storedOwner.password).not.toBe(newOwnerData.password);
|
||||
expect(storedOwner.email).toBe(newOwnerData.email);
|
||||
expect(storedOwner.firstName).toBe(newOwnerData.firstName);
|
||||
expect(storedOwner.lastName).toBe(newOwnerData.lastName);
|
||||
const isInstanceOwnerSetUpConfig = config.getEnv('userManagement.isInstanceOwnerSetUp');
|
||||
expect(isInstanceOwnerSetUpConfig).toBe(true);
|
||||
|
||||
const isInstanceOwnerSetUpConfig = config.getEnv('userManagement.isInstanceOwnerSetUp');
|
||||
expect(isInstanceOwnerSetUpConfig).toBe(true);
|
||||
|
||||
const isInstanceOwnerSetUpSetting = await utils.isInstanceOwnerSetUp();
|
||||
expect(isInstanceOwnerSetUpSetting).toBe(true);
|
||||
});
|
||||
|
||||
test('POST /owner/setup should create owner with lowercased email', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
|
||||
const newOwnerData = {
|
||||
email: randomEmail().toUpperCase(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
};
|
||||
|
||||
const response = await authAgent(ownerShell).post('/owner/setup').send(newOwnerData);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const { id, email } = response.body.data;
|
||||
|
||||
expect(id).toBe(ownerShell.id);
|
||||
expect(email).toBe(newOwnerData.email.toLowerCase());
|
||||
|
||||
const storedOwner = await Db.collections.User.findOneByOrFail({ id });
|
||||
expect(storedOwner.email).toBe(newOwnerData.email.toLowerCase());
|
||||
});
|
||||
|
||||
test('POST /owner/setup should fail with invalid inputs', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = authAgent(ownerShell);
|
||||
|
||||
await Promise.all(
|
||||
INVALID_POST_OWNER_PAYLOADS.map(async (invalidPayload) => {
|
||||
const response = await authOwnerAgent.post('/owner/setup').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('POST /owner/skip-setup should persist skipping setup to the DB', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
|
||||
const response = await authAgent(ownerShell).post('/owner/skip-setup').send();
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const skipConfig = config.getEnv('userManagement.skipInstanceOwnerSetup');
|
||||
expect(skipConfig).toBe(true);
|
||||
|
||||
const { value } = await Db.collections.Settings.findOneByOrFail({
|
||||
key: 'userManagement.skipInstanceOwnerSetup',
|
||||
const isInstanceOwnerSetUpSetting = await utils.isInstanceOwnerSetUp();
|
||||
expect(isInstanceOwnerSetUpSetting).toBe(true);
|
||||
});
|
||||
|
||||
test('should create owner with lowercased email', async () => {
|
||||
const newOwnerData = {
|
||||
email: randomEmail().toUpperCase(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
};
|
||||
|
||||
const response = await authOwnerShellAgent.post('/owner/setup').send(newOwnerData);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const { id, email } = response.body.data;
|
||||
|
||||
expect(id).toBe(ownerShell.id);
|
||||
expect(email).toBe(newOwnerData.email.toLowerCase());
|
||||
|
||||
const storedOwner = await Db.collections.User.findOneByOrFail({ id });
|
||||
expect(storedOwner.email).toBe(newOwnerData.email.toLowerCase());
|
||||
});
|
||||
|
||||
const INVALID_POST_OWNER_PAYLOADS = [
|
||||
{
|
||||
email: '',
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
},
|
||||
{
|
||||
email: randomEmail(),
|
||||
firstName: '',
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
},
|
||||
{
|
||||
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(),
|
||||
},
|
||||
];
|
||||
|
||||
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 = [
|
||||
{
|
||||
email: '',
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
},
|
||||
{
|
||||
email: randomEmail(),
|
||||
firstName: '',
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
},
|
||||
{
|
||||
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(),
|
||||
},
|
||||
];
|
||||
describe('POST /owner/skip-setup', () => {
|
||||
test('should persist skipping setup to the DB', async () => {
|
||||
const response = await authOwnerShellAgent.post('/owner/skip-setup').send();
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const skipConfig = config.getEnv('userManagement.skipInstanceOwnerSetup');
|
||||
expect(skipConfig).toBe(true);
|
||||
|
||||
const { value } = await Db.collections.Settings.findOneByOrFail({
|
||||
key: 'userManagement.skipInstanceOwnerSetup',
|
||||
});
|
||||
expect(value).toBe('true');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import express from 'express';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { compare } from 'bcryptjs';
|
||||
|
||||
import * as utils from './shared/utils';
|
||||
import * as Db from '@/Db';
|
||||
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 {
|
||||
randomEmail,
|
||||
randomInvalidPassword,
|
||||
|
@ -12,276 +14,237 @@ import {
|
|||
randomValidPassword,
|
||||
} from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
|
||||
jest.mock('@/UserManagement/email/NodeMailer');
|
||||
|
||||
let app: express.Application;
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let owner: User;
|
||||
let authlessAgent: SuperAgentTest;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({ endpointGroups: ['passwordReset'] });
|
||||
const app = await utils.initTestServer({ endpointGroups: ['passwordReset'] });
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
|
||||
authlessAgent = utils.createAgent(app);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['User']);
|
||||
|
||||
jest.mock('@/config');
|
||||
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
config.set('userManagement.emails.mode', '');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.terminate();
|
||||
});
|
||||
|
||||
test('POST /forgot-password should send password reset email', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
describe('POST /forgot-password', () => {
|
||||
test('should send password reset email', async () => {
|
||||
const member = await testDb.createUser({
|
||||
email: 'test@test.com',
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
const member = await testDb.createUser({
|
||||
email: 'test@test.com',
|
||||
globalRole: globalMemberRole,
|
||||
config.set('userManagement.emails.mode', 'smtp');
|
||||
|
||||
await Promise.all(
|
||||
[{ 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(
|
||||
[{ email: owner.email }, { email: member.email.toUpperCase() }].map(async (payload) => {
|
||||
const response = await authlessAgent.post('/forgot-password').send(payload);
|
||||
await authlessAgent.post('/forgot-password').send({ email: owner.email }).expect(500);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({});
|
||||
const storedOwner = await Db.collections.User.findOneByOrFail({ email: owner.email });
|
||||
expect(storedOwner.resetPasswordToken).toBeNull();
|
||||
});
|
||||
|
||||
const user = await Db.collections.User.findOneByOrFail({ email: payload.email });
|
||||
expect(user.resetPasswordToken).toBeDefined();
|
||||
expect(user.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000));
|
||||
}),
|
||||
);
|
||||
test('should fail with invalid inputs', async () => {
|
||||
config.set('userManagement.emails.mode', 'smtp');
|
||||
|
||||
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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
describe('GET /resolve-password-token', () => {
|
||||
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(storedOwner.resetPasswordToken).toBeNull();
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
test('POST /forgot-password should fail with invalid inputs', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
test('should fail with invalid inputs', async () => {
|
||||
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');
|
||||
|
||||
const invalidPayloads = [
|
||||
randomEmail(),
|
||||
[randomEmail()],
|
||||
{},
|
||||
[{ name: randomName() }],
|
||||
[{ email: randomName() }],
|
||||
];
|
||||
|
||||
await Promise.all(
|
||||
invalidPayloads.map(async (invalidPayload) => {
|
||||
const response = await authlessAgent.post('/forgot-password').send(invalidPayload);
|
||||
for (const response of [first, second]) {
|
||||
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
|
||||
.get('/resolve-password-token')
|
||||
.query({ userId: owner.id, token: resetPasswordToken });
|
||||
test('should fail if user is not found', async () => {
|
||||
const response = await authlessAgent
|
||||
.get('/resolve-password-token')
|
||||
.query({ userId: owner.id, token: uuid() });
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
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,
|
||||
expect(response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
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
|
||||
.get('/resolve-password-token')
|
||||
.query({ userId: owner.id, token: resetPasswordToken });
|
||||
await Db.collections.User.update(owner.id, {
|
||||
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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
describe('POST /change-password', () => {
|
||||
const resetPasswordToken = uuid();
|
||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
||||
|
||||
await Db.collections.User.update(owner.id, {
|
||||
resetPasswordToken,
|
||||
resetPasswordTokenExpiration,
|
||||
});
|
||||
|
||||
const passwordToStore = randomValidPassword();
|
||||
|
||||
const response = await authlessAgent.post('/change-password').send({
|
||||
token: resetPasswordToken,
|
||||
userId: owner.id,
|
||||
password: passwordToStore,
|
||||
});
|
||||
test('should succeed with valid inputs', async () => {
|
||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
await Db.collections.User.update(owner.id, {
|
||||
resetPasswordToken,
|
||||
resetPasswordTokenExpiration,
|
||||
});
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
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(),
|
||||
const response = await authlessAgent.post('/change-password').send({
|
||||
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,
|
||||
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);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
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 comparisonResult = await compare(passwordToStore, storedPassword);
|
||||
expect(comparisonResult).toBe(true);
|
||||
expect(storedPassword).not.toBe(passwordToStore);
|
||||
});
|
||||
|
||||
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({
|
||||
token: resetPasswordToken,
|
||||
userId: owner.id,
|
||||
password: passwordToStore,
|
||||
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,
|
||||
},
|
||||
{
|
||||
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 * as Db from '@/Db';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
import { randomApiKey, randomName, randomString } from '../shared/random';
|
||||
import * as utils from '../shared/utils';
|
||||
import type { CredentialPayload, SaveCredentialFunction } from '../shared/types';
|
||||
import * as testDb from '../shared/testDb';
|
||||
|
||||
let app: express.Application;
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let credentialOwnerRole: Role;
|
||||
let owner: User;
|
||||
let member: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
let authMemberAgent: SuperAgentTest;
|
||||
|
||||
let saveCredential: SaveCredentialFunction;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({
|
||||
const app = await utils.initTestServer({
|
||||
endpointGroups: ['publicApi'],
|
||||
applyAuth: false,
|
||||
enablePublicAPI: true,
|
||||
|
@ -26,334 +27,265 @@ beforeAll(async () => {
|
|||
|
||||
utils.initConfigFile();
|
||||
|
||||
const [fetchedGlobalOwnerRole, fetchedGlobalMemberRole, _, fetchedCredentialOwnerRole] =
|
||||
const [globalOwnerRole, fetchedGlobalMemberRole, _, fetchedCredentialOwnerRole] =
|
||||
await testDb.getAllRoles();
|
||||
|
||||
globalOwnerRole = fetchedGlobalOwnerRole;
|
||||
globalMemberRole = fetchedGlobalMemberRole;
|
||||
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);
|
||||
|
||||
utils.initCredentialsTypes();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['User', 'SharedCredentials', 'Credentials']);
|
||||
await testDb.truncate(['SharedCredentials', 'Credentials']);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.terminate();
|
||||
});
|
||||
|
||||
test('POST /credentials should create credentials', async () => {
|
||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
ownerShell = await testDb.addApiKey(ownerShell);
|
||||
describe('POST /credentials', () => {
|
||||
test('should create credentials', async () => {
|
||||
const payload = {
|
||||
name: 'test credential',
|
||||
type: 'githubApi',
|
||||
data: {
|
||||
accessToken: 'abcdefghijklmnopqrstuvwxyz',
|
||||
user: 'test',
|
||||
server: 'testServer',
|
||||
},
|
||||
};
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
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);
|
||||
|
||||
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);
|
||||
expect(type).toBe(payload.type);
|
||||
const credential = await Db.collections.Credentials.findOneByOrFail({ id });
|
||||
|
||||
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);
|
||||
expect(credential.type).toBe(payload.type);
|
||||
expect(credential.data).not.toBe(payload.data);
|
||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||
relations: ['user', 'credentials', 'role'],
|
||||
where: { credentialsId: credential.id, userId: owner.id },
|
||||
});
|
||||
|
||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||
relations: ['user', 'credentials', 'role'],
|
||||
where: { credentialsId: credential.id, userId: ownerShell.id },
|
||||
expect(sharedCredential.role).toEqual(credentialOwnerRole);
|
||||
expect(sharedCredential.credentials.name).toBe(payload.name);
|
||||
});
|
||||
|
||||
expect(sharedCredential.role).toEqual(credentialOwnerRole);
|
||||
expect(sharedCredential.credentials.name).toBe(payload.name);
|
||||
test('should fail with invalid inputs', async () => {
|
||||
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 () => {
|
||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
ownerShell = await testDb.addApiKey(ownerShell);
|
||||
describe('DELETE /credentials/:id', () => {
|
||||
test('should delete owned cred for owner', async () => {
|
||||
const savedCredential = await saveCredential(dbCredential(), { user: owner });
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
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
|
||||
});
|
||||
|
||||
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 delete non-owned cred for owner', async () => {
|
||||
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('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 () => {
|
||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||
describe('GET /credentials/schema/:credentialType', () => {
|
||||
test('should fail due to not found type', async () => {
|
||||
const response = await authOwnerAgent.get('/credentials/schema/testing');
|
||||
|
||||
let ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
ownerShell = await testDb.addApiKey(ownerShell);
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: ownerShell,
|
||||
expect(response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
test('DELETE /credentials/:id should delete 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,
|
||||
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 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 => ({
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import express from 'express';
|
||||
|
||||
import type { Application } from 'express';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import config from '@/config';
|
||||
import { Role } from '@db/entities/Role';
|
||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||
import type { User } from '@db/entities/User';
|
||||
import type { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||
|
||||
import { randomApiKey } from '../shared/random';
|
||||
import * as utils from '../shared/utils';
|
||||
import * as testDb from '../shared/testDb';
|
||||
|
||||
let app: express.Application;
|
||||
let globalOwnerRole: Role;
|
||||
let app: Application;
|
||||
let owner: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
let workflowRunner: ActiveWorkflowRunner;
|
||||
|
||||
beforeAll(async () => {
|
||||
|
@ -19,7 +20,8 @@ beforeAll(async () => {
|
|||
enablePublicAPI: true,
|
||||
});
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
const globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
||||
|
||||
await utils.initBinaryManager();
|
||||
await utils.initNodeTypes();
|
||||
|
@ -31,13 +33,19 @@ beforeEach(async () => {
|
|||
await testDb.truncate([
|
||||
'SharedCredentials',
|
||||
'SharedWorkflow',
|
||||
'User',
|
||||
'Workflow',
|
||||
'Credentials',
|
||||
'Execution',
|
||||
'Settings',
|
||||
]);
|
||||
|
||||
authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
auth: true,
|
||||
user: owner,
|
||||
version: 1,
|
||||
});
|
||||
|
||||
config.set('userManagement.disabled', false);
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
});
|
||||
|
@ -50,270 +58,27 @@ afterAll(async () => {
|
|||
await testDb.terminate();
|
||||
});
|
||||
|
||||
test('GET /executions/:id should fail due to missing API Key', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const testWithAPIKey =
|
||||
(method: 'get' | 'post' | 'put' | 'delete', url: string, apiKey: string | null) => async () => {
|
||||
authOwnerAgent.set({ 'X-N8N-API-KEY': apiKey });
|
||||
const response = await authOwnerAgent[method](url);
|
||||
expect(response.statusCode).toBe(401);
|
||||
};
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
auth: true,
|
||||
user: owner,
|
||||
version: 1,
|
||||
});
|
||||
describe('GET /executions/:id', () => {
|
||||
test('should fail due to missing API Key', testWithAPIKey('get', '/executions/1', null));
|
||||
|
||||
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 owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
||||
owner.apiKey = 'abcXYZ';
|
||||
const execution = await testDb.createSuccessfulExecution(workflow);
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
auth: true,
|
||||
user: owner,
|
||||
version: 1,
|
||||
});
|
||||
const response = await authOwnerAgent.get(`/executions/${execution.id}`);
|
||||
|
||||
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 {
|
||||
id,
|
||||
finished,
|
||||
|
@ -324,146 +89,33 @@ test.skip('GET /executions should paginate two executions', async () => {
|
|||
stoppedAt,
|
||||
workflowId,
|
||||
waitTill,
|
||||
} = executions[i];
|
||||
} = response.body;
|
||||
|
||||
expect(id).toBeDefined();
|
||||
expect(finished).toBe(true);
|
||||
expect(mode).toEqual(successfulExecutions[i].mode);
|
||||
expect(mode).toEqual(execution.mode);
|
||||
expect(retrySuccessId).toBeNull();
|
||||
expect(retryOf).toBeNull();
|
||||
expect(startedAt).not.toBeNull();
|
||||
expect(stoppedAt).not.toBeNull();
|
||||
expect(workflowId).toBe(successfulExecutions[i].workflowId);
|
||||
expect(workflowId).toBe(execution.workflowId);
|
||||
expect(waitTill).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('GET /executions should retrieve all error executions', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
||||
describe('DELETE /executions/:id', () => {
|
||||
test('should fail due to missing API Key', testWithAPIKey('delete', '/executions/1', null));
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
auth: true,
|
||||
user: owner,
|
||||
version: 1,
|
||||
});
|
||||
test('should fail due to invalid API Key', testWithAPIKey('delete', '/executions/1', 'abcXYZ'));
|
||||
|
||||
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 {
|
||||
id,
|
||||
finished,
|
||||
|
@ -474,16 +126,240 @@ test('GET /executions should retrieve all executions of specific workflow', asyn
|
|||
stoppedAt,
|
||||
workflowId,
|
||||
waitTill,
|
||||
} = execution;
|
||||
} = response.body;
|
||||
|
||||
expect(savedExecutions.some((exec) => exec.id === id)).toBe(true);
|
||||
expect(id).toBeDefined();
|
||||
expect(finished).toBe(true);
|
||||
expect(mode).toBeDefined();
|
||||
expect(mode).toEqual(execution.mode);
|
||||
expect(retrySuccessId).toBeNull();
|
||||
expect(retryOf).toBeNull();
|
||||
expect(startedAt).not.toBeNull();
|
||||
expect(stoppedAt).not.toBeNull();
|
||||
expect(workflowId).toBe(workflow.id);
|
||||
expect(workflowId).toBe(execution.workflowId);
|
||||
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 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 * as testDb from '../shared/testDb';
|
||||
import type { AuthAgent } from '../shared/types';
|
||||
import * as utils from '../shared/utils';
|
||||
import { setSamlLoginEnabled } from '../../../src/sso/saml/samlHelpers';
|
||||
import { setCurrentAuthenticationMethod } from '../../../src/sso/ssoHelpers';
|
||||
|
||||
let app: express.Application;
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let authAgent: AuthAgent;
|
||||
let owner: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
|
||||
function enableSaml(enable: boolean) {
|
||||
setSamlLoginEnabled(enable);
|
||||
|
@ -21,58 +17,59 @@ function enableSaml(enable: boolean) {
|
|||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({ endpointGroups: ['me'] });
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
|
||||
authAgent = utils.createAuthAgent(app);
|
||||
const app = await utils.initTestServer({ endpointGroups: ['me'] });
|
||||
owner = await testDb.createOwner();
|
||||
authOwnerAgent = utils.createAuthAgent(app)(owner);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['User']);
|
||||
});
|
||||
// beforeEach(async () => {
|
||||
// await testDb.truncate(['User']);
|
||||
// });
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.terminate();
|
||||
});
|
||||
|
||||
describe('Instance owner', () => {
|
||||
test('PATCH /me should succeed with valid inputs', async () => {
|
||||
const owner = await testDb.createOwner();
|
||||
const authOwnerAgent = authAgent(owner);
|
||||
const response = await authOwnerAgent.patch('/me').send({
|
||||
email: randomEmail(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
describe('PATCH /me', () => {
|
||||
test('should succeed with valid inputs', async () => {
|
||||
enableSaml(false);
|
||||
await authOwnerAgent
|
||||
.patch('/me')
|
||||
.send({
|
||||
email: randomEmail(),
|
||||
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 () => {
|
||||
enableSaml(true);
|
||||
const owner = await testDb.createOwner();
|
||||
const authOwnerAgent = authAgent(owner);
|
||||
const response = await authOwnerAgent.patch('/me').send({
|
||||
email: randomEmail(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
describe('PATCH /password', () => {
|
||||
test('should throw BadRequestError if password is changed when SAML is enabled', async () => {
|
||||
enableSaml(true);
|
||||
await authOwnerAgent
|
||||
.patch('/me/password')
|
||||
.send({
|
||||
password: randomValidPassword(),
|
||||
})
|
||||
.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 type { ObjectLiteral } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Make `SuperTest<T>` string-indexable.
|
||||
|
|
|
@ -9,13 +9,11 @@ import set from 'lodash.set';
|
|||
import { BinaryDataManager, UserSettings } from 'n8n-core';
|
||||
import {
|
||||
ICredentialType,
|
||||
ICredentialTypes,
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodesAndCredentials,
|
||||
ITriggerFunctions,
|
||||
ITriggerResponse,
|
||||
LoggerProxy,
|
||||
|
@ -90,13 +88,6 @@ export const mockInstance = <T>(
|
|||
return instance;
|
||||
};
|
||||
|
||||
const loadNodesAndCredentials: INodesAndCredentials = {
|
||||
loaded: { nodes: {}, credentials: {} },
|
||||
known: { nodes: {}, credentials: {} },
|
||||
credentialTypes: {} as ICredentialTypes,
|
||||
};
|
||||
Container.set(LoadNodesAndCredentials, loadNodesAndCredentials);
|
||||
|
||||
/**
|
||||
* Initialize a test server.
|
||||
*/
|
||||
|
@ -740,6 +731,15 @@ export async function isInstanceOwnerSetUp() {
|
|||
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
|
||||
// ----------------------------------
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import express from 'express';
|
||||
import validator from 'validator';
|
||||
import { Not } from 'typeorm';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
|
||||
import config from '@/config';
|
||||
import * as Db from '@/Db';
|
||||
|
@ -8,6 +9,9 @@ import type { Role } from '@db/entities/Role';
|
|||
import type { User } from '@db/entities/User';
|
||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
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 {
|
||||
randomCredentialPayload,
|
||||
|
@ -17,41 +21,40 @@ import {
|
|||
randomValidPassword,
|
||||
} from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { AuthAgent } from './shared/types';
|
||||
import * as utils from './shared/utils';
|
||||
|
||||
import { UserManagementMailer } from '@/UserManagement/email/UserManagementMailer';
|
||||
import { NodeMailer } from '@/UserManagement/email/NodeMailer';
|
||||
|
||||
jest.mock('@/UserManagement/email/NodeMailer');
|
||||
|
||||
let app: express.Application;
|
||||
let globalMemberRole: Role;
|
||||
let globalOwnerRole: Role;
|
||||
let workflowOwnerRole: Role;
|
||||
let credentialOwnerRole: Role;
|
||||
let authAgent: AuthAgent;
|
||||
let owner: User;
|
||||
let authlessAgent: SuperAgentTest;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({ endpointGroups: ['users'] });
|
||||
const app = await utils.initTestServer({ endpointGroups: ['users'] });
|
||||
|
||||
const [
|
||||
fetchedGlobalOwnerRole,
|
||||
globalOwnerRole,
|
||||
fetchedGlobalMemberRole,
|
||||
fetchedWorkflowOwnerRole,
|
||||
fetchedCredentialOwnerRole,
|
||||
] = await testDb.getAllRoles();
|
||||
|
||||
globalOwnerRole = fetchedGlobalOwnerRole;
|
||||
globalMemberRole = fetchedGlobalMemberRole;
|
||||
workflowOwnerRole = fetchedWorkflowOwnerRole;
|
||||
credentialOwnerRole = fetchedCredentialOwnerRole;
|
||||
|
||||
authAgent = utils.createAuthAgent(app);
|
||||
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
authlessAgent = utils.createAgent(app);
|
||||
authOwnerAgent = utils.createAuthAgent(app)(owner);
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
|
@ -65,18 +68,16 @@ afterAll(async () => {
|
|||
await testDb.terminate();
|
||||
});
|
||||
|
||||
test('GET /users should return all users', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
describe('GET /users', () => {
|
||||
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);
|
||||
expect(response.body.data.length).toBe(2);
|
||||
|
||||
await Promise.all(
|
||||
response.body.data.map(async (user: User) => {
|
||||
response.body.data.map((user: User) => {
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
|
@ -100,442 +101,421 @@ test('GET /users should return all users', async () => {
|
|||
expect(isPending).toBe(false);
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(apiKey).not.toBeDefined();
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('DELETE /users/:id should delete the user', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
describe('DELETE /users/:id', () => {
|
||||
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, {
|
||||
name: randomName(),
|
||||
active: false,
|
||||
connections: {},
|
||||
nodes: [],
|
||||
const savedWorkflow = await Db.collections.Workflow.save(newWorkflow);
|
||||
|
||||
await Db.collections.SharedWorkflow.save({
|
||||
role: workflowOwnerRole,
|
||||
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({
|
||||
role: workflowOwnerRole,
|
||||
user: userToDelete,
|
||||
workflow: savedWorkflow,
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const user = await Db.collections.User.findOneBy({ id: owner.id });
|
||||
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, {
|
||||
name: randomName(),
|
||||
data: '',
|
||||
type: '',
|
||||
nodesAccess: [],
|
||||
const response = await authOwnerAgent.delete(`/users/${idToDelete}`).query({
|
||||
transferId: idToDelete,
|
||||
});
|
||||
|
||||
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({
|
||||
role: credentialOwnerRole,
|
||||
user: userToDelete,
|
||||
credentials: savedCredential,
|
||||
const savedWorkflow = await testDb.createWorkflow(undefined, userToDelete);
|
||||
|
||||
const savedCredential = await testDb.saveCredential(randomCredentialPayload(), {
|
||||
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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
describe('POST /users/:id', () => {
|
||||
test('should fill out a user shell', async () => {
|
||||
const memberShell = await testDb.createUserShell(globalMemberRole);
|
||||
|
||||
const response = await authAgent(owner).delete(`/users/${owner.id}`);
|
||||
|
||||
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(),
|
||||
},
|
||||
{
|
||||
const memberData = {
|
||||
inviterId: owner.id,
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
},
|
||||
{
|
||||
inviterId: owner.id,
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomInvalidPassword(),
|
||||
},
|
||||
];
|
||||
password: randomValidPassword(),
|
||||
};
|
||||
|
||||
await Promise.all(
|
||||
invalidPayloads.map(async (invalidPayload) => {
|
||||
const response = await authlessAgent.post(`/users/${memberShell.id}`).send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
const response = await authlessAgent.post(`/users/${memberShell.id}`).send(memberData);
|
||||
|
||||
const storedUser = await Db.collections.User.findOneOrFail({
|
||||
where: { email: memberShellEmail },
|
||||
});
|
||||
const {
|
||||
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(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(email).toBeDefined();
|
||||
expect(firstName).toBe(memberData.firstName);
|
||||
expect(lastName).toBe(memberData.lastName);
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeNull();
|
||||
expect(resetPasswordToken).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('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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = authAgent(owner);
|
||||
describe('POST /users', () => {
|
||||
beforeEach(() => {
|
||||
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 = [
|
||||
randomEmail(),
|
||||
[randomEmail()],
|
||||
{},
|
||||
[{ name: randomName() }],
|
||||
[{ email: randomName() }],
|
||||
];
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body.data[0].user.inviteAcceptUrl).toBeDefined();
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
invalidPayloads.map(async (invalidPayload) => {
|
||||
const response = await authOwnerAgent.post('/users').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
test('should fail if user management is disabled', async () => {
|
||||
config.set('userManagement.disabled', true);
|
||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
||||
|
||||
const users = await Db.collections.User.find();
|
||||
expect(users.length).toBe(1); // DB unaffected
|
||||
}),
|
||||
);
|
||||
const response = await authOwnerAgent.post('/users').send([{ email: randomEmail() }]);
|
||||
|
||||
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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
describe('POST /users/:id/reinvite', () => {
|
||||
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(Array.isArray(data)).toBe(true);
|
||||
expect(data.length).toBe(0);
|
||||
expect(reinviteResponse.statusCode).toBe(200);
|
||||
|
||||
const users = await Db.collections.User.find();
|
||||
expect(users.length).toBe(1);
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
describe('UserManagementMailer expect NodeMailer.verifyConnection', () => {
|
||||
let mockInit: jest.SpyInstance<Promise<void>, []>;
|
||||
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()
|
||||
config.set('userManagement.emails.smtp.host', 'host');
|
||||
config.set('userManagement.emails.smtp.auth.user', 'user');
|
||||
config.set('userManagement.emails.smtp.auth.pass', 'pass');
|
||||
afterAll(() => {
|
||||
mockVerifyConnection.mockRestore();
|
||||
mockInit.mockRestore();
|
||||
});
|
||||
|
||||
const email = randomEmail();
|
||||
const payload = [{ email }];
|
||||
const response = await authOwnerAgent.post('/users').send(payload);
|
||||
test('not be called when SMTP not set up', async () => {
|
||||
const userManagementMailer = new UserManagementMailer();
|
||||
// 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;
|
||||
const invitedUserId = data[0].user.id;
|
||||
const reinviteResponse = await authOwnerAgent.post(`/users/${invitedUserId}/reinvite`);
|
||||
test('to be called when SMTP set up', async () => {
|
||||
// host needs to be set, otherwise smtp is skipped
|
||||
config.set('userManagement.emails.smtp.host', 'host');
|
||||
config.set('userManagement.emails.mode', 'smtp');
|
||||
|
||||
expect(reinviteResponse.statusCode).toBe(200);
|
||||
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
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();
|
||||
const userManagementMailer = new UserManagementMailer();
|
||||
// NodeMailer.verifyConnection gets called only explicitly
|
||||
expect(async () => await userManagementMailer.verifyConnection()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,89 +1,89 @@
|
|||
import express from 'express';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
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 testDb from './shared/testDb';
|
||||
import { createWorkflow } from './shared/testDb';
|
||||
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import config from '@/config';
|
||||
import type { AuthAgent, SaveCredentialFunction } from './shared/types';
|
||||
import type { SaveCredentialFunction } from './shared/types';
|
||||
import { makeWorkflow } from './shared/utils';
|
||||
import { randomCredentialPayload } from './shared/random';
|
||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||
|
||||
let app: express.Application;
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let credentialOwnerRole: Role;
|
||||
let authAgent: AuthAgent;
|
||||
let owner: User;
|
||||
let member: User;
|
||||
let anotherMember: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
let authMemberAgent: SuperAgentTest;
|
||||
let authAnotherMemberAgent: SuperAgentTest;
|
||||
let saveCredential: SaveCredentialFunction;
|
||||
let isSharingEnabled: jest.SpyInstance<boolean>;
|
||||
let workflowRunner: ActiveWorkflowRunner;
|
||||
let sharingSpy: jest.SpyInstance<boolean>;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({ endpointGroups: ['workflows'] });
|
||||
const app = await utils.initTestServer({ endpointGroups: ['workflows'] });
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
credentialOwnerRole = await testDb.getCredentialOwnerRole();
|
||||
const globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
const globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
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);
|
||||
|
||||
authAgent = utils.createAuthAgent(app);
|
||||
|
||||
isSharingEnabled = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
|
||||
sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
|
||||
|
||||
await utils.initNodeTypes();
|
||||
workflowRunner = await utils.initActiveWorkflowRunner();
|
||||
|
||||
config.set('enterprise.features.sharing', true);
|
||||
sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true); // @TODO: Remove on release
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['User', 'Workflow', 'SharedWorkflow']);
|
||||
await testDb.truncate(['Workflow', 'SharedWorkflow']);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.terminate();
|
||||
});
|
||||
|
||||
test('Router should switch dynamically', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
describe('router should switch based on flag', () => {
|
||||
let savedWorkflowId: string;
|
||||
|
||||
const createWorkflowResponse = await authAgent(owner).post('/workflows').send(makeWorkflow());
|
||||
const { id } = createWorkflowResponse.body.data;
|
||||
beforeEach(async () => {
|
||||
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)
|
||||
.put(`/workflows/${id}/share`)
|
||||
.send({ shareWithIds: [member.id] });
|
||||
|
||||
expect(freeShareResponse.status).toBe(404);
|
||||
|
||||
// EE router
|
||||
|
||||
const paidShareResponse = await authAgent(owner)
|
||||
.put(`/workflows/${id}/share`)
|
||||
.send({ shareWithIds: [member.id] });
|
||||
|
||||
expect(paidShareResponse.status).toBe(200);
|
||||
test('when sharing is enabled', async () => {
|
||||
await authOwnerAgent
|
||||
.put(`/workflows/${savedWorkflowId}/share`)
|
||||
.send({ shareWithIds: [member.id] })
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /workflows/:id', () => {
|
||||
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 response = await authAgent(owner)
|
||||
const response = await authOwnerAgent
|
||||
.put(`/workflows/${workflow.id}/share`)
|
||||
.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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const workflow = await createWorkflow({}, owner);
|
||||
|
||||
const response = await authAgent(owner)
|
||||
const response = await authOwnerAgent
|
||||
.put(`/workflows/${workflow.id}/share`)
|
||||
.send({ shareWithIds: [uuid()] });
|
||||
|
||||
|
@ -108,12 +107,9 @@ describe('PUT /workflows/:id', () => {
|
|||
});
|
||||
|
||||
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 response = await authAgent(owner)
|
||||
const response = await authOwnerAgent
|
||||
.put(`/workflows/${workflow.id}/share`)
|
||||
.send({ shareWithIds: [member.id, anotherMember.id] });
|
||||
|
||||
|
@ -124,13 +120,8 @@ describe('PUT /workflows/:id', () => {
|
|||
});
|
||||
|
||||
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 authOwnerAgent = authAgent(owner);
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.put(`/workflows/${workflow.id}/share`)
|
||||
.send({ shareWithIds: [member.id, anotherMember.id] });
|
||||
|
@ -152,8 +143,6 @@ describe('PUT /workflows/:id', () => {
|
|||
|
||||
describe('GET /workflows', () => {
|
||||
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 savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
@ -183,7 +172,7 @@ describe('GET /workflows', () => {
|
|||
|
||||
await testDb.shareWorkflowWithUsers(workflow, [member]);
|
||||
|
||||
const response = await authAgent(owner).get('/workflows');
|
||||
const response = await authOwnerAgent.get('/workflows');
|
||||
|
||||
const [fetchedWorkflow] = response.body.data;
|
||||
|
||||
|
@ -192,42 +181,37 @@ describe('GET /workflows', () => {
|
|||
id: owner.id,
|
||||
});
|
||||
|
||||
expect(fetchedWorkflow.sharedWith).not.toBeDefined()
|
||||
expect(fetchedWorkflow.usedCredentials).not.toBeDefined()
|
||||
expect(fetchedWorkflow.nodes).not.toBeDefined()
|
||||
expect(fetchedWorkflow.sharedWith).not.toBeDefined();
|
||||
expect(fetchedWorkflow.usedCredentials).not.toBeDefined();
|
||||
expect(fetchedWorkflow.nodes).not.toBeDefined();
|
||||
expect(fetchedWorkflow.tags).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
name: expect.any(String)
|
||||
})
|
||||
])
|
||||
)
|
||||
name: expect.any(String),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /workflows/:id', () => {
|
||||
test('GET should fail with invalid id due to route rule', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const response = await authAgent(owner).get('/workflows/potatoes');
|
||||
const response = await authOwnerAgent.get('/workflows/potatoes');
|
||||
|
||||
expect(response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('GET should return 404 for non existing workflow', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const response = await authAgent(owner).get('/workflows/9001');
|
||||
const response = await authOwnerAgent.get('/workflows/9001');
|
||||
|
||||
expect(response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('GET should return a workflow with owner', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
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.body.data.ownedBy).toMatchObject({
|
||||
|
@ -241,12 +225,10 @@ describe('GET /workflows/:id', () => {
|
|||
});
|
||||
|
||||
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);
|
||||
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.body.data.ownedBy).toMatchObject({
|
||||
|
@ -266,13 +248,10 @@ describe('GET /workflows/:id', () => {
|
|||
});
|
||||
|
||||
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);
|
||||
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.body.data.ownedBy).toMatchObject({
|
||||
|
@ -286,7 +265,6 @@ describe('GET /workflows/:id', () => {
|
|||
});
|
||||
|
||||
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 workflowPayload = makeWorkflow({
|
||||
|
@ -295,7 +273,7 @@ describe('GET /workflows/:id', () => {
|
|||
});
|
||||
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.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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||
|
||||
const workflowPayload = makeWorkflow({
|
||||
|
@ -320,7 +296,7 @@ describe('GET /workflows/:id', () => {
|
|||
});
|
||||
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.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 () => {
|
||||
const member1 = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const member2 = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||
|
||||
const workflowPayload = makeWorkflow({
|
||||
withPinData: false,
|
||||
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||
});
|
||||
const workflow = await createWorkflow(workflowPayload, member1);
|
||||
await testDb.shareWorkflowWithUsers(workflow, [member2]);
|
||||
const workflow = await createWorkflow(workflowPayload, member);
|
||||
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.body.data.usedCredentials).toMatchObject([
|
||||
{
|
||||
|
@ -357,7 +331,7 @@ describe('GET /workflows/:id', () => {
|
|||
]);
|
||||
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.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 () => {
|
||||
const member1 = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const member2 = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||
// Both users have access to the credential (none is owner)
|
||||
await testDb.shareCredentialWithUsers(savedCredential, [member2]);
|
||||
await testDb.shareCredentialWithUsers(savedCredential, [anotherMember]);
|
||||
|
||||
const workflowPayload = makeWorkflow({
|
||||
withPinData: false,
|
||||
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||
});
|
||||
const workflow = await createWorkflow(workflowPayload, member1);
|
||||
await testDb.shareWorkflowWithUsers(workflow, [member2]);
|
||||
const workflow = await createWorkflow(workflowPayload, member);
|
||||
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.body.data.usedCredentials).toMatchObject([
|
||||
{
|
||||
|
@ -394,7 +366,7 @@ describe('GET /workflows/:id', () => {
|
|||
]);
|
||||
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.body.data.usedCredentials).toMatchObject([
|
||||
{
|
||||
|
@ -409,33 +381,26 @@ describe('GET /workflows/:id', () => {
|
|||
|
||||
describe('POST /workflows', () => {
|
||||
it('Should create a workflow that uses no credential', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
it('Should save a new workflow with credentials', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
const workflow = makeWorkflow({
|
||||
withPinData: false,
|
||||
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);
|
||||
});
|
||||
|
||||
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.
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
const workflow = makeWorkflow({
|
||||
|
@ -443,7 +408,7 @@ describe('POST /workflows', () => {
|
|||
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.body.message).toBe(
|
||||
|
@ -452,9 +417,6 @@ describe('POST /workflows', () => {
|
|||
});
|
||||
|
||||
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.
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||
const workflow = makeWorkflow({
|
||||
|
@ -462,32 +424,27 @@ describe('POST /workflows', () => {
|
|||
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);
|
||||
});
|
||||
|
||||
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 member2 = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
||||
await testDb.shareCredentialWithUsers(savedCredential, [member2]);
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||
await testDb.shareCredentialWithUsers(savedCredential, [anotherMember]);
|
||||
|
||||
const workflow = makeWorkflow({
|
||||
withPinData: false,
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /workflows/:id - validate credential permissions to user', () => {
|
||||
it('Should succeed when saving unchanged workflow nodes', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
const workflow = {
|
||||
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 response = await authAgent(owner).patch(`/workflows/${id}`).send({
|
||||
const response = await authOwnerAgent.patch(`/workflows/${id}`).send({
|
||||
name: 'new name',
|
||||
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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||
const workflow = {
|
||||
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 response = await authAgent(owner)
|
||||
.patch(`/workflows/${id}`)
|
||||
.send({
|
||||
versionId,
|
||||
nodes: [
|
||||
{
|
||||
id: 'uuid-1234',
|
||||
name: 'Start',
|
||||
parameters: {},
|
||||
position: [-20, 260],
|
||||
type: 'n8n-nodes-base.start',
|
||||
typeVersion: 1,
|
||||
credentials: {
|
||||
default: {
|
||||
id: savedCredential.id,
|
||||
name: savedCredential.name,
|
||||
},
|
||||
const response = await authOwnerAgent.patch(`/workflows/${id}`).send({
|
||||
versionId,
|
||||
nodes: [
|
||||
{
|
||||
id: 'uuid-1234',
|
||||
name: 'Start',
|
||||
parameters: {},
|
||||
position: [-20, 260],
|
||||
type: 'n8n-nodes-base.start',
|
||||
typeVersion: 1,
|
||||
credentials: {
|
||||
default: {
|
||||
id: savedCredential.id,
|
||||
name: savedCredential.name,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
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 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 response = await authAgent(member)
|
||||
.patch(`/workflows/${id}`)
|
||||
.send({
|
||||
versionId,
|
||||
nodes: [
|
||||
{
|
||||
id: 'uuid-1234',
|
||||
name: 'Start',
|
||||
parameters: {},
|
||||
position: [-20, 260],
|
||||
type: 'n8n-nodes-base.start',
|
||||
typeVersion: 1,
|
||||
credentials: {},
|
||||
},
|
||||
{
|
||||
id: 'uuid-12345',
|
||||
name: 'Start',
|
||||
parameters: {},
|
||||
position: [-20, 260],
|
||||
type: 'n8n-nodes-base.start',
|
||||
typeVersion: 1,
|
||||
credentials: {
|
||||
default: {
|
||||
id: savedCredential.id,
|
||||
name: savedCredential.name,
|
||||
},
|
||||
const response = await authMemberAgent.patch(`/workflows/${id}`).send({
|
||||
versionId,
|
||||
nodes: [
|
||||
{
|
||||
id: 'uuid-1234',
|
||||
name: 'Start',
|
||||
parameters: {},
|
||||
position: [-20, 260],
|
||||
type: 'n8n-nodes-base.start',
|
||||
typeVersion: 1,
|
||||
credentials: {},
|
||||
},
|
||||
{
|
||||
id: 'uuid-12345',
|
||||
name: 'Start',
|
||||
parameters: {},
|
||||
position: [-20, 260],
|
||||
type: 'n8n-nodes-base.start',
|
||||
typeVersion: 1,
|
||||
credentials: {
|
||||
default: {
|
||||
id: savedCredential.id,
|
||||
name: savedCredential.name,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it('Should succeed but prevent modifying node attributes other than position, name and disabled', async () => {
|
||||
const member1 = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const member2 = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||
|
||||
const originalNodes: INode[] = [
|
||||
{
|
||||
|
@ -714,14 +658,12 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
|||
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;
|
||||
|
||||
await authAgent(member1)
|
||||
.put(`/workflows/${id}/share`)
|
||||
.send({ shareWithIds: [member2.id] });
|
||||
await authMemberAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [anotherMember.id] });
|
||||
|
||||
const response = await authAgent(member2).patch(`/workflows/${id}`).send({
|
||||
const response = await authAnotherMemberAgent.patch(`/workflows/${id}`).send({
|
||||
versionId,
|
||||
nodes: changedNodes,
|
||||
});
|
||||
|
@ -733,29 +675,24 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
|||
|
||||
describe('PATCH /workflows/:id - validate interim updates', () => {
|
||||
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
|
||||
|
||||
const createResponse = await authAgent(owner).post('/workflows').send(makeWorkflow());
|
||||
const createResponse = await authOwnerAgent.post('/workflows').send(makeWorkflow());
|
||||
const { id, versionId: ownerVersionId } = createResponse.body.data;
|
||||
await authAgent(owner)
|
||||
.put(`/workflows/${id}/share`)
|
||||
.send({ shareWithIds: [member.id] });
|
||||
await authOwnerAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [member.id] });
|
||||
|
||||
// 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;
|
||||
|
||||
await authAgent(member)
|
||||
await authMemberAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.send({ name: 'Update by member', versionId: memberVersionId });
|
||||
|
||||
// owner blocked from updating workflow nodes
|
||||
|
||||
const updateAttemptResponse = await authAgent(owner)
|
||||
const updateAttemptResponse = await authOwnerAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
// 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 updateResponse = await authAgent(owner)
|
||||
const updateResponse = await authOwnerAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.send({ name: 'Update by owner', versionId: ownerFirstVersionId });
|
||||
|
||||
const { versionId: ownerSecondVersionId } = updateResponse.body.data;
|
||||
|
||||
await authAgent(owner)
|
||||
.put(`/workflows/${id}/share`)
|
||||
.send({ shareWithIds: [member.id] });
|
||||
await authOwnerAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [member.id] });
|
||||
|
||||
// member accesses workflow
|
||||
|
||||
const memberGetResponse = await authAgent(member).get(`/workflows/${id}`);
|
||||
const memberGetResponse = await authMemberAgent.get(`/workflows/${id}`);
|
||||
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
||||
|
||||
// owner re-updates workflow
|
||||
|
||||
await authAgent(owner)
|
||||
await authOwnerAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.send({ name: 'Owner update again', versionId: ownerSecondVersionId });
|
||||
|
||||
// member blocked from updating workflow
|
||||
|
||||
const updateAttemptResponse = await authAgent(member)
|
||||
const updateAttemptResponse = await authMemberAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
// 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;
|
||||
await authAgent(owner)
|
||||
.put(`/workflows/${id}/share`)
|
||||
.send({ shareWithIds: [member.id] });
|
||||
await authOwnerAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [member.id] });
|
||||
|
||||
// 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;
|
||||
await authAgent(member)
|
||||
await authMemberAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.send({ active: true, versionId: memberVersionId });
|
||||
|
||||
// owner blocked from activating workflow
|
||||
|
||||
const activationAttemptResponse = await authAgent(owner)
|
||||
const activationAttemptResponse = await authOwnerAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
// 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 updateResponse = await authAgent(owner)
|
||||
const updateResponse = await authOwnerAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.send({ name: 'Update by owner', versionId: ownerFirstVersionId });
|
||||
const { versionId: ownerSecondVersionId } = updateResponse.body.data;
|
||||
|
||||
await authAgent(owner)
|
||||
.put(`/workflows/${id}/share`)
|
||||
.send({ shareWithIds: [member.id] });
|
||||
await authOwnerAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [member.id] });
|
||||
|
||||
// member accesses workflow
|
||||
|
||||
const memberGetResponse = await authAgent(member).get(`/workflows/${id}`);
|
||||
const memberGetResponse = await authMemberAgent.get(`/workflows/${id}`);
|
||||
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
||||
|
||||
// owner activates workflow
|
||||
|
||||
await authAgent(owner)
|
||||
await authOwnerAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.send({ active: true, versionId: ownerSecondVersionId });
|
||||
|
||||
// member blocked from activating workflow
|
||||
|
||||
const updateAttemptResponse = await authAgent(member)
|
||||
const updateAttemptResponse = await authMemberAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
// 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;
|
||||
await authAgent(owner)
|
||||
.put(`/workflows/${id}/share`)
|
||||
.send({ shareWithIds: [member.id] });
|
||||
await authOwnerAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [member.id] });
|
||||
|
||||
// member accesses workflow
|
||||
|
||||
const memberGetResponse = await authAgent(member).get(`/workflows/${id}`);
|
||||
const memberGetResponse = await authMemberAgent.get(`/workflows/${id}`);
|
||||
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
||||
|
||||
// owner updates workflow name
|
||||
|
||||
await authAgent(owner)
|
||||
await authOwnerAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.send({ name: 'Another name', versionId: ownerVersionId });
|
||||
|
||||
// member blocked from updating workflow settings
|
||||
|
||||
const updateAttemptResponse = await authAgent(member)
|
||||
const updateAttemptResponse = await authMemberAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.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 () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
// 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;
|
||||
await authAgent(owner)
|
||||
.put(`/workflows/${id}/share`)
|
||||
.send({ shareWithIds: [member.id] });
|
||||
await authOwnerAgent.put(`/workflows/${id}/share`).send({ shareWithIds: [member.id] });
|
||||
|
||||
// member accesses workflow
|
||||
|
||||
const memberGetResponse = await authAgent(member).get(`/workflows/${id}`);
|
||||
const memberGetResponse = await authMemberAgent.get(`/workflows/${id}`);
|
||||
const { versionId: memberVersionId } = memberGetResponse.body.data;
|
||||
|
||||
// owner updates workflow settings
|
||||
|
||||
await authAgent(owner)
|
||||
await authOwnerAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.send({ settings: { saveManualExecutions: true }, versionId: ownerVersionId });
|
||||
|
||||
// member blocked from updating workflow name
|
||||
|
||||
const updateAttemptResponse = await authAgent(member)
|
||||
const updateAttemptResponse = await authMemberAgent
|
||||
.patch(`/workflows/${id}`)
|
||||
.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 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';
|
||||
|
||||
let app: express.Application;
|
||||
let globalOwnerRole: Role;
|
||||
|
||||
// mock whether sharing is enabled or not
|
||||
jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(false);
|
||||
let ownerShell: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
|
||||
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 () => {
|
||||
await testDb.truncate(['User', 'Workflow', 'SharedWorkflow']);
|
||||
await testDb.truncate(['Workflow', 'SharedWorkflow']);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.terminate();
|
||||
});
|
||||
|
||||
test('POST /workflows should store pin data for node in workflow', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
describe('POST /workflows', () => {
|
||||
test('should store pin data for node in workflow', async () => {
|
||||
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 () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
describe('GET /workflows/:id', () => {
|
||||
test('should return pin data', async () => {
|
||||
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();
|
||||
});
|
||||
|
||||
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);
|
||||
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 { randomEmail, randomName } from '../integration/shared/random';
|
||||
import * as Helpers from './Helpers';
|
||||
import { WorkflowExecuteAdditionalData } from '@/index';
|
||||
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
||||
|
||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
|
|
|
@ -11,20 +11,59 @@ import {
|
|||
} from 'n8n-workflow';
|
||||
import { CredentialsHelper } from '@/CredentialsHelper';
|
||||
import { CredentialTypes } from '@/CredentialTypes';
|
||||
import * as Helpers from './Helpers';
|
||||
import { Container } from 'typedi';
|
||||
import { NodeTypes } from '@/NodeTypes';
|
||||
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', () => {
|
||||
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', () => {
|
||||
const tests: Array<{
|
||||
description: string;
|
||||
|
@ -219,8 +258,6 @@ describe('CredentialsHelper', () => {
|
|||
qs: {},
|
||||
};
|
||||
|
||||
const nodeTypes = Helpers.NodeTypes() as unknown as NodeTypes;
|
||||
|
||||
const workflow = new Workflow({
|
||||
nodes: [node],
|
||||
connections: {},
|
||||
|
|
|
@ -1,32 +1,37 @@
|
|||
import { LoggerProxy, WorkflowExecuteMode } from 'n8n-workflow';
|
||||
import { QueryFailedError } from 'typeorm';
|
||||
import { IRun, LoggerProxy, WorkflowExecuteMode } from 'n8n-workflow';
|
||||
import { QueryFailedError, Repository } from 'typeorm';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
|
||||
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 { getLogger } from '@/Logger';
|
||||
import * as UserManagementHelper from '@/UserManagement/UserManagementHelper';
|
||||
import { getLogger } from '@/Logger';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
|
||||
import { mockInstance } from '../integration/shared/utils';
|
||||
|
||||
const FAKE_USER_ID = 'abcde-fghij';
|
||||
|
||||
type WorkflowStatisticsRepository = Repository<WorkflowStatistics>;
|
||||
jest.mock('@/Db', () => {
|
||||
return {
|
||||
collections: {
|
||||
WorkflowStatistics: {
|
||||
insert: jest.fn((...args) => {}),
|
||||
update: jest.fn((...args) => {}),
|
||||
},
|
||||
WorkflowStatistics: mock<WorkflowStatisticsRepository>(),
|
||||
},
|
||||
};
|
||||
});
|
||||
jest.spyOn(UserManagementHelper, 'getWorkflowOwner').mockImplementation(async (_workflowId) => {
|
||||
return { id: FAKE_USER_ID };
|
||||
});
|
||||
|
||||
describe('Events', () => {
|
||||
const fakeUser = Object.assign(new User(), { id: 'abcde-fghij' });
|
||||
const internalHooks = mockInstance(InternalHooks);
|
||||
|
||||
jest.spyOn(UserManagementHelper, 'getWorkflowOwner').mockResolvedValue(fakeUser);
|
||||
|
||||
const workflowStatisticsRepository = Db.collections.WorkflowStatistics as ReturnType<
|
||||
typeof mock<WorkflowStatisticsRepository>
|
||||
>;
|
||||
|
||||
beforeAll(() => {
|
||||
config.set('diagnostics.enabled', true);
|
||||
config.set('deployment.type', 'n8n-testing');
|
||||
|
@ -57,8 +62,9 @@ describe('Events', () => {
|
|||
nodes: [],
|
||||
connections: {},
|
||||
};
|
||||
const runData = {
|
||||
const runData: IRun = {
|
||||
finished: true,
|
||||
status: 'success',
|
||||
data: { resultData: { runData: {} } },
|
||||
mode: 'internal' as WorkflowExecuteMode,
|
||||
startedAt: new Date(),
|
||||
|
@ -66,7 +72,7 @@ describe('Events', () => {
|
|||
await workflowExecutionCompleted(workflow, runData);
|
||||
expect(internalHooks.onFirstProductionWorkflowSuccess).toBeCalledTimes(1);
|
||||
expect(internalHooks.onFirstProductionWorkflowSuccess).toHaveBeenNthCalledWith(1, {
|
||||
user_id: FAKE_USER_ID,
|
||||
user_id: fakeUser.id,
|
||||
workflow_id: workflow.id,
|
||||
});
|
||||
});
|
||||
|
@ -82,8 +88,9 @@ describe('Events', () => {
|
|||
nodes: [],
|
||||
connections: {},
|
||||
};
|
||||
const runData = {
|
||||
const runData: IRun = {
|
||||
finished: false,
|
||||
status: 'failed',
|
||||
data: { resultData: { runData: {} } },
|
||||
mode: 'internal' as WorkflowExecuteMode,
|
||||
startedAt: new Date(),
|
||||
|
@ -94,7 +101,7 @@ describe('Events', () => {
|
|||
|
||||
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
|
||||
Db.collections.WorkflowStatistics.insert.mockImplementationOnce(() => {
|
||||
workflowStatisticsRepository.insert.mockImplementationOnce(() => {
|
||||
throw new QueryFailedError('invalid insert', [], '');
|
||||
});
|
||||
const workflow = {
|
||||
|
@ -106,8 +113,9 @@ describe('Events', () => {
|
|||
nodes: [],
|
||||
connections: {},
|
||||
};
|
||||
const runData = {
|
||||
const runData: IRun = {
|
||||
finished: true,
|
||||
status: 'success',
|
||||
data: { resultData: { runData: {} } },
|
||||
mode: 'internal' as WorkflowExecuteMode,
|
||||
startedAt: new Date(),
|
||||
|
@ -132,7 +140,7 @@ describe('Events', () => {
|
|||
await nodeFetchedData(workflowId, node);
|
||||
expect(internalHooks.onFirstWorkflowDataLoad).toBeCalledTimes(1);
|
||||
expect(internalHooks.onFirstWorkflowDataLoad).toHaveBeenNthCalledWith(1, {
|
||||
user_id: FAKE_USER_ID,
|
||||
user_id: fakeUser.id,
|
||||
workflow_id: workflowId,
|
||||
node_type: node.type,
|
||||
node_id: node.id,
|
||||
|
@ -159,7 +167,7 @@ describe('Events', () => {
|
|||
await nodeFetchedData(workflowId, node);
|
||||
expect(internalHooks.onFirstWorkflowDataLoad).toBeCalledTimes(1);
|
||||
expect(internalHooks.onFirstWorkflowDataLoad).toHaveBeenNthCalledWith(1, {
|
||||
user_id: FAKE_USER_ID,
|
||||
user_id: fakeUser.id,
|
||||
workflow_id: workflowId,
|
||||
node_type: node.type,
|
||||
node_id: node.id,
|
||||
|
@ -170,7 +178,7 @@ describe('Events', () => {
|
|||
|
||||
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
|
||||
Db.collections.WorkflowStatistics.insert.mockImplementationOnce(() => {
|
||||
workflowStatisticsRepository.insert.mockImplementationOnce(() => {
|
||||
throw new QueryFailedError('invalid insert', [], '');
|
||||
});
|
||||
const workflowId = '1';
|
||||
|
|
|
@ -1,108 +1,4 @@
|
|||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||
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;
|
||||
}
|
||||
import { INodeTypeData } from 'n8n-workflow';
|
||||
|
||||
/**
|
||||
* Ensure all pending promises settle. The promise's `resolve` is placed in
|
||||
|
|
|
@ -1,46 +1,47 @@
|
|||
import { v4 as uuid } from 'uuid';
|
||||
import {
|
||||
ICredentialTypes,
|
||||
INodeTypeData,
|
||||
INodeTypes,
|
||||
SubworkflowOperationError,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
import { Container } from 'typedi';
|
||||
import { ICredentialTypes, INodeTypes, SubworkflowOperationError, Workflow } from 'n8n-workflow';
|
||||
|
||||
import config from '@/config';
|
||||
import * as Db from '@/Db';
|
||||
import * as testDb from '../integration/shared/testDb';
|
||||
import { mockNodeTypesData, NodeTypes as MockNodeTypes } from './Helpers';
|
||||
import { Role } from '@db/entities/Role';
|
||||
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 { PermissionChecker } from '@/UserManagement/PermissionChecker';
|
||||
import * as UserManagementHelper from '@/UserManagement/UserManagementHelper';
|
||||
import { WorkflowsService } from '@/workflows/workflows.services';
|
||||
|
||||
import {
|
||||
randomCredentialPayload as randomCred,
|
||||
randomPositiveDigit,
|
||||
} from '../integration/shared/random';
|
||||
|
||||
import { Role } from '@db/entities/Role';
|
||||
import * as testDb from '../integration/shared/testDb';
|
||||
import { mockNodeTypesData } from './Helpers';
|
||||
import type { SaveCredentialFunction } from '../integration/shared/types';
|
||||
import { User } from '@db/entities/User';
|
||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import { mockInstance } from '../integration/shared/utils';
|
||||
|
||||
let mockNodeTypes: INodeTypes;
|
||||
let credentialOwnerRole: Role;
|
||||
let workflowOwnerRole: Role;
|
||||
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 () => {
|
||||
await testDb.init();
|
||||
|
||||
mockNodeTypes = MockNodeTypes({
|
||||
loaded: {
|
||||
nodes: MOCK_NODE_TYPES_DATA,
|
||||
credentials: {},
|
||||
},
|
||||
known: { nodes: {}, credentials: {} },
|
||||
credentialTypes: {} as ICredentialTypes,
|
||||
});
|
||||
mockNodeTypes = Container.get(NodeTypes);
|
||||
|
||||
credentialOwnerRole = await testDb.getCredentialOwnerRole();
|
||||
workflowOwnerRole = await testDb.getWorkflowOwnerRole();
|
||||
|
@ -241,7 +242,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
|||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes: MockNodeTypes(),
|
||||
nodeTypes: mockNodeTypes,
|
||||
id: '2',
|
||||
});
|
||||
await expect(
|
||||
|
@ -263,7 +264,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
|||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes: MockNodeTypes(),
|
||||
nodeTypes: mockNodeTypes,
|
||||
id: '2',
|
||||
});
|
||||
await expect(
|
||||
|
@ -301,7 +302,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
|||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes: MockNodeTypes(),
|
||||
nodeTypes: mockNodeTypes,
|
||||
id: '2',
|
||||
settings: {
|
||||
callerPolicy: 'workflowsFromAList',
|
||||
|
@ -327,7 +328,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
|||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes: MockNodeTypes(),
|
||||
nodeTypes: mockNodeTypes,
|
||||
id: '2',
|
||||
});
|
||||
await expect(
|
||||
|
@ -350,7 +351,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
|||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes: MockNodeTypes(),
|
||||
nodeTypes: mockNodeTypes,
|
||||
id: '2',
|
||||
settings: {
|
||||
callerPolicy: 'workflowsFromAList',
|
||||
|
@ -376,7 +377,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
|||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes: MockNodeTypes(),
|
||||
nodeTypes: mockNodeTypes,
|
||||
id: '2',
|
||||
settings: {
|
||||
callerPolicy: 'any',
|
||||
|
@ -387,5 +388,3 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
|
|||
).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