Merge pull request #2110 from automatisch/aut-1293

test(app-config): write model tests
This commit is contained in:
Ömer Faruk Aydın
2024-10-25 01:18:26 +02:00
committed by GitHub
29 changed files with 528 additions and 74 deletions

View File

@@ -10,11 +10,11 @@ export default async (request, response) => {
}; };
const appConfigParams = (request) => { const appConfigParams = (request) => {
const { allowCustomConnection, shared, disabled } = request.body; const { customConnectionAllowed, shared, disabled } = request.body;
return { return {
key: request.params.appKey, key: request.params.appKey,
allowCustomConnection, customConnectionAllowed,
shared, shared,
disabled, disabled,
}; };

View File

@@ -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 = {
allowCustomConnection: true, customConnectionAllowed: 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',
allowCustomConnection: true, customConnectionAllowed: true,
shared: true, shared: true,
disabled: false, disabled: false,
}; };

View File

@@ -8,16 +8,19 @@ export default async (request, response) => {
}) })
.throwIfNotFound(); .throwIfNotFound();
await appConfig.$query().patchAndFetch(appConfigParams(request)); await appConfig.$query().patchAndFetch({
...appConfigParams(request),
key: request.params.appKey,
});
renderObject(response, appConfig); renderObject(response, appConfig);
}; };
const appConfigParams = (request) => { const appConfigParams = (request) => {
const { allowCustomConnection, shared, disabled } = request.body; const { customConnectionAllowed, shared, disabled } = request.body;
return { return {
allowCustomConnection, customConnectionAllowed,
shared, shared,
disabled, disabled,
}; };

View File

@@ -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',
allowCustomConnection: true, customConnectionAllowed: 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,
allowCustomConnection: false, customConnectionAllowed: 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,
allowCustomConnection: false, customConnectionAllowed: 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',
allowCustomConnection: true, customConnectionAllowed: true,
shared: true, shared: true,
disabled: false, disabled: false,
}; };

View File

@@ -155,7 +155,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
await createAppConfig({ await createAppConfig({
key: 'gitlab', key: 'gitlab',
disabled: false, disabled: false,
allowCustomConnection: true, customConnectionAllowed: true,
}); });
}); });
@@ -218,7 +218,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
await createAppConfig({ await createAppConfig({
key: 'gitlab', key: 'gitlab',
disabled: false, disabled: false,
allowCustomConnection: false, customConnectionAllowed: false,
}); });
}); });

View File

@@ -17,7 +17,7 @@ describe('GET /api/v1/apps/:appKey/config', () => {
appConfig = await createAppConfig({ appConfig = await createAppConfig({
key: 'deepl', key: 'deepl',
allowCustomConnection: true, customConnectionAllowed: true,
shared: true, shared: true,
disabled: false, disabled: false,
}); });

View File

@@ -0,0 +1,37 @@
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');
});
}

View File

@@ -0,0 +1,11 @@
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');
});
}

View File

@@ -0,0 +1,13 @@
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');
});
}

View File

@@ -0,0 +1,11 @@
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()'));
});
}

View File

@@ -0,0 +1,41 @@
// 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",
}
`;

View File

@@ -2,6 +2,7 @@ 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';
@@ -21,6 +22,17 @@ 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;
@@ -48,6 +60,17 @@ 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) {
@@ -55,11 +78,23 @@ 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();
} }

View File

@@ -2,9 +2,12 @@ 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', () => {
@@ -15,6 +18,23 @@ 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(
@@ -140,6 +160,63 @@ 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,
@@ -151,6 +228,17 @@ 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();
@@ -164,6 +252,19 @@ 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();

View File

@@ -5,6 +5,10 @@ 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'],
@@ -12,7 +16,8 @@ class AppConfig extends Base {
properties: { properties: {
id: { type: 'string', format: 'uuid' }, id: { type: 'string', format: 'uuid' },
key: { type: 'string' }, key: { type: 'string' },
allowCustomConnection: { type: 'boolean', default: false }, connectionAllowed: { 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' },
@@ -31,31 +36,44 @@ 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;

View File

@@ -0,0 +1,180 @@
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();
});
});

View File

@@ -89,7 +89,7 @@ class Connection extends Base {
} }
if (this.appConfig) { if (this.appConfig) {
return !this.appConfig.disabled && this.appConfig.allowCustomConnection; return !this.appConfig.disabled && this.appConfig.customConnectionAllowed;
} }
return true; return true;
@@ -144,7 +144,7 @@ class Connection extends Base {
); );
} }
if (!appConfig.allowCustomConnection && this.formattedData) { if (!appConfig.customConnectionAllowed && 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}!`
); );

View File

@@ -121,7 +121,7 @@ describe('Connection model', () => {
const appConfig = await createAppConfig({ const appConfig = await createAppConfig({
key: 'gitlab', key: 'gitlab',
disabled: false, disabled: false,
allowCustomConnection: true, customConnectionAllowed: true,
}); });
const connection = await createConnection({ const connection = await createConnection({
@@ -151,7 +151,7 @@ describe('Connection model', () => {
await createAppConfig({ await createAppConfig({
key: 'gitlab', key: 'gitlab',
disabled: true, disabled: true,
allowCustomConnection: false, customConnectionAllowed: false,
}); });
const connectionWithAppAuthClient = await connection const connectionWithAppAuthClient = await connection
@@ -373,7 +373,7 @@ describe('Connection model', () => {
vi.spyOn(Connection.prototype, 'getAppConfig').mockResolvedValue({ vi.spyOn(Connection.prototype, 'getAppConfig').mockResolvedValue({
disabled: false, disabled: false,
allowCustomConnection: false, customConnectionAllowed: false,
}); });
const connection = new Connection(); const connection = new Connection();
@@ -410,7 +410,7 @@ describe('Connection model', () => {
await createAppConfig({ await createAppConfig({
key: 'gitlab', key: 'gitlab',
disabled: false, disabled: false,
allowCustomConnection: true, customConnectionAllowed: true,
shared: true, shared: true,
}); });

View File

@@ -2,11 +2,10 @@ const appConfigSerializer = (appConfig) => {
return { return {
id: appConfig.id, id: appConfig.id,
key: appConfig.key, key: appConfig.key,
allowCustomConnection: appConfig.allowCustomConnection, customConnectionAllowed: appConfig.customConnectionAllowed,
shared: appConfig.shared, shared: appConfig.shared,
disabled: appConfig.disabled, disabled: appConfig.disabled,
canConnect: appConfig.canConnect, connectionAllowed: appConfig.connectionAllowed,
canCustomConnect: appConfig.canCustomConnect,
createdAt: appConfig.createdAt.getTime(), createdAt: appConfig.createdAt.getTime(),
updatedAt: appConfig.updatedAt.getTime(), updatedAt: appConfig.updatedAt.getTime(),
}; };

View File

@@ -13,11 +13,10 @@ describe('appConfig serializer', () => {
const expectedPayload = { const expectedPayload = {
id: appConfig.id, id: appConfig.id,
key: appConfig.key, key: appConfig.key,
allowCustomConnection: appConfig.allowCustomConnection, customConnectionAllowed: appConfig.customConnectionAllowed,
shared: appConfig.shared, shared: appConfig.shared,
disabled: appConfig.disabled, disabled: appConfig.disabled,
canConnect: appConfig.canConnect, connectionAllowed: appConfig.connectionAllowed,
canCustomConnect: appConfig.canCustomConnect,
createdAt: appConfig.createdAt.getTime(), createdAt: appConfig.createdAt.getTime(),
updatedAt: appConfig.updatedAt.getTime(), updatedAt: appConfig.updatedAt.getTime(),
}; };

View File

@@ -10,7 +10,6 @@ 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 =

View File

@@ -2,7 +2,7 @@ const createAppConfigMock = (appConfig) => {
return { return {
data: { data: {
key: appConfig.key, key: appConfig.key,
allowCustomConnection: appConfig.allowCustomConnection, customConnectionAllowed: appConfig.customConnectionAllowed,
shared: appConfig.shared, shared: appConfig.shared,
disabled: appConfig.disabled, disabled: appConfig.disabled,
}, },

View File

@@ -3,11 +3,10 @@ const getAppConfigMock = (appConfig) => {
data: { data: {
id: appConfig.id, id: appConfig.id,
key: appConfig.key, key: appConfig.key,
allowCustomConnection: appConfig.allowCustomConnection, customConnectionAllowed: appConfig.customConnectionAllowed,
shared: appConfig.shared, shared: appConfig.shared,
disabled: appConfig.disabled, disabled: appConfig.disabled,
canConnect: appConfig.canConnect, connectionAllowed: appConfig.connectionAllowed,
canCustomConnect: appConfig.canCustomConnect,
createdAt: appConfig.createdAt.getTime(), createdAt: appConfig.createdAt.getTime(),
updatedAt: appConfig.updatedAt.getTime(), updatedAt: appConfig.updatedAt.getTime(),
}, },

View File

@@ -8,11 +8,15 @@ export class AdminApplicationSettingsPage extends AuthenticatedPage {
constructor(page) { constructor(page) {
super(page); super(page);
this.allowCustomConnectionsSwitch = this.page.locator('[name="allowCustomConnection"]'); this.allowCustomConnectionsSwitch = this.page.locator(
'[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('snackbar-save-admin-apps-settings-success'); this.successSnackbar = this.page.getByTestId(
'snackbar-save-admin-apps-settings-success'
);
} }
async allowCustomConnections() { async allowCustomConnections() {

View File

@@ -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,16 +30,15 @@ function AdminApplicationCreateAuthClient(props) {
} = useAdminCreateAppAuthClient(appKey); } = useAdminCreateAppAuthClient(appKey);
const submitHandler = async (values) => { const submitHandler = async (values) => {
let appConfigId = appConfig?.data?.id; let appConfigKey = appConfig?.data?.key;
if (!appConfigId) { if (!appConfigKey) {
const { data: appConfigData } = await createAppConfig({ const { data: appConfigData } = await createAppConfig({
allowCustomConnection: true, customConnectionAllowed: 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;

View File

@@ -46,7 +46,8 @@ function AdminApplicationSettings(props) {
const defaultValues = useMemo( const defaultValues = useMemo(
() => ({ () => ({
allowCustomConnection: appConfig?.data?.allowCustomConnection || false, customConnectionAllowed:
appConfig?.data?.customConnectionAllowed || false,
shared: appConfig?.data?.shared || false, shared: appConfig?.data?.shared || false,
disabled: appConfig?.data?.disabled || false, disabled: appConfig?.data?.disabled || false,
}), }),
@@ -61,8 +62,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="allowCustomConnection" name="customConnectionAllowed"
label={formatMessage('adminAppsSettings.allowCustomConnection')} label={formatMessage('adminAppsSettings.customConnectionAllowed')}
FormControlLabelProps={{ FormControlLabelProps={{
labelPlacement: 'start', labelPlacement: 'start',
}} }}

View File

@@ -93,14 +93,17 @@ function ChooseConnectionSubstep(props) {
appWithConnections?.map((connection) => optionGenerator(connection)) || appWithConnections?.map((connection) => optionGenerator(connection)) ||
[]; [];
if (!appConfig?.data || appConfig?.data?.canCustomConnect) { if (
!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?.canConnect) { if (appConfig?.data?.connectionAllowed) {
options.push({ options.push({
label: formatMessage('chooseConnectionSubstep.addNewSharedConnection'), label: formatMessage('chooseConnectionSubstep.addNewSharedConnection'),
value: ADD_SHARED_CONNECTION_VALUE, value: ADD_SHARED_CONNECTION_VALUE,

View File

@@ -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.allowCustomConnection": "Allow custom connection", "adminAppsSettings.customConnectionAllowed": "Allow custom connection",
"adminAppsSettings.shared": "Shared", "adminAppsSettings.shared": "Shared",
"adminAppsSettings.disabled": "Disabled", "adminAppsSettings.disabled": "Disabled",
"adminAppsSettings.save": "Save", "adminAppsSettings.save": "Save",

View File

@@ -77,14 +77,15 @@ export default function Application() {
const connectionOptions = React.useMemo(() => { const connectionOptions = React.useMemo(() => {
const shouldHaveCustomConnection = const shouldHaveCustomConnection =
appConfig?.data?.canConnect && appConfig?.data?.canCustomConnect; appConfig?.data?.connectionAllowed &&
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?.canConnect), to: URLS.APP_ADD_CONNECTION(appKey, appConfig?.data?.connectionAllowed),
disabled: !currentUserAbility.can('create', 'Connection'), disabled: !currentUserAbility.can('create', 'Connection'),
}, },
]; ];
@@ -155,8 +156,9 @@ export default function Application() {
disabled={ disabled={
!allowed || !allowed ||
(appConfig?.data && (appConfig?.data &&
!appConfig?.data?.canConnect && !appConfig?.data?.disabled &&
!appConfig?.data?.canCustomConnect) || !appConfig?.data?.connectionAllowed &&
!appConfig?.data?.customConnectionAllowed) ||
connectionOptions.every(({ disabled }) => disabled) connectionOptions.every(({ disabled }) => disabled)
} }
options={connectionOptions} options={connectionOptions}

View File

@@ -459,9 +459,8 @@ 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,
allowCustomConnection: PropTypes.bool, customConnectionAllowed: PropTypes.bool,
canConnect: PropTypes.bool, connectionAllowed: PropTypes.bool,
canCustomConnect: PropTypes.bool,
shared: PropTypes.bool, shared: PropTypes.bool,
disabled: PropTypes.bool, disabled: PropTypes.bool,
}); });
@@ -469,7 +468,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,
appConfigId: PropTypes.string, appConfigKey: PropTypes.string,
authDefaults: PropTypes.string, authDefaults: PropTypes.string,
formattedAuthDefaults: PropTypes.object, formattedAuthDefaults: PropTypes.object,
active: PropTypes.bool, active: PropTypes.bool,