@techspokes/typescript-wsdl-client 0.10.4 → 0.11.1

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 (42) hide show
  1. package/README.md +26 -0
  2. package/dist/cli.js +18 -1
  3. package/dist/client/generateOperations.d.ts +13 -0
  4. package/dist/client/generateOperations.d.ts.map +1 -0
  5. package/dist/client/generateOperations.js +71 -0
  6. package/dist/compiler/schemaCompiler.d.ts.map +1 -1
  7. package/dist/compiler/schemaCompiler.js +15 -1
  8. package/dist/gateway/generateGateway.d.ts +1 -0
  9. package/dist/gateway/generateGateway.d.ts.map +1 -1
  10. package/dist/gateway/generateGateway.js +4 -2
  11. package/dist/gateway/generators.d.ts +2 -15
  12. package/dist/gateway/generators.d.ts.map +1 -1
  13. package/dist/gateway/generators.js +114 -30
  14. package/dist/gateway/helpers.d.ts +4 -2
  15. package/dist/gateway/helpers.d.ts.map +1 -1
  16. package/dist/gateway/helpers.js +4 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +3 -0
  19. package/dist/loader/wsdlLoader.d.ts.map +1 -1
  20. package/dist/loader/wsdlLoader.js +30 -4
  21. package/dist/openapi/generateOpenAPI.d.ts +1 -0
  22. package/dist/openapi/generateOpenAPI.d.ts.map +1 -1
  23. package/dist/openapi/generateOpenAPI.js +1 -0
  24. package/dist/openapi/generateSchemas.d.ts +1 -0
  25. package/dist/openapi/generateSchemas.d.ts.map +1 -1
  26. package/dist/openapi/generateSchemas.js +4 -3
  27. package/dist/pipeline.d.ts.map +1 -1
  28. package/dist/pipeline.js +6 -1
  29. package/dist/util/builder.d.ts.map +1 -1
  30. package/dist/util/builder.js +1 -0
  31. package/dist/util/cli.d.ts +3 -2
  32. package/dist/util/cli.d.ts.map +1 -1
  33. package/dist/util/cli.js +14 -4
  34. package/dist/util/errors.d.ts +37 -0
  35. package/dist/util/errors.d.ts.map +1 -0
  36. package/dist/util/errors.js +37 -0
  37. package/docs/README.md +1 -0
  38. package/docs/cli-reference.md +2 -0
  39. package/docs/concepts.md +29 -2
  40. package/docs/generated-code.md +24 -0
  41. package/docs/testing.md +193 -0
  42. package/package.json +9 -4
@@ -0,0 +1,193 @@
1
+ # Testing Guide
2
+
3
+ Guide to running and writing tests for wsdl-tsc development and for testing generated code in consumer projects.
4
+
5
+ See [README](../README.md) for quick start and [CONTRIBUTING](../CONTRIBUTING.md) for development setup.
6
+
7
+ ## Test Architecture
8
+
9
+ The project uses three layers of testing:
10
+
11
+ 1. **Unit tests** — Pure function tests for utilities, parsers, and type mapping
12
+ 2. **Snapshot tests** — Baseline comparisons for all generated pipeline output
13
+ 3. **Integration tests** — End-to-end gateway tests using Fastify's `inject()` with mock clients
14
+
15
+ All tests use [Vitest](https://vitest.dev/) and run in under 3 seconds.
16
+
17
+ ## Running Tests
18
+
19
+ ```bash
20
+ npm test # All Vitest tests
21
+ npm run test:unit # Unit tests only
22
+ npm run test:snap # Snapshot tests only
23
+ npm run test:integration # Integration tests only
24
+ npm run test:watch # Watch mode for development
25
+ ```
26
+
27
+ For the full CI pipeline including smoke tests:
28
+
29
+ ```bash
30
+ npm run ci
31
+ ```
32
+
33
+ ## Unit Tests
34
+
35
+ Unit tests cover pure functions with no I/O or side effects:
36
+
37
+ - **`tools.test.ts`** — `pascal()`, `resolveQName()`, `explodePascal()`, `pascalToSnakeCase()`, `normalizeArray()`, `getChildrenWithLocalName()`, `getFirstWithLocalName()`
38
+ - **`casing.test.ts`** — `toPathSegment()` with kebab, asis, and lower styles
39
+ - **`primitives.test.ts`** — `xsdToTsPrimitive()` covering all XSD types (string-like, boolean, integers, decimals, floats, dates, any)
40
+ - **`errors.test.ts`** — `WsdlCompilationError` construction and `toUserMessage()` formatting
41
+ - **`schema-alignment.test.ts`** — Cross-validates TypeScript types, JSON schemas, and catalog.json for consistency
42
+
43
+ ### Writing Unit Tests
44
+
45
+ ```typescript
46
+ import { describe, it, expect } from "vitest";
47
+ import { pascal } from "../../src/util/tools.js";
48
+
49
+ describe("pascal", () => {
50
+ it("converts kebab-case", () => {
51
+ expect(pascal("get-weather")).toBe("GetWeather");
52
+ });
53
+ });
54
+ ```
55
+
56
+ ## Snapshot Tests
57
+
58
+ Snapshot tests capture the complete output of the pipeline as baselines. When a generator change intentionally alters output, the snapshot diff shows exactly what changed.
59
+
60
+ ### How It Works
61
+
62
+ 1. The pipeline runs against `examples/minimal/weather.wsdl` into a temp directory
63
+ 2. Each generated file is read and compared against the stored snapshot
64
+ 3. A file inventory snapshot detects added or removed files
65
+
66
+ ### Updating Snapshots
67
+
68
+ ```bash
69
+ npx vitest run test/snapshot -u
70
+ ```
71
+
72
+ Always review the diff before committing updated snapshots.
73
+
74
+ ### What's Covered
75
+
76
+ - Client output: `client.ts`, `types.ts`, `utils.ts`, `operations.ts`, `catalog.json`
77
+ - OpenAPI: `openapi.json`
78
+ - Gateway core: `plugin.ts`, `routes.ts`, `schemas.ts`, `runtime.ts`, `_typecheck.ts`
79
+ - Gateway routes: one handler per WSDL operation
80
+ - Gateway schemas: all model and operation JSON schema files
81
+ - File inventory: complete listing of generated files
82
+
83
+ ## Integration Tests
84
+
85
+ Integration tests verify the generated gateway works end-to-end by:
86
+
87
+ 1. Running the pipeline in `beforeAll` to generate gateway code
88
+ 2. Dynamically importing the generated plugin
89
+ 3. Creating a Fastify instance with the plugin and a mock client
90
+ 4. Using `fastify.inject()` to send HTTP requests and verify responses
91
+
92
+ ### Mock Client Pattern
93
+
94
+ The generated `operations.ts` provides a typed interface for creating test doubles:
95
+
96
+ ```typescript
97
+ import type { WeatherOperations } from "../client/operations.js";
98
+
99
+ function createMockClient(): WeatherOperations {
100
+ return {
101
+ GetCityWeatherByZIP: async (args) => ({
102
+ response: {
103
+ GetCityWeatherByZIPResult: {
104
+ Success: true,
105
+ ResponseText: "City Found",
106
+ State: "NY",
107
+ City: "New York",
108
+ Temperature: "72",
109
+ },
110
+ },
111
+ headers: {},
112
+ }),
113
+ GetCityForecastByZIP: async (args) => ({
114
+ response: {
115
+ GetCityForecastByZIPResult: {
116
+ Success: true,
117
+ ResponseText: "Forecast Found",
118
+ // Use SOAP wrapper shape — unwrapArrayWrappers() handles conversion
119
+ ForecastResult: { Forecast: [] },
120
+ },
121
+ },
122
+ headers: {},
123
+ }),
124
+ GetWeatherInformation: async (args) => ({
125
+ response: {
126
+ // Use SOAP wrapper shape — unwrapArrayWrappers() handles conversion
127
+ GetWeatherInformationResult: { WeatherDescription: [] },
128
+ },
129
+ headers: {},
130
+ }),
131
+ };
132
+ }
133
+ ```
134
+
135
+ Each method returns the same `{ response, headers }` shape as the real SOAP client. Use the wrapper object structure matching TypeScript types — the generated `unwrapArrayWrappers()` function handles conversion to the flat array shape expected by JSON schemas.
136
+
137
+ ### Using the Mock with Fastify
138
+
139
+ ```typescript
140
+ import Fastify from "fastify";
141
+
142
+ const app = Fastify();
143
+ await app.register(weatherGateway, {
144
+ client: createMockClient(),
145
+ prefix: "/v1/weather",
146
+ });
147
+ await app.ready();
148
+
149
+ const res = await app.inject({
150
+ method: "POST",
151
+ url: "/v1/weather/get-city-weather-by-zip",
152
+ payload: { ZIP: "10001" },
153
+ });
154
+
155
+ expect(res.statusCode).toBe(200);
156
+ expect(res.json().status).toBe("SUCCESS");
157
+ ```
158
+
159
+ ### Dynamic Import of Generated Code
160
+
161
+ Integration tests dynamically import generated `.ts` files from temp directories. This works because Vitest's Vite module resolution handles TypeScript imports, JSON import attributes, and bare specifiers from the project's `node_modules`:
162
+
163
+ ```typescript
164
+ import { pathToFileURL } from "node:url";
165
+
166
+ const pluginModule = await import(pathToFileURL(join(outDir, "gateway", "plugin.ts")).href);
167
+ ```
168
+
169
+ ## Known Issues
170
+
171
+ ### ArrayOf* Schema-Type Mismatch (Resolved)
172
+
173
+ JSON schemas flatten SOAP `ArrayOf*` wrapper types to plain `type: "array"` (when `--openapi-flatten-array-wrappers` is `true`, the default), while TypeScript types preserve the wrapper structure (e.g., `ArrayOfForecast = { Forecast?: Forecast[] }`).
174
+
175
+ This mismatch is resolved by the generated `unwrapArrayWrappers()` function in `runtime.ts`. Route handlers call it automatically to strip wrapper objects before Fastify serialization. Mock clients should return the real SOAP wrapper structure — the unwrap function handles the conversion.
176
+
177
+ When `--openapi-flatten-array-wrappers false` is used, ArrayOf* types are emitted as `type: "object"` and no unwrap function is generated. In this mode, mock data should use the wrapper object shape matching both the TypeScript types and the JSON schemas.
178
+
179
+ ### Error Details Serialization
180
+
181
+ 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
+ ## For Consumer Projects
184
+
185
+ If you're using wsdl-tsc as a dependency and want to test your integration:
186
+
187
+ 1. Generate code with `npx wsdl-tsc pipeline`
188
+ 2. Import the operations interface from `operations.ts`
189
+ 3. Create a mock client implementing the interface
190
+ 4. Register the generated gateway plugin with your mock client
191
+ 5. Use Fastify's `inject()` to test routes without a running server
192
+
193
+ The operations interface is the recommended seam for dependency injection and testing. It's a pure TypeScript interface with no runtime dependencies on the `soap` package.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techspokes/typescript-wsdl-client",
3
- "version": "0.10.4",
3
+ "version": "0.11.1",
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",
@@ -58,7 +58,11 @@
58
58
  "clean:tmp": "rimraf tmp",
59
59
  "build": "tsc -p tsconfig.json",
60
60
  "typecheck": "tsc --noEmit",
61
- "test": "npm run typecheck && npm run smoke:pipeline",
61
+ "test": "vitest run",
62
+ "test:unit": "vitest run test/unit",
63
+ "test:snap": "vitest run test/snapshot",
64
+ "test:integration": "vitest run test/integration",
65
+ "test:watch": "vitest",
62
66
  "prepublishOnly": "npm run clean && npm run build",
63
67
  "smoke:reset": "npm run clean:tmp",
64
68
  "smoke:compile": "npm run smoke:reset && tsx src/cli.ts compile --wsdl-source examples/minimal/weather.wsdl --catalog-file tmp/catalog.json",
@@ -67,7 +71,7 @@
67
71
  "smoke:gateway": "npm run smoke:reset && tsx src/cli.ts client --wsdl-source examples/minimal/weather.wsdl --client-dir tmp/client && tsx src/cli.ts openapi --catalog-file tmp/client/catalog.json --openapi-file tmp/openapi.json --openapi-format json && tsx src/cli.ts gateway --openapi-file tmp/openapi.json --client-dir tmp/client --gateway-dir tmp/gateway --gateway-service-name weather --gateway-version-prefix v1 && tsc -p tsconfig.smoke.json",
68
72
  "smoke:pipeline": "npm run smoke:reset && tsx src/cli.ts pipeline --wsdl-source examples/minimal/weather.wsdl --client-dir tmp/client --openapi-file tmp/openapi.json --gateway-dir tmp/gateway --gateway-service-name weather --gateway-version-prefix v1 --openapi-format json --init-app && tsc -p tsconfig.smoke.json",
69
73
  "smoke:app": "npm run smoke:reset && tsx src/cli.ts pipeline --wsdl-source examples/minimal/weather.wsdl --client-dir tmp/client --openapi-file tmp/openapi.json --gateway-dir tmp/gateway --gateway-service-name weather --gateway-version-prefix v1 --openapi-format json --openapi-servers https://example.com/api && tsx src/cli.ts app --client-dir tmp/client --gateway-dir tmp/gateway --openapi-file tmp/openapi.json --app-dir tmp/app --port 8080 && tsc -p tsconfig.smoke.json",
70
- "ci": "npm run clean && npm run build && npm run typecheck && npm run smoke:pipeline",
74
+ "ci": "npm run clean && npm run build && npm run typecheck && vitest run && npm run smoke:pipeline",
71
75
  "examples:regenerate": "tsx src/cli.ts pipeline --wsdl-source examples/minimal/weather.wsdl --client-dir examples/generated-output/client --openapi-file examples/generated-output/openapi.json --gateway-dir examples/generated-output/gateway --gateway-service-name weather --gateway-version-prefix v1 --openapi-format json"
72
76
  },
73
77
  "devDependencies": {
@@ -78,7 +82,8 @@
78
82
  "fastify-plugin": "^5.1.0",
79
83
  "rimraf": "^6.1.0",
80
84
  "tsx": "^4.21.0",
81
- "typescript": "^5.9.0"
85
+ "typescript": "^5.9.0",
86
+ "vitest": "^4.0.18"
82
87
  },
83
88
  "dependencies": {
84
89
  "@apidevtools/swagger-parser": "^12.1.0",