@grafana/openapi-to-k6 0.2.5 → 0.3.0
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/.github/workflows/tests.yaml +1 -0
- package/README.md +10 -4
- package/dist/cli.js +27 -12
- package/dist/constants.js +7 -3
- package/dist/errors.js +10 -0
- package/dist/generator/index.js +50 -3
- package/dist/generator/k6Client.js +5 -55
- package/dist/generator/k6ScriptBuilder.js +256 -0
- package/dist/helper.js +32 -0
- package/examples/basic_schema/single/k6-script.sample.ts +3 -2
- package/examples/basic_schema/single/simpleAPI.ts +1 -1
- package/examples/basic_schema/split/k6-script.sample.ts +3 -2
- package/examples/basic_schema/split/simpleAPI.schemas.ts +1 -1
- package/examples/basic_schema/split/simpleAPI.ts +1 -1
- package/examples/basic_schema/tags/default.ts +1 -1
- package/examples/basic_schema/tags/k6-script.sample.ts +4 -3
- package/examples/basic_schema/tags/simpleAPI.schemas.ts +1 -1
- package/examples/form_data_schema/schema.json +6 -3
- package/examples/form_data_schema/single/formDataAPI.ts +1 -1
- package/examples/form_data_schema/single/k6-script.sample.ts +10 -2
- package/examples/form_data_schema/split/formDataAPI.schemas.ts +1 -1
- package/examples/form_data_schema/split/formDataAPI.ts +1 -1
- package/examples/form_data_schema/split/k6-script.sample.ts +10 -2
- package/examples/form_data_schema/tags/default.ts +1 -1
- package/examples/form_data_schema/tags/formDataAPI.schemas.ts +1 -1
- package/examples/form_data_schema/tags/k6-script.sample.ts +11 -3
- package/examples/form_url_encoded_data_schema/schema.json +6 -3
- package/examples/form_url_encoded_data_schema/single/formURLEncodedAPI.ts +1 -1
- package/examples/form_url_encoded_data_schema/single/k6-script.sample.ts +11 -2
- package/examples/form_url_encoded_data_schema/split/formURLEncodedAPI.schemas.ts +1 -1
- package/examples/form_url_encoded_data_schema/split/formURLEncodedAPI.ts +1 -1
- package/examples/form_url_encoded_data_schema/split/k6-script.sample.ts +11 -2
- package/examples/form_url_encoded_data_schema/tags/default.ts +1 -1
- package/examples/form_url_encoded_data_schema/tags/formURLEncodedAPI.schemas.ts +1 -1
- package/examples/form_url_encoded_data_schema/tags/k6-script.sample.ts +12 -3
- package/examples/form_url_encoded_data_with_query_params_schema/schema.json +8 -4
- package/examples/form_url_encoded_data_with_query_params_schema/single/formURLEncodedAPIWithQueryParameters.ts +1 -1
- package/examples/form_url_encoded_data_with_query_params_schema/single/k6-script.sample.ts +19 -6
- package/examples/form_url_encoded_data_with_query_params_schema/split/formURLEncodedAPIWithQueryParameters.schemas.ts +1 -1
- package/examples/form_url_encoded_data_with_query_params_schema/split/formURLEncodedAPIWithQueryParameters.ts +1 -1
- package/examples/form_url_encoded_data_with_query_params_schema/split/k6-script.sample.ts +19 -6
- package/examples/form_url_encoded_data_with_query_params_schema/tags/default.ts +1 -1
- package/examples/form_url_encoded_data_with_query_params_schema/tags/formURLEncodedAPIWithQueryParameters.schemas.ts +1 -1
- package/examples/form_url_encoded_data_with_query_params_schema/tags/k6-script.sample.ts +14 -3
- package/examples/get_request_with_path_parameters_schema/schema.json +2 -1
- package/examples/get_request_with_path_parameters_schema/single/k6-script.sample.ts +6 -2
- package/examples/get_request_with_path_parameters_schema/single/simpleAPI.ts +1 -1
- package/examples/get_request_with_path_parameters_schema/split/k6-script.sample.ts +6 -2
- package/examples/get_request_with_path_parameters_schema/split/simpleAPI.schemas.ts +1 -1
- package/examples/get_request_with_path_parameters_schema/split/simpleAPI.ts +1 -1
- package/examples/get_request_with_path_parameters_schema/tags/default.ts +1 -1
- package/examples/get_request_with_path_parameters_schema/tags/k6-script.sample.ts +7 -3
- package/examples/get_request_with_path_parameters_schema/tags/simpleAPI.schemas.ts +1 -1
- package/examples/headers_schema/schema.json +2 -1
- package/examples/headers_schema/single/headerDemoAPI.ts +1 -1
- package/examples/headers_schema/single/k6-script.sample.ts +15 -4
- package/examples/headers_schema/split/headerDemoAPI.schemas.ts +1 -1
- package/examples/headers_schema/split/headerDemoAPI.ts +1 -1
- package/examples/headers_schema/split/k6-script.sample.ts +15 -4
- package/examples/headers_schema/tags/default.ts +1 -1
- package/examples/headers_schema/tags/headerDemoAPI.schemas.ts +1 -1
- package/examples/headers_schema/tags/k6-script.sample.ts +16 -5
- package/examples/no_title_schema/single/k6-script.sample.ts +3 -2
- package/examples/no_title_schema/single/k6Client.ts +1 -1
- package/examples/no_title_schema/split/k6-script.sample.ts +3 -2
- package/examples/no_title_schema/split/k6Client.schemas.ts +1 -1
- package/examples/no_title_schema/split/k6Client.ts +1 -1
- package/examples/no_title_schema/tags/default.ts +1 -1
- package/examples/no_title_schema/tags/k6-script.sample.ts +4 -3
- package/examples/no_title_schema/tags/k6Client.schemas.ts +1 -1
- package/examples/post_request_with_query_params/schema.json +14 -7
- package/examples/post_request_with_query_params/single/exampleAPI.ts +1 -1
- package/examples/post_request_with_query_params/single/k6-script.sample.ts +11 -2
- package/examples/post_request_with_query_params/split/exampleAPI.schemas.ts +1 -1
- package/examples/post_request_with_query_params/split/exampleAPI.ts +1 -1
- package/examples/post_request_with_query_params/split/k6-script.sample.ts +11 -2
- package/examples/post_request_with_query_params/tags/default.ts +1 -1
- package/examples/post_request_with_query_params/tags/exampleAPI.schemas.ts +1 -1
- package/examples/post_request_with_query_params/tags/k6-script.sample.ts +12 -3
- package/examples/query_params_schema/schema.json +20 -10
- package/examples/query_params_schema/single/exampleAPI.ts +1 -1
- package/examples/query_params_schema/single/k6-script.sample.ts +8 -2
- package/examples/query_params_schema/split/exampleAPI.schemas.ts +1 -1
- package/examples/query_params_schema/split/exampleAPI.ts +1 -1
- package/examples/query_params_schema/split/k6-script.sample.ts +8 -2
- package/examples/query_params_schema/tags/default.ts +1 -1
- package/examples/query_params_schema/tags/exampleAPI.schemas.ts +1 -1
- package/examples/query_params_schema/tags/k6-script.sample.ts +9 -3
- package/examples/simple_post_request_schema/schema.json +30 -15
- package/examples/simple_post_request_schema/single/exampleAPI.ts +1 -1
- package/examples/simple_post_request_schema/single/k6-script.sample.ts +16 -2
- package/examples/simple_post_request_schema/split/exampleAPI.schemas.ts +1 -1
- package/examples/simple_post_request_schema/split/exampleAPI.ts +1 -1
- package/examples/simple_post_request_schema/split/k6-script.sample.ts +16 -2
- package/examples/simple_post_request_schema/tags/default.ts +1 -1
- package/examples/simple_post_request_schema/tags/exampleAPI.schemas.ts +1 -1
- package/examples/simple_post_request_schema/tags/k6-script.sample.ts +17 -3
- package/package.json +3 -1
- package/src/cli.ts +32 -14
- package/src/constants.ts +7 -3
- package/src/errors.ts +6 -0
- package/src/generator/index.ts +67 -2
- package/src/generator/k6Client.ts +3 -72
- package/src/generator/k6ScriptBuilder.ts +328 -0
- package/src/helper.ts +40 -0
- package/src/type.d.ts +1 -0
- package/tests/e2e/schema.json +136 -18
- package/tests/e2e/single-tag-filter/k6Script.ts +50 -0
- package/tests/e2e/tags/k6Script.ts +3 -3
- package/tests/functional-tests/helper.ts +16 -0
- package/tests/functional-tests/{generator.test.ts → test-generator/generator.test.ts} +12 -21
- package/tests/functional-tests/test-sample-k6-scripts/fixtures/schema_using_ref_models.json +394 -0
- package/tests/functional-tests/test-sample-k6-scripts/fixtures/schema_with_examples.json +416 -0
- package/tests/functional-tests/test-sample-k6-scripts/fixtures/schema_with_no_variables.json +32 -0
- package/tests/functional-tests/test-sample-k6-scripts/sampleK6Scripts.test.ts +248 -0
- package/tests/functional-tests/test-tags-filtering/fixtures/tags_filtering.json +141 -0
- package/tests/functional-tests/test-tags-filtering/tagsFiltering.test.ts +166 -0
- package/tests/helper.test.ts +59 -0
- /package/tests/functional-tests/{fixtures/schemas → test-generator/fixtures}/basic_schema.json +0 -0
- /package/tests/functional-tests/{fixtures/schemas → test-generator/fixtures}/form_data_schema.json +0 -0
- /package/tests/functional-tests/{fixtures/schemas → test-generator/fixtures}/form_url_encoded_data_schema.json +0 -0
- /package/tests/functional-tests/{fixtures/schemas → test-generator/fixtures}/form_url_encoded_data_with_query_params_schema.json +0 -0
- /package/tests/functional-tests/{fixtures/schemas → test-generator/fixtures}/get_request_with_path_parameters_schema.json +0 -0
- /package/tests/functional-tests/{fixtures/schemas → test-generator/fixtures}/headers_schema.json +0 -0
- /package/tests/functional-tests/{fixtures/schemas → test-generator/fixtures}/no_title_schema.json +0 -0
- /package/tests/functional-tests/{fixtures/schemas → test-generator/fixtures}/post_request_with_query_params.json +0 -0
- /package/tests/functional-tests/{fixtures/schemas → test-generator/fixtures}/query_params_schema.json +0 -0
- /package/tests/functional-tests/{fixtures/schemas → test-generator/fixtures}/simple_post_request_schema.json +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker'
|
|
2
|
+
import {
|
|
3
|
+
camel,
|
|
4
|
+
ClientExtraFilesBuilder,
|
|
5
|
+
ClientFileBuilder,
|
|
6
|
+
ContextSpecs,
|
|
7
|
+
GeneratorVerbOptions,
|
|
8
|
+
GetterPropType,
|
|
9
|
+
kebab,
|
|
10
|
+
NormalizedOutputOptions,
|
|
11
|
+
pascal,
|
|
12
|
+
resolveRef,
|
|
13
|
+
toObjectString,
|
|
14
|
+
} from '@orval/core'
|
|
15
|
+
import Handlebars from 'handlebars'
|
|
16
|
+
import {
|
|
17
|
+
OperationObject,
|
|
18
|
+
ParameterObject,
|
|
19
|
+
ReferenceObject,
|
|
20
|
+
RequestBodyObject,
|
|
21
|
+
SchemaObject,
|
|
22
|
+
} from 'openapi3-ts/oas30'
|
|
23
|
+
import path from 'path'
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_SCHEMA_TITLE,
|
|
26
|
+
K6_SCRIPT_TEMPLATE,
|
|
27
|
+
SAMPLE_K6_SCRIPT_FILE_NAME,
|
|
28
|
+
} from '../constants'
|
|
29
|
+
import { getDirectoryForPath, getGeneratedClientPath } from '../helper'
|
|
30
|
+
import { logger } from '../logger'
|
|
31
|
+
import { generateTitle } from './k6Client'
|
|
32
|
+
|
|
33
|
+
function getExampleValueForSchema(
|
|
34
|
+
schema: SchemaObject | ReferenceObject,
|
|
35
|
+
context: ContextSpecs
|
|
36
|
+
) {
|
|
37
|
+
// Handle $ref
|
|
38
|
+
if ('$ref' in schema) {
|
|
39
|
+
const { schema: resolvedSchema } = resolveRef(schema, context)
|
|
40
|
+
return getExampleValueForSchema(resolvedSchema as SchemaObject, context)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if ('example' in schema) {
|
|
44
|
+
return `'${schema.example}'`
|
|
45
|
+
}
|
|
46
|
+
let schemaType = schema.type
|
|
47
|
+
if (Array.isArray(schemaType)) {
|
|
48
|
+
schemaType = schemaType[0]
|
|
49
|
+
}
|
|
50
|
+
if (!schemaType) {
|
|
51
|
+
return undefined
|
|
52
|
+
}
|
|
53
|
+
const enumValues = schema.enum
|
|
54
|
+
switch (schemaType) {
|
|
55
|
+
case 'string':
|
|
56
|
+
return enumValues ? `'${enumValues[0]}'` : `'${faker.word.sample()}'`
|
|
57
|
+
case 'number':
|
|
58
|
+
return enumValues ? enumValues[0] : faker.number.int()
|
|
59
|
+
case 'integer':
|
|
60
|
+
return enumValues ? enumValues[0] : faker.number.int()
|
|
61
|
+
case 'boolean':
|
|
62
|
+
return enumValues ? enumValues[0] : faker.datatype.boolean()
|
|
63
|
+
case 'array':
|
|
64
|
+
return '[]'
|
|
65
|
+
case 'object': {
|
|
66
|
+
let objectString = '{\n'
|
|
67
|
+
for (const property in schema.properties) {
|
|
68
|
+
if (schema.properties[property]) {
|
|
69
|
+
const propertyValue = getExampleValueForSchema(
|
|
70
|
+
schema.properties[property],
|
|
71
|
+
context
|
|
72
|
+
)
|
|
73
|
+
objectString += `${property}: ${propertyValue},\n`
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
objectString += '\n}'
|
|
78
|
+
return objectString
|
|
79
|
+
}
|
|
80
|
+
default:
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getExampleValues(
|
|
86
|
+
requiredProps: GeneratorVerbOptions['props'],
|
|
87
|
+
originalOperation: OperationObject,
|
|
88
|
+
context: ContextSpecs
|
|
89
|
+
): string {
|
|
90
|
+
let exampleValues = ''
|
|
91
|
+
for (const prop of requiredProps) {
|
|
92
|
+
const propType = prop.type as GetterPropType
|
|
93
|
+
|
|
94
|
+
switch (propType) {
|
|
95
|
+
case GetterPropType.QUERY_PARAM: {
|
|
96
|
+
let exampleValue = '{\n'
|
|
97
|
+
for (const param of originalOperation.parameters || []) {
|
|
98
|
+
let resolvedParam: ParameterObject | ReferenceObject
|
|
99
|
+
|
|
100
|
+
if ('$ref' in param) {
|
|
101
|
+
const { schema: resolvedSchema } = resolveRef<ParameterObject>(
|
|
102
|
+
param,
|
|
103
|
+
context
|
|
104
|
+
)
|
|
105
|
+
resolvedParam = resolvedSchema
|
|
106
|
+
} else {
|
|
107
|
+
resolvedParam = param
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Only add required query parameters to the example values
|
|
111
|
+
if (resolvedParam.required && resolvedParam.in === 'query') {
|
|
112
|
+
if ('schema' in resolvedParam && resolvedParam.schema) {
|
|
113
|
+
exampleValue += `'${resolvedParam.name}': ${getExampleValueForSchema(resolvedParam.schema, context)},\n`
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exampleValue += '\n}'
|
|
118
|
+
exampleValues += `params = ${exampleValue};\n`
|
|
119
|
+
break
|
|
120
|
+
}
|
|
121
|
+
case GetterPropType.HEADER: {
|
|
122
|
+
let exampleValue = '{\n'
|
|
123
|
+
for (const param of originalOperation.parameters || []) {
|
|
124
|
+
let resolvedParam: ParameterObject | ReferenceObject
|
|
125
|
+
|
|
126
|
+
if ('$ref' in param) {
|
|
127
|
+
const { schema: resolvedSchema } = resolveRef<ParameterObject>(
|
|
128
|
+
param,
|
|
129
|
+
context
|
|
130
|
+
)
|
|
131
|
+
resolvedParam = resolvedSchema
|
|
132
|
+
} else {
|
|
133
|
+
resolvedParam = param
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Only add required query parameters to the example values
|
|
137
|
+
if (resolvedParam.required && resolvedParam.in === 'header') {
|
|
138
|
+
if ('schema' in resolvedParam && resolvedParam.schema) {
|
|
139
|
+
exampleValue += `'${resolvedParam.name}': ${getExampleValueForSchema(resolvedParam.schema, context)},\n`
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
exampleValue += '\n}'
|
|
144
|
+
exampleValues += `headers = ${exampleValue};\n`
|
|
145
|
+
break
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
case GetterPropType.PARAM: {
|
|
149
|
+
let example, paramSchema: SchemaObject | ReferenceObject | undefined
|
|
150
|
+
|
|
151
|
+
for (const parameter of originalOperation.parameters || []) {
|
|
152
|
+
if ('name' in parameter) {
|
|
153
|
+
paramSchema = parameter.schema as SchemaObject
|
|
154
|
+
break
|
|
155
|
+
} else if ('$ref' in parameter) {
|
|
156
|
+
const { schema: resolvedSchema } = resolveRef<ParameterObject>(
|
|
157
|
+
parameter,
|
|
158
|
+
context
|
|
159
|
+
)
|
|
160
|
+
paramSchema = resolvedSchema.schema
|
|
161
|
+
break
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (paramSchema) {
|
|
166
|
+
example = getExampleValueForSchema(paramSchema, context)
|
|
167
|
+
}
|
|
168
|
+
if (example) {
|
|
169
|
+
exampleValues += `${prop.name} = ${example};\n`
|
|
170
|
+
}
|
|
171
|
+
break
|
|
172
|
+
}
|
|
173
|
+
case GetterPropType.BODY: {
|
|
174
|
+
// Generate example value from body schema
|
|
175
|
+
const requestBody = originalOperation.requestBody
|
|
176
|
+
let requestBodyExample
|
|
177
|
+
if (!requestBody) {
|
|
178
|
+
break
|
|
179
|
+
}
|
|
180
|
+
let resolvedSchema
|
|
181
|
+
if ('$ref' in requestBody) {
|
|
182
|
+
const { schema } = resolveRef<RequestBodyObject>(requestBody, context)
|
|
183
|
+
resolvedSchema = schema
|
|
184
|
+
} else if ('content' in requestBody) {
|
|
185
|
+
resolvedSchema = requestBody
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (resolvedSchema && 'content' in resolvedSchema) {
|
|
189
|
+
// Get the first available content type
|
|
190
|
+
const contentType = Object.keys(resolvedSchema.content)[0]
|
|
191
|
+
if (contentType) {
|
|
192
|
+
const requestBodySchema =
|
|
193
|
+
resolvedSchema.content[contentType]?.schema
|
|
194
|
+
if (requestBodySchema) {
|
|
195
|
+
requestBodyExample = getExampleValueForSchema(
|
|
196
|
+
requestBodySchema,
|
|
197
|
+
context
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (requestBodyExample) {
|
|
203
|
+
exampleValues += `${prop.name} = ${requestBodyExample};\n`
|
|
204
|
+
}
|
|
205
|
+
break
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return exampleValues
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function getClientClassName(identifier: string) {
|
|
213
|
+
return generateTitle(pascal(identifier))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function getClientObjectName(identifier: string) {
|
|
217
|
+
return camel(generateTitle(pascal(identifier)))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export const k6ScriptBuilder: ClientExtraFilesBuilder = async (
|
|
221
|
+
verbOptions: Record<string, GeneratorVerbOptions>,
|
|
222
|
+
output: NormalizedOutputOptions,
|
|
223
|
+
context: ContextSpecs
|
|
224
|
+
): Promise<ClientFileBuilder[]> => {
|
|
225
|
+
const schemaTitle =
|
|
226
|
+
context.specs[context.specKey]?.info.title || DEFAULT_SCHEMA_TITLE
|
|
227
|
+
const {
|
|
228
|
+
path: pathOfGeneratedClient,
|
|
229
|
+
filename,
|
|
230
|
+
extension,
|
|
231
|
+
} = await getGeneratedClientPath(output.target!, schemaTitle)
|
|
232
|
+
const directoryPath = getDirectoryForPath(pathOfGeneratedClient)
|
|
233
|
+
const generateScriptPath = path.join(
|
|
234
|
+
directoryPath,
|
|
235
|
+
SAMPLE_K6_SCRIPT_FILE_NAME
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
logger.debug(
|
|
239
|
+
`k6ScriptBuilder ~ Generating sample K6 Script\n${JSON.stringify(
|
|
240
|
+
{
|
|
241
|
+
pathOfGeneratedClient,
|
|
242
|
+
filename,
|
|
243
|
+
extension,
|
|
244
|
+
schemaTitle,
|
|
245
|
+
directoryPath,
|
|
246
|
+
generateScriptPath,
|
|
247
|
+
},
|
|
248
|
+
null,
|
|
249
|
+
2
|
|
250
|
+
)}`
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
const clientFunctionsList = []
|
|
254
|
+
const uniqueVariables = new Set<string>() // Track unique variable names
|
|
255
|
+
const allUniqueTags = new Set<string>()
|
|
256
|
+
|
|
257
|
+
for (const verbOption of Object.values(verbOptions)) {
|
|
258
|
+
if (verbOption.tags && verbOption.tags.length > 0) {
|
|
259
|
+
verbOption.tags.forEach((tag) => allUniqueTags.add(tag))
|
|
260
|
+
} else {
|
|
261
|
+
allUniqueTags.add('default')
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let clientObjectName
|
|
265
|
+
if (output.mode === 'tags') {
|
|
266
|
+
clientObjectName = getClientObjectName(verbOption.tags[0] || 'default')
|
|
267
|
+
} else {
|
|
268
|
+
clientObjectName = getClientObjectName(schemaTitle)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const { operationName, summary, props, originalOperation } = verbOption
|
|
272
|
+
const requiredProps = props.filter((prop) => prop.required)
|
|
273
|
+
// Create example values object
|
|
274
|
+
const exampleValues = getExampleValues(
|
|
275
|
+
requiredProps,
|
|
276
|
+
originalOperation,
|
|
277
|
+
context
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
for (const prop of requiredProps) {
|
|
281
|
+
uniqueVariables.add(prop.name)
|
|
282
|
+
}
|
|
283
|
+
clientFunctionsList.push({
|
|
284
|
+
operationName,
|
|
285
|
+
summary,
|
|
286
|
+
exampleValues,
|
|
287
|
+
requiredParametersString: toObjectString(requiredProps, 'name'),
|
|
288
|
+
clientObjectName,
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let importStatements = ''
|
|
293
|
+
let clientInitializationStatement = ''
|
|
294
|
+
|
|
295
|
+
if (output.mode === 'tags') {
|
|
296
|
+
for (const tag of allUniqueTags) {
|
|
297
|
+
const { extension } = await getGeneratedClientPath(
|
|
298
|
+
output.target!,
|
|
299
|
+
schemaTitle
|
|
300
|
+
)
|
|
301
|
+
const clientName = getClientClassName(tag)
|
|
302
|
+
importStatements += `import { ${clientName} } from './${kebab(tag)}${extension}';\n`
|
|
303
|
+
clientInitializationStatement += `const ${getClientObjectName(tag)} = new ${clientName}({ baseUrl });\n`
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
const clientName = getClientClassName(schemaTitle)
|
|
307
|
+
importStatements = `import { ${clientName} } from './${filename}${extension}';\n`
|
|
308
|
+
clientInitializationStatement = `const ${getClientObjectName(schemaTitle)} = new ${clientName}({ baseUrl });\n`
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const scriptContentData = {
|
|
312
|
+
clientFunctionsList,
|
|
313
|
+
variableDefinition:
|
|
314
|
+
uniqueVariables.size > 0
|
|
315
|
+
? `let ${Array.from(uniqueVariables).join(', ')};`
|
|
316
|
+
: '',
|
|
317
|
+
importStatements,
|
|
318
|
+
clientInitializationStatement,
|
|
319
|
+
}
|
|
320
|
+
const template = Handlebars.compile(K6_SCRIPT_TEMPLATE)
|
|
321
|
+
|
|
322
|
+
return [
|
|
323
|
+
{
|
|
324
|
+
path: generateScriptPath,
|
|
325
|
+
content: template(scriptContentData),
|
|
326
|
+
},
|
|
327
|
+
]
|
|
328
|
+
}
|
package/src/helper.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { camel, getFileInfo } from '@orval/core'
|
|
2
2
|
import fs from 'fs'
|
|
3
|
+
import { createSourceFile, ScriptTarget } from 'typescript'
|
|
4
|
+
|
|
3
5
|
import path from 'path'
|
|
4
6
|
import { format, resolveConfig } from 'prettier'
|
|
5
7
|
import packageJson from '../package.json'
|
|
@@ -158,3 +160,41 @@ export function getDirectoryForPath(pathString: string): string {
|
|
|
158
160
|
// If the path has an extension, it is a file
|
|
159
161
|
return path.dirname(pathString)
|
|
160
162
|
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Checks if a file contains only comments and whitespace, with no actual code.
|
|
166
|
+
*
|
|
167
|
+
* This function uses TypeScript's parser to accurately detect and remove all types
|
|
168
|
+
* of comments (single-line, multi-line, JSDoc) and whitespace from the input text.
|
|
169
|
+
* If nothing remains after removing comments and whitespace, then the file is
|
|
170
|
+
* considered to contain only comments.
|
|
171
|
+
*
|
|
172
|
+
* @param fileContent - The string content of the file to check
|
|
173
|
+
* @returns True if the file contains only comments and whitespace, false if it contains any actual code
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* // Returns true
|
|
177
|
+
* hasOnlyComments('// Just a comment');
|
|
178
|
+
*
|
|
179
|
+
* // Returns false
|
|
180
|
+
* hasOnlyComments('// A comment\nconst x = 1;');
|
|
181
|
+
*/
|
|
182
|
+
export function hasOnlyComments(fileContent: string): boolean {
|
|
183
|
+
// Create a source file
|
|
184
|
+
const sourceFile = createSourceFile(
|
|
185
|
+
'temp.ts',
|
|
186
|
+
fileContent,
|
|
187
|
+
ScriptTarget.Latest,
|
|
188
|
+
true
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
// Remove all comments and whitespace
|
|
192
|
+
const textWithoutComments = sourceFile
|
|
193
|
+
.getFullText()
|
|
194
|
+
.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '') // Remove comments
|
|
195
|
+
.replace(/\s+/g, '') // Remove whitespace
|
|
196
|
+
|
|
197
|
+
// If there's any content left after removing comments and whitespace,
|
|
198
|
+
// then there's actual code
|
|
199
|
+
return textWithoutComments.length === 0
|
|
200
|
+
}
|
package/src/type.d.ts
CHANGED
package/tests/e2e/schema.json
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"required": true,
|
|
18
18
|
"description": "ID of the item to retrieve",
|
|
19
19
|
"schema": {
|
|
20
|
-
"type": "string"
|
|
20
|
+
"type": "string",
|
|
21
|
+
"example": "12345"
|
|
21
22
|
}
|
|
22
23
|
},
|
|
23
24
|
{
|
|
@@ -27,7 +28,8 @@
|
|
|
27
28
|
"description": "Whether to return detailed information",
|
|
28
29
|
"schema": {
|
|
29
30
|
"type": "boolean",
|
|
30
|
-
"default": false
|
|
31
|
+
"default": false,
|
|
32
|
+
"example": true
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
],
|
|
@@ -47,7 +49,8 @@
|
|
|
47
49
|
"required": true,
|
|
48
50
|
"description": "ID of the item to create",
|
|
49
51
|
"schema": {
|
|
50
|
-
"type": "string"
|
|
52
|
+
"type": "string",
|
|
53
|
+
"example": "12345"
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
],
|
|
@@ -59,10 +62,12 @@
|
|
|
59
62
|
"type": "object",
|
|
60
63
|
"properties": {
|
|
61
64
|
"name": {
|
|
62
|
-
"type": "string"
|
|
65
|
+
"type": "string",
|
|
66
|
+
"example": "Sample Item"
|
|
63
67
|
},
|
|
64
68
|
"description": {
|
|
65
|
-
"type": "string"
|
|
69
|
+
"type": "string",
|
|
70
|
+
"example": "This is a sample description for the item."
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
}
|
|
@@ -85,7 +90,8 @@
|
|
|
85
90
|
"required": true,
|
|
86
91
|
"description": "ID of the item to update",
|
|
87
92
|
"schema": {
|
|
88
|
-
"type": "string"
|
|
93
|
+
"type": "string",
|
|
94
|
+
"example": "12345"
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
97
|
],
|
|
@@ -97,10 +103,12 @@
|
|
|
97
103
|
"type": "object",
|
|
98
104
|
"properties": {
|
|
99
105
|
"name": {
|
|
100
|
-
"type": "string"
|
|
106
|
+
"type": "string",
|
|
107
|
+
"example": "Updated Item Name"
|
|
101
108
|
},
|
|
102
109
|
"description": {
|
|
103
|
-
"type": "string"
|
|
110
|
+
"type": "string",
|
|
111
|
+
"example": "Updated description for the item."
|
|
104
112
|
}
|
|
105
113
|
}
|
|
106
114
|
}
|
|
@@ -123,7 +131,8 @@
|
|
|
123
131
|
"required": true,
|
|
124
132
|
"description": "ID of the item to partially update",
|
|
125
133
|
"schema": {
|
|
126
|
-
"type": "string"
|
|
134
|
+
"type": "string",
|
|
135
|
+
"example": "12345"
|
|
127
136
|
}
|
|
128
137
|
}
|
|
129
138
|
],
|
|
@@ -135,7 +144,8 @@
|
|
|
135
144
|
"type": "object",
|
|
136
145
|
"properties": {
|
|
137
146
|
"name": {
|
|
138
|
-
"type": "string"
|
|
147
|
+
"type": "string",
|
|
148
|
+
"example": "Partially Updated Item Name"
|
|
139
149
|
}
|
|
140
150
|
}
|
|
141
151
|
}
|
|
@@ -158,7 +168,8 @@
|
|
|
158
168
|
"required": true,
|
|
159
169
|
"description": "ID of the item to delete",
|
|
160
170
|
"schema": {
|
|
161
|
-
"type": "string"
|
|
171
|
+
"type": "string",
|
|
172
|
+
"example": "12345"
|
|
162
173
|
}
|
|
163
174
|
}
|
|
164
175
|
],
|
|
@@ -178,7 +189,8 @@
|
|
|
178
189
|
"required": true,
|
|
179
190
|
"description": "ID of the item to check",
|
|
180
191
|
"schema": {
|
|
181
|
-
"type": "string"
|
|
192
|
+
"type": "string",
|
|
193
|
+
"example": "12345"
|
|
182
194
|
}
|
|
183
195
|
}
|
|
184
196
|
],
|
|
@@ -204,10 +216,12 @@
|
|
|
204
216
|
"type": "object",
|
|
205
217
|
"properties": {
|
|
206
218
|
"name": {
|
|
207
|
-
"type": "string"
|
|
219
|
+
"type": "string",
|
|
220
|
+
"example": "Form Encoded Item"
|
|
208
221
|
},
|
|
209
222
|
"description": {
|
|
210
|
-
"type": "string"
|
|
223
|
+
"type": "string",
|
|
224
|
+
"example": "Description for form-urlencoded item."
|
|
211
225
|
}
|
|
212
226
|
},
|
|
213
227
|
"required": ["name"]
|
|
@@ -234,7 +248,8 @@
|
|
|
234
248
|
"type": "object",
|
|
235
249
|
"properties": {
|
|
236
250
|
"name": {
|
|
237
|
-
"type": "string"
|
|
251
|
+
"type": "string",
|
|
252
|
+
"example": "Form Data Item"
|
|
238
253
|
}
|
|
239
254
|
},
|
|
240
255
|
"required": ["file", "name"]
|
|
@@ -251,6 +266,7 @@
|
|
|
251
266
|
},
|
|
252
267
|
"/items-header": {
|
|
253
268
|
"get": {
|
|
269
|
+
"tags": ["ItemsHeader"],
|
|
254
270
|
"summary": "Get an item with custom headers",
|
|
255
271
|
"parameters": [
|
|
256
272
|
{
|
|
@@ -259,7 +275,8 @@
|
|
|
259
275
|
"required": true,
|
|
260
276
|
"description": "ID of the item to retrieve",
|
|
261
277
|
"schema": {
|
|
262
|
-
"type": "string"
|
|
278
|
+
"type": "string",
|
|
279
|
+
"example": "12345"
|
|
263
280
|
}
|
|
264
281
|
}
|
|
265
282
|
],
|
|
@@ -278,7 +295,8 @@
|
|
|
278
295
|
"description": "Client ID for the request",
|
|
279
296
|
"required": true,
|
|
280
297
|
"schema": {
|
|
281
|
-
"type": "string"
|
|
298
|
+
"type": "string",
|
|
299
|
+
"example": "client-123"
|
|
282
300
|
}
|
|
283
301
|
},
|
|
284
302
|
{
|
|
@@ -287,11 +305,111 @@
|
|
|
287
305
|
"description": "Unique request identifier",
|
|
288
306
|
"required": false,
|
|
289
307
|
"schema": {
|
|
290
|
-
"type": "string"
|
|
308
|
+
"type": "string",
|
|
309
|
+
"example": "req-56789"
|
|
291
310
|
}
|
|
292
311
|
}
|
|
293
312
|
]
|
|
294
313
|
}
|
|
314
|
+
},
|
|
315
|
+
"/users/{userId}/posts/{postId}": {
|
|
316
|
+
"get": {
|
|
317
|
+
"operationId": "getUserPost",
|
|
318
|
+
"parameters": [
|
|
319
|
+
{
|
|
320
|
+
"name": "userId",
|
|
321
|
+
"in": "path",
|
|
322
|
+
"required": true,
|
|
323
|
+
"schema": {
|
|
324
|
+
"type": "string"
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"name": "postId",
|
|
329
|
+
"in": "path",
|
|
330
|
+
"required": true,
|
|
331
|
+
"schema": {
|
|
332
|
+
"type": "string"
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
],
|
|
336
|
+
"responses": {
|
|
337
|
+
"200": {
|
|
338
|
+
"description": "Success",
|
|
339
|
+
"content": {
|
|
340
|
+
"application/json": {
|
|
341
|
+
"schema": {
|
|
342
|
+
"type": "object",
|
|
343
|
+
"properties": {
|
|
344
|
+
"title": {
|
|
345
|
+
"type": "string"
|
|
346
|
+
},
|
|
347
|
+
"content": {
|
|
348
|
+
"type": "string"
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
"/items-new-mandatory-headers": {
|
|
359
|
+
"get": {
|
|
360
|
+
"tags": ["ItemsHeader"],
|
|
361
|
+
"summary": "Retrieve a new item with mandatory headers",
|
|
362
|
+
"parameters": [
|
|
363
|
+
{
|
|
364
|
+
"name": "itemId",
|
|
365
|
+
"in": "query",
|
|
366
|
+
"required": true,
|
|
367
|
+
"description": "ID of the item to retrieve",
|
|
368
|
+
"schema": {
|
|
369
|
+
"type": "string",
|
|
370
|
+
"example": "67890"
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
"name": "X-Auth-Token",
|
|
375
|
+
"in": "header",
|
|
376
|
+
"description": "Authentication token",
|
|
377
|
+
"required": true,
|
|
378
|
+
"schema": {
|
|
379
|
+
"type": "string",
|
|
380
|
+
"example": "Bearer abcdef12345"
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"name": "X-Correlation-ID",
|
|
385
|
+
"in": "header",
|
|
386
|
+
"description": "Correlation ID for tracking requests",
|
|
387
|
+
"required": true,
|
|
388
|
+
"schema": {
|
|
389
|
+
"type": "string",
|
|
390
|
+
"example": "correlation-12345"
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"name": "X-Optional-Header",
|
|
395
|
+
"in": "header",
|
|
396
|
+
"description": "Optional header",
|
|
397
|
+
"required": false,
|
|
398
|
+
"schema": {
|
|
399
|
+
"type": "string",
|
|
400
|
+
"example": "optional-header-value"
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
],
|
|
404
|
+
"responses": {
|
|
405
|
+
"200": {
|
|
406
|
+
"description": "New item retrieved successfully"
|
|
407
|
+
},
|
|
408
|
+
"400": {
|
|
409
|
+
"description": "Invalid request"
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
295
413
|
}
|
|
296
414
|
}
|
|
297
415
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* eslint-disable import/no-unresolved */
|
|
2
|
+
import { check } from 'k6'
|
|
3
|
+
import { Counter } from 'k6/metrics'
|
|
4
|
+
import { ComprehensiveAPIClient } from './sdk.ts'
|
|
5
|
+
/* eslint-enable import/no-unresolved */
|
|
6
|
+
|
|
7
|
+
const CounterErrors = new Counter('Errors')
|
|
8
|
+
const CounterSuccess = new Counter('Success')
|
|
9
|
+
const baseUrl = 'http://localhost:3000'
|
|
10
|
+
const client = new ComprehensiveAPIClient({ baseUrl })
|
|
11
|
+
|
|
12
|
+
export const options = {
|
|
13
|
+
thresholds: {
|
|
14
|
+
Errors: ['count>=1'],
|
|
15
|
+
Success: ['count>=1'],
|
|
16
|
+
// the rate of successful checks should be higher than 90%
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function checkResponseStatus(response, expectedStatus) {
|
|
21
|
+
const result = check(response, {
|
|
22
|
+
[`status is ${expectedStatus}`]: (r) => r.status === expectedStatus,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
if (!result) {
|
|
26
|
+
console.error(
|
|
27
|
+
`Check failed! Expected status ${expectedStatus} but got ${response.status}. Following is the response:`
|
|
28
|
+
)
|
|
29
|
+
console.log(JSON.stringify(response, null, 2))
|
|
30
|
+
} else {
|
|
31
|
+
CounterSuccess.add(1)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default function () {
|
|
36
|
+
try {
|
|
37
|
+
const getResponseData = client.getItemsId('1')
|
|
38
|
+
checkResponseStatus(getResponseData.response, 200)
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error instanceof TypeError) {
|
|
41
|
+
// Add a k6 check to verify if the error is thrown
|
|
42
|
+
CounterErrors.add(1)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const getItemsHeaderResponseData = client.getItemsHeader({
|
|
47
|
+
id: 'test',
|
|
48
|
+
})
|
|
49
|
+
checkResponseStatus(getItemsHeaderResponseData.response, 200)
|
|
50
|
+
}
|