Compare commits
207 Commits
stringify-
...
AUT-1252
Author | SHA1 | Date | |
---|---|---|---|
![]() |
06e8f5bfdc | ||
![]() |
c8dae9ea9a | ||
![]() |
cbfb5dd8a6 | ||
![]() |
09b2b7350c | ||
![]() |
e146793d32 | ||
![]() |
0bad2ead10 | ||
![]() |
25176884e7 | ||
![]() |
266d4cddb0 | ||
![]() |
6e529a4205 | ||
![]() |
d2cce278bd | ||
![]() |
b774a62f8c | ||
![]() |
66c12e1a92 | ||
![]() |
184d748890 | ||
![]() |
8cc732c8d1 | ||
![]() |
c688d67e4b | ||
![]() |
3408be2840 | ||
![]() |
e126066132 | ||
![]() |
cfec447d8a | ||
![]() |
805d1fdd52 | ||
![]() |
9299948072 | ||
![]() |
b02960a5ec | ||
![]() |
8134b6db6a | ||
![]() |
cd16a3cc15 | ||
![]() |
074e7828f3 | ||
![]() |
5f7d1f9219 | ||
![]() |
b29c6105a1 | ||
![]() |
1297f5d43c | ||
![]() |
f94a5385d7 | ||
![]() |
8ef2000e45 | ||
![]() |
30d496076b | ||
![]() |
5de06d4482 | ||
![]() |
97fa983305 | ||
![]() |
dccc3c5bc1 | ||
![]() |
ed12465975 | ||
![]() |
b78be222d7 | ||
![]() |
342990e1bf | ||
![]() |
2e5dfdbb0d | ||
![]() |
1790ef0ee6 | ||
![]() |
712a5756e2 | ||
![]() |
bf6ff6b0f7 | ||
![]() |
c6003b6695 | ||
![]() |
8352540fcb | ||
![]() |
22299868fa | ||
![]() |
0d126a8e2b | ||
![]() |
776d027dfa | ||
![]() |
fa9b6d1006 | ||
![]() |
89aa7ffc73 | ||
![]() |
fd971449ca | ||
![]() |
f7cd57e549 | ||
![]() |
34aadbfb09 | ||
![]() |
167195a01c | ||
![]() |
b0abf94191 | ||
![]() |
1009c71e72 | ||
![]() |
29b1695159 | ||
![]() |
44f782221f | ||
![]() |
bab25c51d9 | ||
![]() |
89277e1665 | ||
![]() |
1e9c5a1682 | ||
![]() |
0c75486c7a | ||
![]() |
8c39739880 | ||
![]() |
ace1f84094 | ||
![]() |
2cfd6739ca | ||
![]() |
e0d6f0d653 | ||
![]() |
a4d3b387d0 | ||
![]() |
7394aca02d | ||
![]() |
e812725182 | ||
![]() |
bb76bfcd8b | ||
![]() |
6ff6d0a7dc | ||
![]() |
ddc9867058 | ||
![]() |
ba0d46c6cd | ||
![]() |
369f04fdbc | ||
![]() |
09dd8abe23 | ||
![]() |
8c692758ae | ||
![]() |
32d39b88bd | ||
![]() |
8bd66da511 | ||
![]() |
f86799e494 | ||
![]() |
8d10f26f56 | ||
![]() |
297543f9dd | ||
![]() |
0c53ee8460 | ||
![]() |
862842e3e1 | ||
![]() |
a4fad360df | ||
![]() |
3ba4c8b3bf | ||
![]() |
2992236be4 | ||
![]() |
82161f028e | ||
![]() |
1bcaec144b | ||
![]() |
852d4bba0a | ||
![]() |
af56fa2830 | ||
![]() |
813646e392 | ||
![]() |
1ce31eefc6 | ||
![]() |
fdf53844e1 | ||
![]() |
be57a82302 | ||
![]() |
fb82e863e0 | ||
![]() |
101483409f | ||
![]() |
6fe863eec1 | ||
![]() |
fbb6526aac | ||
![]() |
5556aea913 | ||
![]() |
95dc5fb849 | ||
![]() |
062199d0e3 | ||
![]() |
53ce327516 | ||
![]() |
61a1ce57c2 | ||
![]() |
687295f772 | ||
![]() |
e5366534ed | ||
![]() |
66fe84e126 | ||
![]() |
0b6c28422c | ||
![]() |
ea667bb6a9 | ||
![]() |
c9ba219de1 | ||
![]() |
9df1b29d70 | ||
![]() |
3e34359fa9 | ||
![]() |
1818930d2f | ||
![]() |
c03e674001 | ||
![]() |
10a25b82e0 | ||
![]() |
3b2489d738 | ||
![]() |
5c4ca3c84f | ||
![]() |
06c4b7ed2e | ||
![]() |
5591f6ccc9 | ||
![]() |
63dfb6947e | ||
![]() |
b089069b8e | ||
![]() |
e76a99fd68 | ||
![]() |
a4ea6c1fad | ||
![]() |
64ebdce1b2 | ||
![]() |
2709491d59 | ||
![]() |
81beedede6 | ||
![]() |
33a2386d74 | ||
![]() |
0df5e5283e | ||
![]() |
560407b972 | ||
![]() |
f8c25ae508 | ||
![]() |
c524277665 | ||
![]() |
a70fb009c7 | ||
![]() |
11e67f2ea3 | ||
![]() |
e7118ffe15 | ||
![]() |
79e9455244 | ||
![]() |
6ca8e8958a | ||
![]() |
d3dc207166 | ||
![]() |
51e200533b | ||
![]() |
b8a25b87d8 | ||
![]() |
f4fe0a0d4f | ||
![]() |
1d4f829d29 | ||
![]() |
4afa79fca4 | ||
![]() |
ec22184087 | ||
![]() |
413f3db5b4 | ||
![]() |
8ddfcce787 | ||
![]() |
562341adfe | ||
![]() |
9519ec53ef | ||
![]() |
35bada360d | ||
![]() |
1f39765efe | ||
![]() |
cce5b3b533 | ||
![]() |
e77a03b855 | ||
![]() |
7a54ff212e | ||
![]() |
01340f4597 | ||
![]() |
af6fa80d20 | ||
![]() |
e7474dcb9e | ||
![]() |
0eb906d5df | ||
![]() |
c7babf227c | ||
![]() |
d5c81c14f5 | ||
![]() |
59278378e5 | ||
![]() |
990e69143d | ||
![]() |
c413ae06dc | ||
![]() |
456f8a30cc | ||
![]() |
19c4561feb | ||
![]() |
1392fed023 | ||
![]() |
ca81e14d63 | ||
![]() |
8e7a8a02ec | ||
![]() |
e45dfa94ed | ||
![]() |
a153787ae6 | ||
![]() |
cf37c43bc7 | ||
![]() |
d0aa2bca69 | ||
![]() |
7b3811e6ee | ||
![]() |
4054f551d4 | ||
![]() |
4eeda10f3f | ||
![]() |
4231784ed2 | ||
![]() |
61ff6986d3 | ||
![]() |
09bc0bba1e | ||
![]() |
b2bda8479e | ||
![]() |
09b255f99e | ||
![]() |
0800642a2a | ||
![]() |
48b2b006c0 | ||
![]() |
af4c1f08ec | ||
![]() |
87b26b6342 | ||
![]() |
706fb8d82f | ||
![]() |
f7ca59bd5f | ||
![]() |
6540d0ea53 | ||
![]() |
5995038e21 | ||
![]() |
337ba6ea87 | ||
![]() |
04c6183752 | ||
![]() |
0b63922f46 | ||
![]() |
81c39d7d93 | ||
![]() |
164d31dfbc | ||
![]() |
1a833aad52 | ||
![]() |
77246c1fde | ||
![]() |
56c08a3587 | ||
![]() |
ce6214dc0f | ||
![]() |
22002d50ac | ||
![]() |
ab4e94695d | ||
![]() |
47a01cec7e | ||
![]() |
0cf9bc1a32 | ||
![]() |
6552ebcd3c | ||
![]() |
5af1d94fc0 | ||
![]() |
a4ec7b3047 | ||
![]() |
66f5003d91 | ||
![]() |
0c754e4b4b | ||
![]() |
e008087c4a | ||
![]() |
53f63996bd | ||
![]() |
4fedf77991 | ||
![]() |
34331d8763 | ||
![]() |
2c21b7762c | ||
![]() |
7f9c2b687f | ||
![]() |
b452ed648c |
1
.github/workflows/playwright.yml
vendored
1
.github/workflows/playwright.yml
vendored
@@ -23,6 +23,7 @@ env:
|
|||||||
REDIS_HOST: localhost
|
REDIS_HOST: localhost
|
||||||
APP_ENV: production
|
APP_ENV: production
|
||||||
LICENSE_KEY: dummy_license_key
|
LICENSE_KEY: dummy_license_key
|
||||||
|
BACKEND_APP_URL: http://localhost:3000
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
@@ -10,7 +10,7 @@ import process from 'process';
|
|||||||
async function fetchAdminRole() {
|
async function fetchAdminRole() {
|
||||||
const role = await Role.query()
|
const role = await Role.query()
|
||||||
.where({
|
.where({
|
||||||
key: 'admin',
|
name: 'Admin',
|
||||||
})
|
})
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.first();
|
.first();
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
"start:worker": "node src/worker.js",
|
"start:worker": "node src/worker.js",
|
||||||
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
|
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
|
||||||
"test": "APP_ENV=test vitest run",
|
"test": "APP_ENV=test vitest run",
|
||||||
|
"test:watch": "APP_ENV=test vitest watch",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"db:create": "node ./bin/database/create.js",
|
"db:create": "node ./bin/database/create.js",
|
||||||
"db:seed:user": "node ./bin/database/seed-user.js",
|
"db:seed:user": "node ./bin/database/seed-user.js",
|
||||||
@@ -38,7 +39,7 @@
|
|||||||
"debug": "~2.6.9",
|
"debug": "~2.6.9",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"express": "~4.18.2",
|
"express": "~4.18.2",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-errors": "^3.1.1",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"express-graphql": "^0.12.0",
|
"express-graphql": "^0.12.0",
|
||||||
"fast-xml-parser": "^4.0.11",
|
"fast-xml-parser": "^4.0.11",
|
||||||
@@ -106,7 +107,9 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"nodemonConfig": {
|
"nodemonConfig": {
|
||||||
"watch": [ "src/" ],
|
"watch": [
|
||||||
|
"src/"
|
||||||
|
],
|
||||||
"ext": "js"
|
"ext": "js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import createError from 'http-errors';
|
import createError from 'http-errors';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
import 'express-async-errors';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
|
|
||||||
import appConfig from './config/app.js';
|
import appConfig from './config/app.js';
|
||||||
|
@@ -33,6 +33,7 @@ export default defineAction({
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: true,
|
||||||
|
valueType: 'parse',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@@ -4,6 +4,7 @@ export default defineTrigger({
|
|||||||
name: 'New comment',
|
name: 'New comment',
|
||||||
key: 'newComment',
|
key: 'newComment',
|
||||||
description: 'Triggers when a new comment is created.',
|
description: 'Triggers when a new comment is created.',
|
||||||
|
pollInterval: 15,
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
|
@@ -3,6 +3,7 @@ import defineTrigger from '../../../../helpers/define-trigger.js';
|
|||||||
export default defineTrigger({
|
export default defineTrigger({
|
||||||
name: 'New page',
|
name: 'New page',
|
||||||
key: 'newPage',
|
key: 'newPage',
|
||||||
|
pollInterval: 15,
|
||||||
description: 'Triggers when a new page is created.',
|
description: 'Triggers when a new page is created.',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
|
@@ -3,6 +3,7 @@ import defineTrigger from '../../../../helpers/define-trigger.js';
|
|||||||
export default defineTrigger({
|
export default defineTrigger({
|
||||||
name: 'New post',
|
name: 'New post',
|
||||||
key: 'newPost',
|
key: 'newPost',
|
||||||
|
pollInterval: 15,
|
||||||
description: 'Triggers when a new post is created.',
|
description: 'Triggers when a new post is created.',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
|
@@ -3,6 +3,7 @@ import defineTrigger from '../../../../helpers/define-trigger.js';
|
|||||||
export default defineTrigger({
|
export default defineTrigger({
|
||||||
name: 'New video by search',
|
name: 'New video by search',
|
||||||
key: 'newVideoBySearch',
|
key: 'newVideoBySearch',
|
||||||
|
pollInterval: 15,
|
||||||
description:
|
description:
|
||||||
'Triggers when a new video is uploaded that matches a specific search string.',
|
'Triggers when a new video is uploaded that matches a specific search string.',
|
||||||
arguments: [
|
arguments: [
|
||||||
|
@@ -3,6 +3,7 @@ import defineTrigger from '../../../../helpers/define-trigger.js';
|
|||||||
export default defineTrigger({
|
export default defineTrigger({
|
||||||
name: 'New video in channel',
|
name: 'New video in channel',
|
||||||
key: 'newVideoInChannel',
|
key: 'newVideoInChannel',
|
||||||
|
pollInterval: 15,
|
||||||
description:
|
description:
|
||||||
'Triggers when a new video is published to a specific Youtube channel.',
|
'Triggers when a new video is published to a specific Youtube channel.',
|
||||||
arguments: [
|
arguments: [
|
||||||
|
@@ -2,7 +2,7 @@ import appConfig from './app.js';
|
|||||||
|
|
||||||
const corsOptions = {
|
const corsOptions = {
|
||||||
origin: appConfig.webAppUrl,
|
origin: appConfig.webAppUrl,
|
||||||
methods: 'GET,HEAD,POST,DELETE',
|
methods: 'GET,HEAD,POST,PATCH,DELETE',
|
||||||
credentials: true,
|
credentials: true,
|
||||||
optionsSuccessStatus: 200,
|
optionsSuccessStatus: 200,
|
||||||
};
|
};
|
||||||
|
@@ -11,5 +11,5 @@ export default async (request, response) => {
|
|||||||
|
|
||||||
await accessToken.revoke();
|
await accessToken.revoke();
|
||||||
|
|
||||||
response.status(204).send();
|
response.status(204).end();
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,25 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,94 @@
|
|||||||
|
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'",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,21 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,67 @@
|
|||||||
|
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."],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -15,7 +15,7 @@ describe('GET /api/v1/admin/apps/:appKey/auth-clients/:appAuthClientId', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||||
|
|
||||||
adminRole = await createRole({ key: 'admin' });
|
adminRole = await createRole({ name: 'Admin' });
|
||||||
currentUser = await createUser({ roleId: adminRole.id });
|
currentUser = await createUser({ roleId: adminRole.id });
|
||||||
|
|
||||||
currentAppAuthClient = await createAppAuthClient({
|
currentAppAuthClient = await createAppAuthClient({
|
||||||
|
@@ -14,7 +14,7 @@ describe('GET /api/v1/admin/apps/:appKey/auth-clients', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||||
|
|
||||||
adminRole = await createRole({ key: 'admin' });
|
adminRole = await createRole({ name: 'Admin' });
|
||||||
currentUser = await createUser({ roleId: adminRole.id });
|
currentUser = await createUser({ roleId: adminRole.id });
|
||||||
|
|
||||||
token = await createAuthTokenByUserId(currentUser.id);
|
token = await createAuthTokenByUserId(currentUser.id);
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,104 @@
|
|||||||
|
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'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,24 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,91 @@
|
|||||||
|
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'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,23 @@
|
|||||||
|
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);
|
||||||
|
};
|
@@ -0,0 +1,88 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
@@ -11,7 +11,7 @@ describe('GET /api/v1/admin/permissions/catalog', () => {
|
|||||||
let role, currentUser, token;
|
let role, currentUser, token;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
role = await createRole({ key: 'admin' });
|
role = await createRole({ name: 'Admin' });
|
||||||
currentUser = await createUser({ roleId: role.id });
|
currentUser = await createUser({ roleId: role.id });
|
||||||
|
|
||||||
token = await createAuthTokenByUserId(currentUser.id);
|
token = await createAuthTokenByUserId(currentUser.id);
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,109 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,11 @@
|
|||||||
|
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();
|
||||||
|
};
|
@@ -0,0 +1,112 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@@ -13,7 +13,7 @@ describe('GET /api/v1/admin/roles/:roleId', () => {
|
|||||||
let role, currentUser, token, permissionOne, permissionTwo;
|
let role, currentUser, token, permissionOne, permissionTwo;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
role = await createRole({ key: 'admin' });
|
role = await createRole({ name: 'Admin' });
|
||||||
permissionOne = await createPermission({ roleId: role.id });
|
permissionOne = await createPermission({ roleId: role.id });
|
||||||
permissionTwo = await createPermission({ roleId: role.id });
|
permissionTwo = await createPermission({ roleId: role.id });
|
||||||
currentUser = await createUser({ roleId: role.id });
|
currentUser = await createUser({ roleId: role.id });
|
||||||
|
@@ -11,8 +11,8 @@ describe('GET /api/v1/admin/roles', () => {
|
|||||||
let roleOne, roleTwo, currentUser, token;
|
let roleOne, roleTwo, currentUser, token;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
roleOne = await createRole({ key: 'admin' });
|
roleOne = await createRole({ name: 'Admin' });
|
||||||
roleTwo = await createRole({ key: 'user' });
|
roleTwo = await createRole({ name: 'User' });
|
||||||
currentUser = await createUser({ roleId: roleOne.id });
|
currentUser = await createUser({ roleId: roleOne.id });
|
||||||
|
|
||||||
token = await createAuthTokenByUserId(currentUser.id);
|
token = await createAuthTokenByUserId(currentUser.id);
|
||||||
|
@@ -0,0 +1,24 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,177 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,43 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,78 @@
|
|||||||
|
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' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -13,7 +13,7 @@ describe('GET /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mapping
|
|||||||
let roleMappingOne, roleMappingTwo, samlAuthProvider, currentUser, token;
|
let roleMappingOne, roleMappingTwo, samlAuthProvider, currentUser, token;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const role = await createRole({ key: 'admin' });
|
const role = await createRole({ name: 'Admin' });
|
||||||
currentUser = await createUser({ roleId: role.id });
|
currentUser = await createUser({ roleId: role.id });
|
||||||
|
|
||||||
samlAuthProvider = await createSamlAuthProvider();
|
samlAuthProvider = await createSamlAuthProvider();
|
||||||
|
@@ -13,7 +13,7 @@ describe('GET /api/v1/admin/saml-auth-provider/:samlAuthProviderId', () => {
|
|||||||
let samlAuthProvider, currentUser, token;
|
let samlAuthProvider, currentUser, token;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const role = await createRole({ key: 'admin' });
|
const role = await createRole({ name: 'Admin' });
|
||||||
currentUser = await createUser({ roleId: role.id });
|
currentUser = await createUser({ roleId: role.id });
|
||||||
samlAuthProvider = await createSamlAuthProvider();
|
samlAuthProvider = await createSamlAuthProvider();
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@ describe('GET /api/v1/admin/saml-auth-providers', () => {
|
|||||||
let samlAuthProviderOne, samlAuthProviderTwo, currentUser, token;
|
let samlAuthProviderOne, samlAuthProviderTwo, currentUser, token;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const role = await createRole({ key: 'admin' });
|
const role = await createRole({ name: 'Admin' });
|
||||||
currentUser = await createUser({ roleId: role.id });
|
currentUser = await createUser({ roleId: role.id });
|
||||||
|
|
||||||
samlAuthProviderOne = await createSamlAuthProvider();
|
samlAuthProviderOne = await createSamlAuthProvider();
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
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,
|
||||||
|
}));
|
||||||
|
};
|
@@ -0,0 +1,182 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,45 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,119 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,22 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,122 @@
|
|||||||
|
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'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -10,7 +10,7 @@ describe('DELETE /api/v1/admin/users/:userId', () => {
|
|||||||
let currentUser, currentUserRole, anotherUser, token;
|
let currentUser, currentUserRole, anotherUser, token;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
currentUserRole = await createRole({ key: 'admin' });
|
currentUserRole = await createRole({ name: 'Admin' });
|
||||||
currentUser = await createUser({ roleId: currentUserRole.id });
|
currentUser = await createUser({ roleId: currentUserRole.id });
|
||||||
|
|
||||||
anotherUser = await createUser();
|
anotherUser = await createUser();
|
||||||
|
@@ -12,7 +12,7 @@ describe('GET /api/v1/admin/users/:userId', () => {
|
|||||||
let currentUser, currentUserRole, anotherUser, anotherUserRole, token;
|
let currentUser, currentUserRole, anotherUser, anotherUserRole, token;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
currentUserRole = await createRole({ key: 'admin' });
|
currentUserRole = await createRole({ name: 'Admin' });
|
||||||
currentUser = await createUser({ roleId: currentUserRole.id });
|
currentUser = await createUser({ roleId: currentUserRole.id });
|
||||||
|
|
||||||
anotherUser = await createUser();
|
anotherUser = await createUser();
|
||||||
|
@@ -10,7 +10,7 @@ describe('GET /api/v1/admin/users', () => {
|
|||||||
let currentUser, currentUserRole, anotherUser, anotherUserRole, token;
|
let currentUser, currentUserRole, anotherUser, anotherUserRole, token;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
currentUserRole = await createRole({ key: 'admin' });
|
currentUserRole = await createRole({ name: 'Admin' });
|
||||||
|
|
||||||
currentUser = await createUser({
|
currentUser = await createUser({
|
||||||
roleId: currentUserRole.id,
|
roleId: currentUserRole.id,
|
||||||
@@ -18,7 +18,6 @@ describe('GET /api/v1/admin/users', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
anotherUserRole = await createRole({
|
anotherUserRole = await createRole({
|
||||||
key: 'anotherUser',
|
|
||||||
name: 'Another user role',
|
name: 'Another user role',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
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 };
|
||||||
|
};
|
@@ -0,0 +1,87 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,27 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,405 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,11 @@
|
|||||||
|
export default async (request, response) => {
|
||||||
|
await request.currentUser
|
||||||
|
.$relatedQuery('connections')
|
||||||
|
.delete()
|
||||||
|
.findOne({
|
||||||
|
id: request.params.connectionId,
|
||||||
|
})
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
response.status(204).end();
|
||||||
|
};
|
@@ -0,0 +1,77 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,14 @@
|
|||||||
|
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);
|
||||||
|
};
|
@@ -0,0 +1,90 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,14 @@
|
|||||||
|
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);
|
||||||
|
};
|
@@ -0,0 +1,113 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,19 @@
|
|||||||
|
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 };
|
||||||
|
};
|
@@ -0,0 +1,117 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,14 @@
|
|||||||
|
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);
|
||||||
|
};
|
@@ -0,0 +1,82 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
11
packages/backend/src/controllers/api/v1/flows/create-flow.js
Normal file
11
packages/backend/src/controllers/api/v1/flows/create-flow.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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 });
|
||||||
|
};
|
@@ -0,0 +1,41 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
14
packages/backend/src/controllers/api/v1/flows/create-step.js
Normal file
14
packages/backend/src/controllers/api/v1/flows/create-step.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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 });
|
||||||
|
};
|
@@ -0,0 +1,176 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
10
packages/backend/src/controllers/api/v1/flows/delete-flow.js
Normal file
10
packages/backend/src/controllers/api/v1/flows/delete-flow.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export default async (request, response) => {
|
||||||
|
const flow = await request.currentUser.authorizedFlows
|
||||||
|
.clone()
|
||||||
|
.findById(request.params.flowId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
await flow.delete();
|
||||||
|
|
||||||
|
response.status(204).end();
|
||||||
|
};
|
@@ -0,0 +1,110 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,11 @@
|
|||||||
|
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 });
|
||||||
|
};
|
@@ -0,0 +1,204 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,14 @@
|
|||||||
|
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);
|
||||||
|
};
|
@@ -0,0 +1,213 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
15
packages/backend/src/controllers/api/v1/flows/update-flow.js
Normal file
15
packages/backend/src/controllers/api/v1/flows/update-flow.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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);
|
||||||
|
};
|
@@ -0,0 +1,166 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
@@ -13,8 +13,7 @@ describe('POST /api/v1/installation/users', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
adminRole = await createRole({
|
adminRole = await createRole({
|
||||||
name: 'Admin',
|
name: 'Admin',
|
||||||
key: 'admin',
|
});
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('for incomplete installations', () => {
|
describe('for incomplete installations', () => {
|
||||||
@@ -26,7 +25,7 @@ describe('POST /api/v1/installation/users', () => {
|
|||||||
.send({
|
.send({
|
||||||
email: 'user@automatisch.io',
|
email: 'user@automatisch.io',
|
||||||
password: 'password',
|
password: 'password',
|
||||||
fullName: 'Initial admin'
|
fullName: 'Initial admin',
|
||||||
})
|
})
|
||||||
.expect(204);
|
.expect(204);
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ describe('POST /api/v1/installation/users', () => {
|
|||||||
.send({
|
.send({
|
||||||
email: 'user@automatisch.io',
|
email: 'user@automatisch.io',
|
||||||
password: 'password',
|
password: 'password',
|
||||||
fullName: 'Initial admin'
|
fullName: 'Initial admin',
|
||||||
})
|
})
|
||||||
.expect(403);
|
.expect(403);
|
||||||
|
|
||||||
@@ -71,7 +70,7 @@ describe('POST /api/v1/installation/users', () => {
|
|||||||
.send({
|
.send({
|
||||||
email: 'user@automatisch.io',
|
email: 'user@automatisch.io',
|
||||||
password: 'password',
|
password: 'password',
|
||||||
fullName: 'Initial admin'
|
fullName: 'Initial admin',
|
||||||
})
|
})
|
||||||
.expect(403);
|
.expect(403);
|
||||||
|
|
||||||
@@ -80,5 +79,5 @@ describe('POST /api/v1/installation/users', () => {
|
|||||||
expect(user).toBeUndefined();
|
expect(user).toBeUndefined();
|
||||||
expect(await Config.isInstallationCompleted()).toBe(true);
|
expect(await Config.isInstallationCompleted()).toBe(true);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
export default async (request, response) => {
|
||||||
|
const step = await request.currentUser.authorizedSteps
|
||||||
|
.findById(request.params.stepId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
await step.delete();
|
||||||
|
|
||||||
|
response.status(204).end();
|
||||||
|
};
|
@@ -0,0 +1,134 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
12
packages/backend/src/controllers/api/v1/steps/test-step.js
Normal file
12
packages/backend/src/controllers/api/v1/steps/test-step.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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);
|
||||||
|
};
|
209
packages/backend/src/controllers/api/v1/steps/test-step.test.js
Normal file
209
packages/backend/src/controllers/api/v1/steps/test-step.test.js
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
22
packages/backend/src/controllers/api/v1/steps/update-step.js
Normal file
22
packages/backend/src/controllers/api/v1/steps/update-step.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,211 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,5 @@
|
|||||||
|
export default async (request, response) => {
|
||||||
|
await request.currentUser.softRemove();
|
||||||
|
|
||||||
|
response.status(204).end();
|
||||||
|
};
|
@@ -0,0 +1,21 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,18 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,96 @@
|
|||||||
|
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'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,12 @@
|
|||||||
|
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 };
|
||||||
|
};
|
@@ -0,0 +1,51 @@
|
|||||||
|
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.'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,14 @@
|
|||||||
|
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 };
|
||||||
|
};
|
@@ -0,0 +1,56 @@
|
|||||||
|
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'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,11 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
|
}
|
@@ -0,0 +1,19 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
3
packages/backend/src/errors/not-authorized.js
Normal file
3
packages/backend/src/errors/not-authorized.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import BaseError from './base.js';
|
||||||
|
|
||||||
|
export default class NotAuthorized extends BaseError {}
|
@@ -1,64 +1,19 @@
|
|||||||
import createAppAuthClient from './mutations/create-app-auth-client.ee.js';
|
// Converted mutations
|
||||||
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 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';
|
import verifyConnection from './mutations/verify-connection.js';
|
||||||
|
import updateCurrentUser from './mutations/update-current-user.js';
|
||||||
|
import generateAuthUrl from './mutations/generate-auth-url.js';
|
||||||
|
import createConnection from './mutations/create-connection.js';
|
||||||
|
import resetConnection from './mutations/reset-connection.js';
|
||||||
|
import updateConnection from './mutations/update-connection.js';
|
||||||
|
|
||||||
const mutationResolvers = {
|
const mutationResolvers = {
|
||||||
createAppAuthClient,
|
|
||||||
createAppConfig,
|
|
||||||
createConnection,
|
createConnection,
|
||||||
createFlow,
|
|
||||||
createRole,
|
|
||||||
createStep,
|
|
||||||
createUser,
|
|
||||||
deleteConnection,
|
|
||||||
deleteCurrentUser,
|
|
||||||
deleteFlow,
|
|
||||||
deleteRole,
|
|
||||||
deleteStep,
|
|
||||||
duplicateFlow,
|
|
||||||
executeFlow,
|
executeFlow,
|
||||||
generateAuthUrl,
|
generateAuthUrl,
|
||||||
registerUser,
|
|
||||||
resetConnection,
|
resetConnection,
|
||||||
updateAppAuthClient,
|
|
||||||
updateAppConfig,
|
|
||||||
updateConfig,
|
|
||||||
updateConnection,
|
updateConnection,
|
||||||
updateCurrentUser,
|
updateCurrentUser,
|
||||||
updateFlow,
|
|
||||||
updateFlowStatus,
|
|
||||||
updateRole,
|
|
||||||
updateStep,
|
|
||||||
updateUser,
|
|
||||||
upsertSamlAuthProvider,
|
|
||||||
upsertSamlAuthProvidersRoleMappings,
|
|
||||||
verifyConnection,
|
verifyConnection,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,17 +0,0 @@
|
|||||||
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;
|
|
@@ -1,18 +0,0 @@
|
|||||||
import App from '../../models/app.js';
|
|
||||||
import AppConfig from '../../models/app-config.js';
|
|
||||||
|
|
||||||
const createAppConfig = async (_parent, params, context) => {
|
|
||||||
context.currentUser.can('update', 'App');
|
|
||||||
|
|
||||||
const key = params.input.key;
|
|
||||||
|
|
||||||
const app = await App.findOneByKey(key);
|
|
||||||
|
|
||||||
if (!app) throw new Error('The app cannot be found!');
|
|
||||||
|
|
||||||
const appConfig = await AppConfig.query().insert(params.input);
|
|
||||||
|
|
||||||
return appConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createAppConfig;
|
|
@@ -1,45 +0,0 @@
|
|||||||
import App from '../../models/app.js';
|
|
||||||
import Step from '../../models/step.js';
|
|
||||||
|
|
||||||
const createFlow = async (_parent, params, context) => {
|
|
||||||
context.currentUser.can('create', 'Flow');
|
|
||||||
|
|
||||||
const connectionId = params?.input?.connectionId;
|
|
||||||
const appKey = params?.input?.triggerAppKey;
|
|
||||||
|
|
||||||
if (appKey) {
|
|
||||||
await App.findOneByKey(appKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
const flow = await context.currentUser.$relatedQuery('flows').insert({
|
|
||||||
name: 'Name your flow',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (connectionId) {
|
|
||||||
const hasConnection = await context.currentUser
|
|
||||||
.$relatedQuery('connections')
|
|
||||||
.findById(connectionId);
|
|
||||||
|
|
||||||
if (!hasConnection) {
|
|
||||||
throw new Error('The connection does not exist!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Step.query().insert({
|
|
||||||
flowId: flow.id,
|
|
||||||
type: 'trigger',
|
|
||||||
position: 1,
|
|
||||||
appKey,
|
|
||||||
connectionId,
|
|
||||||
});
|
|
||||||
|
|
||||||
await Step.query().insert({
|
|
||||||
flowId: flow.id,
|
|
||||||
type: 'action',
|
|
||||||
position: 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
return flow;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createFlow;
|
|
@@ -1,29 +0,0 @@
|
|||||||
import kebabCase from 'lodash/kebabCase.js';
|
|
||||||
import Role from '../../models/role.js';
|
|
||||||
|
|
||||||
const createRole = async (_parent, params, context) => {
|
|
||||||
context.currentUser.can('create', 'Role');
|
|
||||||
|
|
||||||
const { name, description, permissions } = params.input;
|
|
||||||
const key = kebabCase(name);
|
|
||||||
|
|
||||||
const existingRole = await Role.query().findOne({ key });
|
|
||||||
|
|
||||||
if (existingRole) {
|
|
||||||
throw new Error('Role already exists!');
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Role.query()
|
|
||||||
.insertGraph(
|
|
||||||
{
|
|
||||||
key,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
permissions,
|
|
||||||
},
|
|
||||||
{ relate: ['permissions'] }
|
|
||||||
)
|
|
||||||
.returning('*');
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createRole;
|
|
@@ -1,56 +0,0 @@
|
|||||||
import App from '../../models/app.js';
|
|
||||||
import Flow from '../../models/flow.js';
|
|
||||||
|
|
||||||
const createStep = async (_parent, params, context) => {
|
|
||||||
const conditions = context.currentUser.can('update', 'Flow');
|
|
||||||
const userFlows = context.currentUser.$relatedQuery('flows');
|
|
||||||
const allFlows = Flow.query();
|
|
||||||
const flowsQuery = conditions.isCreator ? userFlows : allFlows;
|
|
||||||
|
|
||||||
const { input } = params;
|
|
||||||
|
|
||||||
if (input.appKey && input.key) {
|
|
||||||
await App.checkAppAndAction(input.appKey, input.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.appKey && !input.key) {
|
|
||||||
await App.findOneByKey(input.appKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
const flow = await flowsQuery
|
|
||||||
.findOne({
|
|
||||||
id: input.flow.id,
|
|
||||||
})
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const previousStep = await flow
|
|
||||||
.$relatedQuery('steps')
|
|
||||||
.findOne({
|
|
||||||
id: input.previousStep.id,
|
|
||||||
})
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const step = await flow.$relatedQuery('steps').insertAndFetch({
|
|
||||||
key: input.key,
|
|
||||||
appKey: input.appKey,
|
|
||||||
type: 'action',
|
|
||||||
position: previousStep.position + 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const nextSteps = await flow
|
|
||||||
.$relatedQuery('steps')
|
|
||||||
.where('position', '>=', step.position)
|
|
||||||
.whereNot('id', step.id);
|
|
||||||
|
|
||||||
const nextStepQueries = nextSteps.map(async (nextStep, index) => {
|
|
||||||
await nextStep.$query().patchAndFetch({
|
|
||||||
position: step.position + index + 1,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(nextStepQueries);
|
|
||||||
|
|
||||||
return step;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createStep;
|
|
@@ -1,66 +0,0 @@
|
|||||||
import appConfig from '../../config/app.js';
|
|
||||||
import User from '../../models/user.js';
|
|
||||||
import Role from '../../models/role.js';
|
|
||||||
import emailQueue from '../../queues/email.js';
|
|
||||||
import {
|
|
||||||
REMOVE_AFTER_30_DAYS_OR_150_JOBS,
|
|
||||||
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
|
||||||
} from '../../helpers/remove-job-configuration.js';
|
|
||||||
|
|
||||||
const createUser = async (_parent, params, context) => {
|
|
||||||
context.currentUser.can('create', 'User');
|
|
||||||
|
|
||||||
const { fullName, email } = params.input;
|
|
||||||
|
|
||||||
const existingUser = await User.query().findOne({
|
|
||||||
email: email.toLowerCase(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingUser) {
|
|
||||||
throw new Error('User already exists!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const userPayload = {
|
|
||||||
fullName,
|
|
||||||
email,
|
|
||||||
status: 'invited',
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
context.currentUser.can('update', 'Role');
|
|
||||||
|
|
||||||
userPayload.roleId = params.input.role.id;
|
|
||||||
} catch {
|
|
||||||
// void
|
|
||||||
const role = await Role.query().findOne({ key: 'admin' });
|
|
||||||
userPayload.roleId = role.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await User.query().insert(userPayload);
|
|
||||||
|
|
||||||
await user.generateInvitationToken();
|
|
||||||
|
|
||||||
const jobName = `Invitation Email - ${user.id}`;
|
|
||||||
const acceptInvitationUrl = `${appConfig.webAppUrl}/accept-invitation?token=${user.invitationToken}`;
|
|
||||||
|
|
||||||
const jobPayload = {
|
|
||||||
email: user.email,
|
|
||||||
subject: 'You are invited!',
|
|
||||||
template: 'invitation-instructions',
|
|
||||||
params: {
|
|
||||||
fullName: user.fullName,
|
|
||||||
acceptInvitationUrl,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const jobOptions = {
|
|
||||||
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
|
||||||
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
|
|
||||||
};
|
|
||||||
|
|
||||||
await emailQueue.add(jobName, jobPayload, jobOptions);
|
|
||||||
|
|
||||||
return { user, acceptInvitationUrl };
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createUser;
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user