From 8f8fe3e78e67302465af02c9fa3b85e0ee6cabb5 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Mon, 1 Jul 2019 10:43:11 -0700 Subject: [PATCH 01/27] CLI Versioning WIP: - Added prompts package - Functionality check versioning against main repo --- lib/prompts.js | 23 +++++++++++++++++++++++ lib/swagger.js | 28 +++++++++++++++++++++++++--- package-lock.json | 7 ++----- package.json | 3 ++- 4 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 lib/prompts.js diff --git a/lib/prompts.js b/lib/prompts.js new file mode 100644 index 000000000..7eea7aa7c --- /dev/null +++ b/lib/prompts.js @@ -0,0 +1,23 @@ +exports.generatePrompts = (versionList) => [ + { + type: 'select', + name: 'option', + message: 'We couldn\'t find a version in ReadMe matching the version in your OAS file. Would you like to use an existing version or create a new one?', + choices: [ + { title: 'Use existing', value: 'update' }, + { title: 'Create a new version', value: 'create' } + ], + }, + { + type: prev => prev === 'update' ? 'select' : null, + name: 'versionSelection', + message: 'Select your desired version', + choices: versionList.map(v => { + return { + title: v.version, + // eslint-disable-next-line + value: v._id + }; + }), + } +]; diff --git a/lib/swagger.js b/lib/swagger.js index cffb8e1a4..e1db84d48 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -2,12 +2,26 @@ const request = require('request-promise-native'); const fs = require('fs'); const path = require('path'); const config = require('config'); +const prompts = require('prompts'); +const promptOpts = require('./prompts'); exports.desc = 'Upload your swagger file to ReadMe'; exports.category = 'services'; exports.weight = 2; -exports.run = function({ args, opts }) { +async function versionPrompt(versionList, specPath) { + const promptSelection = promptOpts.generatePrompts(versionList); + const res = await prompts(promptSelection); + + if (res.option === 'create') { + callApi(specPath, res.option); + } else if (res.option === 'update') { + const versionId = res.versionSelection; + callApi(specPath, res.option, versionId); + } +} + +exports.run = async function({ args, opts }) { let { key, id } = opts; if (!key && opts.token) { @@ -21,7 +35,7 @@ exports.run = function({ args, opts }) { return Promise.reject(new Error('No api key provided. Please use --key')); } - function callApi(specPath) { + function callApi(specPath, versionAction, version) { function success(data) { const message = !id ? "You've successfully uploaded a new swagger file to your ReadMe project!" @@ -45,7 +59,11 @@ exports.run = function({ args, opts }) { function error(err) { try { - return Promise.reject(new Error(JSON.parse(err.error).description)); + const errorDesc = JSON.parse(err.error).description; + if (typeof errorDesc === 'object') { + return Promise.resolve(versionPrompt(errorDesc, specPath)); + } + return Promise.reject(new Error(errorDesc)); } catch (e) { return Promise.reject(new Error('There was an error uploading!')); } @@ -57,6 +75,10 @@ exports.run = function({ args, opts }) { }, auth: { user: key }, resolveWithFullResponse: true, + headers: { + versionAction, + version + } }; // Create diff --git a/package-lock.json b/package-lock.json index f068b7e8c..abbd60f59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5458,8 +5458,7 @@ "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" }, "lazy-cache": { "version": "1.0.4", @@ -6678,7 +6677,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.1.0.tgz", "integrity": "sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg==", - "dev": true, "requires": { "kleur": "^3.0.2", "sisteransi": "^1.0.0" @@ -7288,8 +7286,7 @@ "sisteransi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", - "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==", - "dev": true + "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==" }, "slash": { "version": "2.0.0", diff --git a/package.json b/package.json index 9d7d0cfa2..af93dbe2e 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,10 @@ "gray-matter": "^4.0.1", "isemail": "^3.1.3", "minimist": "^1.2.0", + "oas": "0.8.15", "opn": "^6.0.0", + "prompts": "^2.1.0", "read": "^1.0.7", - "oas": "0.8.15", "request": "^2.88.0", "request-promise-native": "^1.0.5" }, From 210c6a9a46b9f83c80438ec6775d81ba8834fb2a Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Mon, 1 Jul 2019 17:02:18 -0700 Subject: [PATCH 02/27] CLI updates for version API. WIP --- lib/prompts.js | 34 +++++++++++++++++++++----- lib/swagger.js | 31 ++++++++++++------------ lib/versions/create.js | 55 ++++++++++++++++++++++++++++++++++++++++++ lib/versions/delete.js | 35 +++++++++++++++++++++++++++ lib/versions/list.js | 29 ++++++++++++++++++++++ lib/versions/update.js | 0 test/prompts.test.js | 17 +++++++++++++ 7 files changed, 179 insertions(+), 22 deletions(-) create mode 100644 lib/versions/create.js create mode 100644 lib/versions/delete.js create mode 100644 lib/versions/list.js create mode 100644 lib/versions/update.js create mode 100644 test/prompts.test.js diff --git a/lib/prompts.js b/lib/prompts.js index 7eea7aa7c..cc0b8dad4 100644 --- a/lib/prompts.js +++ b/lib/prompts.js @@ -1,23 +1,45 @@ -exports.generatePrompts = (versionList) => [ +exports.generatePrompts = versionList => [ { type: 'select', name: 'option', - message: 'We couldn\'t find a version in ReadMe matching the version in your OAS file. Would you like to use an existing version or create a new one?', + message: + "We couldn't find a version in ReadMe matching the version in your OAS file. Would you like to use an existing version or create a new one?", choices: [ { title: 'Use existing', value: 'update' }, - { title: 'Create a new version', value: 'create' } + { title: 'Create a new version', value: 'create' }, ], }, { - type: prev => prev === 'update' ? 'select' : null, + type: prev => (prev === 'update' ? 'select' : null), name: 'versionSelection', message: 'Select your desired version', choices: versionList.map(v => { return { title: v.version, // eslint-disable-next-line - value: v._id + value: v._id, }; }), - } + }, +]; + +exports.createVersionPrompt = versionList => [ + { + type: 'select', + name: 'fork', + message: 'Which version would you like to fork from?', + choices: versionList.map(v => { + return { + title: v.version, + // eslint-disable-next-line + value: v._id, + }; + }), + }, + { + type: 'select', + name: 'main', + message: 'Would you like to make this version the main version?', + choices: [{ title: 'Yes', value: true }, { title: 'No', value: false }], + }, ]; diff --git a/lib/swagger.js b/lib/swagger.js index e1db84d48..a327fed4f 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -8,19 +8,6 @@ const promptOpts = require('./prompts'); exports.desc = 'Upload your swagger file to ReadMe'; exports.category = 'services'; exports.weight = 2; - -async function versionPrompt(versionList, specPath) { - const promptSelection = promptOpts.generatePrompts(versionList); - const res = await prompts(promptSelection); - - if (res.option === 'create') { - callApi(specPath, res.option); - } else if (res.option === 'update') { - const versionId = res.versionSelection; - callApi(specPath, res.option, versionId); - } -} - exports.run = async function({ args, opts }) { let { key, id } = opts; @@ -36,6 +23,18 @@ exports.run = async function({ args, opts }) { } function callApi(specPath, versionAction, version) { + async function versionPrompt(versionList) { + const promptSelection = promptOpts.generatePrompts(versionList); + const res = await prompts(promptSelection); + + if (res.option === 'create') { + return callApi(specPath, res.option); + } + + const versionId = res.versionSelection; + return callApi(specPath, res.option, versionId); + } + function success(data) { const message = !id ? "You've successfully uploaded a new swagger file to your ReadMe project!" @@ -61,7 +60,7 @@ exports.run = async function({ args, opts }) { try { const errorDesc = JSON.parse(err.error).description; if (typeof errorDesc === 'object') { - return Promise.resolve(versionPrompt(errorDesc, specPath)); + return Promise.resolve(versionPrompt(errorDesc)); } return Promise.reject(new Error(errorDesc)); } catch (e) { @@ -77,8 +76,8 @@ exports.run = async function({ args, opts }) { resolveWithFullResponse: true, headers: { versionAction, - version - } + version, + }, }; // Create diff --git a/lib/versions/create.js b/lib/versions/create.js new file mode 100644 index 000000000..14f08e1b5 --- /dev/null +++ b/lib/versions/create.js @@ -0,0 +1,55 @@ +const request = require('request-promise-native'); +const config = require('config'); +const prompts = require('prompts'); +const promptOpts = require('../prompts'); + +exports.desc = 'Create a new version for your project'; +exports.category = 'services'; +exports.weight = 4; +exports.action = 'versions:create'; + +exports.run = async function({ opts }) { + let { key } = opts; + const { version, codename } = opts; + + if (!key && opts.token) { + console.warn( + 'Using `rdme` with --token has been deprecated. Please use --key and --id instead', + ); + [key] = opts.token.split('-'); + } + + if (!key) { + return Promise.reject(new Error('No api key provided. Please use --key')); + } + + if (!version) { + return Promise.reject( + new Error('No version provided. Please specify a semantic version using --version'), + ); + } + + const versionList = await request + .get(`${config.host}/api/v1/version`, { + json: true, + auth: { user: key }, + }) + .catch(err => Promise.reject(new Error(err))); + + const promptSelection = promptOpts.createVersionPrompt(versionList); + const { fork, main } = await prompts(promptSelection); + + const options = { + json: { + version, + fork, + codename: codename || '', + is_stable: main, + }, + auth: { user: key }, + }; + + return request + .post(`${config.host}/api/v1/version`, options) + .catch(err => Promise.reject(new Error(err))); +}; diff --git a/lib/versions/delete.js b/lib/versions/delete.js new file mode 100644 index 000000000..d2550d9ca --- /dev/null +++ b/lib/versions/delete.js @@ -0,0 +1,35 @@ +const request = require('request-promise-native'); +const config = require('config'); + +exports.desc = 'Delete a version associated with your ReadMe project'; +exports.category = 'services'; +exports.weight = 4; +exports.action = 'versions:delete'; + +exports.run = async function({ opts }) { + let { key } = opts; + const { version } = opts; + + if (!key && opts.token) { + console.warn( + 'Using `rdme` with --token has been deprecated. Please use --key and --id instead', + ); + [key] = opts.token.split('-'); + } + + if (!key) { + return Promise.reject(new Error('No api key provided. Please use --key')); + } + + if (!version) { + return Promise.reject( + new Error('No version provided. Please specify a semantic version using --version'), + ); + } + + return request + .delete(`${config.host}/api/v1/version/${version}`, { + auth: { user: key }, + }) + .catch(err => Promise.reject(new Error(err))); +}; diff --git a/lib/versions/list.js b/lib/versions/list.js new file mode 100644 index 000000000..52fe76128 --- /dev/null +++ b/lib/versions/list.js @@ -0,0 +1,29 @@ +const request = require('request-promise-native'); +const config = require('config'); + +exports.desc = 'List versions available in your project'; +exports.category = 'services'; +exports.weight = 4; +exports.action = 'versions:list'; + +exports.run = function({ opts }) { + let { key } = opts; + + if (!key && opts.token) { + console.warn( + 'Using `rdme` with --token has been deprecated. Please use --key and --id instead', + ); + [key] = opts.token.split('-'); + } + + if (!key) { + return Promise.reject(new Error('No api key provided. Please use --key')); + } + + return request + .get(`${config.host}/api/v1/version`, { + json: true, + auth: { user: key }, + }) + .catch(err => Promise.reject(new Error(err))); +}; diff --git a/lib/versions/update.js b/lib/versions/update.js new file mode 100644 index 000000000..e69de29bb diff --git a/test/prompts.test.js b/test/prompts.test.js new file mode 100644 index 000000000..dca26e022 --- /dev/null +++ b/test/prompts.test.js @@ -0,0 +1,17 @@ +const assert = require('assert'); +const promptHandler = require('../lib/prompts'); + +const versionlist = [ + { + version: '1', + _id: '32', + }, +]; + +describe('generatePrompts()', () => { + it('should create an array of objects based on provided version list', () => { + const res = promptHandler.generatePrompts(versionlist); + console.log(res[1].choices); + assert.equal(res[1].choices[0].title, '1'); + }); +}); From c786949215bdee580f32ad5840d2583d78c77e95 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Tue, 2 Jul 2019 16:32:28 -0700 Subject: [PATCH 03/27] CLI functionality for rdme version CRUD --- lib/prompts.js | 129 +++++++++++++++++++++++++++-------------- lib/versions/create.js | 28 ++++----- lib/versions/update.js | 57 ++++++++++++++++++ package-lock.json | 20 ++++++- package.json | 2 +- 5 files changed, 176 insertions(+), 60 deletions(-) diff --git a/lib/prompts.js b/lib/prompts.js index cc0b8dad4..f9b22055f 100644 --- a/lib/prompts.js +++ b/lib/prompts.js @@ -1,45 +1,86 @@ -exports.generatePrompts = versionList => [ - { - type: 'select', - name: 'option', - message: - "We couldn't find a version in ReadMe matching the version in your OAS file. Would you like to use an existing version or create a new one?", - choices: [ - { title: 'Use existing', value: 'update' }, - { title: 'Create a new version', value: 'create' }, - ], - }, - { - type: prev => (prev === 'update' ? 'select' : null), - name: 'versionSelection', - message: 'Select your desired version', - choices: versionList.map(v => { - return { - title: v.version, - // eslint-disable-next-line - value: v._id, - }; - }), - }, -]; +const { prompt } = require('enquirer'); -exports.createVersionPrompt = versionList => [ - { - type: 'select', - name: 'fork', - message: 'Which version would you like to fork from?', - choices: versionList.map(v => { - return { - title: v.version, - // eslint-disable-next-line - value: v._id, - }; - }), - }, - { - type: 'select', - name: 'main', - message: 'Would you like to make this version the main version?', - choices: [{ title: 'Yes', value: true }, { title: 'No', value: false }], - }, -]; +exports.generatePrompts = async versionList => + prompt([ + { + type: 'select', + name: 'option', + message: + "We couldn't find a version in ReadMe matching the version in your OAS file. Would you like to use an existing version or create a new one?", + choices: [ + { message: 'Use existing', value: 'update' }, + { message: 'Create a new version', value: 'create' }, + ], + }, + { + type: 'select', + name: 'versionSelection', + message: 'Select your desired version', + skip() { + return this.enquirer.answers.option !== 'update'; + }, + choices: versionList.map(v => { + return { + message: v.version, + // eslint-disable-next-line + value: v._id, + }; + }), + }, + ]); + +exports.createVersionPrompt = async (versionList, opts, isUpdate) => + prompt([ + { + type: 'select', + name: 'from', + message: 'Which version would you like to fork from?', + skip() { + return opts.fork || isUpdate; + }, + choices: versionList.map(v => { + return { + message: v.version, + // eslint-disable-next-line + value: v.version, + }; + }), + }, + { + type: 'input', + name: 'newVersion', + message: "What's your new version?", + skip() { + return opts.newVersion || !isUpdate; + }, + hint: '1.0.0', + }, + { + type: 'confirm', + name: 'is_stable', + message: 'Would you like to make this version the main version for this project?', + skip: () => opts.main, + }, + { + type: 'confirm', + name: 'is_beta', + message: 'Should this version be in beta?', + skip: () => opts.beta, + }, + { + type: 'confirm', + name: 'is_hidden', + message: 'Would you like to make this version public?', + skip() { + return opts.isPublic || opts.main || this.enquirer.answers.is_stable; + }, + }, + { + type: 'confirm', + name: 'is_deprecated', + message: 'Would you like to deprecate this version?', + skip() { + return opts.deprecated || opts.main || !isUpdate || this.enquirer.answers.is_stable; + }, + }, + ]); diff --git a/lib/versions/create.js b/lib/versions/create.js index 14f08e1b5..c1bdf0c97 100644 --- a/lib/versions/create.js +++ b/lib/versions/create.js @@ -1,6 +1,5 @@ const request = require('request-promise-native'); const config = require('config'); -const prompts = require('prompts'); const promptOpts = require('../prompts'); exports.desc = 'Create a new version for your project'; @@ -10,7 +9,8 @@ exports.action = 'versions:create'; exports.run = async function({ opts }) { let { key } = opts; - const { version, codename } = opts; + let versionList; + const { version, codename, fork, main, beta, isPublic } = opts; if (!key && opts.token) { console.warn( @@ -29,22 +29,24 @@ exports.run = async function({ opts }) { ); } - const versionList = await request - .get(`${config.host}/api/v1/version`, { - json: true, - auth: { user: key }, - }) - .catch(err => Promise.reject(new Error(err))); - - const promptSelection = promptOpts.createVersionPrompt(versionList); - const { fork, main } = await prompts(promptSelection); + if (!fork) { + versionList = await request + .get(`${config.host}/api/v1/version`, { + json: true, + auth: { user: key }, + }) + .catch(err => Promise.reject(new Error(err))); + } + const promptResponse = await promptOpts.createVersionPrompt(versionList, opts); const options = { json: { version, - fork, codename: codename || '', - is_stable: main, + is_stable: main || promptResponse.is_stable, + is_beta: beta || promptResponse.is_beta, + from: fork || promptResponse.from, + is_hidden: promptResponse.is_stable ? false : !(isPublic || promptResponse.is_hidden), }, auth: { user: key }, }; diff --git a/lib/versions/update.js b/lib/versions/update.js index e69de29bb..0df31d37d 100644 --- a/lib/versions/update.js +++ b/lib/versions/update.js @@ -0,0 +1,57 @@ +const request = require('request-promise-native'); +const config = require('config'); +const promptOpts = require('../prompts'); + +exports.desc = 'Update an existing version for your project'; +exports.category = 'services'; +exports.weight = 4; +exports.action = 'versions:update'; + +exports.run = async function({ opts }) { + let { key } = opts; + let versionList; + const { version, codename, fork, newVersion, main, beta, isPublic, deprecated } = opts; + + if (!key && opts.token) { + console.warn( + 'Using `rdme` with --token has been deprecated. Please use --key and --id instead.', + ); + [key] = opts.token.split('-'); + } + + if (!key) { + return Promise.reject(new Error('No api key provided. Please use --key')); + } + + if (!version) { + return Promise.reject( + new Error('No version provided. Please specify a semantic version using --version'), + ); + } + + if (!fork) { + versionList = await request + .get(`${config.host}/api/v1/version`, { + json: true, + auth: { user: key }, + }) + .catch(err => Promise.reject(new Error(err))); + } + + const promptResponse = await promptOpts.createVersionPrompt(versionList, opts, true); + const options = { + json: { + codename: codename || '', + version: newVersion || promptResponse.newVersion, + is_stable: main || promptResponse.is_stable, + is_beta: beta || promptResponse.is_beta, + is_deprecated: deprecated || promptResponse.is_deprecated, + is_hidden: promptResponse.is_stable ? false : !(isPublic || promptResponse.is_hidden), + }, + auth: { user: key }, + }; + + return request + .put(`${config.host}/api/v1/version/${version}`, options) + .catch(err => Promise.reject(new Error(err))); +}; diff --git a/package-lock.json b/package-lock.json index abbd60f59..5fa4a1f08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -733,6 +733,11 @@ "json-schema-traverse": "^0.3.0" } }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==" + }, "ansi-escapes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", @@ -1980,6 +1985,14 @@ "once": "^1.4.0" } }, + "enquirer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.0.tgz", + "integrity": "sha512-RNGUbRVlfnjmpxV+Ed+7CGu0rg3MK7MmlW+DW0v7V2zdAUBC1s4BxCRiIAozbYB2UJ+q4D+8tW9UFb11kF72/g==", + "requires": { + "ansi-colors": "^3.2.1" + } + }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", @@ -5458,7 +5471,8 @@ "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true }, "lazy-cache": { "version": "1.0.4", @@ -6677,6 +6691,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.1.0.tgz", "integrity": "sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg==", + "dev": true, "requires": { "kleur": "^3.0.2", "sisteransi": "^1.0.0" @@ -7286,7 +7301,8 @@ "sisteransi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", - "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==" + "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==", + "dev": true }, "slash": { "version": "2.0.0", diff --git a/package.json b/package.json index af93dbe2e..8e6b58ca2 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,12 @@ "config": "^3.1.0", "configstore": "^5.0.0", "editor": "^1.0.0", + "enquirer": "^2.3.0", "gray-matter": "^4.0.1", "isemail": "^3.1.3", "minimist": "^1.2.0", "oas": "0.8.15", "opn": "^6.0.0", - "prompts": "^2.1.0", "read": "^1.0.7", "request": "^2.88.0", "request-promise-native": "^1.0.5" From dbb8977e0b93ffd772769f582f2ea3a8a54c4ada Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Tue, 2 Jul 2019 17:15:22 -0700 Subject: [PATCH 04/27] added getbyid --- lib/versions/{list.js => index.js} | 3 +-- lib/versions/versionId.js | 36 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) rename lib/versions/{list.js => index.js} (92%) create mode 100644 lib/versions/versionId.js diff --git a/lib/versions/list.js b/lib/versions/index.js similarity index 92% rename from lib/versions/list.js rename to lib/versions/index.js index 52fe76128..086edb727 100644 --- a/lib/versions/list.js +++ b/lib/versions/index.js @@ -3,8 +3,7 @@ const config = require('config'); exports.desc = 'List versions available in your project'; exports.category = 'services'; -exports.weight = 4; -exports.action = 'versions:list'; +exports.weight = 3; exports.run = function({ opts }) { let { key } = opts; diff --git a/lib/versions/versionId.js b/lib/versions/versionId.js new file mode 100644 index 000000000..767e476c2 --- /dev/null +++ b/lib/versions/versionId.js @@ -0,0 +1,36 @@ +const request = require('request-promise-native'); +const config = require('config'); + +exports.desc = 'List versions available in your project'; +exports.category = 'services'; +exports.weight = 4; +exports.action = 'versions:versionId'; + +exports.run = function({ opts }) { + let { key } = opts; + const { version } = opts; + + if (!key && opts.token) { + console.warn( + 'Using `rdme` with --token has been deprecated. Please use --key and --id instead', + ); + [key] = opts.token.split('-'); + } + + if (!key) { + return Promise.reject(new Error('No api key provided. Please use --key')); + } + + if (!version) { + return Promise.reject( + new Error('No version provided. Please specify a semantic version using --version'), + ); + } + + return request + .get(`${config.host}/api/v1/version/${version}`, { + json: true, + auth: { user: key }, + }) + .catch(err => Promise.reject(new Error(err))); +}; From b1fa308b98dc2996454b8779562c86038e879789 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Wed, 3 Jul 2019 10:30:07 -0700 Subject: [PATCH 05/27] Added error handling to version requests --- lib/versions/create.js | 12 +++++++++--- lib/versions/delete.js | 10 +++++++++- lib/versions/index.js | 10 +++++++++- lib/versions/update.js | 12 +++++++++--- lib/versions/versionId.js | 10 +++++++++- 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/lib/versions/create.js b/lib/versions/create.js index c1bdf0c97..4a7c572ca 100644 --- a/lib/versions/create.js +++ b/lib/versions/create.js @@ -51,7 +51,13 @@ exports.run = async function({ opts }) { auth: { user: key }, }; - return request - .post(`${config.host}/api/v1/version`, options) - .catch(err => Promise.reject(new Error(err))); + return request.post(`${config.host}/api/v1/version`, options).catch(err => { + let errorDesc; + try { + errorDesc = JSON.parse(err.error).description; + } catch (e) { + errorDesc = 'Failed to create a new version using your specified parameters.'; + } + return Promise.reject(new Error(errorDesc)); + }); }; diff --git a/lib/versions/delete.js b/lib/versions/delete.js index d2550d9ca..36a1d1078 100644 --- a/lib/versions/delete.js +++ b/lib/versions/delete.js @@ -31,5 +31,13 @@ exports.run = async function({ opts }) { .delete(`${config.host}/api/v1/version/${version}`, { auth: { user: key }, }) - .catch(err => Promise.reject(new Error(err))); + .catch(err => { + let errorDesc; + try { + errorDesc = JSON.parse(err.error).description; + } catch (e) { + errorDesc = 'Failed to delete target version.'; + } + return Promise.reject(new Error(errorDesc)); + }); }; diff --git a/lib/versions/index.js b/lib/versions/index.js index 086edb727..2fff9e8a3 100644 --- a/lib/versions/index.js +++ b/lib/versions/index.js @@ -24,5 +24,13 @@ exports.run = function({ opts }) { json: true, auth: { user: key }, }) - .catch(err => Promise.reject(new Error(err))); + .catch(err => { + let errorDesc; + try { + errorDesc = JSON.parse(err.error).description; + } catch (e) { + errorDesc = 'Failed to get versions attached to the provided key.'; + } + return Promise.reject(new Error(errorDesc)); + }); }; diff --git a/lib/versions/update.js b/lib/versions/update.js index 0df31d37d..6270f17bd 100644 --- a/lib/versions/update.js +++ b/lib/versions/update.js @@ -51,7 +51,13 @@ exports.run = async function({ opts }) { auth: { user: key }, }; - return request - .put(`${config.host}/api/v1/version/${version}`, options) - .catch(err => Promise.reject(new Error(err))); + return request.put(`${config.host}/api/v1/version/${version}`, options).catch(err => { + let errorDesc; + try { + errorDesc = JSON.parse(err.error).description; + } catch (e) { + errorDesc = 'Failed to update version using your specified parameters.'; + } + return Promise.reject(new Error(errorDesc)); + }); }; diff --git a/lib/versions/versionId.js b/lib/versions/versionId.js index 767e476c2..0a1145baa 100644 --- a/lib/versions/versionId.js +++ b/lib/versions/versionId.js @@ -32,5 +32,13 @@ exports.run = function({ opts }) { json: true, auth: { user: key }, }) - .catch(err => Promise.reject(new Error(err))); + .catch(err => { + let errorDesc; + try { + errorDesc = JSON.parse(err.error).description; + } catch (e) { + errorDesc = 'Failed to get specific version using provided identifier and key.'; + } + return Promise.reject(new Error(errorDesc)); + }); }; From 2c032177f5f8b08f11be0e93ffa359280545fe54 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Wed, 3 Jul 2019 16:25:40 -0700 Subject: [PATCH 06/27] Feature complete. Prompt interaction, leverage version API --- lib/prompts.js | 2 +- lib/swagger.js | 79 ++++++++++++++++++++++++++++++++------------------ 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/lib/prompts.js b/lib/prompts.js index f9b22055f..18e0cfb18 100644 --- a/lib/prompts.js +++ b/lib/prompts.js @@ -23,7 +23,7 @@ exports.generatePrompts = async versionList => return { message: v.version, // eslint-disable-next-line - value: v._id, + value: v.version, }; }), }, diff --git a/lib/swagger.js b/lib/swagger.js index a327fed4f..a828085d1 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -2,13 +2,14 @@ const request = require('request-promise-native'); const fs = require('fs'); const path = require('path'); const config = require('config'); -const prompts = require('prompts'); const promptOpts = require('./prompts'); exports.desc = 'Upload your swagger file to ReadMe'; exports.category = 'services'; exports.weight = 2; + exports.run = async function({ args, opts }) { + const { version } = opts; let { key, id } = opts; if (!key && opts.token) { @@ -22,19 +23,7 @@ exports.run = async function({ args, opts }) { return Promise.reject(new Error('No api key provided. Please use --key')); } - function callApi(specPath, versionAction, version) { - async function versionPrompt(versionList) { - const promptSelection = promptOpts.generatePrompts(versionList); - const res = await prompts(promptSelection); - - if (res.option === 'create') { - return callApi(specPath, res.option); - } - - const versionId = res.versionSelection; - return callApi(specPath, res.option, versionId); - } - + function callApi(specPath, versionCleaned) { function success(data) { const message = !id ? "You've successfully uploaded a new swagger file to your ReadMe project!" @@ -57,12 +46,9 @@ exports.run = async function({ args, opts }) { } function error(err) { + console.log(err) try { - const errorDesc = JSON.parse(err.error).description; - if (typeof errorDesc === 'object') { - return Promise.resolve(versionPrompt(errorDesc)); - } - return Promise.reject(new Error(errorDesc)); + return Promise.reject(new Error(JSON.parse(err.error).description)); } catch (e) { return Promise.reject(new Error('There was an error uploading!')); } @@ -72,12 +58,11 @@ exports.run = async function({ args, opts }) { formData: { spec: fs.createReadStream(path.resolve(process.cwd(), specPath)), }, - auth: { user: key }, - resolveWithFullResponse: true, headers: { - versionAction, - version, + version: versionCleaned }, + auth: { user: key }, + resolveWithFullResponse: true, }; // Create @@ -91,23 +76,61 @@ exports.run = async function({ args, opts }) { .then(success, error); } + async function getSwaggerVersion(specPath, versionFlag) { + let versionSpec; + let versionCleaned; + + if (!versionFlag) { + const file = fs.readFileSync(path.resolve(process.cwd(), specPath), 'utf8'); + versionSpec = JSON.parse(file).info.version; + } + + const options = { json: { version: versionSpec || versionFlag }, auth: { user: key } }; + + try { + versionCleaned = await request.get( + `${config.host}/api/v1/version/${versionSpec || versionFlag}`, + options, + ); + } catch (e) { + if (e.statusCode === 400) { + const versionList = await request.get(`${config.host}/api/v1/version`, options); + options.json.from = versionList[0].version; + + const promptResponse = await promptOpts.generatePrompts(versionList); + const { option, versionSelection } = promptResponse; + + if (option === 'update') return versionSelection; + + versionCleaned = await request.post(`${config.host}/api/v1/version`, options); + } + } + return versionCleaned.version; + } + if (args[0]) { - return callApi(args[0]); + const selectedVersion = await getSwaggerVersion(args[0], version); + return callApi(args[0], selectedVersion); } // If the user didn't supply a specification, let's try to locate what they've got, and upload // that. If they don't have any, let's let the user know how they can get one going. - return new Promise((resolve, reject) => { - ['swagger.json', 'swagger.yaml', 'openapi.json', 'openapi.yaml'].forEach(function(file) { + return new Promise(async (resolve, reject) => { + let foundFile; + ['swagger.json', 'swagger.yaml', 'openapi.json', 'openapi.yaml'].forEach(file => { if (!fs.existsSync(file)) { return; } console.log(`We found ${file} and are attempting to upload it.`.yellow); - - resolve(callApi(file)); + foundFile = file; }); + if (foundFile) { + const selectedVersion = await getSwaggerVersion(foundFile, version); + resolve(callApi(foundFile, selectedVersion)); + } + reject( new Error( "We couldn't find a Swagger or OpenAPI file.\n\n" + From 24cbb97e7d495f3ef2eeae5a07b03d785704a2a0 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Thu, 4 Jul 2019 08:45:37 -0700 Subject: [PATCH 07/27] Modified Request: Data streams + JSON do not play well together --- lib/swagger.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/swagger.js b/lib/swagger.js index a828085d1..298757908 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -46,7 +46,6 @@ exports.run = async function({ args, opts }) { } function error(err) { - console.log(err) try { return Promise.reject(new Error(JSON.parse(err.error).description)); } catch (e) { @@ -59,7 +58,7 @@ exports.run = async function({ args, opts }) { spec: fs.createReadStream(path.resolve(process.cwd(), specPath)), }, headers: { - version: versionCleaned + version: versionCleaned, }, auth: { user: key }, resolveWithFullResponse: true, From acd34436f96e5d0981225bbe33deadad01ee4db4 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Thu, 4 Jul 2019 16:26:11 -0700 Subject: [PATCH 08/27] readme WIP --- README.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 145caba99..e865ae920 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,24 @@ -# `rdme` - ReadMe's API CLI - -This is a CLI wrapper around [ReadMe's HTTP API](https://readme.readme.io/v2.0/reference). +# `rdme` - ReadMe's CLI [![CircleCI](https://circleci.com/gh/readmeio/rdme.svg?style=svg)](https://circleci.com/gh/readmeio/rdme) [![](https://d3vv6lp55qjaqc.cloudfront.net/items/1M3C3j0I0s0j3T362344/Untitled-2.png)](https://readme.io) +### Table of Contents + * [What is rdme?](#what-is-veneur) + * [Configuration](#installation) + * [Installation](#installation) + * [Login](#logging-in-to-a-readme-project) + * [Usage](#usage) + * [Swagger](#swagger) + * [Docs](#docs) + * [Versions](#versions) + * [Opening A Project Spec](#open) + * [Future](#future) + +### About `rdme` +`rdme` is the command line interface wrapper for [ReadMe's RESTful API](https://readme.readme.io/v2.0/reference). It allows you to upload and edit [Swagger](https://swagger.io/) and [OAS](https://swagger.io/specification/) files associated with projects you create on [Readme.io](https://readme.com/). Additionally, you can sync documentation with your project, and manage project versions. + ## Installation ```sh npm install rdme @@ -32,6 +45,19 @@ rdme swagger {path-to-swagger.json} --key={api-key} rdme swagger {path-to-swagger.json} --key={api-key} --id={existing-id} ``` +#### Uploading or editing a swagger file including with specified version +You can additional include a version flag, specifying the target version for your file's destination: +```sh +rdme swagger {path-to-swagger.json} --key={api-key} --version={project-version} +``` + +#### Omitting the file path +If you run `rdme` within a directory that contains your Swagger or OAS file, you can omit the file path. +Be sure to use one of the following file names: 'swagger.json', 'swagger.yaml', 'openapi.json', 'openapi.yaml' +```sh +rdme swagger --key={api-key} +``` + ### Syncing a folder of markdown docs to ReadMe ```sh @@ -44,6 +70,27 @@ rdme docs path-to-markdown-files --key={api-key} --version={project-version} rdme docs:edit --key={api-key} --version={project-version} ``` +### Get all versions associated with your project +```sh +rdme versions --key={api-key} +``` + +### Get all information about a particular version +```sh +rdme versions:versionId --key={api-key} --version={project-version} +``` + +### Create a new version using flags +```sh +rdme versions:create --key={api-key} --version={project-version} --fork={version-fork} --codename={version-name} --main --beta +``` + +### Create a new version without flags +Creating a version without version-specific flags will allow the ReadMe CLI to prompt you with configuration options +```sh +rdme versions:create --key={api-key} --version={project-version} +``` + ### Open your ReadMe project in your browser If you are logged in, this will open the project in your browser: From 17ddfbdc3f42108b0a270339efbc1ffa713d6bff Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Thu, 4 Jul 2019 16:57:36 -0700 Subject: [PATCH 09/27] Modified project readme complete --- README.md | 67 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index e865ae920..eb21a544e 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ # `rdme` - ReadMe's CLI -[![CircleCI](https://circleci.com/gh/readmeio/rdme.svg?style=svg)](https://circleci.com/gh/readmeio/rdme) - [![](https://d3vv6lp55qjaqc.cloudfront.net/items/1M3C3j0I0s0j3T362344/Untitled-2.png)](https://readme.io) +[![CircleCI](https://circleci.com/gh/readmeio/rdme.svg?style=svg)](https://circleci.com/gh/readmeio/rdme) + ### Table of Contents - * [What is rdme?](#what-is-veneur) + * [What is rdme?](#about-rdme) * [Configuration](#installation) * [Installation](#installation) * [Login](#logging-in-to-a-readme-project) * [Usage](#usage) + * [Flags](#rdme-flags) * [Swagger](#swagger) * [Docs](#docs) * [Versions](#versions) @@ -17,15 +18,13 @@ * [Future](#future) ### About `rdme` -`rdme` is the command line interface wrapper for [ReadMe's RESTful API](https://readme.readme.io/v2.0/reference). It allows you to upload and edit [Swagger](https://swagger.io/) and [OAS](https://swagger.io/specification/) files associated with projects you create on [Readme.io](https://readme.com/). Additionally, you can sync documentation with your project, and manage project versions. +`rdme` is the CLI wrapper for [ReadMe's RESTful API](https://readme.readme.io/v2.0/reference). It allows you to upload and edit [Swagger](https://swagger.io/) and [OAS](https://swagger.io/specification/) files associated with projects you create on [readme](https://readme.com/). Additionally, you can sync documentation with your project, and manage project versions. -## Installation +## Configuration +### Installation ```sh npm install rdme ``` - -## Usage - ### Logging in to a ReadMe project If you login to a project, you will not have to provide the `--key` option because we save it locally: @@ -34,65 +33,93 @@ If you login to a project, you will not have to provide the `--key` option becau rdme login ``` -### Uploading a new Swagger file to ReadMe +## Usage +### `rdme` flags +``` +swaggerfile.json || oasfile.yaml +--key # API key associated with your ReadMe project +--version # The version +--id # The id of a OAS file previously uploaded. This is available through uploading a new file through this CLI +--fork # The semantic version which you'd like to fork from +--codename # The codename or nickname for a particular version +--main # Should this version be the primary (default) version for your project? +--beta # Is this version in beta? +--isPublic # Would you like to make this version public? Any primary version must be public +``` +### Swagger +#### Uploading a new Swagger file to ReadMe +This will return an id and url for you to update your file and view it in the client, respectively ```sh rdme swagger {path-to-swagger.json} --key={api-key} ``` -### Editing an existing Swagger file +#### Editing an existing Swagger file ```sh rdme swagger {path-to-swagger.json} --key={api-key} --id={existing-id} ``` #### Uploading or editing a swagger file including with specified version -You can additional include a version flag, specifying the target version for your file's destination: +You can additional include a version flag, specifying the target version for your file's destination ```sh rdme swagger {path-to-swagger.json} --key={api-key} --version={project-version} ``` #### Omitting the file path If you run `rdme` within a directory that contains your Swagger or OAS file, you can omit the file path. -Be sure to use one of the following file names: 'swagger.json', 'swagger.yaml', 'openapi.json', 'openapi.yaml' +Be sure to use one of the following file names: `swagger.json`, `swagger.yaml`, `openapi.json`, `openapi.yaml` if you do. ```sh rdme swagger --key={api-key} ``` -### Syncing a folder of markdown docs to ReadMe +### Docs +#### Syncing a folder of markdown docs to ReadMe ```sh rdme docs path-to-markdown-files --key={api-key} --version={project-version} ``` -### Edit a single readme doc on your local machine +#### Edit a single readme doc on your local machine ```sh rdme docs:edit --key={api-key} --version={project-version} ``` -### Get all versions associated with your project +### Versions +#### Get all versions associated with your project ```sh rdme versions --key={api-key} ``` -### Get all information about a particular version +#### Get all information about a particular version ```sh rdme versions:versionId --key={api-key} --version={project-version} ``` -### Create a new version using flags +#### Create a new version using flags ```sh rdme versions:create --key={api-key} --version={project-version} --fork={version-fork} --codename={version-name} --main --beta ``` -### Create a new version without flags +#### Create a new version without flags Creating a version without version-specific flags will allow the ReadMe CLI to prompt you with configuration options ```sh rdme versions:create --key={api-key} --version={project-version} ``` -### Open your ReadMe project in your browser +#### Update a version +The command to update a version takes the same flags as creating a new version +```sh +rdme versions:create --key={api-key} --version={project-version} +``` +#### Delete a version +You can remove a specific version from your project, as well as all of the attached specs +```sh +rdme versions:delete --key={api-key} --version={project-version} +``` + +### Open your ReadMe project in your browser If you are logged in, this will open the project in your browser: ```sh @@ -100,4 +127,4 @@ rdme open ``` ## Future -We will be expanding and modifying the feature set of this program as/when we expand our public API. Some things will be changed. +We are continually expanding and improving the offerings of this application as we expand our public API and are able. Some interactions may change over time. From c3a5331b382bc6d4da18b25d6d9fa43fa6f506b3 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Mon, 8 Jul 2019 15:47:28 -0700 Subject: [PATCH 10/27] Test work --- lib/prompts.js | 2 +- test/prompts.test.js | 77 ++++++++++++++++++++++++++++++++++++++++--- test/versions.test.js | 0 3 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 test/versions.test.js diff --git a/lib/prompts.js b/lib/prompts.js index 18e0cfb18..3bbadb822 100644 --- a/lib/prompts.js +++ b/lib/prompts.js @@ -1,6 +1,6 @@ const { prompt } = require('enquirer'); -exports.generatePrompts = async versionList => +exports.generatePrompts = (versionList) => prompt([ { type: 'select', diff --git a/test/prompts.test.js b/test/prompts.test.js index dca26e022..d3b6faf05 100644 --- a/test/prompts.test.js +++ b/test/prompts.test.js @@ -1,17 +1,84 @@ const assert = require('assert'); +// const jest = require('jest'); +const Enquirer = require('enquirer'); const promptHandler = require('../lib/prompts'); +// const down = { sequence: '\u001b[B', name: 'down', code: '[B' }; const versionlist = [ { version: '1', _id: '32', }, ]; +// +// const opts = { +// version: '1', +// codename: 'test', +// fork: '1.0.0', +// main: true, +// beta: true, +// isPublic: true +// }; -describe('generatePrompts()', () => { - it('should create an array of objects based on provided version list', () => { - const res = promptHandler.generatePrompts(versionlist); - console.log(res[1].choices); - assert.equal(res[1].choices[0].title, '1'); +describe('prompt test bed', () => { + let enquirer; + + beforeEach(() => { + enquirer = new Enquirer(); }); + + describe('generatePrompts()', () => { + it('should return a version if update is selected', async () => { + enquirer.on('prompt', async prompt => { + await prompt.submit(); + // if (prompt.name === 'option') { + // prompt.submit() + // } else { + // prompt.value = 'red'; + // prompt.submit(); + // } + }); + + // return enquirer.prompt([{ + // type: 'input', + // name: 'color', + // message: 'Favorite color?' + // }]).then(answers => console.log(answers)); + + + return enquirer.prompt(promptHandler.generatePrompts(versionlist)) + .then(answers => console.log(answers)) + + // const promptCall = await + // assert.equal(promptCall.versionSelection, '1'); + // done(); + }); + + // it('should return a create option if selected', async () => { + // const prompt = promptHandler.generatePrompts(versionlist); + // + // prompt.once('run', async () => { + // await prompt.keypress(null, down); + // await prompt.submit(); + // }); + // + // const answer = await prompt.run(); + // assert.equal(answer.option, 'create'); + // }); + }); + + // describe('createVersionPrompt()', () => { + // it('should not use input if the prompt is meant for creating a version', async () => { + // const options = { main: true, beta: true }; + // const prompt = promptHandler.createVersionPrompt(versionlist, options, false); + // + // prompt.once('run', async () => { + // await prompt.submit(); + // }); + // + // const response = {}; + // const answer = await prompt.run(); + // assert.equal(answer, response); + // }); + // }); }); diff --git a/test/versions.test.js b/test/versions.test.js new file mode 100644 index 000000000..e69de29bb From 332c9a4430deb2d81749bd5e501c272c47552ba4 Mon Sep 17 00:00:00 2001 From: Dom Harrington Date: Mon, 8 Jul 2019 16:08:24 -0700 Subject: [PATCH 11/27] wip testing for enquirer --- lib/prompts.js | 4 ++-- test/prompts.test.js | 29 ++++++++++------------------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/lib/prompts.js b/lib/prompts.js index 3bbadb822..4f73c8a5d 100644 --- a/lib/prompts.js +++ b/lib/prompts.js @@ -1,7 +1,7 @@ const { prompt } = require('enquirer'); exports.generatePrompts = (versionList) => - prompt([ + [ { type: 'select', name: 'option', @@ -27,7 +27,7 @@ exports.generatePrompts = (versionList) => }; }), }, - ]); + ]; exports.createVersionPrompt = async (versionList, opts, isUpdate) => prompt([ diff --git a/test/prompts.test.js b/test/prompts.test.js index d3b6faf05..60a5e13b3 100644 --- a/test/prompts.test.js +++ b/test/prompts.test.js @@ -24,34 +24,25 @@ describe('prompt test bed', () => { let enquirer; beforeEach(() => { - enquirer = new Enquirer(); + enquirer = new Enquirer({ show: false }); }); describe('generatePrompts()', () => { it('should return a version if update is selected', async () => { enquirer.on('prompt', async prompt => { - await prompt.submit(); - // if (prompt.name === 'option') { - // prompt.submit() - // } else { - // prompt.value = 'red'; - // prompt.submit(); - // } - }); - - // return enquirer.prompt([{ - // type: 'input', - // name: 'color', - // message: 'Favorite color?' - // }]).then(answers => console.log(answers)); + if (prompt.name === 'option') { + await prompt.keypress(null, { name: 'down' }); + // await prompt.keypress(null, { name: 'up' }); + await prompt.submit() + } + if (prompt.name === 'versionSelection') { + assert.equal(await prompt.skip(), true); + } + }); return enquirer.prompt(promptHandler.generatePrompts(versionlist)) .then(answers => console.log(answers)) - - // const promptCall = await - // assert.equal(promptCall.versionSelection, '1'); - // done(); }); // it('should return a create option if selected', async () => { From 88d3f8c0a123659b1ce7f9e340464cca0eca451c Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Mon, 8 Jul 2019 16:47:53 -0700 Subject: [PATCH 12/27] prompt tests complete --- lib/prompts.js | 156 ++++++++++++++++++++--------------------- lib/swagger.js | 3 +- lib/versions/create.js | 3 +- lib/versions/update.js | 3 +- swagger.json | 1 + test/prompts.test.js | 102 ++++++++++++++++----------- 6 files changed, 143 insertions(+), 125 deletions(-) create mode 100644 swagger.json diff --git a/lib/prompts.js b/lib/prompts.js index 4f73c8a5d..cd9794dc9 100644 --- a/lib/prompts.js +++ b/lib/prompts.js @@ -1,86 +1,82 @@ -const { prompt } = require('enquirer'); - -exports.generatePrompts = (versionList) => - [ - { - type: 'select', - name: 'option', - message: - "We couldn't find a version in ReadMe matching the version in your OAS file. Would you like to use an existing version or create a new one?", - choices: [ - { message: 'Use existing', value: 'update' }, - { message: 'Create a new version', value: 'create' }, - ], - }, - { - type: 'select', - name: 'versionSelection', - message: 'Select your desired version', - skip() { - return this.enquirer.answers.option !== 'update'; - }, - choices: versionList.map(v => { - return { - message: v.version, - // eslint-disable-next-line - value: v.version, - }; - }), +exports.generatePrompts = versionList => [ + { + type: 'select', + name: 'option', + message: + "We couldn't find a version in ReadMe matching the version in your OAS file. Would you like to use an existing version or create a new one?", + choices: [ + { message: 'Use existing', value: 'update' }, + { message: 'Create a new version', value: 'create' }, + ], + }, + { + type: 'select', + name: 'versionSelection', + message: 'Select your desired version', + skip() { + return this.enquirer.answers.option !== 'update'; }, - ]; + choices: versionList.map(v => { + return { + message: v.version, + // eslint-disable-next-line + value: v.version, + }; + }), + }, +]; -exports.createVersionPrompt = async (versionList, opts, isUpdate) => - prompt([ - { - type: 'select', - name: 'from', - message: 'Which version would you like to fork from?', - skip() { - return opts.fork || isUpdate; - }, - choices: versionList.map(v => { - return { - message: v.version, - // eslint-disable-next-line - value: v.version, - }; - }), - }, - { - type: 'input', - name: 'newVersion', - message: "What's your new version?", - skip() { - return opts.newVersion || !isUpdate; - }, - hint: '1.0.0', - }, - { - type: 'confirm', - name: 'is_stable', - message: 'Would you like to make this version the main version for this project?', - skip: () => opts.main, +exports.createVersionPrompt = (versionList, opts, isUpdate) => [ + { + type: 'select', + name: 'from', + message: 'Which version would you like to fork from?', + skip() { + return opts.fork || isUpdate; }, - { - type: 'confirm', - name: 'is_beta', - message: 'Should this version be in beta?', - skip: () => opts.beta, + choices: versionList.map(v => { + return { + message: v.version, + // eslint-disable-next-line + value: v.version, + }; + }), + }, + { + type: 'input', + name: 'newVersion', + message: "What's your new version?", + skip() { + return opts.newVersion || !isUpdate; }, - { - type: 'confirm', - name: 'is_hidden', - message: 'Would you like to make this version public?', - skip() { - return opts.isPublic || opts.main || this.enquirer.answers.is_stable; - }, + hint: '1.0.0', + }, + { + type: 'confirm', + name: 'is_stable', + message: 'Would you like to make this version the main version for this project?', + skip: () => opts.main, + }, + { + type: 'confirm', + name: 'is_beta', + message: 'Should this version be in beta?', + skip: () => opts.beta, + }, + { + type: 'confirm', + name: 'is_hidden', + message: 'Would you like to make this version public?', + skip() { + return opts.isPublic || opts.main || this.enquirer.answers.is_stable; }, - { - type: 'confirm', - name: 'is_deprecated', - message: 'Would you like to deprecate this version?', - skip() { - return opts.deprecated || opts.main || !isUpdate || this.enquirer.answers.is_stable; - }, + }, + { + type: 'confirm', + name: 'is_deprecated', + message: 'Would you like to deprecate this version?', + skip() { + return opts.deprecated || opts.main || !isUpdate || this.enquirer.answers.is_stable; }, - ]); + }, +]; diff --git a/lib/swagger.js b/lib/swagger.js index 298757908..47dd39843 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -2,6 +2,7 @@ const request = require('request-promise-native'); const fs = require('fs'); const path = require('path'); const config = require('config'); +const { prompt } = require('enquirer'); const promptOpts = require('./prompts'); exports.desc = 'Upload your swagger file to ReadMe'; @@ -96,7 +97,7 @@ exports.run = async function({ args, opts }) { const versionList = await request.get(`${config.host}/api/v1/version`, options); options.json.from = versionList[0].version; - const promptResponse = await promptOpts.generatePrompts(versionList); + const promptResponse = await prompt(promptOpts.generatePrompts(versionList)); const { option, versionSelection } = promptResponse; if (option === 'update') return versionSelection; diff --git a/lib/versions/create.js b/lib/versions/create.js index 4a7c572ca..f8fbfee2a 100644 --- a/lib/versions/create.js +++ b/lib/versions/create.js @@ -1,5 +1,6 @@ const request = require('request-promise-native'); const config = require('config'); +const { prompt } = require('enquirer'); const promptOpts = require('../prompts'); exports.desc = 'Create a new version for your project'; @@ -38,7 +39,7 @@ exports.run = async function({ opts }) { .catch(err => Promise.reject(new Error(err))); } - const promptResponse = await promptOpts.createVersionPrompt(versionList, opts); + const promptResponse = await prompt(promptOpts.createVersionPrompt(versionList, opts)); const options = { json: { version, diff --git a/lib/versions/update.js b/lib/versions/update.js index 6270f17bd..821510f3b 100644 --- a/lib/versions/update.js +++ b/lib/versions/update.js @@ -1,5 +1,6 @@ const request = require('request-promise-native'); const config = require('config'); +const { prompt } = require('enquirer'); const promptOpts = require('../prompts'); exports.desc = 'Update an existing version for your project'; @@ -38,7 +39,7 @@ exports.run = async function({ opts }) { .catch(err => Promise.reject(new Error(err))); } - const promptResponse = await promptOpts.createVersionPrompt(versionList, opts, true); + const promptResponse = await prompt(promptOpts.createVersionPrompt(versionList, opts, true)); const options = { json: { codename: codename || '', diff --git a/swagger.json b/swagger.json new file mode 100644 index 000000000..816847f31 --- /dev/null +++ b/swagger.json @@ -0,0 +1 @@ +{"swagger":"2.0","info":{"description":"This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.","version":"1.0.0","title":"Swagger Petstore","termsOfService":"http://swagger.io/terms/","contact":{"email":"apiteam@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"}},"host":"petstore.swagger.io","basePath":"/v2","tags":[{"name":"pet","description":"Everything about your Pets","externalDocs":{"description":"Find out more","url":"http://swagger.io"}},{"name":"store","description":"Access to Petstore orders"},{"name":"user","description":"Operations about user","externalDocs":{"description":"Find out more about our store","url":"http://swagger.io"}}],"schemes":["http"],"paths":{"/pet":{"post":{"tags":["pet"],"summary":"Add a new pet to the store","description":"","operationId":"addPet","consumes":["application/json","application/xml"],"produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"put":{"tags":["pet"],"summary":"Update an existing pet","description":"","operationId":"updatePet","consumes":["application/json","application/xml"],"produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"},"405":{"description":"Validation exception"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByStatus":{"get":{"tags":["pet"],"summary":"Finds Pets by status","description":"Multiple status values can be provided with comma separated strings","operationId":"findPetsByStatus","produces":["application/xml","application/json"],"parameters":[{"name":"status","in":"query","description":"Status values that need to be considered for filter","required":true,"type":"array","items":{"type":"string","enum":["available","pending","sold"],"default":"available"},"collectionFormat":"multi"}],"responses":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400":{"description":"Invalid status value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByTags":{"get":{"tags":["pet"],"summary":"Finds Pets by tags","description":"Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.","operationId":"findPetsByTags","produces":["application/xml","application/json"],"parameters":[{"name":"tags","in":"query","description":"Tags to filter by","required":true,"type":"array","items":{"type":"string"},"collectionFormat":"multi"}],"responses":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400":{"description":"Invalid tag value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}],"deprecated":true}},"/pet/{petId}":{"get":{"tags":["pet"],"summary":"Find pet by ID","description":"Returns a single pet","operationId":"getPetById","produces":["application/xml","application/json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to return","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Pet"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"security":[{"api_key":[]}]},"post":{"tags":["pet"],"summary":"Updates a pet in the store with form data","description":"","operationId":"updatePetWithForm","consumes":["application/x-www-form-urlencoded"],"produces":["application/xml","application/json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet that needs to be updated","required":true,"type":"integer","format":"int64"},{"name":"name","in":"formData","description":"Updated name of the pet","required":false,"type":"string"},{"name":"status","in":"formData","description":"Updated status of the pet","required":false,"type":"string"}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"delete":{"tags":["pet"],"summary":"Deletes a pet","description":"","operationId":"deletePet","produces":["application/xml","application/json"],"parameters":[{"name":"api_key","in":"header","required":false,"type":"string"},{"name":"petId","in":"path","description":"Pet id to delete","required":true,"type":"integer","format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}/uploadImage":{"post":{"tags":["pet"],"summary":"uploads an image","description":"","operationId":"uploadFile","consumes":["multipart/form-data"],"produces":["application/json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to update","required":true,"type":"integer","format":"int64"},{"name":"additionalMetadata","in":"formData","description":"Additional data to pass to server","required":false,"type":"string"},{"name":"file","in":"formData","description":"file to upload","required":false,"type":"file"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/ApiResponse"}}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/store/inventory":{"get":{"tags":["store"],"summary":"Returns pet inventories by status","description":"Returns a map of status codes to quantities","operationId":"getInventory","produces":["application/json"],"parameters":[],"responses":{"200":{"description":"successful operation","schema":{"type":"object","additionalProperties":{"type":"integer","format":"int32"}}}},"security":[{"api_key":[]}]}},"/store/order":{"post":{"tags":["store"],"summary":"Place an order for a pet","description":"","operationId":"placeOrder","produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"order placed for purchasing the pet","required":true,"schema":{"$ref":"#/definitions/Order"}}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid Order"}}}},"/store/order/{orderId}":{"get":{"tags":["store"],"summary":"Find purchase order by ID","description":"For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions","operationId":"getOrderById","produces":["application/xml","application/json"],"parameters":[{"name":"orderId","in":"path","description":"ID of pet that needs to be fetched","required":true,"type":"integer","maximum":10.0,"minimum":1.0,"format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}},"delete":{"tags":["store"],"summary":"Delete purchase order by ID","description":"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors","operationId":"deleteOrder","produces":["application/xml","application/json"],"parameters":[{"name":"orderId","in":"path","description":"ID of the order that needs to be deleted","required":true,"type":"integer","minimum":1.0,"format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}}},"/user":{"post":{"tags":["user"],"summary":"Create user","description":"This can only be done by the logged in user.","operationId":"createUser","produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"Created user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"default":{"description":"successful operation"}}}},"/user/createWithArray":{"post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"","operationId":"createUsersWithArrayInput","produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/User"}}}],"responses":{"default":{"description":"successful operation"}}}},"/user/createWithList":{"post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"","operationId":"createUsersWithListInput","produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/User"}}}],"responses":{"default":{"description":"successful operation"}}}},"/user/login":{"get":{"tags":["user"],"summary":"Logs user into the system","description":"","operationId":"loginUser","produces":["application/xml","application/json"],"parameters":[{"name":"username","in":"query","description":"The user name for login","required":true,"type":"string"},{"name":"password","in":"query","description":"The password for login in clear text","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"type":"string"},"headers":{"X-Rate-Limit":{"type":"integer","format":"int32","description":"calls per hour allowed by the user"},"X-Expires-After":{"type":"string","format":"date-time","description":"date in UTC when token expires"}}},"400":{"description":"Invalid username/password supplied"}}}},"/user/logout":{"get":{"tags":["user"],"summary":"Logs out current logged in user session","description":"","operationId":"logoutUser","produces":["application/xml","application/json"],"parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/user/{username}":{"get":{"tags":["user"],"summary":"Get user by user name","description":"","operationId":"getUserByName","produces":["application/xml","application/json"],"parameters":[{"name":"username","in":"path","description":"The name that needs to be fetched. Use user1 for testing. ","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/User"}},"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}},"put":{"tags":["user"],"summary":"Updated user","description":"This can only be done by the logged in user.","operationId":"updateUser","produces":["application/xml","application/json"],"parameters":[{"name":"username","in":"path","description":"name that need to be updated","required":true,"type":"string"},{"in":"body","name":"body","description":"Updated user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"400":{"description":"Invalid user supplied"},"404":{"description":"User not found"}}},"delete":{"tags":["user"],"summary":"Delete user","description":"This can only be done by the logged in user.","operationId":"deleteUser","produces":["application/xml","application/json"],"parameters":[{"name":"username","in":"path","description":"The name that needs to be deleted","required":true,"type":"string"}],"responses":{"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}}}},"securityDefinitions":{"petstore_auth":{"type":"oauth2","authorizationUrl":"http://petstore.swagger.io/oauth/dialog","flow":"implicit","scopes":{"write:pets":"modify pets in your account","read:pets":"read your pets"}},"api_key":{"type":"apiKey","name":"api_key","in":"header"}},"definitions":{"Order":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"petId":{"type":"integer","format":"int64"},"quantity":{"type":"integer","format":"int32"},"shipDate":{"type":"string","format":"date-time"},"status":{"type":"string","description":"Order Status","enum":["placed","approved","delivered"]},"complete":{"type":"boolean","default":false}},"xml":{"name":"Order"}},"Category":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Category"}},"User":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"username":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"type":"integer","format":"int32","description":"User Status"}},"xml":{"name":"User"}},"Tag":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Tag"}},"Pet":{"type":"object","required":["name","photoUrls"],"properties":{"id":{"type":"integer","format":"int64"},"category":{"$ref":"#/definitions/Category"},"name":{"type":"string","example":"doggie"},"photoUrls":{"type":"array","xml":{"name":"photoUrl","wrapped":true},"items":{"type":"string"}},"tags":{"type":"array","xml":{"name":"tag","wrapped":true},"items":{"$ref":"#/definitions/Tag"}},"status":{"type":"string","description":"pet status in the store","enum":["available","pending","sold"]}},"xml":{"name":"Pet"}},"ApiResponse":{"type":"object","properties":{"code":{"type":"integer","format":"int32"},"type":{"type":"string"},"message":{"type":"string"}}}},"externalDocs":{"description":"Find out more about Swagger","url":"http://swagger.io"}} \ No newline at end of file diff --git a/test/prompts.test.js b/test/prompts.test.js index 60a5e13b3..77c386cfd 100644 --- a/test/prompts.test.js +++ b/test/prompts.test.js @@ -1,24 +1,15 @@ const assert = require('assert'); -// const jest = require('jest'); const Enquirer = require('enquirer'); const promptHandler = require('../lib/prompts'); -// const down = { sequence: '\u001b[B', name: 'down', code: '[B' }; const versionlist = [ { version: '1', - _id: '32', + }, + { + version: '2', }, ]; -// -// const opts = { -// version: '1', -// codename: 'test', -// fork: '1.0.0', -// main: true, -// beta: true, -// isPublic: true -// }; describe('prompt test bed', () => { let enquirer; @@ -28,12 +19,11 @@ describe('prompt test bed', () => { }); describe('generatePrompts()', () => { - it('should return a version if update is selected', async () => { + it('should not allow selection of version if chosen to create new version', async () => { enquirer.on('prompt', async prompt => { if (prompt.name === 'option') { await prompt.keypress(null, { name: 'down' }); - // await prompt.keypress(null, { name: 'up' }); - await prompt.submit() + await prompt.submit(); } if (prompt.name === 'versionSelection') { @@ -41,35 +31,63 @@ describe('prompt test bed', () => { } }); - return enquirer.prompt(promptHandler.generatePrompts(versionlist)) - .then(answers => console.log(answers)) + await enquirer.prompt(promptHandler.generatePrompts(versionlist)); }); - // it('should return a create option if selected', async () => { - // const prompt = promptHandler.generatePrompts(versionlist); - // - // prompt.once('run', async () => { - // await prompt.keypress(null, down); - // await prompt.submit(); - // }); - // - // const answer = await prompt.run(); - // assert.equal(answer.option, 'create'); - // }); + it('should return a create option if selected', async () => { + enquirer.on('prompt', async prompt => { + await prompt.keypress(null, { name: 'down' }); + await prompt.keypress(null, { name: 'up' }); + await prompt.submit(); + await prompt.submit(); + }); + + const answer = await enquirer.prompt(promptHandler.generatePrompts(versionlist)); + assert.equal(answer.versionSelection, '1'); + }); }); - // describe('createVersionPrompt()', () => { - // it('should not use input if the prompt is meant for creating a version', async () => { - // const options = { main: true, beta: true }; - // const prompt = promptHandler.createVersionPrompt(versionlist, options, false); - // - // prompt.once('run', async () => { - // await prompt.submit(); - // }); - // - // const response = {}; - // const answer = await prompt.run(); - // assert.equal(answer, response); - // }); - // }); + describe('createVersionPrompt()', () => { + it('should allow user to choose a fork if flag is not passed', async () => { + const opts = { main: true, beta: true }; + + enquirer.on('prompt', async prompt => { + await prompt.keypress(null, { name: 'down' }); + await prompt.keypress(null, { name: 'up' }); + await prompt.submit(); + }); + const answer = await enquirer.prompt( + promptHandler.createVersionPrompt(versionlist, opts, false), + ); + assert.equal(answer.is_hidden, false); + assert.equal(answer.from, '1'); + }); + + it('should skip fork prompt if value passed', async () => { + const opts = { + version: '1', + codename: 'test', + fork: '1.0.0', + main: false, + beta: true, + isPublic: false, + }; + + enquirer.on('prompt', async prompt => { + if (prompt.name === 'newVersion') { + // eslint-disable-next-line + prompt.value = '1.2.1'; + await prompt.submit(); + } + await prompt.submit(); + await prompt.submit(); + await prompt.submit(); + }); + const answer = await enquirer.prompt( + promptHandler.createVersionPrompt(versionlist, opts, true), + ); + assert.equal(answer.is_hidden, false); + assert.equal(answer.from, ''); + }); + }); }); From 63bb587f0b82305674a1c38f4239f82b8f7759b7 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Fri, 12 Jul 2019 11:10:16 -0700 Subject: [PATCH 13/27] Added error handler to getswaggerversion method -Test swagger method WIP --- lib/swagger.js | 3 ++ swagger.json | 1 - test/swagger.test.js | 76 ++++++++++++++++++++++++++++++++++++------- test/versions.test.js | 0 4 files changed, 68 insertions(+), 12 deletions(-) delete mode 100644 swagger.json delete mode 100644 test/versions.test.js diff --git a/lib/swagger.js b/lib/swagger.js index 47dd39843..5654d6fe6 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -103,6 +103,9 @@ exports.run = async function({ args, opts }) { if (option === 'update') return versionSelection; versionCleaned = await request.post(`${config.host}/api/v1/version`, options); + } else { + // console.log(e) + throw new Error('Error occurred while retrieving swagger version.'); } } return versionCleaned.version; diff --git a/swagger.json b/swagger.json deleted file mode 100644 index 816847f31..000000000 --- a/swagger.json +++ /dev/null @@ -1 +0,0 @@ -{"swagger":"2.0","info":{"description":"This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.","version":"1.0.0","title":"Swagger Petstore","termsOfService":"http://swagger.io/terms/","contact":{"email":"apiteam@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"}},"host":"petstore.swagger.io","basePath":"/v2","tags":[{"name":"pet","description":"Everything about your Pets","externalDocs":{"description":"Find out more","url":"http://swagger.io"}},{"name":"store","description":"Access to Petstore orders"},{"name":"user","description":"Operations about user","externalDocs":{"description":"Find out more about our store","url":"http://swagger.io"}}],"schemes":["http"],"paths":{"/pet":{"post":{"tags":["pet"],"summary":"Add a new pet to the store","description":"","operationId":"addPet","consumes":["application/json","application/xml"],"produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"put":{"tags":["pet"],"summary":"Update an existing pet","description":"","operationId":"updatePet","consumes":["application/json","application/xml"],"produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"},"405":{"description":"Validation exception"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByStatus":{"get":{"tags":["pet"],"summary":"Finds Pets by status","description":"Multiple status values can be provided with comma separated strings","operationId":"findPetsByStatus","produces":["application/xml","application/json"],"parameters":[{"name":"status","in":"query","description":"Status values that need to be considered for filter","required":true,"type":"array","items":{"type":"string","enum":["available","pending","sold"],"default":"available"},"collectionFormat":"multi"}],"responses":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400":{"description":"Invalid status value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByTags":{"get":{"tags":["pet"],"summary":"Finds Pets by tags","description":"Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.","operationId":"findPetsByTags","produces":["application/xml","application/json"],"parameters":[{"name":"tags","in":"query","description":"Tags to filter by","required":true,"type":"array","items":{"type":"string"},"collectionFormat":"multi"}],"responses":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400":{"description":"Invalid tag value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}],"deprecated":true}},"/pet/{petId}":{"get":{"tags":["pet"],"summary":"Find pet by ID","description":"Returns a single pet","operationId":"getPetById","produces":["application/xml","application/json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to return","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Pet"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"security":[{"api_key":[]}]},"post":{"tags":["pet"],"summary":"Updates a pet in the store with form data","description":"","operationId":"updatePetWithForm","consumes":["application/x-www-form-urlencoded"],"produces":["application/xml","application/json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet that needs to be updated","required":true,"type":"integer","format":"int64"},{"name":"name","in":"formData","description":"Updated name of the pet","required":false,"type":"string"},{"name":"status","in":"formData","description":"Updated status of the pet","required":false,"type":"string"}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"delete":{"tags":["pet"],"summary":"Deletes a pet","description":"","operationId":"deletePet","produces":["application/xml","application/json"],"parameters":[{"name":"api_key","in":"header","required":false,"type":"string"},{"name":"petId","in":"path","description":"Pet id to delete","required":true,"type":"integer","format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}/uploadImage":{"post":{"tags":["pet"],"summary":"uploads an image","description":"","operationId":"uploadFile","consumes":["multipart/form-data"],"produces":["application/json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to update","required":true,"type":"integer","format":"int64"},{"name":"additionalMetadata","in":"formData","description":"Additional data to pass to server","required":false,"type":"string"},{"name":"file","in":"formData","description":"file to upload","required":false,"type":"file"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/ApiResponse"}}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/store/inventory":{"get":{"tags":["store"],"summary":"Returns pet inventories by status","description":"Returns a map of status codes to quantities","operationId":"getInventory","produces":["application/json"],"parameters":[],"responses":{"200":{"description":"successful operation","schema":{"type":"object","additionalProperties":{"type":"integer","format":"int32"}}}},"security":[{"api_key":[]}]}},"/store/order":{"post":{"tags":["store"],"summary":"Place an order for a pet","description":"","operationId":"placeOrder","produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"order placed for purchasing the pet","required":true,"schema":{"$ref":"#/definitions/Order"}}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid Order"}}}},"/store/order/{orderId}":{"get":{"tags":["store"],"summary":"Find purchase order by ID","description":"For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions","operationId":"getOrderById","produces":["application/xml","application/json"],"parameters":[{"name":"orderId","in":"path","description":"ID of pet that needs to be fetched","required":true,"type":"integer","maximum":10.0,"minimum":1.0,"format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}},"delete":{"tags":["store"],"summary":"Delete purchase order by ID","description":"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors","operationId":"deleteOrder","produces":["application/xml","application/json"],"parameters":[{"name":"orderId","in":"path","description":"ID of the order that needs to be deleted","required":true,"type":"integer","minimum":1.0,"format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}}},"/user":{"post":{"tags":["user"],"summary":"Create user","description":"This can only be done by the logged in user.","operationId":"createUser","produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"Created user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"default":{"description":"successful operation"}}}},"/user/createWithArray":{"post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"","operationId":"createUsersWithArrayInput","produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/User"}}}],"responses":{"default":{"description":"successful operation"}}}},"/user/createWithList":{"post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"","operationId":"createUsersWithListInput","produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/User"}}}],"responses":{"default":{"description":"successful operation"}}}},"/user/login":{"get":{"tags":["user"],"summary":"Logs user into the system","description":"","operationId":"loginUser","produces":["application/xml","application/json"],"parameters":[{"name":"username","in":"query","description":"The user name for login","required":true,"type":"string"},{"name":"password","in":"query","description":"The password for login in clear text","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"type":"string"},"headers":{"X-Rate-Limit":{"type":"integer","format":"int32","description":"calls per hour allowed by the user"},"X-Expires-After":{"type":"string","format":"date-time","description":"date in UTC when token expires"}}},"400":{"description":"Invalid username/password supplied"}}}},"/user/logout":{"get":{"tags":["user"],"summary":"Logs out current logged in user session","description":"","operationId":"logoutUser","produces":["application/xml","application/json"],"parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/user/{username}":{"get":{"tags":["user"],"summary":"Get user by user name","description":"","operationId":"getUserByName","produces":["application/xml","application/json"],"parameters":[{"name":"username","in":"path","description":"The name that needs to be fetched. Use user1 for testing. ","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/User"}},"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}},"put":{"tags":["user"],"summary":"Updated user","description":"This can only be done by the logged in user.","operationId":"updateUser","produces":["application/xml","application/json"],"parameters":[{"name":"username","in":"path","description":"name that need to be updated","required":true,"type":"string"},{"in":"body","name":"body","description":"Updated user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"400":{"description":"Invalid user supplied"},"404":{"description":"User not found"}}},"delete":{"tags":["user"],"summary":"Delete user","description":"This can only be done by the logged in user.","operationId":"deleteUser","produces":["application/xml","application/json"],"parameters":[{"name":"username","in":"path","description":"The name that needs to be deleted","required":true,"type":"string"}],"responses":{"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}}}},"securityDefinitions":{"petstore_auth":{"type":"oauth2","authorizationUrl":"http://petstore.swagger.io/oauth/dialog","flow":"implicit","scopes":{"write:pets":"modify pets in your account","read:pets":"read your pets"}},"api_key":{"type":"apiKey","name":"api_key","in":"header"}},"definitions":{"Order":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"petId":{"type":"integer","format":"int64"},"quantity":{"type":"integer","format":"int32"},"shipDate":{"type":"string","format":"date-time"},"status":{"type":"string","description":"Order Status","enum":["placed","approved","delivered"]},"complete":{"type":"boolean","default":false}},"xml":{"name":"Order"}},"Category":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Category"}},"User":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"username":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"type":"integer","format":"int32","description":"User Status"}},"xml":{"name":"User"}},"Tag":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Tag"}},"Pet":{"type":"object","required":["name","photoUrls"],"properties":{"id":{"type":"integer","format":"int64"},"category":{"$ref":"#/definitions/Category"},"name":{"type":"string","example":"doggie"},"photoUrls":{"type":"array","xml":{"name":"photoUrl","wrapped":true},"items":{"type":"string"}},"tags":{"type":"array","xml":{"name":"tag","wrapped":true},"items":{"$ref":"#/definitions/Tag"}},"status":{"type":"string","description":"pet status in the store","enum":["available","pending","sold"]}},"xml":{"name":"Pet"}},"ApiResponse":{"type":"object","properties":{"code":{"type":"integer","format":"int32"},"type":{"type":"string"},"message":{"type":"string"}}}},"externalDocs":{"description":"Find out more about Swagger","url":"http://swagger.io"}} \ No newline at end of file diff --git a/test/swagger.test.js b/test/swagger.test.js index fe13df424..a733caf10 100644 --- a/test/swagger.test.js +++ b/test/swagger.test.js @@ -1,19 +1,24 @@ const nock = require('nock'); const config = require('config'); const fs = require('fs'); +const Enquirer = require('enquirer'); +// const promptHandler = require('../lib/prompts'); const swagger = require('../cli').bind(null, 'swagger'); const key = 'Xmw4bGctRVIQz7R7dQXqH9nQe5d0SPQs'; +const version = '1.0.0'; describe('swagger command', () => { + beforeAll(() => nock.disableNetConnect()); - afterAll(() => nock.cleanAll()); + afterEach(() => nock.cleanAll()); - it('should error if no api key provided', () => + it('should error if no api key provided', () => { expect(swagger(['./test/fixtures/swagger.json'], {})).rejects.toThrow( 'No api key provided. Please use --key', - )); + ); + }); it('should error if no file was provided or able to be discovered', () => { expect(swagger([], { key })).rejects.toThrow( @@ -24,7 +29,11 @@ describe('swagger command', () => { it('should POST a discovered file if none provided', () => { const mock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version: '1.0.0' }) .post('/api/v1/api-specification', body => body.match('form-data; name="spec"')) + .delayConnection(1000) .basicAuth({ user: key }) .reply(201, { body: '{ id: 1 }' }); @@ -35,49 +44,94 @@ describe('swagger command', () => { return swagger([], { key }).then(() => { fs.unlinkSync('./swagger.json'); - mock.done(); + return mock.done(); }); }); it('should error if API errors', async () => { const mock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version: '1.0.0' }) .post('/api/v1/api-specification', body => body.match('form-data; name="spec"')) + .delayConnection(1000) .basicAuth({ user: key }) .reply(400); - await expect(swagger(['./test/fixtures/swagger.json'], { key })).rejects.toThrow( - 'There was an error uploading!', - ); - mock.done(); + return expect(swagger(['./test/fixtures/swagger.json'], { key, version })) + .rejects.toThrow('There was an error uploading!') + .then(() => mock.done()); }); it('should POST to the swagger api if no id provided', () => { const mock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version: '1.0.0' }) .post('/api/v1/api-specification', body => body.match('form-data; name="spec"')) .basicAuth({ user: key }) .reply(201, { body: '{ id: 1 }' }); - return swagger(['./test/fixtures/swagger.json'], { key }).then(() => mock.done()); + return swagger(['./test/fixtures/swagger.json'], { key, version }).then(() => mock.done()); + }); + + xit('should request a version list if version is not found', async () => { + const enquirer = new Enquirer({ show: false }); + enquirer.on('prompt', async prompt => { + await prompt.keypress(null, { name: 'down' }); + await prompt.submit(); + }); + + nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(400); + + const mock = nock(config.host) + .get('/api/v1/version') + .basicAuth({ user: key }) + .reply(200, [{ version: '1.0.0' }]); + + return swagger(['./test/fixtures/swagger.json'], { key, version }).then(() => mock.done()); + }); + + it('should throw an error if getting version returns something other than 400', async () => { + const mock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(402); + + return expect(swagger(['./test/fixtures/swagger.json'], { key, version })) + .rejects.toThrowError() + .then(() => mock.done()); }); it('should PUT to the swagger api if id is provided', () => { const id = '5aa0409b7cf527a93bfb44df'; + const mock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version: '1.0.0' }) .put(`/api/v1/api-specification/${id}`, body => body.match('form-data; name="spec"')) .basicAuth({ user: key }) .reply(201, { body: '{ id: 1 }' }); - return swagger(['./test/fixtures/swagger.json'], { key, id }).then(() => mock.done()); + return swagger(['./test/fixtures/swagger.json'], { key, id, version }).then(() => mock.done()); }); it('should still work with `token`', () => { const id = '5aa0409b7cf527a93bfb44df'; + const mock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { body: `{ version: '1.0.0' }` }) .put(`/api/v1/api-specification/${id}`, body => body.match('form-data; name="spec"')) .basicAuth({ user: key }) .reply(201, { body: '{ id: 1 }' }); - return swagger(['./test/fixtures/swagger.json'], { token: `${key}-${id}` }).then(() => + return swagger(['./test/fixtures/swagger.json'], { token: `${key}-${id}`, version }).then(() => mock.done(), ); }); diff --git a/test/versions.test.js b/test/versions.test.js deleted file mode 100644 index e69de29bb..000000000 From b2a7c05c05e0a793caa1c97e5f90eacbb69f128c Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Fri, 12 Jul 2019 12:52:58 -0700 Subject: [PATCH 14/27] Prettier clean --- test/swagger.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/swagger.test.js b/test/swagger.test.js index a733caf10..21c4a812c 100644 --- a/test/swagger.test.js +++ b/test/swagger.test.js @@ -10,7 +10,6 @@ const key = 'Xmw4bGctRVIQz7R7dQXqH9nQe5d0SPQs'; const version = '1.0.0'; describe('swagger command', () => { - beforeAll(() => nock.disableNetConnect()); afterEach(() => nock.cleanAll()); From 25db007f046f497a5a75da73bc6449c3bf6fefb9 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Mon, 15 Jul 2019 13:08:56 -0700 Subject: [PATCH 15/27] Bulk Unit Testing: 1. Testbed covering individual version files 2. Major swagger testbed updates - WIP --- lib/swagger.js | 1 - lib/versions/create.js | 10 +- lib/versions/delete.js | 10 +- lib/versions/index.js | 9 +- lib/versions/update.js | 22 +--- lib/versions/versionId.js | 10 +- test/swagger.test.js | 24 ++-- test/versions.test.js | 249 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 267 insertions(+), 68 deletions(-) create mode 100644 test/versions.test.js diff --git a/lib/swagger.js b/lib/swagger.js index 5654d6fe6..89b5b59e2 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -104,7 +104,6 @@ exports.run = async function({ args, opts }) { versionCleaned = await request.post(`${config.host}/api/v1/version`, options); } else { - // console.log(e) throw new Error('Error occurred while retrieving swagger version.'); } } diff --git a/lib/versions/create.js b/lib/versions/create.js index f8fbfee2a..943decded 100644 --- a/lib/versions/create.js +++ b/lib/versions/create.js @@ -9,16 +9,8 @@ exports.weight = 4; exports.action = 'versions:create'; exports.run = async function({ opts }) { - let { key } = opts; let versionList; - const { version, codename, fork, main, beta, isPublic } = opts; - - if (!key && opts.token) { - console.warn( - 'Using `rdme` with --token has been deprecated. Please use --key and --id instead', - ); - [key] = opts.token.split('-'); - } + const { key, version, codename, fork, main, beta, isPublic } = opts; if (!key) { return Promise.reject(new Error('No api key provided. Please use --key')); diff --git a/lib/versions/delete.js b/lib/versions/delete.js index 36a1d1078..f363bbec4 100644 --- a/lib/versions/delete.js +++ b/lib/versions/delete.js @@ -7,15 +7,7 @@ exports.weight = 4; exports.action = 'versions:delete'; exports.run = async function({ opts }) { - let { key } = opts; - const { version } = opts; - - if (!key && opts.token) { - console.warn( - 'Using `rdme` with --token has been deprecated. Please use --key and --id instead', - ); - [key] = opts.token.split('-'); - } + const { key, version } = opts; if (!key) { return Promise.reject(new Error('No api key provided. Please use --key')); diff --git a/lib/versions/index.js b/lib/versions/index.js index 2fff9e8a3..adacb8904 100644 --- a/lib/versions/index.js +++ b/lib/versions/index.js @@ -6,14 +6,7 @@ exports.category = 'services'; exports.weight = 3; exports.run = function({ opts }) { - let { key } = opts; - - if (!key && opts.token) { - console.warn( - 'Using `rdme` with --token has been deprecated. Please use --key and --id instead', - ); - [key] = opts.token.split('-'); - } + const { key } = opts; if (!key) { return Promise.reject(new Error('No api key provided. Please use --key')); diff --git a/lib/versions/update.js b/lib/versions/update.js index 821510f3b..2186e4406 100644 --- a/lib/versions/update.js +++ b/lib/versions/update.js @@ -9,16 +9,7 @@ exports.weight = 4; exports.action = 'versions:update'; exports.run = async function({ opts }) { - let { key } = opts; - let versionList; - const { version, codename, fork, newVersion, main, beta, isPublic, deprecated } = opts; - - if (!key && opts.token) { - console.warn( - 'Using `rdme` with --token has been deprecated. Please use --key and --id instead.', - ); - [key] = opts.token.split('-'); - } + const { key, version, codename, newVersion, main, beta, isPublic, deprecated } = opts; if (!key) { return Promise.reject(new Error('No api key provided. Please use --key')); @@ -30,16 +21,7 @@ exports.run = async function({ opts }) { ); } - if (!fork) { - versionList = await request - .get(`${config.host}/api/v1/version`, { - json: true, - auth: { user: key }, - }) - .catch(err => Promise.reject(new Error(err))); - } - - const promptResponse = await prompt(promptOpts.createVersionPrompt(versionList, opts, true)); + const promptResponse = await prompt(promptOpts.createVersionPrompt(null, opts, true)); const options = { json: { codename: codename || '', diff --git a/lib/versions/versionId.js b/lib/versions/versionId.js index 0a1145baa..5882bac18 100644 --- a/lib/versions/versionId.js +++ b/lib/versions/versionId.js @@ -7,15 +7,7 @@ exports.weight = 4; exports.action = 'versions:versionId'; exports.run = function({ opts }) { - let { key } = opts; - const { version } = opts; - - if (!key && opts.token) { - console.warn( - 'Using `rdme` with --token has been deprecated. Please use --key and --id instead', - ); - [key] = opts.token.split('-'); - } + const { key, version } = opts; if (!key) { return Promise.reject(new Error('No api key provided. Please use --key')); diff --git a/test/swagger.test.js b/test/swagger.test.js index 21c4a812c..d689d09f1 100644 --- a/test/swagger.test.js +++ b/test/swagger.test.js @@ -1,13 +1,13 @@ const nock = require('nock'); const config = require('config'); const fs = require('fs'); -const Enquirer = require('enquirer'); -// const promptHandler = require('../lib/prompts'); +const promptHandler = require('../lib/prompts'); const swagger = require('../cli').bind(null, 'swagger'); const key = 'Xmw4bGctRVIQz7R7dQXqH9nQe5d0SPQs'; const version = '1.0.0'; +jest.mock('../lib/prompts'); describe('swagger command', () => { beforeAll(() => nock.disableNetConnect()); @@ -74,22 +74,22 @@ describe('swagger command', () => { return swagger(['./test/fixtures/swagger.json'], { key, version }).then(() => mock.done()); }); - xit('should request a version list if version is not found', async () => { - const enquirer = new Enquirer({ show: false }); - enquirer.on('prompt', async prompt => { - await prompt.keypress(null, { name: 'down' }); - await prompt.submit(); + it('should request a version list if version is not found', () => { + promptHandler.generatePrompts.mockResolvedValue({ + option: 'create', + versionSelection: '1.0.1', }); - nock(config.host) + const mock = nock(config.host) .get(`/api/v1/version/${version}`) .basicAuth({ user: key }) - .reply(400); - - const mock = nock(config.host) + .reply(400) .get('/api/v1/version') .basicAuth({ user: key }) - .reply(200, [{ version: '1.0.0' }]); + .reply(200, [{ version: '1.0.1' }]) + .post('/api/v1/version') + .basicAuth({ user: key }) + .reply(200, { version: '1.0.1' }); return swagger(['./test/fixtures/swagger.json'], { key, version }).then(() => mock.done()); }); diff --git a/test/versions.test.js b/test/versions.test.js new file mode 100644 index 000000000..2783d3776 --- /dev/null +++ b/test/versions.test.js @@ -0,0 +1,249 @@ +const nock = require('nock'); +const config = require('config'); +const assert = require('assert'); +const promptHandler = require('../lib/prompts'); + +const versions = require('../cli').bind(null, 'versions'); +const createVersion = require('../cli').bind(null, 'versions:create'); +const deleteVersion = require('../cli').bind(null, 'versions:delete'); +const updateVersion = require('../cli').bind(null, 'versions:update'); +const versionById = require('../cli').bind(null, 'versions:versionId'); + +jest.mock('../lib/prompts'); +const key = 'Xmw4bGctRVIQz7R7dQXqH9nQe5d0SPQs'; +const version = '1.0.0'; + +describe('Versions CLI Commands', () => { + beforeAll(() => nock.disableNetConnect()); + afterEach(() => nock.cleanAll()); + + describe('base command', () => { + it('should error if no api key provided', () => { + versions([], {}).catch(err => { + assert.equal(err.message, 'No api key provided. Please use --key'); + }); + }); + + it('should make a request to get a list of existing versions', async () => { + const mockRequest = nock(config.host) + .get('/api/v1/version') + .basicAuth({ user: key }) + .reply(200, [{ version }, { version }]); + + const versionsResponse = await versions([], { key }); + assert.equal(versionsResponse.length, 2); + mockRequest.done(); + }); + + it('should catch any request errors', async () => { + const mockRequest = nock(config.host) + .get('/api/v1/version') + .basicAuth({ user: key }) + .reply(400); + + await versions([], { key }).catch(err => { + assert.equal(err.message, 'Failed to get versions attached to the provided key.'); + }); + mockRequest.done(); + }); + }); + + describe('get version by id', () => { + it('should error if no api key provided', () => { + versionById([], {}).catch(err => { + assert.equal(err.message, 'No api key provided. Please use --key'); + }); + }); + + it('should error if no version provided', () => { + versionById([], { key }).catch(err => { + assert.equal( + err.message, + 'No version provided. Please specify a semantic version using --version', + ); + }); + }); + + it('should get a specific version object', async () => { + const mockRequest = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); + + await versionById([], { key, version }); + mockRequest.done(); + }); + + it('should catch any request errors', async () => { + const mockRequest = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(400); + + await versionById([], { key, version }).catch(err => { + assert.equal( + err.message, + 'Failed to get specific version using provided identifier and key.', + ); + }); + mockRequest.done(); + }); + }); + + describe('create new version', () => { + it('should error if no api key provided', () => { + createVersion([], {}).catch(err => { + assert.equal(err.message, 'No api key provided. Please use --key'); + }); + }); + + it('should error if no version provided', () => { + createVersion([], { key }).catch(err => { + assert.equal( + err.message, + 'No version provided. Please specify a semantic version using --version', + ); + }); + }); + + it('should get a specific version object', async () => { + promptHandler.createVersionPrompt.mockResolvedValue({ + is_stable: true, + is_beta: false, + from: '1.0.0', + }); + + const mockRequest = nock(config.host) + .get('/api/v1/version') + .basicAuth({ user: key }) + .reply(200, [{ version }, { version }]) + .post(`/api/v1/version`) + .basicAuth({ user: key }) + .reply(201, { version }); + + await createVersion([], { key, version }); + mockRequest.done(); + }); + + it('should throw an error if get versions fails', async () => { + const mockRequest = nock(config.host) + .get('/api/v1/version') + .basicAuth({ user: key }) + .reply(400); + + await createVersion([], { key, version }).catch(err => { + assert.equal(err.message, 'StatusCodeError: 400 - undefined'); + }); + mockRequest.done(); + }); + + it('should catch any post request errors', async () => { + promptHandler.createVersionPrompt.mockResolvedValue({ + is_stable: false, + is_beta: false, + }); + + const mockRequest = nock(config.host) + .post(`/api/v1/version`) + .basicAuth({ user: key }) + .reply(400); + + await createVersion([], { key, version, fork: '0.0.5' }).catch(err => { + assert.equal( + err.message, + 'Failed to create a new version using your specified parameters.', + ); + }); + mockRequest.done(); + }); + }); + + describe('delete version', () => { + it('should error if no api key provided', () => { + deleteVersion([], {}).catch(err => { + assert.equal(err.message, 'No api key provided. Please use --key'); + }); + }); + + it('should error if no version provided', () => { + deleteVersion([], { key }).catch(err => { + assert.equal( + err.message, + 'No version provided. Please specify a semantic version using --version', + ); + }); + }); + + it('should delete a specific version', async () => { + const mockRequest = nock(config.host) + .delete(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(201); + + await deleteVersion([], { key, version }); + mockRequest.done(); + }); + + it('should catch any request errors', async () => { + const mockRequest = nock(config.host) + .delete(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(400); + + await deleteVersion([], { key, version }).catch(err => { + assert.equal(err.message, 'Failed to delete target version.'); + }); + mockRequest.done(); + }); + }); + + describe('create new version', () => { + it('should error if no api key provided', () => { + updateVersion([], {}).catch(err => { + assert.equal(err.message, 'No api key provided. Please use --key'); + }); + }); + + it('should error if no version provided', () => { + updateVersion([], { key }).catch(err => { + assert.equal( + err.message, + 'No version provided. Please specify a semantic version using --version', + ); + }); + }); + + it('should get a specific version object', async () => { + promptHandler.createVersionPrompt.mockResolvedValue({ + is_stable: false, + is_beta: false, + is_deprecated: true, + }); + + const mockRequest = nock(config.host) + .put(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(201, { version }); + + await updateVersion([], { key, version }); + mockRequest.done(); + }); + + it('should catch any post request errors', async () => { + promptHandler.createVersionPrompt.mockResolvedValue({ + is_stable: false, + is_beta: false, + }); + + const mockRequest = nock(config.host) + .put(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(400); + + await updateVersion([], { key, version }).catch(err => { + assert.equal(err.message, 'Failed to update version using your specified parameters.'); + }); + mockRequest.done(); + }); + }); +}); From 5df0bb893618f6c730fe7ab830defa2e3ba95f50 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Mon, 15 Jul 2019 13:16:40 -0700 Subject: [PATCH 16/27] removed depedency on get versions request for update function --- lib/versions/update.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/versions/update.js b/lib/versions/update.js index 2186e4406..02e17e876 100644 --- a/lib/versions/update.js +++ b/lib/versions/update.js @@ -21,7 +21,7 @@ exports.run = async function({ opts }) { ); } - const promptResponse = await prompt(promptOpts.createVersionPrompt(null, opts, true)); + const promptResponse = await prompt(promptOpts.createVersionPrompt([{}], opts, true)); const options = { json: { codename: codename || '', From ce79af3725bb78e501fac161b77086934735e0f0 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Mon, 15 Jul 2019 14:05:07 -0700 Subject: [PATCH 17/27] Completed swagger file testing --- test/swagger.test.js | 5 ++++- test/versions.test.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test/swagger.test.js b/test/swagger.test.js index d689d09f1..18a6dc10c 100644 --- a/test/swagger.test.js +++ b/test/swagger.test.js @@ -89,7 +89,10 @@ describe('swagger command', () => { .reply(200, [{ version: '1.0.1' }]) .post('/api/v1/version') .basicAuth({ user: key }) - .reply(200, { version: '1.0.1' }); + .reply(200, { version: '1.0.1' }) + .post('/api/v1/api-specification', body => body.match('form-data; name="spec"')) + .basicAuth({ user: key }) + .reply(201, { id: 1 }); return swagger(['./test/fixtures/swagger.json'], { key, version }).then(() => mock.done()); }); diff --git a/test/versions.test.js b/test/versions.test.js index 2783d3776..9ab0779c4 100644 --- a/test/versions.test.js +++ b/test/versions.test.js @@ -178,7 +178,7 @@ describe('Versions CLI Commands', () => { const mockRequest = nock(config.host) .delete(`/api/v1/version/${version}`) .basicAuth({ user: key }) - .reply(201); + .reply(200); await deleteVersion([], { key, version }); mockRequest.done(); From b17af487e71ea50c1a3b8a115abbe0da202e1aa6 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Mon, 15 Jul 2019 17:17:53 -0700 Subject: [PATCH 18/27] removed unnecessary eslint disabled lines --- lib/prompts.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/prompts.js b/lib/prompts.js index cd9794dc9..8a5dc3547 100644 --- a/lib/prompts.js +++ b/lib/prompts.js @@ -19,7 +19,6 @@ exports.generatePrompts = versionList => [ choices: versionList.map(v => { return { message: v.version, - // eslint-disable-next-line value: v.version, }; }), @@ -37,7 +36,6 @@ exports.createVersionPrompt = (versionList, opts, isUpdate) => [ choices: versionList.map(v => { return { message: v.version, - // eslint-disable-next-line value: v.version, }; }), From 7a969e81a05aad16df8e02b8b72d171aadb6d621 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Mon, 15 Jul 2019 18:18:13 -0700 Subject: [PATCH 19/27] Minor fixes: 1. Copy edit 2. QOL with main version interaction 3. Test case update --- README.md | 2 +- lib/prompts.js | 4 +++- lib/versions/update.js | 14 ++++++++++++-- test/versions.test.js | 8 +++++++- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index eb21a544e..603f27cf9 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ rdme versions:create --key={api-key} --version={project-version} #### Update a version The command to update a version takes the same flags as creating a new version ```sh -rdme versions:create --key={api-key} --version={project-version} +rdme versions:update --key={api-key} --version={project-version} ``` #### Delete a version diff --git a/lib/prompts.js b/lib/prompts.js index 8a5dc3547..6339c8582 100644 --- a/lib/prompts.js +++ b/lib/prompts.js @@ -53,7 +53,9 @@ exports.createVersionPrompt = (versionList, opts, isUpdate) => [ type: 'confirm', name: 'is_stable', message: 'Would you like to make this version the main version for this project?', - skip: () => opts.main, + skip() { + return opts.main || (isUpdate && isUpdate.is_stable); + }, }, { type: 'confirm', diff --git a/lib/versions/update.js b/lib/versions/update.js index 02e17e876..0b3deee42 100644 --- a/lib/versions/update.js +++ b/lib/versions/update.js @@ -21,12 +21,22 @@ exports.run = async function({ opts }) { ); } - const promptResponse = await prompt(promptOpts.createVersionPrompt([{}], opts, true)); + const foundVersion = await request.get(`${config.host}/api/v1/version/${version}`, { + auth: { user: key }, + }); + + const promptResponse = await prompt( + promptOpts.createVersionPrompt( + [{}], + opts, + foundVersion ? JSON.parse(foundVersion) : foundVersion, + ), + ); const options = { json: { codename: codename || '', version: newVersion || promptResponse.newVersion, - is_stable: main || promptResponse.is_stable, + is_stable: foundVersion.is_stable || main || promptResponse.is_stable, is_beta: beta || promptResponse.is_beta, is_deprecated: deprecated || promptResponse.is_deprecated, is_hidden: promptResponse.is_stable ? false : !(isPublic || promptResponse.is_hidden), diff --git a/test/versions.test.js b/test/versions.test.js index 9ab0779c4..8a3a1485c 100644 --- a/test/versions.test.js +++ b/test/versions.test.js @@ -197,7 +197,7 @@ describe('Versions CLI Commands', () => { }); }); - describe('create new version', () => { + describe('update version', () => { it('should error if no api key provided', () => { updateVersion([], {}).catch(err => { assert.equal(err.message, 'No api key provided. Please use --key'); @@ -221,6 +221,9 @@ describe('Versions CLI Commands', () => { }); const mockRequest = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }) .put(`/api/v1/version/${version}`) .basicAuth({ user: key }) .reply(201, { version }); @@ -236,6 +239,9 @@ describe('Versions CLI Commands', () => { }); const mockRequest = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }) .put(`/api/v1/version/${version}`) .basicAuth({ user: key }) .reply(400); From b34abe0afe9b7e571cd021c244113f1b64afbe28 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Mon, 15 Jul 2019 21:05:52 -0700 Subject: [PATCH 20/27] changed header key --- lib/swagger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/swagger.js b/lib/swagger.js index 89b5b59e2..92e5a8ac3 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -59,7 +59,7 @@ exports.run = async function({ args, opts }) { spec: fs.createReadStream(path.resolve(process.cwd(), specPath)), }, headers: { - version: versionCleaned, + 'x-readme-version': versionCleaned, }, auth: { user: key }, resolveWithFullResponse: true, From f112ea1aa9c419bc4ec15859c41ef75bed05e696 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Tue, 16 Jul 2019 11:33:27 -0700 Subject: [PATCH 21/27] Fix review comments: 1. Error handling for dup creation 2. Help Params for versions 3. Copy --- lib/versions/create.js | 22 +++++++++++++--------- lib/versions/delete.js | 1 + lib/versions/index.js | 1 + lib/versions/versionId.js | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/versions/create.js b/lib/versions/create.js index 943decded..9b19d2ee5 100644 --- a/lib/versions/create.js +++ b/lib/versions/create.js @@ -44,13 +44,17 @@ exports.run = async function({ opts }) { auth: { user: key }, }; - return request.post(`${config.host}/api/v1/version`, options).catch(err => { - let errorDesc; - try { - errorDesc = JSON.parse(err.error).description; - } catch (e) { - errorDesc = 'Failed to create a new version using your specified parameters.'; - } - return Promise.reject(new Error(errorDesc)); - }); + return request + .post(`${config.host}/api/v1/version`, options) + .then(() => Promise.resolve(`Version ${version} created successfully`)) + .catch(err => { + let errorDesc; + try { + errorDesc = + typeof err.error === 'string' ? JSON.parse(err.error).description : err.error.description; + } catch (e) { + errorDesc = 'Failed to create a new version using your specified parameters.'; + } + return Promise.reject(new Error(errorDesc)); + }); }; diff --git a/lib/versions/delete.js b/lib/versions/delete.js index f363bbec4..0888c9bba 100644 --- a/lib/versions/delete.js +++ b/lib/versions/delete.js @@ -23,6 +23,7 @@ exports.run = async function({ opts }) { .delete(`${config.host}/api/v1/version/${version}`, { auth: { user: key }, }) + .then(() => Promise.resolve(`Version ${version} deleted successfully`)) .catch(err => { let errorDesc; try { diff --git a/lib/versions/index.js b/lib/versions/index.js index adacb8904..19ef67ea0 100644 --- a/lib/versions/index.js +++ b/lib/versions/index.js @@ -4,6 +4,7 @@ const config = require('config'); exports.desc = 'List versions available in your project'; exports.category = 'services'; exports.weight = 3; +exports.action = 'versions'; exports.run = function({ opts }) { const { key } = opts; diff --git a/lib/versions/versionId.js b/lib/versions/versionId.js index 5882bac18..4371f9b46 100644 --- a/lib/versions/versionId.js +++ b/lib/versions/versionId.js @@ -1,7 +1,7 @@ const request = require('request-promise-native'); const config = require('config'); -exports.desc = 'List versions available in your project'; +exports.desc = 'Get a specific version from your project'; exports.category = 'services'; exports.weight = 4; exports.action = 'versions:versionId'; From d5997b9e66265b578e6eed2010118e4553b7494e Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Tue, 16 Jul 2019 15:34:33 -0700 Subject: [PATCH 22/27] parse int fix for cli args --- rdme.js | 1 + 1 file changed, 1 insertion(+) diff --git a/rdme.js b/rdme.js index 1d84ba25d..00fab7fcb 100755 --- a/rdme.js +++ b/rdme.js @@ -2,6 +2,7 @@ require('colors'); const parseArgs = require('minimist')(process.argv.slice(2), { + string: 'version', alias: { // Allows --version, -v, -V v: 'version', From a379ba74b0c520dcfa90315e16faaf7b3341decd Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Wed, 17 Jul 2019 09:59:52 -0700 Subject: [PATCH 23/27] Migrated versionId into main version command, cleaned error handling, fixed fork parseint --- README.md | 2 +- lib/versions/create.js | 17 ++++++++-------- lib/versions/delete.js | 14 ++++++------- lib/versions/index.js | 24 +++++++++++++--------- lib/versions/update.js | 21 +++++++++++-------- lib/versions/versionId.js | 36 -------------------------------- rdme.js | 2 +- test/versions.test.js | 43 +++++---------------------------------- 8 files changed, 48 insertions(+), 111 deletions(-) delete mode 100644 lib/versions/versionId.js diff --git a/README.md b/README.md index 603f27cf9..e13157caa 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ rdme versions --key={api-key} #### Get all information about a particular version ```sh -rdme versions:versionId --key={api-key} --version={project-version} +rdme versions --key={api-key} --version={project-version} ``` #### Create a new version using flags diff --git a/lib/versions/create.js b/lib/versions/create.js index 9b19d2ee5..225e5a5a0 100644 --- a/lib/versions/create.js +++ b/lib/versions/create.js @@ -31,7 +31,7 @@ exports.run = async function({ opts }) { .catch(err => Promise.reject(new Error(err))); } - const promptResponse = await prompt(promptOpts.createVersionPrompt(versionList, opts)); + const promptResponse = await prompt(promptOpts.createVersionPrompt(versionList || [{}], opts)); const options = { json: { version, @@ -48,13 +48,12 @@ exports.run = async function({ opts }) { .post(`${config.host}/api/v1/version`, options) .then(() => Promise.resolve(`Version ${version} created successfully`)) .catch(err => { - let errorDesc; - try { - errorDesc = - typeof err.error === 'string' ? JSON.parse(err.error).description : err.error.description; - } catch (e) { - errorDesc = 'Failed to create a new version using your specified parameters.'; - } - return Promise.reject(new Error(errorDesc)); + return Promise.reject( + new Error( + err.error && err.error.description + ? err.error.description + : 'Failed to create a new version using your specified parameters.', + ), + ); }); }; diff --git a/lib/versions/delete.js b/lib/versions/delete.js index 0888c9bba..31e009b81 100644 --- a/lib/versions/delete.js +++ b/lib/versions/delete.js @@ -25,12 +25,12 @@ exports.run = async function({ opts }) { }) .then(() => Promise.resolve(`Version ${version} deleted successfully`)) .catch(err => { - let errorDesc; - try { - errorDesc = JSON.parse(err.error).description; - } catch (e) { - errorDesc = 'Failed to delete target version.'; - } - return Promise.reject(new Error(errorDesc)); + return Promise.reject( + new Error( + err.error && err.error.description + ? err.error.description + : 'Failed to delete target version.', + ), + ); }); }; diff --git a/lib/versions/index.js b/lib/versions/index.js index 19ef67ea0..8a461329e 100644 --- a/lib/versions/index.js +++ b/lib/versions/index.js @@ -1,30 +1,34 @@ const request = require('request-promise-native'); const config = require('config'); -exports.desc = 'List versions available in your project'; +exports.desc = 'List versions available in your project or get version by semver'; exports.category = 'services'; exports.weight = 3; exports.action = 'versions'; exports.run = function({ opts }) { - const { key } = opts; + const { key, version } = opts; if (!key) { return Promise.reject(new Error('No api key provided. Please use --key')); } + const uri = version + ? `${config.host}/api/v1/version/${version}` + : `${config.host}/api/v1/version`; + return request - .get(`${config.host}/api/v1/version`, { + .get(uri, { json: true, auth: { user: key }, }) .catch(err => { - let errorDesc; - try { - errorDesc = JSON.parse(err.error).description; - } catch (e) { - errorDesc = 'Failed to get versions attached to the provided key.'; - } - return Promise.reject(new Error(errorDesc)); + return Promise.reject( + new Error( + err.error && err.error.description + ? err.error.description + : 'Failed to get version(s) attached to the provided key.', + ), + ); }); }; diff --git a/lib/versions/update.js b/lib/versions/update.js index 0b3deee42..ab9d29acf 100644 --- a/lib/versions/update.js +++ b/lib/versions/update.js @@ -44,13 +44,16 @@ exports.run = async function({ opts }) { auth: { user: key }, }; - return request.put(`${config.host}/api/v1/version/${version}`, options).catch(err => { - let errorDesc; - try { - errorDesc = JSON.parse(err.error).description; - } catch (e) { - errorDesc = 'Failed to update version using your specified parameters.'; - } - return Promise.reject(new Error(errorDesc)); - }); + return request + .put(`${config.host}/api/v1/version/${version}`, options) + .then(() => Promise.resolve(`Version ${version} updated successfully`)) + .catch(err => { + return Promise.reject( + new Error( + err.error && err.error.description + ? err.error.description + : 'Failed to update version using your specified parameters.', + ), + ); + }); }; diff --git a/lib/versions/versionId.js b/lib/versions/versionId.js deleted file mode 100644 index 4371f9b46..000000000 --- a/lib/versions/versionId.js +++ /dev/null @@ -1,36 +0,0 @@ -const request = require('request-promise-native'); -const config = require('config'); - -exports.desc = 'Get a specific version from your project'; -exports.category = 'services'; -exports.weight = 4; -exports.action = 'versions:versionId'; - -exports.run = function({ opts }) { - const { key, version } = opts; - - if (!key) { - return Promise.reject(new Error('No api key provided. Please use --key')); - } - - if (!version) { - return Promise.reject( - new Error('No version provided. Please specify a semantic version using --version'), - ); - } - - return request - .get(`${config.host}/api/v1/version/${version}`, { - json: true, - auth: { user: key }, - }) - .catch(err => { - let errorDesc; - try { - errorDesc = JSON.parse(err.error).description; - } catch (e) { - errorDesc = 'Failed to get specific version using provided identifier and key.'; - } - return Promise.reject(new Error(errorDesc)); - }); -}; diff --git a/rdme.js b/rdme.js index 00fab7fcb..7a3d6525c 100755 --- a/rdme.js +++ b/rdme.js @@ -2,7 +2,7 @@ require('colors'); const parseArgs = require('minimist')(process.argv.slice(2), { - string: 'version', + string: ['version', 'fork'], alias: { // Allows --version, -v, -V v: 'version', diff --git a/test/versions.test.js b/test/versions.test.js index 8a3a1485c..6b2fd4033 100644 --- a/test/versions.test.js +++ b/test/versions.test.js @@ -7,7 +7,6 @@ const versions = require('../cli').bind(null, 'versions'); const createVersion = require('../cli').bind(null, 'versions:create'); const deleteVersion = require('../cli').bind(null, 'versions:delete'); const updateVersion = require('../cli').bind(null, 'versions:update'); -const versionById = require('../cli').bind(null, 'versions:versionId'); jest.mock('../lib/prompts'); const key = 'Xmw4bGctRVIQz7R7dQXqH9nQe5d0SPQs'; @@ -35,56 +34,24 @@ describe('Versions CLI Commands', () => { mockRequest.done(); }); - it('should catch any request errors', async () => { - const mockRequest = nock(config.host) - .get('/api/v1/version') - .basicAuth({ user: key }) - .reply(400); - - await versions([], { key }).catch(err => { - assert.equal(err.message, 'Failed to get versions attached to the provided key.'); - }); - mockRequest.done(); - }); - }); - - describe('get version by id', () => { - it('should error if no api key provided', () => { - versionById([], {}).catch(err => { - assert.equal(err.message, 'No api key provided. Please use --key'); - }); - }); - - it('should error if no version provided', () => { - versionById([], { key }).catch(err => { - assert.equal( - err.message, - 'No version provided. Please specify a semantic version using --version', - ); - }); - }); - - it('should get a specific version object', async () => { + it('should get a specific version object if version flag provided', async () => { const mockRequest = nock(config.host) .get(`/api/v1/version/${version}`) .basicAuth({ user: key }) .reply(200, { version }); - await versionById([], { key, version }); + await versions([], { key, version }); mockRequest.done(); }); it('should catch any request errors', async () => { const mockRequest = nock(config.host) - .get(`/api/v1/version/${version}`) + .get('/api/v1/version') .basicAuth({ user: key }) .reply(400); - await versionById([], { key, version }).catch(err => { - assert.equal( - err.message, - 'Failed to get specific version using provided identifier and key.', - ); + await versions([], { key }).catch(err => { + assert.equal(err.message, 'Failed to get version(s) attached to the provided key.'); }); mockRequest.done(); }); From 07454fad7640e5e035c261e97b5c5ba019295e48 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Wed, 17 Jul 2019 16:05:02 -0700 Subject: [PATCH 24/27] Modified swagger implementation for cli v non cli usage --- lib/prompts.js | 11 +++++++++- lib/swagger.js | 50 +++++++++++++++++++----------------------- lib/versions/create.js | 2 +- lib/versions/update.js | 17 ++++++-------- test/prompts.test.js | 6 +++++ test/swagger.test.js | 29 +++++++++--------------- test/versions.test.js | 12 ---------- 7 files changed, 57 insertions(+), 70 deletions(-) diff --git a/lib/prompts.js b/lib/prompts.js index 6339c8582..bcd47051a 100644 --- a/lib/prompts.js +++ b/lib/prompts.js @@ -3,7 +3,7 @@ exports.generatePrompts = versionList => [ type: 'select', name: 'option', message: - "We couldn't find a version in ReadMe matching the version in your OAS file. Would you like to use an existing version or create a new one?", + 'Would you like to use an existing version or create a new one to associate with your OAS file?', choices: [ { message: 'Use existing', value: 'update' }, { message: 'Create a new version', value: 'create' }, @@ -23,6 +23,15 @@ exports.generatePrompts = versionList => [ }; }), }, + { + type: 'input', + name: 'newVersion', + message: "What's your new version?", + skip() { + return this.enquirer.answers.option === 'update'; + }, + hint: '1.0.0', + }, ]; exports.createVersionPrompt = (versionList, opts, isUpdate) => [ diff --git a/lib/swagger.js b/lib/swagger.js index 92e5a8ac3..6e1f997f8 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -76,42 +76,38 @@ exports.run = async function({ args, opts }) { .then(success, error); } - async function getSwaggerVersion(specPath, versionFlag) { - let versionSpec; - let versionCleaned; - - if (!versionFlag) { - const file = fs.readFileSync(path.resolve(process.cwd(), specPath), 'utf8'); - versionSpec = JSON.parse(file).info.version; - } - - const options = { json: { version: versionSpec || versionFlag }, auth: { user: key } }; + async function getSwaggerVersion(versionFlag) { + const options = { json: {}, auth: { user: key } }; try { - versionCleaned = await request.get( - `${config.host}/api/v1/version/${versionSpec || versionFlag}`, - options, + if (versionFlag) { + options.json.version = versionFlag; + const foundVersion = await request.get( + `${config.host}/api/v1/version/${versionFlag}`, + options, + ); + + return foundVersion.version; + } + + const versionList = await request.get(`${config.host}/api/v1/version`, options); + const { option, versionSelection, newVersion } = await prompt( + promptOpts.generatePrompts(versionList, versionFlag), ); - } catch (e) { - if (e.statusCode === 400) { - const versionList = await request.get(`${config.host}/api/v1/version`, options); - options.json.from = versionList[0].version; - const promptResponse = await prompt(promptOpts.generatePrompts(versionList)); - const { option, versionSelection } = promptResponse; + if (option === 'update') return versionSelection; - if (option === 'update') return versionSelection; + options.json = { from: versionList[0].version, version: newVersion, is_stable: false }; + await request.post(`${config.host}/api/v1/version`, options); - versionCleaned = await request.post(`${config.host}/api/v1/version`, options); - } else { - throw new Error('Error occurred while retrieving swagger version.'); - } + return newVersion; + } catch (e) { + return Promise.reject(e.error); } - return versionCleaned.version; } if (args[0]) { - const selectedVersion = await getSwaggerVersion(args[0], version); + const selectedVersion = await getSwaggerVersion(version); return callApi(args[0], selectedVersion); } @@ -129,7 +125,7 @@ exports.run = async function({ args, opts }) { }); if (foundFile) { - const selectedVersion = await getSwaggerVersion(foundFile, version); + const selectedVersion = await getSwaggerVersion(version); resolve(callApi(foundFile, selectedVersion)); } diff --git a/lib/versions/create.js b/lib/versions/create.js index 225e5a5a0..c740a4c5d 100644 --- a/lib/versions/create.js +++ b/lib/versions/create.js @@ -28,7 +28,7 @@ exports.run = async function({ opts }) { json: true, auth: { user: key }, }) - .catch(err => Promise.reject(new Error(err))); + .catch(e => Promise.reject(e.error)); } const promptResponse = await prompt(promptOpts.createVersionPrompt(versionList || [{}], opts)); diff --git a/lib/versions/update.js b/lib/versions/update.js index ab9d29acf..3840d1463 100644 --- a/lib/versions/update.js +++ b/lib/versions/update.js @@ -21,17 +21,14 @@ exports.run = async function({ opts }) { ); } - const foundVersion = await request.get(`${config.host}/api/v1/version/${version}`, { - auth: { user: key }, - }); + const foundVersion = await request + .get(`${config.host}/api/v1/version/${version}`, { + json: true, + auth: { user: key }, + }) + .catch(e => Promise.reject(e.error)); - const promptResponse = await prompt( - promptOpts.createVersionPrompt( - [{}], - opts, - foundVersion ? JSON.parse(foundVersion) : foundVersion, - ), - ); + const promptResponse = await prompt(promptOpts.createVersionPrompt([{}], opts, foundVersion)); const options = { json: { codename: codename || '', diff --git a/test/prompts.test.js b/test/prompts.test.js index 77c386cfd..930ed0232 100644 --- a/test/prompts.test.js +++ b/test/prompts.test.js @@ -29,6 +29,12 @@ describe('prompt test bed', () => { if (prompt.name === 'versionSelection') { assert.equal(await prompt.skip(), true); } + + if (prompt.name === 'newVersion') { + // eslint-disable-next-line + prompt.value = '1.2.1'; + await prompt.submit(); + } }); await enquirer.prompt(promptHandler.generatePrompts(versionlist)); diff --git a/test/swagger.test.js b/test/swagger.test.js index 18a6dc10c..e95a0d34a 100644 --- a/test/swagger.test.js +++ b/test/swagger.test.js @@ -28,9 +28,12 @@ describe('swagger command', () => { it('should POST a discovered file if none provided', () => { const mock = nock(config.host) - .get(`/api/v1/version/${version}`) + .get(`/api/v1/version`) .basicAuth({ user: key }) - .reply(200, { version: '1.0.0' }) + .reply(200, [{ version }]) + .post('/api/v1/version') + .basicAuth({ user: key }) + .reply(200, { from: '1.0.1', version: '1.0.1' }) .post('/api/v1/api-specification', body => body.match('form-data; name="spec"')) .delayConnection(1000) .basicAuth({ user: key }) @@ -74,38 +77,26 @@ describe('swagger command', () => { return swagger(['./test/fixtures/swagger.json'], { key, version }).then(() => mock.done()); }); + it('should return a 404 if version flag not found', () => {}); + it('should request a version list if version is not found', () => { promptHandler.generatePrompts.mockResolvedValue({ option: 'create', - versionSelection: '1.0.1', + newVersion: '1.0.1', }); const mock = nock(config.host) - .get(`/api/v1/version/${version}`) - .basicAuth({ user: key }) - .reply(400) .get('/api/v1/version') .basicAuth({ user: key }) .reply(200, [{ version: '1.0.1' }]) .post('/api/v1/version') .basicAuth({ user: key }) - .reply(200, { version: '1.0.1' }) + .reply(200, { from: '1.0.1', version: '1.0.1' }) .post('/api/v1/api-specification', body => body.match('form-data; name="spec"')) .basicAuth({ user: key }) .reply(201, { id: 1 }); - return swagger(['./test/fixtures/swagger.json'], { key, version }).then(() => mock.done()); - }); - - it('should throw an error if getting version returns something other than 400', async () => { - const mock = nock(config.host) - .get(`/api/v1/version/${version}`) - .basicAuth({ user: key }) - .reply(402); - - return expect(swagger(['./test/fixtures/swagger.json'], { key, version })) - .rejects.toThrowError() - .then(() => mock.done()); + return swagger(['./test/fixtures/swagger.json'], { key }).then(() => mock.done()); }); it('should PUT to the swagger api if id is provided', () => { diff --git a/test/versions.test.js b/test/versions.test.js index 6b2fd4033..1a60aae62 100644 --- a/test/versions.test.js +++ b/test/versions.test.js @@ -92,18 +92,6 @@ describe('Versions CLI Commands', () => { mockRequest.done(); }); - it('should throw an error if get versions fails', async () => { - const mockRequest = nock(config.host) - .get('/api/v1/version') - .basicAuth({ user: key }) - .reply(400); - - await createVersion([], { key, version }).catch(err => { - assert.equal(err.message, 'StatusCodeError: 400 - undefined'); - }); - mockRequest.done(); - }); - it('should catch any post request errors', async () => { promptHandler.createVersionPrompt.mockResolvedValue({ is_stable: false, From 5b101d33797ee2909588d365aae7d161e1935299 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Thu, 18 Jul 2019 14:55:10 -0700 Subject: [PATCH 25/27] modified to deal with swagger id --- lib/swagger.js | 7 +++++-- test/swagger.test.js | 32 +++++++++++++------------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/lib/swagger.js b/lib/swagger.js index 6e1f997f8..7e03d07a1 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -12,6 +12,7 @@ exports.weight = 2; exports.run = async function({ args, opts }) { const { version } = opts; let { key, id } = opts; + let selectedVersion; if (!key && opts.token) { console.warn( @@ -106,8 +107,11 @@ exports.run = async function({ args, opts }) { } } + if (!id) { + selectedVersion = await getSwaggerVersion(version); + } + if (args[0]) { - const selectedVersion = await getSwaggerVersion(version); return callApi(args[0], selectedVersion); } @@ -125,7 +129,6 @@ exports.run = async function({ args, opts }) { }); if (foundFile) { - const selectedVersion = await getSwaggerVersion(version); resolve(callApi(foundFile, selectedVersion)); } diff --git a/test/swagger.test.js b/test/swagger.test.js index e95a0d34a..e806358a8 100644 --- a/test/swagger.test.js +++ b/test/swagger.test.js @@ -13,19 +13,6 @@ describe('swagger command', () => { beforeAll(() => nock.disableNetConnect()); afterEach(() => nock.cleanAll()); - it('should error if no api key provided', () => { - expect(swagger(['./test/fixtures/swagger.json'], {})).rejects.toThrow( - 'No api key provided. Please use --key', - ); - }); - - it('should error if no file was provided or able to be discovered', () => { - expect(swagger([], { key })).rejects.toThrow( - "We couldn't find a Swagger or OpenAPI file.\n\n" + - 'Run `rdme swagger ./path/to/file` to upload an existing file or `rdme oas init` to create a fresh one!', - ); - }); - it('should POST a discovered file if none provided', () => { const mock = nock(config.host) .get(`/api/v1/version`) @@ -103,9 +90,6 @@ describe('swagger command', () => { const id = '5aa0409b7cf527a93bfb44df'; const mock = nock(config.host) - .get(`/api/v1/version/${version}`) - .basicAuth({ user: key }) - .reply(200, { version: '1.0.0' }) .put(`/api/v1/api-specification/${id}`, body => body.match('form-data; name="spec"')) .basicAuth({ user: key }) .reply(201, { body: '{ id: 1 }' }); @@ -117,9 +101,6 @@ describe('swagger command', () => { const id = '5aa0409b7cf527a93bfb44df'; const mock = nock(config.host) - .get(`/api/v1/version/${version}`) - .basicAuth({ user: key }) - .reply(200, { body: `{ version: '1.0.0' }` }) .put(`/api/v1/api-specification/${id}`, body => body.match('form-data; name="spec"')) .basicAuth({ user: key }) .reply(201, { body: '{ id: 1 }' }); @@ -128,4 +109,17 @@ describe('swagger command', () => { mock.done(), ); }); + + it('should error if no api key provided', () => { + expect(swagger(['./test/fixtures/swagger.json'], {})).rejects.toThrow( + 'No api key provided. Please use --key', + ); + }); + + it('should error if no file was provided or able to be discovered', () => { + expect(swagger([], { key })).rejects.toThrow( + "We couldn't find a Swagger or OpenAPI file.\n\n" + + 'Run `rdme swagger ./path/to/file` to upload an existing file or `rdme oas init` to create a fresh one!', + ); + }); }); From 996a4c1d76f965b4080f48fa8fd91c31eab58298 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Thu, 18 Jul 2019 15:16:34 -0700 Subject: [PATCH 26/27] Removed unnecessary conditional block --- lib/swagger.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/swagger.js b/lib/swagger.js index 7e03d07a1..0d1c79242 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -125,12 +125,8 @@ exports.run = async function({ args, opts }) { } console.log(`We found ${file} and are attempting to upload it.`.yellow); - foundFile = file; - }); - - if (foundFile) { resolve(callApi(foundFile, selectedVersion)); - } + }); reject( new Error( From 383feb576a4e38b8277934094af0d712c779cd14 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Thu, 18 Jul 2019 15:29:02 -0700 Subject: [PATCH 27/27] Fixed ref --- lib/swagger.js | 3 +-- test/swagger.test.js | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/swagger.js b/lib/swagger.js index 0d1c79242..e5f7d7bcb 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -118,14 +118,13 @@ exports.run = async function({ args, opts }) { // If the user didn't supply a specification, let's try to locate what they've got, and upload // that. If they don't have any, let's let the user know how they can get one going. return new Promise(async (resolve, reject) => { - let foundFile; ['swagger.json', 'swagger.yaml', 'openapi.json', 'openapi.yaml'].forEach(file => { if (!fs.existsSync(file)) { return; } console.log(`We found ${file} and are attempting to upload it.`.yellow); - resolve(callApi(foundFile, selectedVersion)); + resolve(callApi(file, selectedVersion)); }); reject( diff --git a/test/swagger.test.js b/test/swagger.test.js index e806358a8..0f04af64f 100644 --- a/test/swagger.test.js +++ b/test/swagger.test.js @@ -117,9 +117,6 @@ describe('swagger command', () => { }); it('should error if no file was provided or able to be discovered', () => { - expect(swagger([], { key })).rejects.toThrow( - "We couldn't find a Swagger or OpenAPI file.\n\n" + - 'Run `rdme swagger ./path/to/file` to upload an existing file or `rdme oas init` to create a fresh one!', - ); + expect(swagger([], { key })).rejects.toThrowError(); }); });