diff --git a/package-lock.json b/package-lock.json index aaf9392..2f51e14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "serverless-openapi-documenter", - "version": "0.0.121", + "version": "0.0.122", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "serverless-openapi-documenter", - "version": "0.0.121", + "version": "0.0.122", "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.1.0", diff --git a/package.json b/package.json index 0702139..508652d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless-openapi-documenter", - "version": "0.0.121", + "version": "0.0.122", "description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config", "main": "index.js", "keywords": [ diff --git a/src/definitionGenerator.js b/src/definitionGenerator.js index 355163a..1c36f53 100644 --- a/src/definitionGenerator.js +++ b/src/definitionGenerator.js @@ -167,11 +167,12 @@ class DefinitionGenerator { if (documentation.contact) { const contactObj = {}; - contactObj.name = documentation.contact.name || ""; + + if (documentation.contact.name) contactObj.name = documentation.contact.name; if (documentation.contact.url) contactObj.url = documentation.contact.url; - contactObj.email = documentation.contact.email || ""; + if (documentation.contact.email) contactObj.email = documentation.contact.email; const extendedSpec = this.extendSpecification(documentation.contact); @@ -591,7 +592,9 @@ class DefinitionGenerator { obj.headers = corsHeaders; addHeaders(owaspHeaders); } else { - obj.headers = owaspHeaders; + if (Object.keys(owaspHeaders).length) { + obj.headers = owaspHeaders; + } } } @@ -677,10 +680,13 @@ class DefinitionGenerator { async createRequestBody(requestBodyDetails) { const obj = { - description: requestBodyDetails.description, required: requestBodyDetails.required || false, }; + if (requestBodyDetails.description) { + obj.description = requestBodyDetails.description; + } + obj.content = await this.createMediaTypeObject( requestBodyDetails.models ).catch((err) => { @@ -692,7 +698,6 @@ class DefinitionGenerator { async createMediaTypeObject(models, type) { const mediaTypeObj = {}; - for (const mediaTypeDocumentation of this.schemaHandler.models) { if (models === undefined || models === null) { throw new Error( @@ -700,48 +705,53 @@ class DefinitionGenerator { ); } - if (Object.values(models).includes(mediaTypeDocumentation.name)) { - let contentKey = ""; - for (const [key, value] of Object.entries(models)) { - if (value === mediaTypeDocumentation.name) contentKey = key; + for (const modelContentType in models) { + let contentKey + + if (models[modelContentType] === mediaTypeDocumentation.name) { + contentKey = modelContentType; } - const obj = {}; - let schema; - if (mediaTypeDocumentation?.content) { - if (mediaTypeDocumentation.content[contentKey]?.example) - obj.example = mediaTypeDocumentation.content[contentKey].example; + if (contentKey) { - if (mediaTypeDocumentation.content[contentKey]?.examples) - obj.examples = this.createExamples( - mediaTypeDocumentation.content[contentKey].examples - ); + const obj = {}; + let schema; + if (mediaTypeDocumentation.content) { + if (mediaTypeDocumentation.content[contentKey]?.example) { + obj.example = mediaTypeDocumentation.content[contentKey]?.example; + } - schema = mediaTypeDocumentation.content[contentKey].schema; - } else if ( - mediaTypeDocumentation?.contentType && - mediaTypeDocumentation.schema - ) { - if (mediaTypeDocumentation?.example) - obj.example = mediaTypeDocumentation.example; + if (mediaTypeDocumentation.content[contentKey]?.examples) { + obj.examples = this.createExamples( + mediaTypeDocumentation.content[contentKey].examples + ); + } - if (mediaTypeDocumentation?.examples) - obj.examples = this.createExamples(mediaTypeDocumentation.examples); + schema = (mediaTypeDocumentation.schema) ? mediaTypeDocumentation.schema : mediaTypeDocumentation.schemas[contentKey]; + } else if (mediaTypeDocumentation?.contentType && mediaTypeDocumentation.schema) { + if (mediaTypeDocumentation.example) { + obj.example = mediaTypeDocumentation.example; + } - schema = mediaTypeDocumentation.schema; - } + if (mediaTypeDocumentation.examples) { + obj.example = mediaTypeDocumentation.examples; + } - const schemaRef = await this.schemaHandler - .createSchema(mediaTypeDocumentation.name) - .catch((err) => { - throw err; - }); + schema = mediaTypeDocumentation.schema; + } - obj.schema = { - $ref: schemaRef, - }; + const schemaRef = await this.schemaHandler + .createSchema(mediaTypeDocumentation.name) + .catch((err) => { + throw err; + }); - Object.assign(mediaTypeObj, { [contentKey]: obj }); + obj.schema = { + $ref: schemaRef, + }; + + Object.assign(mediaTypeObj, { [contentKey]: obj }); + } } } @@ -861,7 +871,7 @@ class DefinitionGenerator { if ( this.openAPI.components[type][name] && isEqual(schemaObj[name], this.openAPI.components[type][name]) === - false + false ) { delete schemaObj[name]; newName = `${name}-${uuid()}`; diff --git a/src/schemaHandler.js b/src/schemaHandler.js index d66bc61..41d5e22 100644 --- a/src/schemaHandler.js +++ b/src/schemaHandler.js @@ -42,9 +42,21 @@ class SchemaHandler { return model; } - const contentType = Object.keys(model.content)[0]; - model.contentType = contentType; - model.schema = model.content[contentType].schema; + if (Object.keys(model.content).length === 1) { + const contentType = Object.keys(model.content)[0]; + model.contentType = contentType; + model.contentTypes = [contentType]; + model.schema = model.content[contentType].schema; + } else { + model.contentType = null; + model.contentTypes = Object.keys(model.content); + model.schema = null; + model.schemas = {}; + for (const key in model.content) { + Object.assign(model.schemas, {[key]: {schema: model.content[key].schema}}); + } + // model.schema = model.content[contentType].schema; + } return model; }; @@ -69,43 +81,53 @@ class SchemaHandler { async addModelsToOpenAPI() { for (const model of this.models) { const modelName = model.name; - const modelSchema = model.schema; + const schemas = [] + if (model.schema){ + // const modelSchema = model.schema; + schemas.push(model.schema) + } else { + for (const key in model.schemas) { + schemas.push(model.schemas[key].schema); + } + } - const convertedSchemas = await this.__dereferenceAndConvert( - modelSchema, - modelName, - model - ).catch((err) => { - if (err instanceof Error) throw err; - else return err; - }); + for (const modelSchema of schemas) { + const convertedSchemas = await this.__dereferenceAndConvert( + modelSchema, + modelName, + model + ).catch((err) => { + if (err instanceof Error) throw err; + else return err; + }); + + if ( + typeof convertedSchemas.schemas === "object" && + !Array.isArray(convertedSchemas.schemas) && + convertedSchemas.schemas !== null + ) { + for (const [schemaName, schemaValue] of Object.entries( + convertedSchemas.schemas + )) { + if (schemaName === modelName) { + this.modelReferences[ + schemaName + ] = `#/components/schemas/${modelName}`; + } - if ( - typeof convertedSchemas.schemas === "object" && - !Array.isArray(convertedSchemas.schemas) && - convertedSchemas.schemas !== null - ) { - for (const [schemaName, schemaValue] of Object.entries( - convertedSchemas.schemas - )) { - if (schemaName === modelName) { - this.modelReferences[ - schemaName - ] = `#/components/schemas/${modelName}`; + this.__addToComponents("schemas", schemaValue, schemaName); } - - this.__addToComponents("schemas", schemaValue, schemaName); + } else { + throw new Error( + `There was an error converting the ${ + model.name + } schema. Model received looks like: \n\n${JSON.stringify( + model + )}. The convereted schema looks like \n\n${JSON.stringify( + convertedSchemas + )}` + ); } - } else { - throw new Error( - `There was an error converting the ${ - model.name - } schema. Model received looks like: \n\n${JSON.stringify( - model - )}. The convereted schema looks like \n\n${JSON.stringify( - convertedSchemas - )}` - ); } } } diff --git a/test/unit/definitionGenerator.spec.js b/test/unit/definitionGenerator.spec.js index 65b76f9..2b7cf53 100644 --- a/test/unit/definitionGenerator.spec.js +++ b/test/unit/definitionGenerator.spec.js @@ -1,11 +1,17 @@ "use strict"; +const expect = require("chai").expect; +const sinon = require('sinon'); + + const fs = require("fs").promises; const path = require("path"); -const expect = require("chai").expect; const serverlessMock = require("../helpers/serverless"); const modelsDocument = require("../models/models/models.json"); + +const schemaHandler = require('../../src/schemaHandler'); + const DefinitionGenerator = require("../../src/definitionGenerator"); describe("DefinitionGenerator", () => { @@ -169,152 +175,222 @@ describe("DefinitionGenerator", () => { // expect(definitionGenerator.openAPI.info).to.deep.equal(mockServerless.service.custom.documentation) }); - it("should use the service name when documentation title has not been supplied", function () { - delete mockServerless.service.custom.documentation.title; - const definitionGenerator = new DefinitionGenerator( - mockServerless, - logger - ); - definitionGenerator.createInfo(); + describe(`info`, function () { + it("should use the service name when documentation title has not been supplied", function () { + delete mockServerless.service.custom.documentation.title; + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + definitionGenerator.createInfo(); - expect(definitionGenerator.openAPI).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.be.an("object"); - expect(definitionGenerator.openAPI.info.title).to.be.equal( - mockServerless.service.service - ); - }); + expect(definitionGenerator.openAPI).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.be.an("object"); + expect(definitionGenerator.openAPI.info.title).to.be.equal( + mockServerless.service.service + ); + }); - it("should use the service name when documentation description has not been supplied", function () { - delete mockServerless.service.custom.documentation.description; - const definitionGenerator = new DefinitionGenerator( - mockServerless, - logger - ); - definitionGenerator.createInfo(); + it("should use the service name when documentation description has not been supplied", function () { + delete mockServerless.service.custom.documentation.description; + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + definitionGenerator.createInfo(); - expect(definitionGenerator.openAPI).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.be.an("object"); - expect(definitionGenerator.openAPI.info.description).to.be.equal(""); - }); + expect(definitionGenerator.openAPI).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.be.an("object"); + expect(definitionGenerator.openAPI.info.description).to.be.equal(""); + }); - it("should use an empty string when documentation description has not been supplied", function () { - delete mockServerless.service.custom.documentation.description; - const definitionGenerator = new DefinitionGenerator( - mockServerless, - logger - ); - definitionGenerator.createInfo(); + it("should use an empty string when documentation description has not been supplied", function () { + delete mockServerless.service.custom.documentation.description; + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + definitionGenerator.createInfo(); - expect(definitionGenerator.openAPI).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.be.an("object"); - expect(definitionGenerator.openAPI.info.description).to.be.equal(""); - }); + expect(definitionGenerator.openAPI).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.be.an("object"); + expect(definitionGenerator.openAPI.info.description).to.be.equal(""); + }); - it("should generate a uuid for version when documentation version has not been supplied", function () { - delete mockServerless.service.custom.documentation.version; + it("should generate a uuid for version when documentation version has not been supplied", function () { + delete mockServerless.service.custom.documentation.version; - const definitionGenerator = new DefinitionGenerator( - mockServerless, - logger - ); - definitionGenerator.createInfo(); + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + definitionGenerator.createInfo(); - expect(definitionGenerator.openAPI).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.be.an("object"); - expect(v4.test(definitionGenerator.openAPI.info.version)).to.be.true; + expect(definitionGenerator.openAPI).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.be.an("object"); + expect(v4.test(definitionGenerator.openAPI.info.version)).to.be.true; + }); }); - it("should assign a contact Object when a contact object is included", function () { - mockServerless.service.custom.documentation.contact = { - name: "John", - url: "http://example.com", - email: "john@example.com", - }; - const definitionGenerator = new DefinitionGenerator( - mockServerless, - logger - ); - definitionGenerator.createInfo(); + describe(`termsOfService`, function () { + it(`should add a termsOfService when specified`, function () { + mockServerless.service.custom.documentation.termsOfService = 'https://example.com/ToS' + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + definitionGenerator.createInfo(); - expect(definitionGenerator.openAPI).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.have.property("contact"); - expect(definitionGenerator.openAPI.info.contact).to.be.an("object"); - expect(definitionGenerator.openAPI.info.contact.name).to.be.an("string"); + expect(definitionGenerator.openAPI).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.have.property("termsOfService"); + expect(definitionGenerator.openAPI.info.termsOfService).to.be.an("string"); + expect(definitionGenerator.openAPI.info.termsOfService).to.be.equal('https://example.com/ToS') + }); }); - it("should only assign a contact url if one is provided", function () { - mockServerless.service.custom.documentation.contact = { - name: "John", - email: "john@example.com", - }; - const definitionGenerator = new DefinitionGenerator( - mockServerless, - logger - ); - definitionGenerator.createInfo(); + describe(`contact`, function () { + it("should assign a contact Object when a contact object is included", function () { + mockServerless.service.custom.documentation.contact = { + name: "John", + url: "http://example.com", + email: "john@example.com", + }; + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + definitionGenerator.createInfo(); - expect(definitionGenerator.openAPI).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.have.property("contact"); - expect(definitionGenerator.openAPI.info.contact).to.be.an("object"); - expect(definitionGenerator.openAPI.info.contact.name).to.be.an("string"); - expect(definitionGenerator.openAPI.info.contact).to.not.have.property( - "url" - ); - }); + expect(definitionGenerator.openAPI).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.have.property("contact"); + expect(definitionGenerator.openAPI.info.contact).to.be.an("object"); + expect(definitionGenerator.openAPI.info.contact).to.have.property("name"); + expect(definitionGenerator.openAPI.info.contact).to.have.property("url"); + expect(definitionGenerator.openAPI.info.contact).to.have.property("email"); + expect(definitionGenerator.openAPI.info.contact.name).to.be.an("string"); + expect(definitionGenerator.openAPI.info.contact.url).to.be.an("string"); + expect(definitionGenerator.openAPI.info.contact.email).to.be.an("string"); + }); - it("should assign a license Object when a license object is included with a name", function () { - mockServerless.service.custom.documentation.license = { - name: "Apache 2.0", - url: "https://www.apache.org/licenses/LICENSE-2.0.html", - }; - const definitionGenerator = new DefinitionGenerator( - mockServerless, - logger - ); - definitionGenerator.createInfo(); + it("should only assign a contact url if one is provided", function () { + mockServerless.service.custom.documentation.contact = { + name: "John", + email: "john@example.com", + }; + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + definitionGenerator.createInfo(); - expect(definitionGenerator.openAPI).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.have.property("license"); - expect(definitionGenerator.openAPI.info.license).to.be.an("object"); - expect(definitionGenerator.openAPI.info.license.name).to.be.an("string"); + expect(definitionGenerator.openAPI).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.have.property("contact"); + expect(definitionGenerator.openAPI.info.contact).to.be.an("object"); + expect(definitionGenerator.openAPI.info.contact.name).to.be.an("string"); + expect(definitionGenerator.openAPI.info.contact).to.not.have.property( + "url" + ); + }); + + it("should only assign a contact email if one is provided", function () { + mockServerless.service.custom.documentation.contact = { + name: "John", + }; + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + definitionGenerator.createInfo(); + + expect(definitionGenerator.openAPI).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.have.property("contact"); + expect(definitionGenerator.openAPI.info.contact).to.be.an("object"); + expect(definitionGenerator.openAPI.info.contact.name).to.be.an("string"); + expect(definitionGenerator.openAPI.info.contact).to.not.have.property( + "url" + ); + expect(definitionGenerator.openAPI.info.contact).to.not.have.property( + "email" + ); + }); + + it("should only assign a contact name if one is provided", function () { + mockServerless.service.custom.documentation.contact = {}; + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + definitionGenerator.createInfo(); + + expect(definitionGenerator.openAPI).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.have.property("contact"); + expect(definitionGenerator.openAPI.info.contact).to.be.an("object"); + expect(definitionGenerator.openAPI.info.contact).to.not.have.property( + "url" + ); + expect(definitionGenerator.openAPI.info.contact).to.not.have.property( + "email" + ); + expect(definitionGenerator.openAPI.info.contact).to.not.have.property( + "name" + ); + }); }); - it("should not assign a license Object when a license object is included without a name", function () { - mockServerless.service.custom.documentation.license = { - url: "https://www.apache.org/licenses/LICENSE-2.0.html", - }; - const definitionGenerator = new DefinitionGenerator( - mockServerless, - logger - ); - definitionGenerator.createInfo(); + describe(`license`, function () { + it("should assign a license Object when a license object is included with a name", function () { + mockServerless.service.custom.documentation.license = { + name: "Apache 2.0", + url: "https://www.apache.org/licenses/LICENSE-2.0.html", + }; + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + definitionGenerator.createInfo(); - expect(definitionGenerator.openAPI).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.not.have.property("license"); + expect(definitionGenerator.openAPI).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.have.property("license"); + expect(definitionGenerator.openAPI.info.license).to.be.an("object"); + expect(definitionGenerator.openAPI.info.license.name).to.be.an("string"); + }); + + it("should not assign a license Object when a license object is included without a name", function () { + mockServerless.service.custom.documentation.license = { + url: "https://www.apache.org/licenses/LICENSE-2.0.html", + }; + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + definitionGenerator.createInfo(); + + expect(definitionGenerator.openAPI).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.not.have.property("license"); + }); }); - it("should only assign a contact url if one is provided", function () { - mockServerless.service.custom.documentation.license = { - name: "John", - }; - const definitionGenerator = new DefinitionGenerator( - mockServerless, - logger - ); - definitionGenerator.createInfo(); + describe(`x-tagGroups`, function () { + it(`should add a x-tagGroups when specified`, function () { + mockServerless.service.custom.documentation['x-tagGroups'] = [{ name: 'John' }] + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + definitionGenerator.createInfo(); - expect(definitionGenerator.openAPI).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.be.an("object"); - expect(definitionGenerator.openAPI.info).to.have.property("license"); - expect(definitionGenerator.openAPI.info.license).to.be.an("object"); - expect(definitionGenerator.openAPI.info.license.name).to.be.an("string"); - expect(definitionGenerator.openAPI.info.license).to.not.have.property( - "url" - ); + expect(definitionGenerator.openAPI).to.be.an("object"); + expect(definitionGenerator.openAPI.info).to.be.an("object"); + expect(definitionGenerator.openAPI).to.have.property("x-tagGroups"); + expect(definitionGenerator.openAPI['x-tagGroups']).to.be.an("array"); + }); }); it("should assign specification extension fields when included", function () { @@ -950,31 +1026,233 @@ describe("DefinitionGenerator", () => { }); }); - describe(`createResponses`, async function () { - it(`handles creating headers with pragma as a default`, async function () { - const description = "this is a description"; - const responseMock = { - methodResponses: [ - { - responseBody: { description: description }, - statusCode: 200, - owasp: { pragma: true }, - }, - ], - }; + describe(`createRequestBody`, function () { + it(`required should default to false if required is not true`, async function () { + const requestBody = { + models: { + 'application/json': 'PostRequestBody' + } + } + + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + + const stub = sinon.stub(schemaHandler.prototype, 'createSchema').resolves('#components/schemas/PostRequestBody') + + const expected = await definitionGenerator.createRequestBody(requestBody) + + expect(expected).to.be.an('object'); + expect(expected).to.not.have.property('description'); + expect(expected).to.have.property('required', false); + expect(expected).to.have.property('content'); + + stub.restore(); + }); + + it(`required should be true if required is true`, async function () { + const requestBody = { + required: true, + models: { + 'application/json': 'PostRequestBody' + } + } + + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + + const stub = sinon.stub(schemaHandler.prototype, 'createSchema').resolves('#components/schemas/PostRequestBody') + + const expected = await definitionGenerator.createRequestBody(requestBody) + + expect(expected).to.be.an('object'); + expect(expected).to.not.have.property('description'); + expect(expected).to.have.property('required', true); + expect(expected).to.have.property('content'); + + stub.restore(); + }); + + it(`should have a description when description is passed through`, async function () { + const description = 'a description' + const requestBody = { + description, + models: { + 'application/json': 'PostRequestBody' + } + } + + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + + const stub = sinon.stub(schemaHandler.prototype, 'createSchema').resolves('#components/schemas/PostRequestBody') + + const expected = await definitionGenerator.createRequestBody(requestBody) + + expect(expected).to.be.an('object'); + expect(expected).to.have.property('description', description); + expect(expected).to.have.property('required', false); + expect(expected).to.have.property('content'); + + stub.restore(); + }); + + it(`should handle more than one requestModel type`, async function () { + const description = 'a description' + const requestBody = { + description, + models: { + 'application/json': 'PostRequestBody', + 'application/xml': 'PostRequestBodyXML', + } + } const definitionGenerator = new DefinitionGenerator( mockServerless, logger ); - const response = await definitionGenerator.createResponses(responseMock); + const stub = sinon.stub(schemaHandler.prototype, 'createSchema').onFirstCall().resolves('#components/schemas/PostRequestBody').onSecondCall().resolves('#components/schemas/PostRequestBodyXML'); + + const expected = await definitionGenerator.createRequestBody(requestBody) + + expect(expected).to.be.an('object'); + expect(expected).to.have.property('description', description); + expect(expected).to.have.property('required', false); + expect(expected).to.have.property('content'); + expect(expected.content).to.be.an('object'); + expect(Object.keys(expected.content)).to.have.lengthOf(2); + expect(Object.keys(expected.content)).to.be.eql(['application/json', 'application/xml']); + + stub.restore(); + }); + }); + + describe(`createResponses`, async function () { + describe(`responseHeaders`, function () { + it(`does not create headers when headers are not specified`, async function () { + const description = "this is a description"; + const responseMock = { + methodResponses: [ + { + responseBody: { description: description }, + statusCode: 200, + }, + ], + }; + + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + + const response = await definitionGenerator.createResponses(responseMock); + + expect(response).to.be.an("object"); + expect(response).to.have.property("200"); + expect(response["200"]).to.have.property("description", description); + expect(response["200"]).to.not.have.property('headers'); + }); + + it(`only creates headers when headers are specified`, async function () { + const description = "this is a description"; + const responseMock = { + methodResponses: [ + { + responseBody: { description: description }, + statusCode: 200, + responseHeaders: { + 'x-rate-limit': { + description: 'The number of allowed requests in the current period', + schema: { + type: 'integer' + } + } + } + }, + ], + }; + + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + + const response = await definitionGenerator.createResponses(responseMock); + + expect(response).to.be.an("object"); + expect(response).to.have.property("200"); + expect(response["200"]).to.have.property("description", description); + expect(response["200"]).to.have.property('headers'); + expect(response["200"].headers).to.be.an("object"); + expect(response["200"].headers).to.have.property("x-rate-limit"); + }); + + it(`handles creating headers with pragma as a default`, async function () { + const description = "this is a description"; + const responseMock = { + methodResponses: [ + { + responseBody: { description: description }, + statusCode: 200, + owasp: { pragma: true }, + }, + ], + }; + + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); + + const response = await definitionGenerator.createResponses(responseMock); + + expect(response).to.be.an("object"); + expect(response).to.have.property("200"); + expect(response["200"]).to.have.property("description", description); + expect(response["200"].headers).to.be.an("object"); + expect(response["200"].headers).to.have.property("Pragma"); + }); + + it(`handles creating headers with owasp and user specified headers`, async function () { + const description = "this is a description"; + const responseMock = { + methodResponses: [ + { + responseBody: { description: description }, + statusCode: 200, + responseHeaders: { + 'x-rate-limit': { + description: 'The number of allowed requests in the current period', + schema: { + type: 'integer' + } + } + }, + owasp: { pragma: true }, + }, + ], + }; + + const definitionGenerator = new DefinitionGenerator( + mockServerless, + logger + ); - expect(response).to.be.an("object"); - expect(response).to.have.property("200"); - expect(response["200"]).to.have.property("description", description); - expect(response["200"].headers).to.be.an("object"); - expect(response["200"].headers).to.have.property("Pragma"); + const response = await definitionGenerator.createResponses(responseMock); + + expect(response).to.be.an("object"); + expect(response).to.have.property("200"); + expect(response["200"]).to.have.property("description", description); + expect(response["200"].headers).to.be.an("object"); + expect(response["200"].headers).to.have.property("Pragma"); + expect(response["200"].headers).to.have.property("x-rate-limit"); + }); }); }); });