@aloma.io/integration-sdk 3.8.55 → 3.8.57
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/MULTI_RESOURCE_GUIDE.md +24 -21
- package/OPENAPI_TO_CONNECTOR.md +146 -16
- package/README.md +62 -10
- package/build/cli.mjs +122 -33
- package/build/internal/dispatcher/index.mjs +3 -2
- package/build/openapi-to-connector.d.mts +88 -11
- package/build/openapi-to-connector.mjs +909 -209
- package/package.json +15 -1
- package/src/cli.mts +140 -37
- package/src/internal/dispatcher/index.mts +4 -2
- package/src/openapi-to-connector.mts +1006 -217
- package/test/scenarios/README.md +148 -0
- package/test/scenarios/complex/expected/controller.mts +271 -0
- package/test/scenarios/complex/expected/orders-resource.mts +264 -0
- package/test/scenarios/complex/expected/products-resource.mts +239 -0
- package/test/scenarios/complex/specs/orders.json +362 -0
- package/test/scenarios/complex/specs/products.json +308 -0
- package/test/scenarios/simple/expected-controller.mts +60 -0
- package/test/scenarios/simple/simple-api.json +39 -0
- package/test/scenarios.test.mts +286 -0
- package/test/verify-scenarios.mjs +298 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.0",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "Products API",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"description": "A simple products management API"
|
|
7
|
+
},
|
|
8
|
+
"servers": [
|
|
9
|
+
{
|
|
10
|
+
"url": "https://api.testshop.com",
|
|
11
|
+
"description": "Production server"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"paths": {
|
|
15
|
+
"/products": {
|
|
16
|
+
"get": {
|
|
17
|
+
"operationId": "getProducts",
|
|
18
|
+
"summary": "List all products",
|
|
19
|
+
"parameters": [
|
|
20
|
+
{
|
|
21
|
+
"name": "limit",
|
|
22
|
+
"in": "query",
|
|
23
|
+
"schema": {
|
|
24
|
+
"type": "integer",
|
|
25
|
+
"minimum": 1,
|
|
26
|
+
"maximum": 100
|
|
27
|
+
},
|
|
28
|
+
"description": "Maximum number of products to return"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "category",
|
|
32
|
+
"in": "query",
|
|
33
|
+
"schema": {
|
|
34
|
+
"type": "string"
|
|
35
|
+
},
|
|
36
|
+
"description": "Filter by category"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "archived",
|
|
40
|
+
"in": "query",
|
|
41
|
+
"schema": {
|
|
42
|
+
"type": "boolean"
|
|
43
|
+
},
|
|
44
|
+
"description": "Include archived products"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"responses": {
|
|
48
|
+
"200": {
|
|
49
|
+
"description": "List of products",
|
|
50
|
+
"content": {
|
|
51
|
+
"application/json": {
|
|
52
|
+
"schema": {
|
|
53
|
+
"$ref": "#/components/schemas/ProductList"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"post": {
|
|
61
|
+
"operationId": "createProduct",
|
|
62
|
+
"summary": "Create a new product",
|
|
63
|
+
"requestBody": {
|
|
64
|
+
"required": true,
|
|
65
|
+
"content": {
|
|
66
|
+
"application/json": {
|
|
67
|
+
"schema": {
|
|
68
|
+
"$ref": "#/components/schemas/CreateProductRequest"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"responses": {
|
|
74
|
+
"201": {
|
|
75
|
+
"description": "Product created",
|
|
76
|
+
"content": {
|
|
77
|
+
"application/json": {
|
|
78
|
+
"schema": {
|
|
79
|
+
"$ref": "#/components/schemas/Product"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"/products/{productId}": {
|
|
88
|
+
"get": {
|
|
89
|
+
"operationId": "getProduct",
|
|
90
|
+
"summary": "Get a specific product",
|
|
91
|
+
"parameters": [
|
|
92
|
+
{
|
|
93
|
+
"name": "productId",
|
|
94
|
+
"in": "path",
|
|
95
|
+
"required": true,
|
|
96
|
+
"schema": {
|
|
97
|
+
"type": "string"
|
|
98
|
+
},
|
|
99
|
+
"description": "The product ID"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"responses": {
|
|
103
|
+
"200": {
|
|
104
|
+
"description": "Product details",
|
|
105
|
+
"content": {
|
|
106
|
+
"application/json": {
|
|
107
|
+
"schema": {
|
|
108
|
+
"$ref": "#/components/schemas/Product"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"put": {
|
|
116
|
+
"operationId": "updateProduct",
|
|
117
|
+
"summary": "Update a product",
|
|
118
|
+
"parameters": [
|
|
119
|
+
{
|
|
120
|
+
"name": "productId",
|
|
121
|
+
"in": "path",
|
|
122
|
+
"required": true,
|
|
123
|
+
"schema": {
|
|
124
|
+
"type": "string"
|
|
125
|
+
},
|
|
126
|
+
"description": "The product ID"
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"requestBody": {
|
|
130
|
+
"required": true,
|
|
131
|
+
"content": {
|
|
132
|
+
"application/json": {
|
|
133
|
+
"schema": {
|
|
134
|
+
"$ref": "#/components/schemas/UpdateProductRequest"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"responses": {
|
|
140
|
+
"200": {
|
|
141
|
+
"description": "Product updated",
|
|
142
|
+
"content": {
|
|
143
|
+
"application/json": {
|
|
144
|
+
"schema": {
|
|
145
|
+
"$ref": "#/components/schemas/Product"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"delete": {
|
|
153
|
+
"operationId": "deleteProduct",
|
|
154
|
+
"summary": "Delete a product",
|
|
155
|
+
"parameters": [
|
|
156
|
+
{
|
|
157
|
+
"name": "productId",
|
|
158
|
+
"in": "path",
|
|
159
|
+
"required": true,
|
|
160
|
+
"schema": {
|
|
161
|
+
"type": "string"
|
|
162
|
+
},
|
|
163
|
+
"description": "The product ID"
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
"responses": {
|
|
167
|
+
"204": {
|
|
168
|
+
"description": "Product deleted"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"components": {
|
|
175
|
+
"schemas": {
|
|
176
|
+
"Product": {
|
|
177
|
+
"type": "object",
|
|
178
|
+
"required": ["id", "name", "price"],
|
|
179
|
+
"properties": {
|
|
180
|
+
"id": {
|
|
181
|
+
"type": "string",
|
|
182
|
+
"description": "Unique product identifier"
|
|
183
|
+
},
|
|
184
|
+
"name": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"description": "Product name"
|
|
187
|
+
},
|
|
188
|
+
"description": {
|
|
189
|
+
"type": "string",
|
|
190
|
+
"description": "Product description"
|
|
191
|
+
},
|
|
192
|
+
"price": {
|
|
193
|
+
"type": "number",
|
|
194
|
+
"minimum": 0,
|
|
195
|
+
"description": "Product price"
|
|
196
|
+
},
|
|
197
|
+
"category": {
|
|
198
|
+
"type": "string",
|
|
199
|
+
"description": "Product category"
|
|
200
|
+
},
|
|
201
|
+
"inStock": {
|
|
202
|
+
"type": "boolean",
|
|
203
|
+
"description": "Whether product is in stock"
|
|
204
|
+
},
|
|
205
|
+
"tags": {
|
|
206
|
+
"type": "array",
|
|
207
|
+
"items": {
|
|
208
|
+
"type": "string"
|
|
209
|
+
},
|
|
210
|
+
"description": "Product tags"
|
|
211
|
+
},
|
|
212
|
+
"createdAt": {
|
|
213
|
+
"type": "string",
|
|
214
|
+
"format": "date-time",
|
|
215
|
+
"description": "Creation timestamp"
|
|
216
|
+
},
|
|
217
|
+
"updatedAt": {
|
|
218
|
+
"type": "string",
|
|
219
|
+
"format": "date-time",
|
|
220
|
+
"description": "Last update timestamp"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
"ProductList": {
|
|
225
|
+
"type": "object",
|
|
226
|
+
"properties": {
|
|
227
|
+
"products": {
|
|
228
|
+
"type": "array",
|
|
229
|
+
"items": {
|
|
230
|
+
"$ref": "#/components/schemas/Product"
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
"total": {
|
|
234
|
+
"type": "integer",
|
|
235
|
+
"description": "Total number of products"
|
|
236
|
+
},
|
|
237
|
+
"hasMore": {
|
|
238
|
+
"type": "boolean",
|
|
239
|
+
"description": "Whether there are more products available"
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
"CreateProductRequest": {
|
|
244
|
+
"type": "object",
|
|
245
|
+
"required": ["name", "price"],
|
|
246
|
+
"properties": {
|
|
247
|
+
"name": {
|
|
248
|
+
"type": "string",
|
|
249
|
+
"description": "Product name"
|
|
250
|
+
},
|
|
251
|
+
"description": {
|
|
252
|
+
"type": "string",
|
|
253
|
+
"description": "Product description"
|
|
254
|
+
},
|
|
255
|
+
"price": {
|
|
256
|
+
"type": "number",
|
|
257
|
+
"minimum": 0,
|
|
258
|
+
"description": "Product price"
|
|
259
|
+
},
|
|
260
|
+
"category": {
|
|
261
|
+
"type": "string",
|
|
262
|
+
"description": "Product category"
|
|
263
|
+
},
|
|
264
|
+
"tags": {
|
|
265
|
+
"type": "array",
|
|
266
|
+
"items": {
|
|
267
|
+
"type": "string"
|
|
268
|
+
},
|
|
269
|
+
"description": "Product tags"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
"UpdateProductRequest": {
|
|
274
|
+
"type": "object",
|
|
275
|
+
"properties": {
|
|
276
|
+
"name": {
|
|
277
|
+
"type": "string",
|
|
278
|
+
"description": "Product name"
|
|
279
|
+
},
|
|
280
|
+
"description": {
|
|
281
|
+
"type": "string",
|
|
282
|
+
"description": "Product description"
|
|
283
|
+
},
|
|
284
|
+
"price": {
|
|
285
|
+
"type": "number",
|
|
286
|
+
"minimum": 0,
|
|
287
|
+
"description": "Product price"
|
|
288
|
+
},
|
|
289
|
+
"category": {
|
|
290
|
+
"type": "string",
|
|
291
|
+
"description": "Product category"
|
|
292
|
+
},
|
|
293
|
+
"inStock": {
|
|
294
|
+
"type": "boolean",
|
|
295
|
+
"description": "Whether product is in stock"
|
|
296
|
+
},
|
|
297
|
+
"tags": {
|
|
298
|
+
"type": "array",
|
|
299
|
+
"items": {
|
|
300
|
+
"type": "string"
|
|
301
|
+
},
|
|
302
|
+
"description": "Product tags"
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {AbstractController} from '@aloma.io/integration-sdk';
|
|
2
|
+
|
|
3
|
+
export default class Controller extends AbstractController {
|
|
4
|
+
|
|
5
|
+
private api: any;
|
|
6
|
+
|
|
7
|
+
protected async start(): Promise<void> {
|
|
8
|
+
const config = this.config;
|
|
9
|
+
|
|
10
|
+
this.api = this.getClient({
|
|
11
|
+
baseUrl: 'https://api.example.com',
|
|
12
|
+
customize(request) {
|
|
13
|
+
request.headers ||= {};
|
|
14
|
+
// Add authentication headers based on your API requirements
|
|
15
|
+
// Example: request.headers["Authorization"] = `Bearer ${config.apiToken}`;
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get all products
|
|
22
|
+
*
|
|
23
|
+
*
|
|
24
|
+
* @returns {Promise<any>} GET /products response
|
|
25
|
+
*/
|
|
26
|
+
async getProducts() {
|
|
27
|
+
const url = '/products';
|
|
28
|
+
|
|
29
|
+
const fetchOptions: any = {
|
|
30
|
+
method: 'GET',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return this.api.fetch(url, fetchOptions);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create product
|
|
38
|
+
*
|
|
39
|
+
* @param {Object} options - Request options
|
|
40
|
+
* @param {any} options.body (required) - Request body
|
|
41
|
+
*
|
|
42
|
+
* @returns {Promise<any>} POST /products response
|
|
43
|
+
*/
|
|
44
|
+
async createProduct(options: {body?: any}) {
|
|
45
|
+
options = options || {};
|
|
46
|
+
|
|
47
|
+
const url = '/products';
|
|
48
|
+
|
|
49
|
+
const { headers, ...bodyData } = options;
|
|
50
|
+
const requestBody = Object.keys(bodyData).length > 0 ? bodyData : undefined;
|
|
51
|
+
|
|
52
|
+
const fetchOptions: any = {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
body: requestBody,
|
|
55
|
+
headers: options.headers,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return this.api.fetch(url, fetchOptions);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.0",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "Simple API",
|
|
5
|
+
"version": "1.0.0"
|
|
6
|
+
},
|
|
7
|
+
"paths": {
|
|
8
|
+
"/products": {
|
|
9
|
+
"get": {
|
|
10
|
+
"operationId": "getProducts",
|
|
11
|
+
"summary": "Get all products",
|
|
12
|
+
"responses": {
|
|
13
|
+
"200": {
|
|
14
|
+
"description": "Success"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"post": {
|
|
19
|
+
"operationId": "createProduct",
|
|
20
|
+
"summary": "Create product",
|
|
21
|
+
"requestBody": {
|
|
22
|
+
"required": true,
|
|
23
|
+
"content": {
|
|
24
|
+
"application/json": {
|
|
25
|
+
"schema": {
|
|
26
|
+
"type": "object"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"responses": {
|
|
32
|
+
"201": {
|
|
33
|
+
"description": "Created"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { describe, it } from 'mocha';
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { OpenAPIToConnector } from '../build/openapi-to-connector.mjs';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
describe('Scenario Tests - Full Code Generation', () => {
|
|
12
|
+
|
|
13
|
+
describe('Simple Scenario (single controller)', () => {
|
|
14
|
+
it('should generate exact expected output for simple-api.json', () => {
|
|
15
|
+
// Read input spec
|
|
16
|
+
const specPath = path.join(__dirname, 'scenarios/simple/simple-api.json');
|
|
17
|
+
const specContent = fs.readFileSync(specPath, 'utf-8');
|
|
18
|
+
const spec = OpenAPIToConnector.parseSpec(specContent);
|
|
19
|
+
|
|
20
|
+
// Read expected output
|
|
21
|
+
const expectedPath = path.join(__dirname, 'scenarios/simple/expected-controller.mts');
|
|
22
|
+
const expectedOutput = fs.readFileSync(expectedPath, 'utf-8');
|
|
23
|
+
|
|
24
|
+
// Generate actual output
|
|
25
|
+
const generator = new OpenAPIToConnector(spec, 'simple-test');
|
|
26
|
+
const actualOutput = generator.generateController();
|
|
27
|
+
|
|
28
|
+
// Compare outputs (normalize line endings)
|
|
29
|
+
const normalizeOutput = (output: string) => output
|
|
30
|
+
.replace(/\r\n/g, '\n')
|
|
31
|
+
.trim();
|
|
32
|
+
|
|
33
|
+
expect(normalizeOutput(actualOutput)).to.equal(normalizeOutput(expectedOutput));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should correctly handle methods without options', () => {
|
|
37
|
+
// Read input spec
|
|
38
|
+
const specPath = path.join(__dirname, 'scenarios/simple/simple-api.json');
|
|
39
|
+
const specContent = fs.readFileSync(specPath, 'utf-8');
|
|
40
|
+
const spec = OpenAPIToConnector.parseSpec(specContent);
|
|
41
|
+
|
|
42
|
+
// Generate actual output
|
|
43
|
+
const generator = new OpenAPIToConnector(spec, 'simple-test');
|
|
44
|
+
const actualOutput = generator.generateController();
|
|
45
|
+
|
|
46
|
+
// Verify methods without options don't have unnecessary code
|
|
47
|
+
expect(actualOutput).to.include('async getProducts() {'); // No options parameter
|
|
48
|
+
expect(actualOutput).to.not.include('options = options || {}'); // No options initialization for getProducts
|
|
49
|
+
expect(actualOutput).to.include('async createProduct(options:'); // Has options parameter
|
|
50
|
+
expect(actualOutput).to.include('options = options || {};'); // Has options initialization for createProduct
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('Complex Scenario (multi-resource)', () => {
|
|
55
|
+
it('should generate exact expected main controller output', () => {
|
|
56
|
+
// Read input specs
|
|
57
|
+
const productsSpecPath = path.join(__dirname, 'scenarios/complex/specs/products.json');
|
|
58
|
+
const ordersSpecPath = path.join(__dirname, 'scenarios/complex/specs/orders.json');
|
|
59
|
+
const productsSpecContent = fs.readFileSync(productsSpecPath, 'utf-8');
|
|
60
|
+
const ordersSpecContent = fs.readFileSync(ordersSpecPath, 'utf-8');
|
|
61
|
+
|
|
62
|
+
const productsSpec = OpenAPIToConnector.parseSpec(productsSpecContent);
|
|
63
|
+
const ordersSpec = OpenAPIToConnector.parseSpec(ordersSpecContent);
|
|
64
|
+
|
|
65
|
+
// Read expected output
|
|
66
|
+
const expectedPath = path.join(__dirname, 'scenarios/complex/expected/controller.mts');
|
|
67
|
+
const expectedOutput = fs.readFileSync(expectedPath, 'utf-8');
|
|
68
|
+
|
|
69
|
+
// Generate actual output
|
|
70
|
+
const resources = [
|
|
71
|
+
{ className: 'ProductsResource', fileName: 'products' },
|
|
72
|
+
{ className: 'OrdersResource', fileName: 'orders' }
|
|
73
|
+
];
|
|
74
|
+
const resourceSpecs = [
|
|
75
|
+
{ fileName: 'products', spec: productsSpec },
|
|
76
|
+
{ fileName: 'orders', spec: ordersSpec }
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const mainGenerator = new OpenAPIToConnector(productsSpec, 'test-shop-temp');
|
|
80
|
+
const actualOutput = mainGenerator.generateMainController(resources, resourceSpecs);
|
|
81
|
+
|
|
82
|
+
// Compare outputs (normalize line endings)
|
|
83
|
+
const normalizeOutput = (output: string) => output
|
|
84
|
+
.replace(/\r\n/g, '\n')
|
|
85
|
+
.trim();
|
|
86
|
+
|
|
87
|
+
expect(normalizeOutput(actualOutput)).to.equal(normalizeOutput(expectedOutput));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should generate exact expected products resource output', () => {
|
|
91
|
+
// Read input spec
|
|
92
|
+
const specPath = path.join(__dirname, 'scenarios/complex/specs/products.json');
|
|
93
|
+
const specContent = fs.readFileSync(specPath, 'utf-8');
|
|
94
|
+
const spec = OpenAPIToConnector.parseSpec(specContent);
|
|
95
|
+
|
|
96
|
+
// Read expected output
|
|
97
|
+
const expectedPath = path.join(__dirname, 'scenarios/complex/expected/products-resource.mts');
|
|
98
|
+
const expectedOutput = fs.readFileSync(expectedPath, 'utf-8');
|
|
99
|
+
|
|
100
|
+
// Generate actual output
|
|
101
|
+
const generator = new OpenAPIToConnector(spec, 'test-shop');
|
|
102
|
+
const actualOutput = generator.generateResourceClass('ProductsResource');
|
|
103
|
+
|
|
104
|
+
// Compare outputs (normalize line endings)
|
|
105
|
+
const normalizeOutput = (output: string) => output
|
|
106
|
+
.replace(/\r\n/g, '\n')
|
|
107
|
+
.trim();
|
|
108
|
+
|
|
109
|
+
expect(normalizeOutput(actualOutput)).to.equal(normalizeOutput(expectedOutput));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should generate exact expected orders resource output', () => {
|
|
113
|
+
// Read input spec
|
|
114
|
+
const specPath = path.join(__dirname, 'scenarios/complex/specs/orders.json');
|
|
115
|
+
const specContent = fs.readFileSync(specPath, 'utf-8');
|
|
116
|
+
const spec = OpenAPIToConnector.parseSpec(specContent);
|
|
117
|
+
|
|
118
|
+
// Read expected output
|
|
119
|
+
const expectedPath = path.join(__dirname, 'scenarios/complex/expected/orders-resource.mts');
|
|
120
|
+
const expectedOutput = fs.readFileSync(expectedPath, 'utf-8');
|
|
121
|
+
|
|
122
|
+
// Generate actual output
|
|
123
|
+
const generator = new OpenAPIToConnector(spec, 'test-shop');
|
|
124
|
+
const actualOutput = generator.generateResourceClass('OrdersResource');
|
|
125
|
+
|
|
126
|
+
// Compare outputs (normalize line endings)
|
|
127
|
+
const normalizeOutput = (output: string) => output
|
|
128
|
+
.replace(/\r\n/g, '\n')
|
|
129
|
+
.trim();
|
|
130
|
+
|
|
131
|
+
expect(normalizeOutput(actualOutput)).to.equal(normalizeOutput(expectedOutput));
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should correctly handle TypeScript interface generation', () => {
|
|
135
|
+
// Read products spec
|
|
136
|
+
const specPath = path.join(__dirname, 'scenarios/complex/specs/products.json');
|
|
137
|
+
const specContent = fs.readFileSync(specPath, 'utf-8');
|
|
138
|
+
const spec = OpenAPIToConnector.parseSpec(specContent);
|
|
139
|
+
|
|
140
|
+
// Generate output
|
|
141
|
+
const generator = new OpenAPIToConnector(spec, 'test-shop');
|
|
142
|
+
const actualOutput = generator.generateResourceClass('ProductsResource');
|
|
143
|
+
|
|
144
|
+
// Verify TypeScript interfaces are generated correctly
|
|
145
|
+
expect(actualOutput).to.include('export interface Product {');
|
|
146
|
+
expect(actualOutput).to.include('export interface ProductList {');
|
|
147
|
+
expect(actualOutput).to.include('export interface CreateProductRequest {');
|
|
148
|
+
expect(actualOutput).to.include('export interface UpdateProductRequest {');
|
|
149
|
+
|
|
150
|
+
// Verify proper TypeScript types in method signatures
|
|
151
|
+
expect(actualOutput).to.include('Promise<ProductList>');
|
|
152
|
+
expect(actualOutput).to.include('Promise<Product>');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should correctly handle path parameters and options separation', () => {
|
|
156
|
+
// Read orders spec (has path parameters)
|
|
157
|
+
const specPath = path.join(__dirname, 'scenarios/complex/specs/orders.json');
|
|
158
|
+
const specContent = fs.readFileSync(specPath, 'utf-8');
|
|
159
|
+
const spec = OpenAPIToConnector.parseSpec(specContent);
|
|
160
|
+
|
|
161
|
+
// Generate output
|
|
162
|
+
const generator = new OpenAPIToConnector(spec, 'test-shop');
|
|
163
|
+
const actualOutput = generator.generateResourceClass('OrdersResource');
|
|
164
|
+
|
|
165
|
+
// Verify path parameters are handled correctly
|
|
166
|
+
expect(actualOutput).to.include('export function getOrder(this: any, orderId: string)');
|
|
167
|
+
expect(actualOutput).to.include('export function updateOrderStatus(this: any, orderId: string, options');
|
|
168
|
+
expect(actualOutput).to.include('export function cancelOrder(this: any, orderId: string)');
|
|
169
|
+
|
|
170
|
+
// Verify methods without options don't have unnecessary code
|
|
171
|
+
expect(actualOutput).to.match(/export function getOrder\(this: any, orderId: string\) \{/);
|
|
172
|
+
expect(actualOutput).to.match(/export function cancelOrder\(this: any, orderId: string\) \{/);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should correctly expose resource methods in main controller', () => {
|
|
176
|
+
// Read both specs
|
|
177
|
+
const productsSpecPath = path.join(__dirname, 'scenarios/complex/specs/products.json');
|
|
178
|
+
const ordersSpecPath = path.join(__dirname, 'scenarios/complex/specs/orders.json');
|
|
179
|
+
const productsSpecContent = fs.readFileSync(productsSpecPath, 'utf-8');
|
|
180
|
+
const ordersSpecContent = fs.readFileSync(ordersSpecPath, 'utf-8');
|
|
181
|
+
|
|
182
|
+
const productsSpec = OpenAPIToConnector.parseSpec(productsSpecContent);
|
|
183
|
+
const ordersSpec = OpenAPIToConnector.parseSpec(ordersSpecContent);
|
|
184
|
+
|
|
185
|
+
// Generate main controller
|
|
186
|
+
const resources = [
|
|
187
|
+
{ className: 'ProductsResource', fileName: 'products' },
|
|
188
|
+
{ className: 'OrdersResource', fileName: 'orders' }
|
|
189
|
+
];
|
|
190
|
+
const resourceSpecs = [
|
|
191
|
+
{ fileName: 'products', spec: productsSpec },
|
|
192
|
+
{ fileName: 'orders', spec: ordersSpec }
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
const mainGenerator = new OpenAPIToConnector(productsSpec, 'test-shop');
|
|
196
|
+
const actualOutput = mainGenerator.generateMainController(resources, resourceSpecs);
|
|
197
|
+
|
|
198
|
+
// Verify exposed methods exist
|
|
199
|
+
expect(actualOutput).to.include('async productsGetProducts(');
|
|
200
|
+
expect(actualOutput).to.include('async productsCreateProduct(');
|
|
201
|
+
expect(actualOutput).to.include('async productsGetProduct(');
|
|
202
|
+
expect(actualOutput).to.include('async productsUpdateProduct(');
|
|
203
|
+
expect(actualOutput).to.include('async productsDeleteProduct(');
|
|
204
|
+
|
|
205
|
+
expect(actualOutput).to.include('async ordersGetOrders(');
|
|
206
|
+
expect(actualOutput).to.include('async ordersCreateOrder(');
|
|
207
|
+
expect(actualOutput).to.include('async ordersGetOrder(');
|
|
208
|
+
expect(actualOutput).to.include('async ordersUpdateOrderStatus(');
|
|
209
|
+
expect(actualOutput).to.include('async ordersCancelOrder(');
|
|
210
|
+
|
|
211
|
+
// Verify resource binding
|
|
212
|
+
expect(actualOutput).to.include('this.bindResourceFunctions(\'products\', productsFunctions);');
|
|
213
|
+
expect(actualOutput).to.include('this.bindResourceFunctions(\'orders\', ordersFunctions);');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('Regression Tests', () => {
|
|
218
|
+
it('should handle OpenAPI schemas with different naming patterns', () => {
|
|
219
|
+
const testSpec = {
|
|
220
|
+
openapi: '3.0.0',
|
|
221
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
222
|
+
paths: {
|
|
223
|
+
'/test': {
|
|
224
|
+
get: {
|
|
225
|
+
operationId: 'testOperation',
|
|
226
|
+
responses: {
|
|
227
|
+
'200': {
|
|
228
|
+
description: 'Success',
|
|
229
|
+
content: {
|
|
230
|
+
'application/json': {
|
|
231
|
+
schema: {
|
|
232
|
+
'$ref': '#/components/schemas/Complex.Name.With.Dots'
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
components: {
|
|
242
|
+
schemas: {
|
|
243
|
+
'Complex.Name.With.Dots': {
|
|
244
|
+
type: 'object',
|
|
245
|
+
properties: {
|
|
246
|
+
id: { type: 'string' },
|
|
247
|
+
name: { type: 'string' }
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const generator = new OpenAPIToConnector(testSpec, 'test');
|
|
255
|
+
const output = generator.generateController();
|
|
256
|
+
|
|
257
|
+
// Should generate valid TypeScript interface names
|
|
258
|
+
expect(output).to.include('export interface Complex_Name_With_Dots {');
|
|
259
|
+
expect(output).to.not.include('export interface Complex.Name.With.Dots {'); // Invalid TS
|
|
260
|
+
expect(output).to.include('Promise<Complex_Name_With_Dots>');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should not add options parameter to methods that don\'t need it', () => {
|
|
264
|
+
const simpleSpec = {
|
|
265
|
+
openapi: '3.0.0',
|
|
266
|
+
info: { title: 'Simple API', version: '1.0.0' },
|
|
267
|
+
paths: {
|
|
268
|
+
'/simple': {
|
|
269
|
+
get: {
|
|
270
|
+
operationId: 'getSimple',
|
|
271
|
+
responses: { '200': { description: 'Success' } }
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const generator = new OpenAPIToConnector(simpleSpec, 'test');
|
|
278
|
+
const output = generator.generateController();
|
|
279
|
+
|
|
280
|
+
// Should not have options parameter or initialization
|
|
281
|
+
expect(output).to.include('async getSimple() {');
|
|
282
|
+
expect(output).to.not.include('options = options || {}');
|
|
283
|
+
expect(output).to.not.include('headers: options');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|