diff --git a/src/routes/write/users.js b/src/routes/write/users.js index 92428f237d..7894a82517 100644 --- a/src/routes/write/users.js +++ b/src/routes/write/users.js @@ -34,43 +34,6 @@ function authenticatedRoutes() { setupApiRoute(router, 'post', '/:uid/tokens', [...middlewares, middleware.assert.user, middleware.exposePrivilegeSet], controllers.write.users.generateToken); setupApiRoute(router, 'delete', '/:uid/tokens/:token', [...middlewares, middleware.assert.user, middleware.exposePrivilegeSet], controllers.write.users.deleteToken); - - /** - * Implement this later... - */ - // app.route('/:uid/tokens') - // .get(apiMiddleware.requireUser, function(req, res) { - // if (parseInt(req.params.uid, 10) !== parseInt(req.user.uid, 10)) { - // return errorHandler.respond(401, res); - // } - - // auth.getTokens(req.params.uid, function(err, tokens) { - // return errorHandler.handle(err, res, { - // tokens: tokens - // }); - // }); - // }) - // .post(apiMiddleware.requireUser, function(req, res) { - // if (parseInt(req.params.uid, 10) !== parseInt(req.user.uid)) { - // return errorHandler.respond(401, res); - // } - - // auth.generateToken(req.params.uid, function(err, token) { - // return errorHandler.handle(err, res, { - // token: token - // }); - // }); - // }); - - // app.delete('/:uid/tokens/:token', apiMiddleware.requireUser, function(req, res) { - // if (parseInt(req.params.uid, 10) !== req.user.uid) { - // return errorHandler.respond(401, res); - // } - - // auth.revokeToken(req.params.token, 'user', function(err) { - // errorHandler.handle(err, res); - // }); - // }); } module.exports = function () { diff --git a/test/api.js b/test/api.js index f5eae8ad7b..51521d6ff9 100644 --- a/test/api.js +++ b/test/api.js @@ -21,7 +21,9 @@ const messaging = require('../src/messaging'); describe('Read API', async () => { let readApi = false; - const apiPath = path.resolve(__dirname, '../public/openapi/read.yaml'); + let writeApi = false; + const readApiPath = path.resolve(__dirname, '../public/openapi/read.yaml'); + const writeApiPath = path.resolve(__dirname, '../public/openapi/write.yaml'); let jar; let setup = false; const unauthenticatedRoutes = ['/api/login', '/api/register']; // Everything else will be called with the admin user @@ -67,7 +69,7 @@ describe('Read API', async () => { await socketUser.exportPosts({ uid: adminUid }, { uid: adminUid }); await socketUser.exportUploads({ uid: adminUid }, { uid: adminUid }); // wait for export child process to complete - await wait(20000); + // await wait(20000); // Attach a search hook so /api/search is enabled plugins.registerHook('core', { @@ -81,126 +83,55 @@ describe('Read API', async () => { it('should pass OpenAPI v3 validation', async () => { try { - await SwaggerParser.validate(apiPath); + await SwaggerParser.validate(readApiPath); + await SwaggerParser.validate(writeApiPath); } catch (e) { assert.ifError(e); } }); - readApi = await SwaggerParser.dereference(apiPath); + readApi = await SwaggerParser.dereference(readApiPath); + writeApi = await SwaggerParser.dereference(writeApiPath); // Iterate through all documented paths, make a call to it, and compare the result body with what is defined in the spec - const paths = Object.keys(readApi.paths); + const paths = Object.keys(writeApi.paths); + // const paths = Object.keys(readApi.paths); paths.forEach((path) => { + const context = writeApi.paths[path]; + // const context = readApi.paths[path]; let schema; let response; - let url; + const urls = []; + const methods = []; const headers = {}; const qs = {}; - function compare(schema, response, context) { - let required = []; - const additionalProperties = schema.hasOwnProperty('additionalProperties'); - - if (schema.allOf) { - schema = schema.allOf.reduce((memo, obj) => { - required = required.concat(obj.required ? obj.required : Object.keys(obj.properties)); - memo = { ...memo, ...obj.properties }; - return memo; - }, {}); - } else if (schema.properties) { - required = schema.required || Object.keys(schema.properties); - schema = schema.properties; - } else { - // If schema contains no properties, check passes - return; - } - - // Compare the schema to the response - required.forEach((prop) => { - if (schema.hasOwnProperty(prop)) { - assert(response.hasOwnProperty(prop), '"' + prop + '" is a required property (path: ' + path + ', context: ' + context + ')'); - - // Don't proceed with type-check if the value could possibly be unset (nullable: true, in spec) - if (response[prop] === null && schema[prop].nullable === true) { - return; - } - - // Therefore, if the value is actually null, that's a problem (nullable is probably missing) - assert(response[prop] !== null, '"' + prop + '" was null, but schema does not specify it to be a nullable property (path: ' + path + ', context: ' + context + ')'); - - switch (schema[prop].type) { - case 'string': - assert.strictEqual(typeof response[prop], 'string', '"' + prop + '" was expected to be a string, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')'); - break; - case 'boolean': - assert.strictEqual(typeof response[prop], 'boolean', '"' + prop + '" was expected to be a boolean, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')'); - break; - case 'object': - assert.strictEqual(typeof response[prop], 'object', '"' + prop + '" was expected to be an object, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')'); - compare(schema[prop], response[prop], context ? [context, prop].join('.') : prop); - break; - case 'array': - assert.strictEqual(Array.isArray(response[prop]), true, '"' + prop + '" was expected to be an array, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')'); - - if (schema[prop].items) { - // Ensure the array items have a schema defined - assert(schema[prop].items.type || schema[prop].items.allOf, '"' + prop + '" is defined to be an array, but its items have no schema defined (path: ' + path + ', context: ' + context + ')'); - - // Compare types - if (schema[prop].items.type === 'object' || Array.isArray(schema[prop].items.allOf)) { - response[prop].forEach((res) => { - compare(schema[prop].items, res, context ? [context, prop].join('.') : prop); - }); - } else if (response[prop].length) { // for now - response[prop].forEach((item) => { - assert.strictEqual(typeof item, schema[prop].items.type, '"' + prop + '" should have ' + schema[prop].items.type + ' items, but found ' + typeof items + ' instead (path: ' + path + ', context: ' + context + ')'); - }); - } - } - break; - } - } - }); - - // Compare the response to the schema - Object.keys(response).forEach((prop) => { - if (additionalProperties) { // All bets are off - return; + it('should have examples when parameters are present', () => { + Object.keys(context).forEach((method) => { + const parameters = context[method].parameters; + let testPath = path; + if (parameters) { + parameters.forEach((param) => { + assert(param.example !== null && param.example !== undefined, path + ' has parameters without examples'); + + switch (param.in) { + case 'path': + testPath = testPath.replace('{' + param.name + '}', param.example); + break; + case 'header': + headers[param.name] = param.example; + break; + case 'query': + qs[param.name] = param.example; + break; + } + }); } - assert(schema[prop], '"' + prop + '" was found in response, but is not defined in schema (path: ' + path + ', context: ' + context + ')'); + urls.push = nconf.get('url') + testPath; + methods.push(method); }); - } - - // TOXO: fix -- premature exit for POST-only routes - if (!readApi.paths[path].get) { - return; - } - - it('should have examples when parameters are present', () => { - const parameters = readApi.paths[path].get.parameters; - let testPath = path; - if (parameters) { - parameters.forEach((param) => { - assert(param.example !== null && param.example !== undefined, path + ' has parameters without examples'); - - switch (param.in) { - case 'path': - testPath = testPath.replace('{' + param.name + '}', param.example); - break; - case 'header': - headers[param.name] = param.example; - break; - case 'query': - qs[param.name] = param.example; - break; - } - }); - } - - url = nconf.get('url') + testPath; }); it('should resolve with a 200 when called', async () => { @@ -220,32 +151,93 @@ describe('Read API', async () => { // Recursively iterate through schema properties, comparing type it('response should match schema definition', () => { - const has200 = readApi.paths[path].get.responses['200']; + const has200 = context.get.responses['200']; if (!has200) { return; } const hasJSON = has200.content && has200.content['application/json']; if (hasJSON) { - schema = readApi.paths[path].get.responses['200'].content['application/json'].schema; + schema = context.get.responses['200'].content['application/json'].schema; compare(schema, response, 'root'); } // TODO someday: text/csv, binary file type checking? }); }); -}); - -describe('Write API', async () => { - const apiPath = path.resolve(__dirname, '../public/openapi/write.yaml'); - it('should pass OpenAPI v3 validation', async () => { - try { - await SwaggerParser.validate(apiPath); - } catch (e) { - assert.ifError(e); + function compare(schema, response, context) { + let required = []; + const additionalProperties = schema.hasOwnProperty('additionalProperties'); + + if (schema.allOf) { + schema = schema.allOf.reduce((memo, obj) => { + required = required.concat(obj.required ? obj.required : Object.keys(obj.properties)); + memo = { ...memo, ...obj.properties }; + return memo; + }, {}); + } else if (schema.properties) { + required = schema.required || Object.keys(schema.properties); + schema = schema.properties; + } else { + // If schema contains no properties, check passes + return; } - }); - console.log(await SwaggerParser.dereference(apiPath)); + // Compare the schema to the response + required.forEach((prop) => { + if (schema.hasOwnProperty(prop)) { + assert(response.hasOwnProperty(prop), '"' + prop + '" is a required property (path: ' + path + ', context: ' + context + ')'); + + // Don't proceed with type-check if the value could possibly be unset (nullable: true, in spec) + if (response[prop] === null && schema[prop].nullable === true) { + return; + } + + // Therefore, if the value is actually null, that's a problem (nullable is probably missing) + assert(response[prop] !== null, '"' + prop + '" was null, but schema does not specify it to be a nullable property (path: ' + path + ', context: ' + context + ')'); + + switch (schema[prop].type) { + case 'string': + assert.strictEqual(typeof response[prop], 'string', '"' + prop + '" was expected to be a string, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')'); + break; + case 'boolean': + assert.strictEqual(typeof response[prop], 'boolean', '"' + prop + '" was expected to be a boolean, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')'); + break; + case 'object': + assert.strictEqual(typeof response[prop], 'object', '"' + prop + '" was expected to be an object, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')'); + compare(schema[prop], response[prop], context ? [context, prop].join('.') : prop); + break; + case 'array': + assert.strictEqual(Array.isArray(response[prop]), true, '"' + prop + '" was expected to be an array, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')'); + + if (schema[prop].items) { + // Ensure the array items have a schema defined + assert(schema[prop].items.type || schema[prop].items.allOf, '"' + prop + '" is defined to be an array, but its items have no schema defined (path: ' + path + ', context: ' + context + ')'); + + // Compare types + if (schema[prop].items.type === 'object' || Array.isArray(schema[prop].items.allOf)) { + response[prop].forEach((res) => { + compare(schema[prop].items, res, context ? [context, prop].join('.') : prop); + }); + } else if (response[prop].length) { // for now + response[prop].forEach((item) => { + assert.strictEqual(typeof item, schema[prop].items.type, '"' + prop + '" should have ' + schema[prop].items.type + ' items, but found ' + typeof items + ' instead (path: ' + path + ', context: ' + context + ')'); + }); + } + } + break; + } + } + }); + + // Compare the response to the schema + Object.keys(response).forEach((prop) => { + if (additionalProperties) { // All bets are off + return; + } + + assert(schema[prop], '"' + prop + '" was found in response, but is not defined in schema (path: ' + path + ', context: ' + context + ')'); + }); + } });