serverless-openapi-documenter 0.0.31 → 0.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -59,8 +59,16 @@ Options:
59
59
  | info.title | custom.documentation.title OR service |
60
60
  | info.description | custom.documentation.description OR blank string |
61
61
  | info.version | custom.documentation.version OR random v4 uuid if not provided |
62
+ | info.termsOfService | custom.documentation.termsOfService |
63
+ | info.contact | custom.documentation.contact |
64
+ | info.contact.name | custom.documentation.contact.name OR blank string |
65
+ | info.contact.url | custom.documentation.contact.url if provided |
66
+ | info.license | custom.documentation.license |
67
+ | info.license.name | custom.documentation.license.name OR blank string |
68
+ | info.license.url | custom.documentation.license.url if provided |
62
69
  | externalDocs.description | custom.documentation.externalDocumentation.description |
63
70
  | externalDocs.url | custom.documentation.externalDocumentation.url |
71
+ | security | custom.documentation.security |
64
72
  | servers[].description | custom.documentation.servers.description |
65
73
  | servers[].url | custom.documentation.servers.url |
66
74
  | servers[].variables | custom.documentation.servers.variables |
@@ -82,6 +90,7 @@ Options:
82
90
  | path[path].[operation].externalDocs.url | functions.functions.[http OR httpApi].documentation.externalDocumentation.url |
83
91
  | path[path].[operation].servers[].description | functions.functions.[http OR httpApi].documentation.servers.description |
84
92
  | path[path].[operation].servers[].url | functions.functions.[http OR httpApi].documentation.servers.url |
93
+ | path[path].[operation].security | functions.functions.[http OR httpApi].documentation.security |
85
94
  | path[path].[operation].deprecated | functions.functions.[http OR httpApi].documentation.deprecated |
86
95
  | path[path].[operation].parameters | functions.functions.[http OR httpApi].documentation.[path/query/cookie/header]Params |
87
96
  | path[path].[operation].parameters.name | functions.functions.[http OR httpApi].documentation.[path/query/cookie/header]Params.name |
@@ -118,6 +127,7 @@ custom:
118
127
  version: '1'
119
128
  title: 'My API'
120
129
  description: 'This is my API'
130
+ termsOfService: https://google.com
121
131
  externalDocumentation:
122
132
  url: https://google.com
123
133
  description: A link to google
@@ -126,7 +136,7 @@ custom:
126
136
  description: The server
127
137
  variables:
128
138
  port:
129
- enum:
139
+ enum:
130
140
  - 4000
131
141
  - 3000
132
142
  default: 3000
@@ -142,6 +152,58 @@ custom:
142
152
 
143
153
  Mostly everything here is optional. A version from a UUID will be generated for you if you don't specify one, title will be the name of your service if you don't specify one.
144
154
 
155
+ #### termsOfService
156
+
157
+ Must be in the format of a url if included.
158
+
159
+ #### Contact
160
+
161
+ You can provide an optional contact object such as:
162
+
163
+ ```yml
164
+ custom:
165
+ documentation:
166
+ contact:
167
+ name: John
168
+ url: https://example.com
169
+ email: John@example.com
170
+ ```
171
+
172
+ These fields are optional, though `url` and `email` need to be in the format of an email address (ed: what that might be, i'm not 100% sure... go read the email RFC(s)) and a url.
173
+
174
+ #### License
175
+
176
+ You can provide an optional license object such as:
177
+
178
+ ```yml
179
+ custom:
180
+ documentation:
181
+ license:
182
+ name: Apache 2.0
183
+ url: https://www.apache.org/licenses/LICENSE-2.0.html
184
+ ```
185
+
186
+ Name is required but `url` is optional and must be in the format of a url.
187
+ #### Extended Fields
188
+
189
+ You can also add extended fields to the documentation object:
190
+
191
+ ```yml
192
+ custom:
193
+ documentation:
194
+ x-other-field: This is an extended field
195
+ ```
196
+
197
+ These fields must have `x-` before them, otherwise they will be ignored:
198
+
199
+ ```yml
200
+ custom:
201
+ documentation:
202
+ other-field: This is an extended field
203
+ ```
204
+
205
+ `other-field` here will not make it to the generated OpenAPI schema.
206
+
145
207
  These configurations can be quite verbose; you can separate it out into it's own file, such as `serverless.doc.yml` as below:
146
208
 
147
209
  ```yml
@@ -159,6 +221,40 @@ functions:
159
221
 
160
222
  For more info on `serverless.yml` syntax, see their docs.
161
223
 
224
+ #### securitySchemes
225
+
226
+ You can provide optional Security Schemes:
227
+
228
+ ```yml
229
+ custom:
230
+ documentation:
231
+ securitySchemes:
232
+ my_api_key:
233
+ type: apiKey
234
+ name: api_key
235
+ in: header
236
+ ```
237
+
238
+ It accepts all available Security Schemes and follows the specification: https://spec.openapis.org/oas/v3.0.3#security-scheme-object
239
+
240
+ #### Security on each operation
241
+
242
+ To apply an overall security scheme to all of your operations without having to add the documentation to each one, you can write it like:
243
+
244
+ ```yml
245
+ custom:
246
+ documentation:
247
+ securitySchemes:
248
+ my_api_key:
249
+ type: apiKey
250
+ name: api_key
251
+ in: header
252
+ security:
253
+ - my_api_key: []
254
+ ```
255
+
256
+ This will apply the requirement of each operation requiring your `my_api_key` security scheme, [you can override this](#security).
257
+
162
258
  #### Models
163
259
 
164
260
  There are two ways to write the Models. Models contain additional information that you can use to define schemas for endpoints. You must define the *content type* for each schema that you provide in the models.
@@ -244,20 +340,20 @@ custom:
244
340
  content:
245
341
  application/json:
246
342
  schema: &ErrorItem
247
- type: object
248
- properties:
249
- message:
250
- type: string
251
- code:
252
- type: integer
253
-
343
+ type: object
344
+ properties:
345
+ message:
346
+ type: string
347
+ code:
348
+ type: integer
349
+
254
350
  - name: "PutDocumentResponse"
255
351
  description: "PUT Document response model (external reference example)"
256
352
  content:
257
353
  application/json:
258
- schema:
259
- type: array
260
- items: *ErrorItem
354
+ schema:
355
+ type: array
356
+ items: *ErrorItem
261
357
  ```
262
358
 
263
359
  `&ErrorItem` in the above example creates a node anchor (&ErrorItem) to the `ErrorResponse` schema which then can be used in the `PutDocumentResponse` schema via the reference (*ErrorItem). The node anchor needs to be declared first before it can be used elsewhere via the reference, swapping the above example around would result in an error.
@@ -280,6 +376,7 @@ The `documentation` section of the event configuration can contain the following
280
376
  * `pathParams`: a list of path parameters (see [pathParams](#pathparams) below)
281
377
  * `cookieParams`: a list of cookie parameters (see [cookieParams](#cookieparams) below)
282
378
  * `headerParams`: a list of headers (see [headerParams](#headerparams---request-headers) below)
379
+ * `security`: The security requirement to apply (see [security](#security) below)
283
380
  * `methodResponses`: an array of response models and applicable status codes
284
381
  * `statusCode`: applicable http status code (ie. 200/404/500 etc.)
285
382
  * `responseBody`: contains description of the response
@@ -420,6 +517,62 @@ headerParams:
420
517
  type: "string"
421
518
  ```
422
519
 
520
+ #### `security`
521
+
522
+ The `security` property allows you to specify the [Security Scheme](#securityschemes) to apply to the HTTP Request. If you have applied an `security` ([see Security on each operation](#security-on-each-operation)) then you can either leave this field off, or to override it with a different scheme you can write it like:
523
+
524
+ ```yml
525
+ custom:
526
+ documentation:
527
+ securitySchemes:
528
+ my_api_key:
529
+ type: apiKey
530
+ name: api_key
531
+ in: header
532
+ petstore_auth:
533
+ type: oauth2
534
+ flows:
535
+ implicit:
536
+ authorizationUrl: https://example.com/api/oauth/dialog
537
+ scopes:
538
+ write:pets: modify pets in your account
539
+ read:pets: read your pets
540
+ security:
541
+ - my_api_key: []
542
+
543
+ functions:
544
+ getData:
545
+ events:
546
+ - http:
547
+ documentation:
548
+ security:
549
+ - petstore_auth:
550
+ - write:pets
551
+ - read:pets
552
+ ```
553
+
554
+ If you have specified an `security` at the document root, but this HTTP Request should not apply any security schemes, you should set security to be an array with an empty object:
555
+
556
+ ```yml
557
+ custom:
558
+ documentation:
559
+ securitySchemes:
560
+ my_api_key:
561
+ type: apiKey
562
+ name: api_key
563
+ in: header
564
+ security:
565
+ - my_api_key: []
566
+
567
+ functions:
568
+ getData:
569
+ events:
570
+ - http:
571
+ documentation:
572
+ security:
573
+ - {}
574
+ ```
575
+
423
576
  #### `requestModels`
424
577
 
425
578
  The `requestModels` property allows you to define models for the HTTP Request of the function event. You can define a different model for each different `Content-Type`. You can define a reference to the relevant request model named in the `models` section of your configuration (see [Defining Models](#models) section).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-openapi-documenter",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "devDependencies": {
44
44
  "chai": "^4.3.7",
45
- "mocha": "^10.1.0",
45
+ "mocha": "^10.2.0",
46
46
  "sinon": "^15.0.0"
47
47
  }
48
48
  }
@@ -39,6 +39,15 @@ class DefinitionGenerator {
39
39
 
40
40
  async parse() {
41
41
  this.createInfo()
42
+
43
+ if (this.serverless.service.custom.documentation.securitySchemes) {
44
+ this.createSecuritySchemes(this.serverless.service.custom.documentation.securitySchemes)
45
+
46
+ if (this.serverless.service.custom.documentation.security) {
47
+ this.openAPI.security = this.serverless.service.custom.documentation.security
48
+ }
49
+ }
50
+
42
51
  await this.createPaths()
43
52
  .catch(err => {
44
53
  throw err
@@ -68,6 +77,37 @@ class DefinitionGenerator {
68
77
  description: documentation?.description || '',
69
78
  version: documentation?.version || uuid(),
70
79
  }
80
+
81
+ if (documentation.termsOfService)
82
+ info.termsOfService = documentation.termsOfService
83
+
84
+ if (documentation.contact) {
85
+ const contactObj = {}
86
+ contactObj.name = documentation.contact.name || ''
87
+
88
+ if (documentation.contact.url)
89
+ contactObj.url = documentation.contact.url
90
+
91
+ contactObj.email = documentation.contact.email || ''
92
+ Object.assign(info, {contact: contactObj})
93
+ }
94
+
95
+ if (documentation.license && documentation.license.name) {
96
+ const licenseObj = {}
97
+ licenseObj.name = documentation.license.name || ''
98
+
99
+ if (documentation.license.url)
100
+ licenseObj.url = documentation.license.url || ''
101
+
102
+ Object.assign(info, {license: licenseObj})
103
+ }
104
+
105
+ for (const key of Object.keys(documentation)) {
106
+ if (/^[x\-]/i.test(key)) {
107
+ Object.assign(info, {[key]: documentation[key]})
108
+ }
109
+ }
110
+
71
111
  Object.assign(this.openAPI, {info})
72
112
  }
73
113
 
@@ -234,6 +274,10 @@ class DefinitionGenerator {
234
274
  obj.externalDocs = documentation.externalDocumentation
235
275
  }
236
276
 
277
+ if (Object.keys(documentation).includes('security')) {
278
+ obj.security = documentation.security
279
+ }
280
+
237
281
  if (Object.keys(documentation).includes('deprecated'))
238
282
  obj.deprecated = documentation.deprecated
239
283
 
@@ -492,6 +536,146 @@ class DefinitionGenerator {
492
536
  }
493
537
  }
494
538
 
539
+ addToComponents(type, schema, name) {
540
+ const schemaObj = {
541
+ [name]: schema
542
+ }
543
+
544
+ if (this.openAPI?.components) {
545
+ if (this.openAPI.components[type]) {
546
+ Object.assign(this.openAPI.components[type], schemaObj)
547
+ } else {
548
+ Object.assign(this.openAPI.components, {[type]: schemaObj})
549
+ }
550
+ } else {
551
+ const components = {
552
+ components: {
553
+ [type]: schemaObj
554
+ }
555
+ }
556
+
557
+ Object.assign(this.openAPI, components)
558
+ }
559
+ }
560
+
561
+ createSecuritySchemes(securitySchemes) {
562
+ for (const scheme of Object.keys(securitySchemes)) {
563
+ const securityScheme = securitySchemes[scheme]
564
+ const schema = {}
565
+
566
+ if (securityScheme.description)
567
+ schema.description = securityScheme.description
568
+
569
+ switch(securityScheme.type.toLowerCase()) {
570
+ case 'apikey':
571
+ const apiKeyScheme = this.createAPIKeyScheme(securityScheme)
572
+ schema.type = 'apiKey'
573
+ Object.assign(schema, apiKeyScheme)
574
+ break;
575
+
576
+ case 'http':
577
+ const HTTPScheme = this.createHTTPScheme(securityScheme)
578
+ schema.type = 'http'
579
+ Object.assign(schema, HTTPScheme)
580
+ break;
581
+
582
+ case 'openidconnect':
583
+ const openIdConnectScheme = this.createOpenIDConnectScheme(securityScheme)
584
+ schema.type = 'openIdConnect'
585
+ Object.assign(schema, openIdConnectScheme)
586
+ break;
587
+
588
+ case 'oauth2':
589
+ const oAuth2Scheme = this.createOAuth2Scheme(securityScheme)
590
+ schema.type = 'oauth2'
591
+ Object.assign(schema, oAuth2Scheme)
592
+ break;
593
+ }
594
+
595
+ this.addToComponents('securitySchemes', schema, scheme)
596
+ }
597
+ }
598
+
599
+ createAPIKeyScheme(securitySchema) {
600
+ const schema = {}
601
+ if (securitySchema.name)
602
+ schema.name = securitySchema.name
603
+ else
604
+ throw new Error('Security Scheme for "apiKey" requires the name of the header, query or cookie parameter to be used')
605
+
606
+ if (securitySchema.in)
607
+ schema.in = securitySchema.in
608
+ else
609
+ throw new Error('Security Scheme for "apiKey" requires the location of the API key: header, query or cookie parameter')
610
+
611
+ return schema
612
+ }
613
+
614
+ createHTTPScheme(securitySchema) {
615
+ const schema = {}
616
+
617
+ if (securitySchema.scheme)
618
+ schema.scheme = securitySchema.scheme
619
+ else
620
+ throw new Error('Security Scheme for "http" requires scheme')
621
+
622
+ if (securitySchema.bearerFormat)
623
+ schema.bearerFormat = securitySchema.bearerFormat
624
+
625
+ return schema
626
+ }
627
+
628
+ createOpenIDConnectScheme(securitySchema) {
629
+ const schema = {}
630
+ if (securitySchema.openIdConnectUrl)
631
+ schema.openIdConnectUrl = securitySchema.openIdConnectUrl
632
+ else
633
+ throw new Error('Security Scheme for "openIdConnect" requires openIdConnectUrl')
634
+
635
+ return schema
636
+ }
637
+
638
+ createOAuth2Scheme(securitySchema) {
639
+ const schema = {}
640
+ if (securitySchema.flows) {
641
+ const flows = this.createOAuthFlows(securitySchema.flows)
642
+ Object.assign(schema, {flows: flows})
643
+ } else
644
+ throw new Error('Security Scheme for "oauth2" requires flows')
645
+
646
+ return schema
647
+ }
648
+
649
+ createOAuthFlows(flows) {
650
+ const obj = {}
651
+ for (const flow of Object.keys(flows)) {
652
+ const schema = {}
653
+ if (["implicit", 'authorizationCode'].includes(flow))
654
+ if (flows[flow].authorizationUrl)
655
+ schema.authorizationUrl = flows[flow].authorizationUrl
656
+ else
657
+ throw new Error(`oAuth2 ${flow} flow requires an authorizationUrl`)
658
+
659
+ if (['password', 'clientCredentials', 'authorizationCode'].includes(flow)) {
660
+ if (flows[flow].tokenUrl)
661
+ schema.tokenUrl = flows[flow].tokenUrl
662
+ else
663
+ throw new Error(`oAuth2 ${flow} flow requires a tokenUrl`)
664
+ }
665
+
666
+ if (flows[flow].refreshUrl)
667
+ schema.refreshUrl = flows[flow].refreshUrl
668
+
669
+ if (flows[flow].scopes)
670
+ schema.scopes = flows[flow].scopes
671
+ else
672
+ throw new Error(`oAuth2 ${flow} flow requires scopes`)
673
+
674
+ Object.assign(obj, {[flow]: schema})
675
+ }
676
+ return obj
677
+ }
678
+
495
679
  createExamples(examples) {
496
680
  const examplesObj = {}
497
681
 
@@ -3,7 +3,7 @@
3
3
  const fs = require('fs').promises
4
4
  const path = require('path')
5
5
  const sinon = require('sinon')
6
- const $RefParser = require("@apidevtools/json-schema-ref-parser");
6
+ const $RefParser = require("@apidevtools/json-schema-ref-parser")
7
7
  const expect = require('chai').expect
8
8
 
9
9
  const serverlessMock = require('../helpers/serverless')
@@ -174,6 +174,482 @@ describe('DefinitionGenerator', () => {
174
174
  expect(definitionGenerator.openAPI.info).to.be.an('object')
175
175
  expect(v4.test(definitionGenerator.openAPI.info.version)).to.be.true
176
176
  });
177
+
178
+ it('should assign a contact Object when a contact object is included', function() {
179
+ mockServerless.service.custom.documentation.contact = {
180
+ name: 'John',
181
+ url: 'http://example.com',
182
+ email: 'john@example.com'
183
+ }
184
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
185
+ definitionGenerator.createInfo()
186
+
187
+ expect(definitionGenerator.openAPI).to.be.an('object')
188
+ expect(definitionGenerator.openAPI.info).to.be.an('object')
189
+ expect(definitionGenerator.openAPI.info).to.have.property('contact')
190
+ expect(definitionGenerator.openAPI.info.contact).to.be.an('object')
191
+ expect(definitionGenerator.openAPI.info.contact.name).to.be.an('string')
192
+ });
193
+
194
+ it('should only assign a contact url if one is provided', function() {
195
+ mockServerless.service.custom.documentation.contact = {
196
+ name: 'John',
197
+ email: 'john@example.com'
198
+ }
199
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
200
+ definitionGenerator.createInfo()
201
+
202
+ expect(definitionGenerator.openAPI).to.be.an('object')
203
+ expect(definitionGenerator.openAPI.info).to.be.an('object')
204
+ expect(definitionGenerator.openAPI.info).to.have.property('contact')
205
+ expect(definitionGenerator.openAPI.info.contact).to.be.an('object')
206
+ expect(definitionGenerator.openAPI.info.contact.name).to.be.an('string')
207
+ expect(definitionGenerator.openAPI.info.contact).to.not.have.property('url')
208
+ });
209
+
210
+ it('should assign a license Object when a license object is included with a name', function() {
211
+ mockServerless.service.custom.documentation.license = {
212
+ name: 'Apache 2.0',
213
+ url: 'https://www.apache.org/licenses/LICENSE-2.0.html',
214
+ }
215
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
216
+ definitionGenerator.createInfo()
217
+
218
+ expect(definitionGenerator.openAPI).to.be.an('object')
219
+ expect(definitionGenerator.openAPI.info).to.be.an('object')
220
+ expect(definitionGenerator.openAPI.info).to.have.property('license')
221
+ expect(definitionGenerator.openAPI.info.license).to.be.an('object')
222
+ expect(definitionGenerator.openAPI.info.license.name).to.be.an('string')
223
+ });
224
+
225
+ it('should not assign a license Object when a license object is included without a name', function() {
226
+ mockServerless.service.custom.documentation.license = {
227
+ url: 'https://www.apache.org/licenses/LICENSE-2.0.html',
228
+ }
229
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
230
+ definitionGenerator.createInfo()
231
+
232
+ expect(definitionGenerator.openAPI).to.be.an('object')
233
+ expect(definitionGenerator.openAPI.info).to.be.an('object')
234
+ expect(definitionGenerator.openAPI.info).to.not.have.property('license')
235
+ });
236
+
237
+ it('should only assign a contact url if one is provided', function() {
238
+ mockServerless.service.custom.documentation.license = {
239
+ name: 'John',
240
+ }
241
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
242
+ definitionGenerator.createInfo()
243
+
244
+ expect(definitionGenerator.openAPI).to.be.an('object')
245
+ expect(definitionGenerator.openAPI.info).to.be.an('object')
246
+ expect(definitionGenerator.openAPI.info).to.have.property('license')
247
+ expect(definitionGenerator.openAPI.info.license).to.be.an('object')
248
+ expect(definitionGenerator.openAPI.info.license.name).to.be.an('string')
249
+ expect(definitionGenerator.openAPI.info.license).to.not.have.property('url')
250
+ });
251
+
252
+ it('should assign specification extension fields when included', function() {
253
+ mockServerless.service.custom.documentation['x-field'] = 'john'
254
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
255
+ definitionGenerator.createInfo()
256
+
257
+ expect(definitionGenerator.openAPI).to.be.an('object')
258
+ expect(definitionGenerator.openAPI.info).to.be.an('object')
259
+ expect(definitionGenerator.openAPI.info).to.have.property('x-field')
260
+ expect(definitionGenerator.openAPI.info['x-field']).to.be.equal('john')
261
+ });
262
+
263
+ it('should ignore fields that do not conform to specifiction extension', function() {
264
+ mockServerless.service.custom.documentation.otherField = 'john'
265
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
266
+ definitionGenerator.createInfo()
267
+
268
+ expect(definitionGenerator.openAPI).to.be.an('object')
269
+ expect(definitionGenerator.openAPI.info).to.be.an('object')
270
+ expect(definitionGenerator.openAPI.info).to.not.have.property('otherField')
271
+ });
272
+ });
273
+
274
+ describe('createSecuritySchemes', () => {
275
+ describe('API Keys', () => {
276
+ it('should add an API Key security scheme to components', function() {
277
+ mockServerless.service.custom.documentation.securitySchemes = {
278
+ 'api_key': {
279
+ type: 'apiKey',
280
+ name: 'Authorization',
281
+ in: 'header'
282
+ }
283
+ }
284
+
285
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
286
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
287
+
288
+ expect(definitionGenerator.openAPI).to.be.an('object')
289
+ expect(definitionGenerator.openAPI.components).to.be.an('object')
290
+ expect(definitionGenerator.openAPI.components).to.have.property('securitySchemes')
291
+ expect(definitionGenerator.openAPI.components.securitySchemes).to.be.an('object')
292
+ expect(definitionGenerator.openAPI.components.securitySchemes).to.have.property('api_key')
293
+ expect(definitionGenerator.openAPI.components.securitySchemes.api_key).to.have.property('type')
294
+ expect(definitionGenerator.openAPI.components.securitySchemes.api_key.type).to.be.equal('apiKey')
295
+ });
296
+
297
+ it('should throw an error when name is missing from an API Key scheme', function() {
298
+ mockServerless.service.custom.documentation.securitySchemes = {
299
+ 'api_key': {
300
+ type: 'apiKey',
301
+ in: 'header'
302
+ }
303
+ }
304
+
305
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
306
+ expect(() => {
307
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
308
+ }).to.throw('Security Scheme for "apiKey" requires the name of the header, query or cookie parameter to be used')
309
+ });
310
+
311
+ it('should throw an error when in is missing from an API Key scheme', function() {
312
+ mockServerless.service.custom.documentation.securitySchemes = {
313
+ 'api_key': {
314
+ type: 'apiKey',
315
+ name: 'Authorization',
316
+ }
317
+ }
318
+
319
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
320
+ expect(() => {
321
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
322
+ }).to.throw('Security Scheme for "apiKey" requires the location of the API key: header, query or cookie parameter')
323
+ });
324
+ });
325
+
326
+ describe('HTTP', () => {
327
+ it('should add an HTTP security scheme to components', function() {
328
+ mockServerless.service.custom.documentation.securitySchemes = {
329
+ 'http_key': {
330
+ type: 'http',
331
+ scheme: 'basic'
332
+ }
333
+ }
334
+
335
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
336
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
337
+
338
+ expect(definitionGenerator.openAPI).to.be.an('object')
339
+ expect(definitionGenerator.openAPI.components).to.be.an('object')
340
+ expect(definitionGenerator.openAPI.components).to.have.property('securitySchemes')
341
+ expect(definitionGenerator.openAPI.components.securitySchemes).to.be.an('object')
342
+ expect(definitionGenerator.openAPI.components.securitySchemes).to.have.property('http_key')
343
+ });
344
+
345
+ it('should throw an error when scheme is missing from an HTTP scheme', function() {
346
+ mockServerless.service.custom.documentation.securitySchemes = {
347
+ 'http_key': {
348
+ type: 'http',
349
+ }
350
+ }
351
+
352
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
353
+ expect(() => {
354
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
355
+ }).to.throw('Security Scheme for "http" requires scheme')
356
+ });
357
+ });
358
+
359
+ describe('openIdConnect', () => {
360
+ it('should add an openIdConnect security scheme to components', function() {
361
+ mockServerless.service.custom.documentation.securitySchemes = {
362
+ 'openIdConnect_key': {
363
+ type: 'openIdConnect',
364
+ openIdConnectUrl: 'http://example.com'
365
+ }
366
+ }
367
+
368
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
369
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
370
+
371
+ expect(definitionGenerator.openAPI).to.be.an('object')
372
+ expect(definitionGenerator.openAPI.components).to.be.an('object')
373
+ expect(definitionGenerator.openAPI.components).to.have.property('securitySchemes')
374
+ expect(definitionGenerator.openAPI.components.securitySchemes).to.be.an('object')
375
+ expect(definitionGenerator.openAPI.components.securitySchemes).to.have.property('openIdConnect_key')
376
+ });
377
+
378
+ it('should throw an error when openIdConnectUrl is missing from an openIdConnect scheme', function() {
379
+ mockServerless.service.custom.documentation.securitySchemes = {
380
+ 'openIdConnect_key': {
381
+ type: 'openIdConnect',
382
+ }
383
+ }
384
+
385
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
386
+ expect(() => {
387
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
388
+ }).to.throw('Security Scheme for "openIdConnect" requires openIdConnectUrl')
389
+ });
390
+ });
391
+
392
+ describe('oauth2', () => {
393
+ it('should add an oauth2 security scheme to components', function() {
394
+ mockServerless.service.custom.documentation.securitySchemes = {
395
+ 'oAuth2_key': {
396
+ type: 'oauth2',
397
+ flows: {
398
+ implicit: {
399
+ authorizationUrl: 'http://example.org/api/oauth/dialog',
400
+ scopes: {
401
+ 'write:pets': 'modify pets in your account',
402
+ 'read:pets': 'read your pets'
403
+ }
404
+ }
405
+ }
406
+ }
407
+ }
408
+
409
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
410
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
411
+
412
+ expect(definitionGenerator.openAPI).to.be.an('object')
413
+ expect(definitionGenerator.openAPI.components).to.be.an('object')
414
+ expect(definitionGenerator.openAPI.components).to.have.property('securitySchemes')
415
+ expect(definitionGenerator.openAPI.components.securitySchemes).to.be.an('object')
416
+ expect(definitionGenerator.openAPI.components.securitySchemes).to.have.property('oAuth2_key')
417
+ expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key).to.be.an('object')
418
+ expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key).to.have.property('type')
419
+ expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key).to.have.property('flows')
420
+ expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key.flows).to.be.an('object')
421
+ expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key.flows).to.have.property('implicit')
422
+ expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key.flows.implicit).to.be.an('object')
423
+ expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key.flows.implicit).to.have.property('scopes')
424
+ expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key.flows.implicit.scopes).to.be.an('object')
425
+ });
426
+
427
+ it('should throw an error when flows is missing from an oauth2 scheme', function() {
428
+ mockServerless.service.custom.documentation.securitySchemes = {
429
+ 'oAuth2_key': {
430
+ type: 'oauth2',
431
+ }
432
+ }
433
+
434
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
435
+ expect(() => {
436
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
437
+ }).to.throw('Security Scheme for "oauth2" requires flows')
438
+ });
439
+
440
+ it('should throw an error when authorizationUrl is missing from an oauth2 implicit flow scheme', function() {
441
+ mockServerless.service.custom.documentation.securitySchemes = {
442
+ 'oAuth2_key': {
443
+ type: 'oauth2',
444
+ flows: {
445
+ implicit: {
446
+ scopes: {
447
+ 'write:pets': 'modify pets in your account',
448
+ 'read:pets': 'read your pets'
449
+ }
450
+ }
451
+ }
452
+ }
453
+ }
454
+
455
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
456
+ expect(() => {
457
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
458
+ }).to.throw('oAuth2 implicit flow requires an authorizationUrl')
459
+ });
460
+
461
+ it('should throw an error when authorizationUrl is missing from an oauth2 authorizationCode flow scheme', function() {
462
+ mockServerless.service.custom.documentation.securitySchemes = {
463
+ 'oAuth2_key': {
464
+ type: 'oauth2',
465
+ flows: {
466
+ authorizationCode: {
467
+ tokenUrl: 'http://example.com',
468
+ scopes: {
469
+ 'write:pets': 'modify pets in your account',
470
+ 'read:pets': 'read your pets'
471
+ }
472
+ }
473
+ }
474
+ }
475
+ }
476
+
477
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
478
+ expect(() => {
479
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
480
+ }).to.throw('oAuth2 authorizationCode flow requires an authorizationUrl')
481
+ });
482
+
483
+ it('should throw an error when tokenUrl is missing from an oauth2 authorizationCode flow scheme', function() {
484
+ mockServerless.service.custom.documentation.securitySchemes = {
485
+ 'oAuth2_key': {
486
+ type: 'oauth2',
487
+ flows: {
488
+ authorizationCode: {
489
+ authorizationUrl: 'http://example.org/api/oauth/dialog',
490
+ scopes: {
491
+ 'write:pets': 'modify pets in your account',
492
+ 'read:pets': 'read your pets'
493
+ }
494
+ }
495
+ }
496
+ }
497
+ }
498
+
499
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
500
+ expect(() => {
501
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
502
+ }).to.throw('oAuth2 authorizationCode flow requires a tokenUrl')
503
+ });
504
+
505
+ it('should throw an error when tokenUrl is missing from an oauth2 password flow scheme', function() {
506
+ mockServerless.service.custom.documentation.securitySchemes = {
507
+ 'oAuth2_key': {
508
+ type: 'oauth2',
509
+ flows: {
510
+ password: {
511
+ scopes: {
512
+ 'write:pets': 'modify pets in your account',
513
+ 'read:pets': 'read your pets'
514
+ }
515
+ }
516
+ }
517
+ }
518
+ }
519
+
520
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
521
+ expect(() => {
522
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
523
+ }).to.throw('oAuth2 password flow requires a tokenUrl')
524
+ });
525
+
526
+ it('should throw an error when tokenUrl is missing from an oauth2 clientCredentials flow scheme', function() {
527
+ mockServerless.service.custom.documentation.securitySchemes = {
528
+ 'oAuth2_key': {
529
+ type: 'oauth2',
530
+ flows: {
531
+ clientCredentials: {
532
+ scopes: {
533
+ 'write:pets': 'modify pets in your account',
534
+ 'read:pets': 'read your pets'
535
+ }
536
+ }
537
+ }
538
+ }
539
+ }
540
+
541
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
542
+ expect(() => {
543
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
544
+ }).to.throw('oAuth2 clientCredentials flow requires a tokenUrl')
545
+ });
546
+
547
+ it('should throw an error when scopes is missing from an oauth2 clientCredentials flow scheme', function() {
548
+ mockServerless.service.custom.documentation.securitySchemes = {
549
+ 'oAuth2_key': {
550
+ type: 'oauth2',
551
+ flows: {
552
+ clientCredentials: {
553
+ tokenUrl: 'http://example.com',
554
+ }
555
+ }
556
+ }
557
+ }
558
+
559
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
560
+ expect(() => {
561
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
562
+ }).to.throw('oAuth2 clientCredentials flow requires scopes')
563
+ });
564
+
565
+ it('should throw an error when scopes is missing from an oauth2 authorizationCode flow scheme', function() {
566
+ mockServerless.service.custom.documentation.securitySchemes = {
567
+ 'oAuth2_key': {
568
+ type: 'oauth2',
569
+ flows: {
570
+ authorizationCode: {
571
+ tokenUrl: 'http://example.com',
572
+ authorizationUrl: 'http://example.org/api/oauth/dialog',
573
+ }
574
+ }
575
+ }
576
+ }
577
+
578
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
579
+ expect(() => {
580
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
581
+ }).to.throw('oAuth2 authorizationCode flow requires scopes')
582
+ });
583
+
584
+ it('should throw an error when scopes is missing from an oauth2 password flow scheme', function() {
585
+ mockServerless.service.custom.documentation.securitySchemes = {
586
+ 'oAuth2_key': {
587
+ type: 'oauth2',
588
+ flows: {
589
+ password: {
590
+ tokenUrl: 'http://example.com',
591
+ }
592
+ }
593
+ }
594
+ }
595
+
596
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
597
+ expect(() => {
598
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
599
+ }).to.throw('oAuth2 password flow requires scopes')
600
+ });
601
+
602
+ it('should throw an error when scopes is missing from an oauth2 implicit flow scheme', function() {
603
+ mockServerless.service.custom.documentation.securitySchemes = {
604
+ 'oAuth2_key': {
605
+ type: 'oauth2',
606
+ flows: {
607
+ implicit: {
608
+ authorizationUrl: 'http://example.org/api/oauth/dialog',
609
+ }
610
+ }
611
+ }
612
+ }
613
+
614
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
615
+ expect(() => {
616
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
617
+ }).to.throw('oAuth2 implicit flow requires scopes')
618
+ });
619
+ });
620
+
621
+ describe('Multiple Schemes', () => {
622
+ it('should add an oauth2 and an apiKey security scheme to components', function() {
623
+ mockServerless.service.custom.documentation.securitySchemes = {
624
+ 'oAuth2_key': {
625
+ type: 'oauth2',
626
+ flows: {
627
+ implicit: {
628
+ authorizationUrl: 'http://example.org/api/oauth/dialog',
629
+ scopes: {
630
+ 'write:pets': 'modify pets in your account',
631
+ 'read:pets': 'read your pets'
632
+ }
633
+ }
634
+ }
635
+ },
636
+ 'api_key': {
637
+ type: 'apiKey',
638
+ name: 'Authorization',
639
+ in: 'header'
640
+ }
641
+ }
642
+
643
+ const definitionGenerator = new DefinitionGenerator(mockServerless)
644
+ definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
645
+ expect(definitionGenerator.openAPI).to.be.an('object')
646
+ expect(definitionGenerator.openAPI.components).to.be.an('object')
647
+ expect(definitionGenerator.openAPI.components).to.have.property('securitySchemes')
648
+ expect(definitionGenerator.openAPI.components.securitySchemes).to.be.an('object')
649
+ expect(definitionGenerator.openAPI.components.securitySchemes).to.have.property('oAuth2_key')
650
+ expect(definitionGenerator.openAPI.components.securitySchemes).to.have.property('api_key')
651
+ });
652
+ });
177
653
  });
178
654
 
179
655
  describe('createTags', () => {