@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,133 @@
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
+ * Generates a mock primitive value based on the TypeScript type and property name.
12
+ * Uses contextual defaults based on common property names.
13
+ *
14
+ * @param tsType - The TypeScript type (string, number, boolean)
15
+ * @param propName - The property name for contextual defaults
16
+ * @returns A mock value of the appropriate type
17
+ */
18
+ export function generateMockPrimitive(tsType, propName) {
19
+ const lower = propName.toLowerCase();
20
+ if (tsType === "boolean") {
21
+ if (lower === "success")
22
+ return true;
23
+ return true;
24
+ }
25
+ if (tsType === "number") {
26
+ if (lower.includes("id"))
27
+ return 1;
28
+ if (lower.includes("temperature") || lower.includes("temp"))
29
+ return 72;
30
+ if (lower.includes("humidity"))
31
+ return 65;
32
+ if (lower.includes("pressure"))
33
+ return 1013;
34
+ return 0;
35
+ }
36
+ // string type — use contextual defaults
37
+ if (lower === "zip" || lower === "zipcode" || lower === "postalcode")
38
+ return "10001";
39
+ if (lower === "city")
40
+ return "New York";
41
+ if (lower === "state")
42
+ return "NY";
43
+ if (lower === "country")
44
+ return "US";
45
+ if (lower === "description" || lower === "desciption")
46
+ return "Sample description";
47
+ if (lower === "responsetext")
48
+ return "Data Found";
49
+ if (lower === "weatherstationcity")
50
+ return "White Plains";
51
+ if (lower.includes("url") || lower.includes("picture"))
52
+ return "https://example.com/image.png";
53
+ if (lower.includes("wind"))
54
+ return "CALM";
55
+ if (lower.includes("visibility"))
56
+ return "10 miles";
57
+ if (lower.includes("windchill"))
58
+ return "72";
59
+ if (lower.includes("remarks"))
60
+ return "";
61
+ if (lower.includes("date") || lower.includes("time"))
62
+ return "2026-01-15T12:00:00.000Z";
63
+ if (lower.includes("temperature") || lower === "morninglow" || lower === "daytimehigh")
64
+ return "72";
65
+ if (lower.includes("nighttime") || lower.includes("daytime"))
66
+ return "0";
67
+ return "sample";
68
+ }
69
+ /**
70
+ * Generates a mock data object for a given type by walking the catalog type metadata.
71
+ *
72
+ * @param typeName - The type name to generate mock data for
73
+ * @param catalog - The compiled catalog with type metadata
74
+ * @param opts - Optional generation options
75
+ * @param visited - Set of visited type names for cycle detection (internal)
76
+ * @param depth - Current recursion depth (internal)
77
+ * @returns Mock data object matching the type structure
78
+ */
79
+ export function generateMockData(typeName, catalog, opts, visited, depth) {
80
+ const maxDepth = opts?.maxDepth ?? 10;
81
+ const currentDepth = depth ?? 0;
82
+ const currentVisited = visited ?? new Set();
83
+ if (currentDepth >= maxDepth || currentVisited.has(typeName)) {
84
+ return {};
85
+ }
86
+ const childTypes = catalog.meta?.childType?.[typeName];
87
+ if (!childTypes || Object.keys(childTypes).length === 0) {
88
+ return {};
89
+ }
90
+ const propMeta = catalog.meta?.propMeta?.[typeName] ?? {};
91
+ const newVisited = new Set(currentVisited);
92
+ newVisited.add(typeName);
93
+ const result = {};
94
+ for (const [propName, propType] of Object.entries(childTypes)) {
95
+ const meta = propMeta[propName];
96
+ const isArray = meta?.max === "unbounded" || (typeof meta?.max === "number" && meta.max > 1);
97
+ // Check if it's a primitive type
98
+ if (propType === "string" || propType === "number" || propType === "boolean") {
99
+ const value = generateMockPrimitive(propType, propName);
100
+ result[propName] = isArray ? [value] : value;
101
+ }
102
+ else {
103
+ // Complex type — recurse
104
+ const childData = generateMockData(propType, catalog, opts, newVisited, currentDepth + 1);
105
+ result[propName] = isArray ? [childData] : childData;
106
+ }
107
+ }
108
+ return result;
109
+ }
110
+ /**
111
+ * Generates mock request and response data for all operations in the catalog.
112
+ *
113
+ * Response data uses the pre-unwrap shape (e.g. { WeatherDescription: [{...}] }
114
+ * not [{...}]) since the generated route handler calls unwrapArrayWrappers() at runtime.
115
+ *
116
+ * @param catalog - The compiled catalog with operations and type metadata
117
+ * @returns Map from operation name to { request, response } mock data
118
+ */
119
+ export function generateAllOperationMocks(catalog) {
120
+ const result = new Map();
121
+ if (!catalog.operations)
122
+ return result;
123
+ for (const op of catalog.operations) {
124
+ const request = op.inputTypeName
125
+ ? generateMockData(op.inputTypeName, catalog)
126
+ : {};
127
+ const response = op.outputTypeName
128
+ ? generateMockData(op.outputTypeName, catalog)
129
+ : {};
130
+ result.set(op.name, { request, response });
131
+ }
132
+ return result;
133
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Returns the file extension for the import mode
3
+ *
4
+ * @param {string} imports - Import mode (js, ts, or bare)
5
+ * @returns {string} - File extension with leading dot or empty string for bare
6
+ */
7
+ export declare function getImportExtension(imports: string): string;
8
+ /**
9
+ * Computes a relative import path from source to target
10
+ *
11
+ * @param {string} from - Source directory
12
+ * @param {string} to - Target file or directory
13
+ * @param {string} imports - Import mode (js, ts, or bare)
14
+ * @returns {string} - Relative import specifier with proper extension
15
+ */
16
+ export declare function computeRelativeImport(from: string, to: string, imports: string): string;
17
+ //# sourceMappingURL=imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../src/util/imports.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAI1D;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAavF"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Shared Import Path Utilities
3
+ *
4
+ * Provides functions for computing relative import paths and file extensions
5
+ * across different import modes (js, ts, bare). Used by app scaffold, test
6
+ * generation, and other modules that need to emit import specifiers.
7
+ */
8
+ import path from "node:path";
9
+ /**
10
+ * Returns the file extension for the import mode
11
+ *
12
+ * @param {string} imports - Import mode (js, ts, or bare)
13
+ * @returns {string} - File extension with leading dot or empty string for bare
14
+ */
15
+ export function getImportExtension(imports) {
16
+ if (imports === "js")
17
+ return ".js";
18
+ if (imports === "ts")
19
+ return ".ts";
20
+ return "";
21
+ }
22
+ /**
23
+ * Computes a relative import path from source to target
24
+ *
25
+ * @param {string} from - Source directory
26
+ * @param {string} to - Target file or directory
27
+ * @param {string} imports - Import mode (js, ts, or bare)
28
+ * @returns {string} - Relative import specifier with proper extension
29
+ */
30
+ export function computeRelativeImport(from, to, imports) {
31
+ const rel = path.relative(from, to);
32
+ // Normalize to POSIX separators
33
+ const posix = rel.split(path.sep).join("/");
34
+ // Ensure it starts with ./ or ../
35
+ const prefixed = posix.startsWith(".") ? posix : `./${posix}`;
36
+ // Apply import extension rules
37
+ const ext = getImportExtension(imports);
38
+ if (ext) {
39
+ return prefixed + ext;
40
+ }
41
+ return prefixed;
42
+ }
@@ -122,6 +122,13 @@ The catalog is auto-placed alongside the first available output directory: `{cli
122
122
  | `--app-port` | `3000` | Default server port |
123
123
  | `--app-prefix` | (empty) | Route prefix |
124
124
 
125
+ ### Test Suite Flags
126
+
127
+ | Flag | Default | Description |
128
+ |------|---------|-------------|
129
+ | `--test-dir` | (none) | Generate Vitest test suite in this directory (requires client, gateway) |
130
+ | `--force-test` | `false` | Overwrite existing test files when using --test-dir |
131
+
125
132
  ### Pipeline Workflow
126
133
 
127
134
  Steps execute in order:
@@ -133,6 +140,7 @@ Steps execute in order:
133
140
  5. Generate OpenAPI (if --openapi-file)
134
141
  6. Generate gateway (if --gateway-dir)
135
142
  7. Scaffold app (if --init-app)
143
+ 8. Generate test suite (if --test-dir)
136
144
 
137
145
  All steps share the same parsed WSDL and compiled catalog.
138
146
 
@@ -163,6 +171,19 @@ npx wsdl-tsc pipeline \
163
171
  --init-app
164
172
  ```
165
173
 
174
+ With generated test suite:
175
+
176
+ ```bash
177
+ npx wsdl-tsc pipeline \
178
+ --wsdl-source examples/minimal/weather.wsdl \
179
+ --client-dir tmp/client \
180
+ --openapi-file tmp/openapi.json \
181
+ --gateway-dir tmp/gateway \
182
+ --gateway-service-name weather \
183
+ --gateway-version-prefix v1 \
184
+ --test-dir tmp/tests
185
+ ```
186
+
166
187
  Client and OpenAPI only:
167
188
 
168
189
  ```bash
@@ -0,0 +1,36 @@
1
+ # ADR 001: Generated Test Suite for Gateway Artifacts
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Users of generated Fastify gateways have no automated way to verify the generated code works. The only testing path is to manually write Vitest tests following the pattern in this project's own `test/integration/gateway-routes.test.ts`. The FST_ERR_FAILED_ERROR_SERIALIZATION bug (fixed in 0.11.1) proved that generated code can have subtle runtime issues that only surface under specific conditions. Generated tests would catch these immediately.
10
+
11
+ ## Decision
12
+
13
+ Add an opt-in `--test-dir` flag to the pipeline command that generates a complete, runnable Vitest test suite validating all generated gateway artifacts.
14
+
15
+ ### Why generated tests (not a test library)
16
+
17
+ A reusable test library would require users to configure imports, mock data, and test structure themselves. Generated tests are zero-config: they import the exact generated modules with correct relative paths, use mock data derived from the actual catalog types, and run immediately with `npx vitest run`.
18
+
19
+ ### Why full mocks (not placeholders)
20
+
21
+ Placeholder mocks (empty objects, `TODO` comments) require manual effort before tests pass. Full mocks generated from catalog type metadata produce passing tests out of the box. Property names and types come from the compiled catalog's `meta.childType` map, so mock data matches the actual SOAP response shape.
22
+
23
+ ### Why skip-if-exists
24
+
25
+ Test files are meant to be customized after generation. Re-running the pipeline should not overwrite user modifications. The `--force-test` flag provides an explicit override when regeneration is desired.
26
+
27
+ ### Relationship to operations.ts interface
28
+
29
+ The generated `{ClassName}Operations` interface (from `operations.ts`) is the natural seam for mock injection. The mock client helper implements this interface with full default responses per operation. This matches the pattern established in `test/integration/gateway-routes.test.ts`.
30
+
31
+ ## Consequences
32
+
33
+ - Users get immediate test coverage for generated gateway code
34
+ - Mock data stays in sync with WSDL schema changes (regenerate to update)
35
+ - Test files can be customized without risk of overwrite
36
+ - Additional CLI flag and pipeline step increase the surface area to maintain
package/docs/testing.md CHANGED
@@ -39,6 +39,7 @@ Unit tests cover pure functions with no I/O or side effects:
39
39
  - **`primitives.test.ts`** — `xsdToTsPrimitive()` covering all XSD types (string-like, boolean, integers, decimals, floats, dates, any)
40
40
  - **`errors.test.ts`** — `WsdlCompilationError` construction and `toUserMessage()` formatting
41
41
  - **`schema-alignment.test.ts`** — Cross-validates TypeScript types, JSON schemas, and catalog.json for consistency
42
+ - **`mock-data.test.ts`** — `generateMockPrimitive()`, `generateMockData()`, `generateAllOperationMocks()` with cycle detection and array wrapping
42
43
 
43
44
  ### Writing Unit Tests
44
45
 
@@ -180,10 +181,79 @@ When `--openapi-flatten-array-wrappers false` is used, ArrayOf* types are emitte
180
181
 
181
182
  The `classifyError()` function puts `err.message` (a string) in the `details` field for connection and timeout errors, but the error JSON schema defines `details` as `object | null`. This causes Fastify serialization failures for 503/504 error responses. Test error classification directly via `classifyError()` rather than through Fastify's `inject()` for these error types.
182
183
 
184
+ ## Generated Test Suite
185
+
186
+ The `--test-dir` flag generates a complete, runnable Vitest test suite that validates all generated gateway artifacts out of the box. This is the recommended way to verify generated code in consumer projects.
187
+
188
+ ### Generating Tests
189
+
190
+ ```bash
191
+ npx wsdl-tsc pipeline \
192
+ --wsdl-source service.wsdl \
193
+ --client-dir ./generated/client \
194
+ --openapi-file ./generated/openapi.json \
195
+ --gateway-dir ./generated/gateway \
196
+ --gateway-service-name myservice \
197
+ --gateway-version-prefix v1 \
198
+ --test-dir ./generated/tests
199
+ ```
200
+
201
+ ### Generated Structure
202
+
203
+ ```text
204
+ {test-dir}/
205
+ vitest.config.ts
206
+ helpers/
207
+ mock-client.ts createMockClient() with full default responses per operation
208
+ test-app.ts createTestApp() Fastify bootstrap helper
209
+ gateway/
210
+ routes.test.ts per-operation happy path (one test per route)
211
+ errors.test.ts 400/500/502/503/504 through Fastify
212
+ envelope.test.ts SUCCESS/ERROR structure assertions
213
+ validation.test.ts invalid payloads rejected per route
214
+ runtime/
215
+ classify-error.test.ts classifyError() unit tests
216
+ envelope-builders.test.ts buildSuccessEnvelope/buildErrorEnvelope
217
+ unwrap.test.ts unwrapArrayWrappers (only when ArrayOf* wrappers exist)
218
+ ```
219
+
220
+ ### Running Generated Tests
221
+
222
+ ```bash
223
+ npx vitest run --config ./generated/tests/vitest.config.ts
224
+ ```
225
+
226
+ ### Skip-if-Exists Behavior
227
+
228
+ Test files that already exist are skipped by default. This allows you to customize generated tests without losing changes on regeneration. Use `--force-test` to overwrite all test files.
229
+
230
+ ### Mock Client
231
+
232
+ The generated `helpers/mock-client.ts` creates a fully typed mock client with default responses for every operation. Override individual methods for specific test scenarios:
233
+
234
+ ```typescript
235
+ import { createMockClient } from "./helpers/mock-client.js";
236
+
237
+ const client = createMockClient({
238
+ GetCityWeatherByZIP: async () => ({
239
+ response: { GetCityWeatherByZIPResult: { Success: false, WeatherID: 0 } },
240
+ headers: {},
241
+ }),
242
+ });
243
+ ```
244
+
245
+ Mock responses use the pre-unwrap SOAP wrapper shape. The generated `unwrapArrayWrappers()` function handles conversion at runtime.
246
+
183
247
  ## For Consumer Projects
184
248
 
185
249
  If you're using wsdl-tsc as a dependency and want to test your integration:
186
250
 
251
+ 1. Generate code with `npx wsdl-tsc pipeline --test-dir ./tests` to get a ready-to-run test suite
252
+ 2. Run `npx vitest run --config ./tests/vitest.config.ts` to verify everything works
253
+ 3. Customize generated tests as needed (they won't be overwritten on regeneration)
254
+
255
+ Alternatively, write tests manually:
256
+
187
257
  1. Generate code with `npx wsdl-tsc pipeline`
188
258
  2. Import the operations interface from `operations.ts`
189
259
  3. Create a mock client implementing the interface
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techspokes/typescript-wsdl-client",
3
- "version": "0.11.0",
3
+ "version": "0.11.3",
4
4
  "description": "Generate type-safe TypeScript SOAP clients, OpenAPI 3.1 specs, and production-ready Fastify REST gateways from WSDL/XSD definitions.",
5
5
  "keywords": [
6
6
  "wsdl",