From 1b6590cf6bde20bdb92b2b2741c5e7704b21842f Mon Sep 17 00:00:00 2001 From: tomcorey26 Date: Sat, 17 Oct 2020 19:21:03 -0400 Subject: [PATCH 1/3] feat(add-context-api): add contexts api --- src/app.js | 2 + .../controllers/contextsController.js | 45 +++++++++++++ src/contexts/models/contextManager.js | 50 +++++++++++++++ .../database/sequelize/sequelizeConnector.js | 64 +++++++++++++++++++ src/contexts/routes/contextsRoute.js | 12 ++++ src/database/sequlize-handler/sequlize.js | 2 + 6 files changed, 175 insertions(+) create mode 100644 src/contexts/controllers/contextsController.js create mode 100644 src/contexts/models/contextManager.js create mode 100644 src/contexts/models/database/sequelize/sequelizeConnector.js create mode 100644 src/contexts/routes/contextsRoute.js diff --git a/src/app.js b/src/app.js index 1bf1ddfac..3c3fe8e64 100644 --- a/src/app.js +++ b/src/app.js @@ -11,6 +11,7 @@ const testsRouter = require('./tests/routes/testsRoute.js'); const processorsRouter = require('./processors/routes/processorsRoute.js'); const filesRouter = require('./files/routes/filesRoute.js'); const webhooksRouter = require('./webhooks/routes/webhooksRouter'); +const contextsRouter = require('./contexts/routes/contextsRoute.js'); const swaggerValidator = require('express-ajv-swagger-validation'); const audit = require('express-requests-logger'); @@ -68,6 +69,7 @@ module.exports = async () => { app.use('/v1/processors', processorsRouter); app.use('/v1/files', filesRouter); app.use('/v1/webhooks', webhooksRouter); + app.use('/v1/contexts', contextsRouter); app.use('/', function (req, res, next) { res.redirect('/ui'); diff --git a/src/contexts/controllers/contextsController.js b/src/contexts/controllers/contextsController.js new file mode 100644 index 000000000..63229d12d --- /dev/null +++ b/src/contexts/controllers/contextsController.js @@ -0,0 +1,45 @@ +'use strict'; +const contextManager = require('../models/contextManager'); + +module.exports = { + createContext, + getContexts, + deleteContext +}; + +async function getContexts(_, res, next) { + let contextData; + try { + contextData = await contextManager.getContexts(); + } catch (err) { + return next(err); + } + + return res.json(contextData); +} + +async function createContext(req, res, next) { + let contextId; + try { + contextId = await contextManager.saveContext(req.body.name); + } catch (err) { + return next(err); + } + + return res.status(201).json({ id: contextId, name: req.body.name }); +} + +async function deleteContext(req, res, next) { + let deleteResult; + try { + deleteResult = await contextManager.deleteContext(req.body.id); + } catch (err) { + return next(err); + } + + if (!deleteResult) { + res.status(204).json({ message: 'Context not found' }); + } + + return res.status(202).json({ message: 'Context successfully deleted' }); +} diff --git a/src/contexts/models/contextManager.js b/src/contexts/models/contextManager.js new file mode 100644 index 000000000..3fb51cf8f --- /dev/null +++ b/src/contexts/models/contextManager.js @@ -0,0 +1,50 @@ +'use strict'; +const uuid = require('uuid'); + +const database = require('./database/sequelize/sequelizeConnector'), + { ERROR_MESSAGES } = require('../../common/consts'); + +module.exports = { + getContexts, + saveContext, + deleteContext +}; + +async function getContexts() { + const contexts = await database.getContexts(); + if (contexts) { + return contexts.map(({ dataValues }) => ({ + id: dataValues.id, + name: dataValues.name + })); + } else { + const error = new Error(ERROR_MESSAGES.NOT_FOUND); + error.statusCode = 404; + throw error; + } +} + +async function saveContext(contextName) { + const id = uuid(); + try { + await database.saveContext(id, contextName); + } catch (err) { + const error = new Error(err.message); + error.statusCode = 400; + throw error; + } + return id; +} + +async function deleteContext(contextId) { + let deleteResult; + try { + deleteResult = await database.deleteContext(contextId); + } catch (err) { + const error = new Error(err.message); + error.statusCode = 400; + throw error; + } + + return deleteResult; +} diff --git a/src/contexts/models/database/sequelize/sequelizeConnector.js b/src/contexts/models/database/sequelize/sequelizeConnector.js new file mode 100644 index 000000000..732d1800e --- /dev/null +++ b/src/contexts/models/database/sequelize/sequelizeConnector.js @@ -0,0 +1,64 @@ + +const Sequelize = require('sequelize'); +module.exports = { + init, + saveContext, + getContexts, + deleteContext +}; + +let client; + +async function init(sequlizeClient) { + client = sequlizeClient; + await initSchemas(); +} + +async function initSchemas() { + const context = client.define('context', { + id: { + type: Sequelize.DataTypes.UUID, + primaryKey: true + }, + name: { + type: Sequelize.DataTypes.TEXT(), + allowNull: false, + unique: true + } + }); + await context.sync(); +} + +async function saveContext(id, contextName) { + const contextClient = client.model('context'); + + const duplicate = await contextClient.findOne({ where: { name: contextName } }); + if (duplicate) { + throw Error('Duplicate context'); + } + + const params = { + id: id, + name: contextName + }; + + const result = contextClient.create(params); + return result; +} + +async function deleteContext(id) { + const contextClient = client.model('context'); + + const result = contextClient.destroy({ where: { id: id } }); + return result; +} + +async function getContexts() { + const contextClient = client.model('context'); + const options = { + attributes: { exclude: ['updated_at', 'created_at'] } + }; + + const dbResult = await contextClient.findAll(options); + return dbResult; +} diff --git a/src/contexts/routes/contextsRoute.js b/src/contexts/routes/contextsRoute.js new file mode 100644 index 000000000..a701b9751 --- /dev/null +++ b/src/contexts/routes/contextsRoute.js @@ -0,0 +1,12 @@ +'use strict'; + +const express = require('express'); +const router = express.Router(); +const contextsController = require('../controllers/contextsController'); +const swaggerValidator = require('express-ajv-swagger-validation'); + +router.post('/', swaggerValidator.validate, contextsController.createContext); +router.get('/', swaggerValidator.validate, contextsController.getContexts); +router.delete('/', swaggerValidator.validate, contextsController.deleteContext); + +module.exports = router; diff --git a/src/database/sequlize-handler/sequlize.js b/src/database/sequlize-handler/sequlize.js index a50e58add..18f95d3f1 100644 --- a/src/database/sequlize-handler/sequlize.js +++ b/src/database/sequlize-handler/sequlize.js @@ -8,6 +8,7 @@ const testsSequlizeConnector = require('../../tests/models/database/sequelize/se const configSequlizeConnector = require('../../configManager/models/database/sequelize/sequelizeConnector'); const processorsSequlizeConnector = require('../../processors/models/database/sequelize/sequelizeConnector'); const fileSequlizeConnector = require('../../files/models/database/sequelize/sequelizeConnector'); +const contextSequlizeConnector = require('../../contexts/models/database/sequelize/sequelizeConnector'); const logger = require('../../../src/common/logger'); const databaseConfig = require('../../config/databaseConfig'); const webhooksSequlizeConnector = require('../../webhooks/models/database/sequelize/sequelizeConnector'); @@ -23,6 +24,7 @@ module.exports.init = async () => { await configSequlizeConnector.init(sequlizeClient); await processorsSequlizeConnector.init(sequlizeClient); await fileSequlizeConnector.init(sequlizeClient); + await contextSequlizeConnector.init(sequlizeClient); await runSequlizeMigrations(); await sequlizeClient.sync(); }; From c03ba6a01d13eabbd95f73c9574386d102206aa6 Mon Sep 17 00:00:00 2001 From: tomcorey26 Date: Sat, 17 Oct 2020 19:22:06 -0400 Subject: [PATCH 2/3] feat(add-context-api): integration tests --- .../contexts/contexts-test.js | 57 +++++++++++++++++++ .../contexts/helpers/requestCreator.js | 52 +++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 tests/integration-tests/contexts/contexts-test.js create mode 100644 tests/integration-tests/contexts/helpers/requestCreator.js diff --git a/tests/integration-tests/contexts/contexts-test.js b/tests/integration-tests/contexts/contexts-test.js new file mode 100644 index 000000000..7faabbf3f --- /dev/null +++ b/tests/integration-tests/contexts/contexts-test.js @@ -0,0 +1,57 @@ + +const { expect } = require('chai'); + +const contextRequestSender = require('./helpers/requestCreator'); + +describe('Contexts api', function () { + this.timeout(5000000); + before(async function () { + await contextRequestSender.init(); + }); + + describe('Good requests', async function () { + describe('GET /v1/contexts', async function () { + before('clean contexts', async function() { + const contextResponse = await contextRequestSender.getContexts(); + await Promise.all(contextResponse.body.map(({ id }) => contextRequestSender.deleteContext({ id: id }))); + }); + const numOfContextsToInsert = 5; + it(`return ${numOfContextsToInsert} contexts`, async function() { + const contextsToInsert = (new Array(numOfContextsToInsert)) + .fill(0, 0, numOfContextsToInsert) + .map((_, idx) => ({ name: String(idx) })); + await Promise.all(contextsToInsert.map(context => contextRequestSender.createContext(context))); + + const contextGetResponse = await contextRequestSender.getContexts(); + expect(contextGetResponse.statusCode).to.equal(200); + + const contexts = contextGetResponse.body; + expect(contexts).to.be.an('array').and.have.lengthOf(numOfContextsToInsert); + }); + }); + describe('POST /v1/contexts', function () { + it('Create context and response 201 status code', async function() { + const createContextResponse = await contextRequestSender.createContext({ name: 'foo' }); + expect(createContextResponse.statusCode).to.equal(201); + expect(createContextResponse.body.name).to.equal('foo'); + }); + }); + }); + + describe('Bad requests', function () { + describe('POST /v1/contexts', function () { + describe('name validation', function() { + before('clean contexts', async function() { + const contextResponse = await contextRequestSender.getContexts(); + await Promise.all(contextResponse.body.map(({ id }) => contextRequestSender.deleteContext({ id: id }))); + }); + it('invalidates name duplicates', async function () { + const uniqueName = { name: 'bob' }; + await contextRequestSender.createContext(uniqueName); + const duplicateNameResponse = await contextRequestSender.createContext(uniqueName); + expect(duplicateNameResponse.statusCode).to.equal(400); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/tests/integration-tests/contexts/helpers/requestCreator.js b/tests/integration-tests/contexts/helpers/requestCreator.js new file mode 100644 index 000000000..d66d8d90e --- /dev/null +++ b/tests/integration-tests/contexts/helpers/requestCreator.js @@ -0,0 +1,52 @@ + +const request = require('supertest'); +const expressApp = require('../../../../src/app'); + +let app; +const headers = { 'Content-Type': 'application/json' }; +const resourceUri = '/v1/contexts'; + +module.exports = { + init, + createContext, + getContexts, + deleteContext +}; + +async function init() { + try { + app = await expressApp(); + } catch (err){ + console.log(err); + process.exit(1); + } +} + +function createContext(body) { + return request(app) + .post(resourceUri) + .send(body) + .set(headers) + .expect(function(res){ + return res; + }); +} + +function getContexts() { + return request(app) + .get(resourceUri) + .set(headers) + .expect(function (res) { + return res; + }); +} + +function deleteContext(body) { + return request(app) + .delete(resourceUri) + .send(body) + .set(headers) + .expect(function (res) { + return res; + }); +} \ No newline at end of file From 57e32947b8ac4bc9c1e9465222949dac9e0a7546 Mon Sep 17 00:00:00 2001 From: tomcorey26 Date: Sat, 17 Oct 2020 19:52:29 -0400 Subject: [PATCH 3/3] feat(add-context-api): fixed mysql key spec bug --- src/contexts/models/database/sequelize/sequelizeConnector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contexts/models/database/sequelize/sequelizeConnector.js b/src/contexts/models/database/sequelize/sequelizeConnector.js index 732d1800e..8636b80af 100644 --- a/src/contexts/models/database/sequelize/sequelizeConnector.js +++ b/src/contexts/models/database/sequelize/sequelizeConnector.js @@ -21,7 +21,7 @@ async function initSchemas() { primaryKey: true }, name: { - type: Sequelize.DataTypes.TEXT(), + type: Sequelize.DataTypes.STRING, allowNull: false, unique: true }