Compare commits
1 Commits
dependabot
...
AUT-1324
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d31309a92d |
4
.github/workflows/playwright.yml
vendored
4
.github/workflows/playwright.yml
vendored
@@ -12,6 +12,9 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
BULLMQ_DASHBOARD_USERNAME: root
|
||||||
|
BULLMQ_DASHBOARD_PASSWORD: sample
|
||||||
|
ENABLE_BULLMQ_DASHBOARD: true
|
||||||
ENCRYPTION_KEY: sample_encryption_key
|
ENCRYPTION_KEY: sample_encryption_key
|
||||||
WEBHOOK_SECRET_KEY: sample_webhook_secret_key
|
WEBHOOK_SECRET_KEY: sample_webhook_secret_key
|
||||||
APP_SECRET_KEY: sample_app_secret_key
|
APP_SECRET_KEY: sample_app_secret_key
|
||||||
@@ -22,6 +25,7 @@ env:
|
|||||||
POSTGRES_PASSWORD: automatisch_password
|
POSTGRES_PASSWORD: automatisch_password
|
||||||
REDIS_HOST: localhost
|
REDIS_HOST: localhost
|
||||||
APP_ENV: production
|
APP_ENV: production
|
||||||
|
PORT: 3000
|
||||||
LICENSE_KEY: dummy_license_key
|
LICENSE_KEY: dummy_license_key
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
@@ -10,11 +10,11 @@ export default async (request, response) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const appConfigParams = (request) => {
|
const appConfigParams = (request) => {
|
||||||
const { customConnectionAllowed, shared, disabled } = request.body;
|
const { allowCustomConnection, shared, disabled } = request.body;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: request.params.appKey,
|
key: request.params.appKey,
|
||||||
customConnectionAllowed,
|
allowCustomConnection,
|
||||||
shared,
|
shared,
|
||||||
disabled,
|
disabled,
|
||||||
};
|
};
|
||||||
|
@@ -23,7 +23,7 @@ describe('POST /api/v1/admin/apps/:appKey/config', () => {
|
|||||||
|
|
||||||
it('should return created app config', async () => {
|
it('should return created app config', async () => {
|
||||||
const appConfig = {
|
const appConfig = {
|
||||||
customConnectionAllowed: true,
|
allowCustomConnection: true,
|
||||||
shared: true,
|
shared: true,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
};
|
};
|
||||||
@@ -44,7 +44,7 @@ describe('POST /api/v1/admin/apps/:appKey/config', () => {
|
|||||||
it('should return HTTP 422 for already existing app config', async () => {
|
it('should return HTTP 422 for already existing app config', async () => {
|
||||||
const appConfig = {
|
const appConfig = {
|
||||||
key: 'gitlab',
|
key: 'gitlab',
|
||||||
customConnectionAllowed: true,
|
allowCustomConnection: true,
|
||||||
shared: true,
|
shared: true,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
};
|
};
|
||||||
|
@@ -8,19 +8,16 @@ export default async (request, response) => {
|
|||||||
})
|
})
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
await appConfig.$query().patchAndFetch({
|
await appConfig.$query().patchAndFetch(appConfigParams(request));
|
||||||
...appConfigParams(request),
|
|
||||||
key: request.params.appKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
renderObject(response, appConfig);
|
renderObject(response, appConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
const appConfigParams = (request) => {
|
const appConfigParams = (request) => {
|
||||||
const { customConnectionAllowed, shared, disabled } = request.body;
|
const { allowCustomConnection, shared, disabled } = request.body;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
customConnectionAllowed,
|
allowCustomConnection,
|
||||||
shared,
|
shared,
|
||||||
disabled,
|
disabled,
|
||||||
};
|
};
|
||||||
|
@@ -24,7 +24,7 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => {
|
|||||||
it('should return updated app config', async () => {
|
it('should return updated app config', async () => {
|
||||||
const appConfig = {
|
const appConfig = {
|
||||||
key: 'gitlab',
|
key: 'gitlab',
|
||||||
customConnectionAllowed: true,
|
allowCustomConnection: true,
|
||||||
shared: true,
|
shared: true,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
};
|
};
|
||||||
@@ -34,7 +34,7 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => {
|
|||||||
const newAppConfigValues = {
|
const newAppConfigValues = {
|
||||||
shared: false,
|
shared: false,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
customConnectionAllowed: false,
|
allowCustomConnection: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
@@ -55,7 +55,7 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => {
|
|||||||
const appConfig = {
|
const appConfig = {
|
||||||
shared: false,
|
shared: false,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
customConnectionAllowed: false,
|
allowCustomConnection: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
await request(app)
|
await request(app)
|
||||||
@@ -68,7 +68,7 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => {
|
|||||||
it('should return HTTP 422 for invalid app config data', async () => {
|
it('should return HTTP 422 for invalid app config data', async () => {
|
||||||
const appConfig = {
|
const appConfig = {
|
||||||
key: 'gitlab',
|
key: 'gitlab',
|
||||||
customConnectionAllowed: true,
|
allowCustomConnection: true,
|
||||||
shared: true,
|
shared: true,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
};
|
};
|
||||||
|
@@ -155,7 +155,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
|
|||||||
await createAppConfig({
|
await createAppConfig({
|
||||||
key: 'gitlab',
|
key: 'gitlab',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
customConnectionAllowed: true,
|
allowCustomConnection: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
|
|||||||
await createAppConfig({
|
await createAppConfig({
|
||||||
key: 'gitlab',
|
key: 'gitlab',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
customConnectionAllowed: false,
|
allowCustomConnection: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -17,7 +17,7 @@ describe('GET /api/v1/apps/:appKey/config', () => {
|
|||||||
|
|
||||||
appConfig = await createAppConfig({
|
appConfig = await createAppConfig({
|
||||||
key: 'deepl',
|
key: 'deepl',
|
||||||
customConnectionAllowed: true,
|
allowCustomConnection: true,
|
||||||
shared: true,
|
shared: true,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
});
|
});
|
||||||
|
@@ -8,7 +8,7 @@ export default async (request, response) => {
|
|||||||
})
|
})
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
connection = await connection.updateFormattedData(connectionParams(request));
|
connection = await connection.update(connectionParams(request));
|
||||||
|
|
||||||
renderObject(response, connection);
|
renderObject(response, connection);
|
||||||
};
|
};
|
||||||
|
@@ -1,37 +0,0 @@
|
|||||||
export async function up(knex) {
|
|
||||||
await knex.schema.alterTable('app_configs', (table) => {
|
|
||||||
table.boolean('connection_allowed').defaultTo(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
const appConfigs = await knex('app_configs').select('*');
|
|
||||||
|
|
||||||
for (const appConfig of appConfigs) {
|
|
||||||
const appAuthClients = await knex('app_auth_clients').where(
|
|
||||||
'app_key',
|
|
||||||
appConfig.key
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasSomeActiveAppAuthClients = !!appAuthClients?.some(
|
|
||||||
(appAuthClient) => appAuthClient.active
|
|
||||||
);
|
|
||||||
const shared = appConfig.shared;
|
|
||||||
const active = appConfig.disabled === false;
|
|
||||||
|
|
||||||
const connectionAllowedConditions = [
|
|
||||||
hasSomeActiveAppAuthClients,
|
|
||||||
shared,
|
|
||||||
active,
|
|
||||||
];
|
|
||||||
const connectionAllowed = connectionAllowedConditions.every(Boolean);
|
|
||||||
|
|
||||||
await knex('app_configs')
|
|
||||||
.where('id', appConfig.id)
|
|
||||||
.update({ connection_allowed: connectionAllowed });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex) {
|
|
||||||
await knex.schema.alterTable('app_configs', (table) => {
|
|
||||||
table.dropColumn('connection_allowed');
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
export async function up(knex) {
|
|
||||||
return knex.schema.alterTable('app_configs', (table) => {
|
|
||||||
table.renameColumn('allow_custom_connection', 'custom_connection_allowed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex) {
|
|
||||||
return knex.schema.alterTable('app_configs', (table) => {
|
|
||||||
table.renameColumn('custom_connection_allowed', 'allow_custom_connection');
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
export async function up(knex) {
|
|
||||||
return knex.schema.alterTable('app_configs', function (table) {
|
|
||||||
table.dropPrimary();
|
|
||||||
table.primary('key');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex) {
|
|
||||||
return knex.schema.alterTable('app_configs', function (table) {
|
|
||||||
table.dropPrimary();
|
|
||||||
table.primary('id');
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
export async function up(knex) {
|
|
||||||
return knex.schema.alterTable('app_configs', function (table) {
|
|
||||||
table.dropColumn('id');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex) {
|
|
||||||
return knex.schema.alterTable('app_configs', function (table) {
|
|
||||||
table.uuid('id').defaultTo(knex.raw('gen_random_uuid()'));
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,41 +0,0 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
||||||
|
|
||||||
exports[`AppConfig model > jsonSchema should have correct validations 1`] = `
|
|
||||||
{
|
|
||||||
"properties": {
|
|
||||||
"connectionAllowed": {
|
|
||||||
"default": false,
|
|
||||||
"type": "boolean",
|
|
||||||
},
|
|
||||||
"createdAt": {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"customConnectionAllowed": {
|
|
||||||
"default": false,
|
|
||||||
"type": "boolean",
|
|
||||||
},
|
|
||||||
"disabled": {
|
|
||||||
"default": false,
|
|
||||||
"type": "boolean",
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"key": {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"shared": {
|
|
||||||
"default": false,
|
|
||||||
"type": "boolean",
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"key",
|
|
||||||
],
|
|
||||||
"type": "object",
|
|
||||||
}
|
|
||||||
`;
|
|
@@ -1,42 +0,0 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
||||||
|
|
||||||
exports[`Permission model > jsonSchema should have correct validations 1`] = `
|
|
||||||
{
|
|
||||||
"properties": {
|
|
||||||
"action": {
|
|
||||||
"minLength": 1,
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"conditions": {
|
|
||||||
"items": {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"type": "array",
|
|
||||||
},
|
|
||||||
"createdAt": {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"roleId": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"subject": {
|
|
||||||
"minLength": 1,
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"roleId",
|
|
||||||
"action",
|
|
||||||
"subject",
|
|
||||||
],
|
|
||||||
"type": "object",
|
|
||||||
}
|
|
||||||
`;
|
|
@@ -2,7 +2,6 @@ import AES from 'crypto-js/aes.js';
|
|||||||
import enc from 'crypto-js/enc-utf8.js';
|
import enc from 'crypto-js/enc-utf8.js';
|
||||||
import appConfig from '../config/app.js';
|
import appConfig from '../config/app.js';
|
||||||
import Base from './base.js';
|
import Base from './base.js';
|
||||||
import AppConfig from './app-config.js';
|
|
||||||
|
|
||||||
class AppAuthClient extends Base {
|
class AppAuthClient extends Base {
|
||||||
static tableName = 'app_auth_clients';
|
static tableName = 'app_auth_clients';
|
||||||
@@ -22,17 +21,6 @@ class AppAuthClient extends Base {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static relationMappings = () => ({
|
|
||||||
appConfig: {
|
|
||||||
relation: Base.BelongsToOneRelation,
|
|
||||||
modelClass: AppConfig,
|
|
||||||
join: {
|
|
||||||
from: 'app_auth_clients.app_key',
|
|
||||||
to: 'app_configs.key',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
encryptData() {
|
encryptData() {
|
||||||
if (!this.eligibleForEncryption()) return;
|
if (!this.eligibleForEncryption()) return;
|
||||||
|
|
||||||
@@ -60,17 +48,6 @@ class AppAuthClient extends Base {
|
|||||||
return this.authDefaults ? true : false;
|
return this.authDefaults ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async triggerAppConfigUpdate() {
|
|
||||||
const appConfig = await this.$relatedQuery('appConfig');
|
|
||||||
|
|
||||||
// This is a workaround to update connection allowed column for AppConfig
|
|
||||||
await appConfig?.$query().patch({
|
|
||||||
key: appConfig.key,
|
|
||||||
shared: appConfig.shared,
|
|
||||||
disabled: appConfig.disabled,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make another abstraction like beforeSave instead of using
|
// TODO: Make another abstraction like beforeSave instead of using
|
||||||
// beforeInsert and beforeUpdate separately for the same operation.
|
// beforeInsert and beforeUpdate separately for the same operation.
|
||||||
async $beforeInsert(queryContext) {
|
async $beforeInsert(queryContext) {
|
||||||
@@ -78,23 +55,11 @@ class AppAuthClient extends Base {
|
|||||||
this.encryptData();
|
this.encryptData();
|
||||||
}
|
}
|
||||||
|
|
||||||
async $afterInsert(queryContext) {
|
|
||||||
await super.$afterInsert(queryContext);
|
|
||||||
|
|
||||||
await this.triggerAppConfigUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
async $beforeUpdate(opt, queryContext) {
|
async $beforeUpdate(opt, queryContext) {
|
||||||
await super.$beforeUpdate(opt, queryContext);
|
await super.$beforeUpdate(opt, queryContext);
|
||||||
this.encryptData();
|
this.encryptData();
|
||||||
}
|
}
|
||||||
|
|
||||||
async $afterUpdate(opt, queryContext) {
|
|
||||||
await super.$afterUpdate(opt, queryContext);
|
|
||||||
|
|
||||||
await this.triggerAppConfigUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
async $afterFind() {
|
async $afterFind() {
|
||||||
this.decryptData();
|
this.decryptData();
|
||||||
}
|
}
|
||||||
|
@@ -2,12 +2,9 @@ import { describe, it, expect, vi } from 'vitest';
|
|||||||
import AES from 'crypto-js/aes.js';
|
import AES from 'crypto-js/aes.js';
|
||||||
import enc from 'crypto-js/enc-utf8.js';
|
import enc from 'crypto-js/enc-utf8.js';
|
||||||
|
|
||||||
import AppConfig from './app-config.js';
|
|
||||||
import AppAuthClient from './app-auth-client.js';
|
import AppAuthClient from './app-auth-client.js';
|
||||||
import Base from './base.js';
|
|
||||||
import appConfig from '../config/app.js';
|
import appConfig from '../config/app.js';
|
||||||
import { createAppAuthClient } from '../../test/factories/app-auth-client.js';
|
import { createAppAuthClient } from '../../test/factories/app-auth-client.js';
|
||||||
import { createAppConfig } from '../../test/factories/app-config.js';
|
|
||||||
|
|
||||||
describe('AppAuthClient model', () => {
|
describe('AppAuthClient model', () => {
|
||||||
it('tableName should return correct name', () => {
|
it('tableName should return correct name', () => {
|
||||||
@@ -18,23 +15,6 @@ describe('AppAuthClient model', () => {
|
|||||||
expect(AppAuthClient.jsonSchema).toMatchSnapshot();
|
expect(AppAuthClient.jsonSchema).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('relationMappings should return correct associations', () => {
|
|
||||||
const relationMappings = AppAuthClient.relationMappings();
|
|
||||||
|
|
||||||
const expectedRelations = {
|
|
||||||
appConfig: {
|
|
||||||
relation: Base.BelongsToOneRelation,
|
|
||||||
modelClass: AppConfig,
|
|
||||||
join: {
|
|
||||||
from: 'app_auth_clients.app_key',
|
|
||||||
to: 'app_configs.key',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(relationMappings).toStrictEqual(expectedRelations);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('encryptData', () => {
|
describe('encryptData', () => {
|
||||||
it('should return undefined if eligibleForEncryption is not true', async () => {
|
it('should return undefined if eligibleForEncryption is not true', async () => {
|
||||||
vi.spyOn(
|
vi.spyOn(
|
||||||
@@ -160,63 +140,6 @@ describe('AppAuthClient model', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('triggerAppConfigUpdate', () => {
|
|
||||||
it('should trigger an update in related app config', async () => {
|
|
||||||
await createAppConfig({ key: 'gitlab' });
|
|
||||||
|
|
||||||
const appAuthClient = await createAppAuthClient({
|
|
||||||
appKey: 'gitlab',
|
|
||||||
});
|
|
||||||
|
|
||||||
const appConfigBeforeUpdateSpy = vi.spyOn(
|
|
||||||
AppConfig.prototype,
|
|
||||||
'$beforeUpdate'
|
|
||||||
);
|
|
||||||
|
|
||||||
await appAuthClient.triggerAppConfigUpdate();
|
|
||||||
|
|
||||||
expect(appConfigBeforeUpdateSpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update related AppConfig after creating an instance', async () => {
|
|
||||||
const appConfig = await createAppConfig({
|
|
||||||
key: 'gitlab',
|
|
||||||
disabled: false,
|
|
||||||
shared: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await createAppAuthClient({
|
|
||||||
appKey: 'gitlab',
|
|
||||||
active: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const refetchedAppConfig = await appConfig.$query();
|
|
||||||
|
|
||||||
expect(refetchedAppConfig.connectionAllowed).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update related AppConfig after updating an instance', async () => {
|
|
||||||
const appConfig = await createAppConfig({
|
|
||||||
key: 'gitlab',
|
|
||||||
disabled: false,
|
|
||||||
shared: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const appAuthClient = await createAppAuthClient({
|
|
||||||
appKey: 'gitlab',
|
|
||||||
active: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let refetchedAppConfig = await appConfig.$query();
|
|
||||||
expect(refetchedAppConfig.connectionAllowed).toBe(false);
|
|
||||||
|
|
||||||
await appAuthClient.$query().patchAndFetch({ active: true });
|
|
||||||
|
|
||||||
refetchedAppConfig = await appConfig.$query();
|
|
||||||
expect(refetchedAppConfig.connectionAllowed).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('$beforeInsert should call AppAuthClient.encryptData', async () => {
|
it('$beforeInsert should call AppAuthClient.encryptData', async () => {
|
||||||
const appAuthClientBeforeInsertSpy = vi.spyOn(
|
const appAuthClientBeforeInsertSpy = vi.spyOn(
|
||||||
AppAuthClient.prototype,
|
AppAuthClient.prototype,
|
||||||
@@ -228,17 +151,6 @@ describe('AppAuthClient model', () => {
|
|||||||
expect(appAuthClientBeforeInsertSpy).toHaveBeenCalledOnce();
|
expect(appAuthClientBeforeInsertSpy).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('$afterInsert should call AppAuthClient.triggerAppConfigUpdate', async () => {
|
|
||||||
const appAuthClientAfterInsertSpy = vi.spyOn(
|
|
||||||
AppAuthClient.prototype,
|
|
||||||
'triggerAppConfigUpdate'
|
|
||||||
);
|
|
||||||
|
|
||||||
await createAppAuthClient();
|
|
||||||
|
|
||||||
expect(appAuthClientAfterInsertSpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('$beforeUpdate should call AppAuthClient.encryptData', async () => {
|
it('$beforeUpdate should call AppAuthClient.encryptData', async () => {
|
||||||
const appAuthClient = await createAppAuthClient();
|
const appAuthClient = await createAppAuthClient();
|
||||||
|
|
||||||
@@ -252,19 +164,6 @@ describe('AppAuthClient model', () => {
|
|||||||
expect(appAuthClientBeforeUpdateSpy).toHaveBeenCalledOnce();
|
expect(appAuthClientBeforeUpdateSpy).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('$afterUpdate should call AppAuthClient.triggerAppConfigUpdate', async () => {
|
|
||||||
const appAuthClient = await createAppAuthClient();
|
|
||||||
|
|
||||||
const appAuthClientAfterUpdateSpy = vi.spyOn(
|
|
||||||
AppAuthClient.prototype,
|
|
||||||
'triggerAppConfigUpdate'
|
|
||||||
);
|
|
||||||
|
|
||||||
await appAuthClient.$query().patchAndFetch({ name: 'sample' });
|
|
||||||
|
|
||||||
expect(appAuthClientAfterUpdateSpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('$afterFind should call AppAuthClient.decryptData', async () => {
|
it('$afterFind should call AppAuthClient.decryptData', async () => {
|
||||||
const appAuthClient = await createAppAuthClient();
|
const appAuthClient = await createAppAuthClient();
|
||||||
|
|
||||||
|
@@ -5,10 +5,6 @@ import Base from './base.js';
|
|||||||
class AppConfig extends Base {
|
class AppConfig extends Base {
|
||||||
static tableName = 'app_configs';
|
static tableName = 'app_configs';
|
||||||
|
|
||||||
static get idColumn() {
|
|
||||||
return 'key';
|
|
||||||
}
|
|
||||||
|
|
||||||
static jsonSchema = {
|
static jsonSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['key'],
|
required: ['key'],
|
||||||
@@ -16,8 +12,7 @@ class AppConfig extends Base {
|
|||||||
properties: {
|
properties: {
|
||||||
id: { type: 'string', format: 'uuid' },
|
id: { type: 'string', format: 'uuid' },
|
||||||
key: { type: 'string' },
|
key: { type: 'string' },
|
||||||
connectionAllowed: { type: 'boolean', default: false },
|
allowCustomConnection: { type: 'boolean', default: false },
|
||||||
customConnectionAllowed: { type: 'boolean', default: false },
|
|
||||||
shared: { type: 'boolean', default: false },
|
shared: { type: 'boolean', default: false },
|
||||||
disabled: { type: 'boolean', default: false },
|
disabled: { type: 'boolean', default: false },
|
||||||
createdAt: { type: 'string' },
|
createdAt: { type: 'string' },
|
||||||
@@ -36,44 +31,31 @@ class AppConfig extends Base {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static get virtualAttributes() {
|
||||||
|
return ['canConnect', 'canCustomConnect'];
|
||||||
|
}
|
||||||
|
|
||||||
|
get canCustomConnect() {
|
||||||
|
return !this.disabled && this.allowCustomConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
get canConnect() {
|
||||||
|
const hasSomeActiveAppAuthClients = !!this.appAuthClients?.some(
|
||||||
|
(appAuthClient) => appAuthClient.active
|
||||||
|
);
|
||||||
|
const shared = this.shared;
|
||||||
|
const active = this.disabled === false;
|
||||||
|
|
||||||
|
const conditions = [hasSomeActiveAppAuthClients, shared, active];
|
||||||
|
|
||||||
|
return conditions.every(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
async getApp() {
|
async getApp() {
|
||||||
if (!this.key) return null;
|
if (!this.key) return null;
|
||||||
|
|
||||||
return await App.findOneByKey(this.key);
|
return await App.findOneByKey(this.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
async computeAndAssignConnectionAllowedProperty() {
|
|
||||||
this.connectionAllowed = await this.computeConnectionAllowedProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
async computeConnectionAllowedProperty() {
|
|
||||||
const appAuthClients = await this.$relatedQuery('appAuthClients');
|
|
||||||
|
|
||||||
const hasSomeActiveAppAuthClients =
|
|
||||||
appAuthClients?.some((appAuthClient) => appAuthClient.active) || false;
|
|
||||||
|
|
||||||
const conditions = [
|
|
||||||
hasSomeActiveAppAuthClients,
|
|
||||||
this.shared,
|
|
||||||
!this.disabled,
|
|
||||||
];
|
|
||||||
|
|
||||||
const connectionAllowed = conditions.every(Boolean);
|
|
||||||
|
|
||||||
return connectionAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
async $beforeInsert(queryContext) {
|
|
||||||
await super.$beforeInsert(queryContext);
|
|
||||||
|
|
||||||
await this.computeAndAssignConnectionAllowedProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
async $beforeUpdate(opt, queryContext) {
|
|
||||||
await super.$beforeUpdate(opt, queryContext);
|
|
||||||
|
|
||||||
await this.computeAndAssignConnectionAllowedProperty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AppConfig;
|
export default AppConfig;
|
||||||
|
@@ -1,180 +0,0 @@
|
|||||||
import { vi, describe, it, expect } from 'vitest';
|
|
||||||
|
|
||||||
import Base from './base.js';
|
|
||||||
import AppConfig from './app-config.js';
|
|
||||||
import App from './app.js';
|
|
||||||
import AppAuthClient from './app-auth-client.js';
|
|
||||||
import { createAppConfig } from '../../test/factories/app-config.js';
|
|
||||||
import { createAppAuthClient } from '../../test/factories/app-auth-client.js';
|
|
||||||
|
|
||||||
describe('AppConfig model', () => {
|
|
||||||
it('tableName should return correct name', () => {
|
|
||||||
expect(AppConfig.tableName).toBe('app_configs');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('idColumn should return key field', () => {
|
|
||||||
expect(AppConfig.idColumn).toBe('key');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('jsonSchema should have correct validations', () => {
|
|
||||||
expect(AppConfig.jsonSchema).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('relationMappings should return correct associations', () => {
|
|
||||||
const relationMappings = AppConfig.relationMappings();
|
|
||||||
|
|
||||||
const expectedRelations = {
|
|
||||||
appAuthClients: {
|
|
||||||
relation: Base.HasManyRelation,
|
|
||||||
modelClass: AppAuthClient,
|
|
||||||
join: {
|
|
||||||
from: 'app_configs.key',
|
|
||||||
to: 'app_auth_clients.app_key',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(relationMappings).toStrictEqual(expectedRelations);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getApp', () => {
|
|
||||||
it('getApp should return null if there is no key', async () => {
|
|
||||||
const appConfig = new AppConfig();
|
|
||||||
const app = await appConfig.getApp();
|
|
||||||
|
|
||||||
expect(app).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('getApp should return app with provided key', async () => {
|
|
||||||
const appConfig = new AppConfig();
|
|
||||||
appConfig.key = 'deepl';
|
|
||||||
|
|
||||||
const app = await appConfig.getApp();
|
|
||||||
const expectedApp = await App.findOneByKey(appConfig.key);
|
|
||||||
|
|
||||||
expect(app).toStrictEqual(expectedApp);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('computeAndAssignConnectionAllowedProperty', () => {
|
|
||||||
it('should call computeConnectionAllowedProperty and assign the result', async () => {
|
|
||||||
const appConfig = await createAppConfig();
|
|
||||||
|
|
||||||
const computeConnectionAllowedPropertySpy = vi
|
|
||||||
.spyOn(appConfig, 'computeConnectionAllowedProperty')
|
|
||||||
.mockResolvedValue(true);
|
|
||||||
|
|
||||||
await appConfig.computeAndAssignConnectionAllowedProperty();
|
|
||||||
|
|
||||||
expect(computeConnectionAllowedPropertySpy).toHaveBeenCalled();
|
|
||||||
expect(appConfig.connectionAllowed).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('computeConnectionAllowedProperty', () => {
|
|
||||||
it('should return true when app is enabled, shared and allows custom connection with an active app auth client', async () => {
|
|
||||||
await createAppAuthClient({
|
|
||||||
appKey: 'deepl',
|
|
||||||
active: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await createAppAuthClient({
|
|
||||||
appKey: 'deepl',
|
|
||||||
active: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const appConfig = await createAppConfig({
|
|
||||||
disabled: false,
|
|
||||||
customConnectionAllowed: true,
|
|
||||||
shared: true,
|
|
||||||
key: 'deepl',
|
|
||||||
});
|
|
||||||
|
|
||||||
const connectionAllowed =
|
|
||||||
await appConfig.computeConnectionAllowedProperty();
|
|
||||||
|
|
||||||
expect(connectionAllowed).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false if there is no active app auth client', async () => {
|
|
||||||
await createAppAuthClient({
|
|
||||||
appKey: 'deepl',
|
|
||||||
active: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const appConfig = await createAppConfig({
|
|
||||||
disabled: false,
|
|
||||||
customConnectionAllowed: true,
|
|
||||||
shared: true,
|
|
||||||
key: 'deepl',
|
|
||||||
});
|
|
||||||
|
|
||||||
const connectionAllowed =
|
|
||||||
await appConfig.computeConnectionAllowedProperty();
|
|
||||||
|
|
||||||
expect(connectionAllowed).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false if there is no app auth clients', async () => {
|
|
||||||
const appConfig = await createAppConfig({
|
|
||||||
disabled: false,
|
|
||||||
customConnectionAllowed: true,
|
|
||||||
shared: true,
|
|
||||||
key: 'deepl',
|
|
||||||
});
|
|
||||||
|
|
||||||
const connectionAllowed =
|
|
||||||
await appConfig.computeConnectionAllowedProperty();
|
|
||||||
|
|
||||||
expect(connectionAllowed).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when app is disabled', async () => {
|
|
||||||
const appConfig = await createAppConfig({
|
|
||||||
disabled: true,
|
|
||||||
customConnectionAllowed: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const connectionAllowed =
|
|
||||||
await appConfig.computeConnectionAllowedProperty();
|
|
||||||
|
|
||||||
expect(connectionAllowed).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should return false when app doesn't allow custom connection`, async () => {
|
|
||||||
const appConfig = await createAppConfig({
|
|
||||||
disabled: false,
|
|
||||||
customConnectionAllowed: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const connectionAllowed =
|
|
||||||
await appConfig.computeConnectionAllowedProperty();
|
|
||||||
|
|
||||||
expect(connectionAllowed).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('$beforeInsert should call computeAndAssignConnectionAllowedProperty', async () => {
|
|
||||||
const computeAndAssignConnectionAllowedPropertySpy = vi
|
|
||||||
.spyOn(AppConfig.prototype, 'computeAndAssignConnectionAllowedProperty')
|
|
||||||
.mockResolvedValue(true);
|
|
||||||
|
|
||||||
await createAppConfig();
|
|
||||||
|
|
||||||
expect(computeAndAssignConnectionAllowedPropertySpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('$beforeUpdate should call computeAndAssignConnectionAllowedProperty', async () => {
|
|
||||||
const appConfig = await createAppConfig();
|
|
||||||
|
|
||||||
const computeAndAssignConnectionAllowedPropertySpy = vi
|
|
||||||
.spyOn(AppConfig.prototype, 'computeAndAssignConnectionAllowedProperty')
|
|
||||||
.mockResolvedValue(true);
|
|
||||||
|
|
||||||
await appConfig.$query().patch({
|
|
||||||
key: 'deepl',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(computeAndAssignConnectionAllowedPropertySpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
});
|
|
@@ -89,7 +89,7 @@ class Connection extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.appConfig) {
|
if (this.appConfig) {
|
||||||
return !this.appConfig.disabled && this.appConfig.customConnectionAllowed;
|
return !this.appConfig.disabled && this.appConfig.allowCustomConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -122,20 +122,10 @@ class Connection extends Base {
|
|||||||
return this.data ? true : false;
|
return this.data ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getApp() {
|
|
||||||
if (!this.key) return null;
|
|
||||||
|
|
||||||
return await App.findOneByKey(this.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAppConfig() {
|
|
||||||
return await AppConfig.query().findOne({ key: this.key });
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkEligibilityForCreation() {
|
async checkEligibilityForCreation() {
|
||||||
const app = await this.getApp();
|
const app = await App.findOneByKey(this.key);
|
||||||
|
|
||||||
const appConfig = await this.getAppConfig();
|
const appConfig = await AppConfig.query().findOne({ key: this.key });
|
||||||
|
|
||||||
if (appConfig) {
|
if (appConfig) {
|
||||||
if (appConfig.disabled) {
|
if (appConfig.disabled) {
|
||||||
@@ -144,7 +134,7 @@ class Connection extends Base {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!appConfig.customConnectionAllowed && this.formattedData) {
|
if (!appConfig.allowCustomConnection && this.formattedData) {
|
||||||
throw new NotAuthorizedError(
|
throw new NotAuthorizedError(
|
||||||
`New custom connections have been disabled for ${app.name}!`
|
`New custom connections have been disabled for ${app.name}!`
|
||||||
);
|
);
|
||||||
@@ -170,6 +160,12 @@ class Connection extends Base {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getApp() {
|
||||||
|
if (!this.key) return null;
|
||||||
|
|
||||||
|
return await App.findOneByKey(this.key);
|
||||||
|
}
|
||||||
|
|
||||||
async testAndUpdateConnection() {
|
async testAndUpdateConnection() {
|
||||||
const app = await this.getApp();
|
const app = await this.getApp();
|
||||||
const $ = await globalVariable({ connection: this, app });
|
const $ = await globalVariable({ connection: this, app });
|
||||||
@@ -228,7 +224,7 @@ class Connection extends Base {
|
|||||||
async reset() {
|
async reset() {
|
||||||
const formattedData = this?.formattedData?.screenName
|
const formattedData = this?.formattedData?.screenName
|
||||||
? { screenName: this.formattedData.screenName }
|
? { screenName: this.formattedData.screenName }
|
||||||
: {};
|
: null;
|
||||||
|
|
||||||
const updatedConnection = await this.$query().patchAndFetch({
|
const updatedConnection = await this.$query().patchAndFetch({
|
||||||
formattedData,
|
formattedData,
|
||||||
@@ -237,7 +233,7 @@ class Connection extends Base {
|
|||||||
return updatedConnection;
|
return updatedConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateFormattedData({ formattedData, appAuthClientId }) {
|
async update({ formattedData, appAuthClientId }) {
|
||||||
if (appAuthClientId) {
|
if (appAuthClientId) {
|
||||||
const appAuthClient = await AppAuthClient.query()
|
const appAuthClient = await AppAuthClient.query()
|
||||||
.findById(appAuthClientId)
|
.findById(appAuthClientId)
|
||||||
|
@@ -3,16 +3,11 @@ import AES from 'crypto-js/aes.js';
|
|||||||
import enc from 'crypto-js/enc-utf8.js';
|
import enc from 'crypto-js/enc-utf8.js';
|
||||||
import appConfig from '../config/app.js';
|
import appConfig from '../config/app.js';
|
||||||
import AppAuthClient from './app-auth-client.js';
|
import AppAuthClient from './app-auth-client.js';
|
||||||
import App from './app.js';
|
|
||||||
import AppConfig from './app-config.js';
|
import AppConfig from './app-config.js';
|
||||||
import Base from './base.js';
|
import Base from './base.js';
|
||||||
import Connection from './connection';
|
import Connection from './connection';
|
||||||
import Step from './step.js';
|
import Step from './step.js';
|
||||||
import User from './user.js';
|
import User from './user.js';
|
||||||
import Telemetry from '../helpers/telemetry/index.js';
|
|
||||||
import { createConnection } from '../../test/factories/connection.js';
|
|
||||||
import { createAppConfig } from '../../test/factories/app-config.js';
|
|
||||||
import { createAppAuthClient } from '../../test/factories/app-auth-client.js';
|
|
||||||
|
|
||||||
describe('Connection model', () => {
|
describe('Connection model', () => {
|
||||||
it('tableName should return correct name', () => {
|
it('tableName should return correct name', () => {
|
||||||
@@ -31,138 +26,57 @@ describe('Connection model', () => {
|
|||||||
expect(virtualAttributes).toStrictEqual(expectedAttributes);
|
expect(virtualAttributes).toStrictEqual(expectedAttributes);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('relationMappings', () => {
|
it('relationMappings should return correct associations', () => {
|
||||||
it('should return correct associations', () => {
|
const relationMappings = Connection.relationMappings();
|
||||||
const relationMappings = Connection.relationMappings();
|
|
||||||
|
|
||||||
const expectedRelations = {
|
const expectedRelations = {
|
||||||
user: {
|
user: {
|
||||||
relation: Base.BelongsToOneRelation,
|
relation: Base.BelongsToOneRelation,
|
||||||
modelClass: User,
|
modelClass: User,
|
||||||
join: {
|
join: {
|
||||||
from: 'connections.user_id',
|
from: 'connections.user_id',
|
||||||
to: 'users.id',
|
to: 'users.id',
|
||||||
},
|
|
||||||
},
|
},
|
||||||
steps: {
|
},
|
||||||
relation: Base.HasManyRelation,
|
steps: {
|
||||||
modelClass: Step,
|
relation: Base.HasManyRelation,
|
||||||
join: {
|
modelClass: Step,
|
||||||
from: 'connections.id',
|
join: {
|
||||||
to: 'steps.connection_id',
|
from: 'connections.id',
|
||||||
},
|
to: 'steps.connection_id',
|
||||||
},
|
},
|
||||||
triggerSteps: {
|
},
|
||||||
relation: Base.HasManyRelation,
|
triggerSteps: {
|
||||||
modelClass: Step,
|
relation: Base.HasManyRelation,
|
||||||
join: {
|
modelClass: Step,
|
||||||
from: 'connections.id',
|
join: {
|
||||||
to: 'steps.connection_id',
|
from: 'connections.id',
|
||||||
},
|
to: 'steps.connection_id',
|
||||||
filter: expect.any(Function),
|
|
||||||
},
|
},
|
||||||
appConfig: {
|
filter: expect.any(Function),
|
||||||
relation: Base.BelongsToOneRelation,
|
},
|
||||||
modelClass: AppConfig,
|
appConfig: {
|
||||||
join: {
|
relation: Base.BelongsToOneRelation,
|
||||||
from: 'connections.key',
|
modelClass: AppConfig,
|
||||||
to: 'app_configs.key',
|
join: {
|
||||||
},
|
from: 'connections.key',
|
||||||
|
to: 'app_configs.key',
|
||||||
},
|
},
|
||||||
appAuthClient: {
|
},
|
||||||
relation: Base.BelongsToOneRelation,
|
appAuthClient: {
|
||||||
modelClass: AppAuthClient,
|
relation: Base.BelongsToOneRelation,
|
||||||
join: {
|
modelClass: AppAuthClient,
|
||||||
from: 'connections.app_auth_client_id',
|
join: {
|
||||||
to: 'app_auth_clients.id',
|
from: 'connections.app_auth_client_id',
|
||||||
},
|
to: 'app_auth_clients.id',
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
|
};
|
||||||
|
|
||||||
expect(relationMappings).toStrictEqual(expectedRelations);
|
expect(relationMappings).toStrictEqual(expectedRelations);
|
||||||
});
|
|
||||||
|
|
||||||
it('triggerSteps should return only trigger typed steps', () => {
|
|
||||||
const relations = Connection.relationMappings();
|
|
||||||
const whereSpy = vi.fn();
|
|
||||||
|
|
||||||
relations.triggerSteps.filter({ where: whereSpy });
|
|
||||||
|
|
||||||
expect(whereSpy).toHaveBeenCalledWith('type', '=', 'trigger');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('reconnectable', () => {
|
describe.todo('reconnectable');
|
||||||
it('should return active status of app auth client when created via app auth client', async () => {
|
|
||||||
const appAuthClient = await createAppAuthClient({
|
|
||||||
active: true,
|
|
||||||
formattedAuthDefaults: {
|
|
||||||
clientId: 'sample-id',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const connection = await createConnection({
|
|
||||||
appAuthClientId: appAuthClient.id,
|
|
||||||
formattedData: {
|
|
||||||
token: 'sample-token',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const connectionWithAppAuthClient = await connection
|
|
||||||
.$query()
|
|
||||||
.withGraphFetched({
|
|
||||||
appAuthClient: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(connectionWithAppAuthClient.reconnectable).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true when app config is not disabled and allows custom connection', async () => {
|
|
||||||
const appConfig = await createAppConfig({
|
|
||||||
key: 'gitlab',
|
|
||||||
disabled: false,
|
|
||||||
customConnectionAllowed: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const connection = await createConnection({
|
|
||||||
key: appConfig.key,
|
|
||||||
formattedData: {
|
|
||||||
token: 'sample-token',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const connectionWithAppAuthClient = await connection
|
|
||||||
.$query()
|
|
||||||
.withGraphFetched({
|
|
||||||
appConfig: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(connectionWithAppAuthClient.reconnectable).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when app config is disabled or does not allow custom connection', async () => {
|
|
||||||
const connection = await createConnection({
|
|
||||||
key: 'gitlab',
|
|
||||||
formattedData: {
|
|
||||||
token: 'sample-token',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await createAppConfig({
|
|
||||||
key: 'gitlab',
|
|
||||||
disabled: true,
|
|
||||||
customConnectionAllowed: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const connectionWithAppAuthClient = await connection
|
|
||||||
.$query()
|
|
||||||
.withGraphFetched({
|
|
||||||
appConfig: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(connectionWithAppAuthClient.reconnectable).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('encryptData', () => {
|
describe('encryptData', () => {
|
||||||
it('should return undefined if eligibleForEncryption is not true', async () => {
|
it('should return undefined if eligibleForEncryption is not true', async () => {
|
||||||
@@ -246,558 +160,4 @@ describe('Connection model', () => {
|
|||||||
expect(connection.data).not.toEqual(formattedData);
|
expect(connection.data).not.toEqual(formattedData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('eligibleForEncryption', () => {
|
|
||||||
it('should return true when formattedData property exists', async () => {
|
|
||||||
const connection = new Connection();
|
|
||||||
connection.formattedData = { clientId: 'sample-id' };
|
|
||||||
|
|
||||||
expect(connection.eligibleForEncryption()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return false when formattedData property doesn't exist", async () => {
|
|
||||||
const connection = new Connection();
|
|
||||||
connection.formattedData = undefined;
|
|
||||||
|
|
||||||
expect(connection.eligibleForEncryption()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('eligibleForDecryption', () => {
|
|
||||||
it('should return true when data property exists', async () => {
|
|
||||||
const connection = new Connection();
|
|
||||||
connection.data = 'encrypted-data';
|
|
||||||
|
|
||||||
expect(connection.eligibleForDecryption()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return false when data property doesn't exist", async () => {
|
|
||||||
const connection = new Connection();
|
|
||||||
connection.data = undefined;
|
|
||||||
|
|
||||||
expect(connection.eligibleForDecryption()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getApp', () => {
|
|
||||||
it('should return connection app when valid key exists', async () => {
|
|
||||||
const connection = new Connection();
|
|
||||||
connection.key = 'gitlab';
|
|
||||||
|
|
||||||
const connectionApp = await connection.getApp();
|
|
||||||
const app = await App.findOneByKey('gitlab');
|
|
||||||
|
|
||||||
expect(connectionApp).toStrictEqual(app);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when invalid key exists', async () => {
|
|
||||||
const connection = new Connection();
|
|
||||||
connection.key = 'invalid-key';
|
|
||||||
|
|
||||||
await expect(() => connection.getApp()).rejects.toThrowError(
|
|
||||||
`An application with the "invalid-key" key couldn't be found.`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return null when no key exists', async () => {
|
|
||||||
const connection = new Connection();
|
|
||||||
|
|
||||||
await expect(connection.getApp()).resolves.toBe(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('getAppConfig should return connection app config', async () => {
|
|
||||||
const connection = new Connection();
|
|
||||||
connection.key = 'gitlab';
|
|
||||||
|
|
||||||
const appConfig = await createAppConfig({ key: 'gitlab' });
|
|
||||||
|
|
||||||
const connectionAppConfig = await connection.getAppConfig();
|
|
||||||
|
|
||||||
expect(connectionAppConfig).toStrictEqual(appConfig);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('checkEligibilityForCreation', () => {
|
|
||||||
it('should return connection if no app config exists', async () => {
|
|
||||||
vi.spyOn(Connection.prototype, 'getApp').mockResolvedValue({
|
|
||||||
name: 'gitlab',
|
|
||||||
});
|
|
||||||
|
|
||||||
vi.spyOn(Connection.prototype, 'getAppConfig').mockResolvedValue();
|
|
||||||
|
|
||||||
const connection = new Connection();
|
|
||||||
|
|
||||||
expect(await connection.checkEligibilityForCreation()).toBe(connection);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when app does not exist', async () => {
|
|
||||||
vi.spyOn(Connection.prototype, 'getApp').mockRejectedValue(
|
|
||||||
new Error(
|
|
||||||
`An application with the "unexisting-app" key couldn't be found.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
vi.spyOn(Connection.prototype, 'getAppConfig').mockResolvedValue();
|
|
||||||
|
|
||||||
const connection = new Connection();
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
connection.checkEligibilityForCreation()
|
|
||||||
).rejects.toThrow(
|
|
||||||
`An application with the "unexisting-app" key couldn't be found.`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when app config is disabled', async () => {
|
|
||||||
vi.spyOn(Connection.prototype, 'getApp').mockResolvedValue({
|
|
||||||
name: 'gitlab',
|
|
||||||
});
|
|
||||||
|
|
||||||
vi.spyOn(Connection.prototype, 'getAppConfig').mockResolvedValue({
|
|
||||||
disabled: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const connection = new Connection();
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
connection.checkEligibilityForCreation()
|
|
||||||
).rejects.toThrow(
|
|
||||||
'The application has been disabled for new connections!'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when app config does not allow custom connection with formatted data', async () => {
|
|
||||||
vi.spyOn(Connection.prototype, 'getApp').mockResolvedValue({
|
|
||||||
name: 'gitlab',
|
|
||||||
});
|
|
||||||
|
|
||||||
vi.spyOn(Connection.prototype, 'getAppConfig').mockResolvedValue({
|
|
||||||
disabled: false,
|
|
||||||
customConnectionAllowed: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const connection = new Connection();
|
|
||||||
connection.formattedData = {};
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
connection.checkEligibilityForCreation()
|
|
||||||
).rejects.toThrow(
|
|
||||||
'New custom connections have been disabled for gitlab!'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when app config is not shared with app auth client', async () => {
|
|
||||||
vi.spyOn(Connection.prototype, 'getApp').mockResolvedValue({
|
|
||||||
name: 'gitlab',
|
|
||||||
});
|
|
||||||
|
|
||||||
vi.spyOn(Connection.prototype, 'getAppConfig').mockResolvedValue({
|
|
||||||
disabled: false,
|
|
||||||
shared: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const connection = new Connection();
|
|
||||||
connection.appAuthClientId = 'sample-id';
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
connection.checkEligibilityForCreation()
|
|
||||||
).rejects.toThrow(
|
|
||||||
'The connection with the given app auth client is not allowed!'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should apply app auth client auth defaults when creating with shared app auth client', async () => {
|
|
||||||
await createAppConfig({
|
|
||||||
key: 'gitlab',
|
|
||||||
disabled: false,
|
|
||||||
customConnectionAllowed: true,
|
|
||||||
shared: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const appAuthClient = await createAppAuthClient({
|
|
||||||
appKey: 'gitlab',
|
|
||||||
active: true,
|
|
||||||
formattedAuthDefaults: {
|
|
||||||
clientId: 'sample-id',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const connection = await createConnection({
|
|
||||||
key: 'gitlab',
|
|
||||||
appAuthClientId: appAuthClient.id,
|
|
||||||
formattedData: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
await connection.checkEligibilityForCreation();
|
|
||||||
|
|
||||||
expect(connection.formattedData).toStrictEqual({
|
|
||||||
clientId: 'sample-id',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('testAndUpdateConnection', () => {
|
|
||||||
it('should verify connection and persist it', async () => {
|
|
||||||
const connection = await createConnection({ verified: false });
|
|
||||||
|
|
||||||
const isStillVerifiedSpy = vi.fn().mockReturnValue(true);
|
|
||||||
|
|
||||||
const originalApp = await connection.getApp();
|
|
||||||
|
|
||||||
const getAppSpy = vi
|
|
||||||
.spyOn(connection, 'getApp')
|
|
||||||
.mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
...originalApp,
|
|
||||||
auth: {
|
|
||||||
...originalApp.auth,
|
|
||||||
isStillVerified: isStillVerifiedSpy,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedConnection = await connection.testAndUpdateConnection();
|
|
||||||
|
|
||||||
expect(getAppSpy).toHaveBeenCalledOnce();
|
|
||||||
expect(isStillVerifiedSpy).toHaveBeenCalledOnce();
|
|
||||||
expect(updatedConnection.verified).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should unverify connection and persist it', async () => {
|
|
||||||
const connection = await createConnection({ verified: true });
|
|
||||||
|
|
||||||
const isStillVerifiedSpy = vi
|
|
||||||
.fn()
|
|
||||||
.mockRejectedValue(new Error('Wrong credentials!'));
|
|
||||||
|
|
||||||
const originalApp = await connection.getApp();
|
|
||||||
|
|
||||||
const getAppSpy = vi
|
|
||||||
.spyOn(connection, 'getApp')
|
|
||||||
.mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
...originalApp,
|
|
||||||
auth: {
|
|
||||||
...originalApp.auth,
|
|
||||||
isStillVerified: isStillVerifiedSpy,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedConnection = await connection.testAndUpdateConnection();
|
|
||||||
|
|
||||||
expect(getAppSpy).toHaveBeenCalledOnce();
|
|
||||||
expect(isStillVerifiedSpy).toHaveBeenCalledOnce();
|
|
||||||
expect(updatedConnection.verified).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('verifyAndUpdateConnection', () => {
|
|
||||||
it('should verify connection with valid token', async () => {
|
|
||||||
const connection = await createConnection({
|
|
||||||
verified: false,
|
|
||||||
draft: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const verifyCredentialsSpy = vi.fn().mockResolvedValue(true);
|
|
||||||
|
|
||||||
const originalApp = await connection.getApp();
|
|
||||||
|
|
||||||
vi.spyOn(connection, 'getApp').mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
...originalApp,
|
|
||||||
auth: {
|
|
||||||
...originalApp.auth,
|
|
||||||
verifyCredentials: verifyCredentialsSpy,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedConnection = await connection.verifyAndUpdateConnection();
|
|
||||||
|
|
||||||
expect(verifyCredentialsSpy).toHaveBeenCalledOnce();
|
|
||||||
expect(updatedConnection.verified).toBe(true);
|
|
||||||
expect(updatedConnection.draft).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error with invalid token', async () => {
|
|
||||||
const connection = await createConnection({
|
|
||||||
verified: false,
|
|
||||||
draft: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const verifyCredentialsSpy = vi
|
|
||||||
.fn()
|
|
||||||
.mockRejectedValue(new Error('Invalid token!'));
|
|
||||||
|
|
||||||
const originalApp = await connection.getApp();
|
|
||||||
|
|
||||||
vi.spyOn(connection, 'getApp').mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
...originalApp,
|
|
||||||
auth: {
|
|
||||||
...originalApp.auth,
|
|
||||||
verifyCredentials: verifyCredentialsSpy,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(() =>
|
|
||||||
connection.verifyAndUpdateConnection()
|
|
||||||
).rejects.toThrowError('Invalid token!');
|
|
||||||
expect(verifyCredentialsSpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('verifyWebhook', () => {
|
|
||||||
it('should verify webhook on remote', async () => {
|
|
||||||
const connection = await createConnection({ key: 'typeform' });
|
|
||||||
|
|
||||||
const verifyWebhookSpy = vi.fn().mockResolvedValue('verified-webhook');
|
|
||||||
|
|
||||||
const originalApp = await connection.getApp();
|
|
||||||
|
|
||||||
vi.spyOn(connection, 'getApp').mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
...originalApp,
|
|
||||||
auth: {
|
|
||||||
...originalApp.auth,
|
|
||||||
verifyWebhook: verifyWebhookSpy,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(await connection.verifyWebhook()).toBe('verified-webhook');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true if connection does not have value in key property', async () => {
|
|
||||||
const connection = await createConnection({ key: null });
|
|
||||||
|
|
||||||
expect(await connection.verifyWebhook()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error at failed webhook verification', async () => {
|
|
||||||
const connection = await createConnection({ key: 'typeform' });
|
|
||||||
|
|
||||||
const verifyWebhookSpy = vi.fn().mockRejectedValue('unverified-webhook');
|
|
||||||
|
|
||||||
const originalApp = await connection.getApp();
|
|
||||||
|
|
||||||
vi.spyOn(connection, 'getApp').mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
...originalApp,
|
|
||||||
auth: {
|
|
||||||
...originalApp.auth,
|
|
||||||
verifyWebhook: verifyWebhookSpy,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(() => connection.verifyWebhook()).rejects.toThrowError(
|
|
||||||
'unverified-webhook'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('generateAuthUrl should return authentication url', async () => {
|
|
||||||
const connection = await createConnection({
|
|
||||||
key: 'typeform',
|
|
||||||
formattedData: {
|
|
||||||
url: 'https://automatisch.io/authentication-url',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const generateAuthUrlSpy = vi.fn();
|
|
||||||
|
|
||||||
const originalApp = await connection.getApp();
|
|
||||||
|
|
||||||
vi.spyOn(connection, 'getApp').mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
...originalApp,
|
|
||||||
auth: {
|
|
||||||
...originalApp.auth,
|
|
||||||
generateAuthUrl: generateAuthUrlSpy,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(await connection.generateAuthUrl()).toStrictEqual({
|
|
||||||
url: 'https://automatisch.io/authentication-url',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('reset', () => {
|
|
||||||
it('should keep screen name when exists and reset the rest of the formatted data', async () => {
|
|
||||||
const connection = await createConnection({
|
|
||||||
formattedData: {
|
|
||||||
screenName: 'Sample connection',
|
|
||||||
token: 'sample-token',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await connection.reset();
|
|
||||||
|
|
||||||
const refetchedConnection = await connection.$query();
|
|
||||||
|
|
||||||
expect(refetchedConnection.formattedData).toStrictEqual({
|
|
||||||
screenName: 'Sample connection',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should empty formatted data object when screen name does not exist', async () => {
|
|
||||||
const connection = await createConnection({
|
|
||||||
formattedData: {
|
|
||||||
token: 'sample-token',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await connection.reset();
|
|
||||||
|
|
||||||
const refetchedConnection = await connection.$query();
|
|
||||||
|
|
||||||
expect(refetchedConnection.formattedData).toStrictEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('updateFormattedData', () => {
|
|
||||||
it('should extend connection data with app auth client auth defaults', async () => {
|
|
||||||
const appAuthClient = await createAppAuthClient({
|
|
||||||
formattedAuthDefaults: {
|
|
||||||
clientId: 'sample-id',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const connection = await createConnection({
|
|
||||||
appAuthClientId: appAuthClient.id,
|
|
||||||
formattedData: {
|
|
||||||
token: 'sample-token',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedConnection = await connection.updateFormattedData({
|
|
||||||
appAuthClientId: appAuthClient.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(updatedConnection.formattedData).toStrictEqual({
|
|
||||||
clientId: 'sample-id',
|
|
||||||
token: 'sample-token',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('$beforeInsert', () => {
|
|
||||||
it('should call super.$beforeInsert', async () => {
|
|
||||||
const superBeforeInsertSpy = vi
|
|
||||||
.spyOn(Base.prototype, '$beforeInsert')
|
|
||||||
.mockResolvedValue();
|
|
||||||
|
|
||||||
await createConnection();
|
|
||||||
|
|
||||||
expect(superBeforeInsertSpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call checkEligibilityForCreation', async () => {
|
|
||||||
const checkEligibilityForCreationSpy = vi
|
|
||||||
.spyOn(Connection.prototype, 'checkEligibilityForCreation')
|
|
||||||
.mockResolvedValue();
|
|
||||||
|
|
||||||
await createConnection();
|
|
||||||
|
|
||||||
expect(checkEligibilityForCreationSpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call encryptData', async () => {
|
|
||||||
const encryptDataSpy = vi
|
|
||||||
.spyOn(Connection.prototype, 'encryptData')
|
|
||||||
.mockResolvedValue();
|
|
||||||
|
|
||||||
await createConnection();
|
|
||||||
|
|
||||||
expect(encryptDataSpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('$beforeUpdate', () => {
|
|
||||||
it('should call super.$beforeUpdate', async () => {
|
|
||||||
const superBeforeUpdateSpy = vi
|
|
||||||
.spyOn(Base.prototype, '$beforeUpdate')
|
|
||||||
.mockResolvedValue();
|
|
||||||
|
|
||||||
const connection = await createConnection();
|
|
||||||
|
|
||||||
await connection.$query().patch({ verified: false });
|
|
||||||
|
|
||||||
expect(superBeforeUpdateSpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call encryptData', async () => {
|
|
||||||
const connection = await createConnection();
|
|
||||||
|
|
||||||
const encryptDataSpy = vi
|
|
||||||
.spyOn(Connection.prototype, 'encryptData')
|
|
||||||
.mockResolvedValue();
|
|
||||||
|
|
||||||
await connection.$query().patch({ verified: false });
|
|
||||||
|
|
||||||
expect(encryptDataSpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('$afterFind', () => {
|
|
||||||
it('should call decryptData', async () => {
|
|
||||||
const connection = await createConnection();
|
|
||||||
|
|
||||||
const decryptDataSpy = vi
|
|
||||||
.spyOn(Connection.prototype, 'decryptData')
|
|
||||||
.mockResolvedValue();
|
|
||||||
|
|
||||||
await connection.$query();
|
|
||||||
|
|
||||||
expect(decryptDataSpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('$afterInsert', () => {
|
|
||||||
it('should call super.$afterInsert', async () => {
|
|
||||||
const superAfterInsertSpy = vi.spyOn(Base.prototype, '$afterInsert');
|
|
||||||
|
|
||||||
await createConnection();
|
|
||||||
|
|
||||||
expect(superAfterInsertSpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call Telemetry.connectionCreated', async () => {
|
|
||||||
const telemetryConnectionCreatedSpy = vi
|
|
||||||
.spyOn(Telemetry, 'connectionCreated')
|
|
||||||
.mockImplementation(() => {});
|
|
||||||
|
|
||||||
const connection = await createConnection();
|
|
||||||
|
|
||||||
expect(telemetryConnectionCreatedSpy).toHaveBeenCalledWith(connection);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('$afterUpdate', () => {
|
|
||||||
it('should call super.$afterUpdate', async () => {
|
|
||||||
const superAfterInsertSpy = vi.spyOn(Base.prototype, '$afterUpdate');
|
|
||||||
|
|
||||||
const connection = await createConnection();
|
|
||||||
|
|
||||||
await connection.$query().patch({ verified: false });
|
|
||||||
|
|
||||||
expect(superAfterInsertSpy).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call Telemetry.connectionUpdated', async () => {
|
|
||||||
const telemetryconnectionUpdatedSpy = vi
|
|
||||||
.spyOn(Telemetry, 'connectionCreated')
|
|
||||||
.mockImplementation(() => {});
|
|
||||||
|
|
||||||
const connection = await createConnection();
|
|
||||||
|
|
||||||
await connection.$query().patch({ verified: false });
|
|
||||||
|
|
||||||
expect(telemetryconnectionUpdatedSpy).toHaveBeenCalledWith(connection);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -19,39 +19,25 @@ class Permission extends Base {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static filter(permissions) {
|
static sanitize(permissions) {
|
||||||
const sanitizedPermissions = permissions.filter((permission) => {
|
const sanitizedPermissions = permissions.filter((permission) => {
|
||||||
const { action, subject, conditions } = permission;
|
const { action, subject, conditions } = permission;
|
||||||
|
|
||||||
const relevantAction = this.findAction(action);
|
const relevantAction = permissionCatalog.actions.find(
|
||||||
const validSubject = this.isSubjectValid(subject, relevantAction);
|
(actionCatalogItem) => actionCatalogItem.key === action
|
||||||
const validConditions = this.areConditionsValid(conditions);
|
);
|
||||||
|
const validSubject = relevantAction.subjects.includes(subject);
|
||||||
|
const validConditions = conditions.every((condition) => {
|
||||||
|
return !!permissionCatalog.conditions.find(
|
||||||
|
(conditionCatalogItem) => conditionCatalogItem.key === condition
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return relevantAction && validSubject && validConditions;
|
return validSubject && validConditions;
|
||||||
});
|
});
|
||||||
|
|
||||||
return sanitizedPermissions;
|
return sanitizedPermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
static findAction(action) {
|
|
||||||
return permissionCatalog.actions.find(
|
|
||||||
(actionCatalogItem) => actionCatalogItem.key === action
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static isSubjectValid(subject, action) {
|
|
||||||
return action && action.subjects.includes(subject);
|
|
||||||
}
|
|
||||||
|
|
||||||
static areConditionsValid(conditions) {
|
|
||||||
return conditions.every((condition) => this.isConditionValid(condition));
|
|
||||||
}
|
|
||||||
|
|
||||||
static isConditionValid(condition) {
|
|
||||||
return !!permissionCatalog.conditions.find(
|
|
||||||
(conditionCatalogItem) => conditionCatalogItem.key === condition
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Permission;
|
export default Permission;
|
||||||
|
@@ -1,95 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import Permission from './permission';
|
|
||||||
import permissionCatalog from '../helpers/permission-catalog.ee.js';
|
|
||||||
|
|
||||||
describe('Permission model', () => {
|
|
||||||
it('tableName should return correct name', () => {
|
|
||||||
expect(Permission.tableName).toBe('permissions');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('jsonSchema should have correct validations', () => {
|
|
||||||
expect(Permission.jsonSchema).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('filter should return only valid permissions based on permission catalog', () => {
|
|
||||||
const permissions = [
|
|
||||||
{ action: 'read', subject: 'Flow', conditions: ['isCreator'] },
|
|
||||||
{ action: 'delete', subject: 'Connection', conditions: [] },
|
|
||||||
{ action: 'publish', subject: 'Flow', conditions: ['isCreator'] },
|
|
||||||
{ action: 'update', subject: 'Execution', conditions: [] }, // Invalid subject
|
|
||||||
{ action: 'read', subject: 'Execution', conditions: ['invalid'] }, // Invalid condition
|
|
||||||
{ action: 'invalid', subject: 'Execution', conditions: [] }, // Invalid action
|
|
||||||
];
|
|
||||||
|
|
||||||
const result = Permission.filter(permissions);
|
|
||||||
|
|
||||||
expect(result).toStrictEqual([
|
|
||||||
{ action: 'read', subject: 'Flow', conditions: ['isCreator'] },
|
|
||||||
{ action: 'delete', subject: 'Connection', conditions: [] },
|
|
||||||
{ action: 'publish', subject: 'Flow', conditions: ['isCreator'] },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findAction', () => {
|
|
||||||
it('should return action from permission catalog', () => {
|
|
||||||
const action = Permission.findAction('create');
|
|
||||||
expect(action.key).toStrictEqual('create');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return undefined for invalid actions', () => {
|
|
||||||
const invalidAction = Permission.findAction('invalidAction');
|
|
||||||
expect(invalidAction).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('isSubjectValid', () => {
|
|
||||||
it('should return true for valid subjects', () => {
|
|
||||||
const validAction = permissionCatalog.actions.find(
|
|
||||||
(action) => action.key === 'create'
|
|
||||||
);
|
|
||||||
|
|
||||||
const validSubject = Permission.isSubjectValid('Connection', validAction);
|
|
||||||
expect(validSubject).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false for invalid subjects', () => {
|
|
||||||
const validAction = permissionCatalog.actions.find(
|
|
||||||
(action) => action.key === 'create'
|
|
||||||
);
|
|
||||||
|
|
||||||
const invalidSubject = Permission.isSubjectValid(
|
|
||||||
'Execution',
|
|
||||||
validAction
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(invalidSubject).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('areConditionsValid', () => {
|
|
||||||
it('should return true for valid conditions', () => {
|
|
||||||
const validConditions = Permission.areConditionsValid(['isCreator']);
|
|
||||||
expect(validConditions).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false for invalid conditions', () => {
|
|
||||||
const invalidConditions = Permission.areConditionsValid([
|
|
||||||
'invalidCondition',
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(invalidConditions).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('isConditionValid', () => {
|
|
||||||
it('should return true for valid conditions', () => {
|
|
||||||
const validCondition = Permission.isConditionValid('isCreator');
|
|
||||||
expect(validCondition).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false for invalid conditions', () => {
|
|
||||||
const invalidCondition = Permission.isConditionValid('invalidCondition');
|
|
||||||
expect(invalidCondition).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -63,14 +63,14 @@ class Role extends Base {
|
|||||||
await this.$relatedQuery('permissions', trx).delete();
|
await this.$relatedQuery('permissions', trx).delete();
|
||||||
|
|
||||||
if (permissions?.length) {
|
if (permissions?.length) {
|
||||||
const validPermissions = Permission.filter(permissions).map(
|
const sanitizedPermissions = Permission.sanitize(permissions).map(
|
||||||
(permission) => ({
|
(permission) => ({
|
||||||
...permission,
|
...permission,
|
||||||
roleId: this.id,
|
roleId: this.id,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
await Permission.query().insert(validPermissions);
|
await Permission.query().insert(sanitizedPermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.$query(trx).patch({
|
await this.$query(trx).patch({
|
||||||
|
@@ -2,10 +2,11 @@ const appConfigSerializer = (appConfig) => {
|
|||||||
return {
|
return {
|
||||||
id: appConfig.id,
|
id: appConfig.id,
|
||||||
key: appConfig.key,
|
key: appConfig.key,
|
||||||
customConnectionAllowed: appConfig.customConnectionAllowed,
|
allowCustomConnection: appConfig.allowCustomConnection,
|
||||||
shared: appConfig.shared,
|
shared: appConfig.shared,
|
||||||
disabled: appConfig.disabled,
|
disabled: appConfig.disabled,
|
||||||
connectionAllowed: appConfig.connectionAllowed,
|
canConnect: appConfig.canConnect,
|
||||||
|
canCustomConnect: appConfig.canCustomConnect,
|
||||||
createdAt: appConfig.createdAt.getTime(),
|
createdAt: appConfig.createdAt.getTime(),
|
||||||
updatedAt: appConfig.updatedAt.getTime(),
|
updatedAt: appConfig.updatedAt.getTime(),
|
||||||
};
|
};
|
||||||
|
@@ -13,10 +13,11 @@ describe('appConfig serializer', () => {
|
|||||||
const expectedPayload = {
|
const expectedPayload = {
|
||||||
id: appConfig.id,
|
id: appConfig.id,
|
||||||
key: appConfig.key,
|
key: appConfig.key,
|
||||||
customConnectionAllowed: appConfig.customConnectionAllowed,
|
allowCustomConnection: appConfig.allowCustomConnection,
|
||||||
shared: appConfig.shared,
|
shared: appConfig.shared,
|
||||||
disabled: appConfig.disabled,
|
disabled: appConfig.disabled,
|
||||||
connectionAllowed: appConfig.connectionAllowed,
|
canConnect: appConfig.canConnect,
|
||||||
|
canCustomConnect: appConfig.canCustomConnect,
|
||||||
createdAt: appConfig.createdAt.getTime(),
|
createdAt: appConfig.createdAt.getTime(),
|
||||||
updatedAt: appConfig.updatedAt.getTime(),
|
updatedAt: appConfig.updatedAt.getTime(),
|
||||||
};
|
};
|
||||||
|
@@ -10,6 +10,7 @@ const formattedAuthDefaults = {
|
|||||||
|
|
||||||
export const createAppAuthClient = async (params = {}) => {
|
export const createAppAuthClient = async (params = {}) => {
|
||||||
params.name = params?.name || faker.person.fullName();
|
params.name = params?.name || faker.person.fullName();
|
||||||
|
params.id = params?.id || faker.string.uuid();
|
||||||
params.appKey = params?.appKey || 'deepl';
|
params.appKey = params?.appKey || 'deepl';
|
||||||
params.active = params?.active ?? true;
|
params.active = params?.active ?? true;
|
||||||
params.formattedAuthDefaults =
|
params.formattedAuthDefaults =
|
||||||
|
@@ -2,7 +2,7 @@ const createAppConfigMock = (appConfig) => {
|
|||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
key: appConfig.key,
|
key: appConfig.key,
|
||||||
customConnectionAllowed: appConfig.customConnectionAllowed,
|
allowCustomConnection: appConfig.allowCustomConnection,
|
||||||
shared: appConfig.shared,
|
shared: appConfig.shared,
|
||||||
disabled: appConfig.disabled,
|
disabled: appConfig.disabled,
|
||||||
},
|
},
|
||||||
|
@@ -3,10 +3,11 @@ const getAppConfigMock = (appConfig) => {
|
|||||||
data: {
|
data: {
|
||||||
id: appConfig.id,
|
id: appConfig.id,
|
||||||
key: appConfig.key,
|
key: appConfig.key,
|
||||||
customConnectionAllowed: appConfig.customConnectionAllowed,
|
allowCustomConnection: appConfig.allowCustomConnection,
|
||||||
shared: appConfig.shared,
|
shared: appConfig.shared,
|
||||||
disabled: appConfig.disabled,
|
disabled: appConfig.disabled,
|
||||||
connectionAllowed: appConfig.connectionAllowed,
|
canConnect: appConfig.canConnect,
|
||||||
|
canCustomConnect: appConfig.canCustomConnect,
|
||||||
createdAt: appConfig.createdAt.getTime(),
|
createdAt: appConfig.createdAt.getTime(),
|
||||||
updatedAt: appConfig.updatedAt.getTime(),
|
updatedAt: appConfig.updatedAt.getTime(),
|
||||||
},
|
},
|
||||||
|
@@ -3,3 +3,5 @@ POSTGRES_USER=automatisch_user
|
|||||||
POSTGRES_PASSWORD=automatisch_password
|
POSTGRES_PASSWORD=automatisch_password
|
||||||
POSTGRES_PORT=5432
|
POSTGRES_PORT=5432
|
||||||
POSTGRES_HOST=localhost
|
POSTGRES_HOST=localhost
|
||||||
|
BULLMQ_DASHBOARD_PASSWORD=sample
|
||||||
|
BULLMQ_DASHBOARD_USERNAME=root
|
@@ -8,15 +8,11 @@ export class AdminApplicationSettingsPage extends AuthenticatedPage {
|
|||||||
constructor(page) {
|
constructor(page) {
|
||||||
super(page);
|
super(page);
|
||||||
|
|
||||||
this.allowCustomConnectionsSwitch = this.page.locator(
|
this.allowCustomConnectionsSwitch = this.page.locator('[name="allowCustomConnection"]');
|
||||||
'[name="customConnectionAllowed"]'
|
|
||||||
);
|
|
||||||
this.allowSharedConnectionsSwitch = this.page.locator('[name="shared"]');
|
this.allowSharedConnectionsSwitch = this.page.locator('[name="shared"]');
|
||||||
this.disableConnectionsSwitch = this.page.locator('[name="disabled"]');
|
this.disableConnectionsSwitch = this.page.locator('[name="disabled"]');
|
||||||
this.saveButton = this.page.getByTestId('submit-button');
|
this.saveButton = this.page.getByTestId('submit-button');
|
||||||
this.successSnackbar = this.page.getByTestId(
|
this.successSnackbar = this.page.getByTestId('snackbar-save-admin-apps-settings-success');
|
||||||
'snackbar-save-admin-apps-settings-success'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async allowCustomConnections() {
|
async allowCustomConnections() {
|
||||||
|
35
packages/e2e-tests/fixtures/flows-page.js
Normal file
35
packages/e2e-tests/fixtures/flows-page.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const { AuthenticatedPage } = require('./authenticated-page');
|
||||||
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
export class FlowsPage extends AuthenticatedPage {
|
||||||
|
constructor(page) {
|
||||||
|
super(page);
|
||||||
|
|
||||||
|
this.flowRow = this.page.getByTestId('flow-row');
|
||||||
|
this.flowCard = this.page.getByTestId('card-action-area');
|
||||||
|
this.deleteFlowMenuItem = this.page.getByRole('menuitem', {
|
||||||
|
name: 'Delete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickOnDeleteFlowMenuItem() {
|
||||||
|
await this.deleteFlowMenuItem.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteFlow(flowId) {
|
||||||
|
const desiredFlow = await this.flowRow.filter({
|
||||||
|
has: this.page.locator(`a[href="/editor/${flowId}"]`),
|
||||||
|
});
|
||||||
|
await desiredFlow.locator('button').click();
|
||||||
|
await this.clickOnDeleteFlowMenuItem();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
await this.flowRow.filter({
|
||||||
|
has: this.page.locator(`a[href="/editor/${flowId}"]`),
|
||||||
|
})
|
||||||
|
).toHaveCount(0);
|
||||||
|
|
||||||
|
const snackbar = await this.getSnackbarData();
|
||||||
|
await expect(snackbar.variant).toBe('success');
|
||||||
|
}
|
||||||
|
}
|
@@ -9,6 +9,7 @@ const { AcceptInvitation } = require('./accept-invitation-page');
|
|||||||
const { adminFixtures } = require('./admin');
|
const { adminFixtures } = require('./admin');
|
||||||
const { AdminSetupPage } = require('./admin-setup-page');
|
const { AdminSetupPage } = require('./admin-setup-page');
|
||||||
const { AdminCreateUserPage } = require('./admin/create-user-page');
|
const { AdminCreateUserPage } = require('./admin/create-user-page');
|
||||||
|
const { FlowsPage } = require('./flows-page');
|
||||||
|
|
||||||
exports.test = test.extend({
|
exports.test = test.extend({
|
||||||
page: async ({ page }, use) => {
|
page: async ({ page }, use) => {
|
||||||
@@ -35,6 +36,9 @@ exports.test = test.extend({
|
|||||||
userInterfacePage: async ({ page }, use) => {
|
userInterfacePage: async ({ page }, use) => {
|
||||||
await use(new UserInterfacePage(page));
|
await use(new UserInterfacePage(page));
|
||||||
},
|
},
|
||||||
|
flowsPage: async ({ page }, use) => {
|
||||||
|
await use(new FlowsPage(page));
|
||||||
|
},
|
||||||
...adminFixtures,
|
...adminFixtures,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
20
packages/e2e-tests/helpers/bullmq-helper.js
Normal file
20
packages/e2e-tests/helpers/bullmq-helper.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const { expect } = require('../fixtures/index');
|
||||||
|
|
||||||
|
export const expectNoDelayedJobForFlow = async (request, flowId) => {
|
||||||
|
const token = btoa(
|
||||||
|
`${process.env.BULLMQ_DASHBOARD_USERNAME}:${process.env.BULLMQ_DASHBOARD_PASSWORD}`
|
||||||
|
);
|
||||||
|
const queues = await request.get(
|
||||||
|
`http://localhost:${process.env.PORT}/admin/queues/api/queues?activeQueue=flow&status=delayed&page=1`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Basic ${token}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const queuesJsonResponse = await queues.json();
|
||||||
|
const flowQueue = queuesJsonResponse.queues.find(
|
||||||
|
(queue) => queue.name === 'flow'
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
flowQueue.jobs.find((job) => job.name === `flow-${flowId}`)
|
||||||
|
).toBeUndefined();
|
||||||
|
};
|
54
packages/e2e-tests/helpers/flow-api-helper.js
Normal file
54
packages/e2e-tests/helpers/flow-api-helper.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const { expect } = require('../fixtures/index');
|
||||||
|
|
||||||
|
export const createFlow = async (request, token) => {
|
||||||
|
const response = await request.post(
|
||||||
|
`http://localhost:${process.env.PORT}/api/v1/flows`,
|
||||||
|
{ headers: { Authorization: token } }
|
||||||
|
);
|
||||||
|
await expect(response.status()).toBe(201);
|
||||||
|
return await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateFlowName = async (request, token, flowId) => {
|
||||||
|
const updateFlowNameResponse = await request.patch(
|
||||||
|
`http://localhost:${process.env.PORT}/api/v1/flows/${flowId}`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: token },
|
||||||
|
data: { name: flowId },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expect(updateFlowNameResponse.status()).toBe(200);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateFlowStep = async (request, token, stepId, requestBody) => {
|
||||||
|
const updateTriggerStepResponse = await request.patch(
|
||||||
|
`http://localhost:${process.env.PORT}/api/v1/steps/${stepId}`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: token },
|
||||||
|
data: requestBody,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expect(updateTriggerStepResponse.status()).toBe(200);
|
||||||
|
return await updateTriggerStepResponse.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testStep = async (request, token, stepId) => {
|
||||||
|
const testTriggerStepResponse = await request.post(
|
||||||
|
`http://localhost:${process.env.PORT}/api/v1/steps/${stepId}/test`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: token },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expect(testTriggerStepResponse.status()).toBe(200);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const publishFlow = async (request, token, flowId) => {
|
||||||
|
const publishFlowResponse = await request.patch(
|
||||||
|
`http://localhost:${process.env.PORT}/api/v1/flows/${flowId}/status`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: token },
|
||||||
|
data: { active: true },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expect(publishFlowResponse.status()).toBe(200);
|
||||||
|
};
|
213
packages/e2e-tests/tests/flow/delete-flow.spec.js
Normal file
213
packages/e2e-tests/tests/flow/delete-flow.spec.js
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
const { test, expect } = require('../../fixtures/index');
|
||||||
|
const { expectNoDelayedJobForFlow } = require('../../helpers/bullmq-helper');
|
||||||
|
const {
|
||||||
|
createFlow,
|
||||||
|
updateFlowName,
|
||||||
|
updateFlowStep,
|
||||||
|
testStep,
|
||||||
|
publishFlow,
|
||||||
|
} = require('../../helpers/flow-api-helper');
|
||||||
|
|
||||||
|
let tokenJsonResponse;
|
||||||
|
|
||||||
|
test.beforeAll(async ({ request }) => {
|
||||||
|
const tokenResponse = await request.post(
|
||||||
|
`http://localhost:${process.env.PORT}/api/v1/access-tokens`,
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
email: process.env.LOGIN_EMAIL,
|
||||||
|
password: process.env.LOGIN_PASSWORD,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expect(tokenResponse.status()).toBe(200);
|
||||||
|
tokenJsonResponse = await tokenResponse.json();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Empty flow can be deleted', async ({ page, request, flowsPage }) => {
|
||||||
|
const flow = await createFlow(request, tokenJsonResponse.data.token);
|
||||||
|
const flowId = flow.data.id;
|
||||||
|
await updateFlowName(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
await flowsPage.deleteFlow(flowId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Completed webhook flow can be deleted', async ({
|
||||||
|
page,
|
||||||
|
request,
|
||||||
|
flowsPage,
|
||||||
|
}) => {
|
||||||
|
const flow = await createFlow(request, tokenJsonResponse.data.token);
|
||||||
|
const flowId = flow.data.id;
|
||||||
|
const flowSteps = flow.data.steps;
|
||||||
|
await updateFlowName(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
const triggerStepId = flowSteps.find((step) => step.type === 'trigger').id;
|
||||||
|
const actionStepId = flowSteps.find((step) => step.type === 'action').id;
|
||||||
|
|
||||||
|
const triggerStep = await updateFlowStep(
|
||||||
|
request,
|
||||||
|
tokenJsonResponse.data.token,
|
||||||
|
triggerStepId,
|
||||||
|
{
|
||||||
|
appKey: 'webhook',
|
||||||
|
key: 'catchRawWebhook',
|
||||||
|
parameters: {
|
||||||
|
workSynchronously: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, triggerStepId);
|
||||||
|
|
||||||
|
await updateFlowStep(request, tokenJsonResponse.data.token, actionStepId, {
|
||||||
|
appKey: 'webhook',
|
||||||
|
key: 'respondWith',
|
||||||
|
parameters: {
|
||||||
|
statusCode: '200',
|
||||||
|
body: 'ok',
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, actionStepId);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
await flowsPage.deleteFlow(flowId);
|
||||||
|
const triggerWebhookResponse = await request.get(triggerStep.data.webhookUrl);
|
||||||
|
await expect(triggerWebhookResponse.status()).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Completed poll flow can be deleted', async ({
|
||||||
|
page,
|
||||||
|
request,
|
||||||
|
flowsPage,
|
||||||
|
}) => {
|
||||||
|
const flow = await createFlow(request, tokenJsonResponse.data.token);
|
||||||
|
const flowId = flow.data.id;
|
||||||
|
const flowSteps = flow.data.steps;
|
||||||
|
await updateFlowName(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
const triggerStepId = flowSteps.find((step) => step.type === 'trigger').id;
|
||||||
|
const actionStepId = flowSteps.find((step) => step.type === 'action').id;
|
||||||
|
|
||||||
|
await updateFlowStep(request, tokenJsonResponse.data.token, triggerStepId, {
|
||||||
|
appKey: 'rss',
|
||||||
|
key: 'newItemsInFeed',
|
||||||
|
parameters: { feedUrl: 'https://feeds.bbci.co.uk/news/rss.xml' },
|
||||||
|
});
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, triggerStepId);
|
||||||
|
|
||||||
|
await updateFlowStep(request, tokenJsonResponse.data.token, actionStepId, {
|
||||||
|
appKey: 'datastore',
|
||||||
|
key: 'setValue',
|
||||||
|
parameters: {
|
||||||
|
key: 'newsTitle',
|
||||||
|
value: '{{step.' + triggerStepId + '.title}}',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, actionStepId);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
await flowsPage.deleteFlow(flowId);
|
||||||
|
|
||||||
|
await expectNoDelayedJobForFlow(request, flowId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Published webhook flow can be deleted', async ({
|
||||||
|
page,
|
||||||
|
request,
|
||||||
|
flowsPage,
|
||||||
|
}) => {
|
||||||
|
const flow = await createFlow(request, tokenJsonResponse.data.token);
|
||||||
|
const flowId = flow.data.id;
|
||||||
|
const flowSteps = flow.data.steps;
|
||||||
|
await updateFlowName(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
const triggerStepId = flowSteps.find((step) => step.type === 'trigger').id;
|
||||||
|
const actionStepId = flowSteps.find((step) => step.type === 'action').id;
|
||||||
|
|
||||||
|
const triggerStep = await updateFlowStep(
|
||||||
|
request,
|
||||||
|
tokenJsonResponse.data.token,
|
||||||
|
triggerStepId,
|
||||||
|
{
|
||||||
|
appKey: 'webhook',
|
||||||
|
key: 'catchRawWebhook',
|
||||||
|
parameters: {
|
||||||
|
workSynchronously: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, triggerStepId);
|
||||||
|
|
||||||
|
await updateFlowStep(request, tokenJsonResponse.data.token, actionStepId, {
|
||||||
|
appKey: 'webhook',
|
||||||
|
key: 'respondWith',
|
||||||
|
parameters: {
|
||||||
|
statusCode: '200',
|
||||||
|
body: 'ok',
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, actionStepId);
|
||||||
|
|
||||||
|
await publishFlow(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
await flowsPage.deleteFlow(flowId);
|
||||||
|
const triggerWebhookResponse = await request.get(triggerStep.data.webhookUrl);
|
||||||
|
await expect(triggerWebhookResponse.status()).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Published poll flow can be deleted', async ({
|
||||||
|
page,
|
||||||
|
request,
|
||||||
|
flowsPage,
|
||||||
|
}) => {
|
||||||
|
const flow = await createFlow(request, tokenJsonResponse.data.token);
|
||||||
|
const flowId = flow.data.id;
|
||||||
|
const flowSteps = flow.data.steps;
|
||||||
|
await updateFlowName(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
const triggerStepId = flowSteps.find((step) => step.type === 'trigger').id;
|
||||||
|
const actionStepId = flowSteps.find((step) => step.type === 'action').id;
|
||||||
|
|
||||||
|
await updateFlowStep(request, tokenJsonResponse.data.token, triggerStepId, {
|
||||||
|
appKey: 'rss',
|
||||||
|
key: 'newItemsInFeed',
|
||||||
|
parameters: { feedUrl: 'https://feeds.bbci.co.uk/news/rss.xml' },
|
||||||
|
});
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, triggerStepId);
|
||||||
|
|
||||||
|
await updateFlowStep(request, tokenJsonResponse.data.token, actionStepId, {
|
||||||
|
appKey: 'datastore',
|
||||||
|
key: 'setValue',
|
||||||
|
parameters: {
|
||||||
|
key: 'newsTitle',
|
||||||
|
value: '{{step.' + triggerStepId + '.title}}',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, actionStepId);
|
||||||
|
|
||||||
|
await publishFlow(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
await flowsPage.deleteFlow(flowId);
|
||||||
|
|
||||||
|
await expectNoDelayedJobForFlow(request, flowId);
|
||||||
|
});
|
@@ -20,7 +20,7 @@ function AdminApplicationCreateAuthClient(props) {
|
|||||||
const {
|
const {
|
||||||
mutateAsync: createAppConfig,
|
mutateAsync: createAppConfig,
|
||||||
isPending: isCreateAppConfigPending,
|
isPending: isCreateAppConfigPending,
|
||||||
error: createAppConfigError,
|
error: createAppConfigError
|
||||||
} = useAdminCreateAppConfig(props.appKey);
|
} = useAdminCreateAppConfig(props.appKey);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -30,15 +30,16 @@ function AdminApplicationCreateAuthClient(props) {
|
|||||||
} = useAdminCreateAppAuthClient(appKey);
|
} = useAdminCreateAppAuthClient(appKey);
|
||||||
|
|
||||||
const submitHandler = async (values) => {
|
const submitHandler = async (values) => {
|
||||||
let appConfigKey = appConfig?.data?.key;
|
let appConfigId = appConfig?.data?.id;
|
||||||
|
|
||||||
if (!appConfigKey) {
|
if (!appConfigId) {
|
||||||
const { data: appConfigData } = await createAppConfig({
|
const { data: appConfigData } = await createAppConfig({
|
||||||
customConnectionAllowed: true,
|
allowCustomConnection: true,
|
||||||
shared: false,
|
shared: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
});
|
});
|
||||||
appConfigKey = appConfigData.key;
|
|
||||||
|
appConfigId = appConfigData.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, active, ...formattedAuthDefaults } = values;
|
const { name, active, ...formattedAuthDefaults } = values;
|
||||||
|
@@ -46,8 +46,7 @@ function AdminApplicationSettings(props) {
|
|||||||
|
|
||||||
const defaultValues = useMemo(
|
const defaultValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
customConnectionAllowed:
|
allowCustomConnection: appConfig?.data?.allowCustomConnection || false,
|
||||||
appConfig?.data?.customConnectionAllowed || false,
|
|
||||||
shared: appConfig?.data?.shared || false,
|
shared: appConfig?.data?.shared || false,
|
||||||
disabled: appConfig?.data?.disabled || false,
|
disabled: appConfig?.data?.disabled || false,
|
||||||
}),
|
}),
|
||||||
@@ -62,8 +61,8 @@ function AdminApplicationSettings(props) {
|
|||||||
<Paper sx={{ p: 2, mt: 4 }}>
|
<Paper sx={{ p: 2, mt: 4 }}>
|
||||||
<Stack spacing={2} direction="column">
|
<Stack spacing={2} direction="column">
|
||||||
<Switch
|
<Switch
|
||||||
name="customConnectionAllowed"
|
name="allowCustomConnection"
|
||||||
label={formatMessage('adminAppsSettings.customConnectionAllowed')}
|
label={formatMessage('adminAppsSettings.allowCustomConnection')}
|
||||||
FormControlLabelProps={{
|
FormControlLabelProps={{
|
||||||
labelPlacement: 'start',
|
labelPlacement: 'start',
|
||||||
}}
|
}}
|
||||||
|
@@ -93,17 +93,14 @@ function ChooseConnectionSubstep(props) {
|
|||||||
appWithConnections?.map((connection) => optionGenerator(connection)) ||
|
appWithConnections?.map((connection) => optionGenerator(connection)) ||
|
||||||
[];
|
[];
|
||||||
|
|
||||||
if (
|
if (!appConfig?.data || appConfig?.data?.canCustomConnect) {
|
||||||
!appConfig?.data ||
|
|
||||||
(!appConfig.data?.disabled && appConfig.data?.customConnectionAllowed)
|
|
||||||
) {
|
|
||||||
options.push({
|
options.push({
|
||||||
label: formatMessage('chooseConnectionSubstep.addNewConnection'),
|
label: formatMessage('chooseConnectionSubstep.addNewConnection'),
|
||||||
value: ADD_CONNECTION_VALUE,
|
value: ADD_CONNECTION_VALUE,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appConfig?.data?.connectionAllowed) {
|
if (appConfig?.data?.canConnect) {
|
||||||
options.push({
|
options.push({
|
||||||
label: formatMessage('chooseConnectionSubstep.addNewSharedConnection'),
|
label: formatMessage('chooseConnectionSubstep.addNewSharedConnection'),
|
||||||
value: ADD_SHARED_CONNECTION_VALUE,
|
value: ADD_SHARED_CONNECTION_VALUE,
|
||||||
|
@@ -40,7 +40,7 @@ SuggestionItem.propTypes = {
|
|||||||
data: PropTypes.arrayOf(
|
data: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
sampleValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
sampleValue: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
).isRequired,
|
).isRequired,
|
||||||
onSuggestionClick: PropTypes.func.isRequired,
|
onSuggestionClick: PropTypes.func.isRequired,
|
||||||
|
@@ -53,13 +53,15 @@ function SignUpForm() {
|
|||||||
}, [authentication.isAuthenticated]);
|
}, [authentication.isAuthenticated]);
|
||||||
|
|
||||||
const handleSubmit = async (values) => {
|
const handleSubmit = async (values) => {
|
||||||
|
const { fullName, email, password } = values;
|
||||||
|
|
||||||
|
await registerUser({
|
||||||
|
fullName,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { fullName, email, password } = values;
|
|
||||||
await registerUser({
|
|
||||||
fullName,
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
const { data } = await createAccessToken({
|
const { data } = await createAccessToken({
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
@@ -67,27 +69,9 @@ function SignUpForm() {
|
|||||||
const { token } = data;
|
const { token } = data;
|
||||||
authentication.updateToken(token);
|
authentication.updateToken(token);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errors = error?.response?.data?.errors
|
enqueueSnackbar(error?.message || formatMessage('signupForm.error'), {
|
||||||
? Object.values(error.response.data.errors)
|
variant: 'error',
|
||||||
: [];
|
});
|
||||||
|
|
||||||
if (errors.length) {
|
|
||||||
for (const [error] of errors) {
|
|
||||||
enqueueSnackbar(error, {
|
|
||||||
variant: 'error',
|
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-sign-up-error',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
enqueueSnackbar(error?.message || formatMessage('signupForm.error'), {
|
|
||||||
variant: 'error',
|
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-sign-up-error',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -32,8 +32,7 @@ Variable.propTypes = {
|
|||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
element: PropTypes.shape({
|
element: PropTypes.shape({
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
sampleValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
sampleValue: PropTypes.string.isRequired,
|
||||||
.isRequired,
|
|
||||||
}),
|
}),
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import api from 'helpers/api';
|
import api from 'helpers/api';
|
||||||
import { enqueueSnackbar } from 'notistack';
|
|
||||||
|
|
||||||
export default function useAdminCreateSamlAuthProvider() {
|
export default function useAdminCreateSamlAuthProvider() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -16,20 +15,6 @@ export default function useAdminCreateSamlAuthProvider() {
|
|||||||
queryKey: ['admin', 'samlAuthProviders'],
|
queryKey: ['admin', 'samlAuthProviders'],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
|
||||||
const errors = Object.entries(
|
|
||||||
error.response.data.errors || [['', 'Failed while saving!']],
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const error of errors) {
|
|
||||||
enqueueSnackbar(`${error[0] ? error[0] + ': ' : ''} ${error[1]}`, {
|
|
||||||
variant: 'error',
|
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-create-saml-auth-provider-error',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import api from 'helpers/api';
|
import api from 'helpers/api';
|
||||||
import { enqueueSnackbar } from 'notistack';
|
|
||||||
|
|
||||||
export default function useAdminUpdateSamlAuthProvider(samlAuthProviderId) {
|
export default function useAdminUpdateSamlAuthProvider(samlAuthProviderId) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -19,20 +18,6 @@ export default function useAdminUpdateSamlAuthProvider(samlAuthProviderId) {
|
|||||||
queryKey: ['admin', 'samlAuthProviders'],
|
queryKey: ['admin', 'samlAuthProviders'],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
|
||||||
const errors = Object.entries(
|
|
||||||
error.response.data.errors || [['', 'Failed while saving!']],
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const error of errors) {
|
|
||||||
enqueueSnackbar(`${error[0] ? error[0] + ': ' : ''} ${error[1]}`, {
|
|
||||||
variant: 'error',
|
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-update-saml-auth-provider-error',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
|
@@ -292,7 +292,7 @@
|
|||||||
"adminApps.connections": "Connections",
|
"adminApps.connections": "Connections",
|
||||||
"adminApps.authClients": "Auth clients",
|
"adminApps.authClients": "Auth clients",
|
||||||
"adminApps.settings": "Settings",
|
"adminApps.settings": "Settings",
|
||||||
"adminAppsSettings.customConnectionAllowed": "Allow custom connection",
|
"adminAppsSettings.allowCustomConnection": "Allow custom connection",
|
||||||
"adminAppsSettings.shared": "Shared",
|
"adminAppsSettings.shared": "Shared",
|
||||||
"adminAppsSettings.disabled": "Disabled",
|
"adminAppsSettings.disabled": "Disabled",
|
||||||
"adminAppsSettings.save": "Save",
|
"adminAppsSettings.save": "Save",
|
||||||
|
@@ -77,15 +77,14 @@ export default function Application() {
|
|||||||
|
|
||||||
const connectionOptions = React.useMemo(() => {
|
const connectionOptions = React.useMemo(() => {
|
||||||
const shouldHaveCustomConnection =
|
const shouldHaveCustomConnection =
|
||||||
appConfig?.data?.connectionAllowed &&
|
appConfig?.data?.canConnect && appConfig?.data?.canCustomConnect;
|
||||||
appConfig?.data?.customConnectionAllowed;
|
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
label: formatMessage('app.addConnection'),
|
label: formatMessage('app.addConnection'),
|
||||||
key: 'addConnection',
|
key: 'addConnection',
|
||||||
'data-test': 'add-connection-button',
|
'data-test': 'add-connection-button',
|
||||||
to: URLS.APP_ADD_CONNECTION(appKey, appConfig?.data?.connectionAllowed),
|
to: URLS.APP_ADD_CONNECTION(appKey, appConfig?.data?.canConnect),
|
||||||
disabled: !currentUserAbility.can('create', 'Connection'),
|
disabled: !currentUserAbility.can('create', 'Connection'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -156,9 +155,8 @@ export default function Application() {
|
|||||||
disabled={
|
disabled={
|
||||||
!allowed ||
|
!allowed ||
|
||||||
(appConfig?.data &&
|
(appConfig?.data &&
|
||||||
!appConfig?.data?.disabled &&
|
!appConfig?.data?.canConnect &&
|
||||||
!appConfig?.data?.connectionAllowed &&
|
!appConfig?.data?.canCustomConnect) ||
|
||||||
!appConfig?.data?.customConnectionAllowed) ||
|
|
||||||
connectionOptions.every(({ disabled }) => disabled)
|
connectionOptions.every(({ disabled }) => disabled)
|
||||||
}
|
}
|
||||||
options={connectionOptions}
|
options={connectionOptions}
|
||||||
|
@@ -56,19 +56,6 @@ function RoleMappings({ provider, providerLoading }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errors = Object.values(
|
|
||||||
error.response.data.errors || [['Failed while saving!']],
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const [error] of errors) {
|
|
||||||
enqueueSnackbar(error, {
|
|
||||||
variant: 'error',
|
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-update-role-mappings-error',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Failed while saving!');
|
throw new Error('Failed while saving!');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -66,10 +66,10 @@ function RoleMappingsFieldArray() {
|
|||||||
<MuiTextField
|
<MuiTextField
|
||||||
{...params}
|
{...params}
|
||||||
label={formatMessage('roleMappingsForm.role')}
|
label={formatMessage('roleMappingsForm.role')}
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
loading={isRolesLoading}
|
loading={isRolesLoading}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@@ -65,7 +65,7 @@ function SamlConfiguration({ provider, providerLoading }) {
|
|||||||
'data-test': 'snackbar-save-saml-provider-success',
|
'data-test': 'snackbar-save-saml-provider-success',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
throw new Error('Failed while saving!');
|
throw new Error('Failed while saving!');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -459,8 +459,9 @@ export const SamlAuthProviderRolePropType = PropTypes.shape({
|
|||||||
export const AppConfigPropType = PropTypes.shape({
|
export const AppConfigPropType = PropTypes.shape({
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
key: PropTypes.string,
|
key: PropTypes.string,
|
||||||
customConnectionAllowed: PropTypes.bool,
|
allowCustomConnection: PropTypes.bool,
|
||||||
connectionAllowed: PropTypes.bool,
|
canConnect: PropTypes.bool,
|
||||||
|
canCustomConnect: PropTypes.bool,
|
||||||
shared: PropTypes.bool,
|
shared: PropTypes.bool,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
});
|
});
|
||||||
@@ -468,7 +469,7 @@ export const AppConfigPropType = PropTypes.shape({
|
|||||||
export const AppAuthClientPropType = PropTypes.shape({
|
export const AppAuthClientPropType = PropTypes.shape({
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
appConfigKey: PropTypes.string,
|
appConfigId: PropTypes.string,
|
||||||
authDefaults: PropTypes.string,
|
authDefaults: PropTypes.string,
|
||||||
formattedAuthDefaults: PropTypes.object,
|
formattedAuthDefaults: PropTypes.object,
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
|
16
yarn.lock
16
yarn.lock
@@ -4111,10 +4111,10 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz"
|
resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz"
|
||||||
integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==
|
integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==
|
||||||
|
|
||||||
"@types/http-proxy@^1.17.8":
|
"@types/http-proxy@^1.17.5":
|
||||||
version "1.17.15"
|
version "1.17.8"
|
||||||
resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.15.tgz#12118141ce9775a6499ecb4c01d02f90fc839d36"
|
resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz"
|
||||||
integrity sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==
|
integrity sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
@@ -9451,11 +9451,11 @@ http-proxy-agent@^7.0.0:
|
|||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
|
|
||||||
http-proxy-middleware@^2.0.0:
|
http-proxy-middleware@^2.0.0:
|
||||||
version "2.0.7"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6"
|
resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz"
|
||||||
integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==
|
integrity sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/http-proxy" "^1.17.8"
|
"@types/http-proxy" "^1.17.5"
|
||||||
http-proxy "^1.18.1"
|
http-proxy "^1.18.1"
|
||||||
is-glob "^4.0.1"
|
is-glob "^4.0.1"
|
||||||
is-plain-obj "^3.0.0"
|
is-plain-obj "^3.0.0"
|
||||||
|
Reference in New Issue
Block a user