From 599af1038395076139cf08955d87c98eec45fe9f Mon Sep 17 00:00:00 2001 From: Antonio Pintus Date: Mon, 8 May 2017 17:07:01 +0200 Subject: [PATCH] fix(templates): improved test templates, ESLinting improved test templates and skeletons, improved code, now using ESLint with AirBnB JavaScript style --- .eslintrc.json | 14 + index.js | 82 ++-- package.json | 13 +- src/lib/common/commonTests.js | 377 +++++++++--------- src/lib/common/listing.js | 22 - .../templates/DeleteCollectionSkeleton.js | 11 +- src/lib/common/templates/DeleteOneSkeleton.js | 17 +- .../common/templates/GetCollectionSkeleton.js | 19 +- src/lib/common/templates/GetOneSkeleton.js | 17 +- .../templates/PostCollectionSkeleton.js | 21 +- src/lib/common/templates/PostOneSkeleton.js | 15 +- .../common/templates/PutCollectionSkeleton.js | 13 +- src/lib/common/templates/PutOneSkeleton.js | 23 +- src/lib/common/templates/skeleton.js | 54 ++- src/lib/common/templates/snippets.js | 14 +- src/lib/generator.js | 163 ++++---- src/lib/parser.js | 151 +++---- src/model/api.js | 61 +-- src/utils/cmd.js | 8 +- src/utils/filesystem.js | 33 +- test/testWriteFile.js | 10 +- 21 files changed, 577 insertions(+), 561 deletions(-) create mode 100644 .eslintrc.json delete mode 100644 src/lib/common/listing.js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..97b886a --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "extends": "airbnb-base", + "plugins": [ + "import", + "mocha" + ], + + "rules": { + "no-console": "off", + "no-restricted-syntax": "off", + "no-shadow": ["error", { "allow": ["done", "resolve", "reject", "done", "cb", "callback", "err", "req", "res"] }], + "mocha/no-exclusive-tests": "error" + } +} \ No newline at end of file diff --git a/index.js b/index.js index 8c8f504..a0ad7d3 100644 --- a/index.js +++ b/index.js @@ -1,59 +1,57 @@ #!/usr/bin/env node -//API-PIKI command line tool +// API-PIKI command line tool const program = require('commander'); const generator = require('./src/lib/generator'); const path = require('path'); const fs = require('fs'); +let specFile; + program .usage('[options] ') .version(require('./package.json').version) .arguments('') - .action( spec => { - specFile = spec; + .action((spec) => { + specFile = spec; }) - .option('-o, --out [outDir]', 'Output directory for generated test files. Default: ./test/api-swag') - .option('-f, --forceAuth', 'Force generation of authenticated tests using Basic Authentication, even if spec doesn\'t declare it. In this case be sure to set USERNAME and USERPASSWD env variables when running tests') + .option('-o, --out [outDir]', 'Output directory for generated test files. Default: ./test/api-swag') + .option('-f, --forceAuth', 'Force generation of authenticated tests using Basic Authentication, even if spec doesn\'t declare it. In this case be sure to set USERNAME and USERPASSWD env variables when running tests') .parse(process.argv); - if(typeof specFile === 'undefined') { - console.log('\n Nothing to do here... API specification file / URL not provided'); - process.exit(1); - } else { - let swaggerPath = ( specFile.startsWith('http:') || specFile.startsWith('https:') ) ? specFile : `file://${path.resolve(specFile)}`; - if( swaggerPath.startsWith('file:') && !fs.existsSync(path.resolve(specFile)) ) { - console.log('\n Specified file doesn\'t exist. Please check it.\n'); - process.exit(1); - } - console.log(`\nGenerating API tests for ${specFile}`); - console.log(`Target directory is ${program.out ? program.out : '(DEFAULTING to) ./api-swag'}`); - if(program.forceAuth) { - console.log('Basic Authorization forcing enabled.'); - } - - generator.run(swaggerPath, program.out, {forcedAuth: program.forceAuth ? true : false}) - .then( files => { - console.log('\nGenerated files:'); - for( f of files ) { - console.log(f); - } - console.log(`${program.out ? program.out : process.cwd()+'/api-swag'}/package.json`); - console.log('\n'); - console.log('HOW TO run tests:'); - console.log('1. (optional) if APIs are authenticated through Basic Authentication (or -f flag is used), set USERNAME and USERPASSWD env variables'); - console.log(`2. cd ${program.out ? program.out : './api-swag'} && npm i && [USERNAME= USERPASSWD=] npm test\n`); - console.log('Example: USERNAME=james USERPASSWD=my$trongPa$$word npm test\n'); - - } ) - .catch( err => { - console.error(err); - process.exit(1); - }); - - } +if (typeof specFile === 'undefined') { + console.log('\n Nothing to do here... API specification file / URL not provided'); + process.exit(1); +} else { + const swaggerPath = (specFile.startsWith('http:') || specFile.startsWith('https:')) ? specFile : `file://${path.resolve(specFile)}`; + if (swaggerPath.startsWith('file:') && !fs.existsSync(path.resolve(specFile))) { + console.log('\n Specified file doesn\'t exist. Please check it.\n'); + process.exit(1); + } + console.log(`\nGenerating API tests for ${specFile}`); + console.log(`Target directory is ${program.out ? program.out : '(DEFAULTING to) ./api-swag'}`); + if (program.forceAuth) { + console.log('Basic Authorization forcing enabled.'); + } + generator.run(swaggerPath, program.out, { forcedAuth: Boolean(program.forceAuth) }) + .then((files) => { + console.log('\nGenerated files:'); + for (const f of files) { + console.log(f); + } + const altPath = `${process.cwd()}/api-swag`; + console.log(`${program.out ? program.out : altPath}/package.json`); + console.log('\n'); + console.log('HOW TO run tests:'); + console.log('1. (optional) if APIs are authenticated through Basic Authentication (or -f flag is used), set USERNAME and USERPASSWD env variables'); + console.log(`2. cd ${program.out ? program.out : './api-swag'} && npm i && [USERNAME= USERPASSWD=] npm test\n`); + console.log('Example: USERNAME=james USERPASSWD=my$trongPa$$word npm test\n'); + }) + .catch((err) => { + console.error(err); + process.exit(1); + }); +} - - \ No newline at end of file diff --git a/package.json b/package.json index 86867b0..de29297 100644 --- a/package.json +++ b/package.json @@ -47,18 +47,25 @@ "commitizen": "2.9.6", "coveralls": "^2.13.1", "cz-conventional-changelog": "2.0.0", + "eslint": "^3.19.0", + "eslint-config-airbnb-base": "^11.1.3", + "eslint-plugin-import": "^2.2.0", + "eslint-plugin-mocha": "^4.9.0", "husky": "^0.13.3", "mocha": "^3.3.0", - "nyc": "^10.3.0", + "nyc": "^10.3.2", "rewire": "^2.5.2", - "semantic-release": "^6.3.2", + "semantic-release": "^6.3.6", "supertest": "^3.0.0" }, "dependencies": { + "chai": "^3.5.0", "commander": "^2.9.0", "debug": "^2.6.6", + "jsonpolice": "^5.2.0", "lodash": "^4.17.4", - "request": "^2.81.0" + "request": "^2.81.0", + "supertest": "^3.0.0" }, "config": { "commitizen": { diff --git a/src/lib/common/commonTests.js b/src/lib/common/commonTests.js index c3168c0..402ba2c 100644 --- a/src/lib/common/commonTests.js +++ b/src/lib/common/commonTests.js @@ -1,252 +1,243 @@ -//API-Piki Common Tests - +// API-Piki Common Tests +/* global describe it before after*/ const should = require('chai').should(); const supertest = require('supertest'); const jsonpolice = require('jsonpolice'); +const debug = require('debug')('API-Piki:CommonTests'); -var getEndpoint = resourceName => `/${resourceName.toLowerCase()}s`; +const getEndpoint = resourceName => `/${resourceName.toLowerCase()}s`; exports.getEndpoint = getEndpoint; -exports.getBasicAuthCredentials = () => ({ - username: process.env.USERNAME, - userpasswd: process.env.USERPASSWD +exports.getBasicAuthCredentials = () => ({ + username: process.env.USERNAME, + userpasswd: process.env.USERPASSWD, }); -//future developments ;) -exports.fetchJSONSchema = (schemasURL, resourceName) => { - return new Promise((resolve, reject) => { - debug(`schemasURL: ${schemasURL}, resourceName: ${resourceName}`); - const request = supertest(schemasURL+'/'); - jsonpolice.create(resourceName, { - retriever: function retriever(url) { - return new Promise((resolve, reject) => { - request - .get(url) - .expect('Content-Type', /json/) - .end((err, res) => { - //console.log(err, res); - if(err) return reject(err); - else resolve(res.body); - }); - }); - } - }) - .then(schema => { - return resolve(schema); - }) - .catch(err => { - return reject(err); - }); - }); -}; +// Future developments ;) +exports.fetchJSONSchema = (schemasURL, resourceName) => new Promise((resolve, reject) => { + debug(`schemasURL: ${schemasURL}, resourceName: ${resourceName}`); + const request = supertest(`${schemasURL}/`); + jsonpolice.create(resourceName, { + retriever: function retriever(url) { + return new Promise((resolve, reject) => { + request + .get(url) + .expect('Content-Type', /json/) + .end((err, res) => { + if (err) return reject(err); + return resolve(res.body); + }); + }); + }, + }) + .then(schema => resolve(schema)) + .catch(err => reject(err)); +}); -//This function calls a GET to retrieve a list of the given resource name and tests status, content and its type. -exports.testJSONListing = ({baseURL, endpointPath, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd'}) => { - if (!endpointPath) throw new Error('endpoint path not provided. It is mandatory.'); - if (!baseURL) throw new Error('Base URL not provided'); - const request = supertest(baseURL); - console.log(`Starting testJSONListing common tests for ${baseURL}${endpointPath}`); - describe(`GET ${endpointPath}`, function(){ - it('should be OK and always respond with a JSON', function(done){ - let req = request.get(`${endpointPath}`); - if(isAuthenticated){ - req.auth(username, userpasswd); - } - req.set('Accept', 'application/json') +// calls a GET to retrieve a list of the given resource name and tests status, content and its type. +exports.testJSONListing = ({ baseURL, endpointPath, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd' }) => { + if (!endpointPath) throw new Error('endpoint path not provided. It is mandatory.'); + if (!baseURL) throw new Error('Base URL not provided'); + const request = supertest(baseURL); + console.log(`Starting testJSONListing common tests for ${baseURL}${endpointPath}`); + describe(`GET ${endpointPath}`, () => { + it('should be OK and always respond with a JSON', (done) => { + const req = request.get(`${endpointPath}`); + if (isAuthenticated) { + req.auth(username, userpasswd); + } + req.set('Accept', 'application/json') .expect((res) => { - if(res.status !== 200 && res.status !== 204) throw new Error(`Expected response status was 200 or 204, got ${res.status} instead`) + if (res.status !== 200 && res.status !== 204) throw new Error(`Expected response status was 200 or 204, got ${res.status} instead`); }) .expect('Content-Type', /json/, done); - }); }); + }); }; -//This function performs some repetitive tests about Basic Authentication against provided endpoints -exports.testAuthentication = ( {baseURL, method = 'get', endpoint, username = 'test_user', userpasswd = 'test_passw0rd'} ) => { - if (!endpoint) throw new Error('Endpoint URL not provided. It must be provided as "/path[/:id]"'); - if (!baseURL) throw new Error('Base URL not provided'); - const request = supertest(baseURL); - - console.log(`Starting authentication common tests for ${method.toUpperCase()} ${baseURL}${endpoint}`); +// Performs some repetitive tests about Basic Authentication against provided endpoints +exports.testAuthentication = ({ baseURL, method = 'get', endpoint, username = 'test_user', userpasswd = 'test_passw0rd' }) => { + if (!endpoint) throw new Error('Endpoint URL not provided. It must be provided as "/path[/:id]"'); + if (!baseURL) throw new Error('Base URL not provided'); + const request = supertest(baseURL); - describe(`Calling ${method.toUpperCase()} ${endpoint}`, function(){ + console.log(`Starting authentication common tests for ${method.toUpperCase()} ${baseURL}${endpoint}`); - it('with no authentication should return a 401', function(done){ - request[method](endpoint) + describe(`Calling ${method.toUpperCase()} ${endpoint}`, () => { + it('with no authentication should return a 401', (done) => { + request[method](endpoint) .set('Accept', 'application/json') .expect(401) .expect('Content-Type', /json/, done); - }); + }); - it('with wrong username, should return a 401', function(done){ - request[method](endpoint) - .auth(username + Math.ceil(Math.random()*1000), userpasswd) + it('with wrong username, should return a 401', (done) => { + request[method](endpoint) + .auth(username + Math.ceil(Math.random() * 1000), userpasswd) .set('Accept', 'application/json') .expect(401) .expect('Content-Type', /json/, done); - }); + }); - it('with wrong password, should return a 401', function(done){ - request[method](endpoint) - .auth(username, userpasswd + Math.ceil(Math.random()*1000)) + it('with wrong password, should return a 401', (done) => { + request[method](endpoint) + .auth(username, userpasswd + Math.ceil(Math.random() * 1000)) .set('Accept', 'application/json') .expect(401) .expect('Content-Type', /json/) .end((err, res) => { - if(err) return done(err); - else{ - done(); - } + if (err) return done(err); + debug(res); + return done(); }); - }); }); + }); }; -//This function tests 404 Not Found errors for a given endpoint generating fake Ids -exports.testNotValidIds = ( {baseURL, method = 'get', endpoint, body = {}, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd'} ) => { - if (!endpoint) throw new Error('Endpoint URL not provided. It must be provided as "/path[:id]"'); - if (!baseURL) throw new Error('Base URL not provided'); - if (!body && (method.toLowerCase()==='post' || method.toLowerCase()==='put')) throw new Error('Body not provided for a PUT or a POST'); - - const request = supertest(baseURL); - const cleanEndpoint = endpoint.endsWith('/') ? endpoint.slice(0, endpoint.length-1) : endpoint; - console.log(`Starting NotFound common tests for ${method.toUpperCase()} ${baseURL}${cleanEndpoint}`); - - describe(`Calling ${method.toUpperCase()} ${cleanEndpoint}/:id`, function(){ - for (let i=1;i<10;i++){ - let fakeId = `${Math.ceil(Math.random()*i)}VVC${Math.random().toString(36).substr(2, 5).repeat(Math.random()*10)}`; - it(`with a wrong, fake :id --> ${fakeId} should return a 404`, function(done){ - let apiRequest = request[method](cleanEndpoint+'/'+fakeId); - if(isAuthenticated) apiRequest.auth(username, userpasswd); - apiRequest +// Tests 404 Not Found errors for a given endpoint generating fake Ids +exports.testNotValidIds = ({ baseURL, method = 'get', endpoint, body = {}, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd' }) => { + if (!endpoint) throw new Error('Endpoint URL not provided. It must be provided as "/path[:id]"'); + if (!baseURL) throw new Error('Base URL not provided'); + if (!body && (method.toLowerCase() === 'post' || method.toLowerCase() === 'put')) throw new Error('Body not provided for a PUT or a POST'); + + const request = supertest(baseURL); + const cleanEndpoint = endpoint.endsWith('/') ? endpoint.slice(0, endpoint.length - 1) : endpoint; + console.log(`Starting NotFound common tests for ${method.toUpperCase()} ${baseURL}${cleanEndpoint}`); + + describe(`Calling ${method.toUpperCase()} ${cleanEndpoint}/:id`, () => { + for (let i = 1; i < 10; i += 1) { + const fakeId = `${Math.ceil(Math.random() * i)}VVC${Math.random().toString(36).substr(2, 5).repeat(Math.random() * 10)}`; + it(`with a wrong, fake :id --> ${fakeId} should return a 404`, (done) => { + const apiRequest = request[method](`${cleanEndpoint}/${fakeId}`); + if (isAuthenticated) apiRequest.auth(username, userpasswd); + apiRequest .set('Accept', 'application/json') - .expect(404) - //CHECK the next expectation to match content-type - //.expect('Content-Type', /json/); - if (method.toLowerCase()==='post' || method.toLowerCase()==='put'){ - apiRequest.send(body); - } - apiRequest.end(function(err, res){ - if(err) done(err); - else { - should.exist(res.body); - res.body.should.be.an('object'); - let errorBody = res.body; - errorBody.should.be.ok; - //VALIDATE the error body response - done(); - } - }); - });//-- it - }//-- for - }); + .expect(404); + if (method.toLowerCase() === 'post' || method.toLowerCase() === 'put') { + apiRequest.send(body); + } + apiRequest.end((err, res) => { + if (err) done(err); + else { + should.exist(res.body); + res.body.should.be.an('object'); + const errorBody = res.body; + // eslint-disable-next-line + errorBody.should.be.ok; + // VALIDATE the error body response + done(); + } + }); + });// -- it + }// -- for + }); }; -//Specifying inexistent fields in path, it must not return lists of resources with empty objects -exports.testNoEmptyObjsInLists = ( {baseURL, endpoint, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd'} ) => { - if (!endpoint) throw new Error('Endpoint URL not provided. It must be provided as "/path"'); - if (!baseURL) throw new Error('Base URL not provided'); - const request = supertest(baseURL); - console.log(`Starting noEmptyObjects in lists common tests for GET ${baseURL}${endpoint}`); - describe(`Calling GET ${endpoint}?fields=RandomFieldName`, function(){ - it('with not existing fields should not return empty objects', function(done){ - let req = request.get(`${endpoint}?fields=${Math.random().toString(36).substr(2, 5)}`); - if(isAuthenticated){ - req.auth(username, userpasswd); - } - req.set('Accept', 'application/json') +// Specifying inexistent fields in path, it must not return lists of resources with empty objects +exports.testNoEmptyObjsInLists = ({ baseURL, endpoint, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd' }) => { + if (!endpoint) throw new Error('Endpoint URL not provided. It must be provided as "/path"'); + if (!baseURL) throw new Error('Base URL not provided'); + const request = supertest(baseURL); + console.log(`Starting noEmptyObjects in lists common tests for GET ${baseURL}${endpoint}`); + describe(`Calling GET ${endpoint}?fields=RandomFieldName`, () => { + it('with not existing fields should not return empty objects', (done) => { + const req = request.get(`${endpoint}?fields=${Math.random().toString(36).substr(2, 5)}`); + if (isAuthenticated) { + req.auth(username, userpasswd); + } + req.set('Accept', 'application/json') .expect(200) .expect('Content-Type', /json/) - .end(function(err, res){ - if(err) done(err); - else { - should.exist(res.body); - res.body.should.be.an('array'); - let list = res.body; - for(let obj of list){ - obj.should.be.ok; - obj.should.not.be.empty; - } - done(); + .end((err, res) => { + if (err) done(err); + else { + should.exist(res.body); + res.body.should.be.an('array'); + const list = res.body; + for (const obj of list) { + /* eslint-disable */ + obj.should.be.ok; + obj.should.not.be.empty; + /* eslint-enable */ } - }); - }); + done(); + } + }); }); + }); }; -//This function tests the DELETE operation for an endpoint given a valid body format. -exports.testDelete = ({baseURL, endpoint, body = {}, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd'}) => { - if (!endpoint) throw new Error('Endpoint path name not provided. It is mandatory.'); - if (!baseURL) throw new Error('Base URL not provided'); - const request = supertest(baseURL); - console.log(`Starting testDelete common tests for DELETE ${baseURL}${endpoint}`); - let validID; - - describe(`DELETE ${endpoint}`, function(){ - - before('Create a Resource', function(done){ - let req = request.post(endpoint.replace(/{.+}/, '')); - if(isAuthenticated) req.auth(username, userpasswd); - req.send(body) +// Tests the DELETE operation for an endpoint given a valid body format. +exports.testDelete = ({ baseURL, endpoint, body = {}, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd' }) => { + if (!endpoint) throw new Error('Endpoint path name not provided. It is mandatory.'); + if (!baseURL) throw new Error('Base URL not provided'); + const request = supertest(baseURL); + console.log(`Starting testDelete common tests for DELETE ${baseURL}${endpoint}`); + let validID; + + describe(`DELETE ${endpoint}`, () => { + before('Create a Resource', (done) => { + const req = request.post(endpoint.replace(/{.+}/, '')); + if (isAuthenticated) req.auth(username, userpasswd); + req.send(body) .set('Accept', 'application/json') .set('Content-Type', 'application/json') .expect(201) .expect('Content-Type', /json/) - .end(function(err, res){ - if(err) done(err); - else { - should.exist(res.body); - res.body.should.be.an('object'); - if (res.body.hasOwnProperty('id')){ - validID = res.body.id; - } else { - validID = res.body._id; - } - done(); + .end((err, res) => { + if (err) done(err); + else { + should.exist(res.body); + res.body.should.be.an('object'); + if (Object.prototype.hasOwnProperty.call(res.body, 'id')) { + validID = res.body.id; + } else { + // eslint-disable-next-line + validID = res.body._id; } + done(); + } }); - }); + }); - it('should return 200 OK and the Resource should be removed', function(done){ - //DELETE - let req = request.delete(`${endpoint.replace(/{.+}/, validID)}`); - if(isAuthenticated) req.auth(username, userpasswd); - req.set('Accept', 'application/json') + it('should return 200 OK and the Resource should be removed', (done) => { + // DELETE + const req = request.delete(`${endpoint.replace(/{.+}/, validID)}`); + if (isAuthenticated) req.auth(username, userpasswd); + req.set('Accept', 'application/json') .expect(200) - .end(function(err, res){ - //Checking the delete - let req = request.get(`${endpoint.replace(/{.+}/, validID)}`) - if(isAuthenticated) req.auth(username, userpasswd); - req.set('Accept', 'application/json') + .end(() => { + // Checking the delete + const req = request.get(`${endpoint.replace(/{.+}/, validID)}`); + if (isAuthenticated) req.auth(username, userpasswd); + req.set('Accept', 'application/json') .expect(404) .expect('Content-Type', /json/) - .end(function(err, res){ - if (err) return done(err); - else { - should.not.exist(err); - done(); - } - }); - }); - }); - }); -}; - -//this function tests generic 404 Not Found errors for GETs against a list of fake resources -exports.test404ForFakeResources = ({baseURL, fakeResources = ['aDummyResource','anotherBrickIntheWall','fakeOrDieResource'], isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd'}) => { - const request = supertest(baseURL); - describe('Getting a fake resource name', function(){ - for(let r of fakeResources){ - it(`like ${baseURL}/${r} should return a 404`, function(done){ - let req = request.get(`/${r}`); - if(isAuthenticated) req.auth(username, userpasswd); - req.expect(404, done); - }); - } + .end((err) => { + if (err) return done(err); + should.not.exist(err); + return done(); + }); + }); }); + }); +}; + +// this function tests generic 404 Not Found errors for GETs against a list of fake resources +exports.test404ForFakeResources = ({ baseURL, fakeResources = ['aDummyResource', 'anotherBrickIntheWall', 'fakeOrDieResource'], isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd' }) => { + const request = supertest(baseURL); + describe('Getting a fake resource name', () => { + for (const r of fakeResources) { + it(`like ${baseURL}/${r} should return a 404`, (done) => { + const req = request.get(`/${r}`); + if (isAuthenticated) req.auth(username, userpasswd); + req.expect(404, done); + }); + } + }); }; diff --git a/src/lib/common/listing.js b/src/lib/common/listing.js deleted file mode 100644 index 0f83074..0000000 --- a/src/lib/common/listing.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Common mocha + chai/should test for listing resources through a generic GET /resources - */ - -exports.testJSONListing = ( ({baseURL, path, body = {}, username = 'test_user', userpasswd = 'test_passw0rd'}) => { - if (!resourceName) throw new Error('Resource name not provided. It is mandatory. E.g.: Book, User, Application'); - if (!baseURL) throw new Error('Base URL not provided'); - const request = supertest(baseURL); - - describe(`GET ${path}`, function(){ - it('with valid auth, should be OK and always respond with a JSON object', function(done){ - request - .get(`${path}`) - .auth(username, userpasswd) - .set('Accept', 'application/json') - .expect((res) => { - if(res.status !== 200 && res.status !== 204) throw new Error(`Expected response status was 200 or 204, got ${res.status} instead`) - }) - .expect('Content-Type', /json/, done); - }); - }); -}); diff --git a/src/lib/common/templates/DeleteCollectionSkeleton.js b/src/lib/common/templates/DeleteCollectionSkeleton.js index 166e496..5a099c8 100644 --- a/src/lib/common/templates/DeleteCollectionSkeleton.js +++ b/src/lib/common/templates/DeleteCollectionSkeleton.js @@ -1,12 +1,11 @@ -const { +const { skipComment, requirements, - commonAuthTest, - getBasicAuthCredentials + commonAuthTest, + getBasicAuthCredentials, } = require('./snippets'); -exports.getTest = ({baseURL, method = 'get', endpointPath, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd', isError=false, statusCode=200, description="WRITE YOUR TEST CASE DESCRIPTION HERE"}) => { - return `/** +exports.getTest = ({ baseURL, method = 'get', endpointPath, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd' }) => `/** * Generated test skeleton for ${isAuthenticated ? 'an authenticated' : ''} ${method.toUpperCase()} ${endpointPath} */ ${skipComment} @@ -22,4 +21,4 @@ ${isAuthenticated ? `${commonAuthTest(baseURL, method, endpointPath, username, u -})`}; //end skeleton +})`; // end skeleton diff --git a/src/lib/common/templates/DeleteOneSkeleton.js b/src/lib/common/templates/DeleteOneSkeleton.js index 804383b..bf72a89 100644 --- a/src/lib/common/templates/DeleteOneSkeleton.js +++ b/src/lib/common/templates/DeleteOneSkeleton.js @@ -1,14 +1,13 @@ const { - skipComment, - commonAuthTest, - requirements, + skipComment, + commonAuthTest, + requirements, testNotValidIdsTest, deleteTest, - getBasicAuthCredentials + getBasicAuthCredentials, } = require('./snippets'); -exports.getTest = ({baseURL, method = 'get', endpointPath, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd', isError=false, statusCode=200, description="WRITE YOUR TEST CASE DESCRIPTION HERE"}) => { - return `/** +exports.getTest = ({ baseURL, method = 'get', endpointPath, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd' }) => `/** * Generated test skeleton for ${isAuthenticated ? 'an authenticated' : ''} ${method.toUpperCase()} ${endpointPath} */ @@ -16,7 +15,7 @@ ${skipComment} ${requirements(baseURL)} ${getBasicAuthCredentials()} describe('DELETE ${endpointPath}', function() { -${isAuthenticated ? `${commonAuthTest(baseURL, method, endpointPath.replace(/{.+}/, 'aSuperFakeID', username, userpasswd ))}` : ''} +${isAuthenticated ? `${commonAuthTest(baseURL, method, endpointPath.replace(/{.+}/, 'aSuperFakeID', username, userpasswd))}` : ''} //TODO: Edit the following variable setting it to a valid resource body @@ -25,7 +24,7 @@ const validResourceBody = { }; describe.skip('${method.toUpperCase()} ${endpointPath}', function(){ ${testNotValidIdsTest(baseURL, method, endpointPath.replace(/{.+}/, ''), {}, isAuthenticated, username, userpasswd)} - ${deleteTest( baseURL, endpointPath, "validResourceBody", isAuthenticated, username, userpasswd )} + ${deleteTest(baseURL, endpointPath, "validResourceBody", isAuthenticated, username, userpasswd)} }); -});`}; //end skeleton +});`; // end skeleton diff --git a/src/lib/common/templates/GetCollectionSkeleton.js b/src/lib/common/templates/GetCollectionSkeleton.js index e6d8797..b56d889 100644 --- a/src/lib/common/templates/GetCollectionSkeleton.js +++ b/src/lib/common/templates/GetCollectionSkeleton.js @@ -1,17 +1,16 @@ const { - skipComment, - okSnippet, - errorSnippet, + skipComment, + okSnippet, + errorSnippet, getBasicAuthCredentials, - commonAuthTest, - requirements, - jsonListingTest, + commonAuthTest, + requirements, + jsonListingTest, notFoundForFakeTest, - testNoEmptyObjsInListsTest + testNoEmptyObjsInListsTest, } = require('./snippets'); -exports.getTest = ({baseURL, method = 'get', endpointPath, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd', isError=false, statusCode=200, description="WRITE YOUR TEST CASE DESCRIPTION HERE"}) => { - return `/** +exports.getTest = ({ baseURL, method = 'get', endpointPath, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd', isError = false, statusCode = 200, description = 'WRITE YOUR TEST CASE DESCRIPTION HERE' }) => `/** * Generated test skeleton for ${isAuthenticated ? 'an authenticated' : ''} ${method.toUpperCase()} ${endpointPath} */ @@ -38,4 +37,4 @@ describe.skip('${method.toUpperCase()} ${endpointPath}', function(){ }); }); }); -});`}; //end skeleton +});`; // end skeleton diff --git a/src/lib/common/templates/GetOneSkeleton.js b/src/lib/common/templates/GetOneSkeleton.js index 7738eaf..b55aec6 100644 --- a/src/lib/common/templates/GetOneSkeleton.js +++ b/src/lib/common/templates/GetOneSkeleton.js @@ -1,15 +1,14 @@ const { - skipComment, - okSnippet, + skipComment, + okSnippet, errorSnippet, - getBasicAuthCredentials, - commonAuthTest, + getBasicAuthCredentials, + commonAuthTest, requirements, - testNotValidIdsTest + testNotValidIdsTest, } = require('./snippets'); -exports.getTest = ({baseURL, method = 'get', endpointPath, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd', isError=false, statusCode=200, description="WRITE YOUR TEST CASE DESCRIPTION HERE"}) => { - return `/** +exports.getTest = ({ baseURL, method = 'get', endpointPath, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd', isError = false, statusCode = 200 }) => `/** * Generated test skeleton for ${isAuthenticated ? 'an authenticated' : ''} ${method.toUpperCase()} ${endpointPath} */ @@ -17,7 +16,7 @@ ${skipComment} ${requirements(baseURL)} ${getBasicAuthCredentials()} describe('GET ${endpointPath}', function() { -${isAuthenticated ? `${commonAuthTest(baseURL, method, endpointPath.replace(/{.+}/, 'aSuperFakeID'), username, userpasswd )}` : ''} +${isAuthenticated ? `${commonAuthTest(baseURL, method, endpointPath.replace(/{.+}/, 'aSuperFakeID'), username, userpasswd)}` : ''} ${testNotValidIdsTest(baseURL, method, endpointPath.replace(/{.+}/, ''), {}, isAuthenticated, username, userpasswd)} describe.skip('${method.toUpperCase()} ${endpointPath} for an existing resource', function(){ @@ -40,4 +39,4 @@ describe.skip('${method.toUpperCase()} ${endpointPath} for an existing resource' }); }); }); -});`}; //end skeleton +});`; // end skeleton diff --git a/src/lib/common/templates/PostCollectionSkeleton.js b/src/lib/common/templates/PostCollectionSkeleton.js index 0cf211b..358129f 100644 --- a/src/lib/common/templates/PostCollectionSkeleton.js +++ b/src/lib/common/templates/PostCollectionSkeleton.js @@ -1,14 +1,13 @@ const { - skipComment, - okSnippet, - errorSnippet, + skipComment, + okSnippet, + errorSnippet, getBasicAuthCredentials, - commonAuthTest, - requirements, + commonAuthTest, + requirements, } = require('./snippets'); -exports.getTest = ({baseURL, method = 'get', endpointPath, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd', isError=false, statusCode=200, description="WRITE YOUR TEST CASE DESCRIPTION HERE"}) => { - return `/** +exports.getTest = ({ baseURL, method = 'get', endpointPath, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd' }) => `/** * Generated test skeleton for ${isAuthenticated ? 'an authenticated' : ''} ${method.toUpperCase()} ${endpointPath} */ @@ -16,7 +15,7 @@ ${skipComment} ${requirements(baseURL)} ${getBasicAuthCredentials()} describe('POST ${endpointPath}', function() { -${isAuthenticated ? `${commonAuthTest(baseURL, method, endpointPath, username, userpasswd )}` : ''} +${isAuthenticated ? `${commonAuthTest(baseURL, method, endpointPath, username, userpasswd)}` : ''} describe.skip('POST Test Suite', function() { @@ -29,7 +28,7 @@ describe.skip('POST Test Suite', function() { }; request .post('${endpointPath}')${isAuthenticated ? ` - .auth(username, userpasswd)`:''} + .auth(username, userpasswd)` : ''} .set('Accept', 'application/json') .send(resourceData) .expect(400) @@ -57,7 +56,7 @@ describe.skip('POST Test Suite', function() { }; request .post('${endpointPath}')${isAuthenticated ? ` - .auth(username, userpasswd)`:''} + .auth(username, userpasswd)` : ''} .set('Accept', 'application/json') .send(resourceData) .expect(201) @@ -80,4 +79,4 @@ describe.skip('POST Test Suite', function() { }); -});`}; //end skeleton +});`; // end skeleton diff --git a/src/lib/common/templates/PostOneSkeleton.js b/src/lib/common/templates/PostOneSkeleton.js index 42b22bd..11f04a2 100644 --- a/src/lib/common/templates/PostOneSkeleton.js +++ b/src/lib/common/templates/PostOneSkeleton.js @@ -1,19 +1,18 @@ const { - skipComment, - commonAuthTest, - requirements, - getBasicAuthCredentials + skipComment, + commonAuthTest, + requirements, + getBasicAuthCredentials, } = require('./snippets'); -exports.getTest = ({baseURL, method = 'get', endpointPath, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd', isError=false, statusCode=200, description="WRITE YOUR TEST CASE DESCRIPTION HERE"}) => { - return `/** +exports.getTest = ({ baseURL, method = 'get', endpointPath, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd' }) => `/** * Generated test skeleton for ${isAuthenticated ? 'an authenticated' : ''} ${method.toUpperCase()} ${endpointPath} */ ${skipComment} ${requirements(baseURL)} ${getBasicAuthCredentials()} describe('POST ${endpointPath}', function() { -${isAuthenticated ? `${commonAuthTest(baseURL, method, endpointPath.replace(/{.+}/, 'aSuperFakeID', username, userpasswd ))}` : ''} +${isAuthenticated ? `${commonAuthTest(baseURL, method, endpointPath.replace(/{.+}/, 'aSuperFakeID', username, userpasswd))}` : ''} //the following test case is incomplete and can be used as a template to write new cases describe.skip('${method.toUpperCase()} ${endpointPath}', function(){ @@ -22,4 +21,4 @@ describe.skip('${method.toUpperCase()} ${endpointPath}', function(){ -});`}; //end skeleton +});`; // end skeleton diff --git a/src/lib/common/templates/PutCollectionSkeleton.js b/src/lib/common/templates/PutCollectionSkeleton.js index fcb1736..c963e37 100644 --- a/src/lib/common/templates/PutCollectionSkeleton.js +++ b/src/lib/common/templates/PutCollectionSkeleton.js @@ -1,12 +1,11 @@ -const { - skipComment, - commonAuthTest, +const { + skipComment, + commonAuthTest, requirements, - getBasicAuthCredentials + getBasicAuthCredentials, } = require('./snippets'); -exports.getTest = ({baseURL, method = 'get', endpointPath, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd', isError=false, statusCode=200, description="WRITE YOUR TEST CASE DESCRIPTION HERE"}) => { - return `/** +exports.getTest = ({ baseURL, method = 'get', endpointPath, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd' }) => `/** * Generated test skeleton for ${isAuthenticated ? 'an authenticated' : ''} ${method.toUpperCase()} ${endpointPath} */ ${skipComment} @@ -20,4 +19,4 @@ describe.skip('${method.toUpperCase()} ${endpointPath}', function(){ it('TBD, write test case in the context of your PUT ${endpointPath} semantics'); }); -});`}; //end skeleton +});`; // end skeleton diff --git a/src/lib/common/templates/PutOneSkeleton.js b/src/lib/common/templates/PutOneSkeleton.js index 9d33a01..f7efa02 100644 --- a/src/lib/common/templates/PutOneSkeleton.js +++ b/src/lib/common/templates/PutOneSkeleton.js @@ -1,22 +1,21 @@ const { - skipComment, - okSnippet, - errorSnippet, + skipComment, + okSnippet, + errorSnippet, getBasicAuthCredentials, - commonAuthTest, - requirements, - testNotValidIdsTest + commonAuthTest, + requirements, + testNotValidIdsTest, } = require('./snippets'); -exports.getTest = ({baseURL, method = 'get', endpointPath, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd', isError=false, statusCode=200, description="WRITE YOUR TEST CASE DESCRIPTION HERE"}) => { - return `/** +exports.getTest = ({ baseURL, method = 'get', endpointPath, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd' }) => `/** * Generated test skeleton for ${isAuthenticated ? 'an authenticated' : ''} ${method.toUpperCase()} ${endpointPath} */ ${skipComment} ${requirements(baseURL)} ${getBasicAuthCredentials()} describe('DELETE ${endpointPath}', function() { -${isAuthenticated ? `${commonAuthTest(baseURL, method, endpointPath.replace(/{.+}/, 'aSuperFakeID', username, userpasswd ))}` : ''} +${isAuthenticated ? `${commonAuthTest(baseURL, method, endpointPath.replace(/{.+}/, 'aSuperFakeID', username, userpasswd))}` : ''} //the following test case is incomplete and can be used as a template to write new cases @@ -40,7 +39,7 @@ describe.skip('PUT Test Suite', function() { }; request .put(\`\${"${endpointPath}".replace(/{.+}/, existingID)}\`)${isAuthenticated ? ` - .auth(username, userpasswd)`:''} + .auth(username, userpasswd)` : ''} .set('Accept', 'application/json') .send(resourceData) .expect(400) @@ -77,7 +76,7 @@ describe.skip('PUT Test Suite', function() { }; request .put(\`\${"${endpointPath}".replace(/{.+}/, existingID)}\`)${isAuthenticated ? ` - .auth(username, userpasswd)`:''} + .auth(username, userpasswd)` : ''} .set('Accept', 'application/json') .send(resourceData) .expect(200) @@ -97,4 +96,4 @@ describe.skip('PUT Test Suite', function() { }); -});`}; //end skeleton +});`; // end skeleton diff --git a/src/lib/common/templates/skeleton.js b/src/lib/common/templates/skeleton.js index 257c529..dbe0110 100644 --- a/src/lib/common/templates/skeleton.js +++ b/src/lib/common/templates/skeleton.js @@ -1,20 +1,40 @@ +const gcs = require('./GetCollectionSkeleton'); +const gos = require('./GetOneSkeleton'); +const pcs = require('./PostCollectionSkeleton'); +const pos = require('./PostOneSkeleton'); +const putcs = require('./PutCollectionSkeleton'); +const putos = require('./PutOneSkeleton'); +const dcs = require('./DeleteCollectionSkeleton'); +const dos = require('./DeleteOneSkeleton'); + const skeletons = { - getCollection: require('./GetCollectionSkeleton'), - getOne: require('./GetOneSkeleton'), - postCollection: require('./PostCollectionSkeleton'), - postOne: require('./PostOneSkeleton'), - putCollection: require('./PutCollectionSkeleton'), - putOne: require('./PutOneSkeleton'), - deleteCollection: require('./DeleteCollectionSkeleton'), - deleteOne: require('./DeleteOneSkeleton') + getCollection: gcs, + getOne: gos, + postCollection: pcs, + postOne: pos, + putCollection: putcs, + putOne: putos, + deleteCollection: dcs, + deleteOne: dos, +}; + +exports.getTest = ({ baseURL, method = 'get', endpointPath, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd', isError = false, statusCode = 200, description = 'WRITE YOUR TEST CASE DESCRIPTION HERE' }) => { + let resourceName = endpointPath.split('/').join('-'); + if (resourceName.endsWith('-')) resourceName = resourceName.slice(0, resourceName.length - 1); + const skeletonName = (resourceName.match(/{.+}/) !== null) ? `${method.toLowerCase()}One` : `${method.toLowerCase()}Collection`; + return { + name: `test-${method.toUpperCase()}${resourceName}`, + skeleton: skeletons[skeletonName].getTest({ + baseURL, + method, + endpointPath, + isAuthenticated, + username, + userpasswd, + isError, + statusCode, + description, + }), + }; }; -exports.getTest = ( {baseURL, method='get', endpointPath, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd', isError=false, statusCode=200, description='WRITE YOUR TEST CASE DESCRIPTION HERE'} ) => { - let resourceName = endpointPath.split('/').join('-'); - if(resourceName.endsWith('-')) resourceName = resourceName.slice(0, resourceName.length-1); - const skeletonName = (resourceName.match(/{.+}/) !== null) ? `${method.toLowerCase()}One` : `${method.toLowerCase()}Collection`; - return { - name: `test-${method.toUpperCase()}${resourceName}`, - skeleton: skeletons[skeletonName].getTest({baseURL, method, endpointPath, isAuthenticated, username, userpasswd, isError, statusCode, description}) - }; -} diff --git a/src/lib/common/templates/snippets.js b/src/lib/common/templates/snippets.js index 819620a..fcd065f 100644 --- a/src/lib/common/templates/snippets.js +++ b/src/lib/common/templates/snippets.js @@ -25,20 +25,20 @@ const errorSnippet = ` should.exist(res.body); `; -const getBasicAuthCredentials = () => `const { username, userpasswd } = common.getBasicAuthCredentials();`; -const commonAuthTest = (baseURL, method, endpointPath, username, userpasswd) => `common.testAuthentication( {baseURL: "${baseURL}", method: "${method}", endpoint: "${endpointPath}", username: username, userpasswd: userpasswd});`; +const getBasicAuthCredentials = () => 'const { username, userpasswd } = common.getBasicAuthCredentials();'; +const commonAuthTest = (baseURL, method, endpointPath) => `common.testAuthentication( {baseURL: "${baseURL}", method: "${method}", endpoint: "${endpointPath}", username: username, userpasswd: userpasswd});`; -const requirements = (baseURL) => ` +const requirements = baseURL => ` const should = require('chai').should(); const supertest = require('supertest'); const common = require('./commonTests'); const request = supertest('${baseURL}'); `; -const jsonListingTest = ( baseURL, endpointPath, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd' ) => `common.testJSONListing({baseURL: "${baseURL}", endpointPath: "${endpointPath}", isAuthenticated: ${isAuthenticated}, username: username, userpasswd: userpasswd });`; -const notFoundForFakeTest = ( baseURL, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd' ) => `common.test404ForFakeResources({baseURL: "${baseURL}", fakeResources: ['aDummyResource','anotherBrickIntheWall','fakeOrDieResource'], isAuthenticated: ${isAuthenticated}, username: username, userpasswd: userpasswd });`; -const testNoEmptyObjsInListsTest = ( baseURL, endpointPath, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd' ) => `common.testNoEmptyObjsInLists( { baseURL: "${baseURL}", endpoint: "${endpointPath}", isAuthenticated: ${isAuthenticated}, username: username, userpasswd: userpasswd } );`; -const testNotValidIdsTest = ( baseURL, method='get', endpointPath, body={}, isAuthenticated=false, username = 'test_user', userpasswd = 'test_passw0rd' ) => `common.testNotValidIds( { baseURL: "${baseURL}", method: "${method}", endpoint: "${endpointPath}", isAuthenticated: ${isAuthenticated}, username: username, userpasswd: userpasswd } );`; +const jsonListingTest = (baseURL, endpointPath, isAuthenticated = false) => `common.testJSONListing({baseURL: "${baseURL}", endpointPath: "${endpointPath}", isAuthenticated: ${isAuthenticated}, username: username, userpasswd: userpasswd });`; +const notFoundForFakeTest = (baseURL, isAuthenticated = false) => `common.test404ForFakeResources({baseURL: "${baseURL}", fakeResources: ['aDummyResource','anotherBrickIntheWall','fakeOrDieResource'], isAuthenticated: ${isAuthenticated}, username: username, userpasswd: userpasswd });`; +const testNoEmptyObjsInListsTest = (baseURL, endpointPath, isAuthenticated = false) => `common.testNoEmptyObjsInLists( { baseURL: "${baseURL}", endpoint: "${endpointPath}", isAuthenticated: ${isAuthenticated}, username: username, userpasswd: userpasswd } );`; +const testNotValidIdsTest = (baseURL, method = 'get', endpointPath, body = {}, isAuthenticated = false) => `common.testNotValidIds( { baseURL: "${baseURL}", method: "${method}", endpoint: "${endpointPath}", isAuthenticated: ${isAuthenticated}, username: username, userpasswd: userpasswd } );`; const deleteTest = ( baseURL, endpointPath, body = {}, isAuthenticated = false, username = 'test_user', userpasswd = 'test_passw0rd' ) => `common.testDelete = ( {baseURL: "${baseURL}" , endpoint: "${endpointPath}", body:${body}, isAuthenticated: ${isAuthenticated}, username: username, userpasswd: userpasswd } );` module.exports = { diff --git a/src/lib/generator.js b/src/lib/generator.js index 18b7755..399a30e 100644 --- a/src/lib/generator.js +++ b/src/lib/generator.js @@ -1,4 +1,3 @@ -const API = require('../model/api'); const parser = require('./parser').parser; const fs = require('fs'); const path = require('path'); @@ -6,95 +5,103 @@ const skeleton = require('./common/templates/skeleton'); const getCredentials = require('../utils/cmd').getBasicAuthCredentials; const debug = require('debug')('API-SWAG:Generator'); -const commonTestsFileName = `commonTests.js`; +const commonTestsFileName = 'commonTests.js'; const commonTestsDir = `${__dirname}/common`; -const sketch = (parsedSwag, outputDir=`${process.cwd()}/api-swag`, options = {}) => { - let api = parsedSwag.api; - let tasks = []; - for (ep of api.endpoints) { - for (m of ep.methods) { - //current version supports BASIC Authentication only - const isAuth = options.forcedAuth || m.authSchemes.map( a => Object.values(a)[0] ).includes('basic'); - const { username, userpasswd } = getCredentials(); - tasks.push( _writeToFile(skeleton.getTest({ baseURL: `${api.schemes[0]}://${api.host}${api.basePath}`, endpointPath: ep.path, method: m.operation, isAuthenticated: isAuth, username: username, userpasswd: userpasswd }), outputDir) ); - } - }; - tasks.push(_copyCommonTests( {toDir: outputDir} )); - return Promise.all(tasks) - .then(values => { - debug('results:', values); - console.log(`${values.length} test file${values.length > 1 || values.length < 1 ? 's':''} created.`); - return values; +function createWorkingDir(dirPath) { + const dir = path.normalize(dirPath); + if (!fs.existsSync(dir)) { + debug('Creating dir: ', dir); + fs.mkdirSync(dir); + } +} + +function createPackageJSON(toDir = 'api-swag') { + const pack = { + name: 'API-PIKI-generated-tests', + version: '1.1.0', + description: 'API tests generated by API-PIKI package, edit them as required', + license: 'MIT', + scripts: { + test: './node_modules/mocha/bin/mocha ./*.js --reporter=spec --no-timeouts', + }, + devDependencies: { + chai: '^3.5.0', + mocha: '^3.2.0', + nyc: '^10.1.2', + rewire: '^2.5.2', + supertest: '^3.0.0', + debug: '^2.6.1', + jsonpolice: 'latest', + }, + }; + createWorkingDir(toDir); + return fs.writeFileSync(path.normalize(`${toDir}/package.json`), JSON.stringify(pack)); +} + +function copyCommonTests({ srcFile = `${commonTestsDir}/${commonTestsFileName}`, toDir = 'api-swag' } = {}) { + const src = path.normalize(srcFile); + const dest = path.normalize(`${toDir}/${commonTestsFileName}`); + debug(`copying ${src} to ${dest}`); + return new Promise((resolve, reject) => { + const writeStream = fs.createWriteStream(dest); + writeStream.on('error', err => reject(err)); + writeStream.on('finish', () => { + debug('All writes are now complete.'); + resolve(dest); }); + fs.createReadStream(src).pipe(writeStream); + }); +} + +function writeToFile(testSkeleton, outputDir = `${process.cwd()}/api-swag`) { + return new Promise((resolve, reject) => { + const file = path.normalize(`${outputDir}/${testSkeleton.name}.js`); + debug('writing file: ', file); + createWorkingDir(outputDir); + fs.writeFile(file, testSkeleton.skeleton, err => (err ? reject(err) : resolve(file))); + }); +} + +const sketch = (parsedSwag, outputDir = `${process.cwd()}/api-swag`, options = {}) => { + const api = parsedSwag.api; + const tasks = []; + for (const ep of api.endpoints) { + for (const m of ep.methods) { + // current version supports BASIC Authentication only + const isAuth = options.forcedAuth || m.authSchemes.map(a => Object.values(a)[0]).includes('basic'); + const { username, userpasswd } = getCredentials(); + tasks.push(writeToFile(skeleton.getTest({ baseURL: `${api.schemes[0]}://${api.host}${api.basePath}`, endpointPath: ep.path, method: m.operation, isAuthenticated: isAuth, username: username, userpasswd: userpasswd }), outputDir)); + } + } + tasks.push(copyCommonTests({ toDir: outputDir })); + return Promise.all(tasks) + .then((values) => { + debug('results:', values); + console.log(`${values.length} test file${values.length > 1 || values.length < 1 ? 's' : ''} created.`); + return values; + }); }; exports.sketch = sketch; -exports.run = (swaggerFile, outputDir=`${process.cwd()}/api-swag`, options = {}) => { - debug('Running parser...'); - createPackageJSON(outputDir); - return parser.parse(swaggerFile) - .then( api => sketch(api, outputDir, {forcedAuth} = options) ) - .catch( err => { - console.dir(err); - }); +exports.run = (swaggerFile, outputDir = `${process.cwd()}/api-swag`, options = {}) => { + debug('Running parser...'); + createPackageJSON(outputDir); + return parser.parse(swaggerFile) + .then((api) => { + const { forcedAuth } = options; + return sketch(api, outputDir, forcedAuth); + }) + .catch((err) => { + console.dir(err); + }); }; -function _writeToFile(testSkeleton, outputDir=`${process.cwd()}/api-swag`) { - return new Promise( (resolve, reject) => { - const file = path.normalize(`${outputDir}/${testSkeleton.name}.js`) - debug('writing file: ', file); - _createWorkingDir(outputDir); - fs.writeFile(file, testSkeleton.skeleton, (err, written, string) => err ? reject(err) : resolve(file)); - }); -} -function _createWorkingDir(dirPath) { - const dir = path.normalize(dirPath); - if (!fs.existsSync(dir)) { - debug('Creating dir: ', dir); - fs.mkdirSync(dir); - } -} -function _copyCommonTests( { srcFile=`${commonTestsDir}/${commonTestsFileName}`, toDir = 'api-swag' } = {}) { - const src = path.normalize(srcFile); - const dest = path.normalize(`${toDir}/${commonTestsFileName}`); - debug(`copying ${src} to ${dest}`); - return new Promise((resolve, reject) => { - let writeStream = fs.createWriteStream(dest); - writeStream.on('error', err => reject(err)); - writeStream.on('finish', () => { - debug('All writes are now complete.'); - resolve(dest); - }); - fs.createReadStream(src).pipe(writeStream); - }); -}; -function createPackageJSON(toDir = 'api-swag') { - let pack = { - name: 'API-PIKI-generated-tests', - version: '1.0.0', - description: 'API tests generated by API-PIKI package, edit them as required', - license: 'MIT', - scripts: { - test: './node_modules/mocha/bin/mocha ./*.js --reporter=spec --no-timeouts' - }, - devDependencies: { - chai: "^3.5.0", - mocha: "^3.2.0", - nyc: "^10.1.2", - rewire: "^2.5.2", - supertest: "^3.0.0", - debug: "^2.6.1", - jsonpolice: "latest" - } - }; - _createWorkingDir(toDir); - return fs.writeFileSync(path.normalize(`${toDir}/package.json`), JSON.stringify(pack)); -} + exports.createPackageJSON = createPackageJSON; diff --git a/src/lib/parser.js b/src/lib/parser.js index 5147fc4..397f26e 100644 --- a/src/lib/parser.js +++ b/src/lib/parser.js @@ -2,84 +2,89 @@ const url = require('url'); const fs = require('fs'); const request = require('request'); const _ = require('lodash'); -const {API, Endpoint, Method} = require('../model/api'); +const { API, Endpoint, Method } = require('../model/api'); const debug = require('debug')('API-SWAG:parser'); -exports.parser = { - parse(url) { return _parse(url); } -}; -const _parse = (swaggerURL) => { - try { - let sourceURL = new url.URL(swaggerURL); - return (sourceURL.protocol === 'http:' || sourceURL.protocol === 'https:') ? _fetchFromWeb(sourceURL) : _fetchFromFile(sourceURL); - } catch (error) { - return Promise.reject(error); +const toAPIObj = (swagData) => { + const api = new API({ + host: swagData.host, + basePath: swagData.basePath, + info: swagData.info, + schemes: swagData.schemes, + }); + const endpoints = _.mapValues(swagData.paths, (path) => { + const methods = []; + for (const op of Object.keys(path)) { + methods.push(new Method({ + operation: op, + params: path[op].parameters ? + path[op].parameters.map((p) => { + if (p.$ref && p.$ref.startsWith('#')) { + return swagData.parameters[p.$ref.split('/').pop()]; + } + return p; + }).filter(p => p.in === 'path') : [], + queryParams: path[op].parameters ? + path[op].parameters.map((p) => { + if (p.$ref && p.$ref.startsWith('#')) { + return swagData.parameters[p.$ref.split('/').pop()]; + } + return p; + }).filter(p => p.in === 'query') : [], + responses: path[op].responses, + authSchemes: (path[op].security) ? + (path[op].security.map((s) => { + const secKey = Object.keys(s).pop(); + return (swagData.securityDefinitions[secKey]) ? + { [secKey]: swagData.securityDefinitions[secKey].type } : + null; + }).filter(p => p !== null)) + : [], + })); } + return methods; + }); + for (const ep in endpoints) { + if (ep) api.addEndpoint(new Endpoint(ep, endpoints[ep])); + } + debug('Parsed API', api); + return api; }; -const _fetchFromWeb = (url) => { - return new Promise((resolve, reject) => { - request({url: url}, (err, res, body) => { - if(err) reject(err); - else (res.statusCode === 200) ? resolve({ original: JSON.parse(body), api: _toAPIObj(JSON.parse(body)) }) : reject(new Error(res.statusMessage)); - }); + + +const fetchFromWeb = specURL => + new Promise((resolve, reject) => { + request({ url: specURL }, (err, res, body) => { + if (err) reject(err); + else if (res.statusCode === 200) { + resolve({ original: JSON.parse(body), api: toAPIObj(JSON.parse(body)) }); + } else reject(new Error(res.statusMessage)); + }); }); -}; -const _fetchFromFile = (filePath) => { - return new Promise((resolve, reject) => { - fs.readFile(filePath, { encoding: 'utf8' }, + +const fetchFromFile = filePath => new Promise((resolve, reject) => { + fs.readFile(filePath, { encoding: 'utf8' }, (error, data) => { - if (error) { - reject(error); - } else { - resolve( { original: JSON.parse(data), api: _toAPIObj(JSON.parse(data)) }); - } - }); - }); + if (error) { + reject(error); + } else { + resolve({ original: JSON.parse(data), api: toAPIObj(JSON.parse(data)) }); + } + }); +}); + +const parseSpec = (swaggerURL) => { + try { + const sourceURL = new url.URL(swaggerURL); + return (sourceURL.protocol === 'http:' || sourceURL.protocol === 'https:') ? fetchFromWeb(sourceURL) : fetchFromFile(sourceURL); + } catch (error) { + return Promise.reject(error); + } }; -const _toAPIObj = (swagData) => { - let api = new API( {host: swagData.host, basePath: swagData.basePath , info: swagData.info, schemes: swagData.schemes} ); - let endpoints = _.mapValues(swagData.paths, (path, k, o) => { - let methods = []; - for (op of Object.keys(path)) { - methods.push( new Method( { operation: op, - params: path[op].parameters ? - path[op].parameters.map( p => { - if(p.$ref && p.$ref.startsWith('#')){ - return swagData.parameters[p.$ref.split('/').pop()]; - } else { - return p; - } - }).filter( p => { - return p.in === 'path'; - }) : [], - queryParams: path[op].parameters ? - path[op].parameters.map( p => { - if(p.$ref && p.$ref.startsWith('#')){ - return swagData.parameters[p.$ref.split('/').pop()]; - } else { - return p; - } - }).filter( p => { - return p.in === 'query'; - }) : [], - responses: path[op].responses, - authSchemes: (path[op].security) ? - (path[op].security.map( s => { - let secKey = Object.keys(s).pop(); - return (swagData.securityDefinitions[secKey]) ? - {[secKey]: swagData.securityDefinitions[secKey].type} : - null; - } ).filter( p => p!==null)) - : [] - } )); - }; - return methods; - }); - for (ep in endpoints){ - api.addEndpoint(new Endpoint(ep, endpoints[ep])); - } - debug('Parsed API', api); - //console.dir(api, {colors: true, depth: null}); - return api; -} \ No newline at end of file + + + +exports.parser = { + parse(specURL) { return parseSpec(specURL); }, +}; diff --git a/src/model/api.js b/src/model/api.js index 40956c1..20ef5d4 100644 --- a/src/model/api.js +++ b/src/model/api.js @@ -1,39 +1,40 @@ + class Endpoint { - constructor(path, methods=[]){ - if (!Array.isArray(methods)) throw new Error('methods param in Endpoint constructor must be an array'); - this.path = path; - this.methods = methods - } -}; + constructor(path, methods = []) { + if (!Array.isArray(methods)) throw new Error('methods param in Endpoint constructor must be an array'); + this.path = path; + this.methods = methods; + } +} class Method { - constructor({operation = 'get', params={}, queryParams = {}, request={}, responses={}, authSchemes = [{basic: 'basic'}]}){ - this.operation = operation; - this.params = params; - this.queryParams = queryParams; - this.request = request; - this.responses = responses; - this.authSchemes = authSchemes; - } -}; + constructor({ operation = 'get', params = {}, queryParams = {}, request = {}, responses = {}, authSchemes = [{ basic: 'basic' }] }) { + this.operation = operation; + this.params = params; + this.queryParams = queryParams; + this.request = request; + this.responses = responses; + this.authSchemes = authSchemes; + } +} class API { - constructor({host, basePath, info, schemes}){ - this.host = host; - this.basePath = basePath; - this.info = info; - this.endpoints = []; - this.schemes = schemes; - - } + constructor({ host, basePath, info, schemes }) { + this.host = host; + this.basePath = basePath; + this.info = info; + this.endpoints = []; + this.schemes = schemes; + } - addEndpoint(e){ - this.endpoints.push(e); - }; + addEndpoint(e) { + this.endpoints.push(e); + } } module.exports = { - Endpoint, - API, - Method -} \ No newline at end of file + Endpoint, + API, + Method, +}; + diff --git a/src/utils/cmd.js b/src/utils/cmd.js index b140e0c..cb8ca61 100644 --- a/src/utils/cmd.js +++ b/src/utils/cmd.js @@ -1,4 +1,4 @@ -exports.getBasicAuthCredentials = () => ({ - username: process.env.USERNAME, - userpasswd: process.env.USERPASSWD -}); \ No newline at end of file +exports.getBasicAuthCredentials = () => ({ + username: process.env.USERNAME, + userpasswd: process.env.USERPASSWD, +}); diff --git a/src/utils/filesystem.js b/src/utils/filesystem.js index 56cb26a..95adb5d 100644 --- a/src/utils/filesystem.js +++ b/src/utils/filesystem.js @@ -1,23 +1,26 @@ const fs = require('fs'); -let rmdir = dirPath => { - try { var files = fs.readdirSync(dirPath); } - catch(e) { return; } - if (files.length > 0) - for (var i = 0; i < files.length; i++) { - var filePath = dirPath + '/' + files[i]; - if (fs.statSync(filePath).isFile()) fs.unlinkSync(filePath); - else rmdir(filePath); +const rmdir = (dirPath) => { + let files; + try { + files = fs.readdirSync(dirPath); + } catch (e) { return; } + if (files.length > 0) { + for (let i = 0; i < files.length; i += 1) { + const filePath = `${dirPath}/${files[i]}`; + if (fs.statSync(filePath).isFile()) fs.unlinkSync(filePath); + else rmdir(filePath); } - fs.rmdirSync(dirPath); + } + fs.rmdirSync(dirPath); }; -let countFiles = dirPath => { - let files = fs.readdirSync(dirPath); - return files.length; +const countFiles = (dirPath) => { + const files = fs.readdirSync(dirPath); + return files.length; }; module.exports = { - rmdir, - countFiles -} + rmdir, + countFiles, +}; diff --git a/test/testWriteFile.js b/test/testWriteFile.js index eec97d4..913b4ad 100644 --- a/test/testWriteFile.js +++ b/test/testWriteFile.js @@ -7,7 +7,7 @@ const rmdir = require('../src/utils/filesystem').rmdir; describe('writing a skeleton to file', function() { it('should result in creating the file at the right path', function(done) { let testGen = rewire('../src/lib/generator'); - let writeToFile = testGen.__get__("_writeToFile"); + let writeToFile = testGen.__get__("writeToFile"); writeToFile({ name: 'GETFakeThings', skeleton: 'const info = "this is a test skeleton";' @@ -24,7 +24,7 @@ describe('writing a skeleton to file', function() { it('should fail in creating a wrong content', function(done) { let testGen = rewire('../src/lib/generator'); - let writeToFile = testGen.__get__("_writeToFile"); + let writeToFile = testGen.__get__("writeToFile"); const file = path.normalize(`${process.cwd()}/api-swag/FAKE.js`) fs.writeFileSync(file, 'just a fake file'); @@ -52,7 +52,7 @@ describe('writing a skeleton to file', function() { const dir = path.normalize(`${process.cwd()}/api-swag`); rmdir(dir); let testGen = rewire('../src/lib/generator'); - let writeToFile = testGen.__get__("_writeToFile"); + let writeToFile = testGen.__get__("writeToFile"); writeToFile({ name: 'GETFakeThings', skeleton: 'const info = "this is a test skeleton";' @@ -72,7 +72,7 @@ describe('writing a skeleton to file', function() { describe('Copying commonTests.js', function() { it('should result in copying the default file to /api-swag', function(done) { let testGen = rewire('../src/lib/generator'); - let copyCommonTests = testGen.__get__("_copyCommonTests"); + let copyCommonTests = testGen.__get__("copyCommonTests"); copyCommonTests() .then(() => { fs.existsSync(path.normalize(`${process.cwd()}/api-swag/commonTests.js`)).should.be.true; @@ -91,7 +91,7 @@ describe('Copying commonTests.js', function() { describe('Copying commonTests.js', function() { it('for to a restricted destination should result in an Error', function(done) { let testGen = rewire('../src/lib/generator'); - let copyCommonTests = testGen.__get__("_copyCommonTests"); + let copyCommonTests = testGen.__get__("copyCommonTests"); copyCommonTests( {toDir: './test/FAKE-TEST-DIR'} ) .then(dest => { done();