Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
465dc8ba38 |
@@ -6,7 +6,8 @@
|
|||||||
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
|
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
|
||||||
"start:web": "lerna run --stream --scope=@*/web dev",
|
"start:web": "lerna run --stream --scope=@*/web dev",
|
||||||
"start:backend": "lerna run --stream --scope=@*/backend dev",
|
"start:backend": "lerna run --stream --scope=@*/backend dev",
|
||||||
"lint": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend} lint",
|
"lint": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend,cli} lint",
|
||||||
|
"build:watch": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend,cli} build:watch",
|
||||||
"build:docs": "cd ./packages/docs && yarn install && yarn build"
|
"build:docs": "cd ./packages/docs && yarn install && yarn build"
|
||||||
},
|
},
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
|
@@ -1,43 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action.js';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Acknowledge incident',
|
|
||||||
key: 'acknowledgeIncident',
|
|
||||||
description: 'Acknowledges an incident.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Incident ID',
|
|
||||||
key: 'incidentId',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
description:
|
|
||||||
'This serves as the incident ID that requires your acknowledgment.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Acknowledged by',
|
|
||||||
key: 'acknowledgedBy',
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description:
|
|
||||||
"This refers to the individual's name, email, or another form of identification that the person who acknowledged the incident has provided.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const acknowledgedBy = $.step.parameters.acknowledgedBy;
|
|
||||||
const incidentId = $.step.parameters.incidentId;
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
acknowledged_by: acknowledgedBy,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await $.http.post(
|
|
||||||
`/v2/incidents/${incidentId}/acknowledge`,
|
|
||||||
body
|
|
||||||
);
|
|
||||||
|
|
||||||
$.setActionItem({ raw: response.data.data });
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,120 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action.js';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Create incident',
|
|
||||||
key: 'createIncident',
|
|
||||||
description: 'Creates an incident that informs the team.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Brief Summary',
|
|
||||||
key: 'briefSummary',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
description: 'A short description outlining the issue.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Description',
|
|
||||||
key: 'description',
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description:
|
|
||||||
'An elaborate description of the situation, offering insights into what is occurring, along with instructions to reproduce the problem.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Requester Email',
|
|
||||||
key: 'requesterEmail',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
description:
|
|
||||||
'This represents the email address of the individual who initiated the incident request.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Alert Settings - Call',
|
|
||||||
key: 'alertSettingsCall',
|
|
||||||
type: 'dropdown',
|
|
||||||
required: true,
|
|
||||||
description: 'Should we call the on-call person?',
|
|
||||||
variables: true,
|
|
||||||
options: [
|
|
||||||
{ label: 'Yes', value: 'true' },
|
|
||||||
{ label: 'No', value: 'false' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Alert Settings - Text',
|
|
||||||
key: 'alertSettingsText',
|
|
||||||
type: 'dropdown',
|
|
||||||
required: true,
|
|
||||||
description: 'Should we text the on-call person?',
|
|
||||||
variables: true,
|
|
||||||
options: [
|
|
||||||
{ label: 'Yes', value: 'true' },
|
|
||||||
{ label: 'No', value: 'false' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Alert Settings - Email',
|
|
||||||
key: 'alertSettingsEmail',
|
|
||||||
type: 'dropdown',
|
|
||||||
required: true,
|
|
||||||
description: 'Should we email the on-call person?',
|
|
||||||
variables: true,
|
|
||||||
options: [
|
|
||||||
{ label: 'Yes', value: 'true' },
|
|
||||||
{ label: 'No', value: 'false' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Alert Settings - Push Notification',
|
|
||||||
key: 'alertSettingsPushNotification',
|
|
||||||
type: 'dropdown',
|
|
||||||
required: true,
|
|
||||||
description: 'Should we send a push notification to the on-call person?',
|
|
||||||
variables: true,
|
|
||||||
options: [
|
|
||||||
{ label: 'Yes', value: 'true' },
|
|
||||||
{ label: 'No', value: 'false' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Team Alert Wait Time',
|
|
||||||
key: 'teamAlertWaitTime',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
description:
|
|
||||||
"What is the time threshold for acknowledgment before escalating to the entire team? (Specify in seconds) - Use a negative value to indicate no team alert if the on-call person doesn't respond, and use 0 for an immediate alert to the entire team.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const {
|
|
||||||
briefSummary,
|
|
||||||
description,
|
|
||||||
requesterEmail,
|
|
||||||
alertSettingsCall,
|
|
||||||
alertSettingsText,
|
|
||||||
alertSettingsEmail,
|
|
||||||
alertSettingsPushNotification,
|
|
||||||
teamAlertWaitTime,
|
|
||||||
} = $.step.parameters;
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
summary: briefSummary,
|
|
||||||
description,
|
|
||||||
requester_email: requesterEmail,
|
|
||||||
call: alertSettingsCall,
|
|
||||||
sms: alertSettingsText,
|
|
||||||
email: alertSettingsEmail,
|
|
||||||
push: alertSettingsPushNotification,
|
|
||||||
team_wait: teamAlertWaitTime,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await $.http.post('/v2/incidents', body);
|
|
||||||
|
|
||||||
$.setActionItem({ raw: response.data.data });
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,4 +0,0 @@
|
|||||||
import acknowledgeIncident from './acknowledge-incident/index.js';
|
|
||||||
import createIncident from './create-incident/index.js';
|
|
||||||
|
|
||||||
export default [acknowledgeIncident, createIncident];
|
|
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
|
||||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
||||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
|
|
||||||
preserveAspectRatio="xMidYMid meet">
|
|
||||||
|
|
||||||
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
|
|
||||||
fill="#000" stroke="none">
|
|
||||||
<path d="M0 1000 l0 -1000 1000 0 1000 0 0 1000 0 1000 -1000 0 -1000 0 0
|
|
||||||
-1000z m1162 460 c14 -11 113 -184 232 -408 228 -429 231 -439 175 -486 -35
|
|
||||||
-30 -30 -29 -140 -15 -89 12 -123 25 -152 56 -9 11 -72 147 -140 304 -113 263
|
|
||||||
-124 284 -149 287 -14 2 -29 10 -32 17 -8 21 67 214 94 242 28 29 78 30 112 3z
|
|
||||||
m-340 -148 c10 -10 72 -175 139 -367 114 -325 121 -351 108 -374 -8 -14 -27
|
|
||||||
-32 -41 -41 -25 -13 -34 -12 -126 18 -55 18 -111 43 -125 56 -19 17 -40 67
|
|
||||||
-76 182 -36 112 -58 164 -73 176 l-22 16 27 99 c63 224 66 232 95 248 31 17
|
|
||||||
69 12 94 -13z m-314 -219 c16 -15 26 -59 56 -243 42 -262 43 -285 17 -300 -11
|
|
||||||
-5 -24 -10 -30 -10 -19 0 -140 114 -150 141 -7 20 -4 76 10 191 10 90 19 171
|
|
||||||
19 181 0 18 33 57 49 57 5 0 18 -8 29 -17z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,33 +0,0 @@
|
|||||||
import verifyCredentials from './verify-credentials.js';
|
|
||||||
import isStillVerified from './is-still-verified.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
key: 'screenName',
|
|
||||||
label: 'Screen Name',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description:
|
|
||||||
'Screen name of your connection to be used on Automatisch UI.',
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'apiKey',
|
|
||||||
label: 'API Key',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: 'Better Stack API key of your account.',
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
verifyCredentials,
|
|
||||||
isStillVerified,
|
|
||||||
};
|
|
@@ -1,8 +0,0 @@
|
|||||||
import verifyCredentials from './verify-credentials.js';
|
|
||||||
|
|
||||||
const isStillVerified = async ($) => {
|
|
||||||
await verifyCredentials($);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default isStillVerified;
|
|
@@ -1,10 +0,0 @@
|
|||||||
const verifyCredentials = async ($) => {
|
|
||||||
await $.http.get('/v2/metadata');
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
screenName: $.auth.data.screenName,
|
|
||||||
apiKey: $.auth.data.apiKey,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default verifyCredentials;
|
|
@@ -1,9 +0,0 @@
|
|||||||
const addAuthHeader = ($, requestConfig) => {
|
|
||||||
if ($.auth.data?.apiKey) {
|
|
||||||
requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default addAuthHeader;
|
|
@@ -1,18 +0,0 @@
|
|||||||
import defineApp from '../../helpers/define-app.js';
|
|
||||||
import addAuthHeader from './common/add-auth-header.js';
|
|
||||||
import auth from './auth/index.js';
|
|
||||||
import actions from './actions/index.js';
|
|
||||||
|
|
||||||
export default defineApp({
|
|
||||||
name: 'Better Stack',
|
|
||||||
key: 'better-stack',
|
|
||||||
iconUrl: '{BASE_URL}/apps/better-stack/assets/favicon.svg',
|
|
||||||
authDocUrl: 'https://automatisch.io/docs/apps/better-stack/connection',
|
|
||||||
supportsConnections: true,
|
|
||||||
baseUrl: 'https://betterstack.com',
|
|
||||||
apiBaseUrl: 'https://uptime.betterstack.com/api',
|
|
||||||
primaryColor: '000000',
|
|
||||||
beforeRequest: [addAuthHeader],
|
|
||||||
auth,
|
|
||||||
actions,
|
|
||||||
});
|
|
@@ -1,5 +1,11 @@
|
|||||||
import createDatabaseItem from './create-database-item/index.js';
|
import createDatabaseItem from './create-database-item/index.js';
|
||||||
import createPage from './create-page/index.js';
|
import createPage from './create-page/index.js';
|
||||||
import findDatabaseItem from './find-database-item/index.js';
|
import findDatabaseItem from './find-database-item/index.js';
|
||||||
|
import updateDatabaseItem from './update-database-item/index.js';
|
||||||
|
|
||||||
export default [createDatabaseItem, createPage, findDatabaseItem];
|
export default [
|
||||||
|
createDatabaseItem,
|
||||||
|
createPage,
|
||||||
|
findDatabaseItem,
|
||||||
|
updateDatabaseItem,
|
||||||
|
];
|
||||||
|
@@ -0,0 +1,157 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Update database item',
|
||||||
|
key: 'updateDatabaseItem',
|
||||||
|
description: 'Updates a database item.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Database',
|
||||||
|
key: 'databaseId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listDatabases',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Item',
|
||||||
|
key: 'itemId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
dependsOn: ['parameters.databaseId'],
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listDatabaseItems',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.databaseId',
|
||||||
|
value: '{parameters.databaseId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Name',
|
||||||
|
key: 'name',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'This field has a 2000 character limit. Any characters beyond 2000 will not be included.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tags',
|
||||||
|
key: 'tags',
|
||||||
|
type: 'dynamic',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Tag',
|
||||||
|
key: 'tag',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
dependsOn: ['parameters.databaseId'],
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTags',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.databaseId',
|
||||||
|
value: '{parameters.databaseId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Content',
|
||||||
|
key: 'content',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'You can choose to add extra text to the database item, with a limit of up to 2000 characters if desired.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const itemId = $.step.parameters.itemId;
|
||||||
|
const name = $.step.parameters.name;
|
||||||
|
const truncatedName = name.slice(0, 2000);
|
||||||
|
const content = $.step.parameters.content;
|
||||||
|
const truncatedContent = content.slice(0, 2000);
|
||||||
|
const tags = $.step.parameters.tags;
|
||||||
|
const formattedTags = tags
|
||||||
|
.filter((tag) => tag.tag !== '')
|
||||||
|
.map((tag) => tag.tag);
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
properties: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (truncatedName) {
|
||||||
|
body.properties.Name = {
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: truncatedName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formattedTags?.length) {
|
||||||
|
body.properties.Tags = {
|
||||||
|
multi_select: formattedTags.map((tag) => ({ name: tag })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (truncatedContent) {
|
||||||
|
const response = await $.http.get(`/v1/blocks/${itemId}/children`);
|
||||||
|
const firstBlockId = response.data.results[0].id;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
paragraph: {
|
||||||
|
rich_text: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: {
|
||||||
|
content: truncatedContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await $.http.patch(`/v1/blocks/${firstBlockId}`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.patch(`/v1/pages/${itemId}`, body);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@@ -1,4 +1,6 @@
|
|||||||
|
import listDatabaseItems from './list-database-items/index.js';
|
||||||
import listDatabases from './list-databases/index.js';
|
import listDatabases from './list-databases/index.js';
|
||||||
import listParentPages from './list-parent-pages/index.js';
|
import listParentPages from './list-parent-pages/index.js';
|
||||||
|
import listTags from './list-tags/index.js';
|
||||||
|
|
||||||
export default [listDatabases, listParentPages];
|
export default [listDatabaseItems, listDatabases, listParentPages, listTags];
|
||||||
|
@@ -0,0 +1,38 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List database items',
|
||||||
|
key: 'listDatabaseItems',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const databases = {
|
||||||
|
data: [],
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
const payload = {
|
||||||
|
start_cursor: undefined,
|
||||||
|
};
|
||||||
|
const databaseId = $.step.parameters.databaseId;
|
||||||
|
|
||||||
|
if (!databaseId) {
|
||||||
|
return databases;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
const response = await $.http.post(
|
||||||
|
`/v1/databases/${databaseId}/query`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
payload.start_cursor = response.data.next_cursor;
|
||||||
|
|
||||||
|
for (const database of response.data.results) {
|
||||||
|
databases.data.push({
|
||||||
|
value: database.id,
|
||||||
|
name:
|
||||||
|
database.properties.Name?.title?.[0]?.plain_text || 'Untitled Page',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} while (payload.start_cursor);
|
||||||
|
|
||||||
|
return databases;
|
||||||
|
},
|
||||||
|
};
|
@@ -22,7 +22,7 @@ export default {
|
|||||||
for (const database of response.data.results) {
|
for (const database of response.data.results) {
|
||||||
databases.data.push({
|
databases.data.push({
|
||||||
value: database.id,
|
value: database.id,
|
||||||
name: database.title[0].plain_text,
|
name: database.title?.[0]?.plain_text || 'Untitled Database',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} while (payload.start_cursor);
|
} while (payload.start_cursor);
|
||||||
|
@@ -0,0 +1,38 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List tags',
|
||||||
|
key: 'listTags',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const tags = {
|
||||||
|
data: [],
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
const databaseId = $.step.parameters.databaseId;
|
||||||
|
let allTags;
|
||||||
|
|
||||||
|
if (!databaseId) {
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await $.http.get(`/v1/databases/${databaseId}`);
|
||||||
|
const tagsExist =
|
||||||
|
response.data.properties.Tags.multi_select.options.length !== 0;
|
||||||
|
|
||||||
|
if (tagsExist) {
|
||||||
|
allTags = response.data.properties.Tags.multi_select.options.map(
|
||||||
|
(tag) => tag.name
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tag of allTags) {
|
||||||
|
tags.data.push({
|
||||||
|
value: tag,
|
||||||
|
name: tag,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
},
|
||||||
|
};
|
@@ -6,74 +6,100 @@ import { createRole } from '../../../test/factories/role';
|
|||||||
import { createUser } from '../../../test/factories/user';
|
import { createUser } from '../../../test/factories/user';
|
||||||
|
|
||||||
describe('graphQL getCurrentUser query', () => {
|
describe('graphQL getCurrentUser query', () => {
|
||||||
let role, currentUser, token, requestObject;
|
describe('with unauthenticated user', () => {
|
||||||
|
it('should throw not authorized error', async () => {
|
||||||
|
const invalidUserToken = 'invalid-token';
|
||||||
|
|
||||||
beforeEach(async () => {
|
const query = `
|
||||||
role = await createRole({
|
query {
|
||||||
key: 'sample',
|
getCurrentUser {
|
||||||
name: 'sample',
|
|
||||||
});
|
|
||||||
|
|
||||||
currentUser = await createUser({
|
|
||||||
roleId: role.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
requestObject = request(app).post('/graphql').set('Authorization', token);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return user data', async () => {
|
|
||||||
const query = `
|
|
||||||
query {
|
|
||||||
getCurrentUser {
|
|
||||||
id
|
|
||||||
email
|
|
||||||
fullName
|
|
||||||
email
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
role {
|
|
||||||
id
|
id
|
||||||
name
|
email
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
`;
|
||||||
`;
|
|
||||||
|
|
||||||
const response = await requestObject.send({ query }).expect(200);
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', invalidUserToken)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
expect(response.body.errors).toBeDefined();
|
||||||
data: {
|
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||||
getCurrentUser: {
|
});
|
||||||
createdAt: currentUser.createdAt.getTime().toString(),
|
|
||||||
email: currentUser.email,
|
|
||||||
fullName: currentUser.fullName,
|
|
||||||
id: currentUser.id,
|
|
||||||
role: { id: role.id, name: role.name },
|
|
||||||
updatedAt: currentUser.updatedAt.getTime().toString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return user password', async () => {
|
describe('with authenticated user', () => {
|
||||||
const query = `
|
let role, currentUser, token, requestObject;
|
||||||
query {
|
|
||||||
getCurrentUser {
|
beforeEach(async () => {
|
||||||
id
|
role = await createRole({
|
||||||
email
|
key: 'sample',
|
||||||
password
|
name: 'sample',
|
||||||
|
});
|
||||||
|
|
||||||
|
currentUser = await createUser({
|
||||||
|
roleId: role.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
requestObject = request(app).post('/graphql').set('Authorization', token);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return user data', async () => {
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
getCurrentUser {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
fullName
|
||||||
|
email
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
role {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
`;
|
||||||
`;
|
|
||||||
|
|
||||||
const response = await requestObject.send({ query }).expect(400);
|
const response = await requestObject.send({ query }).expect(200);
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
const expectedResponsePayload = {
|
||||||
expect(response.body.errors[0].message).toEqual(
|
data: {
|
||||||
'Cannot query field "password" on type "User".'
|
getCurrentUser: {
|
||||||
);
|
createdAt: currentUser.createdAt.getTime().toString(),
|
||||||
|
email: currentUser.email,
|
||||||
|
fullName: currentUser.fullName,
|
||||||
|
id: currentUser.id,
|
||||||
|
role: { id: role.id, name: role.name },
|
||||||
|
updatedAt: currentUser.updatedAt.getTime().toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not return user password', async () => {
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
getCurrentUser {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await requestObject.send({ query }).expect(400);
|
||||||
|
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
expect(response.body.errors[0].message).toEqual(
|
||||||
|
'Cannot query field "password" on type "User".'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -40,291 +40,307 @@ describe('graphQL getExecutions query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
describe('and without correct permissions', () => {
|
const invalidToken = 'invalid-token';
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const userWithoutPermissions = await createUser();
|
|
||||||
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
|
||||||
|
|
||||||
|
describe('with unauthenticated user', () => {
|
||||||
|
it('should throw not authorized error', async () => {
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/graphql')
|
.post('/graphql')
|
||||||
.set('Authorization', token)
|
.set('Authorization', invalidToken)
|
||||||
.send({ query })
|
.send({ query })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
expect(response.body.errors).toBeDefined();
|
||||||
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and with correct permission', () => {
|
describe('with authenticated user', () => {
|
||||||
let role,
|
describe('and without permissions', () => {
|
||||||
currentUser,
|
it('should throw not authorized error', async () => {
|
||||||
anotherUser,
|
const userWithoutPermissions = await createUser();
|
||||||
token,
|
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
||||||
flowOne,
|
|
||||||
stepOneForFlowOne,
|
|
||||||
stepTwoForFlowOne,
|
|
||||||
executionOne,
|
|
||||||
flowTwo,
|
|
||||||
stepOneForFlowTwo,
|
|
||||||
stepTwoForFlowTwo,
|
|
||||||
executionTwo,
|
|
||||||
flowThree,
|
|
||||||
stepOneForFlowThree,
|
|
||||||
stepTwoForFlowThree,
|
|
||||||
executionThree,
|
|
||||||
expectedResponseForExecutionOne,
|
|
||||||
expectedResponseForExecutionTwo,
|
|
||||||
expectedResponseForExecutionThree;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
const response = await request(app)
|
||||||
role = await createRole({
|
.post('/graphql')
|
||||||
key: 'sample',
|
.set('Authorization', token)
|
||||||
name: 'sample',
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
currentUser = await createUser({
|
describe('and with correct permission', () => {
|
||||||
roleId: role.id,
|
let role,
|
||||||
fullName: 'Current User',
|
currentUser,
|
||||||
});
|
anotherUser,
|
||||||
|
token,
|
||||||
|
flowOne,
|
||||||
|
stepOneForFlowOne,
|
||||||
|
stepTwoForFlowOne,
|
||||||
|
executionOne,
|
||||||
|
flowTwo,
|
||||||
|
stepOneForFlowTwo,
|
||||||
|
stepTwoForFlowTwo,
|
||||||
|
executionTwo,
|
||||||
|
flowThree,
|
||||||
|
stepOneForFlowThree,
|
||||||
|
stepTwoForFlowThree,
|
||||||
|
executionThree,
|
||||||
|
expectedResponseForExecutionOne,
|
||||||
|
expectedResponseForExecutionTwo,
|
||||||
|
expectedResponseForExecutionThree;
|
||||||
|
|
||||||
anotherUser = await createUser();
|
beforeEach(async () => {
|
||||||
|
role = await createRole({
|
||||||
|
key: 'sample',
|
||||||
|
name: 'sample',
|
||||||
|
});
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
currentUser = await createUser({
|
||||||
|
roleId: role.id,
|
||||||
|
fullName: 'Current User',
|
||||||
|
});
|
||||||
|
|
||||||
flowOne = await createFlow({
|
anotherUser = await createUser();
|
||||||
userId: currentUser.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
stepOneForFlowOne = await createStep({
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
flowId: flowOne.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
stepTwoForFlowOne = await createStep({
|
flowOne = await createFlow({
|
||||||
flowId: flowOne.id,
|
userId: currentUser.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
executionOne = await createExecution({
|
stepOneForFlowOne = await createStep({
|
||||||
flowId: flowOne.id,
|
flowId: flowOne.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
await createExecutionStep({
|
stepTwoForFlowOne = await createStep({
|
||||||
executionId: executionOne.id,
|
flowId: flowOne.id,
|
||||||
stepId: stepOneForFlowOne.id,
|
});
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createExecutionStep({
|
executionOne = await createExecution({
|
||||||
executionId: executionOne.id,
|
flowId: flowOne.id,
|
||||||
stepId: stepTwoForFlowOne.id,
|
});
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
|
|
||||||
flowTwo = await createFlow({
|
await createExecutionStep({
|
||||||
userId: currentUser.id,
|
executionId: executionOne.id,
|
||||||
});
|
stepId: stepOneForFlowOne.id,
|
||||||
|
|
||||||
stepOneForFlowTwo = await createStep({
|
|
||||||
flowId: flowTwo.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
stepTwoForFlowTwo = await createStep({
|
|
||||||
flowId: flowTwo.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
executionTwo = await createExecution({
|
|
||||||
flowId: flowTwo.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await createExecutionStep({
|
|
||||||
executionId: executionTwo.id,
|
|
||||||
stepId: stepOneForFlowTwo.id,
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createExecutionStep({
|
|
||||||
executionId: executionTwo.id,
|
|
||||||
stepId: stepTwoForFlowTwo.id,
|
|
||||||
status: 'failure',
|
|
||||||
});
|
|
||||||
|
|
||||||
flowThree = await createFlow({
|
|
||||||
userId: anotherUser.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
stepOneForFlowThree = await createStep({
|
|
||||||
flowId: flowThree.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
stepTwoForFlowThree = await createStep({
|
|
||||||
flowId: flowThree.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
executionThree = await createExecution({
|
|
||||||
flowId: flowThree.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await createExecutionStep({
|
|
||||||
executionId: executionThree.id,
|
|
||||||
stepId: stepOneForFlowThree.id,
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createExecutionStep({
|
|
||||||
executionId: executionThree.id,
|
|
||||||
stepId: stepTwoForFlowThree.id,
|
|
||||||
status: 'failure',
|
|
||||||
});
|
|
||||||
|
|
||||||
expectedResponseForExecutionOne = {
|
|
||||||
node: {
|
|
||||||
createdAt: executionOne.createdAt.getTime().toString(),
|
|
||||||
flow: {
|
|
||||||
active: flowOne.active,
|
|
||||||
id: flowOne.id,
|
|
||||||
name: flowOne.name,
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowOne.appKey}/assets/favicon.svg`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowOne.appKey}/assets/favicon.svg`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
id: executionOne.id,
|
|
||||||
status: 'success',
|
status: 'success',
|
||||||
testRun: executionOne.testRun,
|
|
||||||
updatedAt: executionOne.updatedAt.getTime().toString(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expectedResponseForExecutionTwo = {
|
|
||||||
node: {
|
|
||||||
createdAt: executionTwo.createdAt.getTime().toString(),
|
|
||||||
flow: {
|
|
||||||
active: flowTwo.active,
|
|
||||||
id: flowTwo.id,
|
|
||||||
name: flowTwo.name,
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
id: executionTwo.id,
|
|
||||||
status: 'failure',
|
|
||||||
testRun: executionTwo.testRun,
|
|
||||||
updatedAt: executionTwo.updatedAt.getTime().toString(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expectedResponseForExecutionThree = {
|
|
||||||
node: {
|
|
||||||
createdAt: executionThree.createdAt.getTime().toString(),
|
|
||||||
flow: {
|
|
||||||
active: flowThree.active,
|
|
||||||
id: flowThree.id,
|
|
||||||
name: flowThree.name,
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowThree.appKey}/assets/favicon.svg`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowThree.appKey}/assets/favicon.svg`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
id: executionThree.id,
|
|
||||||
status: 'failure',
|
|
||||||
testRun: executionThree.testRun,
|
|
||||||
updatedAt: executionThree.updatedAt.getTime().toString(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with isCreator condition', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: role.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('should return executions data of the current user', async () => {
|
await createExecutionStep({
|
||||||
const response = await request(app)
|
executionId: executionOne.id,
|
||||||
.post('/graphql')
|
stepId: stepTwoForFlowOne.id,
|
||||||
.set('Authorization', token)
|
status: 'success',
|
||||||
.send({ query })
|
});
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
flowTwo = await createFlow({
|
||||||
data: {
|
userId: currentUser.id,
|
||||||
getExecutions: {
|
});
|
||||||
edges: [
|
|
||||||
expectedResponseForExecutionTwo,
|
stepOneForFlowTwo = await createStep({
|
||||||
expectedResponseForExecutionOne,
|
flowId: flowTwo.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
stepTwoForFlowTwo = await createStep({
|
||||||
|
flowId: flowTwo.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
executionTwo = await createExecution({
|
||||||
|
flowId: flowTwo.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createExecutionStep({
|
||||||
|
executionId: executionTwo.id,
|
||||||
|
stepId: stepOneForFlowTwo.id,
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
|
||||||
|
await createExecutionStep({
|
||||||
|
executionId: executionTwo.id,
|
||||||
|
stepId: stepTwoForFlowTwo.id,
|
||||||
|
status: 'failure',
|
||||||
|
});
|
||||||
|
|
||||||
|
flowThree = await createFlow({
|
||||||
|
userId: anotherUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
stepOneForFlowThree = await createStep({
|
||||||
|
flowId: flowThree.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
stepTwoForFlowThree = await createStep({
|
||||||
|
flowId: flowThree.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
executionThree = await createExecution({
|
||||||
|
flowId: flowThree.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createExecutionStep({
|
||||||
|
executionId: executionThree.id,
|
||||||
|
stepId: stepOneForFlowThree.id,
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
|
||||||
|
await createExecutionStep({
|
||||||
|
executionId: executionThree.id,
|
||||||
|
stepId: stepTwoForFlowThree.id,
|
||||||
|
status: 'failure',
|
||||||
|
});
|
||||||
|
|
||||||
|
expectedResponseForExecutionOne = {
|
||||||
|
node: {
|
||||||
|
createdAt: executionOne.createdAt.getTime().toString(),
|
||||||
|
flow: {
|
||||||
|
active: flowOne.active,
|
||||||
|
id: flowOne.id,
|
||||||
|
name: flowOne.name,
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowOne.appKey}/assets/favicon.svg`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowOne.appKey}/assets/favicon.svg`,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
|
||||||
},
|
},
|
||||||
|
id: executionOne.id,
|
||||||
|
status: 'success',
|
||||||
|
testRun: executionOne.testRun,
|
||||||
|
updatedAt: executionOne.updatedAt.getTime().toString(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
expectedResponseForExecutionTwo = {
|
||||||
});
|
node: {
|
||||||
});
|
createdAt: executionTwo.createdAt.getTime().toString(),
|
||||||
|
flow: {
|
||||||
describe('and without isCreator condition', () => {
|
active: flowTwo.active,
|
||||||
beforeEach(async () => {
|
id: flowTwo.id,
|
||||||
await createPermission({
|
name: flowTwo.name,
|
||||||
action: 'read',
|
steps: [
|
||||||
subject: 'Execution',
|
{
|
||||||
roleId: role.id,
|
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`,
|
||||||
conditions: [],
|
},
|
||||||
});
|
{
|
||||||
});
|
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`,
|
||||||
|
},
|
||||||
it('should return executions data of all users', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getExecutions: {
|
|
||||||
edges: [
|
|
||||||
expectedResponseForExecutionThree,
|
|
||||||
expectedResponseForExecutionTwo,
|
|
||||||
expectedResponseForExecutionOne,
|
|
||||||
],
|
],
|
||||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
|
||||||
},
|
},
|
||||||
|
id: executionTwo.id,
|
||||||
|
status: 'failure',
|
||||||
|
testRun: executionTwo.testRun,
|
||||||
|
updatedAt: executionTwo.updatedAt.getTime().toString(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
expectedResponseForExecutionThree = {
|
||||||
|
node: {
|
||||||
|
createdAt: executionThree.createdAt.getTime().toString(),
|
||||||
|
flow: {
|
||||||
|
active: flowThree.active,
|
||||||
|
id: flowThree.id,
|
||||||
|
name: flowThree.name,
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowThree.appKey}/assets/favicon.svg`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowThree.appKey}/assets/favicon.svg`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
id: executionThree.id,
|
||||||
|
status: 'failure',
|
||||||
|
testRun: executionThree.testRun,
|
||||||
|
updatedAt: executionThree.updatedAt.getTime().toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with filters', () => {
|
describe('and with isCreator condition', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createPermission({
|
await createPermission({
|
||||||
action: 'read',
|
action: 'read',
|
||||||
subject: 'Execution',
|
subject: 'Execution',
|
||||||
roleId: role.id,
|
roleId: role.id,
|
||||||
conditions: [],
|
conditions: ['isCreator'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return executions data of the current user', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getExecutions: {
|
||||||
|
edges: [
|
||||||
|
expectedResponseForExecutionTwo,
|
||||||
|
expectedResponseForExecutionOne,
|
||||||
|
],
|
||||||
|
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return executions data for the specified flow', async () => {
|
describe('and without isCreator condition', () => {
|
||||||
const query = `
|
beforeEach(async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Execution',
|
||||||
|
roleId: role.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return executions data of all users', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getExecutions: {
|
||||||
|
edges: [
|
||||||
|
expectedResponseForExecutionThree,
|
||||||
|
expectedResponseForExecutionTwo,
|
||||||
|
expectedResponseForExecutionOne,
|
||||||
|
],
|
||||||
|
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and with filters', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Execution',
|
||||||
|
roleId: role.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return executions data for the specified flow', async () => {
|
||||||
|
const query = `
|
||||||
query {
|
query {
|
||||||
getExecutions(limit: 10, offset: 0, filters: { flowId: "${flowOne.id}" }) {
|
getExecutions(limit: 10, offset: 0, filters: { flowId: "${flowOne.id}" }) {
|
||||||
pageInfo {
|
pageInfo {
|
||||||
@@ -352,26 +368,26 @@ describe('graphQL getExecutions query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/graphql')
|
.post('/graphql')
|
||||||
.set('Authorization', token)
|
.set('Authorization', token)
|
||||||
.send({ query })
|
.send({ query })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
const expectedResponsePayload = {
|
||||||
data: {
|
data: {
|
||||||
getExecutions: {
|
getExecutions: {
|
||||||
edges: [expectedResponseForExecutionOne],
|
edges: [expectedResponseForExecutionOne],
|
||||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return only executions data with success status', async () => {
|
it('should return only executions data with success status', async () => {
|
||||||
const query = `
|
const query = `
|
||||||
query {
|
query {
|
||||||
getExecutions(limit: 10, offset: 0, filters: { status: "success" }) {
|
getExecutions(limit: 10, offset: 0, filters: { status: "success" }) {
|
||||||
pageInfo {
|
pageInfo {
|
||||||
@@ -399,30 +415,30 @@ describe('graphQL getExecutions query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/graphql')
|
.post('/graphql')
|
||||||
.set('Authorization', token)
|
.set('Authorization', token)
|
||||||
.send({ query })
|
.send({ query })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
const expectedResponsePayload = {
|
||||||
data: {
|
data: {
|
||||||
getExecutions: {
|
getExecutions: {
|
||||||
edges: [expectedResponseForExecutionOne],
|
edges: [expectedResponseForExecutionOne],
|
||||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return only executions data within date range', async () => {
|
it('should return only executions data within date range', async () => {
|
||||||
const createdAtFrom = executionOne.createdAt.getTime().toString();
|
const createdAtFrom = executionOne.createdAt.getTime().toString();
|
||||||
|
|
||||||
const createdAtTo = executionOne.createdAt.getTime().toString();
|
const createdAtTo = executionOne.createdAt.getTime().toString();
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
query {
|
query {
|
||||||
getExecutions(limit: 10, offset: 0, filters: { createdAt: { from: "${createdAtFrom}", to: "${createdAtTo}" }}) {
|
getExecutions(limit: 10, offset: 0, filters: { createdAt: { from: "${createdAtFrom}", to: "${createdAtTo}" }}) {
|
||||||
pageInfo {
|
pageInfo {
|
||||||
@@ -450,22 +466,23 @@ describe('graphQL getExecutions query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/graphql')
|
.post('/graphql')
|
||||||
.set('Authorization', token)
|
.set('Authorization', token)
|
||||||
.send({ query })
|
.send({ query })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
const expectedResponsePayload = {
|
||||||
data: {
|
data: {
|
||||||
getExecutions: {
|
getExecutions: {
|
||||||
edges: [expectedResponseForExecutionOne],
|
edges: [expectedResponseForExecutionOne],
|
||||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -40,200 +40,222 @@ describe('graphQL getFlow query', () => {
|
|||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('and without permissions', () => {
|
describe('with unauthenticated user', () => {
|
||||||
it('should throw not authorized error', async () => {
|
it('should throw not authorized error', async () => {
|
||||||
const userWithoutPermissions = await createUser();
|
const invalidToken = 'invalid-token';
|
||||||
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
|
||||||
const flow = await createFlow();
|
const flow = await createFlow();
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/graphql')
|
.post('/graphql')
|
||||||
.set('Authorization', token)
|
.set('Authorization', invalidToken)
|
||||||
.send({ query: query(flow.id) })
|
.send({ query: query(flow.id) })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
expect(response.body.errors).toBeDefined();
|
||||||
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and with correct permission', () => {
|
describe('with authenticated user', () => {
|
||||||
let currentUser, currentUserRole, currentUserFlow;
|
describe('and without permissions', () => {
|
||||||
|
it('should throw not authorized error', async () => {
|
||||||
beforeEach(async () => {
|
const userWithoutPermissions = await createUser();
|
||||||
currentUserRole = await createRole();
|
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
||||||
currentUser = await createUser({ roleId: currentUserRole.id });
|
const flow = await createFlow();
|
||||||
currentUserFlow = await createFlow({ userId: currentUser.id });
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with isCreator condition', () => {
|
|
||||||
it('should return executions data of the current user', async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const triggerStep = await createStep({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
type: 'trigger',
|
|
||||||
key: 'catchRawWebhook',
|
|
||||||
webhookPath: `/webhooks/flows/${currentUserFlow.id}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionConnection = await createConnection({
|
|
||||||
userId: currentUser.id,
|
|
||||||
formattedData: {
|
|
||||||
screenName: 'Test',
|
|
||||||
authenticationKey: 'test key',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionStep = await createStep({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
type: 'action',
|
|
||||||
connectionId: actionConnection.id,
|
|
||||||
key: 'translateText',
|
|
||||||
});
|
|
||||||
|
|
||||||
const token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/graphql')
|
.post('/graphql')
|
||||||
.set('Authorization', token)
|
.set('Authorization', token)
|
||||||
.send({ query: query(currentUserFlow.id) })
|
.send({ query: query(flow.id) })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
expect(response.body.errors).toBeDefined();
|
||||||
data: {
|
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
||||||
getFlow: {
|
|
||||||
active: currentUserFlow.active,
|
|
||||||
id: currentUserFlow.id,
|
|
||||||
name: currentUserFlow.name,
|
|
||||||
status: 'draft',
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
appKey: triggerStep.appKey,
|
|
||||||
connection: null,
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${triggerStep.appKey}/assets/favicon.svg`,
|
|
||||||
id: triggerStep.id,
|
|
||||||
key: 'catchRawWebhook',
|
|
||||||
parameters: {},
|
|
||||||
position: 1,
|
|
||||||
status: triggerStep.status,
|
|
||||||
type: 'trigger',
|
|
||||||
webhookUrl: `${appConfig.baseUrl}/webhooks/flows/${currentUserFlow.id}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
appKey: actionStep.appKey,
|
|
||||||
connection: {
|
|
||||||
createdAt: actionConnection.createdAt.getTime().toString(),
|
|
||||||
id: actionConnection.id,
|
|
||||||
verified: actionConnection.verified,
|
|
||||||
},
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${actionStep.appKey}/assets/favicon.svg`,
|
|
||||||
id: actionStep.id,
|
|
||||||
key: 'translateText',
|
|
||||||
parameters: {},
|
|
||||||
position: 1,
|
|
||||||
status: actionStep.status,
|
|
||||||
type: 'action',
|
|
||||||
webhookUrl: 'http://localhost:3000/null',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and without isCreator condition', () => {
|
describe('and with correct permission', () => {
|
||||||
it('should return executions data of all users', async () => {
|
let currentUser, currentUserRole, currentUserFlow;
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const anotherUser = await createUser();
|
beforeEach(async () => {
|
||||||
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
|
currentUserRole = await createRole();
|
||||||
|
currentUser = await createUser({ roleId: currentUserRole.id });
|
||||||
|
currentUserFlow = await createFlow({ userId: currentUser.id });
|
||||||
|
});
|
||||||
|
|
||||||
const triggerStep = await createStep({
|
describe('and with isCreator condition', () => {
|
||||||
flowId: anotherUserFlow.id,
|
it('should return executions data of the current user', async () => {
|
||||||
type: 'trigger',
|
await createPermission({
|
||||||
key: 'catchRawWebhook',
|
action: 'read',
|
||||||
webhookPath: `/webhooks/flows/${anotherUserFlow.id}`,
|
subject: 'Flow',
|
||||||
});
|
roleId: currentUserRole.id,
|
||||||
|
conditions: ['isCreator'],
|
||||||
|
});
|
||||||
|
|
||||||
const actionConnection = await createConnection({
|
const triggerStep = await createStep({
|
||||||
userId: anotherUser.id,
|
flowId: currentUserFlow.id,
|
||||||
formattedData: {
|
type: 'trigger',
|
||||||
screenName: 'Test',
|
key: 'catchRawWebhook',
|
||||||
authenticationKey: 'test key',
|
webhookPath: `/webhooks/flows/${currentUserFlow.id}`,
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const actionStep = await createStep({
|
const actionConnection = await createConnection({
|
||||||
flowId: anotherUserFlow.id,
|
userId: currentUser.id,
|
||||||
type: 'action',
|
formattedData: {
|
||||||
connectionId: actionConnection.id,
|
screenName: 'Test',
|
||||||
key: 'translateText',
|
authenticationKey: 'test key',
|
||||||
});
|
|
||||||
|
|
||||||
const token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query: query(anotherUserFlow.id) })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getFlow: {
|
|
||||||
active: anotherUserFlow.active,
|
|
||||||
id: anotherUserFlow.id,
|
|
||||||
name: anotherUserFlow.name,
|
|
||||||
status: 'draft',
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
appKey: triggerStep.appKey,
|
|
||||||
connection: null,
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${triggerStep.appKey}/assets/favicon.svg`,
|
|
||||||
id: triggerStep.id,
|
|
||||||
key: 'catchRawWebhook',
|
|
||||||
parameters: {},
|
|
||||||
position: 1,
|
|
||||||
status: triggerStep.status,
|
|
||||||
type: 'trigger',
|
|
||||||
webhookUrl: `${appConfig.baseUrl}/webhooks/flows/${anotherUserFlow.id}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
appKey: actionStep.appKey,
|
|
||||||
connection: {
|
|
||||||
createdAt: actionConnection.createdAt.getTime().toString(),
|
|
||||||
id: actionConnection.id,
|
|
||||||
verified: actionConnection.verified,
|
|
||||||
},
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${actionStep.appKey}/assets/favicon.svg`,
|
|
||||||
id: actionStep.id,
|
|
||||||
key: 'translateText',
|
|
||||||
parameters: {},
|
|
||||||
position: 1,
|
|
||||||
status: actionStep.status,
|
|
||||||
type: 'action',
|
|
||||||
webhookUrl: 'http://localhost:3000/null',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
const actionStep = await createStep({
|
||||||
|
flowId: currentUserFlow.id,
|
||||||
|
type: 'action',
|
||||||
|
connectionId: actionConnection.id,
|
||||||
|
key: 'translateText',
|
||||||
|
});
|
||||||
|
|
||||||
|
const token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({ query: query(currentUserFlow.id) })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getFlow: {
|
||||||
|
active: currentUserFlow.active,
|
||||||
|
id: currentUserFlow.id,
|
||||||
|
name: currentUserFlow.name,
|
||||||
|
status: 'draft',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
appKey: triggerStep.appKey,
|
||||||
|
connection: null,
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${triggerStep.appKey}/assets/favicon.svg`,
|
||||||
|
id: triggerStep.id,
|
||||||
|
key: 'catchRawWebhook',
|
||||||
|
parameters: {},
|
||||||
|
position: 1,
|
||||||
|
status: triggerStep.status,
|
||||||
|
type: 'trigger',
|
||||||
|
webhookUrl: `${appConfig.baseUrl}/webhooks/flows/${currentUserFlow.id}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
appKey: actionStep.appKey,
|
||||||
|
connection: {
|
||||||
|
createdAt: actionConnection.createdAt
|
||||||
|
.getTime()
|
||||||
|
.toString(),
|
||||||
|
id: actionConnection.id,
|
||||||
|
verified: actionConnection.verified,
|
||||||
|
},
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${actionStep.appKey}/assets/favicon.svg`,
|
||||||
|
id: actionStep.id,
|
||||||
|
key: 'translateText',
|
||||||
|
parameters: {},
|
||||||
|
position: 1,
|
||||||
|
status: actionStep.status,
|
||||||
|
type: 'action',
|
||||||
|
webhookUrl: 'http://localhost:3000/null',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and without isCreator condition', () => {
|
||||||
|
it('should return executions data of all users', async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const anotherUser = await createUser();
|
||||||
|
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
|
||||||
|
|
||||||
|
const triggerStep = await createStep({
|
||||||
|
flowId: anotherUserFlow.id,
|
||||||
|
type: 'trigger',
|
||||||
|
key: 'catchRawWebhook',
|
||||||
|
webhookPath: `/webhooks/flows/${anotherUserFlow.id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const actionConnection = await createConnection({
|
||||||
|
userId: anotherUser.id,
|
||||||
|
formattedData: {
|
||||||
|
screenName: 'Test',
|
||||||
|
authenticationKey: 'test key',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const actionStep = await createStep({
|
||||||
|
flowId: anotherUserFlow.id,
|
||||||
|
type: 'action',
|
||||||
|
connectionId: actionConnection.id,
|
||||||
|
key: 'translateText',
|
||||||
|
});
|
||||||
|
|
||||||
|
const token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({ query: query(anotherUserFlow.id) })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getFlow: {
|
||||||
|
active: anotherUserFlow.active,
|
||||||
|
id: anotherUserFlow.id,
|
||||||
|
name: anotherUserFlow.name,
|
||||||
|
status: 'draft',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
appKey: triggerStep.appKey,
|
||||||
|
connection: null,
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${triggerStep.appKey}/assets/favicon.svg`,
|
||||||
|
id: triggerStep.id,
|
||||||
|
key: 'catchRawWebhook',
|
||||||
|
parameters: {},
|
||||||
|
position: 1,
|
||||||
|
status: triggerStep.status,
|
||||||
|
type: 'trigger',
|
||||||
|
webhookUrl: `${appConfig.baseUrl}/webhooks/flows/${anotherUserFlow.id}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
appKey: actionStep.appKey,
|
||||||
|
connection: {
|
||||||
|
createdAt: actionConnection.createdAt
|
||||||
|
.getTime()
|
||||||
|
.toString(),
|
||||||
|
id: actionConnection.id,
|
||||||
|
verified: actionConnection.verified,
|
||||||
|
},
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${actionStep.appKey}/assets/favicon.svg`,
|
||||||
|
id: actionStep.id,
|
||||||
|
key: 'translateText',
|
||||||
|
parameters: {},
|
||||||
|
position: 1,
|
||||||
|
status: actionStep.status,
|
||||||
|
type: 'action',
|
||||||
|
webhookUrl: 'http://localhost:3000/null',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -17,6 +17,7 @@ describe('graphQL getRole query', () => {
|
|||||||
userWithoutPermissions,
|
userWithoutPermissions,
|
||||||
tokenWithPermissions,
|
tokenWithPermissions,
|
||||||
tokenWithoutPermissions,
|
tokenWithoutPermissions,
|
||||||
|
invalidToken,
|
||||||
permissionOne,
|
permissionOne,
|
||||||
permissionTwo;
|
permissionTwo;
|
||||||
|
|
||||||
@@ -73,91 +74,108 @@ describe('graphQL getRole query', () => {
|
|||||||
tokenWithoutPermissions = createAuthTokenByUserId(
|
tokenWithoutPermissions = createAuthTokenByUserId(
|
||||||
userWithoutPermissions.id
|
userWithoutPermissions.id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
invalidToken = 'invalid-token';
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and with valid license', () => {
|
describe('with unauthenticated user', () => {
|
||||||
beforeEach(async () => {
|
it('should throw not authorized error', async () => {
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', invalidToken)
|
||||||
|
.send({ query: queryWithValidRole })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('and without permissions', () => {
|
describe('with authenticated user', () => {
|
||||||
it('should throw not authorized error', async () => {
|
describe('and with valid license', () => {
|
||||||
const response = await request(app)
|
beforeEach(async () => {
|
||||||
.post('/graphql')
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||||
.set('Authorization', tokenWithoutPermissions)
|
|
||||||
.send({ query: queryWithValidRole })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('and correct permissions', () => {
|
describe('and without permissions', () => {
|
||||||
it('should return role data for a valid role id', async () => {
|
it('should throw not authorized error', async () => {
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/graphql')
|
.post('/graphql')
|
||||||
.set('Authorization', tokenWithPermissions)
|
.set('Authorization', tokenWithoutPermissions)
|
||||||
.send({ query: queryWithValidRole })
|
.send({ query: queryWithValidRole })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
expect(response.body.errors).toBeDefined();
|
||||||
data: {
|
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
||||||
getRole: {
|
});
|
||||||
description: validRole.description,
|
});
|
||||||
id: validRole.id,
|
|
||||||
isAdmin: validRole.key === 'admin',
|
describe('and correct permissions', () => {
|
||||||
key: validRole.key,
|
it('should return role data for a valid role id', async () => {
|
||||||
name: validRole.name,
|
const response = await request(app)
|
||||||
permissions: [
|
.post('/graphql')
|
||||||
{
|
.set('Authorization', tokenWithPermissions)
|
||||||
action: permissionOne.action,
|
.send({ query: queryWithValidRole })
|
||||||
conditions: permissionOne.conditions,
|
.expect(200);
|
||||||
id: permissionOne.id,
|
|
||||||
subject: permissionOne.subject,
|
const expectedResponsePayload = {
|
||||||
},
|
data: {
|
||||||
{
|
getRole: {
|
||||||
action: permissionTwo.action,
|
description: validRole.description,
|
||||||
conditions: permissionTwo.conditions,
|
id: validRole.id,
|
||||||
id: permissionTwo.id,
|
isAdmin: validRole.key === 'admin',
|
||||||
subject: permissionTwo.subject,
|
key: validRole.key,
|
||||||
},
|
name: validRole.name,
|
||||||
],
|
permissions: [
|
||||||
|
{
|
||||||
|
action: permissionOne.action,
|
||||||
|
conditions: permissionOne.conditions,
|
||||||
|
id: permissionOne.id,
|
||||||
|
subject: permissionOne.subject,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: permissionTwo.action,
|
||||||
|
conditions: permissionTwo.conditions,
|
||||||
|
id: permissionTwo.id,
|
||||||
|
subject: permissionTwo.subject,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return not found for invalid role id', async () => {
|
it('should return not found for invalid role id', async () => {
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/graphql')
|
.post('/graphql')
|
||||||
.set('Authorization', tokenWithPermissions)
|
.set('Authorization', tokenWithPermissions)
|
||||||
.send({ query: queryWithInvalidRole })
|
.send({ query: queryWithInvalidRole })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
expect(response.body.errors).toBeDefined();
|
||||||
expect(response.body.errors[0].message).toEqual('NotFoundError');
|
expect(response.body.errors[0].message).toEqual('NotFoundError');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('and without valid license', () => {
|
describe('and without valid license', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(false);
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and correct permissions', () => {
|
describe('and correct permissions', () => {
|
||||||
it('should throw not authorized error', async () => {
|
it('should throw not authorized error', async () => {
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/graphql')
|
.post('/graphql')
|
||||||
.set('Authorization', tokenWithPermissions)
|
.set('Authorization', tokenWithPermissions)
|
||||||
.send({ query: queryWithInvalidRole })
|
.send({ query: queryWithInvalidRole })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
expect(response.body.errors).toBeDefined();
|
||||||
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -15,7 +15,8 @@ describe('graphQL getRoles query', () => {
|
|||||||
userWithPermissions,
|
userWithPermissions,
|
||||||
userWithoutPermissions,
|
userWithoutPermissions,
|
||||||
tokenWithPermissions,
|
tokenWithPermissions,
|
||||||
tokenWithoutPermissions;
|
tokenWithoutPermissions,
|
||||||
|
invalidToken;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
currentUserRole = await createRole({ name: 'Current user role' });
|
currentUserRole = await createRole({ name: 'Current user role' });
|
||||||
@@ -52,82 +53,99 @@ describe('graphQL getRoles query', () => {
|
|||||||
tokenWithoutPermissions = createAuthTokenByUserId(
|
tokenWithoutPermissions = createAuthTokenByUserId(
|
||||||
userWithoutPermissions.id
|
userWithoutPermissions.id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
invalidToken = 'invalid-token';
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and with valid license', () => {
|
describe('with unauthenticated user', () => {
|
||||||
beforeEach(async () => {
|
it('should throw not authorized error', async () => {
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
const response = await request(app)
|
||||||
});
|
.post('/graphql')
|
||||||
|
.set('Authorization', invalidToken)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
describe('and without permissions', () => {
|
expect(response.body.errors).toBeDefined();
|
||||||
it('should throw not authorized error', async () => {
|
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', tokenWithoutPermissions)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and correct permissions', () => {
|
|
||||||
it('should return roles data', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', tokenWithPermissions)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getRoles: [
|
|
||||||
{
|
|
||||||
description: currentUserRole.description,
|
|
||||||
id: currentUserRole.id,
|
|
||||||
isAdmin: currentUserRole.key === 'admin',
|
|
||||||
key: currentUserRole.key,
|
|
||||||
name: currentUserRole.name,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: roleOne.description,
|
|
||||||
id: roleOne.id,
|
|
||||||
isAdmin: roleOne.key === 'admin',
|
|
||||||
key: roleOne.key,
|
|
||||||
name: roleOne.name,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: roleSecond.description,
|
|
||||||
id: roleSecond.id,
|
|
||||||
isAdmin: roleSecond.key === 'admin',
|
|
||||||
key: roleSecond.key,
|
|
||||||
name: roleSecond.name,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and without valid license', () => {
|
describe('with authenticated user', () => {
|
||||||
beforeEach(async () => {
|
describe('and with valid license', () => {
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(false);
|
beforeEach(async () => {
|
||||||
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and without permissions', () => {
|
||||||
|
it('should throw not authorized error', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', tokenWithoutPermissions)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and correct permissions', () => {
|
||||||
|
it('should return roles data', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', tokenWithPermissions)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getRoles: [
|
||||||
|
{
|
||||||
|
description: currentUserRole.description,
|
||||||
|
id: currentUserRole.id,
|
||||||
|
isAdmin: currentUserRole.key === 'admin',
|
||||||
|
key: currentUserRole.key,
|
||||||
|
name: currentUserRole.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: roleOne.description,
|
||||||
|
id: roleOne.id,
|
||||||
|
isAdmin: roleOne.key === 'admin',
|
||||||
|
key: roleOne.key,
|
||||||
|
name: roleOne.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: roleSecond.description,
|
||||||
|
id: roleSecond.id,
|
||||||
|
isAdmin: roleSecond.key === 'admin',
|
||||||
|
key: roleSecond.key,
|
||||||
|
name: roleSecond.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and correct permissions', () => {
|
describe('and without valid license', () => {
|
||||||
it('should throw not authorized error', async () => {
|
beforeEach(async () => {
|
||||||
const response = await request(app)
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(false);
|
||||||
.post('/graphql')
|
});
|
||||||
.set('Authorization', tokenWithPermissions)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
describe('and correct permissions', () => {
|
||||||
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
it('should throw not authorized error', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', tokenWithPermissions)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -16,46 +16,34 @@ describe('graphQL getTrialStatus query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
let user, userToken;
|
const invalidToken = 'invalid-token';
|
||||||
|
|
||||||
beforeEach(async () => {
|
describe('with unauthenticated user', () => {
|
||||||
const trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
|
it('should throw not authorized error', async () => {
|
||||||
|
|
||||||
user = await createUser({ trialExpiryDate });
|
|
||||||
userToken = createAuthTokenByUserId(user.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with cloud flag disabled', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return null', async () => {
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/graphql')
|
.post('/graphql')
|
||||||
.set('Authorization', userToken)
|
.set('Authorization', invalidToken)
|
||||||
.send({ query })
|
.send({ query })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
expect(response.body.errors).toBeDefined();
|
||||||
data: { getTrialStatus: null },
|
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and with cloud flag enabled', () => {
|
describe('with authenticated user', () => {
|
||||||
|
let user, userToken;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
|
const trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
|
||||||
|
|
||||||
|
user = await createUser({ trialExpiryDate });
|
||||||
|
userToken = createAuthTokenByUserId(user.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and not in trial and has active subscription', () => {
|
describe('and with cloud flag disabled', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.spyOn(User.prototype, 'inTrial').mockResolvedValue(false);
|
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
|
||||||
vi.spyOn(User.prototype, 'hasActiveSubscription').mockResolvedValue(
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null', async () => {
|
it('should return null', async () => {
|
||||||
@@ -73,27 +61,56 @@ describe('graphQL getTrialStatus query', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and in trial period', () => {
|
describe('and with cloud flag enabled', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.spyOn(User.prototype, 'inTrial').mockResolvedValue(true);
|
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null', async () => {
|
describe('and not in trial and has active subscription', () => {
|
||||||
const response = await request(app)
|
beforeEach(async () => {
|
||||||
.post('/graphql')
|
vi.spyOn(User.prototype, 'inTrial').mockResolvedValue(false);
|
||||||
.set('Authorization', userToken)
|
vi.spyOn(User.prototype, 'hasActiveSubscription').mockResolvedValue(
|
||||||
.send({ query })
|
true
|
||||||
.expect(200);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
it('should return null', async () => {
|
||||||
data: {
|
const response = await request(app)
|
||||||
getTrialStatus: {
|
.post('/graphql')
|
||||||
expireAt: new Date(user.trialExpiryDate).getTime().toString(),
|
.set('Authorization', userToken)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: { getTrialStatus: null },
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and in trial period', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.spyOn(User.prototype, 'inTrial').mockResolvedValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', userToken)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getTrialStatus: {
|
||||||
|
expireAt: new Date(user.trialExpiryDate).getTime().toString(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -8,12 +8,37 @@ import { createPermission } from '../../../test/factories/permission';
|
|||||||
import { createUser } from '../../../test/factories/user';
|
import { createUser } from '../../../test/factories/user';
|
||||||
|
|
||||||
describe('graphQL getUser query', () => {
|
describe('graphQL getUser query', () => {
|
||||||
describe('and without permissions', () => {
|
describe('with unauthenticated user', () => {
|
||||||
it('should throw not authorized error', async () => {
|
it('should throw not authorized error', async () => {
|
||||||
const userWithoutPermissions = await createUser();
|
const invalidUserId = '123123123';
|
||||||
const anotherUser = await createUser();
|
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
|
query {
|
||||||
|
getUser(id: "${invalidUserId}") {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', 'invalid-token')
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with authenticated user', () => {
|
||||||
|
describe('and without permissions', () => {
|
||||||
|
it('should throw not authorized error', async () => {
|
||||||
|
const userWithoutPermissions = await createUser();
|
||||||
|
const anotherUser = await createUser();
|
||||||
|
|
||||||
|
const query = `
|
||||||
query {
|
query {
|
||||||
getUser(id: "${anotherUser.id}") {
|
getUser(id: "${anotherUser.id}") {
|
||||||
id
|
id
|
||||||
@@ -22,48 +47,50 @@ describe('graphQL getUser query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/graphql')
|
.post('/graphql')
|
||||||
.set('Authorization', token)
|
.set('Authorization', token)
|
||||||
.send({ query })
|
.send({ query })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
expect(response.body.errors).toBeDefined();
|
||||||
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and correct permissions', () => {
|
|
||||||
let role, currentUser, anotherUser, token, requestObject;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
role = await createRole({
|
|
||||||
key: 'sample',
|
|
||||||
name: 'sample',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'User',
|
|
||||||
roleId: role.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
currentUser = await createUser({
|
|
||||||
roleId: role.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
anotherUser = await createUser({
|
|
||||||
roleId: role.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
requestObject = request(app).post('/graphql').set('Authorization', token);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return user data for a valid user id', async () => {
|
describe('and correct permissions', () => {
|
||||||
const query = `
|
let role, currentUser, anotherUser, token, requestObject;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
role = await createRole({
|
||||||
|
key: 'sample',
|
||||||
|
name: 'sample',
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'User',
|
||||||
|
roleId: role.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
currentUser = await createUser({
|
||||||
|
roleId: role.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
anotherUser = await createUser({
|
||||||
|
roleId: role.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
requestObject = request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', token);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return user data for a valid user id', async () => {
|
||||||
|
const query = `
|
||||||
query {
|
query {
|
||||||
getUser(id: "${anotherUser.id}") {
|
getUser(id: "${anotherUser.id}") {
|
||||||
id
|
id
|
||||||
@@ -80,26 +107,26 @@ describe('graphQL getUser query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const response = await requestObject.send({ query }).expect(200);
|
const response = await requestObject.send({ query }).expect(200);
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
const expectedResponsePayload = {
|
||||||
data: {
|
data: {
|
||||||
getUser: {
|
getUser: {
|
||||||
createdAt: anotherUser.createdAt.getTime().toString(),
|
createdAt: anotherUser.createdAt.getTime().toString(),
|
||||||
email: anotherUser.email,
|
email: anotherUser.email,
|
||||||
fullName: anotherUser.fullName,
|
fullName: anotherUser.fullName,
|
||||||
id: anotherUser.id,
|
id: anotherUser.id,
|
||||||
role: { id: role.id, name: role.name },
|
role: { id: role.id, name: role.name },
|
||||||
updatedAt: anotherUser.updatedAt.getTime().toString(),
|
updatedAt: anotherUser.updatedAt.getTime().toString(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return user password for a valid user id', async () => {
|
it('should not return user password for a valid user id', async () => {
|
||||||
const query = `
|
const query = `
|
||||||
query {
|
query {
|
||||||
getUser(id: "${anotherUser.id}") {
|
getUser(id: "${anotherUser.id}") {
|
||||||
id
|
id
|
||||||
@@ -109,18 +136,18 @@ describe('graphQL getUser query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const response = await requestObject.send({ query }).expect(400);
|
const response = await requestObject.send({ query }).expect(400);
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
expect(response.body.errors).toBeDefined();
|
||||||
expect(response.body.errors[0].message).toEqual(
|
expect(response.body.errors[0].message).toEqual(
|
||||||
'Cannot query field "password" on type "User".'
|
'Cannot query field "password" on type "User".'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return not found for invalid user id', async () => {
|
it('should return not found for invalid user id', async () => {
|
||||||
const invalidUserId = Crypto.randomUUID();
|
const invalidUserId = Crypto.randomUUID();
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
query {
|
query {
|
||||||
getUser(id: "${invalidUserId}") {
|
getUser(id: "${invalidUserId}") {
|
||||||
id
|
id
|
||||||
@@ -137,10 +164,11 @@ describe('graphQL getUser query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const response = await requestObject.send({ query }).expect(200);
|
const response = await requestObject.send({ query }).expect(200);
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
expect(response.body.errors).toBeDefined();
|
||||||
expect(response.body.errors[0].message).toEqual('NotFoundError');
|
expect(response.body.errors[0].message).toEqual('NotFoundError');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -30,95 +30,111 @@ describe('graphQL getUsers query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
describe('and without permissions', () => {
|
describe('with unauthenticated user', () => {
|
||||||
it('should throw not authorized error', async () => {
|
it('should throw not authorized error', async () => {
|
||||||
const userWithoutPermissions = await createUser();
|
|
||||||
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/graphql')
|
.post('/graphql')
|
||||||
.set('Authorization', token)
|
.set('Authorization', 'invalid-token')
|
||||||
.send({ query })
|
.send({ query })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
expect(response.body.errors).toBeDefined();
|
||||||
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and with correct permissions', () => {
|
describe('with authenticated user', () => {
|
||||||
let role, currentUser, anotherUser, token, requestObject;
|
describe('and without permissions', () => {
|
||||||
|
it('should throw not authorized error', async () => {
|
||||||
|
const userWithoutPermissions = await createUser();
|
||||||
|
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
||||||
|
|
||||||
beforeEach(async () => {
|
const response = await request(app)
|
||||||
role = await createRole({
|
.post('/graphql')
|
||||||
key: 'sample',
|
.set('Authorization', token)
|
||||||
name: 'sample',
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
||||||
});
|
});
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'User',
|
|
||||||
roleId: role.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
currentUser = await createUser({
|
|
||||||
roleId: role.id,
|
|
||||||
fullName: 'Current User',
|
|
||||||
});
|
|
||||||
|
|
||||||
anotherUser = await createUser({
|
|
||||||
roleId: role.id,
|
|
||||||
fullName: 'Another User',
|
|
||||||
});
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
requestObject = request(app).post('/graphql').set('Authorization', token);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return users data', async () => {
|
describe('and with correct permissions', () => {
|
||||||
const response = await requestObject.send({ query }).expect(200);
|
let role, currentUser, anotherUser, token, requestObject;
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
beforeEach(async () => {
|
||||||
data: {
|
role = await createRole({
|
||||||
getUsers: {
|
key: 'sample',
|
||||||
edges: [
|
name: 'sample',
|
||||||
{
|
});
|
||||||
node: {
|
|
||||||
email: anotherUser.email,
|
await createPermission({
|
||||||
fullName: anotherUser.fullName,
|
action: 'read',
|
||||||
id: anotherUser.id,
|
subject: 'User',
|
||||||
role: {
|
roleId: role.id,
|
||||||
id: role.id,
|
});
|
||||||
name: role.name,
|
|
||||||
|
currentUser = await createUser({
|
||||||
|
roleId: role.id,
|
||||||
|
fullName: 'Current User',
|
||||||
|
});
|
||||||
|
|
||||||
|
anotherUser = await createUser({
|
||||||
|
roleId: role.id,
|
||||||
|
fullName: 'Another User',
|
||||||
|
});
|
||||||
|
|
||||||
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
requestObject = request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', token);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return users data', async () => {
|
||||||
|
const response = await requestObject.send({ query }).expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getUsers: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
email: anotherUser.email,
|
||||||
|
fullName: anotherUser.fullName,
|
||||||
|
id: anotherUser.id,
|
||||||
|
role: {
|
||||||
|
id: role.id,
|
||||||
|
name: role.name,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
node: {
|
||||||
node: {
|
email: currentUser.email,
|
||||||
email: currentUser.email,
|
fullName: currentUser.fullName,
|
||||||
fullName: currentUser.fullName,
|
id: currentUser.id,
|
||||||
id: currentUser.id,
|
role: {
|
||||||
role: {
|
id: role.id,
|
||||||
id: role.id,
|
name: role.name,
|
||||||
name: role.name,
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
pageInfo: {
|
||||||
|
currentPage: 1,
|
||||||
|
totalPages: 1,
|
||||||
},
|
},
|
||||||
],
|
totalCount: 2,
|
||||||
pageInfo: {
|
|
||||||
currentPage: 1,
|
|
||||||
totalPages: 1,
|
|
||||||
},
|
},
|
||||||
totalCount: 2,
|
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return users data with password', async () => {
|
it('should not return users data with password', async () => {
|
||||||
const query = `
|
const query = `
|
||||||
query {
|
query {
|
||||||
getUsers(limit: 10, offset: 0) {
|
getUsers(limit: 10, offset: 0) {
|
||||||
pageInfo {
|
pageInfo {
|
||||||
@@ -137,12 +153,13 @@ describe('graphQL getUsers query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const response = await requestObject.send({ query }).expect(400);
|
const response = await requestObject.send({ query }).expect(400);
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
expect(response.body.errors).toBeDefined();
|
||||||
expect(response.body.errors[0].message).toEqual(
|
expect(response.body.errors[0].message).toEqual(
|
||||||
'Cannot query field "password" on type "User".'
|
'Cannot query field "password" on type "User".'
|
||||||
);
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -3,7 +3,7 @@ import jwt from 'jsonwebtoken';
|
|||||||
import appConfig from '../config/app.js';
|
import appConfig from '../config/app.js';
|
||||||
import User from '../models/user.js';
|
import User from '../models/user.js';
|
||||||
|
|
||||||
export const isAuthenticated = async (_parent, _args, req) => {
|
const isAuthenticated = rule()(async (_parent, _args, req) => {
|
||||||
const token = req.headers['authorization'];
|
const token = req.headers['authorization'];
|
||||||
|
|
||||||
if (token == null) return false;
|
if (token == null) return false;
|
||||||
@@ -26,32 +26,29 @@ export const isAuthenticated = async (_parent, _args, req) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
const isAuthenticatedRule = rule()(isAuthenticated);
|
const authentication = shield(
|
||||||
|
{
|
||||||
export const authenticationRules = {
|
Query: {
|
||||||
Query: {
|
'*': isAuthenticated,
|
||||||
'*': isAuthenticatedRule,
|
getAutomatischInfo: allow,
|
||||||
getAutomatischInfo: allow,
|
getConfig: allow,
|
||||||
getConfig: allow,
|
getNotifications: allow,
|
||||||
getNotifications: allow,
|
healthcheck: allow,
|
||||||
healthcheck: allow,
|
listSamlAuthProviders: allow,
|
||||||
listSamlAuthProviders: allow,
|
},
|
||||||
|
Mutation: {
|
||||||
|
'*': isAuthenticated,
|
||||||
|
forgotPassword: allow,
|
||||||
|
login: allow,
|
||||||
|
registerUser: allow,
|
||||||
|
resetPassword: allow,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Mutation: {
|
{
|
||||||
'*': isAuthenticatedRule,
|
allowExternalErrors: true,
|
||||||
forgotPassword: allow,
|
}
|
||||||
login: allow,
|
);
|
||||||
registerUser: allow,
|
|
||||||
resetPassword: allow,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const authenticationOptions = {
|
|
||||||
allowExternalErrors: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const authentication = shield(authenticationRules, authenticationOptions);
|
|
||||||
|
|
||||||
export default authentication;
|
export default authentication;
|
||||||
|
@@ -1,78 +0,0 @@
|
|||||||
import { describe, it, expect, vi } from 'vitest';
|
|
||||||
import { allow } from 'graphql-shield';
|
|
||||||
import jwt from 'jsonwebtoken';
|
|
||||||
import User from '../models/user.js';
|
|
||||||
import { isAuthenticated, authenticationRules } from './authentication.js';
|
|
||||||
|
|
||||||
vi.mock('jsonwebtoken');
|
|
||||||
vi.mock('../models/user.js');
|
|
||||||
|
|
||||||
describe('isAuthenticated', () => {
|
|
||||||
it('should return false if no token is provided', async () => {
|
|
||||||
const req = { headers: {} };
|
|
||||||
expect(await isAuthenticated(null, null, req)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false if token is invalid', async () => {
|
|
||||||
jwt.verify.mockImplementation(() => {
|
|
||||||
throw new Error('invalid token');
|
|
||||||
});
|
|
||||||
|
|
||||||
const req = { headers: { authorization: 'invalidToken' } };
|
|
||||||
expect(await isAuthenticated(null, null, req)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true if token is valid', async () => {
|
|
||||||
jwt.verify.mockReturnValue({ userId: '123' });
|
|
||||||
|
|
||||||
User.query.mockReturnValue({
|
|
||||||
findById: vi.fn().mockReturnValue({
|
|
||||||
leftJoinRelated: vi.fn().mockReturnThis(),
|
|
||||||
withGraphFetched: vi
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValue({ id: '123', role: {}, permissions: {} }),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const req = { headers: { authorization: 'validToken' } };
|
|
||||||
expect(await isAuthenticated(null, null, req)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('authentication rules', () => {
|
|
||||||
const getQueryAndMutationNames = (rules) => {
|
|
||||||
const queries = Object.keys(rules.Query || {});
|
|
||||||
const mutations = Object.keys(rules.Mutation || {});
|
|
||||||
return { queries, mutations };
|
|
||||||
};
|
|
||||||
|
|
||||||
const { queries, mutations } = getQueryAndMutationNames(authenticationRules);
|
|
||||||
|
|
||||||
describe('for queries', () => {
|
|
||||||
queries.forEach((query) => {
|
|
||||||
it(`should apply correct rule for query: ${query}`, () => {
|
|
||||||
const ruleApplied = authenticationRules.Query[query];
|
|
||||||
|
|
||||||
if (query === '*') {
|
|
||||||
expect(ruleApplied.func).toBe(isAuthenticated);
|
|
||||||
} else {
|
|
||||||
expect(ruleApplied).toEqual(allow);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('for mutations', () => {
|
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
it(`should apply correct rule for mutation: ${mutation}`, () => {
|
|
||||||
const ruleApplied = authenticationRules.Mutation[mutation];
|
|
||||||
|
|
||||||
if (mutation === '*') {
|
|
||||||
expect(ruleApplied.func).toBe(isAuthenticated);
|
|
||||||
} else {
|
|
||||||
expect(ruleApplied).toBe(allow);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -32,15 +32,6 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
sidebar: {
|
sidebar: {
|
||||||
'/apps/': [
|
'/apps/': [
|
||||||
{
|
|
||||||
text: 'Better Stack',
|
|
||||||
collapsible: true,
|
|
||||||
collapsed: true,
|
|
||||||
items: [
|
|
||||||
{ text: 'Actions', link: '/apps/better-stack/actions' },
|
|
||||||
{ text: 'Connection', link: '/apps/better-stack/connection' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
text: 'Carbone',
|
text: 'Carbone',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
@@ -314,7 +305,7 @@ export default defineConfig({
|
|||||||
collapsed: true,
|
collapsed: true,
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Actions', link: '/apps/removebg/actions' },
|
{ text: 'Actions', link: '/apps/removebg/actions' },
|
||||||
{ text: 'Connection', link: '/apps/removebg/connection' },
|
{ text: 'Connection', link: '/apps/removebg/connection' }
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
favicon: /favicons/better-stack.svg
|
|
||||||
items:
|
|
||||||
- name: Acknowledge incident
|
|
||||||
desc: Acknowledges an incident.
|
|
||||||
- name: Create incident
|
|
||||||
desc: Creates an incident that informs the team.
|
|
||||||
---
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import CustomListing from '../../components/CustomListing.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<CustomListing />
|
|
@@ -1,14 +0,0 @@
|
|||||||
# Better Stack
|
|
||||||
|
|
||||||
:::info
|
|
||||||
This page explains the steps you need to follow to set up the Better Stack
|
|
||||||
connection in Automatisch. If any of the steps are outdated, please let us know!
|
|
||||||
:::
|
|
||||||
|
|
||||||
1. Login to your Better Stack account: [https://betterstack.com/](https://betterstack.com/).
|
|
||||||
2. Click on the team name bottom left and select **Manage Teams** option.
|
|
||||||
3. Click on the three dots icon of your team and select **manage** option.
|
|
||||||
4. Click on the **API tokens** tab.
|
|
||||||
5. Copy the token next to **Direct API tokens** to the `API Key` field on Automatisch.
|
|
||||||
6. Fill the screen name on Automatisch.
|
|
||||||
7. Now, you can start using the Better Stack connection with Automatisch.
|
|
@@ -7,6 +7,8 @@ items:
|
|||||||
desc: Creates a page inside a parent page.
|
desc: Creates a page inside a parent page.
|
||||||
- name: Find database item
|
- name: Find database item
|
||||||
desc: Searches for an item in a database by property.
|
desc: Searches for an item in a database by property.
|
||||||
|
- name: Update database item
|
||||||
|
desc: Updates a database item.
|
||||||
---
|
---
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
The following integrations are currently supported by Automatisch.
|
The following integrations are currently supported by Automatisch.
|
||||||
|
|
||||||
- [Better Stack](/apps/better-stack/actions)
|
|
||||||
- [Carbone](/apps/carbone/actions)
|
- [Carbone](/apps/carbone/actions)
|
||||||
- [DeepL](/apps/deepl/actions)
|
- [DeepL](/apps/deepl/actions)
|
||||||
- [Delay](/apps/delay/actions)
|
- [Delay](/apps/delay/actions)
|
||||||
|
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
|
||||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
||||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
|
|
||||||
preserveAspectRatio="xMidYMid meet">
|
|
||||||
|
|
||||||
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
|
|
||||||
fill="#000000" stroke="none">
|
|
||||||
<path d="M0 1000 l0 -1000 1000 0 1000 0 0 1000 0 1000 -1000 0 -1000 0 0
|
|
||||||
-1000z m1162 460 c14 -11 113 -184 232 -408 228 -429 231 -439 175 -486 -35
|
|
||||||
-30 -30 -29 -140 -15 -89 12 -123 25 -152 56 -9 11 -72 147 -140 304 -113 263
|
|
||||||
-124 284 -149 287 -14 2 -29 10 -32 17 -8 21 67 214 94 242 28 29 78 30 112 3z
|
|
||||||
m-340 -148 c10 -10 72 -175 139 -367 114 -325 121 -351 108 -374 -8 -14 -27
|
|
||||||
-32 -41 -41 -25 -13 -34 -12 -126 18 -55 18 -111 43 -125 56 -19 17 -40 67
|
|
||||||
-76 182 -36 112 -58 164 -73 176 l-22 16 27 99 c63 224 66 232 95 248 31 17
|
|
||||||
69 12 94 -13z m-314 -219 c16 -15 26 -59 56 -243 42 -262 43 -285 17 -300 -11
|
|
||||||
-5 -24 -10 -30 -10 -19 0 -140 114 -150 141 -7 20 -4 76 10 191 10 90 19 171
|
|
||||||
19 181 0 18 33 57 49 57 5 0 18 -8 29 -17z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.1 KiB |
Reference in New Issue
Block a user