From 751a232eb6ddd402e5d77bac9ba88832db1d5d3a Mon Sep 17 00:00:00 2001 From: bradsawadye Date: Mon, 21 Aug 2023 09:33:34 +0200 Subject: [PATCH 01/12] Create app mongoose model --- src/model/apps.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/model/apps.js diff --git a/src/model/apps.js b/src/model/apps.js new file mode 100644 index 00000000..961fa43e --- /dev/null +++ b/src/model/apps.js @@ -0,0 +1,31 @@ +'use strict' + +import {Schema} from 'mongoose' + +import {connectionAPI, connectionDefault} from '../config' + +const AppSchema = new Schema({ + name: { + type: String, + unique: true + }, + description: String, + icon: { + data: Buffer, + contentType: String + }, + category: String, + access_roles: [String], + url: { + type: String, + unique: true + }, + showInPortal: { + type: Boolean, + default: true + }, + showInSideBar: Boolean +}) + +export const AppModelAPI = connectionAPI.model('App', AppSchema) +export const AppModel = connectionDefault.model('App', AppSchema) From 564d014bfccaefef3a22f9dfda5dc14555bef414 Mon Sep 17 00:00:00 2001 From: bradsawadye Date: Mon, 21 Aug 2023 09:36:37 +0200 Subject: [PATCH 02/12] Add api methods for creating and updating an app --- src/api/apps.js | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/api/apps.js diff --git a/src/api/apps.js b/src/api/apps.js new file mode 100644 index 00000000..19eaccbf --- /dev/null +++ b/src/api/apps.js @@ -0,0 +1,65 @@ +'use strict' + +import logger from 'winston' + +import * as authorisation from './authorisation' +import {AppModelAPI} from '../model/apps' + +export async function addApp(ctx) { + try { + if (!authorisation.inGroup('admin', ctx.authenticated)) { + ctx.statusCode = 403 + throw Error(`User ${ctx.authenticated.email} is not an admin, API access to addClient denied.`) + } + + const app = new AppModelAPI(ctx.request.body) + + await app + .save() + .then(app => { + logger.info(`User ${ctx.request.email} created app ${app.name}`) + ctx.status = 201 + ctx.body = app + }) + .catch(e => { + ctx.statusCode = 400 + throw e + }) + } catch (e) { + logger.error(`Could not add an app via the API: ${e.message}`) + ctx.body = e.message + ctx.status = ctx.statusCode ? ctx.statusCode : 500 + } +} + +export async function updateApp(ctx) { + try { + if (!authorisation.inGroup('admin', ctx.authenticated)) { + ctx.statusCode = 403 + throw Error(`User ${ctx.authenticated.email} is not an admin, API access to addClient denied.`) + } + + const id = ctx.params.id + const update = ctx.request.body + + const app = AppModelAPI.findById(id) + + if (!app) { + ctx.statusCode = 404 + throw Error(`App with ${id} does not exist`) + } + + await AppModelAPI.findOneAndUpdate({_id}, update).then(app => { + logger.info(`User ${ctx.authenticated.email} updated app ${app.name}`) + ctx.body = app + ctx.status = 200 + }).catch(e => { + ctx.statusCode = 400 + throw e + }) + } catch (e) { + logger.error(`Could not update app via the API: ${e.message}`) + ctx.body = e.message + ctx.status = ctx.statusCode ? ctx.statusCode : 500 + } +} From ee3f22710705baf53ab2f286008b2ad1aced14ef Mon Sep 17 00:00:00 2001 From: bradsawadye Date: Mon, 21 Aug 2023 11:31:13 +0200 Subject: [PATCH 03/12] Fix lint issues --- src/api/apps.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/api/apps.js b/src/api/apps.js index 19eaccbf..74dbfc28 100644 --- a/src/api/apps.js +++ b/src/api/apps.js @@ -9,7 +9,9 @@ export async function addApp(ctx) { try { if (!authorisation.inGroup('admin', ctx.authenticated)) { ctx.statusCode = 403 - throw Error(`User ${ctx.authenticated.email} is not an admin, API access to addClient denied.`) + throw Error( + `User ${ctx.authenticated.email} is not an admin, API access to add an app denied.` + ) } const app = new AppModelAPI(ctx.request.body) @@ -18,6 +20,7 @@ export async function addApp(ctx) { .save() .then(app => { logger.info(`User ${ctx.request.email} created app ${app.name}`) + ctx.status = 201 ctx.body = app }) @@ -27,6 +30,7 @@ export async function addApp(ctx) { }) } catch (e) { logger.error(`Could not add an app via the API: ${e.message}`) + ctx.body = e.message ctx.status = ctx.statusCode ? ctx.statusCode : 500 } @@ -36,7 +40,9 @@ export async function updateApp(ctx) { try { if (!authorisation.inGroup('admin', ctx.authenticated)) { ctx.statusCode = 403 - throw Error(`User ${ctx.authenticated.email} is not an admin, API access to addClient denied.`) + throw Error( + `User ${ctx.authenticated.email} is not an admin, API access to update an app denied.` + ) } const id = ctx.params.id From c07b7ea87537ff65728e910fd8bc22315af09402 Mon Sep 17 00:00:00 2001 From: bradsawadye Date: Mon, 21 Aug 2023 11:52:47 +0200 Subject: [PATCH 04/12] Add api methods for retrieving apps --- src/api/apps.js | 59 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/api/apps.js b/src/api/apps.js index 74dbfc28..9c062325 100644 --- a/src/api/apps.js +++ b/src/api/apps.js @@ -55,16 +55,59 @@ export async function updateApp(ctx) { throw Error(`App with ${id} does not exist`) } - await AppModelAPI.findOneAndUpdate({_id}, update).then(app => { - logger.info(`User ${ctx.authenticated.email} updated app ${app.name}`) - ctx.body = app - ctx.status = 200 - }).catch(e => { - ctx.statusCode = 400 - throw e - }) + await AppModelAPI.findOneAndUpdate({_id}, update) + .then(app => { + logger.info(`User ${ctx.authenticated.email} updated app ${app.name}`) + + ctx.body = app + ctx.status = 200 + }) + .catch(e => { + ctx.statusCode = 400 + throw e + }) } catch (e) { logger.error(`Could not update app via the API: ${e.message}`) + + ctx.body = e.message + ctx.status = ctx.statusCode ? ctx.statusCode : 500 + } +} + +export async function getApps(ctx) { + try { + const apps = await AppModelAPI.find(ctx.request.query) + + logger.info(`User ${ctx.authenticated.email} fetched ${apps.length} apps`) + + ctx.body = apps + ctx.status = 200 + } catch (e) { + logger.error(`Could not retrieve apps via the API: ${e.message}`) + + ctx.body = e.message + ctx.status = 500 + } +} + +export async function getApp(ctx) { + try { + const id = ctx.params.id + + const app = AppModelAPI.findById(id) + + if (!app) { + ctx.statusCode = 404 + throw Error(`App with ${id} does not exist`) + } + + logger.info(`User ${ctx.authenticated.email} app fetched ${id}`) + + ctx.body = app + ctx.status = 200 + } catch (e) { + logger.error(`Could not retrieve an app via the API: ${e.message}`) + ctx.body = e.message ctx.status = ctx.statusCode ? ctx.statusCode : 500 } From ce1369e94c43b28890ab28be701f696070081413 Mon Sep 17 00:00:00 2001 From: bradsawadye Date: Mon, 21 Aug 2023 12:22:21 +0200 Subject: [PATCH 05/12] Add api method for deleting app --- src/api/apps.js | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/api/apps.js b/src/api/apps.js index 9c062325..65031d7d 100644 --- a/src/api/apps.js +++ b/src/api/apps.js @@ -48,7 +48,7 @@ export async function updateApp(ctx) { const id = ctx.params.id const update = ctx.request.body - const app = AppModelAPI.findById(id) + const app = await AppModelAPI.findById(id) if (!app) { ctx.statusCode = 404 @@ -94,7 +94,7 @@ export async function getApp(ctx) { try { const id = ctx.params.id - const app = AppModelAPI.findById(id) + const app = await AppModelAPI.findById(id) if (!app) { ctx.statusCode = 404 @@ -112,3 +112,28 @@ export async function getApp(ctx) { ctx.status = ctx.statusCode ? ctx.statusCode : 500 } } + +export async function deleteApp(ctx) { + try { + const _id = ctx.params.id + + const app = await AppModelAPI.findById(_id) + + if (!app) { + ctx.statusCode = 404 + throw Error(`App with ${id} does not exist`) + } + + await AppModelAPI.deleteOne({_id}).then(() => { + logger.info(`User ${ctx.authenticated.email} deleted app ${id}`) + + ctx.status = 200 + ctx.body = 'Successful' + }) + } catch (e) { + logger.error(`Could not delete an app via the API: ${e.message}`) + + ctx.body = e.message + ctx.status = ctx.statusCode ? ctx.statusCode : 500 + } +} From 1b307c7d606ec99ec8ea88e4f5c5457400645737 Mon Sep 17 00:00:00 2001 From: bradsawadye Date: Mon, 21 Aug 2023 12:54:41 +0200 Subject: [PATCH 06/12] Add routes for creating, reading, updating and deleting apps --- src/api/apps.js | 6 +++--- src/koaApi.js | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/api/apps.js b/src/api/apps.js index 65031d7d..a0dc348a 100644 --- a/src/api/apps.js +++ b/src/api/apps.js @@ -45,7 +45,7 @@ export async function updateApp(ctx) { ) } - const id = ctx.params.id + const id = ctx.params.appId const update = ctx.request.body const app = await AppModelAPI.findById(id) @@ -92,7 +92,7 @@ export async function getApps(ctx) { export async function getApp(ctx) { try { - const id = ctx.params.id + const id = ctx.params.appId const app = await AppModelAPI.findById(id) @@ -115,7 +115,7 @@ export async function getApp(ctx) { export async function deleteApp(ctx) { try { - const _id = ctx.params.id + const _id = ctx.params.appId const app = await AppModelAPI.findById(_id) diff --git a/src/koaApi.js b/src/koaApi.js index b7ae8736..e369da68 100644 --- a/src/koaApi.js +++ b/src/koaApi.js @@ -8,6 +8,7 @@ import session from 'koa-session' import compose from 'koa-compose' import * as about from './api/about' +import * as apps from './api/apps' import * as audits from './api/audits' import * as authentication from './api/authentication' import * as certificateAuthority from './api/certificateAuthority' @@ -136,6 +137,12 @@ export function setupApp(done) { app.use(route.get('/logout', users.logout)) // Define the api routes + app.use(route.get('/apps', apps.getApps)) + app.use(route.get('/apps/:appId', apps.getApp)) + app.use(route.put('/apps/:appId', apps.updateApp)) + app.use(route.post('/apps', apps.addApp)) + app.use(route.delete('/apps/:appId', apps.deleteApp)) + app.use(route.get('/users', users.getUsers)) app.use(route.get('/users/:email', users.getUser)) app.use(route.post('/users', users.addUser)) From 54efc45898df352bf5d152ff1fb2eddc36d684b9 Mon Sep 17 00:00:00 2001 From: bradsawadye Date: Wed, 23 Aug 2023 12:59:33 +0200 Subject: [PATCH 07/12] Refactor and clean up --- src/api/apps.js | 90 ++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/src/api/apps.js b/src/api/apps.js index a0dc348a..edf49d08 100644 --- a/src/api/apps.js +++ b/src/api/apps.js @@ -5,14 +5,36 @@ import logger from 'winston' import * as authorisation from './authorisation' import {AppModelAPI} from '../model/apps' +/* + Checks admin permission for create, update and delete operations. + Throws error if user does not have admin access +*/ +const checkUserPermission = (ctx, operation) => { + if (!authorisation.inGroup('admin', ctx.authenticated)) { + ctx.statusCode = 403 + throw Error( + `User ${ctx.authenticated.email} is not an admin, API access to ${operation} an app denied.` + ) + } +} + +/* + Returns app if it exists, if not it throws an error +*/ +const checkAppExists = async (ctx, appId) => { + const app = await AppModelAPI.findById(appId) + + if (!app) { + ctx.statusCode = 404 + throw Error(`App with ${appId} does not exist`) + } + + return app +} + export async function addApp(ctx) { try { - if (!authorisation.inGroup('admin', ctx.authenticated)) { - ctx.statusCode = 403 - throw Error( - `User ${ctx.authenticated.email} is not an admin, API access to add an app denied.` - ) - } + checkUserPermission(ctx, 'add') const app = new AppModelAPI(ctx.request.body) @@ -36,26 +58,18 @@ export async function addApp(ctx) { } } -export async function updateApp(ctx) { +export async function updateApp(ctx, appId) { try { - if (!authorisation.inGroup('admin', ctx.authenticated)) { - ctx.statusCode = 403 - throw Error( - `User ${ctx.authenticated.email} is not an admin, API access to update an app denied.` - ) - } - - const id = ctx.params.appId - const update = ctx.request.body + checkUserPermission(ctx, 'update') - const app = await AppModelAPI.findById(id) + const update = ctx.request.body - if (!app) { - ctx.statusCode = 404 - throw Error(`App with ${id} does not exist`) - } + await checkAppExists(ctx, appId) - await AppModelAPI.findOneAndUpdate({_id}, update) + await AppModelAPI.findOneAndUpdate({_id: appId}, update, { + new: true, + runValidators: true + }) .then(app => { logger.info(`User ${ctx.authenticated.email} updated app ${app.name}`) @@ -90,18 +104,11 @@ export async function getApps(ctx) { } } -export async function getApp(ctx) { +export async function getApp(ctx, appId) { try { - const id = ctx.params.appId + const app = await checkAppExists(ctx, appId) - const app = await AppModelAPI.findById(id) - - if (!app) { - ctx.statusCode = 404 - throw Error(`App with ${id} does not exist`) - } - - logger.info(`User ${ctx.authenticated.email} app fetched ${id}`) + logger.info(`User ${ctx.authenticated.email} app fetched ${appId}`) ctx.body = app ctx.status = 200 @@ -113,22 +120,19 @@ export async function getApp(ctx) { } } -export async function deleteApp(ctx) { +export async function deleteApp(ctx, appId) { try { - const _id = ctx.params.appId - - const app = await AppModelAPI.findById(_id) + checkUserPermission(ctx, 'delete') - if (!app) { - ctx.statusCode = 404 - throw Error(`App with ${id} does not exist`) - } + await checkAppExists(ctx, appId) - await AppModelAPI.deleteOne({_id}).then(() => { - logger.info(`User ${ctx.authenticated.email} deleted app ${id}`) + await AppModelAPI.deleteOne({_id: appId}).then(() => { + logger.info(`User ${ctx.authenticated.email} deleted app ${appId}`) ctx.status = 200 - ctx.body = 'Successful' + ctx.body = { + success: true + } }) } catch (e) { logger.error(`Could not delete an app via the API: ${e.message}`) From f725e02dedc3c7332f0afe1ec93f9342382ca219 Mon Sep 17 00:00:00 2001 From: bradsawadye Date: Fri, 25 Aug 2023 08:39:06 +0200 Subject: [PATCH 08/12] Make the name and the url of the app required properties Every app that is registered should have a unique name and url --- src/model/apps.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/model/apps.js b/src/model/apps.js index 961fa43e..bd1104b9 100644 --- a/src/model/apps.js +++ b/src/model/apps.js @@ -7,7 +7,8 @@ import {connectionAPI, connectionDefault} from '../config' const AppSchema = new Schema({ name: { type: String, - unique: true + unique: true, + required: true }, description: String, icon: { @@ -18,7 +19,8 @@ const AppSchema = new Schema({ access_roles: [String], url: { type: String, - unique: true + unique: true, + required: true }, showInPortal: { type: Boolean, From 5364b7e14406335e9e995c4c90f441a6a4d282e7 Mon Sep 17 00:00:00 2001 From: bradsawadye Date: Fri, 25 Aug 2023 08:41:55 +0200 Subject: [PATCH 09/12] Refactor and clean up This also adds logic to check the validity of the app id, and responds with an error more descriptive of the issue TB-151 --- src/api/apps.js | 52 ++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/api/apps.js b/src/api/apps.js index edf49d08..4c42018f 100644 --- a/src/api/apps.js +++ b/src/api/apps.js @@ -26,12 +26,29 @@ const checkAppExists = async (ctx, appId) => { if (!app) { ctx.statusCode = 404 - throw Error(`App with ${appId} does not exist`) + throw Error(`App with id ${appId} does not exist`) } return app } +// Creates error response operations create, read, update and delete +const createErrorResponse = (ctx, operation, error) => { + logger.error(`Could not ${operation} an app via the API: ${error.message}`) + + ctx.body = { + error: error.message + } + ctx.status = ctx.statusCode ? ctx.statusCode : 500 +} + +const validateId = (ctx, id) => { + if (!id.match(/^[0-9a-fA-F]{24}$/)) { + ctx.statusCode = 400 + throw Error(`App id "${id}" is invalid. ObjectId should contain 24 characters`) + } +} + export async function addApp(ctx) { try { checkUserPermission(ctx, 'add') @@ -51,10 +68,7 @@ export async function addApp(ctx) { throw e }) } catch (e) { - logger.error(`Could not add an app via the API: ${e.message}`) - - ctx.body = e.message - ctx.status = ctx.statusCode ? ctx.statusCode : 500 + createErrorResponse(ctx, 'add', e) } } @@ -62,10 +76,12 @@ export async function updateApp(ctx, appId) { try { checkUserPermission(ctx, 'update') - const update = ctx.request.body + validateId(ctx, appId) await checkAppExists(ctx, appId) + const update = ctx.request.body + await AppModelAPI.findOneAndUpdate({_id: appId}, update, { new: true, runValidators: true @@ -81,10 +97,7 @@ export async function updateApp(ctx, appId) { throw e }) } catch (e) { - logger.error(`Could not update app via the API: ${e.message}`) - - ctx.body = e.message - ctx.status = ctx.statusCode ? ctx.statusCode : 500 + createErrorResponse(ctx, 'update', e) } } @@ -97,15 +110,14 @@ export async function getApps(ctx) { ctx.body = apps ctx.status = 200 } catch (e) { - logger.error(`Could not retrieve apps via the API: ${e.message}`) - - ctx.body = e.message - ctx.status = 500 + createErrorResponse(ctx, 'retrieve', e) } } export async function getApp(ctx, appId) { try { + validateId(ctx, appId) + const app = await checkAppExists(ctx, appId) logger.info(`User ${ctx.authenticated.email} app fetched ${appId}`) @@ -113,10 +125,7 @@ export async function getApp(ctx, appId) { ctx.body = app ctx.status = 200 } catch (e) { - logger.error(`Could not retrieve an app via the API: ${e.message}`) - - ctx.body = e.message - ctx.status = ctx.statusCode ? ctx.statusCode : 500 + createErrorResponse(ctx, 'retrieve', e) } } @@ -124,6 +133,8 @@ export async function deleteApp(ctx, appId) { try { checkUserPermission(ctx, 'delete') + validateId(ctx, appId) + await checkAppExists(ctx, appId) await AppModelAPI.deleteOne({_id: appId}).then(() => { @@ -135,9 +146,6 @@ export async function deleteApp(ctx, appId) { } }) } catch (e) { - logger.error(`Could not delete an app via the API: ${e.message}`) - - ctx.body = e.message - ctx.status = ctx.statusCode ? ctx.statusCode : 500 + createErrorResponse(ctx, 'delete', e) } } From 73d0c3e509a53beb3d83ce2a58dcd928f613f690 Mon Sep 17 00:00:00 2001 From: bradsawadye Date: Fri, 25 Aug 2023 08:45:18 +0200 Subject: [PATCH 10/12] Test the apps endpoints --- test/integration/appsAPITests.js | 265 +++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 test/integration/appsAPITests.js diff --git a/test/integration/appsAPITests.js b/test/integration/appsAPITests.js new file mode 100644 index 00000000..7f448cb7 --- /dev/null +++ b/test/integration/appsAPITests.js @@ -0,0 +1,265 @@ +'use strict' + +/* eslint-env mocha */ +/* eslint no-unused-expressions:0 */ + +import request from 'supertest' +import should from 'should' +import {promisify} from 'util' + +import * as constants from '../constants' +import * as server from '../../src/server' +import * as testUtils from '../utils' +import {AppModelAPI} from '../../src/model/apps' + +const {SERVER_PORTS, BASE_URL} = constants + +describe('API Integration Tests', () => { + describe('Apps REST Api Testing', () => { + const testAppDoc = { + name: 'Test app', + description: 'An app for testing the app framework', + icon: 'data:image/png;base64, ', + type: 'link|embedded', + category: 'Operations', + access_roles: ['test-app-user'], + url: 'http://test-app.org/app', + showInPortal: true, + showInSideBar: true + } + let rootCookie = '', + nonRootCookie = '' + + before(async () => { + await promisify(server.start)({apiPort: SERVER_PORTS.apiPort}) + await testUtils.setupTestUsers() + }) + + after(async () => { + await testUtils.cleanupTestUsers() + await promisify(server.stop)() + }) + + beforeEach(async () => { + rootCookie = await testUtils.authenticate( + request, + BASE_URL, + testUtils.rootUser + ) + nonRootCookie = await testUtils.authenticate( + request, + BASE_URL, + testUtils.nonRootUser + ) + }) + + afterEach(async () => { + await AppModelAPI.deleteMany({}) + }) + + describe('*addApp', () => { + it('should only allow an admin user to add an app', async () => { + const res = await request(BASE_URL) + .post('/apps') + .set('Cookie', nonRootCookie) + .send(testAppDoc) + .expect(403) + + res.body.error.should.equal( + 'User nonroot@jembi.org is not an admin, API access to add an app denied.' + ) + }) + + it('should fail when app is invalid', async () => { + await request(BASE_URL) + .post('/apps') + .set('Cookie', rootCookie) + .send({}) + .expect(400) + }) + + it('should create an app', async () => { + const res = await request(BASE_URL) + .post('/apps') + .set('Cookie', rootCookie) + .send(testAppDoc) + .expect(201) + + res.body.name.should.equal(testAppDoc.name) + }) + }) + + describe('*getApps', () => { + let appId + + beforeEach(async () => { + const res = await request(BASE_URL) + .post('/apps') + .set('Cookie', rootCookie) + .send(testAppDoc) + .expect(201) + + appId = res.body._id + }) + + it('should get apps', async () => { + const res = await request(BASE_URL) + .get('/apps') + .set('Cookie', rootCookie) + .expect(200) + + res.body[0].name.should.equal(testAppDoc.name) + }) + + it('should get app', async () => { + const res = await request(BASE_URL) + .get(`/apps/${appId}`) + .set('Cookie', rootCookie) + .expect(200) + + res.body.name.should.equal(testAppDoc.name) + }) + + it('should fail when app id is invalid', async () => { + const res = await request(BASE_URL) + .put(`/apps/testapp`) + .set('Cookie', rootCookie) + .expect(400) + + res.body.error.should.equal( + 'App id "testapp" is invalid. ObjectId should contain 24 characters' + ) + }) + + it('should fail when app does not exist', async () => { + const res = await request(BASE_URL) + .get('/apps/507f1f77bcf86cd799439011') + .set('Cookie', nonRootCookie) + .expect(404) + + res.body.error.should.equal( + 'App with id 507f1f77bcf86cd799439011 does not exist' + ) + }) + }) + + describe('*updateApp', () => { + const update = { + description: 'Test app' + } + + let appId + + beforeEach(async () => { + const res = await request(BASE_URL) + .post('/apps') + .set('Cookie', rootCookie) + .send(testAppDoc) + .expect(201) + + appId = res.body._id + }) + + it('should only allow an admin user to update an app', async () => { + const res = await request(BASE_URL) + .put('/apps/507f1f77bcf86cd799439011') + .set('Cookie', nonRootCookie) + .send(update) + .expect(403) + + res.body.error.should.equal( + 'User nonroot@jembi.org is not an admin, API access to update an app denied.' + ) + }) + + it('should fail to update when app id is invalid', async () => { + const res = await request(BASE_URL) + .put(`/apps/testapp`) + .set('Cookie', rootCookie) + .expect(400) + + res.body.error.should.equal( + 'App id "testapp" is invalid. ObjectId should contain 24 characters' + ) + }) + + it('should fail to update when app does not exist', async () => { + const res = await request(BASE_URL) + .put(`/apps/507f1f77bcf86cd799439011`) + .set('Cookie', rootCookie) + .send(update) + .expect(404) + + res.body.error.should.equal( + 'App with id 507f1f77bcf86cd799439011 does not exist' + ) + }) + + it('should update app', async () => { + const res = await request(BASE_URL) + .put(`/apps/${appId}`) + .set('Cookie', rootCookie) + .send(update) + .expect(200) + + res.body.description.should.equal(update.description) + }) + }) + + describe('*deleteApp', () => { + let appId + + beforeEach(async () => { + const res = await request(BASE_URL) + .post('/apps') + .set('Cookie', rootCookie) + .send(testAppDoc) + .expect(201) + + appId = res.body._id + }) + + it('should only allow an admin user to delete an app', async () => { + const res = await request(BASE_URL) + .delete('/apps/507f1f77bcf86cd799439011') + .set('Cookie', nonRootCookie) + .expect(403) + + res.body.error.should.equal( + 'User nonroot@jembi.org is not an admin, API access to delete an app denied.' + ) + }) + + it('should fail to delete when app id is invalid', async () => { + const res = await request(BASE_URL) + .delete(`/apps/testapp`) + .set('Cookie', rootCookie) + .expect(400) + + res.body.error.should.equal( + 'App id "testapp" is invalid. ObjectId should contain 24 characters' + ) + }) + + it('should fail to delete when app does not exist', async () => { + const res = await request(BASE_URL) + .delete('/apps/507f1f77bcf86cd799439011') + .set('Cookie', rootCookie) + .expect(404) + + res.body.error.should.equal( + 'App with id 507f1f77bcf86cd799439011 does not exist' + ) + }) + + it('should delete app', async () => { + const res = await request(BASE_URL) + .delete(`/apps/${appId}`) + .set('Cookie', rootCookie) + .expect(200) + + res.body.success.should.equal(true) + }) + }) + }) +}) From de84e0f173359743860b2c34d1c02614ea3150fe Mon Sep 17 00:00:00 2001 From: bradsawadye Date: Fri, 25 Aug 2023 08:45:57 +0200 Subject: [PATCH 11/12] Bump the version New feature has been added --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2132cf41..ea9aacc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "openhim-core", - "version": "8.1.1", + "version": "8.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1cf9bd5f..60178dbc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "openhim-core", "description": "The OpenHIM core application that provides logging and routing of http requests", - "version": "8.1.1", + "version": "8.2.0", "main": "./lib/server.js", "bin": { "openhim-core": "./bin/openhim-core.js" From 8f751a8cd67b30bb7ad71649f9ecd9592e56a9bc Mon Sep 17 00:00:00 2001 From: bradsawadye Date: Fri, 25 Aug 2023 08:48:35 +0200 Subject: [PATCH 12/12] Fix typo --- src/api/apps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/apps.js b/src/api/apps.js index 4c42018f..1e5b308f 100644 --- a/src/api/apps.js +++ b/src/api/apps.js @@ -32,7 +32,7 @@ const checkAppExists = async (ctx, appId) => { return app } -// Creates error response operations create, read, update and delete +// Creates error response for operations create, read, update and delete const createErrorResponse = (ctx, operation, error) => { logger.error(`Could not ${operation} an app via the API: ${error.message}`)