Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(processors): adding exported functions to processors #255

Merged
merged 9 commits into from
Jan 14, 2020
8 changes: 8 additions & 0 deletions docs/openapi3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,7 @@ components:
properties:
name:
type: string
minLength: 1
description: The name of the test.
example: Order from Pet Store
processor_file_url:
Expand Down Expand Up @@ -1758,12 +1759,19 @@ components:
readOnly: true
name:
type: string
minLength: 1
description: The name of the processor.
example: Custom javascript for logging
description:
type: string
description: A description of the processor.
example: logs every error (5xx).
exported_functions:
type: array
readOnly: true
description: Names of all exported function in the javascript file
items:
type: string
updated_at:
type: string
format: date-time
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"copy-dir": "^0.3.0",
"cron": "^1.7.1",
"dockerode": "^2.5.8",
"esprima": "^4.0.1",
"express": "^4.17.1",
"express-ajv-swagger-validation": "^0.9.0",
"express-easy-zip": "^1.1.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE processors ADD exported_functions list<text>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const Sequelize = require('sequelize');

module.exports.up = async (query, DataTypes) => {
let testsTable = await query.describeTable('processors');

if (!testsTable.exported_functions) {
await query.addColumn(
'processors', 'exported_functions',
Sequelize.DataTypes.STRING);
}
};

module.exports.down = async (query, DataTypes) => {
await query.removeColumn('processors', 'exported_functions');
};
10 changes: 5 additions & 5 deletions src/processors/models/database/cassandra/cassandraConnector.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ let databaseConfig = require('../../../../config/databaseConfig');
let _ = require('lodash');
let client;

const INSERT_PROCESSOR = 'INSERT INTO processors(id, name, description, javascript, created_at, updated_at) values(?,?,?,?,?,?)';
const INSERT_PROCESSOR = 'INSERT INTO processors(id, name, description, javascript, exported_functions, created_at, updated_at) values(?,?,?,?,?,?,?)';
const GET_ALL_PROCESSORS = 'SELECT * FROM processors';
const GET_PROCESSOR_BY_ID = 'SELECT * FROM processors WHERE id=?';
const DELETE_PROCESSOR = 'DELETE FROM processors WHERE id=?';
const UPDATE_PROCESSOR = 'UPDATE processors SET name=?, description=?, javascript=?, updated_at=? WHERE id=? AND created_at=? IF EXISTS';
const UPDATE_PROCESSOR = 'UPDATE processors SET name=?, description=?, javascript=?, exported_functions=?, updated_at=? WHERE id=? AND created_at=? IF EXISTS';

const INSERT_PROCESSOR_MAPPING = 'INSERT INTO processors_mapping(name, id) VALUES(?, ?)';
const DELETE_PROCESSOR_MAPPING = 'DELETE FROM processors_mapping WHERE name=?';
Expand Down Expand Up @@ -72,7 +72,7 @@ async function deleteProcessor(processorId) {
}

async function insertProcessor(processorId, processorInfo) {
let params = [processorId, processorInfo.name, processorInfo.description, processorInfo.javascript, Date.now(), Date.now()];
let params = [processorId, processorInfo.name, processorInfo.description, processorInfo.javascript, processorInfo.exported_functions, Date.now(), Date.now()];
let mappingParams = [processorInfo.name, processorId];
const [processor] = await Promise.all([
executeQuery(INSERT_PROCESSOR, params, queryOptions),
Expand All @@ -82,9 +82,9 @@ async function insertProcessor(processorId, processorInfo) {
}

async function updateProcessor(processorId, updatedProcessor) {
const { name, description, javascript, created_at: createdAt } = updatedProcessor;
const { name, description, javascript, exported_functions, created_at: createdAt } = updatedProcessor;
const processor = await getProcessorById(processorId);
const params = [ name, description, javascript, Date.now(), processorId, createdAt.getTime() ];
const params = [ name, description, javascript, exported_functions, Date.now(), processorId, createdAt.getTime() ];
return Promise.all([
executeQuery(UPDATE_PROCESSOR, params, queryOptions),
executeQuery(INSERT_PROCESSOR_MAPPING, [updatedProcessor.name, processorId]),
Expand Down
14 changes: 12 additions & 2 deletions src/processors/models/database/sequelize/sequelizeConnector.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ async function insertProcessor(processorId, processorInfo) {
name: processorInfo.name,
description: processorInfo.description,
javascript: processorInfo.javascript,
exported_functions: processorInfo.exported_functions,
created_at: Date.now(),
updated_at: Date.now()
};
Expand Down Expand Up @@ -67,8 +68,8 @@ async function deleteProcessor(processorId) {

async function updateProcessor(processorId, updatedProcessor) {
const processorsModel = client.model('processor');
const { name, description, javascript } = updatedProcessor;
return processorsModel.update({ name, description, javascript, updated_at: Date.now() }, { where: { id: processorId } });
const { name, description, javascript, exported_functions } = updatedProcessor;
return processorsModel.update({ name, description, javascript, updated_at: Date.now(), exported_functions }, { where: { id: processorId } });
}

async function initSchemas() {
Expand All @@ -91,6 +92,15 @@ async function initSchemas() {
},
updated_at: {
type: Sequelize.DataTypes.DATE
},
exported_functions: {
type: Sequelize.DataTypes.STRING,
get: function() {
return JSON.parse(this.getDataValue('exported_functions'));
},
set: function(val) {
return this.setDataValue('exported_functions', JSON.stringify(val));
}
}
});
await processorsFiles.sync();
Expand Down
41 changes: 35 additions & 6 deletions src/processors/models/processorsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@ const uuid = require('uuid');

const logger = require('../../common/logger'),
databaseConnector = require('./database/databaseConnector'),
fileManager = require('../../tests/models/fileManager.js'),
testsManager = require('../../tests/models/manager'),
{ ERROR_MESSAGES } = require('../../common/consts');

let testsManager = require('../../tests/models/manager');

module.exports.createProcessor = async function (processor) {
const processorWithTheSameName = await databaseConnector.getProcessorByName(processor.name);
if (processorWithTheSameName) {
throw generateProcessorNameAlreadyExistsError();
}
let processorId = uuid.v4();
try {
fileManager.validateJavascriptContent(processor.javascript);
let exportedFunctions = verifyJSAndGetExportedFunctions(processor.javascript);
processor.exported_functions = exportedFunctions;
await databaseConnector.insertProcessor(processorId, processor);
processor.id = processorId;
logger.info('Processor saved successfully to database');
Expand All @@ -28,7 +27,10 @@ module.exports.createProcessor = async function (processor) {
};

module.exports.getAllProcessors = async function (from, limit) {
return databaseConnector.getAllProcessors(from, limit);
let allProcessors = await databaseConnector.getAllProcessors(from, limit);
allProcessors.forEach(processor => {
});
return allProcessors;
};

module.exports.getProcessor = async function (processorId) {
Expand Down Expand Up @@ -62,8 +64,10 @@ module.exports.updateProcessor = async function (processorId, processor) {
}

processor.created_at = oldProcessor.created_at;
fileManager.validateJavascriptContent(processor.javascript);
let exportedFunctions = verifyJSAndGetExportedFunctions(processor.javascript);
processor.exported_functions = exportedFunctions;
await databaseConnector.updateProcessor(processorId, processor);
processor.exported_functions = verifyJSAndGetExportedFunctions(processor.javascript, true);
return processor;
};

Expand All @@ -79,6 +83,31 @@ function generateProcessorNameAlreadyExistsError() {
return error;
}

function generateUnprocessableEntityError(message) {
const error = new Error(message);
error.statusCode = 422;
return error;
}
function verifyJSAndGetExportedFunctions(src) {
let exportedFunctions;
try {
let m = new module.constructor();
m.paths = module.paths;
m._compile(src, 'none');
let exports = m.exports;
exportedFunctions = Object.keys(exports);
} catch (err) {
let error = generateUnprocessableEntityError('javascript syntax validation failed with error: ' + err.message);
throw error;
}

if (exportedFunctions.length === 0) {
let error = generateUnprocessableEntityError('javascript has 0 exported function');
throw error;
}
return exportedFunctions;
}

function generateProcessorIsUsedByTestsError(testNames) {
const error = new Error(`${ERROR_MESSAGES.PROCESSOR_DELETION_FORBIDDEN}: ${testNames.join(', ')}`);
error.statusCode = 409;
Expand Down
18 changes: 2 additions & 16 deletions src/tests/models/fileManager.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
'use strict';
const uuid = require('uuid'),
request = require('request-promise-native'),
esprima = require('esprima');
request = require('request-promise-native');

const database = require('./database'),
{ ERROR_MESSAGES } = require('../../common/consts');

module.exports = {
saveFile,
getFile,
validateJavascriptContent
getFile
};

async function downloadFile(fileUrl) {
Expand Down Expand Up @@ -45,15 +43,3 @@ async function saveFile(fileUrl) {
await database.saveFile(id, fileBase64Value);
return id;
}

function validateJavascriptContent (javascriptFileContent) {
let error, errorMessage;
try {
esprima.parseScript(javascriptFileContent);
} catch (err) {
errorMessage = err.description;
error = new Error('javascript syntax validation failed with error: ' + errorMessage);
error.statusCode = 422;
throw error;
}
}
73 changes: 71 additions & 2 deletions tests/integration-tests/processors/processors-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ describe('Processors api', function() {
let getProcessorResponse = await processorRequestSender.getProcessor(processor.id, validHeaders);
getProcessorResponse.statusCode.should.eql(200);
should(getProcessorResponse.body).containDeep(processorData);
should(getProcessorResponse.body.exported_functions).eql(['simple']);
});
it('Get non-existent processor by id', async () => {
let getProcessorResponse = await processorRequestSender.getProcessor(uuid(), validHeaders);
Expand Down Expand Up @@ -138,14 +139,15 @@ describe('Processors api', function() {
};
let createProcessorResponse = await processorRequestSender.createProcessor(requestBody, validHeaders);
createProcessorResponse.statusCode.should.eql(201);
createProcessorResponse.body.exported_functions.should.eql(['createAuthToken']);

let deleteResponse = await processorRequestSender.deleteProcessor(createProcessorResponse.body.id);
should(deleteResponse.statusCode).equal(204);
});
});
describe('PUT /v1/processors/{processor_id}', function() {
it('update a processor', async function() {
const processor = generateRawJSProcessor('predator');
const processor = generateRawJSProcessor('predator ' + uuid());
const createResponse = await processorRequestSender.createProcessor(processor, validHeaders);
should(createResponse.statusCode).equal(201);
const processorId = createResponse.body.id;
Expand All @@ -156,6 +158,7 @@ describe('Processors api', function() {
should(updateResponse.statusCode).equal(200);
should(updateResponse.body.javascript).equal(processor.javascript);
should(updateResponse.body.description).equal(processor.description);
should(updateResponse.body.exported_functions).eql(['add']);

const deleteResponse = await processorRequestSender.deleteProcessor(processorId);
should(deleteResponse.statusCode).equal(204);
Expand Down Expand Up @@ -244,6 +247,72 @@ describe('Processors api', function() {
let createProcessorResponse = await processorRequestSender.createProcessor(requestBody, validHeaders);
createProcessorResponse.statusCode.should.eql(422);
});


it('Create processor without export functions', async () => {
const requestBody = {
name: 'authentication',
description: 'Creates authorization token and saves it in the context',
javascript:
`{
const uuid = require('uuid/v4');
module.exports = {
};

function createAuthToken(userContext, events, done) {
userContext.vars.token = uuid();
return done();
}
}`
};
let createProcessorResponse = await processorRequestSender.createProcessor(requestBody, validHeaders);
createProcessorResponse.statusCode.should.eql(422);
createProcessorResponse.body.message.should.eql('javascript has 0 exported function');
});

it('Create processor export function that not exists', async () => {
const requestBody = {
name: 'authentication',
description: 'Creates authorization token and saves it in the context',
javascript:
`{
const uuid = require('uuid/v4');
module.exports = {
hello,
};

function createAuthToken(userContext, events, done) {
userContext.vars.token = uuid();
return done();
}
}`
};
let createProcessorResponse = await processorRequestSender.createProcessor(requestBody, validHeaders);
createProcessorResponse.statusCode.should.eql(422);
createProcessorResponse.body.message.should.eql('javascript syntax validation failed with error: hello is not defined');
});

it('Create processor with Unexpected token', async () => {
const requestBody = {
name: 'authentication',
description: 'Creates authorization token and saves it in the context',
javascript:
`{
const uuid = require('uuid/v4');
module.exports = {
hello,
};

function createAut) {
userContext.vars.token = uuid();
return done();
}
}`
};
let createProcessorResponse = await processorRequestSender.createProcessor(requestBody, validHeaders);
createProcessorResponse.statusCode.should.eql(422);
createProcessorResponse.body.message.should.eql('javascript syntax validation failed with error: Unexpected token )');
});
});
describe('PUT /processors/{processor_id}', () => {
it('processor doesn\'t exist', async function() {
Expand Down Expand Up @@ -282,6 +351,6 @@ function generateRawJSProcessor(name) {
return {
name,
description: 'exports a number',
javascript: 'module.exports = 5;'
javascript: 'module.exports.simple = 5;'
};
}
2 changes: 1 addition & 1 deletion tests/integration-tests/runLocal.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ LOCAL_TEST=true DATABASE_TYPE=cassandra JOB_PLATFORM=kubernetes ./tests/integrat
LOCAL_TEST=true DATABASE_TYPE=mysql JOB_PLATFORM=kubernetes ./tests/integration-tests/run.sh
LOCAL_TEST=true DATABASE_TYPE=sqlite JOB_PLATFORM=kubernetes ./tests/integration-tests/run.sh
LOCAL_TEST=true DATABASE_TYPE=postgres JOB_PLATFORM=metronome ./tests/integration-tests/run.sh
LOCAL_TEST=true DATABASE_TYPE=sqlite JOB_PLATFORM=docker ./tests/integration-tests/run.sh
LOCAL_TEST=true DATABASE_TYPE=sqlite JOB_PLATFORM=docker ./tests/integration-tests/run.sh
17 changes: 16 additions & 1 deletion tests/unit-tests/processors/models/processorsManager-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ describe('Processor manager tests', function () {
});

beforeEach(() => {
sandbox.resetHistory();
sandbox.reset();
});

Expand Down Expand Up @@ -85,6 +84,22 @@ describe('Processor manager tests', function () {
should(e.statusCode).equal(400);
}
});

it('Should return error for js without public functions', async function () {
let exception;
const firstProcessor = {
description: 'bad processor',
name: 'mickey',
javascript: 'return 5'
};
try {
await manager.createProcessor(firstProcessor);
} catch (e) {
exception = e;
}
should(exception.statusCode).eql(422);
should(exception.message).eql('javascript has 0 exported function');
});
});
describe('Delete existing processor', function () {
it('Should delete processor', async function () {
Expand Down
Loading