serverless-openapi-documenter 0.0.72 → 0.0.81
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 +56 -1
- package/package.json +6 -6
- package/src/definitionGenerator.js +88 -14
- package/src/schemaHandler.js +21 -11
- package/test/unit/openAPIGenerator.spec.js +241 -200
package/README.md
CHANGED
|
@@ -751,6 +751,8 @@ requestModels:
|
|
|
751
751
|
|
|
752
752
|
#### `methodResponses`
|
|
753
753
|
|
|
754
|
+
`methodResponses` is a mandatory property and should include the `responseBody` and `description` properties.
|
|
755
|
+
|
|
754
756
|
You can define the response schemas by defining properties for your function event.
|
|
755
757
|
|
|
756
758
|
For an example of a `methodResponses` configuration for an event see below:
|
|
@@ -763,6 +765,12 @@ methodResponse:
|
|
|
763
765
|
responseModels:
|
|
764
766
|
application/json: "CreateResponse"
|
|
765
767
|
application/xml: "CreateResponseXML"
|
|
768
|
+
links:
|
|
769
|
+
getDataLink:
|
|
770
|
+
operation: getData
|
|
771
|
+
description: The id created here can be used to get Data
|
|
772
|
+
parameters:
|
|
773
|
+
contentId: $response.body#/id
|
|
766
774
|
responseHeaders:
|
|
767
775
|
X-Rate-Limit-Limit:
|
|
768
776
|
description: The number of allowed requests in the current period
|
|
@@ -788,6 +796,53 @@ responseModels:
|
|
|
788
796
|
application/xml: "CreateResponseXML"
|
|
789
797
|
```
|
|
790
798
|
|
|
799
|
+
##### `links`
|
|
800
|
+
|
|
801
|
+
The `links` property allows you to define how operations are linked to each other:
|
|
802
|
+
|
|
803
|
+
```yml
|
|
804
|
+
links:
|
|
805
|
+
linkName:
|
|
806
|
+
operation: getContent
|
|
807
|
+
description: The contentId created here can be used to get content
|
|
808
|
+
parameters:
|
|
809
|
+
contentId: $response.body#/contentId
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
Where we are specifying operation, this should map to the function name:
|
|
813
|
+
|
|
814
|
+
```yml
|
|
815
|
+
functions:
|
|
816
|
+
createContent:
|
|
817
|
+
events:
|
|
818
|
+
- httpApi:
|
|
819
|
+
path: /
|
|
820
|
+
method: POST
|
|
821
|
+
documentation: ...
|
|
822
|
+
getContent:
|
|
823
|
+
events:
|
|
824
|
+
- http:
|
|
825
|
+
path: /{contentId}
|
|
826
|
+
method: POST
|
|
827
|
+
documentation: ...
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
If our example link was attached to the **createContent** function, and we wanted the `contentId` that was created to be used on the **getContent** function in the `contentId` parameter, we'd specify the `operation` property as **getContent**. If however, you had specified an operationId in the documentation to override the automatically created one:
|
|
831
|
+
|
|
832
|
+
```yml
|
|
833
|
+
getContent:
|
|
834
|
+
events:
|
|
835
|
+
- http:
|
|
836
|
+
path: /{contentId}
|
|
837
|
+
method: POST
|
|
838
|
+
documentation:
|
|
839
|
+
operationId: getMyContent
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
You can refer to the `operationId` that you created.
|
|
843
|
+
|
|
844
|
+
You can read more about [links](https://swagger.io/docs/specification/links/) on the swagger.io site and in the [OpenAPI](https://spec.openapis.org/oas/v3.0.3#link-object) specification. They don't seem widely supported just yet, but perhaps they'll improve your documentation.
|
|
845
|
+
|
|
791
846
|
##### `responseHeaders`
|
|
792
847
|
|
|
793
848
|
The `responseHeaders` property allows you to define the headers expected in a HTTP Response of the function event. This should only contain a description and a schema, which must be a JSON schema (inline, file or externally hosted).
|
|
@@ -882,7 +937,7 @@ This will set the `Cache-Control` Response Header to have a value of "no-store"
|
|
|
882
937
|
|
|
883
938
|
## Example configuration
|
|
884
939
|
|
|
885
|
-
Please view the example [serverless.yml](test/serverless-tests/
|
|
940
|
+
Please view the example [serverless.yml](test/serverless-tests/best/serverless.yml).
|
|
886
941
|
|
|
887
942
|
## Notes on schemas
|
|
888
943
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serverless-openapi-documenter",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.81",
|
|
4
4
|
"description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"@apidevtools/json-schema-ref-parser": "^9.1.0",
|
|
41
41
|
"chalk": "^4.1.2",
|
|
42
42
|
"js-yaml": "^4.1.0",
|
|
43
|
-
"json-schema-for-openapi": "^0.
|
|
43
|
+
"json-schema-for-openapi": "^0.4.1",
|
|
44
44
|
"lodash.isequal": "^4.5.0",
|
|
45
45
|
"oas-validator": "^5.0.8",
|
|
46
|
-
"openapi-to-postmanv2": "^4.
|
|
46
|
+
"openapi-to-postmanv2": "^4.17.0",
|
|
47
47
|
"swagger2openapi": "^7.0.8",
|
|
48
48
|
"uuid": "^9.0.0"
|
|
49
49
|
},
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"node": ">=14"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"chai": "^4.3.
|
|
54
|
+
"chai": "^4.3.8",
|
|
55
55
|
"mocha": "^10.2.0",
|
|
56
|
-
"nock": "^13.3.
|
|
57
|
-
"sinon": "^
|
|
56
|
+
"nock": "^13.3.3",
|
|
57
|
+
"sinon": "^16.0.0"
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -34,6 +34,9 @@ class DefinitionGenerator {
|
|
|
34
34
|
|
|
35
35
|
this.schemaHandler = new SchemaHandler(serverless, this.openAPI);
|
|
36
36
|
|
|
37
|
+
this.operationIdMap = {};
|
|
38
|
+
this.functionMap = {};
|
|
39
|
+
|
|
37
40
|
this.operationIds = [];
|
|
38
41
|
this.schemaIDs = [];
|
|
39
42
|
|
|
@@ -92,6 +95,8 @@ class DefinitionGenerator {
|
|
|
92
95
|
throw err;
|
|
93
96
|
});
|
|
94
97
|
|
|
98
|
+
this.cleanupLinks();
|
|
99
|
+
|
|
95
100
|
if (this.serverless.service.custom.documentation.servers) {
|
|
96
101
|
const servers = this.createServers(
|
|
97
102
|
this.serverless.service.custom.documentation.servers
|
|
@@ -156,6 +161,7 @@ class DefinitionGenerator {
|
|
|
156
161
|
async createPaths() {
|
|
157
162
|
const paths = {};
|
|
158
163
|
const httpFunctions = this.getHTTPFunctions();
|
|
164
|
+
|
|
159
165
|
for (const httpFunction of httpFunctions) {
|
|
160
166
|
for (const event of httpFunction.event) {
|
|
161
167
|
if (event?.http?.documentation || event?.httpApi?.documentation) {
|
|
@@ -164,21 +170,11 @@ class DefinitionGenerator {
|
|
|
164
170
|
event?.http?.documentation || event?.httpApi?.documentation;
|
|
165
171
|
|
|
166
172
|
this.currentFunctionName = httpFunction.functionInfo.name;
|
|
167
|
-
|
|
168
|
-
let opId;
|
|
169
|
-
if (
|
|
170
|
-
this.operationIds.includes(httpFunction.functionInfo.name) === false
|
|
171
|
-
) {
|
|
172
|
-
opId = httpFunction.functionInfo.name;
|
|
173
|
-
this.operationIds.push(opId);
|
|
174
|
-
} else {
|
|
175
|
-
opId = `${httpFunction.functionInfo.name}-${uuid()}`;
|
|
176
|
-
}
|
|
173
|
+
this.operationName = httpFunction.operationName;
|
|
177
174
|
|
|
178
175
|
const path = await this.createOperationObject(
|
|
179
176
|
event?.http?.method || event?.httpApi?.method,
|
|
180
|
-
documentation
|
|
181
|
-
opId
|
|
177
|
+
documentation
|
|
182
178
|
).catch((err) => {
|
|
183
179
|
throw err;
|
|
184
180
|
});
|
|
@@ -282,11 +278,25 @@ class DefinitionGenerator {
|
|
|
282
278
|
Object.assign(this.openAPI, { tags: tags });
|
|
283
279
|
}
|
|
284
280
|
|
|
285
|
-
async createOperationObject(method, documentation
|
|
281
|
+
async createOperationObject(method, documentation) {
|
|
282
|
+
let operationId = documentation?.operationId || this.operationName;
|
|
283
|
+
if (this.operationIds.includes(operationId)) {
|
|
284
|
+
operationId += `-${uuid()}`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const arr = this.functionMap[this.operationName];
|
|
288
|
+
arr.push(operationId);
|
|
289
|
+
this.functionMap[this.operationName] = arr;
|
|
290
|
+
|
|
291
|
+
this.operationIds.push(operationId);
|
|
292
|
+
Object.assign(this.operationIdMap, {
|
|
293
|
+
[operationId]: this.operationName,
|
|
294
|
+
});
|
|
295
|
+
|
|
286
296
|
const obj = {
|
|
287
297
|
summary: documentation.summary || "",
|
|
288
298
|
description: documentation.description || "",
|
|
289
|
-
operationId:
|
|
299
|
+
operationId: operationId,
|
|
290
300
|
parameters: [],
|
|
291
301
|
tags: documentation.tags || [],
|
|
292
302
|
};
|
|
@@ -472,6 +482,10 @@ class DefinitionGenerator {
|
|
|
472
482
|
}
|
|
473
483
|
}
|
|
474
484
|
|
|
485
|
+
if (response.links) {
|
|
486
|
+
obj.links = this.createLinks(response.links);
|
|
487
|
+
}
|
|
488
|
+
|
|
475
489
|
Object.assign(responses, { [response.statusCode]: obj });
|
|
476
490
|
}
|
|
477
491
|
|
|
@@ -691,6 +705,36 @@ class DefinitionGenerator {
|
|
|
691
705
|
return params;
|
|
692
706
|
}
|
|
693
707
|
|
|
708
|
+
createLinks(links) {
|
|
709
|
+
const linksObj = {};
|
|
710
|
+
for (const link in links) {
|
|
711
|
+
const linkObj = links[link];
|
|
712
|
+
const obj = {};
|
|
713
|
+
|
|
714
|
+
obj.operationId = linkObj.operation;
|
|
715
|
+
|
|
716
|
+
if (linkObj.description) {
|
|
717
|
+
obj.description = linkObj.description;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (linkObj.server) {
|
|
721
|
+
obj.server = this.createServers(linkObj.server);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (linkObj.parameters) {
|
|
725
|
+
obj.parameters = linkObj.parameters;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (linkObj.requestBody) {
|
|
729
|
+
obj.requestBody = linkObj.requestBody;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
Object.assign(linksObj, { [link]: obj });
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return linksObj;
|
|
736
|
+
}
|
|
737
|
+
|
|
694
738
|
addToComponents(type, schema, name) {
|
|
695
739
|
const schemaObj = {
|
|
696
740
|
[name]: schema,
|
|
@@ -863,6 +907,27 @@ class DefinitionGenerator {
|
|
|
863
907
|
return examplesObj;
|
|
864
908
|
}
|
|
865
909
|
|
|
910
|
+
cleanupLinks() {
|
|
911
|
+
for (const path of Object.keys(this.openAPI.paths)) {
|
|
912
|
+
for (const [name, value] of Object.entries(this.openAPI.paths[path])) {
|
|
913
|
+
for (const [statusCode, responseObj] of Object.entries(
|
|
914
|
+
value.responses
|
|
915
|
+
)) {
|
|
916
|
+
if (responseObj.links) {
|
|
917
|
+
for (const [linkName, linkObj] of Object.entries(
|
|
918
|
+
responseObj.links
|
|
919
|
+
)) {
|
|
920
|
+
const opId = linkObj.operationId;
|
|
921
|
+
if (this.functionMap[opId]) {
|
|
922
|
+
linkObj.operationId = this.functionMap[opId][0];
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
866
931
|
getHTTPFunctions() {
|
|
867
932
|
const isHttpFunction = (funcType) => {
|
|
868
933
|
const keys = Object.keys(funcType);
|
|
@@ -883,7 +948,16 @@ class DefinitionGenerator {
|
|
|
883
948
|
})
|
|
884
949
|
.map((functionType) => {
|
|
885
950
|
const event = functionType.events.filter(isHttpFunction);
|
|
951
|
+
const name = functionType.name.split(
|
|
952
|
+
`${this.serverless.service.service}-${this.serverless.service.provider.stage}-`
|
|
953
|
+
)[1];
|
|
954
|
+
|
|
955
|
+
Object.assign(this.functionMap, {
|
|
956
|
+
[name]: [],
|
|
957
|
+
});
|
|
958
|
+
|
|
886
959
|
return {
|
|
960
|
+
operationName: name,
|
|
887
961
|
functionInfo: functionType,
|
|
888
962
|
handler: functionType.handler,
|
|
889
963
|
name: functionType.name,
|
package/src/schemaHandler.js
CHANGED
|
@@ -68,17 +68,10 @@ class SchemaHandler {
|
|
|
68
68
|
).catch((err) => {
|
|
69
69
|
if (err.errors) {
|
|
70
70
|
for (const error of err?.errors) {
|
|
71
|
-
|
|
72
|
-
// throw err;
|
|
73
|
-
throw new Error(
|
|
74
|
-
`There was an error dereferencing ${
|
|
75
|
-
model.name
|
|
76
|
-
} schema. \n\n dereferencing message: ${
|
|
77
|
-
error.message
|
|
78
|
-
} \n\n Model received: ${JSON.stringify(model)}`
|
|
79
|
-
);
|
|
80
|
-
}
|
|
71
|
+
this.__HTTPError(error);
|
|
81
72
|
}
|
|
73
|
+
} else {
|
|
74
|
+
this.__HTTPError(err);
|
|
82
75
|
}
|
|
83
76
|
return modelSchema;
|
|
84
77
|
});
|
|
@@ -108,7 +101,11 @@ class SchemaHandler {
|
|
|
108
101
|
throw new Error(
|
|
109
102
|
`There was an error converting the ${
|
|
110
103
|
model.name
|
|
111
|
-
} schema. Model received looks like: \n\n${JSON.stringify(
|
|
104
|
+
} schema. Model received looks like: \n\n${JSON.stringify(
|
|
105
|
+
model
|
|
106
|
+
)}. The convereted schema looks like \n\n${JSON.stringify(
|
|
107
|
+
convertedSchemas
|
|
108
|
+
)}`
|
|
112
109
|
);
|
|
113
110
|
}
|
|
114
111
|
}
|
|
@@ -230,6 +227,19 @@ class SchemaHandler {
|
|
|
230
227
|
Object.assign(this.openAPI, components);
|
|
231
228
|
}
|
|
232
229
|
}
|
|
230
|
+
|
|
231
|
+
__HTTPError(error) {
|
|
232
|
+
if (error.message.includes("HTTP ERROR")) {
|
|
233
|
+
// throw err;
|
|
234
|
+
throw new Error(
|
|
235
|
+
`There was an error dereferencing ${
|
|
236
|
+
model.name
|
|
237
|
+
} schema. \n\n dereferencing message: ${
|
|
238
|
+
error.message
|
|
239
|
+
} \n\n Model received: ${JSON.stringify(model)}`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
233
243
|
}
|
|
234
244
|
|
|
235
245
|
module.exports = SchemaHandler;
|
|
@@ -1,223 +1,264 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const fs = require(
|
|
4
|
-
const PostmanGenerator = require(
|
|
5
|
-
const sinon = require(
|
|
6
|
-
const expect = require(
|
|
7
|
-
|
|
8
|
-
const validOpenAPI = require(
|
|
9
|
-
|
|
10
|
-
const basicDocumentation = require(
|
|
11
|
-
const basicValidFunction = require(
|
|
12
|
-
|
|
13
|
-
const OpenAPIGenerator = require(
|
|
14
|
-
|
|
15
|
-
describe(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const PostmanGenerator = require("openapi-to-postmanv2");
|
|
5
|
+
const sinon = require("sinon");
|
|
6
|
+
const expect = require("chai").expect;
|
|
7
|
+
|
|
8
|
+
const validOpenAPI = require("../json/valid-openAPI.json");
|
|
9
|
+
|
|
10
|
+
const basicDocumentation = require("../models/BasicDocumentation.json");
|
|
11
|
+
const basicValidFunction = require("../models/BasicValidFunction.json");
|
|
12
|
+
|
|
13
|
+
const OpenAPIGenerator = require("../../src/openAPIGenerator");
|
|
14
|
+
|
|
15
|
+
describe("OpenAPIGenerator", () => {
|
|
16
|
+
let sls, logOutput;
|
|
17
|
+
beforeEach(function () {
|
|
18
|
+
sls = {
|
|
19
|
+
service: {
|
|
20
|
+
service: "test-service",
|
|
21
|
+
provider: {
|
|
22
|
+
stage: "test",
|
|
23
|
+
},
|
|
24
|
+
getAllFunctions: () => {},
|
|
25
|
+
getFunction: () => {},
|
|
26
|
+
},
|
|
27
|
+
version: "3.0.0",
|
|
28
|
+
variables: {
|
|
29
|
+
service: {
|
|
30
|
+
custom: {},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
configSchemaHandler: {
|
|
34
|
+
defineFunctionEventProperties: () => {},
|
|
35
|
+
defineFunctionProperties: () => {},
|
|
36
|
+
defineCustomProperties: () => {},
|
|
37
|
+
},
|
|
38
|
+
classes: {
|
|
39
|
+
Error: class ServerlessError {
|
|
40
|
+
constructor(err) {
|
|
41
|
+
return new Error(err);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
processedInput: {
|
|
46
|
+
options: {
|
|
47
|
+
postmanCollection: "postman.json",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
logOutput = {
|
|
53
|
+
log: {
|
|
54
|
+
notice: (str) => {},
|
|
55
|
+
error: (str) => {},
|
|
56
|
+
success: (str) => {},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("generationAndValidation", () => {
|
|
62
|
+
it("should correctly generate a valid openAPI document", async function () {
|
|
63
|
+
const succSpy = sinon.spy(logOutput.log, "success");
|
|
64
|
+
const errSpy = sinon.spy(logOutput.log, "error");
|
|
65
|
+
|
|
66
|
+
Object.assign(sls.service, basicDocumentation);
|
|
67
|
+
const getAllFuncsStub = sinon
|
|
68
|
+
.stub(sls.service, "getAllFunctions")
|
|
69
|
+
.returns(["createUser"]);
|
|
70
|
+
|
|
71
|
+
const getFuncStub = sinon
|
|
72
|
+
.stub(sls.service, "getFunction")
|
|
73
|
+
.returns(basicValidFunction.createUser);
|
|
74
|
+
|
|
75
|
+
const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput);
|
|
76
|
+
openAPIGenerator.processCliInput();
|
|
77
|
+
|
|
78
|
+
const validOpenAPIDocument = await openAPIGenerator
|
|
79
|
+
.generationAndValidation()
|
|
80
|
+
.catch((err) => {
|
|
81
|
+
expect(err).to.be.undefined;
|
|
81
82
|
});
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const errSpy = sinon.spy(logOutput.log, 'error')
|
|
84
|
+
expect(succSpy.called).to.be.true;
|
|
85
|
+
expect(errSpy.called).to.be.false;
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const getFuncStub = sinon.stub(sls.service, 'getFunction').returns(basicInvalidFunction.createUser)
|
|
93
|
-
|
|
94
|
-
const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput)
|
|
95
|
-
openAPIGenerator.processCliInput()
|
|
96
|
-
|
|
97
|
-
const validOpenAPIDocument = await openAPIGenerator.generationAndValidation()
|
|
98
|
-
.catch(err => {
|
|
99
|
-
expect(err.message).to.be.equal('Error: createUser is missing a Response Model for statusCode 200')
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
expect(succSpy.called).to.be.false
|
|
103
|
-
expect(errSpy.called).to.be.true
|
|
87
|
+
succSpy.restore();
|
|
88
|
+
errSpy.restore();
|
|
89
|
+
getAllFuncsStub.reset();
|
|
90
|
+
getFuncStub.reset();
|
|
91
|
+
});
|
|
104
92
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
93
|
+
it("should throw an error when trying to generate an invalid openAPI document", async function () {
|
|
94
|
+
const succSpy = sinon.spy(logOutput.log, "success");
|
|
95
|
+
const errSpy = sinon.spy(logOutput.log, "error");
|
|
96
|
+
|
|
97
|
+
Object.assign(sls.service, basicDocumentation);
|
|
98
|
+
const getAllFuncsStub = sinon
|
|
99
|
+
.stub(sls.service, "getAllFunctions")
|
|
100
|
+
.returns(["createUser"]);
|
|
101
|
+
const basicInvalidFunction = JSON.parse(
|
|
102
|
+
JSON.stringify(basicValidFunction)
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
delete basicInvalidFunction.createUser.events[0].http.documentation
|
|
106
|
+
.methodResponses[0].responseModels;
|
|
107
|
+
const getFuncStub = sinon
|
|
108
|
+
.stub(sls.service, "getFunction")
|
|
109
|
+
.returns(basicInvalidFunction.createUser);
|
|
110
|
+
|
|
111
|
+
const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput);
|
|
112
|
+
openAPIGenerator.processCliInput();
|
|
113
|
+
|
|
114
|
+
const validOpenAPIDocument = await openAPIGenerator
|
|
115
|
+
.generationAndValidation()
|
|
116
|
+
.catch((err) => {
|
|
117
|
+
expect(err.message).to.be.equal(
|
|
118
|
+
"Error: createUser is missing a Response Model for statusCode 200"
|
|
119
|
+
);
|
|
109
120
|
});
|
|
110
121
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const errSpy = sinon.spy(logOutput.log, 'error')
|
|
114
|
-
|
|
115
|
-
Object.assign(sls.service, basicDocumentation)
|
|
116
|
-
const getAllFuncsStub = sinon.stub(sls.service, 'getAllFunctions').returns(['createUser'])
|
|
117
|
-
const basicInvalidFunction = JSON.parse(JSON.stringify(basicValidFunction))
|
|
118
|
-
|
|
119
|
-
const getFuncStub = sinon.stub(sls.service, 'getFunction').returns(basicInvalidFunction.createUser)
|
|
122
|
+
expect(succSpy.called).to.be.false;
|
|
123
|
+
expect(errSpy.called).to.be.true;
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
expect(err).to.be.undefined
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
expect(succSpy.called).to.be.true
|
|
130
|
-
expect(errSpy.called).to.be.false
|
|
131
|
-
expect(validOpenAPIDocument).to.have.property('openapi')
|
|
125
|
+
succSpy.restore();
|
|
126
|
+
errSpy.restore();
|
|
127
|
+
getAllFuncsStub.reset();
|
|
128
|
+
getFuncStub.reset();
|
|
129
|
+
});
|
|
132
130
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
it("should correctly validate a valid openAPI document", async function () {
|
|
132
|
+
const succSpy = sinon.spy(logOutput.log, "success");
|
|
133
|
+
const errSpy = sinon.spy(logOutput.log, "error");
|
|
134
|
+
|
|
135
|
+
Object.assign(sls.service, basicDocumentation);
|
|
136
|
+
const getAllFuncsStub = sinon
|
|
137
|
+
.stub(sls.service, "getAllFunctions")
|
|
138
|
+
.returns(["createUser"]);
|
|
139
|
+
const basicInvalidFunction = JSON.parse(
|
|
140
|
+
JSON.stringify(basicValidFunction)
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const getFuncStub = sinon
|
|
144
|
+
.stub(sls.service, "getFunction")
|
|
145
|
+
.returns(basicInvalidFunction.createUser);
|
|
146
|
+
|
|
147
|
+
const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput);
|
|
148
|
+
openAPIGenerator.processCliInput();
|
|
149
|
+
|
|
150
|
+
const validOpenAPIDocument = await openAPIGenerator
|
|
151
|
+
.generationAndValidation()
|
|
152
|
+
.catch((err) => {
|
|
153
|
+
expect(err).to.be.undefined;
|
|
137
154
|
});
|
|
138
155
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
Object.assign(sls.service, basicDocumentation)
|
|
144
|
-
const getAllFuncsStub = sinon.stub(sls.service, 'getAllFunctions').returns(['createUser'])
|
|
145
|
-
const basicInvalidFunction = JSON.parse(JSON.stringify(basicValidFunction))
|
|
156
|
+
expect(succSpy.called).to.be.true;
|
|
157
|
+
expect(errSpy.called).to.be.false;
|
|
158
|
+
expect(validOpenAPIDocument).to.have.property("openapi");
|
|
146
159
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
160
|
+
succSpy.restore();
|
|
161
|
+
errSpy.restore();
|
|
162
|
+
getAllFuncsStub.reset();
|
|
163
|
+
getFuncStub.reset();
|
|
164
|
+
});
|
|
152
165
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
166
|
+
it("should throw an error when trying to validate an invalid openAPI document", async function () {
|
|
167
|
+
const succSpy = sinon.spy(logOutput.log, "success");
|
|
168
|
+
const errSpy = sinon.spy(logOutput.log, "error");
|
|
169
|
+
|
|
170
|
+
Object.assign(sls.service, basicDocumentation);
|
|
171
|
+
const getAllFuncsStub = sinon
|
|
172
|
+
.stub(sls.service, "getAllFunctions")
|
|
173
|
+
.returns(["createUser"]);
|
|
174
|
+
const basicInvalidFunction = JSON.parse(
|
|
175
|
+
JSON.stringify(basicValidFunction)
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
delete basicInvalidFunction.createUser.events[0].http.documentation
|
|
179
|
+
.pathParams;
|
|
180
|
+
const getFuncStub = sinon
|
|
181
|
+
.stub(sls.service, "getFunction")
|
|
182
|
+
.returns(basicInvalidFunction.createUser);
|
|
183
|
+
|
|
184
|
+
const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput);
|
|
185
|
+
openAPIGenerator.processCliInput();
|
|
186
|
+
|
|
187
|
+
const validOpenAPIDocument = await openAPIGenerator
|
|
188
|
+
.generationAndValidation()
|
|
189
|
+
.catch((err) => {
|
|
190
|
+
expect(err.message).to.be.equal(
|
|
191
|
+
"AssertionError: Templated parameter name not found"
|
|
192
|
+
);
|
|
193
|
+
});
|
|
157
194
|
|
|
158
|
-
|
|
159
|
-
|
|
195
|
+
expect(succSpy.called).to.be.false;
|
|
196
|
+
expect(errSpy.called).to.be.true;
|
|
160
197
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
198
|
+
succSpy.restore();
|
|
199
|
+
errSpy.restore();
|
|
200
|
+
getAllFuncsStub.reset();
|
|
201
|
+
getFuncStub.reset();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe("createPostman", () => {
|
|
206
|
+
it("should generate a postman collection when a valid openAPI file is generated", function () {
|
|
207
|
+
const fsStub = sinon.stub(fs, "writeFileSync").returns(true);
|
|
208
|
+
const succSpy = sinon.spy(logOutput.log, "success");
|
|
209
|
+
const errSpy = sinon.spy(logOutput.log, "error");
|
|
210
|
+
const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput);
|
|
211
|
+
openAPIGenerator.processCliInput();
|
|
212
|
+
|
|
213
|
+
openAPIGenerator.createPostman(validOpenAPI);
|
|
214
|
+
|
|
215
|
+
expect(fsStub.called).to.be.true;
|
|
216
|
+
expect(succSpy.calledTwice).to.be.true;
|
|
217
|
+
expect(errSpy.called).to.be.false;
|
|
218
|
+
fsStub.restore();
|
|
219
|
+
succSpy.restore();
|
|
220
|
+
errSpy.restore();
|
|
166
221
|
});
|
|
167
222
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const errStub = sinon.stub(logOutput.log, 'error').returns('')
|
|
188
|
-
const succSpy = sinon.spy(logOutput.log, 'success')
|
|
189
|
-
const fsStub = sinon.stub(fs, 'writeFileSync').throws(new Error())
|
|
190
|
-
const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput)
|
|
191
|
-
openAPIGenerator.processCliInput()
|
|
192
|
-
|
|
193
|
-
expect(() => {openAPIGenerator.createPostman(validOpenAPI)}).to.throw()
|
|
194
|
-
|
|
195
|
-
expect(fsStub.called).to.be.true
|
|
196
|
-
expect(errStub.called).to.be.true
|
|
197
|
-
expect(succSpy.calledOnce).to.be.true
|
|
198
|
-
expect(succSpy.calledTwice).to.be.false
|
|
199
|
-
fsStub.restore()
|
|
200
|
-
succSpy.restore()
|
|
201
|
-
errStub.restore()
|
|
202
|
-
});
|
|
223
|
+
it("should throw an error when writing a file fails", function () {
|
|
224
|
+
const errStub = sinon.stub(logOutput.log, "error").returns("");
|
|
225
|
+
const succSpy = sinon.spy(logOutput.log, "success");
|
|
226
|
+
const fsStub = sinon.stub(fs, "writeFileSync").throws(new Error());
|
|
227
|
+
const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput);
|
|
228
|
+
openAPIGenerator.processCliInput();
|
|
229
|
+
|
|
230
|
+
expect(() => {
|
|
231
|
+
openAPIGenerator.createPostman(validOpenAPI);
|
|
232
|
+
}).to.throw();
|
|
233
|
+
|
|
234
|
+
expect(fsStub.called).to.be.true;
|
|
235
|
+
expect(errStub.called).to.be.true;
|
|
236
|
+
expect(succSpy.calledOnce).to.be.true;
|
|
237
|
+
expect(succSpy.calledTwice).to.be.false;
|
|
238
|
+
fsStub.restore();
|
|
239
|
+
succSpy.restore();
|
|
240
|
+
errStub.restore();
|
|
241
|
+
});
|
|
203
242
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
243
|
+
it("should throw an error converting an OpenAPI fails", function () {
|
|
244
|
+
const errStub = sinon.spy(logOutput.log, "error");
|
|
245
|
+
const succSpy = sinon.spy(logOutput.log, "success");
|
|
246
|
+
const pgStub = sinon.stub(PostmanGenerator, "convert");
|
|
247
|
+
pgStub.yields(new Error());
|
|
209
248
|
|
|
210
|
-
|
|
211
|
-
|
|
249
|
+
const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput);
|
|
250
|
+
openAPIGenerator.processCliInput();
|
|
212
251
|
|
|
213
|
-
|
|
252
|
+
expect(() => {
|
|
253
|
+
openAPIGenerator.createPostman(validOpenAPI);
|
|
254
|
+
}).to.throw();
|
|
214
255
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
256
|
+
expect(errStub.called).to.be.true;
|
|
257
|
+
expect(succSpy.calledOnce).to.be.false;
|
|
258
|
+
expect(succSpy.calledTwice).to.be.false;
|
|
218
259
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
});
|
|
260
|
+
succSpy.restore();
|
|
261
|
+
errStub.restore();
|
|
222
262
|
});
|
|
263
|
+
});
|
|
223
264
|
});
|