From adf0c95c3280fd3a0fae7ceb7c1343f164d02219 Mon Sep 17 00:00:00 2001 From: iandrc Date: Fri, 24 Jul 2020 14:46:16 +0300 Subject: [PATCH] increase code coverage (#267) --- dynamic.js | 51 +-- examples/example-static-specification.js | 0 examples/example-static-specification.json | 47 +++ examples/test-package.json | 3 + package.json | 2 +- routes.js | 6 +- test/route.js | 19 + test/static.js | 443 +++++++++++++++++++-- 8 files changed, 499 insertions(+), 72 deletions(-) create mode 100644 examples/example-static-specification.js create mode 100644 examples/example-static-specification.json create mode 100644 examples/test-package.json diff --git a/dynamic.js b/dynamic.js index cb85675f..1b30c43d 100644 --- a/dynamic.js +++ b/dynamic.js @@ -106,22 +106,20 @@ module.exports = function (fastify, opts, next) { swaggerObject.externalDocs = externalDocs } - if (!ref) { - const externalSchemas = Array.from(sharedSchemasMap.values()) + const externalSchemas = Array.from(sharedSchemasMap.values()) - ref = Ref({ clone: true, applicationUri: 'todo.com', externalSchemas }) - swaggerObject.definitions = { - ...swaggerObject.definitions, - ...(ref.definitions().definitions) - } - - // Swagger doesn't accept $id on /definitions schemas. - // The $ids are needed by Ref() to check the URI so we need - // to remove them at the end of the process - Object.values(swaggerObject.definitions) - .forEach(_ => { delete _.$id }) + ref = Ref({ clone: true, applicationUri: 'todo.com', externalSchemas }) + swaggerObject.definitions = { + ...swaggerObject.definitions, + ...(ref.definitions().definitions) } + // Swagger doesn't accept $id on /definitions schemas. + // The $ids are needed by Ref() to check the URI so we need + // to remove them at the end of the process + Object.values(swaggerObject.definitions) + .forEach(_ => { delete _.$id }) + swaggerObject.paths = {} for (var route of routes) { if (route.schema && route.schema.hide) { @@ -218,9 +216,8 @@ module.exports = function (fastify, opts, next) { } swaggerMethod.responses = genResponse(schema ? schema.response : null) - if (swaggerRoute) { - swaggerObject.paths[url] = swaggerRoute - } + + swaggerObject.paths[url] = swaggerRoute } if (opts && opts.yaml) { @@ -281,16 +278,9 @@ module.exports = function (fastify, opts, next) { const rawJsonSchema = fastifyResponseJson[key] const resolved = ref.resolve(rawJsonSchema) - if (resolved.type || resolved.$ref) { - responsesContainer[key] = { - schema: resolved - } - } else { - responsesContainer[key] = resolved - } - - if (!responsesContainer[key].description) { - responsesContainer[key].description = 'Default Response' + responsesContainer[key] = { + schema: resolved, + description: 'Default Response' } }) @@ -395,10 +385,7 @@ function localRefResolve (jsonSchema, externalSchemas) { return propertiesMap } - if (jsonSchema.$ref) { - // $ref is in the format: #/definitions// - const localReference = jsonSchema.$ref.split('/')[2] - return localRefResolve(externalSchemas[localReference], externalSchemas) - } - return jsonSchema + // $ref is in the format: #/definitions// + const localReference = jsonSchema.$ref.split('/')[2] + return localRefResolve(externalSchemas[localReference], externalSchemas) } diff --git a/examples/example-static-specification.js b/examples/example-static-specification.js new file mode 100644 index 00000000..e69de29b diff --git a/examples/example-static-specification.json b/examples/example-static-specification.json new file mode 100644 index 00000000..f24f8d00 --- /dev/null +++ b/examples/example-static-specification.json @@ -0,0 +1,47 @@ +{ + "openapi": "3.0.0", + "info": { + "description": "Test swagger specification", + "version": "1.0.0", + "title": "Test swagger specification", + "contact": { + "email": "super.developer@gmail.com" + } + }, + "servers": { + "url": "http://localhost:3000/", + "description": "Localhost (uses test data)" + }, + "paths": { + "/status": { + "get": { + "description": "Status route, so we can check if server is alive", + "tags": ["Status"] + }, + "responses": { + "200": { + "description": "Server is alive", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "health": { + "type": "boolean" + }, + "date": { + "type": "string" + } + }, + "example": { + "health": true, + "date": "2018-02-19T15:36:46.758Z" + } + } + } + } + } + } + } + } +} diff --git a/examples/test-package.json b/examples/test-package.json new file mode 100644 index 00000000..05e6f1d1 --- /dev/null +++ b/examples/test-package.json @@ -0,0 +1,3 @@ +{ + "version": "3.1.0" +} diff --git a/package.json b/package.json index 0a77eeda..ea941010 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "prepare": "node prepare-swagger-ui", - "test": "standard && tap test/*.js && npm run typescript", + "test": "standard && tap --100 test/*.js && npm run typescript", "prepublishOnly": "npm run prepare", "typescript": "tsd" }, diff --git a/routes.js b/routes.js index 9f126171..8e8c7a95 100644 --- a/routes.js +++ b/routes.js @@ -68,11 +68,7 @@ function fastifySwagger (fastify, opts, next) { schema: { hide: true }, handler: function (req, reply) { const file = req.params['*'] - if (file === '') { - reply.redirect(getRedirectPathForTheRootRoute(req.raw.url)) - } else { - reply.sendFile(file) - } + reply.sendFile(file) } }) diff --git a/test/route.js b/test/route.js index e77dc27b..b8181cf0 100644 --- a/test/route.js +++ b/test/route.js @@ -104,6 +104,24 @@ const opts5 = { } } +const opts6 = { + schema: { + params: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'user id' + }, + key: { + type: 'string', + description: 'just some random key' + } + } + } + } +} + test('/documentation/json route', t => { t.plan(2) const fastify = Fastify() @@ -488,6 +506,7 @@ test('/documentation2/json route (overwrite)', t => { fastify.post('/example', opts2, () => {}) fastify.get('/parameters/:id', opts3, () => {}) fastify.get('/example1', opts4, () => {}) + fastify.get('/parameters/:id/:key', opts6, () => {}) fastify.inject({ method: 'GET', diff --git a/test/static.js b/test/static.js index b6ce8680..5efe5d3e 100644 --- a/test/static.js +++ b/test/static.js @@ -1,9 +1,11 @@ 'use strict' +const path = require('path') const t = require('tap') const test = t.test const Fastify = require('fastify') const fastifySwagger = require('../index') +const fastifySwaggerDynamic = require('../dynamic') const yaml = require('js-yaml') const resolve = require('path').resolve @@ -43,17 +45,88 @@ test('specification validation check works', t => { }) }) -test('swagger route returns yaml', t => { - t.plan(4) +test('registering plugin with invalid mode throws an error', t => { + const config = { + mode: 'error' + } + + t.plan(1) const fastify = Fastify() - fastify.register(fastifySwagger, { + fastify.register(fastifySwagger, config) + + fastify.ready(err => { + t.equal(err.message, 'unsupported mode, should be one of [\'static\', \'dynamic\']') + }) +}) + +test('unsupported file extension in specification.path throws an error', t => { + const config = { mode: 'static', specification: { - path: './examples/example-static-specification.yaml' + path: './examples/example-static-specification.js' }, exposeRoute: true + } + + t.plan(1) + const fastify = Fastify() + fastify.register(fastifySwagger, config) + + fastify.ready(err => { + t.equal(err.message, 'specification.path extension name is not supported, should be one from [\'.yaml\', \'.json\']') }) +}) + +test('non-string specification.baseDir throws an error ', t => { + const config = { + exposeRoute: true, + mode: 'static', + specification: { + path: './examples/example-static-specification.yaml', + baseDir: 1 + } + } + + t.plan(1) + const fastify = Fastify() + fastify.register(fastifySwagger, config) + + fastify.ready(err => { + t.equal(err.message, 'specification.baseDir should be string') + }) +}) + +test('non-object specification.document throws an error', t => { + const config = { + exposeRoute: true, + mode: 'static', + specification: { + document: 'doc' + } + } + + t.plan(1) + const fastify = new Fastify() + fastify.register(fastifySwagger, config) + + fastify.ready(err => { + t.equal(err.message, 'specification.document is not an object') + }) +}) + +test('swagger route returns yaml', t => { + const config = { + mode: 'static', + specification: { + path: './examples/example-static-specification.yaml' + }, + exposeRoute: true + } + + t.plan(4) + const fastify = Fastify() + fastify.register(fastifySwagger, config) // check that yaml is there fastify.inject( @@ -74,18 +147,19 @@ test('swagger route returns yaml', t => { } ) }) -test('swagger route returns json', t => { - t.plan(2) - const fastify = Fastify() - fastify.register(fastifySwagger, { +test('swagger route returns json', t => { + const config = { mode: 'static', specification: { - type: 'file', - path: './examples/example-static-specification.yaml' + path: './examples/example-static-specification.json' }, exposeRoute: true - }) + } + + t.plan(4) + const fastify = Fastify() + fastify.register(fastifySwagger, config) // check that json is there fastify.inject( @@ -95,22 +169,20 @@ test('swagger route returns json', t => { }, (err, res) => { t.error(err) - + t.is(typeof res.payload, 'string') + t.is(res.headers['content-type'], 'application/json; charset=utf-8') try { - var payload = JSON.parse(res.payload) - t.matchSnapshot(JSON.stringify(payload, null, 2)) - } catch (error) { - t.fail(error) + yaml.safeLoad(res.payload) + t.pass('valid swagger json') + } catch (err) { + t.fail(err) } } ) }) test('postProcessor works, swagger route returns updated yaml', t => { - t.plan(5) - const fastify = Fastify() - - fastify.register(fastifySwagger, { + const config = { mode: 'static', specification: { path: './examples/example-static-specification.yaml', @@ -120,7 +192,11 @@ test('postProcessor works, swagger route returns updated yaml', t => { } }, exposeRoute: true - }) + } + + t.plan(5) + const fastify = Fastify() + fastify.register(fastifySwagger, config) // check that yaml is there fastify.inject( @@ -142,10 +218,8 @@ test('postProcessor works, swagger route returns updated yaml', t => { } ) }) -test('swagger route returns explicitly passed doc', t => { - t.plan(3) - const fastify = Fastify() +test('swagger route returns explicitly passed doc', t => { const document = { info: { title: 'Test swagger', @@ -153,13 +227,18 @@ test('swagger route returns explicitly passed doc', t => { version: '0.1.0' } } - fastify.register(fastifySwagger, { + + const config = { mode: 'static', specification: { document }, exposeRoute: true - }) + } + + t.plan(3) + const fastify = Fastify() + fastify.register(fastifySwagger, config) // check that json is there fastify.inject( @@ -182,8 +261,6 @@ test('swagger route returns explicitly passed doc', t => { }) test('/documentation/:file should serve static file from the location of main specification file', t => { - t.plan(7) - const config = { exposeRoute: true, mode: 'static', @@ -191,6 +268,8 @@ test('/documentation/:file should serve static file from the location of main sp path: './examples/example-static-specification.yaml' } } + + t.plan(7) const fastify = new Fastify() fastify.register(fastifySwagger, config) @@ -227,8 +306,6 @@ test('/documentation/:file should serve static file from the location of main sp }) test('/documentation/non-existing-file calls custom NotFoundHandler', t => { - t.plan(2) - const config = { exposeRoute: true, mode: 'static', @@ -236,6 +313,8 @@ test('/documentation/non-existing-file calls custom NotFoundHandler', t => { path: './examples/example-static-specification.yaml' } } + + t.plan(2) const fastify = new Fastify() fastify.register(fastifySwagger, config) fastify.setNotFoundHandler((request, reply) => { @@ -252,8 +331,6 @@ test('/documentation/non-existing-file calls custom NotFoundHandler', t => { }) test('/documentation/:file should be served from custom location', t => { - t.plan(3) - const config = { exposeRoute: true, mode: 'static', @@ -262,6 +339,8 @@ test('/documentation/:file should be served from custom location', t => { baseDir: resolve(__dirname, '..', 'static') } } + + t.plan(3) const fastify = new Fastify() fastify.register(fastifySwagger, config) @@ -279,8 +358,6 @@ test('/documentation/:file should be served from custom location', t => { }) test('/documentation/:file should be served from custom location with trailing slash(es)', t => { - t.plan(3) - const config = { exposeRoute: true, mode: 'static', @@ -289,6 +366,8 @@ test('/documentation/:file should be served from custom location with trailing s baseDir: resolve(__dirname, '..', 'static') + '/' } } + + t.plan(3) const fastify = new Fastify() fastify.register(fastifySwagger, config) @@ -304,3 +383,299 @@ test('/documentation/:file should be served from custom location with trailing s ) }) }) + +test('/documentation/yaml returns cache.swaggerString on second request in static mode', t => { + const config = { + mode: 'static', + specification: { + path: './examples/example-static-specification.yaml' + }, + exposeRoute: true + } + + t.plan(8) + const fastify = Fastify() + fastify.register(fastifySwagger, config) + + fastify.inject( + { + method: 'GET', + url: '/documentation/yaml' + }, + (err, res) => { + t.error(err) + t.is(typeof res.payload, 'string') + t.is(res.headers['content-type'], 'application/x-yaml') + try { + yaml.safeLoad(res.payload) + t.pass('valid swagger yaml') + } catch (err) { + t.fail(err) + } + } + ) + + fastify.inject( + { + method: 'GET', + url: '/documentation/yaml' + }, + (err, res) => { + t.error(err) + t.is(typeof res.payload, 'string') + t.is(res.headers['content-type'], 'application/x-yaml') + yaml.safeLoad(res.payload) + t.pass('valid swagger yaml') + } + ) +}) + +test('/documentation/json returns cache.swaggerObject on second request in static mode', t => { + const config = { + mode: 'static', + specification: { + path: './examples/example-static-specification.json' + }, + exposeRoute: true + } + + t.plan(8) + const fastify = Fastify() + fastify.register(fastifySwagger, config) + + fastify.inject( + { + method: 'GET', + url: '/documentation/json' + }, + (err, res) => { + t.error(err) + t.is(typeof res.payload, 'string') + t.is(res.headers['content-type'], 'application/json; charset=utf-8') + t.pass('valid swagger json') + } + ) + + fastify.inject( + { + method: 'GET', + url: '/documentation/json' + }, + (err, res) => { + t.error(err) + t.is(typeof res.payload, 'string') + t.is(res.headers['content-type'], 'application/json; charset=utf-8') + t.pass('valid swagger json') + } + ) +}) + +test('/documentation/yaml returns cache.swaggerString on second request in dynamic mode', t => { + const config = { + specification: { + path: './examples/example-static-specification.yaml' + }, + exposeRoute: true + } + + t.plan(8) + const fastify = Fastify() + fastify.register(fastifySwagger, config) + + fastify.inject( + { + method: 'GET', + url: '/documentation/yaml' + }, + (err, res) => { + t.error(err) + t.is(typeof res.payload, 'string') + t.is(res.headers['content-type'], 'application/x-yaml') + try { + yaml.safeLoad(res.payload) + t.pass('valid swagger yaml') + } catch (err) { + t.fail(err) + } + } + ) + + fastify.inject( + { + method: 'GET', + url: '/documentation/yaml' + }, + (err, res) => { + t.error(err) + t.is(typeof res.payload, 'string') + t.is(res.headers['content-type'], 'application/x-yaml') + try { + yaml.safeLoad(res.payload) + t.pass('valid swagger yaml') + } catch (err) { + t.fail(err) + } + } + ) +}) + +test('/documentation/json returns cache.swaggerObject on second request in dynamic mode', t => { + const config = { + specification: { + path: './examples/example-static-specification.json' + }, + exposeRoute: true + } + + t.plan(8) + const fastify = Fastify() + fastify.register(fastifySwagger, config) + + fastify.inject( + { + method: 'GET', + url: '/documentation/json' + }, + (err, res) => { + t.error(err) + t.is(typeof res.payload, 'string') + t.is(res.headers['content-type'], 'application/json; charset=utf-8') + t.pass('valid swagger json') + } + ) + + fastify.inject( + { + method: 'GET', + url: '/documentation/json' + }, + (err, res) => { + t.error(err) + t.is(typeof res.payload, 'string') + t.is(res.headers['content-type'], 'application/json; charset=utf-8') + t.pass('valid swagger json') + } + ) +}) + +test('swagger routes are not exposed', t => { + const config = { + mode: 'static', + specification: { + path: './examples/example-static-specification.json' + }, + exposeRoute: false + } + + t.plan(4) + const fastify = Fastify() + fastify.register(fastifySwagger, config) + + // check that yaml is there + fastify.inject( + { + method: 'GET', + url: '/documentation/json' + }, + (err, res) => { + t.error(err) + t.is(typeof res.payload, 'string') + t.is(res.headers['content-type'], 'application/json; charset=utf-8') + t.pass('routes are not exposed') + } + ) +}) + +test('inserts default opts in fastifySwagger', t => { + t.plan(1) + const fastify = Fastify() + const next = () => {} + + fastify.register(() => (fastifySwagger(fastify, null, next))) + + fastify.ready(() => { + t.pass('Inserted default option for fastifySwagger.') + }) +}) + +test('inserts default package name', t => { + const config = { + mode: 'dynamic', + specification: { + path: './examples/example-static-specification.json' + }, + exposeRoute: true + } + + t.plan(2) + const fastify = Fastify() + fastify.register(fastifySwagger, config) + + const originalPathJoin = path.join + const testPackageJSON = path.join(__dirname, '../examples/test-package.json') + + path.join = (...args) => { + if (args[1] === 'package.json') { + return testPackageJSON + } + return originalPathJoin(...args) + } + + fastify.inject( + { + method: 'GET', + url: '/documentation/json' + }, + (err, res) => { + t.error(err) + t.pass('Inserted default package name.') + } + ) +}) + +test('throws an error if cannot parse package\'s JSON', t => { + const config = { + mode: 'dynamic', + specification: { + path: './examples/example-static-specification.json' + }, + exposeRoute: true + } + + t.plan(2) + const fastify = Fastify() + fastify.register(fastifySwagger, config) + + const originalPathJoin = path.join + const testPackageJSON = path.join(__dirname, '') + + path.join = (...args) => { + if (args[1] === 'package.json') { + return testPackageJSON + } + return originalPathJoin(...args) + } + + fastify.inject( + { + method: 'GET', + url: '/documentation/json' + }, + (err, res) => { + t.error(err) + t.equal(err, null) + } + ) +}) + +test('inserts default opts in fastifySwaggerDynamic (dynamic.js)', t => { + t.plan(1) + const fastify = Fastify() + const next = () => {} + + fastify.register(() => (fastifySwaggerDynamic(fastify, null, next))) + + fastify.ready(() => { + t.pass('Inserted default option for fastifySwagger.') + }) +})