@testsmith/testblocks 0.9.5 → 0.9.8
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/dist/cli/index.js +4 -1
- package/dist/client/assets/index-CDAzk2fI.css +1 -0
- package/dist/client/assets/index-CYoJ_6bl.js +2231 -0
- package/dist/client/assets/index-CYoJ_6bl.js.map +1 -0
- package/dist/client/index.html +22 -2
- package/dist/core/blocks/api/ApiDeleteBlock.js +1 -1
- package/dist/core/blocks/api/ApiGetBlock.js +1 -1
- package/dist/core/blocks/api/ApiPatchBlock.js +2 -1
- package/dist/core/blocks/api/ApiPostBlock.js +2 -1
- package/dist/core/blocks/api/ApiPutBlock.js +2 -1
- package/dist/server/executor.d.ts +14 -0
- package/dist/server/executor.js +27 -7
- package/dist/server/index.js +117 -11
- package/dist/server/openApiParser.d.ts +82 -0
- package/dist/server/openApiParser.js +495 -0
- package/dist/server/startServer.d.ts +1 -0
- package/dist/server/startServer.js +216 -6
- package/package.json +4 -2
- package/dist/client/assets/index-BzcQ5WS6.css +0 -1
- package/dist/client/assets/index-CJ8vFqNf.js +0 -2197
- package/dist/client/assets/index-CJ8vFqNf.js.map +0 -1
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.parseOpenApiSpec = parseOpenApiSpec;
|
|
40
|
+
exports.generateTestFiles = generateTestFiles;
|
|
41
|
+
const swagger_parser_1 = __importDefault(require("@apidevtools/swagger-parser"));
|
|
42
|
+
/**
|
|
43
|
+
* Parse an OpenAPI/Swagger spec from a URL or content string
|
|
44
|
+
*/
|
|
45
|
+
async function parseOpenApiSpec(source, isUrl = true) {
|
|
46
|
+
let api;
|
|
47
|
+
if (isUrl) {
|
|
48
|
+
api = await swagger_parser_1.default.dereference(source);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Parse from content string (JSON or YAML)
|
|
52
|
+
const content = source.trim();
|
|
53
|
+
let parsed;
|
|
54
|
+
if (content.startsWith('{')) {
|
|
55
|
+
parsed = JSON.parse(content);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Import yaml dynamically
|
|
59
|
+
const yaml = await Promise.resolve().then(() => __importStar(require('yaml')));
|
|
60
|
+
parsed = yaml.parse(content);
|
|
61
|
+
}
|
|
62
|
+
api = await swagger_parser_1.default.dereference(parsed);
|
|
63
|
+
}
|
|
64
|
+
return extractSpecInfo(api);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extract structured information from a parsed OpenAPI spec
|
|
68
|
+
*/
|
|
69
|
+
function extractSpecInfo(api) {
|
|
70
|
+
const isV3 = 'openapi' in api;
|
|
71
|
+
// Extract info
|
|
72
|
+
const info = {
|
|
73
|
+
title: api.info.title,
|
|
74
|
+
version: api.info.version,
|
|
75
|
+
description: api.info.description,
|
|
76
|
+
};
|
|
77
|
+
// Extract servers
|
|
78
|
+
const servers = [];
|
|
79
|
+
if (isV3) {
|
|
80
|
+
const v3Api = api;
|
|
81
|
+
if (v3Api.servers) {
|
|
82
|
+
servers.push(...v3Api.servers.map(s => ({ url: s.url, description: s.description })));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const v2Api = api;
|
|
87
|
+
const scheme = v2Api.schemes?.[0] || 'https';
|
|
88
|
+
const host = v2Api.host || 'localhost';
|
|
89
|
+
const basePath = v2Api.basePath || '';
|
|
90
|
+
servers.push({ url: `${scheme}://${host}${basePath}` });
|
|
91
|
+
}
|
|
92
|
+
// Extract security schemes
|
|
93
|
+
const securitySchemes = {};
|
|
94
|
+
if (isV3) {
|
|
95
|
+
const v3Api = api;
|
|
96
|
+
const schemes = v3Api.components?.securitySchemes || {};
|
|
97
|
+
for (const [name, scheme] of Object.entries(schemes)) {
|
|
98
|
+
if ('type' in scheme) {
|
|
99
|
+
securitySchemes[name] = extractSecurityScheme(scheme);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const v2Api = api;
|
|
105
|
+
const schemes = v2Api.securityDefinitions || {};
|
|
106
|
+
for (const [name, scheme] of Object.entries(schemes)) {
|
|
107
|
+
securitySchemes[name] = extractSecuritySchemeV2(scheme);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Extract tags
|
|
111
|
+
const tags = (api.tags || []).map(t => ({
|
|
112
|
+
name: t.name,
|
|
113
|
+
description: t.description,
|
|
114
|
+
}));
|
|
115
|
+
// Extract endpoints
|
|
116
|
+
const endpoints = [];
|
|
117
|
+
const paths = api.paths || {};
|
|
118
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
119
|
+
if (!pathItem)
|
|
120
|
+
continue;
|
|
121
|
+
const methods = ['get', 'post', 'put', 'patch', 'delete'];
|
|
122
|
+
for (const method of methods) {
|
|
123
|
+
const operation = pathItem[method];
|
|
124
|
+
if (!operation)
|
|
125
|
+
continue;
|
|
126
|
+
const endpoint = extractEndpoint(path, method, operation, pathItem, isV3);
|
|
127
|
+
endpoints.push(endpoint);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { info, servers, endpoints, securitySchemes, tags };
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Extract a single endpoint from an operation
|
|
134
|
+
*/
|
|
135
|
+
function extractEndpoint(path, method, operation, pathItem, isV3) {
|
|
136
|
+
// Combine path-level and operation-level parameters
|
|
137
|
+
const pathParams = (pathItem.parameters || []);
|
|
138
|
+
const opParams = (operation.parameters || []);
|
|
139
|
+
const allParams = [...pathParams, ...opParams];
|
|
140
|
+
// Extract parameters
|
|
141
|
+
const parameters = allParams.map(param => ({
|
|
142
|
+
name: param.name,
|
|
143
|
+
in: param.in,
|
|
144
|
+
required: param.required || false,
|
|
145
|
+
description: param.description,
|
|
146
|
+
schema: isV3
|
|
147
|
+
? extractSchema(param.schema)
|
|
148
|
+
: extractSchemaV2(param),
|
|
149
|
+
}));
|
|
150
|
+
// Extract request body
|
|
151
|
+
let requestBody;
|
|
152
|
+
if (isV3) {
|
|
153
|
+
const v3Op = operation;
|
|
154
|
+
if (v3Op.requestBody && 'content' in v3Op.requestBody) {
|
|
155
|
+
const rb = v3Op.requestBody;
|
|
156
|
+
const contentType = Object.keys(rb.content)[0] || 'application/json';
|
|
157
|
+
const mediaType = rb.content[contentType];
|
|
158
|
+
requestBody = {
|
|
159
|
+
required: rb.required || false,
|
|
160
|
+
contentType,
|
|
161
|
+
schema: mediaType?.schema,
|
|
162
|
+
example: mediaType?.example || mediaType?.schema?.example,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const v2Op = operation;
|
|
168
|
+
const bodyParam = allParams.find(p => p.in === 'body');
|
|
169
|
+
if (bodyParam) {
|
|
170
|
+
requestBody = {
|
|
171
|
+
required: bodyParam.required || false,
|
|
172
|
+
contentType: 'application/json',
|
|
173
|
+
schema: bodyParam.schema,
|
|
174
|
+
example: bodyParam.schema?.example,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Extract responses
|
|
179
|
+
const responses = [];
|
|
180
|
+
for (const [statusCode, response] of Object.entries(operation.responses || {})) {
|
|
181
|
+
if (!response || '$ref' in response)
|
|
182
|
+
continue;
|
|
183
|
+
const resp = response;
|
|
184
|
+
let example;
|
|
185
|
+
let schema;
|
|
186
|
+
if (isV3) {
|
|
187
|
+
const v3Resp = resp;
|
|
188
|
+
const content = v3Resp.content?.['application/json'];
|
|
189
|
+
schema = content?.schema;
|
|
190
|
+
example = content?.example || content?.schema?.example;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const v2Resp = resp;
|
|
194
|
+
schema = v2Resp.schema;
|
|
195
|
+
example = v2Resp.schema?.example;
|
|
196
|
+
}
|
|
197
|
+
responses.push({
|
|
198
|
+
statusCode,
|
|
199
|
+
description: resp.description,
|
|
200
|
+
schema,
|
|
201
|
+
example,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
// Generate operation ID if not present
|
|
205
|
+
const operationId = operation.operationId || `${method}_${path.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
206
|
+
return {
|
|
207
|
+
operationId,
|
|
208
|
+
method,
|
|
209
|
+
path,
|
|
210
|
+
summary: operation.summary,
|
|
211
|
+
description: operation.description,
|
|
212
|
+
tags: operation.tags || [],
|
|
213
|
+
parameters,
|
|
214
|
+
requestBody,
|
|
215
|
+
responses,
|
|
216
|
+
security: operation.security,
|
|
217
|
+
deprecated: operation.deprecated,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function extractSchema(schema) {
|
|
221
|
+
if (!schema)
|
|
222
|
+
return undefined;
|
|
223
|
+
return {
|
|
224
|
+
type: schema.type || 'string',
|
|
225
|
+
format: schema.format,
|
|
226
|
+
default: schema.default,
|
|
227
|
+
example: schema.example,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function extractSchemaV2(param) {
|
|
231
|
+
if (!('type' in param))
|
|
232
|
+
return undefined;
|
|
233
|
+
const p = param;
|
|
234
|
+
return {
|
|
235
|
+
type: p.type || 'string',
|
|
236
|
+
format: p.format,
|
|
237
|
+
default: p.default,
|
|
238
|
+
example: undefined, // V2 doesn't have example on parameters
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function extractSecurityScheme(scheme) {
|
|
242
|
+
return {
|
|
243
|
+
type: scheme.type,
|
|
244
|
+
name: 'name' in scheme ? scheme.name : undefined,
|
|
245
|
+
in: 'in' in scheme ? scheme.in : undefined,
|
|
246
|
+
scheme: 'scheme' in scheme ? scheme.scheme : undefined,
|
|
247
|
+
bearerFormat: 'bearerFormat' in scheme ? scheme.bearerFormat : undefined,
|
|
248
|
+
description: scheme.description,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function extractSecuritySchemeV2(scheme) {
|
|
252
|
+
return {
|
|
253
|
+
type: scheme.type === 'basic' ? 'http' : scheme.type,
|
|
254
|
+
name: 'name' in scheme ? scheme.name : undefined,
|
|
255
|
+
in: 'in' in scheme ? scheme.in : undefined,
|
|
256
|
+
scheme: scheme.type === 'basic' ? 'basic' : undefined,
|
|
257
|
+
description: scheme.description,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Generate test files from selected endpoints
|
|
262
|
+
*/
|
|
263
|
+
function generateTestFiles(spec, selectedEndpoints, options) {
|
|
264
|
+
const endpoints = spec.endpoints.filter(e => selectedEndpoints.includes(e.operationId));
|
|
265
|
+
if (endpoints.length === 0) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
switch (options.fileStrategy) {
|
|
269
|
+
case 'single':
|
|
270
|
+
return [generateSingleFile(spec, endpoints, options)];
|
|
271
|
+
case 'per-tag':
|
|
272
|
+
return generatePerTagFiles(spec, endpoints, options);
|
|
273
|
+
case 'per-path':
|
|
274
|
+
return generatePerPathFiles(spec, endpoints, options);
|
|
275
|
+
default:
|
|
276
|
+
return [generateSingleFile(spec, endpoints, options)];
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function generateSingleFile(spec, endpoints, options) {
|
|
280
|
+
const variables = extractVariables(endpoints, options);
|
|
281
|
+
const tests = endpoints.map(endpoint => generateTestCase(endpoint, options));
|
|
282
|
+
const testFile = {
|
|
283
|
+
version: '1.0.0',
|
|
284
|
+
name: spec.info.title,
|
|
285
|
+
description: spec.info.description || `API tests generated from ${spec.info.title} v${spec.info.version}`,
|
|
286
|
+
variables,
|
|
287
|
+
tests,
|
|
288
|
+
};
|
|
289
|
+
const fileName = sanitizeFileName(spec.info.title) + '.testblocks.json';
|
|
290
|
+
return { fileName, testFile };
|
|
291
|
+
}
|
|
292
|
+
function generatePerTagFiles(spec, endpoints, options) {
|
|
293
|
+
// Group endpoints by tag
|
|
294
|
+
const byTag = new Map();
|
|
295
|
+
for (const endpoint of endpoints) {
|
|
296
|
+
const tag = endpoint.tags[0] || 'default';
|
|
297
|
+
if (!byTag.has(tag)) {
|
|
298
|
+
byTag.set(tag, []);
|
|
299
|
+
}
|
|
300
|
+
byTag.get(tag).push(endpoint);
|
|
301
|
+
}
|
|
302
|
+
const files = [];
|
|
303
|
+
for (const [tag, tagEndpoints] of byTag) {
|
|
304
|
+
const variables = extractVariables(tagEndpoints, options);
|
|
305
|
+
const tests = tagEndpoints.map(endpoint => generateTestCase(endpoint, options));
|
|
306
|
+
const tagInfo = spec.tags.find(t => t.name === tag);
|
|
307
|
+
const testFile = {
|
|
308
|
+
version: '1.0.0',
|
|
309
|
+
name: `${spec.info.title} - ${tag}`,
|
|
310
|
+
description: tagInfo?.description || `API tests for ${tag}`,
|
|
311
|
+
variables,
|
|
312
|
+
tests,
|
|
313
|
+
};
|
|
314
|
+
const fileName = sanitizeFileName(`${spec.info.title}-${tag}`) + '.testblocks.json';
|
|
315
|
+
files.push({ fileName, testFile });
|
|
316
|
+
}
|
|
317
|
+
return files;
|
|
318
|
+
}
|
|
319
|
+
function generatePerPathFiles(spec, endpoints, options) {
|
|
320
|
+
// Group endpoints by base path (first segment)
|
|
321
|
+
const byPath = new Map();
|
|
322
|
+
for (const endpoint of endpoints) {
|
|
323
|
+
const segments = endpoint.path.split('/').filter(Boolean);
|
|
324
|
+
const basePath = segments[0] || 'root';
|
|
325
|
+
if (!byPath.has(basePath)) {
|
|
326
|
+
byPath.set(basePath, []);
|
|
327
|
+
}
|
|
328
|
+
byPath.get(basePath).push(endpoint);
|
|
329
|
+
}
|
|
330
|
+
const files = [];
|
|
331
|
+
for (const [basePath, pathEndpoints] of byPath) {
|
|
332
|
+
const variables = extractVariables(pathEndpoints, options);
|
|
333
|
+
const tests = pathEndpoints.map(endpoint => generateTestCase(endpoint, options));
|
|
334
|
+
const testFile = {
|
|
335
|
+
version: '1.0.0',
|
|
336
|
+
name: `${spec.info.title} - /${basePath}`,
|
|
337
|
+
description: `API tests for /${basePath} endpoints`,
|
|
338
|
+
variables,
|
|
339
|
+
tests,
|
|
340
|
+
};
|
|
341
|
+
const fileName = sanitizeFileName(`${spec.info.title}-${basePath}`) + '.testblocks.json';
|
|
342
|
+
files.push({ fileName, testFile });
|
|
343
|
+
}
|
|
344
|
+
return files;
|
|
345
|
+
}
|
|
346
|
+
function extractVariables(endpoints, options) {
|
|
347
|
+
const variables = {
|
|
348
|
+
baseUrl: {
|
|
349
|
+
type: 'string',
|
|
350
|
+
default: options.baseUrl || '',
|
|
351
|
+
description: 'API base URL',
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
// Extract unique path parameters
|
|
355
|
+
const pathParams = new Set();
|
|
356
|
+
for (const endpoint of endpoints) {
|
|
357
|
+
for (const param of endpoint.parameters) {
|
|
358
|
+
if (param.in === 'path') {
|
|
359
|
+
pathParams.add(param.name);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (const paramName of pathParams) {
|
|
364
|
+
const param = endpoints
|
|
365
|
+
.flatMap(e => e.parameters)
|
|
366
|
+
.find(p => p.name === paramName && p.in === 'path');
|
|
367
|
+
variables[paramName] = {
|
|
368
|
+
type: (param?.schema?.type || 'string'),
|
|
369
|
+
default: param?.schema?.example || param?.schema?.default || '',
|
|
370
|
+
description: param?.description || `Path parameter: ${paramName}`,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
return variables;
|
|
374
|
+
}
|
|
375
|
+
function generateTestCase(endpoint, options) {
|
|
376
|
+
const steps = [];
|
|
377
|
+
// Generate API call step
|
|
378
|
+
const apiStep = generateApiCallStep(endpoint, options);
|
|
379
|
+
steps.push(apiStep);
|
|
380
|
+
// Generate assertion step if enabled
|
|
381
|
+
if (options.generateAssertions) {
|
|
382
|
+
const assertionStep = generateAssertionStep(endpoint);
|
|
383
|
+
if (assertionStep) {
|
|
384
|
+
steps.push(assertionStep);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
id: `test-${endpoint.operationId}-${Date.now()}`,
|
|
389
|
+
name: `${endpoint.method.toUpperCase()} ${endpoint.path}`,
|
|
390
|
+
description: endpoint.summary || endpoint.description,
|
|
391
|
+
tags: endpoint.tags,
|
|
392
|
+
steps,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function generateApiCallStep(endpoint, options) {
|
|
396
|
+
const blockType = `api_${endpoint.method}`;
|
|
397
|
+
// Convert path parameters from {param} to ${param}
|
|
398
|
+
const urlPath = endpoint.path.replace(/\{([^}]+)\}/g, '${$1}');
|
|
399
|
+
const url = `\${baseUrl}${urlPath}`;
|
|
400
|
+
const params = {
|
|
401
|
+
URL: url,
|
|
402
|
+
};
|
|
403
|
+
// Add request body for POST/PUT/PATCH
|
|
404
|
+
if (['post', 'put', 'patch'].includes(endpoint.method) && endpoint.requestBody) {
|
|
405
|
+
let body = '{}';
|
|
406
|
+
if (options.includeExamples && endpoint.requestBody.example) {
|
|
407
|
+
body = JSON.stringify(endpoint.requestBody.example, null, 2);
|
|
408
|
+
}
|
|
409
|
+
else if (endpoint.requestBody.schema) {
|
|
410
|
+
body = generateSchemaExample(endpoint.requestBody.schema);
|
|
411
|
+
}
|
|
412
|
+
params.BODY = body;
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
id: `step-${endpoint.operationId}-call-${Date.now()}`,
|
|
416
|
+
type: blockType,
|
|
417
|
+
params,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function generateAssertionStep(endpoint) {
|
|
421
|
+
// Find the first 2xx response
|
|
422
|
+
const successResponse = endpoint.responses.find(r => r.statusCode.startsWith('2'));
|
|
423
|
+
if (!successResponse) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
const statusCode = parseInt(successResponse.statusCode, 10);
|
|
427
|
+
return {
|
|
428
|
+
id: `step-${endpoint.operationId}-assert-${Date.now()}`,
|
|
429
|
+
type: 'api_assert_status',
|
|
430
|
+
params: {
|
|
431
|
+
STATUS: isNaN(statusCode) ? 200 : statusCode,
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function generateSchemaExample(schema) {
|
|
436
|
+
// Simple schema-to-example generation
|
|
437
|
+
const type = schema.type;
|
|
438
|
+
if (schema.example !== undefined) {
|
|
439
|
+
return JSON.stringify(schema.example, null, 2);
|
|
440
|
+
}
|
|
441
|
+
switch (type) {
|
|
442
|
+
case 'object': {
|
|
443
|
+
const properties = schema.properties;
|
|
444
|
+
if (!properties)
|
|
445
|
+
return '{}';
|
|
446
|
+
const obj = {};
|
|
447
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
448
|
+
obj[key] = getDefaultValueForType(prop.type, prop.example, prop.format);
|
|
449
|
+
}
|
|
450
|
+
return JSON.stringify(obj, null, 2);
|
|
451
|
+
}
|
|
452
|
+
case 'array': {
|
|
453
|
+
const items = schema.items;
|
|
454
|
+
if (!items)
|
|
455
|
+
return '[]';
|
|
456
|
+
const itemValue = getDefaultValueForType(items.type, items.example, items.format);
|
|
457
|
+
return JSON.stringify([itemValue], null, 2);
|
|
458
|
+
}
|
|
459
|
+
default:
|
|
460
|
+
return '{}';
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function getDefaultValueForType(type, example, format) {
|
|
464
|
+
if (example !== undefined)
|
|
465
|
+
return example;
|
|
466
|
+
switch (type) {
|
|
467
|
+
case 'string':
|
|
468
|
+
if (format === 'email')
|
|
469
|
+
return 'user@example.com';
|
|
470
|
+
if (format === 'date')
|
|
471
|
+
return '2024-01-01';
|
|
472
|
+
if (format === 'date-time')
|
|
473
|
+
return '2024-01-01T00:00:00Z';
|
|
474
|
+
if (format === 'uuid')
|
|
475
|
+
return '00000000-0000-0000-0000-000000000000';
|
|
476
|
+
return 'string';
|
|
477
|
+
case 'integer':
|
|
478
|
+
case 'number':
|
|
479
|
+
return 0;
|
|
480
|
+
case 'boolean':
|
|
481
|
+
return true;
|
|
482
|
+
case 'array':
|
|
483
|
+
return [];
|
|
484
|
+
case 'object':
|
|
485
|
+
return {};
|
|
486
|
+
default:
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function sanitizeFileName(name) {
|
|
491
|
+
return name
|
|
492
|
+
.toLowerCase()
|
|
493
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
494
|
+
.replace(/^-|-$/g, '');
|
|
495
|
+
}
|