|
1 | 1 | import { SwaggerParser } from '../../../core/parser.js'; |
2 | 2 | import { SwaggerDefinition } from '../../../core/types.js'; |
3 | 3 |
|
4 | | -/** |
5 | | - * Generates mock data objects from OpenAPI schemas for use in tests. |
6 | | - * It recursively traverses schema definitions to produce a representative |
7 | | - * object with placeholder values, respecting formats and compositions (`allOf`). |
8 | | - */ |
9 | 4 | export class MockDataGenerator { |
10 | 5 | constructor(private parser: SwaggerParser) {} |
11 | 6 |
|
12 | 7 | public generate(schemaName: string): string { |
13 | | - const schema = this.parser.schemas.find(s => s.name === schemaName)?.definition; |
14 | | - if (!schema) { |
15 | | - return '{}'; |
16 | | - } |
17 | | - const mockObject = this.generateValue(schema, new Set()); |
18 | | - return JSON.stringify(mockObject, null, 2); |
| 8 | + const schemaDef = this.parser.schemas.find(s => s.name === schemaName)?.definition; |
| 9 | + if (!schemaDef) return '{}'; |
| 10 | + const value = this.generateValue(schemaDef, new Set<SwaggerDefinition>()); |
| 11 | + return typeof value === 'undefined' ? '{}' : JSON.stringify(value, null, 2); |
19 | 12 | } |
20 | 13 |
|
21 | | - /** |
22 | | - * The core recursive value generation logic. |
23 | | - * @param schema The schema definition to generate a value for. |
24 | | - * @param visited A set to track visited schemas and prevent infinite recursion in cyclic definitions. |
25 | | - * @returns A mock value (e.g., object, string, number) corresponding to the schema. |
26 | | - * @private |
27 | | - */ |
28 | | - private generateValue(schema: SwaggerDefinition, visited: Set<SwaggerDefinition>): any { |
29 | | - if (schema.example) { |
30 | | - return schema.example; |
| 14 | + private generateValue(schema: SwaggerDefinition | undefined, visited: Set<SwaggerDefinition>): any { |
| 15 | + if (!schema) return undefined; |
| 16 | + if (schema.$ref) { |
| 17 | + const resolved = this.parser.resolve<SwaggerDefinition>(schema); |
| 18 | + return this.generateValue(resolved, visited); |
31 | 19 | } |
| 20 | + if (visited.has(schema)) return {}; |
32 | 21 |
|
33 | | - if (visited.has(schema)) { |
34 | | - return {}; |
35 | | - } |
36 | | - visited.add(schema); |
| 22 | + if ('example' in schema && schema.example !== undefined) return schema.example; |
37 | 23 |
|
38 | | - if (schema.allOf) { |
39 | | - const combined = schema.allOf.reduce((acc, subSchemaRef) => { |
40 | | - const subSchema = this.parser.resolve(subSchemaRef); |
41 | | - if (!subSchema) return acc; |
42 | | - return { ...acc, ...this.generateValue(subSchema, visited) }; |
43 | | - }, {}); |
44 | | - visited.delete(schema); |
45 | | - return combined; |
46 | | - } |
| 24 | + visited.add(schema); |
| 25 | + try { |
| 26 | + if (schema.allOf) { |
| 27 | + let mergedObj: any = {}; |
| 28 | + let mergedObjHasKeys = false; |
| 29 | + let lastPrimitiveValue: any = undefined; |
| 30 | + for (const sub of schema.allOf) { |
| 31 | + const val = this.generateValue(sub, visited); |
| 32 | + if (typeof val === 'object' && val !== null && !Array.isArray(val)) { |
| 33 | + Object.assign(mergedObj, val); |
| 34 | + mergedObjHasKeys = mergedObjHasKeys || Object.keys(val).length > 0; |
| 35 | + } else if (typeof val !== 'undefined') { |
| 36 | + lastPrimitiveValue = val; |
| 37 | + } |
| 38 | + } |
| 39 | + if (mergedObjHasKeys) return mergedObj; |
| 40 | + if (typeof lastPrimitiveValue !== 'undefined') return lastPrimitiveValue; |
| 41 | + return undefined; |
| 42 | + } |
47 | 43 |
|
48 | | - if (schema.$ref) { |
49 | | - const resolved = this.parser.resolve(schema); |
50 | | - const result = resolved ? this.generateValue(resolved, visited) : {}; |
51 | | - visited.delete(schema); |
52 | | - return result; |
53 | | - } |
| 44 | + let type = Array.isArray(schema.type) ? schema.type[0] : schema.type; |
| 45 | + if (!type && schema.properties) type = 'object'; |
54 | 46 |
|
55 | | - switch (schema.type) { |
56 | | - case 'string': |
57 | | - if (schema.format === 'date-time' || schema.format === 'date') return new Date().toISOString(); |
58 | | - if (schema.format === 'email') return '[email protected]'; |
59 | | - if (schema.format === 'uuid') return '123e4567-e89b-12d3-a456-426614174000'; |
60 | | - return 'string-value'; |
61 | | - case 'number': |
62 | | - case 'integer': |
63 | | - return schema.minimum ?? (schema.default ?? 123); |
64 | | - case 'boolean': |
65 | | - return schema.default ?? true; |
66 | | - case 'array': |
67 | | - if (schema.items && !Array.isArray(schema.items)) { |
68 | | - return [this.generateValue(schema.items as SwaggerDefinition, visited)]; |
69 | | - } |
70 | | - return []; |
71 | | - case 'object': |
72 | | - const obj: Record<string, any> = {}; |
73 | | - if (schema.properties) { |
74 | | - for (const [propName, propSchema] of Object.entries(schema.properties)) { |
75 | | - if (!propSchema.readOnly) { |
76 | | - obj[propName] = this.generateValue(propSchema, visited); |
| 47 | + switch (type) { |
| 48 | + case 'object': { |
| 49 | + if (!schema.properties) return {}; |
| 50 | + const obj: any = {}; |
| 51 | + for (const [k, v] of Object.entries(schema.properties)) { |
| 52 | + if (v && !v.readOnly) { |
| 53 | + const propValue = this.generateValue(v, visited); |
| 54 | + if (typeof propValue !== 'undefined') obj[k] = propValue; |
77 | 55 | } |
78 | 56 | } |
| 57 | + return obj; |
| 58 | + } |
| 59 | + case 'array': { |
| 60 | + if (schema.items && !Array.isArray(schema.items)) { |
| 61 | + const val = this.generateValue(schema.items as SwaggerDefinition, visited); |
| 62 | + return typeof val === 'undefined' ? [] : [val]; |
| 63 | + } |
| 64 | + return []; |
79 | 65 | } |
80 | | - visited.delete(schema); |
81 | | - return obj; |
82 | | - default: |
83 | | - visited.delete(schema); |
84 | | - return null; |
| 66 | + case 'string': |
| 67 | + if (schema.format === 'date-time' || schema.format === 'date') return new Date().toISOString(); |
| 68 | + if (schema.format === 'email') return "[email protected]"; |
| 69 | + if (schema.format === 'uuid') return "123e4567-e89b-12d3-a456-426614174000"; |
| 70 | + return 'string-value'; |
| 71 | + case 'number': |
| 72 | + case 'integer': |
| 73 | + if (typeof schema.minimum !== 'undefined') return schema.minimum; |
| 74 | + if (typeof schema.default !== 'undefined') return schema.default; |
| 75 | + return 123; |
| 76 | + case 'boolean': |
| 77 | + if (typeof schema.default !== 'undefined') return schema.default; |
| 78 | + return true; |
| 79 | + case 'null': |
| 80 | + return null; |
| 81 | + default: |
| 82 | + return undefined; |
| 83 | + } |
| 84 | + } finally { |
| 85 | + visited.delete(schema); |
85 | 86 | } |
86 | 87 | } |
87 | 88 | } |
0 commit comments