@techspokes/typescript-wsdl-client 0.11.0 → 0.11.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.
@@ -0,0 +1,630 @@
1
+ /**
2
+ * Test File Emitters
3
+ *
4
+ * Functions that produce test file content as template strings.
5
+ * Follows the same pattern as src/gateway/generators.ts.
6
+ *
7
+ * All emitters receive importsMode and compute relative import paths
8
+ * via computeRelativeImport() from src/util/imports.ts.
9
+ */
10
+ import { computeRelativeImport } from "../util/imports.js";
11
+ import { generateAllOperationMocks } from "./mockData.js";
12
+ /**
13
+ * Emits vitest.config.ts content.
14
+ *
15
+ * Sets root to __dirname so test glob patterns resolve relative to the
16
+ * config file location, not the working directory.
17
+ */
18
+ export function emitVitestConfig() {
19
+ return `import { dirname } from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+
24
+ export default {
25
+ test: {
26
+ root: __dirname,
27
+ include: [
28
+ "gateway/**/*.test.ts",
29
+ "runtime/**/*.test.ts",
30
+ ],
31
+ testTimeout: 30000,
32
+ },
33
+ };
34
+ `;
35
+ }
36
+ /**
37
+ * Emits helpers/mock-client.ts content with full default responses per operation.
38
+ *
39
+ * @param testDir - Absolute path to test output directory
40
+ * @param clientDir - Absolute path to client directory
41
+ * @param importsMode - Import extension mode
42
+ * @param clientMeta - Client metadata
43
+ * @param operations - Resolved operation metadata
44
+ * @param catalog - Compiled catalog for mock data generation
45
+ */
46
+ export function emitMockClientHelper(testDir, clientDir, importsMode, clientMeta, operations, catalog) {
47
+ const helpersDir = `${testDir}/helpers`;
48
+ const operationsImport = computeRelativeImport(helpersDir, `${clientDir}/operations`, importsMode);
49
+ const mocks = generateAllOperationMocks(catalog);
50
+ // Sort operations for deterministic output
51
+ const sortedOps = [...operations].sort((a, b) => a.operationId.localeCompare(b.operationId));
52
+ const methodEntries = sortedOps.map((op) => {
53
+ const mockData = mocks.get(op.operationId);
54
+ const response = mockData?.response ?? {};
55
+ const responseJson = JSON.stringify(response, null, 6).replace(/\n/g, "\n ");
56
+ return ` ${op.operationId}: async () => ({
57
+ response: ${responseJson},
58
+ headers: {},
59
+ })`;
60
+ }).join(",\n");
61
+ return `/**
62
+ * Mock SOAP Client Helper
63
+ *
64
+ * Creates a mock client implementing the ${clientMeta.className}Operations interface
65
+ * with full default responses per operation. Override individual methods as needed.
66
+ *
67
+ * Auto-generated by wsdl-tsc --test-dir. Customize freely.
68
+ */
69
+ import type { ${clientMeta.className}Operations } from "${operationsImport}";
70
+
71
+ /**
72
+ * Creates a mock SOAP client with default responses for all operations.
73
+ *
74
+ * Response shapes match the real SOAP client wrapper structure.
75
+ * ArrayOf* types use wrapper objects (e.g. { Forecast: [...] }).
76
+ * The generated unwrapArrayWrappers() function strips these at runtime.
77
+ *
78
+ * @param overrides - Override specific operation implementations
79
+ * @returns Mock client implementing ${clientMeta.className}Operations
80
+ */
81
+ export function createMockClient(
82
+ overrides: Partial<${clientMeta.className}Operations> = {}
83
+ ): ${clientMeta.className}Operations {
84
+ return {
85
+ ${methodEntries},
86
+ ...overrides,
87
+ } as ${clientMeta.className}Operations;
88
+ }
89
+ `;
90
+ }
91
+ /**
92
+ * Emits helpers/test-app.ts content.
93
+ *
94
+ * @param testDir - Absolute path to test output directory
95
+ * @param gatewayDir - Absolute path to gateway directory
96
+ * @param importsMode - Import extension mode
97
+ * @param clientMeta - Client metadata
98
+ */
99
+ export function emitTestAppHelper(testDir, gatewayDir, importsMode, clientMeta) {
100
+ const helpersDir = `${testDir}/helpers`;
101
+ const suffix = importsMode === "bare" ? "" : `.${importsMode}`;
102
+ const pluginImport = computeRelativeImport(helpersDir, `${gatewayDir}/plugin`, importsMode);
103
+ const mockClientImport = `./mock-client${suffix}`;
104
+ return `/**
105
+ * Test App Helper
106
+ *
107
+ * Creates a Fastify instance with the generated gateway plugin and a mock client.
108
+ * Uses static imports for reliable module resolution.
109
+ *
110
+ * Auto-generated by wsdl-tsc --test-dir. Customize freely.
111
+ */
112
+ import Fastify, { type FastifyInstance } from "fastify";
113
+ import gatewayPlugin from "${pluginImport}";
114
+ import type { ${clientMeta.className}Operations } from "${computeRelativeImport(helpersDir, `${gatewayDir}/../client/operations`, importsMode)}";
115
+ import { createMockClient } from "${mockClientImport}";
116
+
117
+ export { createMockClient };
118
+
119
+ /**
120
+ * Creates a Fastify app with the gateway plugin and a mock (or custom) client.
121
+ *
122
+ * @param client - Optional client override (defaults to createMockClient())
123
+ * @returns Ready Fastify instance
124
+ */
125
+ export async function createTestApp(
126
+ client?: ${clientMeta.className}Operations
127
+ ): Promise<FastifyInstance> {
128
+ const app = Fastify({ logger: false });
129
+ await app.register(gatewayPlugin, {
130
+ client: client ?? createMockClient(),
131
+ });
132
+ await app.ready();
133
+ return app;
134
+ }
135
+ `;
136
+ }
137
+ /**
138
+ * Emits gateway/routes.test.ts with one test per operation.
139
+ *
140
+ * @param testDir - Absolute path to test output directory
141
+ * @param importsMode - Import extension mode
142
+ * @param operations - Resolved operation metadata
143
+ * @param catalog - Compiled catalog for mock data generation
144
+ */
145
+ export function emitRoutesTest(testDir, importsMode, operations, catalog) {
146
+ const suffix = importsMode === "bare" ? "" : `.${importsMode}`;
147
+ const testAppImport = `../helpers/test-app${suffix}`;
148
+ const mocks = generateAllOperationMocks(catalog);
149
+ // Sort operations for deterministic output
150
+ const sortedOps = [...operations].sort((a, b) => a.operationId.localeCompare(b.operationId));
151
+ const testCases = sortedOps.map((op) => {
152
+ const mockData = mocks.get(op.operationId);
153
+ const requestPayload = JSON.stringify(mockData?.request ?? {}, null, 4).replace(/\n/g, "\n ");
154
+ return ` it("${op.method.toUpperCase()} ${op.path} returns SUCCESS envelope", async () => {
155
+ const app = await createTestApp();
156
+ try {
157
+ const res = await app.inject({
158
+ method: "${op.method.toUpperCase()}",
159
+ url: "${op.path}",
160
+ payload: ${requestPayload},
161
+ });
162
+ expect(res.statusCode).toBe(200);
163
+ const body = res.json();
164
+ expect(body.status).toBe("SUCCESS");
165
+ expect(body.data).toBeDefined();
166
+ expect(body.error).toBeNull();
167
+ } finally {
168
+ await app.close();
169
+ }
170
+ });`;
171
+ }).join("\n\n");
172
+ return `/**
173
+ * Gateway Routes — Happy Path Tests
174
+ *
175
+ * One test per operation: inject request, assert 200 + SUCCESS envelope.
176
+ *
177
+ * Auto-generated by wsdl-tsc --test-dir. Customize freely.
178
+ */
179
+ import { describe, it, expect } from "vitest";
180
+ import { createTestApp } from "${testAppImport}";
181
+
182
+ describe("gateway routes — happy path", () => {
183
+ ${testCases}
184
+ });
185
+ `;
186
+ }
187
+ /**
188
+ * Emits gateway/errors.test.ts with error classification tests through Fastify.
189
+ *
190
+ * @param testDir - Absolute path to test output directory
191
+ * @param importsMode - Import extension mode
192
+ * @param operations - Resolved operation metadata (uses first operation for error tests)
193
+ * @param catalog - Compiled catalog for mock data generation
194
+ */
195
+ export function emitErrorsTest(testDir, importsMode, operations, catalog) {
196
+ const suffix = importsMode === "bare" ? "" : `.${importsMode}`;
197
+ const testAppImport = `../helpers/test-app${suffix}`;
198
+ const mockClientImport = `../helpers/mock-client${suffix}`;
199
+ // Use first operation for error tests
200
+ const sortedOps = [...operations].sort((a, b) => a.operationId.localeCompare(b.operationId));
201
+ const op = sortedOps[0];
202
+ if (!op)
203
+ return "// No operations found\n";
204
+ const mocks = generateAllOperationMocks(catalog);
205
+ const mockData = mocks.get(op.operationId);
206
+ const requestPayload = JSON.stringify(mockData?.request ?? {}, null, 4).replace(/\n/g, "\n ");
207
+ return `/**
208
+ * Gateway Error Handling Tests
209
+ *
210
+ * Tests error classification through Fastify: 500, 502, 503, 504.
211
+ *
212
+ * Auto-generated by wsdl-tsc --test-dir. Customize freely.
213
+ */
214
+ import { describe, it, expect } from "vitest";
215
+ import { createTestApp } from "${testAppImport}";
216
+ import { createMockClient } from "${mockClientImport}";
217
+
218
+ describe("gateway routes — error handling", () => {
219
+ it("returns 500 for generic errors", async () => {
220
+ const app = await createTestApp(
221
+ createMockClient({
222
+ ${op.operationId}: async () => {
223
+ throw new Error("something broke");
224
+ },
225
+ })
226
+ );
227
+ try {
228
+ const res = await app.inject({
229
+ method: "${op.method.toUpperCase()}",
230
+ url: "${op.path}",
231
+ payload: ${requestPayload},
232
+ });
233
+ expect(res.statusCode).toBe(500);
234
+ const body = res.json();
235
+ expect(body.status).toBe("ERROR");
236
+ expect(body.error.code).toBe("INTERNAL_ERROR");
237
+ } finally {
238
+ await app.close();
239
+ }
240
+ });
241
+
242
+ it("returns 502 for SOAP fault errors", async () => {
243
+ const app = await createTestApp(
244
+ createMockClient({
245
+ ${op.operationId}: async () => {
246
+ throw Object.assign(new Error("SOAP fault"), {
247
+ root: {
248
+ Envelope: {
249
+ Body: {
250
+ Fault: {
251
+ faultcode: "soap:Server",
252
+ faultstring: "Service error",
253
+ },
254
+ },
255
+ },
256
+ },
257
+ });
258
+ },
259
+ })
260
+ );
261
+ try {
262
+ const res = await app.inject({
263
+ method: "${op.method.toUpperCase()}",
264
+ url: "${op.path}",
265
+ payload: ${requestPayload},
266
+ });
267
+ expect(res.statusCode).toBe(502);
268
+ const body = res.json();
269
+ expect(body.status).toBe("ERROR");
270
+ expect(body.error.code).toBe("SOAP_FAULT");
271
+ } finally {
272
+ await app.close();
273
+ }
274
+ });
275
+
276
+ it("returns 503 for connection errors", async () => {
277
+ const app = await createTestApp(
278
+ createMockClient({
279
+ ${op.operationId}: async () => {
280
+ throw new Error("connect ECONNREFUSED 127.0.0.1:80");
281
+ },
282
+ })
283
+ );
284
+ try {
285
+ const res = await app.inject({
286
+ method: "${op.method.toUpperCase()}",
287
+ url: "${op.path}",
288
+ payload: ${requestPayload},
289
+ });
290
+ expect(res.statusCode).toBe(503);
291
+ const body = res.json();
292
+ expect(body.status).toBe("ERROR");
293
+ expect(body.error.code).toBe("SERVICE_UNAVAILABLE");
294
+ } finally {
295
+ await app.close();
296
+ }
297
+ });
298
+
299
+ it("returns 504 for timeout errors", async () => {
300
+ const app = await createTestApp(
301
+ createMockClient({
302
+ ${op.operationId}: async () => {
303
+ throw new Error("ETIMEDOUT");
304
+ },
305
+ })
306
+ );
307
+ try {
308
+ const res = await app.inject({
309
+ method: "${op.method.toUpperCase()}",
310
+ url: "${op.path}",
311
+ payload: ${requestPayload},
312
+ });
313
+ expect(res.statusCode).toBe(504);
314
+ const body = res.json();
315
+ expect(body.status).toBe("ERROR");
316
+ expect(body.error.code).toBe("GATEWAY_TIMEOUT");
317
+ } finally {
318
+ await app.close();
319
+ }
320
+ });
321
+ });
322
+ `;
323
+ }
324
+ /**
325
+ * Emits gateway/envelope.test.ts with SUCCESS/ERROR structure assertions.
326
+ */
327
+ export function emitEnvelopeTest(testDir, importsMode, operations, catalog) {
328
+ const suffix = importsMode === "bare" ? "" : `.${importsMode}`;
329
+ const testAppImport = `../helpers/test-app${suffix}`;
330
+ const mockClientImport = `../helpers/mock-client${suffix}`;
331
+ const sortedOps = [...operations].sort((a, b) => a.operationId.localeCompare(b.operationId));
332
+ const op = sortedOps[0];
333
+ if (!op)
334
+ return "// No operations found\n";
335
+ const mocks = generateAllOperationMocks(catalog);
336
+ const mockData = mocks.get(op.operationId);
337
+ const requestPayload = JSON.stringify(mockData?.request ?? {}, null, 4).replace(/\n/g, "\n ");
338
+ return `/**
339
+ * Gateway Envelope Structure Tests
340
+ *
341
+ * Validates SUCCESS and ERROR envelope shapes.
342
+ *
343
+ * Auto-generated by wsdl-tsc --test-dir. Customize freely.
344
+ */
345
+ import { describe, it, expect } from "vitest";
346
+ import { createTestApp } from "${testAppImport}";
347
+ import { createMockClient } from "${mockClientImport}";
348
+
349
+ describe("gateway — envelope structure", () => {
350
+ it("success envelope has correct shape", async () => {
351
+ const app = await createTestApp();
352
+ try {
353
+ const res = await app.inject({
354
+ method: "${op.method.toUpperCase()}",
355
+ url: "${op.path}",
356
+ payload: ${requestPayload},
357
+ });
358
+ expect(res.statusCode).toBe(200);
359
+ const body = res.json();
360
+ expect(body).toHaveProperty("status", "SUCCESS");
361
+ expect(body).toHaveProperty("message", null);
362
+ expect(body).toHaveProperty("data");
363
+ expect(body).toHaveProperty("error", null);
364
+ } finally {
365
+ await app.close();
366
+ }
367
+ });
368
+
369
+ it("error envelope has correct shape", async () => {
370
+ const app = await createTestApp(
371
+ createMockClient({
372
+ ${op.operationId}: async () => {
373
+ throw new Error("test error");
374
+ },
375
+ })
376
+ );
377
+ try {
378
+ const res = await app.inject({
379
+ method: "${op.method.toUpperCase()}",
380
+ url: "${op.path}",
381
+ payload: ${requestPayload},
382
+ });
383
+ expect(res.statusCode).toBe(500);
384
+ const body = res.json();
385
+ expect(body).toHaveProperty("status", "ERROR");
386
+ expect(body).toHaveProperty("message");
387
+ expect(body).toHaveProperty("data", null);
388
+ expect(body.error).toHaveProperty("code");
389
+ expect(body.error).toHaveProperty("message");
390
+ } finally {
391
+ await app.close();
392
+ }
393
+ });
394
+ });
395
+ `;
396
+ }
397
+ /**
398
+ * Emits gateway/validation.test.ts with invalid payload tests per route.
399
+ */
400
+ export function emitValidationTest(testDir, importsMode, operations) {
401
+ const suffix = importsMode === "bare" ? "" : `.${importsMode}`;
402
+ const testAppImport = `../helpers/test-app${suffix}`;
403
+ const sortedOps = [...operations].sort((a, b) => a.operationId.localeCompare(b.operationId));
404
+ const testCases = sortedOps.map((op) => {
405
+ return ` it("${op.method.toUpperCase()} ${op.path} rejects array body", async () => {
406
+ const app = await createTestApp();
407
+ try {
408
+ const res = await app.inject({
409
+ method: "${op.method.toUpperCase()}",
410
+ url: "${op.path}",
411
+ payload: [1, 2, 3],
412
+ });
413
+ expect(res.statusCode).toBe(400);
414
+ } finally {
415
+ await app.close();
416
+ }
417
+ });`;
418
+ }).join("\n\n");
419
+ return `/**
420
+ * Gateway Validation Tests
421
+ *
422
+ * Tests that invalid payloads are rejected per route.
423
+ *
424
+ * Auto-generated by wsdl-tsc --test-dir. Customize freely.
425
+ */
426
+ import { describe, it, expect } from "vitest";
427
+ import { createTestApp } from "${testAppImport}";
428
+
429
+ describe("gateway routes — validation", () => {
430
+ ${testCases}
431
+ });
432
+ `;
433
+ }
434
+ /**
435
+ * Emits runtime/classify-error.test.ts with direct classifyError() unit tests.
436
+ */
437
+ export function emitClassifyErrorTest(testDir, gatewayDir, importsMode) {
438
+ const runtimeDir = `${testDir}/runtime`;
439
+ const runtimeImport = computeRelativeImport(runtimeDir, `${gatewayDir}/runtime`, importsMode);
440
+ return `/**
441
+ * classifyError() Unit Tests
442
+ *
443
+ * Direct unit tests of the classifyError() function from gateway runtime.
444
+ *
445
+ * Auto-generated by wsdl-tsc --test-dir. Customize freely.
446
+ */
447
+ import { describe, it, expect } from "vitest";
448
+ import { classifyError } from "${runtimeImport}";
449
+
450
+ describe("classifyError", () => {
451
+ it("returns 400 for validation errors", () => {
452
+ const err = Object.assign(new Error("Validation failed"), {
453
+ validation: [{ message: "body/field must be string" }],
454
+ });
455
+ const result = classifyError(err);
456
+ expect(result.httpStatus).toBe(400);
457
+ expect(result.code).toBe("VALIDATION_ERROR");
458
+ });
459
+
460
+ it("returns 502 for SOAP faults", () => {
461
+ const err = Object.assign(new Error("SOAP fault"), {
462
+ root: {
463
+ Envelope: {
464
+ Body: {
465
+ Fault: {
466
+ faultcode: "soap:Server",
467
+ faultstring: "Service error",
468
+ },
469
+ },
470
+ },
471
+ },
472
+ });
473
+ const result = classifyError(err);
474
+ expect(result.httpStatus).toBe(502);
475
+ expect(result.code).toBe("SOAP_FAULT");
476
+ });
477
+
478
+ it("returns 503 for ECONNREFUSED", () => {
479
+ const result = classifyError(new Error("connect ECONNREFUSED 127.0.0.1:80"));
480
+ expect(result.httpStatus).toBe(503);
481
+ expect(result.code).toBe("SERVICE_UNAVAILABLE");
482
+ });
483
+
484
+ it("returns 503 for ENOTFOUND", () => {
485
+ const result = classifyError(new Error("getaddrinfo ENOTFOUND soap.example.com"));
486
+ expect(result.httpStatus).toBe(503);
487
+ expect(result.code).toBe("SERVICE_UNAVAILABLE");
488
+ });
489
+
490
+ it("returns 504 for ETIMEDOUT", () => {
491
+ const result = classifyError(new Error("ETIMEDOUT"));
492
+ expect(result.httpStatus).toBe(504);
493
+ expect(result.code).toBe("GATEWAY_TIMEOUT");
494
+ });
495
+
496
+ it("returns 504 for timeout messages", () => {
497
+ const result = classifyError(new Error("Request timeout after 30000ms"));
498
+ expect(result.httpStatus).toBe(504);
499
+ expect(result.code).toBe("GATEWAY_TIMEOUT");
500
+ });
501
+
502
+ it("returns 500 for unknown errors", () => {
503
+ const result = classifyError(new Error("something unexpected"));
504
+ expect(result.httpStatus).toBe(500);
505
+ expect(result.code).toBe("INTERNAL_ERROR");
506
+ });
507
+
508
+ it("handles non-Error values", () => {
509
+ const result = classifyError("string error");
510
+ expect(result.httpStatus).toBe(500);
511
+ expect(result.code).toBe("INTERNAL_ERROR");
512
+ expect(result.message).toBe("string error");
513
+ });
514
+ });
515
+ `;
516
+ }
517
+ /**
518
+ * Emits runtime/envelope-builders.test.ts with buildSuccessEnvelope/buildErrorEnvelope tests.
519
+ */
520
+ export function emitEnvelopeBuildersTest(testDir, gatewayDir, importsMode) {
521
+ const runtimeDir = `${testDir}/runtime`;
522
+ const runtimeImport = computeRelativeImport(runtimeDir, `${gatewayDir}/runtime`, importsMode);
523
+ return `/**
524
+ * Envelope Builder Unit Tests
525
+ *
526
+ * Tests buildSuccessEnvelope() and buildErrorEnvelope() from gateway runtime.
527
+ *
528
+ * Auto-generated by wsdl-tsc --test-dir. Customize freely.
529
+ */
530
+ import { describe, it, expect } from "vitest";
531
+ import { buildSuccessEnvelope, buildErrorEnvelope } from "${runtimeImport}";
532
+
533
+ describe("buildSuccessEnvelope", () => {
534
+ it("produces correct structure", () => {
535
+ const envelope = buildSuccessEnvelope({ foo: "bar" });
536
+ expect(envelope).toEqual({
537
+ status: "SUCCESS",
538
+ message: null,
539
+ data: { foo: "bar" },
540
+ error: null,
541
+ });
542
+ });
543
+
544
+ it("accepts optional message", () => {
545
+ const envelope = buildSuccessEnvelope({ foo: "bar" }, "All good");
546
+ expect(envelope.message).toBe("All good");
547
+ });
548
+ });
549
+
550
+ describe("buildErrorEnvelope", () => {
551
+ it("produces correct structure", () => {
552
+ const envelope = buildErrorEnvelope("SOME_ERROR", "Something failed");
553
+ expect(envelope).toEqual({
554
+ status: "ERROR",
555
+ message: "Something failed",
556
+ data: null,
557
+ error: { code: "SOME_ERROR", message: "Something failed" },
558
+ });
559
+ });
560
+
561
+ it("includes details when provided", () => {
562
+ const envelope = buildErrorEnvelope("SOME_ERROR", "Failed", { reason: "timeout" });
563
+ expect(envelope.error.details).toEqual({ reason: "timeout" });
564
+ });
565
+ });
566
+ `;
567
+ }
568
+ /**
569
+ * Emits runtime/unwrap.test.ts with unwrapArrayWrappers tests.
570
+ * Returns null if no array wrappers exist in the catalog.
571
+ */
572
+ export function emitUnwrapTest(testDir, gatewayDir, importsMode, catalog) {
573
+ // Detect array wrappers
574
+ const wrappers = detectArrayWrappersFromCatalog(catalog);
575
+ if (Object.keys(wrappers).length === 0)
576
+ return null;
577
+ const runtimeDir = `${testDir}/runtime`;
578
+ const runtimeImport = computeRelativeImport(runtimeDir, `${gatewayDir}/runtime`, importsMode);
579
+ const wrapperTests = Object.entries(wrappers).map(([wrapperType, innerKey]) => {
580
+ return ` it("unwraps ${wrapperType} to flat array", () => {
581
+ const wrapped = { ${innerKey}: [{ id: 1 }] };
582
+ const result = unwrapArrayWrappers(wrapped, "${wrapperType}");
583
+ expect(result).toEqual([{ id: 1 }]);
584
+ });`;
585
+ }).join("\n\n");
586
+ return `/**
587
+ * unwrapArrayWrappers() Unit Tests
588
+ *
589
+ * Tests the ArrayOf* wrapper unwrapping from gateway runtime.
590
+ *
591
+ * Auto-generated by wsdl-tsc --test-dir. Customize freely.
592
+ */
593
+ import { describe, it, expect } from "vitest";
594
+ import { unwrapArrayWrappers } from "${runtimeImport}";
595
+
596
+ describe("unwrapArrayWrappers", () => {
597
+ ${wrapperTests}
598
+
599
+ it("returns empty array when inner key is missing", () => {
600
+ const result = unwrapArrayWrappers({}, "${Object.keys(wrappers)[0]}");
601
+ expect(result).toEqual([]);
602
+ });
603
+
604
+ it("returns null/undefined/primitives unchanged", () => {
605
+ expect(unwrapArrayWrappers(null, "Anything")).toBeNull();
606
+ expect(unwrapArrayWrappers(undefined, "Anything")).toBeUndefined();
607
+ expect(unwrapArrayWrappers("hello", "Anything")).toBe("hello");
608
+ });
609
+ });
610
+ `;
611
+ }
612
+ /**
613
+ * Detects ArrayOf* wrapper types from catalog (mirrors the logic in generators.ts).
614
+ */
615
+ function detectArrayWrappersFromCatalog(catalog) {
616
+ const wrappers = {};
617
+ const childTypes = catalog.meta?.childType ?? {};
618
+ const propMeta = catalog.meta?.propMeta ?? {};
619
+ for (const [typeName, children] of Object.entries(childTypes)) {
620
+ const childEntries = Object.entries(children);
621
+ if (childEntries.length !== 1)
622
+ continue;
623
+ const [propName] = childEntries[0];
624
+ const meta = propMeta[typeName]?.[propName];
625
+ if (meta?.max === "unbounded" || (typeof meta?.max === "number" && meta.max > 1)) {
626
+ wrappers[typeName] = propName;
627
+ }
628
+ }
629
+ return wrappers;
630
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Mock Data Generator
3
+ *
4
+ * Generates realistic mock data trees from compiled catalog type metadata.
5
+ * Used by the test generator to create full default responses per operation
6
+ * so that generated tests pass out of the box.
7
+ *
8
+ * Pure logic — no I/O or side effects.
9
+ */
10
+ /**
11
+ * Options for mock data generation
12
+ */
13
+ export interface MockDataOptions {
14
+ maxDepth?: number;
15
+ }
16
+ /**
17
+ * Compiled catalog structure (minimal interface for mock data generation)
18
+ */
19
+ export interface CatalogForMocks {
20
+ meta?: {
21
+ childType?: Record<string, Record<string, string>>;
22
+ propMeta?: Record<string, Record<string, {
23
+ declaredType?: string;
24
+ min?: number;
25
+ max?: number | "unbounded";
26
+ nillable?: boolean;
27
+ }>>;
28
+ };
29
+ operations?: Array<{
30
+ name: string;
31
+ inputTypeName?: string;
32
+ outputTypeName?: string;
33
+ }>;
34
+ }
35
+ /**
36
+ * Generates a mock primitive value based on the TypeScript type and property name.
37
+ * Uses contextual defaults based on common property names.
38
+ *
39
+ * @param tsType - The TypeScript type (string, number, boolean)
40
+ * @param propName - The property name for contextual defaults
41
+ * @returns A mock value of the appropriate type
42
+ */
43
+ export declare function generateMockPrimitive(tsType: string, propName: string): string | number | boolean;
44
+ /**
45
+ * Generates a mock data object for a given type by walking the catalog type metadata.
46
+ *
47
+ * @param typeName - The type name to generate mock data for
48
+ * @param catalog - The compiled catalog with type metadata
49
+ * @param opts - Optional generation options
50
+ * @param visited - Set of visited type names for cycle detection (internal)
51
+ * @param depth - Current recursion depth (internal)
52
+ * @returns Mock data object matching the type structure
53
+ */
54
+ export declare function generateMockData(typeName: string, catalog: CatalogForMocks, opts?: MockDataOptions, visited?: Set<string>, depth?: number): Record<string, unknown>;
55
+ /**
56
+ * Generates mock request and response data for all operations in the catalog.
57
+ *
58
+ * Response data uses the pre-unwrap shape (e.g. { WeatherDescription: [{...}] }
59
+ * not [{...}]) since the generated route handler calls unwrapArrayWrappers() at runtime.
60
+ *
61
+ * @param catalog - The compiled catalog with operations and type metadata
62
+ * @returns Map from operation name to { request, response } mock data
63
+ */
64
+ export declare function generateAllOperationMocks(catalog: CatalogForMocks): Map<string, {
65
+ request: Record<string, unknown>;
66
+ response: Record<string, unknown>;
67
+ }>;
68
+ //# sourceMappingURL=mockData.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mockData.d.ts","sourceRoot":"","sources":["../../src/test/mockData.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QACnD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;YACvC,YAAY,CAAC,EAAE,MAAM,CAAC;YACtB,GAAG,CAAC,EAAE,MAAM,CAAC;YACb,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;YAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;SACpB,CAAC,CAAC,CAAC;KACL,CAAC;IACF,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC,CAAC;CACJ;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAiCjG;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,EACxB,IAAI,CAAC,EAAE,eAAe,EACtB,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EACrB,KAAK,CAAC,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoCzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,eAAe,GACvB,GAAG,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAkBtF"}