diff --git a/src/node/hooks/express/openapi.js b/src/node/hooks/express/openapi.js index 437c06cc..87a23194 100644 --- a/src/node/hooks/express/openapi.js +++ b/src/node/hooks/express/openapi.js @@ -19,6 +19,7 @@ const cloneDeep = require('lodash.clonedeep'); const apiHandler = require('../../handler/APIHandler'); const settings = require('../../utils/Settings'); +const isValidJSONPName = require('./isValidJSONPName'); const log4js = require('log4js'); const apiLogger = log4js.getLogger('API'); @@ -582,11 +583,11 @@ exports.expressCreateServer = async (_, args) => { api.register({ notFound: (c, req, res) => { res.statusCode = 404; - return { code: 3, message: 'no such function', data: null }; + res.send({ code: 3, message: 'no such function', data: null }); }, notImplemented: (c, req, res) => { res.statusCode = 501; - return { code: 3, message: 'not implemented', data: null }; + res.send({ code: 3, message: 'not implemented', data: null }); }, }); @@ -616,12 +617,18 @@ exports.expressCreateServer = async (_, args) => { } // return in common format - const response = { code: 0, message: 'ok', data }; + let response = { code: 0, message: 'ok', data }; // log response apiLogger.info(`RESPONSE, ${funcName}, ${JSON.stringify(response)}`); - return response; + // is this a jsonp call, add the function call + if (query.jsonp && isValidJSONPName.check(query.jsonp)) { + res.header('Content-Type', 'application/javascript'); + response = `${req.query.jsonp}(${JSON.stringify(response)}`; + } + + res.send(response); }; // each operation can be called with either GET or POST @@ -635,7 +642,7 @@ exports.expressCreateServer = async (_, args) => { try { // allow cors res.header('Access-Control-Allow-Origin', '*'); - res.send(await api.handleRequest(req, req, res)); + await api.handleRequest(req, req, res); } catch (err) { if (err.name == 'apierror') { // parameters were wrong and the api stopped execution, pass the error diff --git a/tests/backend/specs/api/api.js b/tests/backend/specs/api/api.js new file mode 100644 index 00000000..745c7990 --- /dev/null +++ b/tests/backend/specs/api/api.js @@ -0,0 +1,79 @@ +/** + * API specs + * + * Tests for generic overarching HTTP API related features not related to any + * specific part of the data model or domain. For example: tests for versioning + * and openapi definitions. + */ + +const assert = require('assert'); +const supertest = require(__dirname + '/../../../../src/node_modules/supertest'); +const fs = require('fs'); +const settings = require(__dirname + '/../../loadSettings').loadSettings(); +const api = supertest('http://' + settings.ip + ':' + settings.port); +const path = require('path'); + +var validateOpenAPI = require(__dirname + '/../../../../src/node_modules/openapi-schema-validation').validate; + +var filePath = path.join(__dirname, '../../../../APIKEY.txt'); + +var apiKey = fs.readFileSync(filePath, { encoding: 'utf-8' }); +apiKey = apiKey.replace(/\n$/, ''); +var apiVersion = 1; + +var testPadId = makeid(); + +describe('API Versioning', function() { + it('errors if can not connect', function(done) { + api + .get('/api/') + .expect(function(res) { + apiVersion = res.body.currentVersion; + if (!res.body.currentVersion) throw new Error('No version set in API'); + return; + }) + .expect(200, done); + }); +}); + +describe('OpenAPI definition', function() { + it('generates valid openapi definition document', function(done) { + api + .get('/api/openapi.json') + .expect(function(res) { + const { valid, errors } = validateOpenAPI(res.body, 3); + if (!valid) { + const prettyErrors = JSON.stringify(errors, null, 2); + throw new Error(`Document is not valid OpenAPI. ${errors.length} validation errors:\n${prettyErrors}`); + } + return; + }) + .expect(200, done); + }); +}); + +describe('jsonp support', function() { + it('supports jsonp calls', function(done) { + api + .get(endPoint('createPad') + '&jsonp=jsonp_1&padID=' + testPadId) + .expect(function(res) { + if (!res.text.match('jsonp_1')) throw new Error('no jsonp call seen'); + }) + .expect('Content-Type', /javascript/) + .expect(200, done); + }); +}); + +var endPoint = function(point) { + return '/api/' + apiVersion + '/' + point + '?apikey=' + apiKey; +}; + +function makeid() { + var text = ''; + var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + for (var i = 0; i < 5; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +}