From d3a901e826aee5c8c94dfe366b2526fdc49bb1db Mon Sep 17 00:00:00 2001 From: Sander Vocke Date: Fri, 13 Nov 2020 16:50:27 +0100 Subject: [PATCH] Add back-end support and tests for integration objects. --- client/src/api.ts | 57 +++++++++- server/app.ts | 48 +++++--- server/endpoints/CreateIntegration.ts | 43 +++++++ server/endpoints/DeleteIntegration.ts | 49 ++++++++ server/endpoints/IntegrationDetails.ts | 34 ++++++ server/endpoints/ModifyIntegration.ts | 52 +++++++++ .../20201113155620_add_integrations.ts | 22 ++++ .../test/integration/flows/IntegrationFlow.js | 105 ++++++++++++++++++ server/test/integration/flows/helpers.js | 75 +++++++++++++ 9 files changed, 470 insertions(+), 15 deletions(-) create mode 100644 server/endpoints/CreateIntegration.ts create mode 100644 server/endpoints/DeleteIntegration.ts create mode 100644 server/endpoints/IntegrationDetails.ts create mode 100644 server/endpoints/ModifyIntegration.ts create mode 100644 server/migrations/20201113155620_add_integrations.ts create mode 100644 server/test/integration/flows/IntegrationFlow.js diff --git a/client/src/api.ts b/client/src/api.ts index d1cb334..63c3754 100644 --- a/client/src/api.ts +++ b/client/src/api.ts @@ -356,4 +356,59 @@ export function checkRegisterUserRequest(req: any): boolean { // Note: Login is handled by Passport.js, so it is not explicitly written here. export const LoginEndpoint = "/login"; -export const LogoutEndpoint = "/logout"; \ No newline at end of file +export const LogoutEndpoint = "/logout"; + +export enum IntegrationType { + spotify = "spotify", +} + +// Create a new integration (POST). +export const CreateIntegrationEndpoint = '/integration'; +export interface CreateIntegrationRequest { + name: string, + type: IntegrationType, + details: any, +} +export interface CreateIntegrationResponse { + id: number; +} +export function checkCreateIntegrationRequest(req: any): boolean { + return "body" in req && + "name" in req.body && + "type" in req.body && + "details" in req.body && + (req.body.type in IntegrationType); +} + +// Modify an existing integration (PUT). +export const ModifyIntegrationEndpoint = '/integration/:id'; +export interface ModifyIntegrationRequest { + name?: string, + type?: IntegrationType, + details?: any, +} +export interface ModifyIntegrationResponse { } +export function checkModifyIntegrationRequest(req: any): boolean { + if("type" in req.body && !(req.body.type in IntegrationType)) return false; + return true; +} + +// Get integration details (GET). +export const IntegrationDetailsEndpoint = '/integration/:id'; +export interface IntegrationDetailsRequest { } +export interface IntegrationDetailsResponse { + name: string, + type: IntegrationType, + details: any, +} +export function checkIntegrationDetailsRequest(req: any): boolean { + return true; +} + +// Delete integration (DELETE). +export const DeleteIntegrationEndpoint = '/integration/:id'; +export interface DeleteIntegrationRequest { } +export interface DeleteIntegrationResponse { } +export function checkDeleteIntegrationRequest(req: any): boolean { + return true; +} \ No newline at end of file diff --git a/server/app.ts b/server/app.ts index 878a4ab..df82b1d 100644 --- a/server/app.ts +++ b/server/app.ts @@ -2,22 +2,33 @@ const bodyParser = require('body-parser'); import * as api from '../client/src/api'; import Knex from 'knex'; -import { CreateSongEndpointHandler } from './endpoints/CreateSong'; -import { CreateArtistEndpointHandler } from './endpoints/CreateArtist'; import { QueryEndpointHandler } from './endpoints/Query'; + +import { CreateArtistEndpointHandler } from './endpoints/CreateArtist'; import { ArtistDetailsEndpointHandler } from './endpoints/ArtistDetails' -import { SongDetailsEndpointHandler } from './endpoints/SongDetails'; import { ModifyArtistEndpointHandler } from './endpoints/ModifyArtist'; + import { ModifySongEndpointHandler } from './endpoints/ModifySong'; -import { CreateTagEndpointHandler } from './endpoints/CreateTag'; -import { ModifyTagEndpointHandler } from './endpoints/ModifyTag'; -import { TagDetailsEndpointHandler } from './endpoints/TagDetails'; +import { SongDetailsEndpointHandler } from './endpoints/SongDetails'; +import { CreateSongEndpointHandler } from './endpoints/CreateSong'; + import { CreateAlbumEndpointHandler } from './endpoints/CreateAlbum'; import { ModifyAlbumEndpointHandler } from './endpoints/ModifyAlbum'; import { AlbumDetailsEndpointHandler } from './endpoints/AlbumDetails'; + +import { CreateTagEndpointHandler } from './endpoints/CreateTag'; +import { ModifyTagEndpointHandler } from './endpoints/ModifyTag'; import { DeleteTagEndpointHandler } from './endpoints/DeleteTag'; import { MergeTagEndpointHandler } from './endpoints/MergeTag'; +import { TagDetailsEndpointHandler } from './endpoints/TagDetails'; + import { RegisterUserEndpointHandler } from './endpoints/RegisterUser'; + +import { CreateIntegrationEndpointHandler } from './endpoints/CreateIntegration'; +import { ModifyIntegrationEndpointHandler } from './endpoints/ModifyIntegration'; +import { DeleteIntegrationEndpointHandler } from './endpoints/DeleteIntegration'; +import { IntegrationDetailsEndpointHandler } from './endpoints/IntegrationDetails'; + import * as endpointTypes from './endpoints/types'; import { sha512 } from 'js-sha512'; @@ -108,26 +119,35 @@ const SetupApp = (app: any, knex: Knex, apiBaseUrl: string) => { // Set up REST API endpoints app.post(apiBaseUrl + api.CreateSongEndpoint, checkLogin(), _invoke(CreateSongEndpointHandler)); + app.put(apiBaseUrl + api.ModifySongEndpoint, checkLogin(), _invoke(ModifySongEndpointHandler)); + app.get(apiBaseUrl + api.SongDetailsEndpoint, checkLogin(), _invoke(SongDetailsEndpointHandler)); + app.post(apiBaseUrl + api.QueryEndpoint, checkLogin(), _invoke(QueryEndpointHandler)); + app.post(apiBaseUrl + api.CreateArtistEndpoint, checkLogin(), _invoke(CreateArtistEndpointHandler)); app.put(apiBaseUrl + api.ModifyArtistEndpoint, checkLogin(), _invoke(ModifyArtistEndpointHandler)); - app.put(apiBaseUrl + api.ModifySongEndpoint, checkLogin(), _invoke(ModifySongEndpointHandler)); - app.get(apiBaseUrl + api.SongDetailsEndpoint, checkLogin(), _invoke(SongDetailsEndpointHandler)); app.get(apiBaseUrl + api.ArtistDetailsEndpoint, checkLogin(), _invoke(ArtistDetailsEndpointHandler)); - app.post(apiBaseUrl + api.CreateTagEndpoint, checkLogin(), _invoke(CreateTagEndpointHandler)); - app.put(apiBaseUrl + api.ModifyTagEndpoint, checkLogin(), _invoke(ModifyTagEndpointHandler)); - app.get(apiBaseUrl + api.TagDetailsEndpoint, checkLogin(), _invoke(TagDetailsEndpointHandler)); + app.post(apiBaseUrl + api.CreateAlbumEndpoint, checkLogin(), _invoke(CreateAlbumEndpointHandler)); app.put(apiBaseUrl + api.ModifyAlbumEndpoint, checkLogin(), _invoke(ModifyAlbumEndpointHandler)); app.get(apiBaseUrl + api.AlbumDetailsEndpoint, checkLogin(), _invoke(AlbumDetailsEndpointHandler)); + + app.post(apiBaseUrl + api.CreateTagEndpoint, checkLogin(), _invoke(CreateTagEndpointHandler)); + app.put(apiBaseUrl + api.ModifyTagEndpoint, checkLogin(), _invoke(ModifyTagEndpointHandler)); + app.get(apiBaseUrl + api.TagDetailsEndpoint, checkLogin(), _invoke(TagDetailsEndpointHandler)); app.delete(apiBaseUrl + api.DeleteTagEndpoint, checkLogin(), _invoke(DeleteTagEndpointHandler)); app.post(apiBaseUrl + api.MergeTagEndpoint, checkLogin(), _invoke(MergeTagEndpointHandler)); - app.post(apiBaseUrl + api.RegisterUserEndpoint, _invoke(RegisterUserEndpointHandler)); - app.post(apiBaseUrl + '/login', passport.authenticate('local'), (req: any, res: any) => { + app.post(apiBaseUrl + api.CreateIntegrationEndpoint, checkLogin(), _invoke(CreateIntegrationEndpointHandler)); + app.put(apiBaseUrl + api.ModifyIntegrationEndpoint, checkLogin(), _invoke(ModifyIntegrationEndpointHandler)); + app.get(apiBaseUrl + api.IntegrationDetailsEndpoint, checkLogin(), _invoke(IntegrationDetailsEndpointHandler)); + app.delete(apiBaseUrl + api.DeleteIntegrationEndpoint, checkLogin(), _invoke(DeleteIntegrationEndpointHandler)); + + app.post(apiBaseUrl + api.RegisterUserEndpoint, _invoke(RegisterUserEndpointHandler)); + app.post(apiBaseUrl + api.LoginEndpoint, passport.authenticate('local'), (req: any, res: any) => { res.status(200).send({ userId: req.user.id }); }); - app.post(apiBaseUrl + '/logout', function (req: any, res: any) { + app.post(apiBaseUrl + api.LogoutEndpoint, function (req: any, res: any) { req.logout(); res.status(200).send(); }); diff --git a/server/endpoints/CreateIntegration.ts b/server/endpoints/CreateIntegration.ts new file mode 100644 index 0000000..6f6eef3 --- /dev/null +++ b/server/endpoints/CreateIntegration.ts @@ -0,0 +1,43 @@ +import * as api from '../../client/src/api'; +import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types'; +import Knex from 'knex'; + +export const CreateIntegrationEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => { + if (!api.checkCreateIntegrationRequest(req)) { + const e: EndpointError = { + internalMessage: 'Invalid CreateIntegration request: ' + JSON.stringify(req.body), + httpStatus: 400 + }; + throw e; + } + const reqObject: api.CreateIntegrationRequest = req.body; + const { id: userId } = req.user; + + console.log("User ", userId, ": Create Integration ", reqObject); + + await knex.transaction(async (trx) => { + try { + // Create the new integration. + var integration: any = { + name: reqObject.name, + user: userId, + type: reqObject.type, + details: JSON.stringify(reqObject.details), + } + const integrationId = (await trx('integrations') + .insert(integration) + .returning('id') // Needed for Postgres + )[0]; + + // Respond to the request. + const responseObject: api.CreateIntegrationResponse = { + id: integrationId + }; + res.status(200).send(responseObject); + + } catch (e) { + catchUnhandledErrors(e); + trx.rollback(); + } + }) +} \ No newline at end of file diff --git a/server/endpoints/DeleteIntegration.ts b/server/endpoints/DeleteIntegration.ts new file mode 100644 index 0000000..b0eff9b --- /dev/null +++ b/server/endpoints/DeleteIntegration.ts @@ -0,0 +1,49 @@ +import * as api from '../../client/src/api'; +import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types'; +import Knex from 'knex'; + +export const DeleteIntegrationEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => { + if (!api.checkDeleteIntegrationRequest(req)) { + const e: EndpointError = { + internalMessage: 'Invalid DeleteIntegration request: ' + JSON.stringify(req.body), + httpStatus: 400 + }; + throw e; + } + const reqObject: api.DeleteIntegrationRequest = req.body; + const { id: userId } = req.user; + + console.log("User ", userId, ": Delete Integration ", reqObject); + + await knex.transaction(async (trx) => { + try { + // Start retrieving the integration itself. + const integrationId = await trx.select('id') + .from('integrations') + .where({ 'user': userId }) + .where({ id: req.params.id }) + .then((r: any) => (r && r[0]) ? r[0]['id'] : undefined) + + // Check that we found all objects we need. + if (!integrationId) { + const e: EndpointError = { + internalMessage: 'Integration does not exist for DeleteIntegration request: ' + JSON.stringify(req.body), + httpStatus: 404 + }; + throw e; + } + + // Delete the integration. + await trx('integrations') + .where({ 'user': userId, 'id': integrationId }) + .del(); + + // Respond to the request. + res.status(200).send(); + + } catch (e) { + catchUnhandledErrors(e); + trx.rollback(); + } + }) +} \ No newline at end of file diff --git a/server/endpoints/IntegrationDetails.ts b/server/endpoints/IntegrationDetails.ts new file mode 100644 index 0000000..709ff25 --- /dev/null +++ b/server/endpoints/IntegrationDetails.ts @@ -0,0 +1,34 @@ +import * as api from '../../client/src/api'; +import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types'; +import Knex from 'knex'; + +export const IntegrationDetailsEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => { + if (!api.checkIntegrationDetailsRequest(req)) { + const e: EndpointError = { + internalMessage: 'Invalid IntegrationDetails request: ' + JSON.stringify(req.body), + httpStatus: 400 + }; + throw e; + } + + const { id: userId } = req.user; + + try { + const integration = (await knex.select(['id', 'name', 'type', 'details']) + .from('integrations') + .where({ 'user': userId, 'id': req.params.id }))[0]; + + if (integration) { + const response: api.IntegrationDetailsResponse = { + name: integration.name, + type: integration.type, + details: JSON.parse(integration.details), + } + await res.send(response); + } else { + await res.status(404).send({}); + } + } catch (e) { + catchUnhandledErrors(e) + } +} \ No newline at end of file diff --git a/server/endpoints/ModifyIntegration.ts b/server/endpoints/ModifyIntegration.ts new file mode 100644 index 0000000..0532ece --- /dev/null +++ b/server/endpoints/ModifyIntegration.ts @@ -0,0 +1,52 @@ +import * as api from '../../client/src/api'; +import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types'; +import Knex from 'knex'; + +export const ModifyIntegrationEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => { + if (!api.checkModifyIntegrationRequest(req)) { + const e: EndpointError = { + internalMessage: 'Invalid ModifyIntegration request: ' + JSON.stringify(req.body), + httpStatus: 400 + }; + throw e; + } + const reqObject: api.ModifyIntegrationRequest = req.body; + const { id: userId } = req.user; + + console.log("User ", userId, ": Modify Integration ", reqObject); + + await knex.transaction(async (trx) => { + try { + // Start retrieving the integration. + const integrationId = await trx.select('id') + .from('integrations') + .where({ 'user': userId }) + .then((r: any) => (r && r[0]) ? r[0]['id'] : undefined) + + // Check that we found all objects we need. + if (!integrationId) { + const e: EndpointError = { + internalMessage: 'Integration does not exist for ModifyIntegration request: ' + JSON.stringify(req.body), + httpStatus: 404 + }; + throw e; + } + + // Modify the integration. + var update: any = {}; + if ("name" in reqObject) { update["name"] = reqObject.name; } + if ("details" in reqObject) { update["details"] = JSON.stringify(reqObject.details); } + if ("type" in reqObject) { update["type"] = reqObject.type; } + await trx('integrations') + .where({ 'user': userId, 'id': req.params.id }) + .update(update) + + // Respond to the request. + res.status(200).send(); + + } catch (e) { + catchUnhandledErrors(e); + trx.rollback(); + } + }) +} \ No newline at end of file diff --git a/server/migrations/20201113155620_add_integrations.ts b/server/migrations/20201113155620_add_integrations.ts new file mode 100644 index 0000000..c560218 --- /dev/null +++ b/server/migrations/20201113155620_add_integrations.ts @@ -0,0 +1,22 @@ +import * as Knex from "knex"; + + +export async function up(knex: Knex): Promise { + // Integrations table. + await knex.schema.createTable( + 'integrations', + (table: any) => { + table.increments('id'); + table.integer('user').unsigned().notNullable().defaultTo(1); + table.string('name').notNullable(); // Uniquely identifies this integration configuration for the user. + table.string('type').notNullable(); // Enumerates different supported integration types (e.g. Spotify) + table.json('details'); // Stores anything that might be needed for the integration to work. + } + ) +} + + +export async function down(knex: Knex): Promise { + await knex.schema.dropTable('integrations'); +} + diff --git a/server/test/integration/flows/IntegrationFlow.js b/server/test/integration/flows/IntegrationFlow.js new file mode 100644 index 0000000..366ab32 --- /dev/null +++ b/server/test/integration/flows/IntegrationFlow.js @@ -0,0 +1,105 @@ +const chai = require('chai'); +const chaiHttp = require('chai-http'); +const express = require('express'); +import { SetupApp } from '../../../app'; +import * as helpers from './helpers'; +import { sha512 } from 'js-sha512'; +import { IntegrationType } from '../../../../client/src/api'; + +async function init() { + chai.use(chaiHttp); + const app = express(); + const knex = await helpers.initTestDB(); + + // Add test users. + await knex.insert({ email: "test1@test.com", passwordHash: sha512('pass1') }).into('users'); + await knex.insert({ email: "test2@test.com", passwordHash: sha512('pass2') }).into('users'); + + SetupApp(app, knex, ''); + + // Login as a test user. + var agent = chai.request.agent(app); + await agent + .post('/login?username=' + encodeURIComponent("test1@test.com") + '&password=' + encodeURIComponent('pass1')) + .send({}); + return agent; +} + +describe('POST /integration with missing or wrong data', () => { + it('should fail', async done => { + let agent = await init(); + let req = agent.keepOpen(); + try { + await helpers.createIntegration(req, { name: "A", type: IntegrationType.spotify }, 400); + await helpers.createIntegration(req, { details: {}, type: IntegrationType.spotify }, 400); + await helpers.createIntegration(req, { name: "A", details: {} }, 400); + await helpers.createIntegration(req, { name: "A", type: "NonexistentType", details: {} }, 400); + } finally { + req.close(); + agent.close(); + done(); + } + }); +}); + +describe('POST /integration with a correct request', () => { + it('should succeed', async done => { + let agent = await init(); + let req = agent.keepOpen(); + try { + await helpers.createIntegration(req, { name: "A", type: IntegrationType.spotify, details: {} }, 200, { id: 1 }); + } finally { + req.close(); + agent.close(); + done(); + } + }); +}); + +describe('PUT /integration with a correct request', () => { + it('should succeed', async done => { + let agent = await init(); + let req = agent.keepOpen(); + try { + await helpers.createIntegration(req, { name: "A", type: IntegrationType.spotify, details: {} }, 200, { id: 1 }); + await helpers.modifyIntegration(req, 1, { name: "B", type: IntegrationType.spotify, details: { secret: 'cat' } }, 200); + await helpers.checkIntegration(req, 1, 200, { name: "B", type: IntegrationType.spotify, details: { secret: 'cat' } }) + } finally { + req.close(); + agent.close(); + done(); + } + }); +}); + +describe('PUT /integration with wrong data', () => { + it('should fail', async done => { + let agent = await init(); + let req = agent.keepOpen(); + try { + await helpers.createIntegration(req, { name: "A", type: IntegrationType.spotify, details: {} }, 200, { id: 1 }); + await helpers.modifyIntegration(req, 1, { name: "B", type: "UnknownType", details: {} }, 400); + } finally { + req.close(); + agent.close(); + done(); + } + }); +}); + +describe('DELETE /integration with a correct request', () => { + it('should succeed', async done => { + let agent = await init(); + let req = agent.keepOpen(); + try { + await helpers.createIntegration(req, { name: "A", type: IntegrationType.spotify, details: {} }, 200, { id: 1 }); + await helpers.checkIntegration(req, 1, 200, { name: "A", type: IntegrationType.spotify, details: {} }) + await helpers.deleteIntegration(req, 1, 200); + await helpers.checkIntegration(req, 1, 404); + } finally { + req.close(); + agent.close(); + done(); + } + }); +}); \ No newline at end of file diff --git a/server/test/integration/flows/helpers.js b/server/test/integration/flows/helpers.js index bbf427e..13f6b45 100644 --- a/server/test/integration/flows/helpers.js +++ b/server/test/integration/flows/helpers.js @@ -1,5 +1,6 @@ import { expect } from "chai"; import { sha512 } from "js-sha512"; +import { IntegrationType } from "../../../../client/src/api"; export async function initTestDB() { // Allow different database configs - but fall back to SQLite in memory if necessary. @@ -28,6 +29,7 @@ export async function createSong( .then((res) => { expectStatus && expect(res).to.have.status(expectStatus); expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; }); } @@ -42,6 +44,7 @@ export async function modifySong( .send(props) .then((res) => { expectStatus && expect(res).to.have.status(expectStatus); + return res; }); } @@ -56,6 +59,7 @@ export async function checkSong( .then((res) => { expectStatus && expect(res).to.have.status(expectStatus); expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; }) } @@ -71,6 +75,7 @@ export async function createArtist( .then((res) => { expectStatus && expect(res).to.have.status(expectStatus); expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; }); } @@ -85,6 +90,7 @@ export async function modifyArtist( .send(props) .then((res) => { expectStatus && expect(res).to.have.status(expectStatus); + return res; }); } @@ -99,6 +105,7 @@ export async function checkArtist( .then((res) => { expectStatus && expect(res).to.have.status(expectStatus); expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; }) } @@ -114,6 +121,7 @@ export async function createTag( .then((res) => { expectStatus && expect(res).to.have.status(expectStatus); expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; }); } @@ -128,6 +136,7 @@ export async function modifyTag( .send(props) .then((res) => { expectStatus && expect(res).to.have.status(expectStatus); + return res; }); } @@ -142,6 +151,7 @@ export async function checkTag( .then((res) => { expectStatus && expect(res).to.have.status(expectStatus); expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; }) } @@ -157,6 +167,7 @@ export async function createAlbum( .then((res) => { expectStatus && expect(res).to.have.status(expectStatus); expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; }); } @@ -171,6 +182,7 @@ export async function modifyAlbum( .send(props) .then((res) => { expectStatus && expect(res).to.have.status(expectStatus); + return res; }); } @@ -185,6 +197,7 @@ export async function checkAlbum( .then((res) => { expectStatus && expect(res).to.have.status(expectStatus); expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; }) } @@ -203,6 +216,7 @@ export async function createUser( }); expectStatus && expect(res).to.have.status(expectStatus); expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; } export async function login( @@ -217,6 +231,7 @@ export async function login( .send({}); expectStatus && expect(res).to.have.status(expectStatus); expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; } export async function logout( @@ -229,4 +244,64 @@ export async function logout( .send({}); expectStatus && expect(res).to.have.status(expectStatus); expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; +} + +export async function createIntegration( + req, + props = { name: "Integration", type: IntegrationType.Spotify, details: {} }, + expectStatus = undefined, + expectResponse = undefined +) { + await req + .post('/integration') + .send(props) + .then((res) => { + expectStatus && expect(res).to.have.status(expectStatus); + expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; + }); +} + +export async function modifyIntegration( + req, + id = 1, + props = { name: "NewIntegration", type: IntegrationType.Spotify, details: {} }, + expectStatus = undefined, +) { + await req + .put('/integration/' + id) + .send(props) + .then((res) => { + expectStatus && expect(res).to.have.status(expectStatus); + return res; + }); +} + +export async function checkIntegration( + req, + id, + expectStatus = undefined, + expectResponse = undefined, +) { + await req + .get('/integration/' + id) + .then((res) => { + expectStatus && expect(res).to.have.status(expectStatus); + expectResponse && expect(res.body).to.deep.equal(expectResponse); + return res; + }) +} + +export async function deleteIntegration( + req, + id, + expectStatus = undefined, +) { + await req + .delete('/integration/' + id) + .then((res) => { + expectStatus && expect(res).to.have.status(expectStatus); + return res; + }) } \ No newline at end of file