diff --git a/README.md b/README.md index 145caba99..e13157caa 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,30 @@ -# `rdme` - ReadMe's API CLI +# `rdme` - ReadMe's CLI -This is a CLI wrapper around [ReadMe's HTTP API](https://readme.readme.io/v2.0/reference). +[![](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) -[![](https://d3vv6lp55qjaqc.cloudfront.net/items/1M3C3j0I0s0j3T362344/Untitled-2.png)](https://readme.io) - -## Installation +### Table of Contents + * [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) + * [Opening A Project Spec](#open) + * [Future](#future) + +### About `rdme` +`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. + +## 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: @@ -21,31 +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} ``` -### Syncing a folder of markdown docs to ReadMe +#### 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` if you do. +```sh +rdme swagger --key={api-key} +``` + +### 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} ``` -### Open your ReadMe project in your browser +### Versions +#### Get all versions associated with your project +```sh +rdme versions --key={api-key} +``` +#### Get all information about a particular version +```sh +rdme versions --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} +``` + +#### Update a version +The command to update a version takes the same flags as creating a new version +```sh +rdme versions:update --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 @@ -53,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. diff --git a/lib/prompts.js b/lib/prompts.js new file mode 100644 index 000000000..bcd47051a --- /dev/null +++ b/lib/prompts.js @@ -0,0 +1,91 @@ +exports.generatePrompts = versionList => [ + { + type: 'select', + name: 'option', + message: + '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' }, + ], + }, + { + type: 'select', + name: 'versionSelection', + message: 'Select your desired version', + skip() { + return this.enquirer.answers.option !== 'update'; + }, + choices: versionList.map(v => { + return { + message: v.version, + value: v.version, + }; + }), + }, + { + 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) => [ + { + 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, + 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() { + return opts.main || (isUpdate && isUpdate.is_stable); + }, + }, + { + 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/swagger.js b/lib/swagger.js index cffb8e1a4..e5f7d7bcb 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -2,13 +2,17 @@ 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'; exports.category = 'services'; exports.weight = 2; -exports.run = function({ args, opts }) { +exports.run = async function({ args, opts }) { + const { version } = opts; let { key, id } = opts; + let selectedVersion; if (!key && opts.token) { console.warn( @@ -21,7 +25,7 @@ exports.run = function({ args, opts }) { return Promise.reject(new Error('No api key provided. Please use --key')); } - function callApi(specPath) { + function callApi(specPath, versionCleaned) { function success(data) { const message = !id ? "You've successfully uploaded a new swagger file to your ReadMe project!" @@ -55,6 +59,9 @@ exports.run = function({ args, opts }) { formData: { spec: fs.createReadStream(path.resolve(process.cwd(), specPath)), }, + headers: { + 'x-readme-version': versionCleaned, + }, auth: { user: key }, resolveWithFullResponse: true, }; @@ -70,21 +77,54 @@ exports.run = function({ args, opts }) { .then(success, error); } + async function getSwaggerVersion(versionFlag) { + const options = { json: {}, auth: { user: key } }; + + try { + 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), + ); + + 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); + + return newVersion; + } catch (e) { + return Promise.reject(e.error); + } + } + + if (!id) { + selectedVersion = await getSwaggerVersion(version); + } + if (args[0]) { - return callApi(args[0]); + 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) => { + ['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)); + resolve(callApi(file, selectedVersion)); }); reject( diff --git a/lib/versions/create.js b/lib/versions/create.js new file mode 100644 index 000000000..c740a4c5d --- /dev/null +++ b/lib/versions/create.js @@ -0,0 +1,59 @@ +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'; +exports.category = 'services'; +exports.weight = 4; +exports.action = 'versions:create'; + +exports.run = async function({ opts }) { + let versionList; + const { key, version, codename, fork, main, beta, isPublic } = 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'), + ); + } + + if (!fork) { + versionList = await request + .get(`${config.host}/api/v1/version`, { + json: true, + auth: { user: key }, + }) + .catch(e => Promise.reject(e.error)); + } + + const promptResponse = await prompt(promptOpts.createVersionPrompt(versionList || [{}], opts)); + const options = { + json: { + version, + codename: codename || '', + 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 }, + }; + + return request + .post(`${config.host}/api/v1/version`, options) + .then(() => Promise.resolve(`Version ${version} created successfully`)) + .catch(err => { + 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 new file mode 100644 index 000000000..31e009b81 --- /dev/null +++ b/lib/versions/delete.js @@ -0,0 +1,36 @@ +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 }) { + 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 + .delete(`${config.host}/api/v1/version/${version}`, { + auth: { user: key }, + }) + .then(() => Promise.resolve(`Version ${version} deleted successfully`)) + .catch(err => { + 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 new file mode 100644 index 000000000..8a461329e --- /dev/null +++ b/lib/versions/index.js @@ -0,0 +1,34 @@ +const request = require('request-promise-native'); +const config = require('config'); + +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, 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(uri, { + json: true, + auth: { user: key }, + }) + .catch(err => { + 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 new file mode 100644 index 000000000..3840d1463 --- /dev/null +++ b/lib/versions/update.js @@ -0,0 +1,56 @@ +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'; +exports.category = 'services'; +exports.weight = 4; +exports.action = 'versions:update'; + +exports.run = async function({ opts }) { + const { key, version, codename, newVersion, main, beta, isPublic, deprecated } = 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'), + ); + } + + 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)); + const options = { + json: { + codename: codename || '', + version: newVersion || promptResponse.newVersion, + 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), + }, + auth: { user: key }, + }; + + 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/package-lock.json b/package-lock.json index 20b9dde6e..a688c7071 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", @@ -1979,6 +1984,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", diff --git a/package.json b/package.json index 97c840789..c135caad0 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,13 @@ "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", "read": "^1.0.7", - "oas": "0.8.15", "request": "^2.88.0", "request-promise-native": "^1.0.5" }, diff --git a/rdme.js b/rdme.js index 1d84ba25d..7a3d6525c 100755 --- a/rdme.js +++ b/rdme.js @@ -2,6 +2,7 @@ require('colors'); const parseArgs = require('minimist')(process.argv.slice(2), { + string: ['version', 'fork'], alias: { // Allows --version, -v, -V v: 'version', diff --git a/test/prompts.test.js b/test/prompts.test.js new file mode 100644 index 000000000..930ed0232 --- /dev/null +++ b/test/prompts.test.js @@ -0,0 +1,99 @@ +const assert = require('assert'); +const Enquirer = require('enquirer'); +const promptHandler = require('../lib/prompts'); + +const versionlist = [ + { + version: '1', + }, + { + version: '2', + }, +]; + +describe('prompt test bed', () => { + let enquirer; + + beforeEach(() => { + enquirer = new Enquirer({ show: false }); + }); + + describe('generatePrompts()', () => { + 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.submit(); + } + + 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)); + }); + + 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 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, ''); + }); + }); +}); diff --git a/test/swagger.test.js b/test/swagger.test.js index fe13df424..0f04af64f 100644 --- a/test/swagger.test.js +++ b/test/swagger.test.js @@ -1,30 +1,28 @@ const nock = require('nock'); const config = require('config'); const fs = require('fs'); +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()); - afterAll(() => 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!', - ); - }); + afterEach(() => nock.cleanAll()); it('should POST a discovered file if none provided', () => { const mock = nock(config.host) + .get(`/api/v1/version`) + .basicAuth({ user: key }) + .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 }) .reply(201, { body: '{ id: 1 }' }); @@ -35,50 +33,90 @@ 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, 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', + newVersion: '1.0.1', + }); + + const mock = nock(config.host) + .get('/api/v1/version') + .basicAuth({ user: key }) + .reply(200, [{ version: '1.0.1' }]) + .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"')) + .basicAuth({ user: key }) + .reply(201, { id: 1 }); + return swagger(['./test/fixtures/swagger.json'], { key }).then(() => mock.done()); }); it('should PUT to the swagger api if id is provided', () => { const id = '5aa0409b7cf527a93bfb44df'; + const mock = nock(config.host) .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) .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(), ); }); + + 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.toThrowError(); + }); }); diff --git a/test/versions.test.js b/test/versions.test.js new file mode 100644 index 000000000..1a60aae62 --- /dev/null +++ b/test/versions.test.js @@ -0,0 +1,210 @@ +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'); + +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 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 versions([], { key, version }); + 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 version(s) attached to the provided 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 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(200); + + 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('update 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) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }) + .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) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }) + .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(); + }); + }); +});