Merge pull request #2130 from automatisch/aut-1322

test(connection): write model tests
This commit is contained in:
Ömer Faruk Aydın
2024-10-21 12:41:04 +02:00
committed by GitHub
3 changed files with 367 additions and 53 deletions

View File

@@ -8,7 +8,7 @@ export default async (request, response) => {
})
.throwIfNotFound();
connection = await connection.update(connectionParams(request));
connection = await connection.updateFormattedData(connectionParams(request));
renderObject(response, connection);
};

View File

@@ -122,10 +122,20 @@ class Connection extends Base {
return this.data ? true : false;
}
async checkEligibilityForCreation() {
const app = await App.findOneByKey(this.key);
async getApp() {
if (!this.key) return null;
const appConfig = await AppConfig.query().findOne({ key: this.key });
return await App.findOneByKey(this.key);
}
async getAppConfig() {
return await AppConfig.query().findOne({ key: this.key });
}
async checkEligibilityForCreation() {
const app = await this.getApp();
const appConfig = await this.getAppConfig();
if (appConfig) {
if (appConfig.disabled) {
@@ -160,12 +170,6 @@ class Connection extends Base {
return this;
}
async getApp() {
if (!this.key) return null;
return await App.findOneByKey(this.key);
}
async testAndUpdateConnection() {
const app = await this.getApp();
const $ = await globalVariable({ connection: this, app });
@@ -224,7 +228,7 @@ class Connection extends Base {
async reset() {
const formattedData = this?.formattedData?.screenName
? { screenName: this.formattedData.screenName }
: null;
: {};
const updatedConnection = await this.$query().patchAndFetch({
formattedData,
@@ -233,7 +237,7 @@ class Connection extends Base {
return updatedConnection;
}
async update({ formattedData, appAuthClientId }) {
async updateFormattedData({ formattedData, appAuthClientId }) {
if (appAuthClientId) {
const appAuthClient = await AppAuthClient.query()
.findById(appAuthClientId)

View File

@@ -3,11 +3,15 @@ import AES from 'crypto-js/aes.js';
import enc from 'crypto-js/enc-utf8.js';
import appConfig from '../config/app.js';
import AppAuthClient from './app-auth-client.js';
import App from './app.js';
import AppConfig from './app-config.js';
import Base from './base.js';
import Connection from './connection';
import Step from './step.js';
import User from './user.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', () => {
it('tableName should return correct name', () => {
@@ -26,54 +30,65 @@ describe('Connection model', () => {
expect(virtualAttributes).toStrictEqual(expectedAttributes);
});
it('relationMappings should return correct associations', () => {
const relationMappings = Connection.relationMappings();
describe('relationMappings', () => {
it('should return correct associations', () => {
const relationMappings = Connection.relationMappings();
const expectedRelations = {
user: {
relation: Base.BelongsToOneRelation,
modelClass: User,
join: {
from: 'connections.user_id',
to: 'users.id',
const expectedRelations = {
user: {
relation: Base.BelongsToOneRelation,
modelClass: User,
join: {
from: 'connections.user_id',
to: 'users.id',
},
},
},
steps: {
relation: Base.HasManyRelation,
modelClass: Step,
join: {
from: 'connections.id',
to: 'steps.connection_id',
steps: {
relation: Base.HasManyRelation,
modelClass: Step,
join: {
from: 'connections.id',
to: 'steps.connection_id',
},
},
},
triggerSteps: {
relation: Base.HasManyRelation,
modelClass: Step,
join: {
from: 'connections.id',
to: 'steps.connection_id',
triggerSteps: {
relation: Base.HasManyRelation,
modelClass: Step,
join: {
from: 'connections.id',
to: 'steps.connection_id',
},
filter: expect.any(Function),
},
filter: expect.any(Function),
},
appConfig: {
relation: Base.BelongsToOneRelation,
modelClass: AppConfig,
join: {
from: 'connections.key',
to: 'app_configs.key',
appConfig: {
relation: Base.BelongsToOneRelation,
modelClass: AppConfig,
join: {
from: 'connections.key',
to: 'app_configs.key',
},
},
},
appAuthClient: {
relation: Base.BelongsToOneRelation,
modelClass: AppAuthClient,
join: {
from: 'connections.app_auth_client_id',
to: 'app_auth_clients.id',
appAuthClient: {
relation: Base.BelongsToOneRelation,
modelClass: AppAuthClient,
join: {
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.todo('reconnectable');
@@ -160,4 +175,299 @@ describe('Connection model', () => {
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.todo('checkEligibilityForCreation', async () => {});
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.todo('should unverify connection and persist it');
});
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',
});
});
});
});