Skip to content

Commit

Permalink
feat: rename swagger command to openapi and keep swagger as alias (#333)
Browse files Browse the repository at this point in the history
* chore: test that oas-normalize works as expected

* chore(deps): upgrading oas and oas-normalize

* fix: validate files before uploading

* feat: add openapi command and keep swagger as an alias

* refactor: update openapi command and tests

* chore: update wording

* test: fix test for related commands

* test: add test for swagger command

Co-authored-by: Jon Ursenbach <[email protected]>
  • Loading branch information
rahulhegdee and erunion authored Jul 6, 2021
1 parent f035b24 commit 04e7f93
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 187 deletions.
34 changes: 23 additions & 11 deletions __tests__/cmds/swagger.test.js → __tests__/cmds/openapi.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const config = require('config');
const fs = require('fs');
const promptHandler = require('../../src/lib/prompts');
const swagger = require('../../src/cmds/swagger');
const openapi = require('../../src/cmds/openapi');

const key = 'Xmw4bGctRVIQz7R7dQXqH9nQe5d0SPQs';
const version = '1.0.0';
Expand All @@ -13,7 +14,7 @@ const getCommandOutput = () => {
return [console.warn.mock.calls.join('\n\n'), console.log.mock.calls.join('\n\n')].filter(Boolean).join('\n\n');
};

describe('rdme swagger', () => {
describe('rdme openapi', () => {
const exampleRefLocation = `${config.host}/project/example-project/1.0.1/refs/ex`;

beforeAll(() => nock.disableNetConnect());
Expand Down Expand Up @@ -53,7 +54,7 @@ describe('rdme swagger', () => {
// to break.
fs.copyFileSync('./__tests__/__fixtures__/swagger.json', './swagger.json');

return swagger.run({ key }).then(() => {
return openapi.run({ key }).then(() => {
expect(console.log).toHaveBeenCalledTimes(2);

const output = getCommandOutput();
Expand Down Expand Up @@ -86,7 +87,7 @@ describe('rdme swagger', () => {
help: 'If you need help, email [email protected] and mention log "fake-metrics-uuid".',
});

return expect(swagger.run({ spec: './__tests__/__fixtures__/swagger.json', key, version }))
return expect(openapi.run({ spec: './__tests__/__fixtures__/swagger.json', key, version }))
.rejects.toThrow('The version you specified')
.then(() => mock.done());
});
Expand All @@ -103,7 +104,7 @@ describe('rdme swagger', () => {
.basicAuth({ user: key })
.reply(201, { _id: 1 }, { location: exampleRefLocation });

return swagger.run({ spec: './__tests__/__fixtures__/swagger.json', key, version }).then(() => {
return openapi.run({ spec: './__tests__/__fixtures__/swagger.json', key, version }).then(() => {
expect(console.log).toHaveBeenCalledTimes(1);

const output = getCommandOutput();
Expand Down Expand Up @@ -134,7 +135,7 @@ describe('rdme swagger', () => {
help: 'If you need help, email [email protected] and mention log "fake-metrics-uuid".',
});

return expect(swagger.run({ spec: './__tests__/__fixtures__/invalid-swagger.json', key, version }))
return expect(openapi.run({ spec: './__tests__/__fixtures__/invalid-swagger.json', key, version }))
.rejects.toThrow('README VALIDATION ERROR "x-samples-languages" must be of type "Array"')
.then(() => mock.done());
});
Expand All @@ -161,7 +162,7 @@ describe('rdme swagger', () => {
.basicAuth({ user: key })
.reply(201, { _id: 1 }, { location: exampleRefLocation });

return swagger.run({ spec: './__tests__/__fixtures__/swagger.json', key }).then(() => {
return openapi.run({ spec: './__tests__/__fixtures__/swagger.json', key }).then(() => {
mock.done();
});
});
Expand All @@ -174,7 +175,7 @@ describe('rdme swagger', () => {
.basicAuth({ user: key })
.reply(201, { body: '{ id: 1 }' });

return swagger.run({ spec: './__tests__/__fixtures__/swagger.json', key, id, version }).then(() => {
return openapi.run({ spec: './__tests__/__fixtures__/swagger.json', key, id, version }).then(() => {
mock.done();
});
});
Expand All @@ -187,7 +188,7 @@ describe('rdme swagger', () => {
.basicAuth({ user: key })
.reply(201, { id: 1 }, { location: exampleRefLocation });

return swagger.run({ spec: './__tests__/__fixtures__/swagger.json', token: `${key}-${id}`, version }).then(() => {
return openapi.run({ spec: './__tests__/__fixtures__/swagger.json', token: `${key}-${id}`, version }).then(() => {
expect(console.warn).toHaveBeenCalledTimes(1);
expect(console.log).toHaveBeenCalledTimes(1);

Expand All @@ -200,7 +201,7 @@ describe('rdme swagger', () => {
});

it('should error if no api key provided', async () => {
await expect(swagger.run({ spec: './__tests__/__fixtures__/swagger.json' })).rejects.toThrow(
await expect(openapi.run({ spec: './__tests__/__fixtures__/swagger.json' })).rejects.toThrow(
'No project API key provided. Please use `--key`.'
);
});
Expand All @@ -211,16 +212,27 @@ describe('rdme swagger', () => {
.basicAuth({ user: key })
.reply(200, { version: '1.0.0' });

await expect(swagger.run({ key, version })).rejects.toThrow(/We couldn't find a Swagger or OpenAPI file./);
await expect(openapi.run({ key, version })).rejects.toThrow(/We couldn't find a Swagger or OpenAPI file./);

mock.done();
});

it('should throw an error if file is invalid', async () => {
const id = '5aa0409b7cf527a93bfb44df';

await expect(swagger.run({ spec: './__tests__/__fixtures__/invalid-oas.json', key, id, version })).rejects.toThrow(
await expect(openapi.run({ spec: './__tests__/__fixtures__/invalid-oas.json', key, id, version })).rejects.toThrow(
'Token "Error" does not exist.'
);
});
});

describe('rdme swagger', () => {
it('should run `rdme openapi`', async () => {
const id = '5aa0409b7cf527a93bfb44df';

await expect(swagger.run({ spec: '', key, id, version })).rejects.toThrow(
"We couldn't find a Swagger or OpenAPI file.\n\n" +
'Run `rdme openapi ./path/to/file` to upload an existing file or `rdme oas init` to create a fresh one!'
);
});
});
6 changes: 1 addition & 5 deletions __tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,7 @@ describe('cli', () => {
});
});

it('should not show related commands on commands that have none', () => {
return cli(['swagger', '--help']).then(output => {
expect(output).not.toContain('Related commands');
});
});
it.todo('should not show related commands on commands that have none');
});

describe('subcommands', () => {
Expand Down
208 changes: 208 additions & 0 deletions src/cmds/openapi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
require('colors');
const request = require('request-promise-native');
const fs = require('fs');
const path = require('path');
const config = require('config');
const { prompt } = require('enquirer');
const OASNormalize = require('oas-normalize');
const promptOpts = require('../lib/prompts');
const APIError = require('../lib/apiError');

exports.command = 'openapi';
exports.usage = 'openapi [file] [options]';
exports.description = 'Upload, or sync, your Swagger/OpenAPI file to ReadMe.';
exports.category = 'apis';
exports.position = 1;

exports.hiddenArgs = ['token', 'spec'];
exports.args = [
{
name: 'key',
type: String,
description: 'Project API key',
},
{
name: 'id',
type: String,
description: `Unique identifier for your specification. Use this if you're resyncing an existing specification`,
},
{
name: 'token',
type: String,
description: 'Project token. Deprecated, please use `--key` instead',
},
{
name: 'version',
type: String,
description: 'Project version',
},
{
name: 'spec',
type: String,
defaultOption: true,
},
];

exports.run = async function (opts) {
const { spec, version } = opts;
let { key, id } = opts;
let selectedVersion;
let isUpdate;

if (!key && opts.token) {
console.warn('Using `rdme` with --token has been deprecated. Please use `--key` and `--id` instead.');

[key, id] = opts.token.split('-');
}

if (!key) {
return Promise.reject(new Error('No project API key provided. Please use `--key`.'));
}

async function callApi(specPath, versionCleaned) {
// @todo Tailor messaging to what is actually being handled here. If the user is uploading an OpenAPI file, never mention that they uploaded/updated a Swagger file.

function success(data) {
const message = !isUpdate
? "You've successfully uploaded a new Swagger file to your ReadMe project!"
: "You've successfully updated a Swagger file on your ReadMe project!";

console.log(
[
message,
'',
`\t${`${data.headers.location}`.green}`,
'',
'To update your Swagger or OpenAPI file, run the following:',
'',
// eslint-disable-next-line no-underscore-dangle
`\trdme openapi FILE --key=${key} --id=${JSON.parse(data.body)._id}`.green,
].join('\n')
);
}

function error(err) {
try {
const parsedError = JSON.parse(err.error);
return Promise.reject(new APIError(parsedError));
} catch (e) {
return Promise.reject(new Error('There was an error uploading!'));
}
}

const options = {
formData: {
spec: fs.createReadStream(path.resolve(process.cwd(), specPath)),
},
headers: {
'x-readme-version': versionCleaned,
'x-readme-source': 'cli',
},
auth: { user: key },
resolveWithFullResponse: true,
};

function createSpec() {
return request.post(`${config.host}/api/v1/api-specification`, options).then(success, error);
}

function updateSpec(specId) {
isUpdate = true;

return request.put(`${config.host}/api/v1/api-specification/${specId}`, options).then(success, error);
}

if (spec) {
const oas = new OASNormalize(spec, { enablePaths: true });
await oas.validate().catch(err => {
return Promise.reject(err);
});
}

/*
Create a new OAS file in Readme:
- Enter flow if user does not pass an id as cli arg
- Check to see if any existing files exist with a specific version
- If none exist, default to creating a new instance of a spec
- If found, prompt user to either create a new spec or update an existing one
*/

if (!id) {
const apiSettings = await request.get(`${config.host}/api/v1/api-specification`, {
headers: {
'x-readme-version': versionCleaned,
},
json: true,
auth: { user: key },
});

if (!apiSettings.length) return createSpec();

const { option, specId } = await prompt(promptOpts.createOasPrompt(apiSettings));
return option === 'create' ? createSpec() : updateSpec(specId);
}

/*
Update an existing OAS file in Readme:
- Enter flow if user passes an id as cli arg
*/
return updateSpec(id);
}

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 (err) {
return Promise.reject(new APIError(err));
}
}

if (!id) {
selectedVersion = await getSwaggerVersion(version).catch(e => {
return Promise.reject(e);
});
}

if (spec) {
return callApi(spec, 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(file => {
if (!fs.existsSync(file)) {
return;
}

console.log(`We found ${file} and are attempting to upload it.`.yellow);
resolve(callApi(file, selectedVersion));
});

reject(
new Error(
"We couldn't find a Swagger or OpenAPI file.\n\n" +
'Run `rdme openapi ./path/to/file` to upload an existing file or `rdme oas init` to create a fresh one!'
)
);
});
};
Loading

0 comments on commit 04e7f93

Please sign in to comment.