Compare commits

..

1 Commits

Author SHA1 Message Date
Ali BARIN
ab4abd590a feat(formatter/text): stringify json transformer 2024-08-16 14:08:00 +00:00
302 changed files with 2316 additions and 8830 deletions

View File

@@ -23,7 +23,6 @@ env:
REDIS_HOST: localhost
APP_ENV: production
LICENSE_KEY: dummy_license_key
BACKEND_APP_URL: http://localhost:3000
jobs:
test:

View File

@@ -10,7 +10,7 @@ import process from 'process';
async function fetchAdminRole() {
const role = await Role.query()
.where({
name: 'Admin',
key: 'admin',
})
.limit(1)
.first();

View File

@@ -11,7 +11,6 @@
"start:worker": "node src/worker.js",
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
"test": "APP_ENV=test vitest run",
"test:watch": "APP_ENV=test vitest watch",
"lint": "eslint .",
"db:create": "node ./bin/database/create.js",
"db:seed:user": "node ./bin/database/seed-user.js",
@@ -39,7 +38,7 @@
"debug": "~2.6.9",
"dotenv": "^10.0.0",
"express": "~4.18.2",
"express-async-errors": "^3.1.1",
"express-async-handler": "^1.2.0",
"express-basic-auth": "^1.2.1",
"express-graphql": "^0.12.0",
"fast-xml-parser": "^4.0.11",
@@ -107,9 +106,7 @@
"access": "public"
},
"nodemonConfig": {
"watch": [
"src/"
],
"watch": [ "src/" ],
"ext": "js"
}
}

View File

@@ -1,6 +1,5 @@
import createError from 'http-errors';
import express from 'express';
import 'express-async-errors';
import cors from 'cors';
import appConfig from './config/app.js';

View File

@@ -33,7 +33,6 @@ export default defineAction({
type: 'string',
required: true,
variables: true,
valueType: 'parse',
},
],
},

View File

@@ -16,6 +16,7 @@ import trimWhitespace from './transformers/trim-whitespace.js';
import useDefaultValue from './transformers/use-default-value.js';
import parseStringifiedJson from './transformers/parse-stringified-json.js';
import createUuid from './transformers/create-uuid.js';
import stringifyJson from './transformers/stringify-json.js';
const transformers = {
base64ToString,
@@ -34,6 +35,7 @@ const transformers = {
useDefaultValue,
parseStringifiedJson,
createUuid,
stringifyJson,
};
export default defineAction({
@@ -63,6 +65,7 @@ export default defineAction({
{ label: 'Extract Number', value: 'extractNumber' },
{ label: 'Lowercase', value: 'lowercase' },
{ label: 'Parse stringified JSON', value: 'parseStringifiedJson' },
{ label: 'Stringify JSON', value: 'stringifyJson' },
{ label: 'Pluralize', value: 'pluralize' },
{ label: 'Replace', value: 'replace' },
{ label: 'String to Base64', value: 'stringToBase64' },

View File

@@ -0,0 +1,7 @@
const stringifyJson = ($) => {
const input = $.step.parameters.input;
return JSON.stringify(input);
};
export default stringifyJson;

View File

@@ -13,6 +13,7 @@ import encodeUri from './text/encode-uri.js';
import trimWhitespace from './text/trim-whitespace.js';
import useDefaultValue from './text/use-default-value.js';
import parseStringifiedJson from './text/parse-stringified-json.js';
import stringifyJson from './text/stringify-json.js';
import performMathOperation from './numbers/perform-math-operation.js';
import randomNumber from './numbers/random-number.js';
import formatNumber from './numbers/format-number.js';
@@ -40,6 +41,7 @@ const options = {
formatPhoneNumber,
formatDateTime,
parseStringifiedJson,
stringifyJson,
};
export default {

View File

@@ -1,4 +1,4 @@
const useDefaultValue = [
const parseStringifiedJson = [
{
label: 'Input',
key: 'input',
@@ -9,4 +9,4 @@ const useDefaultValue = [
},
];
export default useDefaultValue;
export default parseStringifiedJson;

View File

@@ -0,0 +1,12 @@
const stringifyJson = [
{
label: 'Input',
key: 'input',
type: 'string',
required: true,
description: 'JSON to stringify.',
variables: true,
},
];
export default stringifyJson;

View File

@@ -4,7 +4,6 @@ export default defineTrigger({
name: 'New comment',
key: 'newComment',
description: 'Triggers when a new comment is created.',
pollInterval: 15,
arguments: [
{
label: 'Status',

View File

@@ -3,7 +3,6 @@ import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New page',
key: 'newPage',
pollInterval: 15,
description: 'Triggers when a new page is created.',
arguments: [
{

View File

@@ -3,7 +3,6 @@ import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New post',
key: 'newPost',
pollInterval: 15,
description: 'Triggers when a new post is created.',
arguments: [
{

View File

@@ -3,7 +3,6 @@ import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New video by search',
key: 'newVideoBySearch',
pollInterval: 15,
description:
'Triggers when a new video is uploaded that matches a specific search string.',
arguments: [

View File

@@ -3,7 +3,6 @@ import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New video in channel',
key: 'newVideoInChannel',
pollInterval: 15,
description:
'Triggers when a new video is published to a specific Youtube channel.',
arguments: [

View File

@@ -2,7 +2,7 @@ import appConfig from './app.js';
const corsOptions = {
origin: appConfig.webAppUrl,
methods: 'GET,HEAD,POST,PATCH,DELETE',
methods: 'GET,HEAD,POST,DELETE',
credentials: true,
optionsSuccessStatus: 200,
};

View File

@@ -11,5 +11,5 @@ export default async (request, response) => {
await accessToken.revoke();
response.status(204).end();
response.status(204).send();
};

View File

@@ -1,25 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import AppConfig from '../../../../../models/app-config.js';
export default async (request, response) => {
const appConfig = await AppConfig.query()
.findOne({ key: request.params.appKey })
.throwIfNotFound();
const appAuthClient = await appConfig
.$relatedQuery('appAuthClients')
.insert(appAuthClientParams(request));
renderObject(response, appAuthClient, { status: 201 });
};
const appAuthClientParams = (request) => {
const { active, appKey, name, formattedAuthDefaults } = request.body;
return {
active,
appKey,
name,
formattedAuthDefaults,
};
};

View File

@@ -1,94 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createRole } from '../../../../../../test/factories/role.js';
import createAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/create-auth-client.js';
import { createAppConfig } from '../../../../../../test/factories/app-config.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('POST /api/v1/admin/apps/:appKey/auth-clients', () => {
let currentUser, adminRole, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ name: 'Admin' });
currentUser = await createUser({ roleId: adminRole.id });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return created response for valid app config', async () => {
await createAppConfig({
key: 'gitlab',
});
const appAuthClient = {
active: true,
appKey: 'gitlab',
name: 'First auth client',
formattedAuthDefaults: {
clientid: 'sample client ID',
clientSecret: 'sample client secret',
instanceUrl: 'https://gitlab.com',
oAuthRedirectUrl: 'http://localhost:3001/app/gitlab/connection/add',
},
};
const response = await request(app)
.post('/api/v1/admin/apps/gitlab/auth-clients')
.set('Authorization', token)
.send(appAuthClient)
.expect(201);
const expectedPayload = createAppAuthClientMock(appAuthClient);
expect(response.body).toMatchObject(expectedPayload);
});
it('should return not found response for not existing app config', async () => {
const appAuthClient = {
active: true,
appKey: 'gitlab',
name: 'First auth client',
formattedAuthDefaults: {
clientid: 'sample client ID',
clientSecret: 'sample client secret',
instanceUrl: 'https://gitlab.com',
oAuthRedirectUrl: 'http://localhost:3001/app/gitlab/connection/add',
},
};
await request(app)
.post('/api/v1/admin/apps/gitlab/auth-clients')
.set('Authorization', token)
.send(appAuthClient)
.expect(404);
});
it('should return bad request response for missing required fields', async () => {
await createAppConfig({
key: 'gitlab',
});
const appAuthClient = {
appKey: 'gitlab',
};
const response = await request(app)
.post('/api/v1/admin/apps/gitlab/auth-clients')
.set('Authorization', token)
.send(appAuthClient)
.expect(422);
expect(response.body.meta.type).toEqual('ModelValidation');
expect(response.body.errors).toMatchObject({
name: ["must have required property 'name'"],
formattedAuthDefaults: [
"must have required property 'formattedAuthDefaults'",
],
});
});
});

View File

@@ -1,21 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import AppConfig from '../../../../../models/app-config.js';
export default async (request, response) => {
const createdAppConfig = await AppConfig.query().insertAndFetch(
appConfigParams(request)
);
renderObject(response, createdAppConfig, { status: 201 });
};
const appConfigParams = (request) => {
const { allowCustomConnection, shared, disabled } = request.body;
return {
key: request.params.appKey,
allowCustomConnection,
shared,
disabled,
};
};

View File

@@ -1,67 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createRole } from '../../../../../../test/factories/role.js';
import createAppConfigMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/create-config.js';
import { createAppConfig } from '../../../../../../test/factories/app-config.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('POST /api/v1/admin/apps/:appKey/config', () => {
let currentUser, adminRole, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ name: 'Admin' });
currentUser = await createUser({ roleId: adminRole.id });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return created app config', async () => {
const appConfig = {
allowCustomConnection: true,
shared: true,
disabled: false,
};
const response = await request(app)
.post('/api/v1/admin/apps/gitlab/config')
.set('Authorization', token)
.send(appConfig)
.expect(201);
const expectedPayload = createAppConfigMock({
...appConfig,
key: 'gitlab',
});
expect(response.body).toMatchObject(expectedPayload);
});
it('should return HTTP 422 for already existing app config', async () => {
const appConfig = {
key: 'gitlab',
allowCustomConnection: true,
shared: true,
disabled: false,
};
await createAppConfig(appConfig);
const response = await request(app)
.post('/api/v1/admin/apps/gitlab/config')
.set('Authorization', token)
.send({
disabled: false,
})
.expect(422);
expect(response.body.meta.type).toEqual('UniqueViolationError');
expect(response.body.errors).toMatchObject({
key: ["'key' must be unique."],
});
});
});

View File

@@ -15,7 +15,7 @@ describe('GET /api/v1/admin/apps/:appKey/auth-clients/:appAuthClientId', () => {
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ name: 'Admin' });
adminRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: adminRole.id });
currentAppAuthClient = await createAppAuthClient({

View File

@@ -14,7 +14,7 @@ describe('GET /api/v1/admin/apps/:appKey/auth-clients', () => {
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ name: 'Admin' });
adminRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: adminRole.id });
token = await createAuthTokenByUserId(currentUser.id);

View File

@@ -1,22 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import AppAuthClient from '../../../../../models/app-auth-client.js';
export default async (request, response) => {
const appAuthClient = await AppAuthClient.query()
.findById(request.params.appAuthClientId)
.throwIfNotFound();
await appAuthClient.$query().patchAndFetch(appAuthClientParams(request));
renderObject(response, appAuthClient);
};
const appAuthClientParams = (request) => {
const { active, name, formattedAuthDefaults } = request.body;
return {
active,
name,
formattedAuthDefaults,
};
};

View File

@@ -1,104 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createRole } from '../../../../../../test/factories/role.js';
import updateAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/update-auth-client.js';
import { createAppConfig } from '../../../../../../test/factories/app-config.js';
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('PATCH /api/v1/admin/apps/:appKey/auth-clients', () => {
let currentUser, adminRole, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ name: 'Admin' });
currentUser = await createUser({ roleId: adminRole.id });
token = await createAuthTokenByUserId(currentUser.id);
await createAppConfig({
key: 'gitlab',
});
});
it('should return updated entity for valid app auth client', async () => {
const appAuthClient = {
active: true,
appKey: 'gitlab',
formattedAuthDefaults: {
clientid: 'sample client ID',
clientSecret: 'sample client secret',
instanceUrl: 'https://gitlab.com',
oAuthRedirectUrl: 'http://localhost:3001/app/gitlab/connection/add',
},
};
const existingAppAuthClient = await createAppAuthClient({
appKey: 'gitlab',
name: 'First auth client',
});
const response = await request(app)
.patch(
`/api/v1/admin/apps/gitlab/auth-clients/${existingAppAuthClient.id}`
)
.set('Authorization', token)
.send(appAuthClient)
.expect(200);
const expectedPayload = updateAppAuthClientMock({
...existingAppAuthClient,
...appAuthClient,
});
expect(response.body).toMatchObject(expectedPayload);
});
it('should return not found response for not existing app auth client', async () => {
const notExistingAppAuthClientId = Crypto.randomUUID();
await request(app)
.patch(
`/api/v1/admin/apps/gitlab/auth-clients/${notExistingAppAuthClientId}`
)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.patch('/api/v1/admin/apps/gitlab/auth-clients/invalidAuthClientUUID')
.set('Authorization', token)
.expect(400);
});
it('should return HTTP 422 for invalid payload', async () => {
const appAuthClient = {
formattedAuthDefaults: 'invalid input',
};
const existingAppAuthClient = await createAppAuthClient({
appKey: 'gitlab',
name: 'First auth client',
});
const response = await request(app)
.patch(
`/api/v1/admin/apps/gitlab/auth-clients/${existingAppAuthClient.id}`
)
.set('Authorization', token)
.send(appAuthClient)
.expect(422);
expect(response.body.meta.type).toBe('ModelValidation');
expect(response.body.errors).toMatchObject({
formattedAuthDefaults: ['must be object'],
});
});
});

View File

@@ -1,24 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import AppConfig from '../../../../../models/app-config.js';
export default async (request, response) => {
const appConfig = await AppConfig.query()
.findOne({
key: request.params.appKey,
})
.throwIfNotFound();
await appConfig.$query().patchAndFetch(appConfigParams(request));
renderObject(response, appConfig);
};
const appConfigParams = (request) => {
const { allowCustomConnection, shared, disabled } = request.body;
return {
allowCustomConnection,
shared,
disabled,
};
};

View File

@@ -1,91 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createRole } from '../../../../../../test/factories/role.js';
import createAppConfigMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/create-config.js';
import { createAppConfig } from '../../../../../../test/factories/app-config.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('PATCH /api/v1/admin/apps/:appKey/config', () => {
let currentUser, adminRole, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ name: 'Admin' });
currentUser = await createUser({ roleId: adminRole.id });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return updated app config', async () => {
const appConfig = {
key: 'gitlab',
allowCustomConnection: true,
shared: true,
disabled: false,
};
await createAppConfig(appConfig);
const newAppConfigValues = {
shared: false,
disabled: true,
allowCustomConnection: false,
};
const response = await request(app)
.patch('/api/v1/admin/apps/gitlab/config')
.set('Authorization', token)
.send(newAppConfigValues)
.expect(200);
const expectedPayload = createAppConfigMock({
...newAppConfigValues,
key: 'gitlab',
});
expect(response.body).toMatchObject(expectedPayload);
});
it('should return not found response for unexisting app config', async () => {
const appConfig = {
shared: false,
disabled: true,
allowCustomConnection: false,
};
await request(app)
.patch('/api/v1/admin/apps/gitlab/config')
.set('Authorization', token)
.send(appConfig)
.expect(404);
});
it('should return HTTP 422 for invalid app config data', async () => {
const appConfig = {
key: 'gitlab',
allowCustomConnection: true,
shared: true,
disabled: false,
};
await createAppConfig(appConfig);
const response = await request(app)
.patch('/api/v1/admin/apps/gitlab/config')
.set('Authorization', token)
.send({
disabled: 'invalid value type',
})
.expect(422);
expect(response.body.meta.type).toEqual('ModelValidation');
expect(response.body.errors).toMatchObject({
disabled: ['must be boolean'],
});
});
});

View File

@@ -1,23 +0,0 @@
import pick from 'lodash/pick.js';
import { renderObject } from '../../../../../helpers/renderer.js';
import Config from '../../../../../models/config.js';
export default async (request, response) => {
const config = configParams(request);
await Config.batchUpdate(config);
renderObject(response, config);
};
const configParams = (request) => {
const updatableConfigurationKeys = [
'logo.svgData',
'palette.primary.dark',
'palette.primary.light',
'palette.primary.main',
'title',
];
return pick(request.body, updatableConfigurationKeys);
};

View File

@@ -1,88 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createBulkConfig } from '../../../../../../test/factories/config.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('PATCH /api/v1/admin/config', () => {
let currentUser, adminRole, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ name: 'Admin' });
currentUser = await createUser({ roleId: adminRole.id });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return updated config', async () => {
const title = 'Test environment - Automatisch';
const palettePrimaryMain = '#00adef';
const palettePrimaryDark = '#222222';
const palettePrimaryLight = '#f90707';
const logoSvgData =
'<svg width="25" height="25" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100"><rect width="100%" height="100%" fill="white" /><text x="10" y="40" font-family="Arial" font-size="40" fill="black">A</text></svg>';
const appConfig = {
title,
'palette.primary.main': palettePrimaryMain,
'palette.primary.dark': palettePrimaryDark,
'palette.primary.light': palettePrimaryLight,
'logo.svgData': logoSvgData,
};
await createBulkConfig(appConfig);
const newTitle = 'Updated title';
const newConfigValues = {
title: newTitle,
};
const response = await request(app)
.patch('/api/v1/admin/config')
.set('Authorization', token)
.send(newConfigValues)
.expect(200);
expect(response.body.data.title).toEqual(newTitle);
expect(response.body.meta.type).toEqual('Object');
});
it('should return created config for unexisting config', async () => {
const newTitle = 'Updated title';
const newConfigValues = {
title: newTitle,
};
const response = await request(app)
.patch('/api/v1/admin/config')
.set('Authorization', token)
.send(newConfigValues)
.expect(200);
expect(response.body.data.title).toEqual(newTitle);
expect(response.body.meta.type).toEqual('Object');
});
it('should return null for deleted config entry', async () => {
const newConfigValues = {
title: null,
};
const response = await request(app)
.patch('/api/v1/admin/config')
.set('Authorization', token)
.send(newConfigValues)
.expect(200);
expect(response.body.data.title).toBeNull();
expect(response.body.meta.type).toEqual('Object');
});
});

View File

@@ -11,7 +11,7 @@ describe('GET /api/v1/admin/permissions/catalog', () => {
let role, currentUser, token;
beforeEach(async () => {
role = await createRole({ name: 'Admin' });
role = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: role.id });
token = await createAuthTokenByUserId(currentUser.id);

View File

@@ -1,22 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import Role from '../../../../../models/role.js';
export default async (request, response) => {
const roleData = roleParams(request);
const roleWithPermissions = await Role.query().insertGraphAndFetch(roleData, {
relate: ['permissions'],
});
renderObject(response, roleWithPermissions, { status: 201 });
};
const roleParams = (request) => {
const { name, description, permissions } = request.body;
return {
name,
description,
permissions,
};
};

View File

@@ -1,109 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import Role from '../../../../../models/role.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import createRoleMock from '../../../../../../test/mocks/rest/api/v1/admin/roles/create-role.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('POST /api/v1/admin/roles', () => {
let role, currentUser, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
role = await createRole({ name: 'Admin' });
currentUser = await createUser({ roleId: role.id });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return the created role along with permissions', async () => {
const roleData = {
name: 'Viewer',
description: '',
permissions: [
{
action: 'read',
subject: 'Flow',
conditions: ['isCreator'],
},
],
};
const response = await request(app)
.post('/api/v1/admin/roles')
.set('Authorization', token)
.send(roleData)
.expect(201);
const createdRole = await Role.query()
.withGraphFetched({ permissions: true })
.findOne({ name: 'Viewer' })
.throwIfNotFound();
const expectedPayload = await createRoleMock(
{
...createdRole,
...roleData,
isAdmin: createdRole.isAdmin,
},
[
{
...createdRole.permissions[0],
...roleData.permissions[0],
},
]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return unprocessable entity response for invalid role data', async () => {
const roleData = {
description: '',
permissions: [],
};
const response = await request(app)
.post('/api/v1/admin/roles')
.set('Authorization', token)
.send(roleData)
.expect(422);
expect(response.body).toStrictEqual({
errors: {
name: ["must have required property 'name'"],
},
meta: {
type: 'ModelValidation',
},
});
});
it('should return unprocessable entity response for duplicate role', async () => {
await createRole({ name: 'Viewer' });
const roleData = {
name: 'Viewer',
permissions: [],
};
const response = await request(app)
.post('/api/v1/admin/roles')
.set('Authorization', token)
.send(roleData)
.expect(422);
expect(response.body).toStrictEqual({
errors: {
name: ["'name' must be unique."],
},
meta: {
type: 'UniqueViolationError',
},
});
});
});

View File

@@ -1,11 +0,0 @@
import Role from '../../../../../models/role.js';
export default async (request, response) => {
const role = await Role.query()
.findById(request.params.roleId)
.throwIfNotFound();
await role.deleteWithPermissions();
response.status(204).end();
};

View File

@@ -1,112 +0,0 @@
import Crypto from 'node:crypto';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createPermission } from '../../../../../../test/factories/permission.js';
import { createSamlAuthProvider } from '../../../../../../test/factories/saml-auth-provider.ee.js';
import { createUser } from '../../../../../../test/factories/user.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('DELETE /api/v1/admin/roles/:roleId', () => {
let adminRole, currentUser, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ name: 'Admin' });
currentUser = await createUser({ roleId: adminRole.id });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return HTTP 204 for unused role', async () => {
const role = await createRole();
const permission = await createPermission({ roleId: role.id });
await request(app)
.delete(`/api/v1/admin/roles/${role.id}`)
.set('Authorization', token)
.expect(204);
const refetchedRole = await role.$query();
const refetchedPermission = await permission.$query();
expect(refetchedRole).toBeUndefined();
expect(refetchedPermission).toBeUndefined();
});
it('should return HTTP 404 for not existing role UUID', async () => {
const notExistingRoleUUID = Crypto.randomUUID();
await request(app)
.delete(`/api/v1/admin/roles/${notExistingRoleUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return not authorized response for deleting admin role', async () => {
await request(app)
.delete(`/api/v1/admin/roles/${adminRole.id}`)
.set('Authorization', token)
.expect(403);
});
it('should return unprocessable entity response for role used by users', async () => {
const role = await createRole();
await createUser({ roleId: role.id });
const response = await request(app)
.delete(`/api/v1/admin/roles/${role.id}`)
.set('Authorization', token)
.expect(422);
expect(response.body).toStrictEqual({
errors: {
role: [`All users must be migrated away from the "${role.name}" role.`],
},
meta: {
type: 'ValidationError',
},
});
});
it('should return unprocessable entity response for role used by saml auth providers', async () => {
const samlAuthProvider = await createSamlAuthProvider();
const response = await request(app)
.delete(`/api/v1/admin/roles/${samlAuthProvider.defaultRoleId}`)
.set('Authorization', token)
.expect(422);
expect(response.body).toStrictEqual({
errors: {
samlAuthProvider: [
'You need to change the default role in the SAML configuration before deleting this role.',
],
},
meta: {
type: 'ValidationError',
},
});
});
it('should not delete role and permissions on unsuccessful response', async () => {
const role = await createRole();
const permission = await createPermission({ roleId: role.id });
await createUser({ roleId: role.id });
await request(app)
.delete(`/api/v1/admin/roles/${role.id}`)
.set('Authorization', token)
.expect(422);
const refetchedRole = await role.$query();
const refetchedPermission = await permission.$query();
expect(refetchedRole).toStrictEqual(role);
expect(refetchedPermission).toStrictEqual(permission);
});
});

View File

@@ -13,7 +13,7 @@ describe('GET /api/v1/admin/roles/:roleId', () => {
let role, currentUser, token, permissionOne, permissionTwo;
beforeEach(async () => {
role = await createRole({ name: 'Admin' });
role = await createRole({ key: 'admin' });
permissionOne = await createPermission({ roleId: role.id });
permissionTwo = await createPermission({ roleId: role.id });
currentUser = await createUser({ roleId: role.id });

View File

@@ -11,8 +11,8 @@ describe('GET /api/v1/admin/roles', () => {
let roleOne, roleTwo, currentUser, token;
beforeEach(async () => {
roleOne = await createRole({ name: 'Admin' });
roleTwo = await createRole({ name: 'User' });
roleOne = await createRole({ key: 'admin' });
roleTwo = await createRole({ key: 'user' });
currentUser = await createUser({ roleId: roleOne.id });
token = await createAuthTokenByUserId(currentUser.id);

View File

@@ -1,24 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import Role from '../../../../../models/role.js';
export default async (request, response) => {
const role = await Role.query()
.findById(request.params.roleId)
.throwIfNotFound();
const updatedRoleWithPermissions = await role.updateWithPermissions(
roleParams(request)
);
renderObject(response, updatedRoleWithPermissions);
};
const roleParams = (request) => {
const { name, description, permissions } = request.body;
return {
name,
description,
permissions,
};
};

View File

@@ -1,177 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createPermission } from '../../../../../../test/factories/permission.js';
import { createUser } from '../../../../../../test/factories/user.js';
import updateRoleMock from '../../../../../../test/mocks/rest/api/v1/admin/roles/update-role.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('PATCH /api/v1/admin/roles/:roleId', () => {
let adminRole, viewerRole, currentUser, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ name: 'Admin' });
viewerRole = await createRole({ name: 'Viewer' });
await createPermission({
action: 'read',
subject: 'Connection',
});
await createPermission({
action: 'read',
subject: 'Flow',
});
currentUser = await createUser({ roleId: adminRole.id });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return the updated role along with permissions', async () => {
const roleData = {
name: 'Updated role name',
description: 'A new description',
permissions: [
{
action: 'read',
subject: 'Execution',
conditions: ['isCreator'],
},
],
};
const response = await request(app)
.patch(`/api/v1/admin/roles/${viewerRole.id}`)
.set('Authorization', token)
.send(roleData)
.expect(200);
const refetchedViewerRole = await viewerRole
.$query()
.withGraphFetched({ permissions: true });
const expectedPayload = await updateRoleMock(
{
...refetchedViewerRole,
...roleData,
isAdmin: false,
},
[
{
...refetchedViewerRole.permissions[0],
...roleData.permissions[0],
},
]
);
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return the updated role with sanitized permissions', async () => {
const validPermission = {
action: 'create',
subject: 'Connection',
conditions: ['isCreator'],
};
const invalidPermission = {
action: 'publish',
subject: 'Connection',
conditions: ['isCreator'],
};
const roleData = {
permissions: [validPermission, invalidPermission],
};
const response = await request(app)
.patch(`/api/v1/admin/roles/${viewerRole.id}`)
.set('Authorization', token)
.send(roleData)
.expect(200);
const refetchedViewerRole = await viewerRole.$query().withGraphFetched({
permissions: true,
});
const expectedPayload = updateRoleMock(refetchedViewerRole, [
{
...refetchedViewerRole.permissions[0],
...validPermission,
},
]);
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return not authorized response for updating admin role', async () => {
const roleData = {
name: 'Updated role name',
description: 'A new description',
permissions: [
{
action: 'read',
subject: 'Execution',
conditions: ['isCreator'],
},
],
};
await request(app)
.patch(`/api/v1/admin/roles/${adminRole.id}`)
.set('Authorization', token)
.send(roleData)
.expect(403);
});
it('should return unprocessable entity response for invalid role data', async () => {
const roleData = {
description: 123,
permissions: [],
};
const response = await request(app)
.patch(`/api/v1/admin/roles/${viewerRole.id}`)
.set('Authorization', token)
.send(roleData)
.expect(422);
expect(response.body).toStrictEqual({
errors: {
description: ['must be string,null'],
},
meta: {
type: 'ModelValidation',
},
});
});
it('should return unique violation response for duplicate role data', async () => {
await createRole({ name: 'Editor' });
const roleData = {
name: 'Editor',
permissions: [],
};
const response = await request(app)
.patch(`/api/v1/admin/roles/${viewerRole.id}`)
.set('Authorization', token)
.send(roleData)
.expect(422);
expect(response.body).toStrictEqual({
errors: {
name: ["'name' must be unique."],
},
meta: {
type: 'UniqueViolationError',
},
});
});
});

View File

@@ -1,43 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import SamlAuthProvider from '../../../../../models/saml-auth-provider.ee.js';
export default async (request, response) => {
const samlAuthProvider = await SamlAuthProvider.query().insert(
samlAuthProviderParams(request)
);
renderObject(response, samlAuthProvider, {
serializer: 'AdminSamlAuthProvider',
status: 201,
});
};
const samlAuthProviderParams = (request) => {
const {
name,
certificate,
signatureAlgorithm,
issuer,
entryPoint,
firstnameAttributeName,
surnameAttributeName,
emailAttributeName,
roleAttributeName,
defaultRoleId,
active,
} = request.body;
return {
name,
certificate,
signatureAlgorithm,
issuer,
entryPoint,
firstnameAttributeName,
surnameAttributeName,
emailAttributeName,
roleAttributeName,
defaultRoleId,
active,
};
};

View File

@@ -1,78 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import createSamlAuthProviderMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/create-saml-auth-provider.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('POST /api/v1/admin/saml-auth-provider', () => {
let currentUser, token, role;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
role = await createRole({ name: 'Admin' });
currentUser = await createUser({ roleId: role.id });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return the created saml auth provider', async () => {
const samlAuthProviderPayload = {
active: true,
name: 'Name',
issuer: 'theclientid',
certificate: 'dummycert',
entryPoint: 'http://localhost:8080/realms/automatisch/protocol/saml',
signatureAlgorithm: 'sha256',
defaultRoleId: role.id,
firstnameAttributeName: 'urn:oid:2.5.4.42',
surnameAttributeName: 'urn:oid:2.5.4.4',
emailAttributeName: 'urn:oid:1.2.840.113549.1.9.1',
roleAttributeName: 'Role',
};
const response = await request(app)
.post('/api/v1/admin/saml-auth-providers')
.set('Authorization', token)
.send(samlAuthProviderPayload)
.expect(201);
const expectedPayload = await createSamlAuthProviderMock({
id: response.body.data.id,
...samlAuthProviderPayload,
});
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return unprocessable entity response for invalid data', async () => {
const response = await request(app)
.post('/api/v1/admin/saml-auth-providers')
.set('Authorization', token)
.send({
active: true,
name: 'Name',
issuer: 'theclientid',
signatureAlgorithm: 'invalid',
firstnameAttributeName: 'urn:oid:2.5.4.42',
surnameAttributeName: 'urn:oid:2.5.4.4',
emailAttributeName: 'urn:oid:1.2.840.113549.1.9.1',
roleAttributeName: 123,
})
.expect(422);
expect(response.body).toStrictEqual({
errors: {
certificate: ["must have required property 'certificate'"],
entryPoint: ["must have required property 'entryPoint'"],
defaultRoleId: ["must have required property 'defaultRoleId'"],
signatureAlgorithm: ['must be equal to one of the allowed values'],
roleAttributeName: ['must be string'],
},
meta: { type: 'ModelValidation' },
});
});
});

View File

@@ -13,7 +13,7 @@ describe('GET /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mapping
let roleMappingOne, roleMappingTwo, samlAuthProvider, currentUser, token;
beforeEach(async () => {
const role = await createRole({ name: 'Admin' });
const role = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: role.id });
samlAuthProvider = await createSamlAuthProvider();

View File

@@ -13,7 +13,7 @@ describe('GET /api/v1/admin/saml-auth-provider/:samlAuthProviderId', () => {
let samlAuthProvider, currentUser, token;
beforeEach(async () => {
const role = await createRole({ name: 'Admin' });
const role = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: role.id });
samlAuthProvider = await createSamlAuthProvider();

View File

@@ -12,7 +12,7 @@ describe('GET /api/v1/admin/saml-auth-providers', () => {
let samlAuthProviderOne, samlAuthProviderTwo, currentUser, token;
beforeEach(async () => {
const role = await createRole({ name: 'Admin' });
const role = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: role.id });
samlAuthProviderOne = await createSamlAuthProvider();

View File

@@ -1,26 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import SamlAuthProvider from '../../../../../models/saml-auth-provider.ee.js';
export default async (request, response) => {
const samlAuthProviderId = request.params.samlAuthProviderId;
const samlAuthProvider = await SamlAuthProvider.query()
.findById(samlAuthProviderId)
.throwIfNotFound();
const samlAuthProvidersRoleMappings =
await samlAuthProvider.updateRoleMappings(
samlAuthProvidersRoleMappingsParams(request)
);
renderObject(response, samlAuthProvidersRoleMappings);
};
const samlAuthProvidersRoleMappingsParams = (request) => {
const roleMappings = request.body;
return roleMappings.map(({ roleId, remoteRoleName }) => ({
roleId,
remoteRoleName,
}));
};

View File

@@ -1,182 +0,0 @@
import Crypto from 'node:crypto';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createSamlAuthProvider } from '../../../../../../test/factories/saml-auth-provider.ee.js';
import { createSamlAuthProvidersRoleMapping } from '../../../../../../test/factories/saml-auth-providers-role-mapping.js';
import createRoleMappingsMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/update-role-mappings.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('PATCH /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mappings', () => {
let samlAuthProvider, currentUser, userRole, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
userRole = await createRole({ name: 'Admin' });
currentUser = await createUser({ roleId: userRole.id });
samlAuthProvider = await createSamlAuthProvider();
await createSamlAuthProvidersRoleMapping({
samlAuthProviderId: samlAuthProvider.id,
remoteRoleName: 'Viewer',
});
await createSamlAuthProvidersRoleMapping({
samlAuthProviderId: samlAuthProvider.id,
remoteRoleName: 'Editor',
});
token = await createAuthTokenByUserId(currentUser.id);
});
it('should update role mappings', async () => {
const roleMappings = [
{
roleId: userRole.id,
remoteRoleName: 'Admin',
},
];
const response = await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
)
.set('Authorization', token)
.send(roleMappings)
.expect(200);
const expectedPayload = await createRoleMappingsMock([
{
roleId: userRole.id,
remoteRoleName: 'Admin',
id: response.body.data[0].id,
samlAuthProviderId: samlAuthProvider.id,
},
]);
expect(response.body).toStrictEqual(expectedPayload);
});
it('should delete role mappings when given empty role mappings', async () => {
const existingRoleMappings = await samlAuthProvider.$relatedQuery(
'samlAuthProvidersRoleMappings'
);
expect(existingRoleMappings.length).toBe(2);
const response = await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
)
.set('Authorization', token)
.send([])
.expect(200);
const expectedPayload = await createRoleMappingsMock([]);
expect(response.body).toStrictEqual({
...expectedPayload,
meta: {
...expectedPayload.meta,
type: 'Object',
},
});
});
it('should return internal server error response for not existing role UUID', async () => {
const notExistingRoleUUID = Crypto.randomUUID();
const roleMappings = [
{
roleId: notExistingRoleUUID,
remoteRoleName: 'Admin',
},
];
await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
)
.set('Authorization', token)
.send(roleMappings)
.expect(500);
});
it('should return unprocessable entity response for invalid data', async () => {
const roleMappings = [
{
roleId: userRole.id,
remoteRoleName: {},
},
];
const response = await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
)
.set('Authorization', token)
.send(roleMappings)
.expect(422);
expect(response.body).toStrictEqual({
errors: {
remoteRoleName: ['must be string'],
},
meta: {
type: 'ModelValidation',
},
});
});
it('should return not found response for not existing SAML auth provider UUID', async () => {
const notExistingSamlAuthProviderUUID = Crypto.randomUUID();
const roleMappings = [
{
roleId: userRole.id,
remoteRoleName: 'Admin',
},
];
await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${notExistingSamlAuthProviderUUID}/role-mappings`
)
.set('Authorization', token)
.send(roleMappings)
.expect(404);
});
it('should not delete existing role mapping when error thrown', async () => {
const roleMappings = [
{
roleId: userRole.id,
remoteRoleName: {
invalid: 'data',
},
},
];
const roleMappingsBeforeRequest = await samlAuthProvider.$relatedQuery(
'samlAuthProvidersRoleMappings'
);
await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
)
.set('Authorization', token)
.send(roleMappings)
.expect(422);
const roleMappingsAfterRequest = await samlAuthProvider.$relatedQuery(
'samlAuthProvidersRoleMappings'
);
expect(roleMappingsBeforeRequest).toStrictEqual(roleMappingsAfterRequest);
expect(roleMappingsAfterRequest.length).toBe(2);
});
});

View File

@@ -1,45 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import SamlAuthProvider from '../../../../../models/saml-auth-provider.ee.js';
export default async (request, response) => {
const samlAuthProvider = await SamlAuthProvider.query()
.patchAndFetchById(
request.params.samlAuthProviderId,
samlAuthProviderParams(request)
)
.throwIfNotFound();
renderObject(response, samlAuthProvider, {
serializer: 'AdminSamlAuthProvider',
});
};
const samlAuthProviderParams = (request) => {
const {
name,
certificate,
signatureAlgorithm,
issuer,
entryPoint,
firstnameAttributeName,
surnameAttributeName,
emailAttributeName,
roleAttributeName,
defaultRoleId,
active,
} = request.body;
return {
name,
certificate,
signatureAlgorithm,
issuer,
entryPoint,
firstnameAttributeName,
surnameAttributeName,
emailAttributeName,
roleAttributeName,
defaultRoleId,
active,
};
};

View File

@@ -1,119 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createSamlAuthProvider } from '../../../../../../test/factories/saml-auth-provider.ee.js';
import createSamlAuthProviderMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/create-saml-auth-provider.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('PATCH /api/v1/admin/saml-auth-provider/:samlAuthProviderId', () => {
let currentUser, token, role;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
role = await createRole({ name: 'Admin' });
currentUser = await createUser({ roleId: role.id });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return the updated saml auth provider', async () => {
const samlAuthProviderPayload = {
active: true,
name: 'Name',
issuer: 'theclientid',
certificate: 'dummycert',
entryPoint: 'http://localhost:8080/realms/automatisch/protocol/saml',
signatureAlgorithm: 'sha256',
defaultRoleId: role.id,
firstnameAttributeName: 'urn:oid:2.5.4.42',
surnameAttributeName: 'urn:oid:2.5.4.4',
emailAttributeName: 'urn:oid:1.2.840.113549.1.9.1',
roleAttributeName: 'Role',
};
const samlAuthProvider = await createSamlAuthProvider(
samlAuthProviderPayload
);
const response = await request(app)
.patch(`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}`)
.set('Authorization', token)
.send({
active: false,
name: 'Archived',
})
.expect(200);
const refetchedSamlAuthProvider = await samlAuthProvider.$query();
const expectedPayload = await createSamlAuthProviderMock({
...refetchedSamlAuthProvider,
name: 'Archived',
active: false,
});
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return unprocessable entity response for invalid data', async () => {
const samlAuthProviderPayload = {
active: true,
name: 'Name',
issuer: 'theclientid',
certificate: 'dummycert',
entryPoint: 'http://localhost:8080/realms/automatisch/protocol/saml',
signatureAlgorithm: 'sha256',
defaultRoleId: role.id,
firstnameAttributeName: 'urn:oid:2.5.4.42',
surnameAttributeName: 'urn:oid:2.5.4.4',
emailAttributeName: 'urn:oid:1.2.840.113549.1.9.1',
roleAttributeName: 'Role',
};
const samlAuthProvider = await createSamlAuthProvider(
samlAuthProviderPayload
);
const response = await request(app)
.patch(`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}`)
.set('Authorization', token)
.send({
active: 'true',
name: 123,
roleAttributeName: 123,
})
.expect(422);
expect(response.body).toStrictEqual({
errors: {
name: ['must be string'],
active: ['must be boolean'],
roleAttributeName: ['must be string'],
},
meta: { type: 'ModelValidation' },
});
});
it('should return not found response for not existing SAML auth provider UUID', async () => {
const notExistingSamlAuthProviderUUID = Crypto.randomUUID();
await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${notExistingSamlAuthProviderUUID}`
)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.patch('/api/v1/admin/saml-auth-providers/invalidSamlAuthProviderUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,22 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import User from '../../../../../models/user.js';
import Role from '../../../../../models/role.js';
export default async (request, response) => {
const user = await User.query().insertAndFetch(await userParams(request));
await user.sendInvitationEmail();
renderObject(response, user, { status: 201, serializer: 'AdminUser' });
};
const userParams = async (request) => {
const { fullName, email } = request.body;
const roleId = request.body.roleId || (await Role.findAdmin()).id;
return {
fullName,
status: 'invited',
email: email?.toLowerCase(),
roleId,
};
};

View File

@@ -1,122 +0,0 @@
import { describe, beforeEach, it, expect } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import User from '../../../../../models/user.js';
import Role from '../../../../../models/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createRole } from '../../../../../../test/factories/role.js';
import createUserMock from '../../../../../../test/mocks/rest/api/v1/admin/users/create-user.js';
describe('POST /api/v1/admin/users', () => {
let currentUser, adminRole, token;
beforeEach(async () => {
adminRole = await createRole({ name: 'Admin' });
currentUser = await createUser({ roleId: adminRole.id });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return created user with valid data', async () => {
const userRole = await createRole({ name: 'User' });
const userData = {
email: 'created@sample.com',
fullName: 'Full Name',
password: 'samplePassword123',
roleId: userRole.id,
};
const response = await request(app)
.post('/api/v1/admin/users')
.set('Authorization', token)
.send(userData)
.expect(201);
const refetchedRegisteredUser = await User.query()
.findById(response.body.data.id)
.throwIfNotFound();
const expectedPayload = createUserMock(refetchedRegisteredUser);
expect(response.body).toStrictEqual(expectedPayload);
expect(refetchedRegisteredUser.roleId).toStrictEqual(userRole.id);
});
it('should create user with admin role if there is no role id given', async () => {
const userData = {
email: 'created@sample.com',
fullName: 'Full Name',
password: 'samplePassword123',
};
const response = await request(app)
.post('/api/v1/admin/users')
.set('Authorization', token)
.send(userData)
.expect(201);
const refetchedRegisteredUser = await User.query()
.findById(response.body.data.id)
.throwIfNotFound();
const refetchedUserRole = await Role.query().findById(
refetchedRegisteredUser.roleId
);
const expectedPayload = createUserMock(refetchedRegisteredUser);
expect(response.body).toStrictEqual(expectedPayload);
expect(refetchedUserRole.name).toStrictEqual('Admin');
});
it('should return unprocessable entity response with already used email', async () => {
await createRole({ name: 'User' });
await createUser({
email: 'created@sample.com',
});
const userData = {
email: 'created@sample.com',
fullName: 'Full Name',
password: 'samplePassword123',
};
const response = await request(app)
.post('/api/v1/admin/users')
.set('Authorization', token)
.send(userData)
.expect(422);
expect(response.body.errors).toStrictEqual({
email: ["'email' must be unique."],
});
expect(response.body.meta).toStrictEqual({
type: 'UniqueViolationError',
});
});
it('should return unprocessable entity response with invalid user data', async () => {
await createRole({ name: 'User' });
const userData = {
email: null,
fullName: null,
};
const response = await request(app)
.post('/api/v1/admin/users')
.set('Authorization', token)
.send(userData)
.expect(422);
expect(response.body.meta.type).toStrictEqual('ModelValidation');
expect(response.body.errors).toStrictEqual({
email: ["must have required property 'email'"],
fullName: ['must be string'],
});
});
});

View File

@@ -10,7 +10,7 @@ describe('DELETE /api/v1/admin/users/:userId', () => {
let currentUser, currentUserRole, anotherUser, token;
beforeEach(async () => {
currentUserRole = await createRole({ name: 'Admin' });
currentUserRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: currentUserRole.id });
anotherUser = await createUser();

View File

@@ -12,7 +12,7 @@ describe('GET /api/v1/admin/users/:userId', () => {
let currentUser, currentUserRole, anotherUser, anotherUserRole, token;
beforeEach(async () => {
currentUserRole = await createRole({ name: 'Admin' });
currentUserRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: currentUserRole.id });
anotherUser = await createUser();

View File

@@ -10,7 +10,7 @@ describe('GET /api/v1/admin/users', () => {
let currentUser, currentUserRole, anotherUser, anotherUserRole, token;
beforeEach(async () => {
currentUserRole = await createRole({ name: 'Admin' });
currentUserRole = await createRole({ key: 'admin' });
currentUser = await createUser({
roleId: currentUserRole.id,
@@ -18,6 +18,7 @@ describe('GET /api/v1/admin/users', () => {
});
anotherUserRole = await createRole({
key: 'anotherUser',
name: 'Another user role',
});

View File

@@ -1,18 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import User from '../../../../../models/user.js';
export default async (request, response) => {
const user = await User.query()
.withGraphFetched({
role: true,
})
.patchAndFetchById(request.params.userId, userParams(request))
.throwIfNotFound();
renderObject(response, user);
};
const userParams = (request) => {
const { email, fullName, roleId } = request.body;
return { email, fullName, roleId };
};

View File

@@ -1,87 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createRole } from '../../../../../../test/factories/role.js';
import updateUserMock from '../../../../../../test/mocks/rest/api/v1/admin/users/update-user.js';
describe('PATCH /api/v1/admin/users/:userId', () => {
let currentUser, adminRole, token;
beforeEach(async () => {
adminRole = await createRole({ name: 'Admin' });
currentUser = await createUser({ roleId: adminRole.id });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return updated user with valid data for another user', async () => {
const anotherUser = await createUser();
const anotherRole = await createRole();
const anotherUserUpdatedData = {
email: 'updated@sample.com',
fullName: 'Updated Full Name',
roleId: anotherRole.id,
};
const response = await request(app)
.patch(`/api/v1/admin/users/${anotherUser.id}`)
.set('Authorization', token)
.send(anotherUserUpdatedData)
.expect(200);
const refetchedAnotherUser = await anotherUser.$query();
const expectedPayload = updateUserMock(
{
...refetchedAnotherUser,
...anotherUserUpdatedData,
},
anotherRole
);
expect(response.body).toMatchObject(expectedPayload);
});
it('should return HTTP 422 with invalid user data', async () => {
const anotherUser = await createUser();
const anotherUserUpdatedData = {
email: null,
fullName: null,
roleId: null,
};
const response = await request(app)
.patch(`/api/v1/admin/users/${anotherUser.id}`)
.set('Authorization', token)
.send(anotherUserUpdatedData)
.expect(422);
expect(response.body.meta.type).toEqual('ModelValidation');
expect(response.body.errors).toMatchObject({
email: ['must be string'],
fullName: ['must be string'],
roleId: ['must be string'],
});
});
it('should return not found response for not existing user UUID', async () => {
const notExistingUserUUID = Crypto.randomUUID();
await request(app)
.patch(`/api/v1/admin/users/${notExistingUserUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.patch('/api/v1/admin/users/invalidUserUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,27 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const connection = await request.currentUser
.$relatedQuery('connections')
.insertAndFetch(connectionParams(request));
const connectionWithAppConfigAndAuthClient = await connection
.$query()
.withGraphFetched({
appConfig: true,
appAuthClient: true,
});
renderObject(response, connectionWithAppConfigAndAuthClient, { status: 201 });
};
const connectionParams = (request) => {
const { appAuthClientId, formattedData } = request.body;
return {
key: request.params.appKey,
appAuthClientId,
formattedData,
verified: false,
};
};

View File

@@ -1,405 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createAppConfig } from '../../../../../test/factories/app-config.js';
import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createPermission } from '../../../../../test/factories/permission.js';
import { createRole } from '../../../../../test/factories/role.js';
import createConnection from '../../../../../test/mocks/rest/api/v1/apps/create-connection.js';
describe('POST /api/v1/apps/:appKey/connections', () => {
let currentUser, token;
beforeEach(async () => {
const role = await createRole();
await createPermission({
action: 'read',
subject: 'Connection',
roleId: role.id,
});
await createPermission({
action: 'create',
subject: 'Connection',
roleId: role.id,
});
currentUser = await createUser({ roleId: role.id });
currentUser = await currentUser
.$query()
.leftJoinRelated({
role: true,
permissions: true,
})
.withGraphFetched({
role: true,
permissions: true,
});
token = await createAuthTokenByUserId(currentUser.id);
});
describe('with no app config', async () => {
it('should return created connection', async () => {
const connectionData = {
formattedData: {
oAuthRedirectUrl: 'http://localhost:3000/app/gitlab/connections/add',
instanceUrl: 'https://gitlab.com',
clientId: 'sample_client_id',
clientSecret: 'sample_client_secret',
},
};
const response = await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send(connectionData)
.expect(201);
const fetchedConnection =
await currentUser.authorizedConnections.findById(response.body.data.id);
const expectedPayload = createConnection({
...fetchedConnection,
formattedData: {},
});
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.post('/api/v1/apps/invalid-app-key/connections')
.set('Authorization', token)
.expect(404);
});
it('should return unprocesible entity response for invalid connection data', async () => {
const response = await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send({
formattedData: 123,
})
.expect(422);
expect(response.body).toStrictEqual({
errors: {
formattedData: ['must be object'],
},
meta: {
type: 'ModelValidation',
},
});
});
});
describe('with app disabled', async () => {
beforeEach(async () => {
await createAppConfig({
key: 'gitlab',
disabled: true,
});
});
it('should return with not authorized response', async () => {
const connectionData = {
formattedData: {
oAuthRedirectUrl: 'http://localhost:3000/app/gitlab/connections/add',
instanceUrl: 'https://gitlab.com',
clientId: 'sample_client_id',
clientSecret: 'sample_client_secret',
},
};
await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send(connectionData)
.expect(403);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.post('/api/v1/apps/invalid-app-key/connections')
.set('Authorization', token)
.expect(404);
});
it('should return unprocesible entity response for invalid connection data', async () => {
const response = await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send({
formattedData: 123,
})
.expect(422);
expect(response.body).toStrictEqual({
errors: {
formattedData: ['must be object'],
},
meta: {
type: 'ModelValidation',
},
});
});
});
describe('with custom connections enabled', async () => {
beforeEach(async () => {
await createAppConfig({
key: 'gitlab',
disabled: false,
allowCustomConnection: true,
});
});
it('should return created conncetion', async () => {
const connectionData = {
formattedData: {
oAuthRedirectUrl: 'http://localhost:3000/app/gitlab/connections/add',
instanceUrl: 'https://gitlab.com',
clientId: 'sample_client_id',
clientSecret: 'sample_client_secret',
},
};
const response = await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send(connectionData)
.expect(201);
const fetchedConnection =
await currentUser.authorizedConnections.findById(response.body.data.id);
const expectedPayload = createConnection({
...fetchedConnection,
formattedData: {},
});
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.post('/api/v1/apps/invalid-app-key/connections')
.set('Authorization', token)
.expect(404);
});
it('should return unprocesible entity response for invalid connection data', async () => {
const response = await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send({
formattedData: 123,
})
.expect(422);
expect(response.body).toStrictEqual({
errors: {
formattedData: ['must be object'],
},
meta: {
type: 'ModelValidation',
},
});
});
});
describe('with custom connections disabled', async () => {
beforeEach(async () => {
await createAppConfig({
key: 'gitlab',
disabled: false,
allowCustomConnection: false,
});
});
it('should return with not authorized response', async () => {
const connectionData = {
formattedData: {
oAuthRedirectUrl: 'http://localhost:3000/app/gitlab/connections/add',
instanceUrl: 'https://gitlab.com',
clientId: 'sample_client_id',
clientSecret: 'sample_client_secret',
},
};
await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send(connectionData)
.expect(403);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.post('/api/v1/apps/invalid-app-key/connections')
.set('Authorization', token)
.expect(404);
});
it('should return unprocesible entity response for invalid connection data', async () => {
const response = await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send({
formattedData: 123,
})
.expect(422);
expect(response.body).toStrictEqual({
errors: {
formattedData: ['must be object'],
},
meta: {
type: 'ModelValidation',
},
});
});
});
describe('with auth clients enabled', async () => {
let appAuthClient;
beforeEach(async () => {
await createAppConfig({
key: 'gitlab',
disabled: false,
shared: true,
});
appAuthClient = await createAppAuthClient({
appKey: 'gitlab',
active: true,
formattedAuthDefaults: {
oAuthRedirectUrl: 'http://localhost:3000/app/gitlab/connections/add',
instanceUrl: 'https://gitlab.com',
clientId: 'sample_client_id',
clientSecret: 'sample_client_secret',
},
});
});
it('should return created connection', async () => {
const connectionData = {
appAuthClientId: appAuthClient.id,
};
const response = await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send(connectionData)
.expect(201);
const fetchedConnection =
await currentUser.authorizedConnections.findById(response.body.data.id);
const expectedPayload = createConnection({
...fetchedConnection,
formattedData: {},
});
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return not authorized response for appAuthClientId and formattedData together', async () => {
const connectionData = {
appAuthClientId: appAuthClient.id,
formattedData: {},
};
await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send(connectionData)
.expect(403);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.post('/api/v1/apps/invalid-app-key/connections')
.set('Authorization', token)
.expect(404);
});
it('should return unprocesible entity response for invalid connection data', async () => {
const response = await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send({
formattedData: 123,
})
.expect(422);
expect(response.body).toStrictEqual({
errors: {
formattedData: ['must be object'],
},
meta: {
type: 'ModelValidation',
},
});
});
});
describe('with auth clients disabled', async () => {
let appAuthClient;
beforeEach(async () => {
await createAppConfig({
key: 'gitlab',
disabled: false,
shared: false,
});
appAuthClient = await createAppAuthClient({
appKey: 'gitlab',
});
});
it('should return with not authorized response', async () => {
const connectionData = {
appAuthClientId: appAuthClient.id,
};
await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send(connectionData)
.expect(403);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.post('/api/v1/apps/invalid-app-key/connections')
.set('Authorization', token)
.expect(404);
});
it('should return unprocesible entity response for invalid connection data', async () => {
const response = await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send({
formattedData: 123,
})
.expect(422);
expect(response.body).toStrictEqual({
errors: {
formattedData: ['must be object'],
},
meta: {
type: 'ModelValidation',
},
});
});
});
});

View File

@@ -1,11 +0,0 @@
export default async (request, response) => {
await request.currentUser
.$relatedQuery('connections')
.delete()
.findOne({
id: request.params.connectionId,
})
.throwIfNotFound();
response.status(204).end();
};

View File

@@ -1,77 +0,0 @@
import { describe, it, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createConnection } from '../../../../../test/factories/connection.js';
import { createPermission } from '../../../../../test/factories/permission.js';
describe('DELETE /api/v1/connections/:connectionId', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
await createPermission({
action: 'delete',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
token = await createAuthTokenByUserId(currentUser.id);
});
it('should delete the connection for current user', async () => {
const currentUserConnection = await createConnection({
userId: currentUser.id,
key: 'deepl',
verified: true,
});
await request(app)
.delete(`/api/v1/connections/${currentUserConnection.id}`)
.set('Authorization', token)
.expect(204);
});
it(`should return not found for other users' connections`, async () => {
const anotherUser = await createUser();
const anotherUserConnection = await createConnection({
userId: anotherUser.id,
key: 'deepl',
verified: true,
});
await request(app)
.post(`/api/v1/connections/${anotherUserConnection.id}`)
.set('Authorization', token)
.expect(404);
});
it('should return not found response for not existing connection UUID', async () => {
const notExistingConnectionUUID = Crypto.randomUUID();
await request(app)
.delete(`/api/v1/connections/${notExistingConnectionUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.delete('/api/v1/connections/invalidConnectionUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,14 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
let connection = await request.currentUser
.$relatedQuery('connections')
.findOne({
id: request.params.connectionId,
})
.throwIfNotFound();
connection = await connection.generateAuthUrl();
renderObject(response, connection);
};

View File

@@ -1,90 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createConnection } from '../../../../../test/factories/connection.js';
import { createPermission } from '../../../../../test/factories/permission.js';
describe('POST /api/v1/connections/:connectionId/auth-url', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
await createPermission({
action: 'create',
subject: 'Connection',
roleId: currentUser.roleId,
conditions: ['isCreator'],
});
token = await createAuthTokenByUserId(currentUser.id);
});
it('should generate auth url for the connection', async () => {
const connection = await createConnection({
userId: currentUser.id,
key: 'gitlab',
formattedData: {
clientId: 'CLIENT_ID',
oAuthRedirectUrl: 'http://localhost:3001/app/gitlab/connections/add',
},
verified: false,
});
const response = await request(app)
.post(`/api/v1/connections/${connection.id}/auth-url`)
.set('Authorization', token)
.expect(200);
expect(response.body.data).toStrictEqual({
url: expect.stringContaining('https://gitlab.com/oauth/authorize?'),
});
expect(response.body.data).toStrictEqual({
url: expect.stringContaining('client_id=CLIENT_ID'),
});
expect(response.body.data).toStrictEqual({
url: expect.stringContaining(
`redirect_uri=${encodeURIComponent(
'http://localhost:3001/app/gitlab/connections/add'
)}`
),
});
});
it(`should return internal server error response for invalid connection data`, async () => {
const connection = await createConnection({
userId: currentUser.id,
key: 'gitlab',
formattedData: {
instanceUrl: 123,
},
verified: false,
});
await request(app)
.post(`/api/v1/connections/${connection.id}/auth-url`)
.set('Authorization', token)
.expect(500);
});
it('should return not found response for not existing connection UUID', async () => {
const notExistingConnectionUUID = Crypto.randomUUID();
await request(app)
.post(`/api/v1/connections/${notExistingConnectionUUID}/auth-url`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.post('/api/v1/connections/invalidConnectionUUID/auth-url')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,14 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
let connection = await request.currentUser
.$relatedQuery('connections')
.findOne({
id: request.params.connectionId,
})
.throwIfNotFound();
connection = await connection.reset();
renderObject(response, connection);
};

View File

@@ -1,113 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createConnection } from '../../../../../test/factories/connection.js';
import { createPermission } from '../../../../../test/factories/permission.js';
import resetConnectionMock from '../../../../../test/mocks/rest/api/v1/connections/reset-connection.js';
describe('POST /api/v1/connections/:connectionId/reset', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = await createAuthTokenByUserId(currentUser.id);
});
it(`should reset the connection's formatted data`, async () => {
const currentUserConnection = await createConnection({
userId: currentUser.id,
key: 'deepl',
verified: true,
formattedData: {
screenName: 'Connection name',
clientSecret: 'secret',
clientId: 'id',
token: 'token',
},
});
await createPermission({
action: 'create',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.post(`/api/v1/connections/${currentUserConnection.id}/reset`)
.set('Authorization', token)
.expect(200);
const refetchedCurrentUserConnection = await currentUserConnection.$query();
const expectedPayload = resetConnectionMock({
...refetchedCurrentUserConnection,
reconnectable: refetchedCurrentUserConnection.reconnectable,
formattedData: {
screenName: 'Connection name',
},
});
expect(response.body).toStrictEqual(expectedPayload);
expect(refetchedCurrentUserConnection.formattedData).toStrictEqual(
expectedPayload.data.formattedData
);
});
it('should return not found response for another user', async () => {
const anotherUser = await createUser();
const anotherUserConnection = await createConnection({
userId: anotherUser.id,
key: 'deepl',
verified: true,
});
await createPermission({
action: 'create',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.post(`/api/v1/connections/${anotherUserConnection.id}/reset`)
.set('Authorization', token)
.expect(404);
});
it('should return not found response for not existing connection UUID', async () => {
const notExistingConnectionUUID = Crypto.randomUUID();
await createPermission({
action: 'create',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.post(`/api/v1/connections/${notExistingConnectionUUID}/reset`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'create',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.post('/api/v1/connections/invalidConnectionUUID/reset')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,19 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
let connection = await request.currentUser
.$relatedQuery('connections')
.findOne({
id: request.params.connectionId,
})
.throwIfNotFound();
connection = await connection.update(connectionParams(request));
renderObject(response, connection);
};
const connectionParams = (request) => {
const { formattedData, appAuthClientId } = request.body;
return { formattedData, appAuthClientId };
};

View File

@@ -1,117 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createConnection } from '../../../../../test/factories/connection.js';
import { createPermission } from '../../../../../test/factories/permission.js';
import updateConnectionMock from '../../../../../test/mocks/rest/api/v1/connections/update-connection.js';
describe('PATCH /api/v1/connections/:connectionId', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = await createAuthTokenByUserId(currentUser.id);
});
it('should update the connection with valid data for current user', async () => {
const connectionData = {
userId: currentUser.id,
key: 'deepl',
verified: true,
formattedData: {
screenName: 'Connection name',
clientSecret: 'secret',
clientId: 'id',
token: 'token',
},
};
const currentUserConnection = await createConnection(connectionData);
await createPermission({
action: 'update',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.patch(`/api/v1/connections/${currentUserConnection.id}`)
.set('Authorization', token)
.send({
formattedData: {
screenName: 'New connection name',
clientSecret: 'new secret',
clientId: 'new id',
token: 'new token',
},
})
.expect(200);
const refetchedCurrentUserConnection = await currentUserConnection.$query();
const expectedPayload = updateConnectionMock({
...refetchedCurrentUserConnection,
reconnectable: refetchedCurrentUserConnection.reconnectable,
});
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return not found response for another user', async () => {
const anotherUser = await createUser();
const anotherUserConnection = await createConnection({
userId: anotherUser.id,
key: 'deepl',
verified: true,
});
await createPermission({
action: 'update',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.patch(`/api/v1/connections/${anotherUserConnection.id}`)
.set('Authorization', token)
.expect(404);
});
it('should return not found response for not existing connection UUID', async () => {
const notExistingConnectionUUID = Crypto.randomUUID();
await createPermission({
action: 'update',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.patch(`/api/v1/connections/${notExistingConnectionUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'update',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.patch('/api/v1/connections/invalidConnectionUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,14 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
let connection = await request.currentUser
.$relatedQuery('connections')
.findOne({
id: request.params.connectionId,
})
.throwIfNotFound();
connection = await connection.verifyAndUpdateConnection();
renderObject(response, connection);
};

View File

@@ -1,82 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import App from '../../../../models/app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createConnection } from '../../../../../test/factories/connection.js';
import { createPermission } from '../../../../../test/factories/permission.js';
describe('POST /api/v1/connections/:connectionId/verify', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = await createAuthTokenByUserId(currentUser.id);
});
it('should update the connection as verified for current user', async () => {
const currentUserConnection = await createConnection({
userId: currentUser.id,
key: 'deepl',
verified: true,
});
await createPermission({
action: 'create',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
vi.spyOn(App, 'findOneByKey').mockImplementation((key) => {
if (key !== currentUserConnection.key) return;
return {
auth: {
verifyCredentials: vi.fn().mockResolvedValue(),
},
};
});
const response = await request(app)
.post(`/api/v1/connections/${currentUserConnection.id}/verify`)
.set('Authorization', token)
.expect(200);
expect(response.body.data.verified).toEqual(true);
});
it('should return not found response for not existing connection UUID', async () => {
const notExistingConnectionUUID = Crypto.randomUUID();
await createPermission({
action: 'create',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.post(`/api/v1/connections/${notExistingConnectionUUID}/verify`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'create',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.post('/api/v1/connections/invalidConnectionUUID/verify')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,11 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
let flow = await request.currentUser.$relatedQuery('flows').insert({
name: 'Name your flow',
});
flow = await flow.createInitialSteps();
renderObject(response, flow, { status: 201 });
};

View File

@@ -1,41 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import createFlowMock from '../../../../../test/mocks/rest/api/v1/flows/create-flow.js';
import { createPermission } from '../../../../../test/factories/permission.js';
describe('POST /api/v1/flows', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return created flow', async () => {
await createPermission({
action: 'create',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.post('/api/v1/flows')
.set('Authorization', token)
.expect(201);
const refetchedFlow = await currentUser
.$relatedQuery('flows')
.findById(response.body.data.id);
const expectedPayload = await createFlowMock(refetchedFlow);
expect(response.body).toMatchObject(expectedPayload);
});
});

View File

@@ -1,14 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const flow = await request.currentUser.authorizedFlows
.clone()
.findById(request.params.flowId)
.throwIfNotFound();
const createdActionStep = await flow.createActionStep(
request.body.previousStepId
);
renderObject(response, createdActionStep, { status: 201 });
};

View File

@@ -1,176 +0,0 @@
import Crypto from 'node:crypto';
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import createStepMock from '../../../../../test/mocks/rest/api/v1/flows/create-step.js';
import { createPermission } from '../../../../../test/factories/permission.js';
describe('POST /api/v1/flows/:flowId/steps', () => {
let currentUser, flow, triggerStep, token;
beforeEach(async () => {
currentUser = await createUser();
flow = await createFlow({ userId: currentUser.id });
triggerStep = await createStep({ flowId: flow.id, type: 'trigger' });
await createStep({ flowId: flow.id, type: 'action' });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return created step for current user', async () => {
await createPermission({
roleId: currentUser.roleId,
subject: 'Flow',
action: 'read',
conditions: ['isCreator'],
});
await createPermission({
roleId: currentUser.roleId,
subject: 'Flow',
action: 'update',
conditions: ['isCreator'],
});
const response = await request(app)
.post(`/api/v1/flows/${flow.id}/steps`)
.set('Authorization', token)
.send({
previousStepId: triggerStep.id,
})
.expect(201);
const expectedPayload = await createStepMock({
id: response.body.data.id,
position: 2,
});
expect(response.body).toMatchObject(expectedPayload);
});
it('should return created step for another user', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const anotherUserFlowTriggerStep = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
});
await createStep({ flowId: anotherUserFlow.id, type: 'action' });
await createPermission({
roleId: currentUser.roleId,
subject: 'Flow',
action: 'read',
conditions: [],
});
await createPermission({
roleId: currentUser.roleId,
subject: 'Flow',
action: 'update',
conditions: [],
});
const response = await request(app)
.post(`/api/v1/flows/${anotherUserFlow.id}/steps`)
.set('Authorization', token)
.send({
previousStepId: anotherUserFlowTriggerStep.id,
})
.expect(201);
const expectedPayload = await createStepMock({
id: response.body.data.id,
position: 2,
});
expect(response.body).toMatchObject(expectedPayload);
});
it('should return bad request response for invalid flow UUID', async () => {
await createPermission({
roleId: currentUser.roleId,
subject: 'Flow',
action: 'read',
conditions: ['isCreator'],
});
await createPermission({
roleId: currentUser.roleId,
subject: 'Flow',
action: 'update',
conditions: ['isCreator'],
});
await request(app)
.post('/api/v1/flows/invalidFlowUUID/steps')
.set('Authorization', token)
.send({
previousStepId: triggerStep.id,
})
.expect(400);
});
it('should return not found response for invalid flow UUID', async () => {
await createPermission({
roleId: currentUser.roleId,
subject: 'Flow',
action: 'read',
conditions: ['isCreator'],
});
await createPermission({
roleId: currentUser.roleId,
subject: 'Flow',
action: 'update',
conditions: ['isCreator'],
});
const notExistingFlowUUID = Crypto.randomUUID();
await request(app)
.post(`/api/v1/flows/${notExistingFlowUUID}/steps`)
.set('Authorization', token)
.send({
previousStepId: triggerStep.id,
})
.expect(404);
});
it('should return not found response for invalid flow UUID', async () => {
await createPermission({
roleId: currentUser.roleId,
subject: 'Flow',
action: 'read',
conditions: ['isCreator'],
});
await createPermission({
roleId: currentUser.roleId,
subject: 'Flow',
action: 'update',
conditions: ['isCreator'],
});
const notExistingStepUUID = Crypto.randomUUID();
await request(app)
.post(`/api/v1/flows/${flow.id}/steps`)
.set('Authorization', token)
.send({
previousStepId: notExistingStepUUID,
})
.expect(404);
});
});

View File

@@ -1,10 +0,0 @@
export default async (request, response) => {
const flow = await request.currentUser.authorizedFlows
.clone()
.findById(request.params.flowId)
.throwIfNotFound();
await flow.delete();
response.status(204).end();
};

View File

@@ -1,110 +0,0 @@
import { describe, it, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createPermission } from '../../../../../test/factories/permission.js';
describe('DELETE /api/v1/flows/:flowId', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = await createAuthTokenByUserId(currentUser.id);
});
it('should remove the current user flow and return no content', async () => {
const currentUserFlow = await createFlow({ userId: currentUser.id });
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'delete',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.delete(`/api/v1/flows/${currentUserFlow.id}`)
.set('Authorization', token)
.expect(204);
});
it('should remove another user flow and return no content', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'delete',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.delete(`/api/v1/flows/${anotherUserFlow.id}`)
.set('Authorization', token)
.expect(204);
});
it('should return not found response for not existing flow UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'delete',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const notExistingFlowUUID = Crypto.randomUUID();
await request(app)
.delete(`/api/v1/flows/${notExistingFlowUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'delete',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.delete('/api/v1/flows/invalidFlowUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,11 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const flow = await request.currentUser.authorizedFlows
.findById(request.params.flowId)
.throwIfNotFound();
const duplicatedFlow = await flow.duplicateFor(request.currentUser);
renderObject(response, duplicatedFlow, { status: 201 });
};

View File

@@ -1,204 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createPermission } from '../../../../../test/factories/permission.js';
import duplicateFlowMock from '../../../../../test/mocks/rest/api/v1/flows/duplicate-flow.js';
describe('POST /api/v1/flows/:flowId/duplicate', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return duplicated flow data of current user', async () => {
const currentUserFlow = await createFlow({ userId: currentUser.id });
const triggerStep = await createStep({
flowId: currentUserFlow.id,
type: 'trigger',
appKey: 'webhook',
key: 'catchRawWebhook',
});
await createStep({
flowId: currentUserFlow.id,
type: 'action',
appKey: 'ntfy',
key: 'sendMessage',
parameters: {
topic: 'Test notification',
message: `Message: {{step.${triggerStep.id}.body.message}} by {{step.${triggerStep.id}.body.sender}}`,
},
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'create',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.post(`/api/v1/flows/${currentUserFlow.id}/duplicate`)
.set('Authorization', token)
.expect(201);
const refetchedDuplicateFlow = await currentUser
.$relatedQuery('flows')
.findById(response.body.data.id);
const refetchedDuplicateFlowSteps = await refetchedDuplicateFlow
.$relatedQuery('steps')
.orderBy('position', 'asc');
const expectedPayload = await duplicateFlowMock(
refetchedDuplicateFlow,
refetchedDuplicateFlowSteps
);
expect(response.body).toStrictEqual(expectedPayload);
expect(refetchedDuplicateFlow.userId).toStrictEqual(currentUser.id);
});
it('should return duplicated flow data of another user', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const triggerStep = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
appKey: 'webhook',
key: 'catchRawWebhook',
});
await createStep({
flowId: anotherUserFlow.id,
type: 'action',
appKey: 'ntfy',
key: 'sendMessage',
parameters: {
topic: 'Test notification',
message: `Message: {{step.${triggerStep.id}.body.message}} by {{step.${triggerStep.id}.body.sender}}`,
},
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'create',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.post(`/api/v1/flows/${anotherUserFlow.id}/duplicate`)
.set('Authorization', token)
.expect(201);
const refetchedDuplicateFlow = await currentUser
.$relatedQuery('flows')
.findById(response.body.data.id);
const refetchedDuplicateFlowSteps = await refetchedDuplicateFlow
.$relatedQuery('steps')
.orderBy('position', 'asc');
const expectedPayload = await duplicateFlowMock(
refetchedDuplicateFlow,
refetchedDuplicateFlowSteps
);
expect(response.body).toStrictEqual(expectedPayload);
expect(refetchedDuplicateFlow.userId).toStrictEqual(currentUser.id);
});
it('should return not found response for not existing flow UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'create',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const notExistingFlowUUID = Crypto.randomUUID();
await request(app)
.post(`/api/v1/flows/${notExistingFlowUUID}/duplicate`)
.set('Authorization', token)
.expect(404);
});
it('should return not found response for unauthorized flow', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'create',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.post(`/api/v1/flows/${anotherUserFlow.id}/duplicate`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'create',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.post('/api/v1/flows/invalidFlowUUID/duplicate')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,14 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
let flow = await request.currentUser.authorizedFlows
.clone()
.findOne({
id: request.params.flowId,
})
.throwIfNotFound();
flow = await flow.updateStatus(request.body.active);
renderObject(response, flow);
};

View File

@@ -1,213 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createPermission } from '../../../../../test/factories/permission.js';
import updateFlowStatusMock from '../../../../../test/mocks/rest/api/v1/flows/update-flow-status.js';
describe('PATCH /api/v1/flows/:flowId/status', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return updated flow data of current user', async () => {
const currentUserFlow = await createFlow({
userId: currentUser.id,
active: false,
});
const triggerStep = await createStep({
flowId: currentUserFlow.id,
type: 'trigger',
appKey: 'webhook',
key: 'catchRawWebhook',
});
await createStep({
flowId: currentUserFlow.id,
type: 'action',
appKey: 'ntfy',
key: 'sendMessage',
parameters: {
topic: 'Test notification',
message: `Message: {{step.${triggerStep.id}.body.message}} by {{step.${triggerStep.id}.body.sender}}`,
},
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'publish',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.patch(`/api/v1/flows/${currentUserFlow.id}/status`)
.set('Authorization', token)
.send({ active: true })
.expect(200);
const refetchedFlow = await currentUser
.$relatedQuery('flows')
.findById(response.body.data.id);
const refetchedFlowSteps = await refetchedFlow
.$relatedQuery('steps')
.orderBy('position', 'asc');
const expectedPayload = await updateFlowStatusMock(
refetchedFlow,
refetchedFlowSteps
);
expect(response.body).toStrictEqual(expectedPayload);
expect(response.body.data.status).toStrictEqual('published');
});
it('should return updated flow data of another user', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({
userId: anotherUser.id,
active: false,
});
const triggerStep = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
appKey: 'webhook',
key: 'catchRawWebhook',
});
await createStep({
flowId: anotherUserFlow.id,
type: 'action',
appKey: 'ntfy',
key: 'sendMessage',
parameters: {
topic: 'Test notification',
message: `Message: {{step.${triggerStep.id}.body.message}} by {{step.${triggerStep.id}.body.sender}}`,
},
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'publish',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.patch(`/api/v1/flows/${anotherUserFlow.id}/status`)
.set('Authorization', token)
.send({ active: true })
.expect(200);
const refetchedFlow = await anotherUser
.$relatedQuery('flows')
.findById(response.body.data.id);
const refetchedFlowSteps = await refetchedFlow
.$relatedQuery('steps')
.orderBy('position', 'asc');
const expectedPayload = await updateFlowStatusMock(
refetchedFlow,
refetchedFlowSteps
);
expect(response.body).toStrictEqual(expectedPayload);
expect(response.body.data.status).toStrictEqual('published');
});
it('should return not found response for not existing flow UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'publish',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const notExistingFlowUUID = Crypto.randomUUID();
await request(app)
.patch(`/api/v1/flows/${notExistingFlowUUID}/status`)
.set('Authorization', token)
.expect(404);
});
it('should return not found response for unauthorized flow', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'publish',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.patch(`/api/v1/flows/${anotherUserFlow.id}/status`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'publish',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.patch('/api/v1/flows/invalidFlowUUID/status')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,15 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const flow = await request.currentUser.authorizedFlows
.findOne({
id: request.params.flowId,
})
.throwIfNotFound();
await flow.$query().patchAndFetch({
name: request.body.name,
});
renderObject(response, flow);
};

View File

@@ -1,166 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createPermission } from '../../../../../test/factories/permission.js';
import getFlowMock from '../../../../../test/mocks/rest/api/v1/flows/get-flow.js';
describe('PATCH /api/v1/flows/:flowId', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return the updated flow data of current user', async () => {
const currentUserFlow = await createFlow({ userId: currentUser.id });
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.patch(`/api/v1/flows/${currentUserFlow.id}`)
.set('Authorization', token)
.send({
name: 'Updated flow',
})
.expect(200);
const refetchedCurrentUserFlow = await currentUserFlow.$query();
const expectedPayload = await getFlowMock({
...refetchedCurrentUserFlow,
name: 'Updated flow',
});
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return the updated flow data of another user', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.patch(`/api/v1/flows/${anotherUserFlow.id}`)
.set('Authorization', token)
.send({
name: 'Updated flow',
})
.expect(200);
const refetchedAnotherUserFlow = await anotherUserFlow.$query();
const expectedPayload = await getFlowMock({
...refetchedAnotherUserFlow,
name: 'Updated flow',
});
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return not found response for not existing flow UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const notExistingFlowUUID = Crypto.randomUUID();
await request(app)
.patch(`/api/v1/flows/${notExistingFlowUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.patch('/api/v1/flows/invalidFlowUUID')
.set('Authorization', token)
.expect(400);
});
it('should return unprocessable entity response for invalid data', async () => {
const currentUserFlow = await createFlow({ userId: currentUser.id });
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.patch(`/api/v1/flows/${currentUserFlow.id}`)
.set('Authorization', token)
.send({
name: 123123,
})
.expect(422);
expect(response.body.errors).toStrictEqual({
name: ['must be string'],
});
expect(response.body.meta.type).toStrictEqual('ModelValidation');
});
});

View File

@@ -13,7 +13,8 @@ describe('POST /api/v1/installation/users', () => {
beforeEach(async () => {
adminRole = await createRole({
name: 'Admin',
});
key: 'admin',
})
});
describe('for incomplete installations', () => {
@@ -25,7 +26,7 @@ describe('POST /api/v1/installation/users', () => {
.send({
email: 'user@automatisch.io',
password: 'password',
fullName: 'Initial admin',
fullName: 'Initial admin'
})
.expect(204);
@@ -47,7 +48,7 @@ describe('POST /api/v1/installation/users', () => {
.send({
email: 'user@automatisch.io',
password: 'password',
fullName: 'Initial admin',
fullName: 'Initial admin'
})
.expect(403);
@@ -70,7 +71,7 @@ describe('POST /api/v1/installation/users', () => {
.send({
email: 'user@automatisch.io',
password: 'password',
fullName: 'Initial admin',
fullName: 'Initial admin'
})
.expect(403);
@@ -79,5 +80,5 @@ describe('POST /api/v1/installation/users', () => {
expect(user).toBeUndefined();
expect(await Config.isInstallationCompleted()).toBe(true);
});
});
})
});

View File

@@ -1,9 +0,0 @@
export default async (request, response) => {
const step = await request.currentUser.authorizedSteps
.findById(request.params.stepId)
.throwIfNotFound();
await step.delete();
response.status(204).end();
};

View File

@@ -1,134 +0,0 @@
import { describe, it, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createConnection } from '../../../../../test/factories/connection';
import { createFlow } from '../../../../../test/factories/flow';
import { createStep } from '../../../../../test/factories/step';
import { createPermission } from '../../../../../test/factories/permission';
describe('DELETE /api/v1/steps/:stepId', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = await createAuthTokenByUserId(currentUser.id);
});
it('should remove the step of the current user and return no content', async () => {
const currentUserFlow = await createFlow({ userId: currentUser.id });
const currentUserConnection = await createConnection();
await createStep({
flowId: currentUserFlow.id,
connectionId: currentUserConnection.id,
});
const actionStep = await createStep({
flowId: currentUserFlow.id,
connectionId: currentUserConnection.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.delete(`/api/v1/steps/${actionStep.id}`)
.set('Authorization', token)
.expect(204);
});
it('should remove the step of the another user and return no content', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const anotherUserConnection = await createConnection();
await createStep({
flowId: anotherUserFlow.id,
connectionId: anotherUserConnection.id,
});
const actionStep = await createStep({
flowId: anotherUserFlow.id,
connectionId: anotherUserConnection.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.delete(`/api/v1/steps/${actionStep.id}`)
.set('Authorization', token)
.expect(204);
});
it('should return not found response for not existing step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingStepUUID = Crypto.randomUUID();
await request(app)
.delete(`/api/v1/steps/${notExistingStepUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.delete('/api/v1/steps/invalidStepUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,12 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
let step = await request.currentUser.authorizedSteps
.clone()
.findById(request.params.stepId)
.throwIfNotFound();
step = await step.test();
renderObject(response, step);
};

View File

@@ -1,209 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createConnection } from '../../../../../test/factories/connection';
import { createFlow } from '../../../../../test/factories/flow';
import { createStep } from '../../../../../test/factories/step';
import { createExecution } from '../../../../../test/factories/execution.js';
import { createExecutionStep } from '../../../../../test/factories/execution-step.js';
import { createPermission } from '../../../../../test/factories/permission';
import testStepMock from '../../../../../test/mocks/rest/api/v1/steps/test-step.js';
describe('POST /api/v1/steps/:stepId/test', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = await createAuthTokenByUserId(currentUser.id);
});
it('should test the step of the current user and return step data', async () => {
const currentUserFlow = await createFlow({ userId: currentUser.id });
const currentUserConnection = await createConnection();
const triggerStep = await createStep({
flowId: currentUserFlow.id,
connectionId: currentUserConnection.id,
appKey: 'webhook',
key: 'catchRawWebhook',
type: 'trigger',
parameters: {
workSynchronously: false,
},
});
const actionStep = await createStep({
flowId: currentUserFlow.id,
connectionId: currentUserConnection.id,
appKey: 'formatter',
key: 'text',
type: 'action',
parameters: {
input: `{{step.${triggerStep.id}.body.name}}`,
transform: 'capitalize',
},
});
const execution = await createExecution({
flowId: currentUserFlow.id,
testRun: true,
});
await createExecutionStep({
dataIn: { workSynchronously: false },
dataOut: { body: { name: 'john doe' } },
stepId: triggerStep.id,
executionId: execution.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.post(`/api/v1/steps/${actionStep.id}/test`)
.set('Authorization', token)
.expect(200);
const expectedLastExecutionStep = await actionStep.$relatedQuery(
'lastExecutionStep'
);
const expectedPayload = await testStepMock(
actionStep,
expectedLastExecutionStep
);
expect(response.body).toMatchObject(expectedPayload);
});
it('should test the step of the another user and return step data', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const anotherUserConnection = await createConnection();
const triggerStep = await createStep({
flowId: anotherUserFlow.id,
connectionId: anotherUserConnection.id,
appKey: 'webhook',
key: 'catchRawWebhook',
type: 'trigger',
parameters: {
workSynchronously: false,
},
});
const actionStep = await createStep({
flowId: anotherUserFlow.id,
connectionId: anotherUserConnection.id,
appKey: 'formatter',
key: 'text',
type: 'action',
parameters: {
input: `{{step.${triggerStep.id}.body.name}}`,
transform: 'capitalize',
},
});
const execution = await createExecution({
flowId: anotherUserFlow.id,
testRun: true,
});
await createExecutionStep({
dataIn: { workSynchronously: false },
dataOut: { body: { name: 'john doe' } },
stepId: triggerStep.id,
executionId: execution.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.post(`/api/v1/steps/${actionStep.id}/test`)
.set('Authorization', token)
.expect(200);
const expectedLastExecutionStep = await actionStep.$relatedQuery(
'lastExecutionStep'
);
const expectedPayload = await testStepMock(
actionStep,
expectedLastExecutionStep
);
expect(response.body).toMatchObject(expectedPayload);
});
it('should return not found response for not existing step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingStepUUID = Crypto.randomUUID();
await request(app)
.post(`/api/v1/steps/${notExistingStepUUID}/test`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.post('/api/v1/steps/invalidStepUUID/test')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,22 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
let step = await request.currentUser.authorizedSteps
.findById(request.params.stepId)
.throwIfNotFound();
step = await step.updateFor(request.currentUser, stepParams(request));
renderObject(response, step);
};
const stepParams = (request) => {
const { connectionId, appKey, key, parameters } = request.body;
return {
connectionId,
appKey,
key,
parameters,
};
};

View File

@@ -1,211 +0,0 @@
import { describe, it, beforeEach, expect } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createConnection } from '../../../../../test/factories/connection.js';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createPermission } from '../../../../../test/factories/permission.js';
import updateStepMock from '../../../../../test/mocks/rest/api/v1/steps/update-step.js';
describe('PATCH /api/v1/steps/:stepId', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = await createAuthTokenByUserId(currentUser.id);
});
it('should update the step of the current user', async () => {
const currentUserFlow = await createFlow({ userId: currentUser.id });
const currentUserConnection = await createConnection({
key: 'deepl',
});
await createStep({
flowId: currentUserFlow.id,
connectionId: currentUserConnection.id,
});
const actionStep = await createStep({
flowId: currentUserFlow.id,
connectionId: currentUserConnection.id,
appKey: 'deepl',
key: 'translateText',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: ['isCreator'],
});
const response = await request(app)
.patch(`/api/v1/steps/${actionStep.id}`)
.set('Authorization', token)
.send({
parameters: {
text: 'Hello world!',
targetLanguage: 'de',
},
})
.expect(200);
const refetchedStep = await actionStep.$query();
const expectedResponse = updateStepMock(refetchedStep);
expect(response.body).toStrictEqual(expectedResponse);
});
it('should update the step of the another user', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const anotherUserConnection = await createConnection({
key: 'deepl',
});
await createStep({
flowId: anotherUserFlow.id,
connectionId: anotherUserConnection.id,
});
const actionStep = await createStep({
flowId: anotherUserFlow.id,
connectionId: anotherUserConnection.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: [],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: [],
});
const response = await request(app)
.patch(`/api/v1/steps/${actionStep.id}`)
.set('Authorization', token)
.send({
parameters: {
text: 'Hello world!',
targetLanguage: 'de',
},
})
.expect(200);
const refetchedStep = await actionStep.$query();
const expectedResponse = updateStepMock(refetchedStep);
expect(response.body).toStrictEqual(expectedResponse);
});
it('should return not found response for inaccessible connection', async () => {
const currentUserFlow = await createFlow({ userId: currentUser.id });
const anotherUser = await createUser();
const anotherUserConnection = await createConnection({
key: 'deepl',
userId: anotherUser.id,
});
await createStep({
flowId: currentUserFlow.id,
});
const actionStep = await createStep({
flowId: currentUserFlow.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: ['isCreator'],
});
await createPermission({
action: 'read',
subject: 'Connection',
roleId: currentUser.roleId,
conditions: ['isCreator'],
});
await request(app)
.patch(`/api/v1/steps/${actionStep.id}`)
.set('Authorization', token)
.send({
connectionId: anotherUserConnection.id,
})
.expect(404);
});
it('should return not found response for not existing step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: [],
});
const notExistingStepUUID = Crypto.randomUUID();
await request(app)
.patch(`/api/v1/steps/${notExistingStepUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: [],
});
await request(app)
.patch('/api/v1/steps/invalidStepUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,5 +0,0 @@
export default async (request, response) => {
await request.currentUser.softRemove();
response.status(204).end();
};

View File

@@ -1,21 +0,0 @@
import { describe, it, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
describe('DELETE /api/v1/users/:userId', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = await createAuthTokenByUserId(currentUser.id);
});
it('should remove user and return 204 no content', async () => {
await request(app)
.delete(`/api/v1/users/${currentUser.id}`)
.set('Authorization', token)
.expect(204);
});
});

View File

@@ -1,18 +0,0 @@
import User from '../../../../models/user.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const user = await User.registerUser(userParams(request));
renderObject(response, user, { status: 201 });
};
const userParams = (request) => {
const { fullName, email, password } = request.body;
return {
fullName,
email,
password,
};
};

View File

@@ -1,96 +0,0 @@
import { beforeEach, describe, it, expect, vi } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import User from '../../../../models/user.js';
import appConfig from '../../../../config/app.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createRole } from '../../../../../test/factories/role.js';
import registerUserMock from '../../../../../test/mocks/rest/api/v1/users/register-user.ee.js';
describe('POST /api/v1/users/register', () => {
beforeEach(async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
});
it('should return registered user with valid data', async () => {
await createRole({ name: 'User' });
const userData = {
email: 'registered@sample.com',
fullName: 'Full Name',
password: 'samplePassword123',
};
const response = await request(app)
.post('/api/v1/users/register')
.send(userData)
.expect(201);
const refetchedRegisteredUser = await User.query()
.findById(response.body.data.id)
.throwIfNotFound();
const expectedPayload = registerUserMock(refetchedRegisteredUser);
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return not found response without user role existing', async () => {
const userData = {
email: 'registered@sample.com',
fullName: 'Full Name',
password: 'samplePassword123',
};
await request(app)
.post('/api/v1/users/register')
.send(userData)
.expect(404);
});
it('should return unprocessable entity response with already used email', async () => {
await createRole({ name: 'User' });
await createUser({
email: 'registered@sample.com',
});
const userData = {
email: 'registered@sample.com',
fullName: 'Full Name',
password: 'samplePassword123',
};
const response = await request(app)
.post('/api/v1/users/register')
.send(userData)
.expect(422);
expect(response.body.errors).toStrictEqual({
email: ["'email' must be unique."],
});
expect(response.body.meta).toStrictEqual({
type: 'UniqueViolationError',
});
});
it('should return unprocessable entity response with invalid user data', async () => {
await createRole({ name: 'User' });
const userData = {
email: null,
fullName: null,
};
const response = await request(app)
.post('/api/v1/users/register')
.send(userData)
.expect(422);
expect(response.body.meta.type).toStrictEqual('ModelValidation');
expect(response.body.errors).toStrictEqual({
email: ['must be string'],
fullName: ['must be string'],
});
});
});

View File

@@ -1,12 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const user = await request.currentUser.updatePassword(userParams(request));
renderObject(response, user);
};
const userParams = (request) => {
const { currentPassword, password } = request.body;
return { currentPassword, password };
};

View File

@@ -1,51 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import updateCurrentUserPasswordMock from '../../../../../test/mocks/rest/api/v1/users/update-current-user-password.js';
describe('PATCH /api/v1/users/:userId/password', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser({ password: 'old-password' });
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return updated user with valid password', async () => {
const userData = {
currentPassword: 'old-password',
password: 'new-password',
};
const response = await request(app)
.patch(`/api/v1/users/${currentUser.id}/password`)
.set('Authorization', token)
.send(userData)
.expect(200);
const refetchedCurrentUser = await currentUser.$query();
const expectedPayload = updateCurrentUserPasswordMock(refetchedCurrentUser);
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return HTTP 422 with invalid current password', async () => {
const userData = {
currentPassword: '',
password: 'new-password',
};
const response = await request(app)
.patch(`/api/v1/users/${currentUser.id}/password`)
.set('Authorization', token)
.send(userData)
.expect(422);
expect(response.body.meta.type).toEqual('ValidationError');
expect(response.body.errors).toMatchObject({
currentPassword: ['is incorrect.'],
});
});
});

View File

@@ -1,14 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const user = await request.currentUser
.$query()
.patchAndFetch(userParams(request));
renderObject(response, user);
};
const userParams = (request) => {
const { email, fullName } = request.body;
return { email, fullName };
};

View File

@@ -1,56 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import updateCurrentUserMock from '../../../../../test/mocks/rest/api/v1/users/update-current-user.js';
describe('PATCH /api/v1/users/:userId', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = await createAuthTokenByUserId(currentUser.id);
});
it('should return updated user with valid data', async () => {
const userData = {
email: 'updated@sample.com',
fullName: 'Updated Full Name',
};
const response = await request(app)
.patch(`/api/v1/users/${currentUser.id}`)
.set('Authorization', token)
.send(userData)
.expect(200);
const refetchedCurrentUser = await currentUser.$query();
const expectedPayload = updateCurrentUserMock({
...refetchedCurrentUser,
...userData,
});
expect(response.body).toMatchObject(expectedPayload);
});
it('should return HTTP 422 with invalid user data', async () => {
const userData = {
email: null,
fullName: null,
};
const response = await request(app)
.patch(`/api/v1/users/${currentUser.id}`)
.set('Authorization', token)
.send(userData)
.expect(422);
expect(response.body.meta.type).toEqual('ModelValidation');
expect(response.body.errors).toMatchObject({
email: ['must be string'],
fullName: ['must be string'],
});
});
});

View File

@@ -1,11 +0,0 @@
export async function up(knex) {
return await knex.schema.alterTable('roles', (table) => {
table.unique('name');
});
}
export async function down(knex) {
return await knex.schema.alterTable('roles', function (table) {
table.dropUnique('name');
});
}

View File

@@ -1,19 +0,0 @@
export async function up(knex) {
return await knex.schema.alterTable('roles', (table) => {
table.dropColumn('key');
});
}
export async function down(knex) {
await knex.schema.alterTable('roles', (table) => {
table.string('key');
});
await knex('roles').update({
key: knex.raw('LOWER(??)', ['name']),
});
return await knex.schema.alterTable('roles', (table) => {
table.string('key').notNullable().alter();
});
}

View File

@@ -1,3 +0,0 @@
import BaseError from './base.js';
export default class NotAuthorized extends BaseError {}

View File

@@ -1,19 +1,64 @@
// Converted mutations
import executeFlow from './mutations/execute-flow.js';
import verifyConnection from './mutations/verify-connection.js';
import updateCurrentUser from './mutations/update-current-user.js';
import generateAuthUrl from './mutations/generate-auth-url.js';
import createAppAuthClient from './mutations/create-app-auth-client.ee.js';
import createAppConfig from './mutations/create-app-config.ee.js';
import createConnection from './mutations/create-connection.js';
import createFlow from './mutations/create-flow.js';
import createRole from './mutations/create-role.ee.js';
import createStep from './mutations/create-step.js';
import createUser from './mutations/create-user.ee.js';
import deleteConnection from './mutations/delete-connection.js';
import deleteCurrentUser from './mutations/delete-current-user.ee.js';
import deleteFlow from './mutations/delete-flow.js';
import deleteRole from './mutations/delete-role.ee.js';
import deleteStep from './mutations/delete-step.js';
import duplicateFlow from './mutations/duplicate-flow.js';
import executeFlow from './mutations/execute-flow.js';
import generateAuthUrl from './mutations/generate-auth-url.js';
import registerUser from './mutations/register-user.ee.js';
import resetConnection from './mutations/reset-connection.js';
import updateAppAuthClient from './mutations/update-app-auth-client.ee.js';
import updateAppConfig from './mutations/update-app-config.ee.js';
import updateConfig from './mutations/update-config.ee.js';
import updateConnection from './mutations/update-connection.js';
import updateCurrentUser from './mutations/update-current-user.js';
import updateFlow from './mutations/update-flow.js';
import updateFlowStatus from './mutations/update-flow-status.js';
import updateRole from './mutations/update-role.ee.js';
import updateStep from './mutations/update-step.js';
import updateUser from './mutations/update-user.ee.js';
import upsertSamlAuthProvider from './mutations/upsert-saml-auth-provider.ee.js';
import upsertSamlAuthProvidersRoleMappings from './mutations/upsert-saml-auth-providers-role-mappings.ee.js';
import verifyConnection from './mutations/verify-connection.js';
const mutationResolvers = {
createAppAuthClient,
createAppConfig,
createConnection,
createFlow,
createRole,
createStep,
createUser,
deleteConnection,
deleteCurrentUser,
deleteFlow,
deleteRole,
deleteStep,
duplicateFlow,
executeFlow,
generateAuthUrl,
registerUser,
resetConnection,
updateAppAuthClient,
updateAppConfig,
updateConfig,
updateConnection,
updateCurrentUser,
updateFlow,
updateFlowStatus,
updateRole,
updateStep,
updateUser,
upsertSamlAuthProvider,
upsertSamlAuthProvidersRoleMappings,
verifyConnection,
};

View File

@@ -0,0 +1,17 @@
import AppConfig from '../../models/app-config.js';
const createAppAuthClient = async (_parent, params, context) => {
context.currentUser.can('update', 'App');
const appConfig = await AppConfig.query()
.findById(params.input.appConfigId)
.throwIfNotFound();
const appAuthClient = await appConfig
.$relatedQuery('appAuthClients')
.insert(params.input);
return appAuthClient;
};
export default createAppAuthClient;

Some files were not shown because too many files have changed in this diff Show More