@azure-tools/typespec-ts 0.50.2 → 0.50.3
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/CHANGELOG.md +6 -0
- package/dist/src/framework/hooks/binder.d.ts +1 -1
- package/dist/src/framework/hooks/binder.d.ts.map +1 -1
- package/dist/src/framework/hooks/binder.js +11 -3
- package/dist/src/framework/hooks/binder.js.map +1 -1
- package/dist/src/framework/load-static-helpers.d.ts +3 -0
- package/dist/src/framework/load-static-helpers.d.ts.map +1 -1
- package/dist/src/framework/load-static-helpers.js +49 -38
- package/dist/src/framework/load-static-helpers.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +19 -10
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib.d.ts +7 -0
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/src/lib.js +5 -0
- package/dist/src/lib.js.map +1 -1
- package/dist/src/modular/buildOperations.d.ts.map +1 -1
- package/dist/src/modular/buildOperations.js +1 -1
- package/dist/src/modular/buildOperations.js.map +1 -1
- package/dist/src/modular/emitModels.d.ts +8 -0
- package/dist/src/modular/emitModels.d.ts.map +1 -1
- package/dist/src/modular/emitModels.js +32 -2
- package/dist/src/modular/emitModels.js.map +1 -1
- package/dist/src/modular/emitSamples.js +9 -4
- package/dist/src/modular/emitSamples.js.map +1 -1
- package/dist/src/modular/emitTests.d.ts +7 -0
- package/dist/src/modular/emitTests.d.ts.map +1 -0
- package/dist/src/modular/emitTests.js +160 -0
- package/dist/src/modular/emitTests.js.map +1 -0
- package/dist/src/modular/external-dependencies.d.ts +42 -0
- package/dist/src/modular/external-dependencies.d.ts.map +1 -1
- package/dist/src/modular/external-dependencies.js +42 -0
- package/dist/src/modular/external-dependencies.js.map +1 -1
- package/dist/src/modular/helpers/exampleValueHelpers.d.ts +83 -0
- package/dist/src/modular/helpers/exampleValueHelpers.d.ts.map +1 -0
- package/dist/src/modular/helpers/exampleValueHelpers.js +631 -0
- package/dist/src/modular/helpers/exampleValueHelpers.js.map +1 -0
- package/dist/src/modular/helpers/operationHelpers.d.ts +22 -2
- package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
- package/dist/src/modular/helpers/operationHelpers.js +178 -9
- package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
- package/dist/src/modular/static-helpers-metadata.d.ts +12 -0
- package/dist/src/modular/static-helpers-metadata.d.ts.map +1 -1
- package/dist/src/modular/static-helpers-metadata.js +12 -0
- package/dist/src/modular/static-helpers-metadata.js.map +1 -1
- package/dist/src/transform/transfromRLCOptions.d.ts.map +1 -1
- package/dist/src/transform/transfromRLCOptions.js +10 -0
- package/dist/src/transform/transfromRLCOptions.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/framework/hooks/binder.ts +15 -5
- package/src/framework/load-static-helpers.ts +79 -51
- package/src/index.ts +22 -7
- package/src/lib.ts +13 -0
- package/src/modular/buildOperations.ts +2 -1
- package/src/modular/emitModels.ts +47 -2
- package/src/modular/emitSamples.ts +7 -1
- package/src/modular/emitTests.ts +227 -0
- package/src/modular/external-dependencies.ts +43 -0
- package/src/modular/helpers/exampleValueHelpers.ts +940 -0
- package/src/modular/helpers/operationHelpers.ts +229 -17
- package/src/modular/static-helpers-metadata.ts +13 -0
- package/src/transform/transfromRLCOptions.ts +14 -0
- package/static/static-helpers/serialization/get-binary-response-body-browser.mts +22 -0
- package/static/static-helpers/serialization/get-binary-response-body.ts +24 -0
- package/static/test-helpers/recordedClient.ts +30 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { SourceFile } from "ts-morph";
|
|
2
|
+
import { SdkContext } from "../utils/interfaces.js";
|
|
3
|
+
import { NameType, normalizeName } from "@azure-tools/rlc-common";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { existsSync, rmSync } from "fs";
|
|
6
|
+
import { getClassicalClientName } from "./helpers/namingHelpers.js";
|
|
7
|
+
import { ServiceOperation } from "../utils/operationUtil.js";
|
|
8
|
+
import {
|
|
9
|
+
buildParameterValueMap,
|
|
10
|
+
prepareCommonParameters,
|
|
11
|
+
getDescriptiveName,
|
|
12
|
+
ClientEmitOptions,
|
|
13
|
+
iterateClientsAndMethods,
|
|
14
|
+
generateMethodCall,
|
|
15
|
+
createSourceFile,
|
|
16
|
+
generateResponseAssertions
|
|
17
|
+
} from "./helpers/exampleValueHelpers.js";
|
|
18
|
+
import { AzureTestDependencies } from "./external-dependencies.js";
|
|
19
|
+
import { resolveReference } from "../framework/reference.js";
|
|
20
|
+
import { CreateRecorderHelpers } from "./static-helpers-metadata.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Clean up the test/generated folder before generating new tests
|
|
24
|
+
*/
|
|
25
|
+
async function cleanupTestFolder(dpgContext: SdkContext) {
|
|
26
|
+
const clients = dpgContext.sdkPackage.clients;
|
|
27
|
+
const baseTestFolder = join(
|
|
28
|
+
dpgContext.generationPathDetail?.rootDir ?? "",
|
|
29
|
+
"test",
|
|
30
|
+
"generated"
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// If there are multiple clients, clean up subfolders
|
|
34
|
+
if (clients.length > 1) {
|
|
35
|
+
for (const client of clients) {
|
|
36
|
+
const subFolder = normalizeName(
|
|
37
|
+
getClassicalClientName(client),
|
|
38
|
+
NameType.File
|
|
39
|
+
);
|
|
40
|
+
const clientTestFolder = join(baseTestFolder, subFolder);
|
|
41
|
+
if (existsSync(clientTestFolder)) {
|
|
42
|
+
rmSync(clientTestFolder, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
// Single client, clean up the entire test/generated folder
|
|
47
|
+
if (existsSync(baseTestFolder)) {
|
|
48
|
+
rmSync(baseTestFolder, { recursive: true, force: true });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Helpers to emit tests similar to samples
|
|
55
|
+
*/
|
|
56
|
+
export async function emitTests(dpgContext: SdkContext): Promise<SourceFile[]> {
|
|
57
|
+
// Clean up the test/generated folder before generating new tests
|
|
58
|
+
await cleanupTestFolder(dpgContext);
|
|
59
|
+
|
|
60
|
+
return iterateClientsAndMethods(dpgContext, emitMethodTests);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function emitMethodTests(
|
|
64
|
+
dpgContext: SdkContext,
|
|
65
|
+
method: ServiceOperation,
|
|
66
|
+
options: ClientEmitOptions
|
|
67
|
+
): SourceFile | undefined {
|
|
68
|
+
const examples = method.operation.examples ?? [];
|
|
69
|
+
if (examples.length === 0) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const methodPrefix = `${options.classicalMethodPrefix ?? ""} ${
|
|
74
|
+
method.oriName ?? method.name
|
|
75
|
+
}`;
|
|
76
|
+
const fileName = normalizeName(`${methodPrefix} Test`, NameType.File);
|
|
77
|
+
const sourceFile = createSourceFile(
|
|
78
|
+
dpgContext,
|
|
79
|
+
method,
|
|
80
|
+
options,
|
|
81
|
+
"test",
|
|
82
|
+
fileName
|
|
83
|
+
);
|
|
84
|
+
const clientName = getClassicalClientName(options.client);
|
|
85
|
+
|
|
86
|
+
// Use resolveReference for test dependencies to let the binder handle imports automatically
|
|
87
|
+
const recorderType = resolveReference(AzureTestDependencies.Recorder);
|
|
88
|
+
const assertType = resolveReference(AzureTestDependencies.assert);
|
|
89
|
+
const beforeEachType = resolveReference(AzureTestDependencies.beforeEach);
|
|
90
|
+
const afterEachType = resolveReference(AzureTestDependencies.afterEach);
|
|
91
|
+
const itType = resolveReference(AzureTestDependencies.it);
|
|
92
|
+
const describeType = resolveReference(AzureTestDependencies.describe);
|
|
93
|
+
const createRecorderHelper = resolveReference(
|
|
94
|
+
CreateRecorderHelpers.createRecorder
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Compute the relative path from the generated test file to src/index.js.
|
|
98
|
+
// Single-client files land at test/generated/<file>.spec.ts (2 levels up),
|
|
99
|
+
// while multi-client files land at test/generated/<subFolder>/<file>.spec.ts (3 levels up).
|
|
100
|
+
const srcIndexRelativePath = options.subFolder
|
|
101
|
+
? "../../../src/index.js"
|
|
102
|
+
: "../../src/index.js";
|
|
103
|
+
|
|
104
|
+
// Import the client
|
|
105
|
+
sourceFile.addImportDeclaration({
|
|
106
|
+
moduleSpecifier: srcIndexRelativePath,
|
|
107
|
+
namedImports: [clientName]
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const testFunctions = [];
|
|
111
|
+
let clientParamNames: string[] = [];
|
|
112
|
+
let clientParameterDefs: string[] = [];
|
|
113
|
+
|
|
114
|
+
// Create test describe block
|
|
115
|
+
const methodDescription =
|
|
116
|
+
method.doc ?? `test ${method.oriName ?? method.name}`;
|
|
117
|
+
let normalizedDescription =
|
|
118
|
+
methodDescription.charAt(0).toLowerCase() + methodDescription.slice(1);
|
|
119
|
+
|
|
120
|
+
// Remove any trailing dots from describe block
|
|
121
|
+
normalizedDescription = normalizedDescription.replace(/\.$/, "");
|
|
122
|
+
|
|
123
|
+
// Generate test functions for each example
|
|
124
|
+
for (const example of examples) {
|
|
125
|
+
const testFunctionBody: string[] = [];
|
|
126
|
+
// Create a more descriptive test name based on the operation (same as samples)
|
|
127
|
+
const testName = getDescriptiveName(method, example.name, "test");
|
|
128
|
+
const parameterMap = buildParameterValueMap(example);
|
|
129
|
+
const parameters = prepareCommonParameters(
|
|
130
|
+
dpgContext,
|
|
131
|
+
method,
|
|
132
|
+
parameterMap,
|
|
133
|
+
options.client
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Prepare client-level parameters
|
|
137
|
+
const requiredClientParams = parameters.filter(
|
|
138
|
+
(p) => p.onClient && !p.isOptional
|
|
139
|
+
);
|
|
140
|
+
clientParameterDefs = requiredClientParams.map(
|
|
141
|
+
(p) => `const ${p.name} = ${p.value};`
|
|
142
|
+
);
|
|
143
|
+
clientParamNames = requiredClientParams.map((p) => p.name);
|
|
144
|
+
// add client options to parameters
|
|
145
|
+
// const clientOptions = recorder.configureClientOptions({});
|
|
146
|
+
clientParamNames.push("clientOptions");
|
|
147
|
+
clientParameterDefs.push(
|
|
148
|
+
`const clientOptions = recorder.configureClientOptions({});`
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const { methodCall } = generateMethodCall(method, parameters, options);
|
|
152
|
+
|
|
153
|
+
// Add method call based on type
|
|
154
|
+
const isPaging = method.kind === "paging";
|
|
155
|
+
const isLRO = method.kind === "lro" || method.kind === "lropaging";
|
|
156
|
+
|
|
157
|
+
if (method.response.type === undefined) {
|
|
158
|
+
// skip response handling for void methods
|
|
159
|
+
testFunctionBody.push(`await ${methodCall};`);
|
|
160
|
+
testFunctionBody.push(`/* Test passes if no exception is thrown */`);
|
|
161
|
+
} else if (isPaging) {
|
|
162
|
+
testFunctionBody.push(`const resArray = new Array();`);
|
|
163
|
+
testFunctionBody.push(
|
|
164
|
+
`for await (const item of ${methodCall}) { resArray.push(item); }`
|
|
165
|
+
);
|
|
166
|
+
testFunctionBody.push(`${assertType}.ok(resArray);`);
|
|
167
|
+
// Add response assertions for paging results
|
|
168
|
+
const pagingAssertions = generateResponseAssertions(
|
|
169
|
+
example,
|
|
170
|
+
"resArray",
|
|
171
|
+
true // isPaging = true
|
|
172
|
+
);
|
|
173
|
+
testFunctionBody.push(...pagingAssertions);
|
|
174
|
+
} else if (isLRO) {
|
|
175
|
+
testFunctionBody.push(`const result = await ${methodCall};`);
|
|
176
|
+
testFunctionBody.push(`${assertType}.ok(result);`);
|
|
177
|
+
// Add response assertions for LRO results
|
|
178
|
+
const responseAssertions = generateResponseAssertions(example, "result");
|
|
179
|
+
testFunctionBody.push(...responseAssertions);
|
|
180
|
+
} else {
|
|
181
|
+
testFunctionBody.push(`const result = await ${methodCall};`);
|
|
182
|
+
testFunctionBody.push(`${assertType}.ok(result);`);
|
|
183
|
+
// Add response assertions for non-paging results
|
|
184
|
+
const responseAssertions = generateResponseAssertions(example, "result");
|
|
185
|
+
testFunctionBody.push(...responseAssertions);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Create a test function
|
|
189
|
+
const testFunction = {
|
|
190
|
+
name: testName,
|
|
191
|
+
body: testFunctionBody
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
testFunctions.push(testFunction);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Create describe block with beforeEach and afterEach
|
|
198
|
+
const describeBlock = `
|
|
199
|
+
${describeType}("${normalizedDescription}", () => {
|
|
200
|
+
let recorder: ${recorderType};
|
|
201
|
+
let client: ${clientName};
|
|
202
|
+
|
|
203
|
+
${beforeEachType}(async function(ctx) {
|
|
204
|
+
recorder = await ${createRecorderHelper}(ctx);
|
|
205
|
+
${clientParameterDefs.join("\n")}
|
|
206
|
+
client = new ${clientName}(${clientParamNames.join(", ")});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
${afterEachType}(async function() {
|
|
210
|
+
await recorder.stop();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
${testFunctions
|
|
214
|
+
.map(
|
|
215
|
+
(fn) => `
|
|
216
|
+
${itType}("should ${fn.name}", async function() {
|
|
217
|
+
${fn.body.join("\n ")}
|
|
218
|
+
});
|
|
219
|
+
`
|
|
220
|
+
)
|
|
221
|
+
.join("")}
|
|
222
|
+
});`;
|
|
223
|
+
|
|
224
|
+
sourceFile.addStatements(describeBlock);
|
|
225
|
+
options.generatedFiles.push(sourceFile);
|
|
226
|
+
return sourceFile;
|
|
227
|
+
}
|
|
@@ -216,3 +216,46 @@ export const AzureIdentityDependencies = {
|
|
|
216
216
|
name: "DefaultAzureCredential"
|
|
217
217
|
}
|
|
218
218
|
};
|
|
219
|
+
|
|
220
|
+
export const AzureTestDependencies = {
|
|
221
|
+
Recorder: {
|
|
222
|
+
kind: "externalDependency",
|
|
223
|
+
module: "@azure-tools/test-recorder",
|
|
224
|
+
name: "Recorder"
|
|
225
|
+
},
|
|
226
|
+
env: {
|
|
227
|
+
kind: "externalDependency",
|
|
228
|
+
module: "@azure-tools/test-recorder",
|
|
229
|
+
name: "env"
|
|
230
|
+
},
|
|
231
|
+
createTestCredential: {
|
|
232
|
+
kind: "externalDependency",
|
|
233
|
+
module: "@azure-tools/test-credential",
|
|
234
|
+
name: "createTestCredential"
|
|
235
|
+
},
|
|
236
|
+
assert: {
|
|
237
|
+
kind: "externalDependency",
|
|
238
|
+
module: "vitest",
|
|
239
|
+
name: "assert"
|
|
240
|
+
},
|
|
241
|
+
beforeEach: {
|
|
242
|
+
kind: "externalDependency",
|
|
243
|
+
module: "vitest",
|
|
244
|
+
name: "beforeEach"
|
|
245
|
+
},
|
|
246
|
+
afterEach: {
|
|
247
|
+
kind: "externalDependency",
|
|
248
|
+
module: "vitest",
|
|
249
|
+
name: "afterEach"
|
|
250
|
+
},
|
|
251
|
+
it: {
|
|
252
|
+
kind: "externalDependency",
|
|
253
|
+
module: "vitest",
|
|
254
|
+
name: "it"
|
|
255
|
+
},
|
|
256
|
+
describe: {
|
|
257
|
+
kind: "externalDependency",
|
|
258
|
+
module: "vitest",
|
|
259
|
+
name: "describe"
|
|
260
|
+
}
|
|
261
|
+
} as const;
|