diff --git a/__tests__/__fixtures__/relative-ref-oas/components/external-components.json b/__tests__/__fixtures__/relative-ref-oas/components/external-components.json new file mode 100644 index 000000000..0a87758ce --- /dev/null +++ b/__tests__/__fixtures__/relative-ref-oas/components/external-components.json @@ -0,0 +1,179 @@ +{ + "requestBodies": { + "Pet": { + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/schemas/Pet" + } + } + }, + "description": "Pet object that needs to be added to the store", + "required": true + } + }, + "schemas": { + "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", + "default": 40, + "example": 25 + }, + "category": { + "$ref": "#/schemas/Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "name": "photoUrl", + "wrapped": true + }, + "items": { + "type": "string", + "example": "https://example.com/photo.png" + } + }, + "tags": { + "type": "array", + "xml": { + "name": "tag", + "wrapped": true + }, + "items": { + "$ref": "#/schemas/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "Pet" + } + } + } +} diff --git a/__tests__/__fixtures__/relative-ref-oas/petstore.json b/__tests__/__fixtures__/relative-ref-oas/petstore.json new file mode 100644 index 000000000..a060f83f6 --- /dev/null +++ b/__tests__/__fixtures__/relative-ref-oas/petstore.json @@ -0,0 +1,132 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "Example petstore to demo our handling of external $ref pointers" + }, + "servers": [ + { + "url": "http://petstore.swagger.io/v2" + } + ], + "paths": { + "/pet": { + "post": { + "tags": ["pet"], + "summary": "Add a new pet to the store", + "description": "", + "operationId": "addPet", + "requestBody": { + "$ref": "components/external-components.json#/requestBodies/Pet" + }, + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": ["write:pets", "read:pets"] + } + ] + }, + "put": { + "tags": ["pet"], + "summary": "Update an existing pet", + "description": "", + "operationId": "updatePet", + "requestBody": { + "$ref": "components/external-components.json#/requestBodies/Pet" + }, + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": ["write:pets", "read:pets"] + } + ] + } + }, + "/pet/{petId}": { + "get": { + "tags": ["pet"], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "components/external-components.json#/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "components/external-components.json#/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "default": { + "description": "successful response" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} diff --git a/__tests__/__snapshots__/index.test.js.snap b/__tests__/__snapshots__/index.test.js.snap index 6422ad648..cde9d198f 100644 --- a/__tests__/__snapshots__/index.test.js.snap +++ b/__tests__/__snapshots__/index.test.js.snap @@ -14,6 +14,7 @@ Options --id string Unique identifier for your API definition. Use this if you're re-uploading an existing API definition --version string Project version + --workdir string Directory as working directory -h, --help Display this usage guide Related commands @@ -183,6 +184,7 @@ Options --id string Unique identifier for your API definition. Use this if you're re-uploading an existing API definition --version string Project version + --workdir string Directory as working directory -h, --help Display this usage guide Related commands @@ -206,6 +208,7 @@ Options --id string Unique identifier for your API definition. Use this if you're re-uploading an existing API definition --version string Project version + --workdir string Directory as working directory -h, --help Display this usage guide Related commands diff --git a/__tests__/cmds/__snapshots__/openapi.test.js.snap b/__tests__/cmds/__snapshots__/openapi.test.js.snap index 2cdff9232..2f7ebc6ac 100644 --- a/__tests__/cmds/__snapshots__/openapi.test.js.snap +++ b/__tests__/cmds/__snapshots__/openapi.test.js.snap @@ -249,3 +249,239 @@ Object { ], } `; + +exports[`rdme openapi should use specified workdir and upload the expected content 1`] = ` +Object { + "components": Object { + "securitySchemes": Object { + "api_key": Object { + "in": "header", + "name": "api_key", + "type": "apiKey", + }, + "petstore_auth": Object { + "flows": Object { + "implicit": Object { + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "scopes": Object { + "read:pets": "read your pets", + "write:pets": "modify pets in your account", + }, + }, + }, + "type": "oauth2", + }, + }, + }, + "info": Object { + "title": "Example petstore to demo our handling of external $ref pointers", + "version": "1.0.0", + }, + "openapi": "3.0.0", + "paths": Object { + "/pet": Object { + "post": Object { + "description": "", + "operationId": "addPet", + "requestBody": Object { + "$ref": "#/paths/~1pet/put/requestBody", + }, + "responses": Object { + "405": Object { + "description": "Invalid input", + }, + }, + "security": Array [ + Object { + "petstore_auth": Array [ + "write:pets", + "read:pets", + ], + }, + ], + "summary": "Add a new pet to the store", + "tags": Array [ + "pet", + ], + }, + "put": Object { + "description": "", + "operationId": "updatePet", + "requestBody": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/paths/~1pet~1%7BpetId%7D/get/responses/200/content/application~1xml/schema", + }, + }, + "application/xml": Object { + "schema": Object { + "$ref": "#/paths/~1pet~1%7BpetId%7D/get/responses/200/content/application~1xml/schema", + }, + }, + }, + "description": "Pet object that needs to be added to the store", + "required": true, + }, + "responses": Object { + "400": Object { + "description": "Invalid ID supplied", + }, + "404": Object { + "description": "Pet not found", + }, + "405": Object { + "description": "Validation exception", + }, + }, + "security": Array [ + Object { + "petstore_auth": Array [ + "write:pets", + "read:pets", + ], + }, + ], + "summary": "Update an existing pet", + "tags": Array [ + "pet", + ], + }, + }, + "/pet/{petId}": Object { + "get": Object { + "description": "Returns a single pet", + "operationId": "getPetById", + "parameters": Array [ + Object { + "description": "ID of pet to return", + "in": "path", + "name": "petId", + "required": true, + "schema": Object { + "format": "int64", + "type": "integer", + }, + }, + ], + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/paths/~1pet~1%7BpetId%7D/get/responses/200/content/application~1xml/schema", + }, + }, + "application/xml": Object { + "schema": Object { + "properties": Object { + "category": Object { + "properties": Object { + "id": Object { + "format": "int64", + "type": "integer", + }, + "name": Object { + "type": "string", + }, + }, + "type": "object", + "xml": Object { + "name": "Category", + }, + }, + "id": Object { + "default": 40, + "example": 25, + "format": "int64", + "type": "integer", + }, + "name": Object { + "example": "doggie", + "type": "string", + }, + "photoUrls": Object { + "items": Object { + "example": "https://example.com/photo.png", + "type": "string", + }, + "type": "array", + "xml": Object { + "name": "photoUrl", + "wrapped": true, + }, + }, + "status": Object { + "description": "pet status in the store", + "enum": Array [ + "available", + "pending", + "sold", + ], + "type": "string", + }, + "tags": Object { + "items": Object { + "properties": Object { + "id": Object { + "format": "int64", + "type": "integer", + }, + "name": Object { + "type": "string", + }, + }, + "type": "object", + "xml": Object { + "name": "Tag", + }, + }, + "type": "array", + "xml": Object { + "name": "tag", + "wrapped": true, + }, + }, + }, + "required": Array [ + "name", + "photoUrls", + ], + "type": "object", + "xml": Object { + "name": "Pet", + }, + }, + }, + }, + "description": "successful operation", + }, + "400": Object { + "description": "Invalid ID supplied", + }, + "404": Object { + "description": "Pet not found", + }, + "default": Object { + "description": "successful response", + }, + }, + "security": Array [ + Object { + "api_key": Array [], + }, + ], + "summary": "Find pet by ID", + "tags": Array [ + "pet", + ], + }, + }, + }, + "servers": Array [ + Object { + "url": "http://petstore.swagger.io/v2", + }, + ], +} +`; diff --git a/__tests__/cmds/openapi.test.js b/__tests__/cmds/openapi.test.js index 37c70ebe7..b959d9951 100644 --- a/__tests__/cmds/openapi.test.js +++ b/__tests__/cmds/openapi.test.js @@ -33,6 +33,8 @@ const successfulUpdate = [ ...successfulMessageBase, ].join('\n'); +const testWorkingDir = process.cwd(); + jest.mock('../../src/lib/prompts'); const getCommandOutput = () => { @@ -52,6 +54,8 @@ describe('rdme openapi', () => { console.warn.mockRestore(); nock.cleanAll(); + + process.chdir(testWorkingDir); }); describe('upload', () => { @@ -282,6 +286,40 @@ describe('rdme openapi', () => { return mock.done(); }); + it('should use specified workdir and upload the expected content', async () => { + let requestBody = null; + const mock = getApiNock() + .get('/api/v1/api-specification') + .basicAuth({ user: key }) + .reply(200, []) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version: '1.0.0' }) + .post('/api/v1/api-specification', body => { + requestBody = body.substring(body.indexOf('{'), body.lastIndexOf('}') + 1); + requestBody = JSON.parse(requestBody); + + return body.match('form-data; name="spec"'); + }) + .basicAuth({ user: key }) + .reply(201, { _id: 1 }, { location: exampleRefLocation }); + + await expect( + openapi.run({ + spec: 'petstore.json', + key, + version, + workdir: './__tests__/__fixtures__/relative-ref-oas', + }) + ).resolves.toBe(successfulUpload); + + expect(console.info).toHaveBeenCalledTimes(0); + + expect(requestBody).toMatchSnapshot(); + + return mock.done(); + }); + describe('error handling', () => { it('should error if no api key provided', () => { return expect( diff --git a/__tests__/cmds/validate.test.js b/__tests__/cmds/validate.test.js index dfaa4cb0f..fc53bd6ed 100644 --- a/__tests__/cmds/validate.test.js +++ b/__tests__/cmds/validate.test.js @@ -2,6 +2,8 @@ const fs = require('fs'); const chalk = require('chalk'); const Command = require('../../src/cmds/validate'); +const testWorkingDir = process.cwd(); + const validate = new Command(); const getCommandOutput = () => { @@ -15,6 +17,8 @@ describe('rdme validate', () => { afterEach(() => { console.info.mockRestore(); + + process.chdir(testWorkingDir); }); it.each([ @@ -51,6 +55,15 @@ describe('rdme validate', () => { fs.unlinkSync('./swagger.json'); }); + it('should use specified workdir', async () => { + await expect( + validate.run({ + spec: 'petstore.json', + workdir: './__tests__/__fixtures__/relative-ref-oas', + }) + ).resolves.toBe(chalk.green('petstore.json is a valid OpenAPI API definition!')); + }); + describe('error handling', () => { it('should throw an error if an invalid OpenAPI 3.0 definition is supplied', () => { return expect(validate.run({ spec: './__tests__/__fixtures__/invalid-oas.json' })).rejects.toThrow( diff --git a/src/cmds/openapi.js b/src/cmds/openapi.js index 91352f764..a63cd8375 100644 --- a/src/cmds/openapi.js +++ b/src/cmds/openapi.js @@ -48,11 +48,16 @@ module.exports = class OpenAPICommand { type: String, defaultOption: true, }, + { + name: 'workdir', + type: String, + description: 'Directory as working directory', + }, ]; } async run(opts) { - const { spec, version } = opts; + const { spec, version, workdir } = opts; let { key, id } = opts; let selectedVersion; let isUpdate; @@ -60,6 +65,10 @@ module.exports = class OpenAPICommand { debug(`command: ${this.command}`); debug(`opts: ${JSON.stringify(opts)}`); + if (workdir) { + process.chdir(workdir); + } + if (!key && opts.token) { console.warn( chalk.yellow('⚠️ Warning! The `--token` option has been deprecated. Please use `--key` and `--id` instead.') diff --git a/src/cmds/validate.js b/src/cmds/validate.js index 74e900744..3ee22d2a5 100644 --- a/src/cmds/validate.js +++ b/src/cmds/validate.js @@ -18,11 +18,20 @@ module.exports = class ValidateCommand { type: String, defaultOption: true, }, + { + name: 'workdir', + type: String, + description: 'Directory as working directory', + }, ]; } async run(opts) { - const { spec } = opts; + const { spec, workdir } = opts; + + if (workdir) { + process.chdir(workdir); + } debug(`command: ${this.command}`); debug(`opts: ${JSON.stringify(opts)}`);