@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.
Files changed (66) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/src/framework/hooks/binder.d.ts +1 -1
  3. package/dist/src/framework/hooks/binder.d.ts.map +1 -1
  4. package/dist/src/framework/hooks/binder.js +11 -3
  5. package/dist/src/framework/hooks/binder.js.map +1 -1
  6. package/dist/src/framework/load-static-helpers.d.ts +3 -0
  7. package/dist/src/framework/load-static-helpers.d.ts.map +1 -1
  8. package/dist/src/framework/load-static-helpers.js +49 -38
  9. package/dist/src/framework/load-static-helpers.js.map +1 -1
  10. package/dist/src/index.d.ts.map +1 -1
  11. package/dist/src/index.js +19 -10
  12. package/dist/src/index.js.map +1 -1
  13. package/dist/src/lib.d.ts +7 -0
  14. package/dist/src/lib.d.ts.map +1 -1
  15. package/dist/src/lib.js +5 -0
  16. package/dist/src/lib.js.map +1 -1
  17. package/dist/src/modular/buildOperations.d.ts.map +1 -1
  18. package/dist/src/modular/buildOperations.js +1 -1
  19. package/dist/src/modular/buildOperations.js.map +1 -1
  20. package/dist/src/modular/emitModels.d.ts +8 -0
  21. package/dist/src/modular/emitModels.d.ts.map +1 -1
  22. package/dist/src/modular/emitModels.js +32 -2
  23. package/dist/src/modular/emitModels.js.map +1 -1
  24. package/dist/src/modular/emitSamples.js +9 -4
  25. package/dist/src/modular/emitSamples.js.map +1 -1
  26. package/dist/src/modular/emitTests.d.ts +7 -0
  27. package/dist/src/modular/emitTests.d.ts.map +1 -0
  28. package/dist/src/modular/emitTests.js +160 -0
  29. package/dist/src/modular/emitTests.js.map +1 -0
  30. package/dist/src/modular/external-dependencies.d.ts +42 -0
  31. package/dist/src/modular/external-dependencies.d.ts.map +1 -1
  32. package/dist/src/modular/external-dependencies.js +42 -0
  33. package/dist/src/modular/external-dependencies.js.map +1 -1
  34. package/dist/src/modular/helpers/exampleValueHelpers.d.ts +83 -0
  35. package/dist/src/modular/helpers/exampleValueHelpers.d.ts.map +1 -0
  36. package/dist/src/modular/helpers/exampleValueHelpers.js +631 -0
  37. package/dist/src/modular/helpers/exampleValueHelpers.js.map +1 -0
  38. package/dist/src/modular/helpers/operationHelpers.d.ts +22 -2
  39. package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
  40. package/dist/src/modular/helpers/operationHelpers.js +178 -9
  41. package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
  42. package/dist/src/modular/static-helpers-metadata.d.ts +12 -0
  43. package/dist/src/modular/static-helpers-metadata.d.ts.map +1 -1
  44. package/dist/src/modular/static-helpers-metadata.js +12 -0
  45. package/dist/src/modular/static-helpers-metadata.js.map +1 -1
  46. package/dist/src/transform/transfromRLCOptions.d.ts.map +1 -1
  47. package/dist/src/transform/transfromRLCOptions.js +10 -0
  48. package/dist/src/transform/transfromRLCOptions.js.map +1 -1
  49. package/dist/tsconfig.tsbuildinfo +1 -1
  50. package/package.json +2 -2
  51. package/src/framework/hooks/binder.ts +15 -5
  52. package/src/framework/load-static-helpers.ts +79 -51
  53. package/src/index.ts +22 -7
  54. package/src/lib.ts +13 -0
  55. package/src/modular/buildOperations.ts +2 -1
  56. package/src/modular/emitModels.ts +47 -2
  57. package/src/modular/emitSamples.ts +7 -1
  58. package/src/modular/emitTests.ts +227 -0
  59. package/src/modular/external-dependencies.ts +43 -0
  60. package/src/modular/helpers/exampleValueHelpers.ts +940 -0
  61. package/src/modular/helpers/operationHelpers.ts +229 -17
  62. package/src/modular/static-helpers-metadata.ts +13 -0
  63. package/src/transform/transfromRLCOptions.ts +14 -0
  64. package/static/static-helpers/serialization/get-binary-response-body-browser.mts +22 -0
  65. package/static/static-helpers/serialization/get-binary-response-body.ts +24 -0
  66. 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;