@testsmith/api-spector 0.2.3 → 0.2.5

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,326 @@
1
+ "use strict";
2
+ const https = require("https");
3
+ const http = require("http");
4
+ const xmldom = require("@xmldom/xmldom");
5
+ function fetchUrl(url, headers = {}) {
6
+ return new Promise((resolveP, rejectP) => {
7
+ const lib = url.startsWith("https") ? https : http;
8
+ const req = lib.get(url, { headers }, (res) => {
9
+ const chunks = [];
10
+ res.on("data", (chunk) => chunks.push(chunk));
11
+ res.on("end", () => resolveP(Buffer.concat(chunks).toString("utf8")));
12
+ res.on("error", rejectP);
13
+ });
14
+ req.on("error", rejectP);
15
+ req.setTimeout(15e3, () => {
16
+ req.destroy();
17
+ rejectP(new Error("WSDL fetch timed out"));
18
+ });
19
+ });
20
+ }
21
+ const NS_SOAP = "http://schemas.xmlsoap.org/wsdl/soap/";
22
+ const NS_SOAP12 = "http://schemas.xmlsoap.org/wsdl/soap12/";
23
+ function isElement(node) {
24
+ return node.nodeType === 1;
25
+ }
26
+ function nodeListToArray(list) {
27
+ if (!list || typeof list.length !== "number") return [];
28
+ const out = [];
29
+ for (let i = 0; i < list.length; i++) out.push(list[i]);
30
+ return out;
31
+ }
32
+ function children(node) {
33
+ return nodeListToArray(node.childNodes).filter(isElement);
34
+ }
35
+ function childrenByLocalName(node, localName) {
36
+ return children(node).filter((c) => (c.localName ?? c.tagName) === localName);
37
+ }
38
+ function descendantsByLocalName(node, localName) {
39
+ const out = [];
40
+ const stack = [node];
41
+ while (stack.length) {
42
+ const cur = stack.pop();
43
+ for (const c of nodeListToArray(cur.childNodes)) {
44
+ if (isElement(c)) {
45
+ if ((c.localName ?? c.tagName) === localName) out.push(c);
46
+ stack.push(c);
47
+ }
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+ function attr(node, name) {
53
+ const v = node.getAttribute?.(name);
54
+ return v == null || v === "" ? void 0 : v;
55
+ }
56
+ function localOfQName(q) {
57
+ const i = q.indexOf(":");
58
+ return i === -1 ? q : q.slice(i + 1);
59
+ }
60
+ function indexSchema(definitions) {
61
+ const idx = /* @__PURE__ */ new Map();
62
+ for (const types of childrenByLocalName(definitions, "types")) {
63
+ for (const schema of childrenByLocalName(types, "schema")) {
64
+ for (const el of childrenByLocalName(schema, "element")) {
65
+ const n = attr(el, "name");
66
+ if (n) idx.set(n, el);
67
+ }
68
+ for (const ct of childrenByLocalName(schema, "complexType")) {
69
+ const n = attr(ct, "name");
70
+ if (n) idx.set("__type__:" + n, ct);
71
+ }
72
+ }
73
+ }
74
+ return idx;
75
+ }
76
+ function resolveComplexType(el, schemaIdx) {
77
+ const inlineCt = childrenByLocalName(el, "complexType")[0];
78
+ if (inlineCt) return inlineCt;
79
+ const typeAttr = attr(el, "type");
80
+ if (typeAttr) {
81
+ const local = localOfQName(typeAttr);
82
+ const named = schemaIdx.get("__type__:" + local);
83
+ if (named) return named;
84
+ return null;
85
+ }
86
+ return null;
87
+ }
88
+ function buildParams(complexType, schemaIdx, depth = 0) {
89
+ if (depth > 5) return [];
90
+ const params = [];
91
+ const containers = ["sequence", "all", "choice"].flatMap((n) => childrenByLocalName(complexType, n));
92
+ for (const container of containers) {
93
+ for (const childEl of childrenByLocalName(container, "element")) {
94
+ const name = attr(childEl, "name") ?? attr(childEl, "ref");
95
+ if (!name) continue;
96
+ const local = localOfQName(name);
97
+ const typeAttr = attr(childEl, "type");
98
+ let typeHint = "string";
99
+ if (typeAttr) typeHint = localOfQName(typeAttr);
100
+ const nested = resolveComplexType(childEl, schemaIdx);
101
+ if (nested) {
102
+ params.push({ name: local, typeHint: "complex", children: buildParams(nested, schemaIdx, depth + 1) });
103
+ } else {
104
+ params.push({ name: local, typeHint });
105
+ }
106
+ }
107
+ }
108
+ return params;
109
+ }
110
+ function indent(level) {
111
+ return " ".repeat(level);
112
+ }
113
+ function renderParams(params, level) {
114
+ return params.map((p) => {
115
+ if (p.children && p.children.length) {
116
+ return `${indent(level)}<tns:${p.name}>
117
+ ${renderParams(p.children, level + 1)}
118
+ ${indent(level)}</tns:${p.name}>`;
119
+ }
120
+ return `${indent(level)}<tns:${p.name}><!-- ${p.typeHint} --></tns:${p.name}>`;
121
+ }).join("\n");
122
+ }
123
+ function buildEnvelopeTemplate(operationName, namespace, params = [], soapVersion = "1.1") {
124
+ const envNs = soapVersion === "1.2" ? "http://www.w3.org/2003/05/soap-envelope" : "http://schemas.xmlsoap.org/soap/envelope/";
125
+ const body = params.length ? `
126
+ ${renderParams(params, 3)}
127
+ ` : `
128
+ <!-- Add parameters here -->
129
+ `;
130
+ return `<?xml version="1.0" encoding="utf-8"?>
131
+ <soap:Envelope
132
+ xmlns:soap="${envNs}"
133
+ xmlns:tns="${namespace}">
134
+ <soap:Header/>
135
+ <soap:Body>
136
+ <tns:${operationName}>${body}</tns:${operationName}>
137
+ </soap:Body>
138
+ </soap:Envelope>`;
139
+ }
140
+ function parseWsdl(wsdlText) {
141
+ let doc;
142
+ try {
143
+ doc = new xmldom.DOMParser().parseFromString(wsdlText, "text/xml");
144
+ } catch (err) {
145
+ throw new Error(`WSDL parse failed: ${err instanceof Error ? err.message : String(err)}`);
146
+ }
147
+ const definitions = childrenByLocalName(doc, "definitions")[0] ?? descendantsByLocalName(doc, "definitions")[0];
148
+ if (!definitions) {
149
+ return { targetNamespace: "", endpoints: [], operations: [] };
150
+ }
151
+ const targetNamespace = attr(definitions, "targetNamespace") ?? "";
152
+ const schemaIdx = indexSchema(definitions);
153
+ const portTypeInputs = {};
154
+ for (const pt of childrenByLocalName(definitions, "portType")) {
155
+ for (const op of childrenByLocalName(pt, "operation")) {
156
+ const name = attr(op, "name");
157
+ if (!name) continue;
158
+ const inputEl = childrenByLocalName(op, "input")[0];
159
+ portTypeInputs[name] = inputEl ? attr(inputEl, "message") : void 0;
160
+ }
161
+ }
162
+ const messageElements = {};
163
+ for (const msg of childrenByLocalName(definitions, "message")) {
164
+ const name = attr(msg, "name");
165
+ if (!name) continue;
166
+ const part = childrenByLocalName(msg, "part")[0];
167
+ if (!part) continue;
168
+ messageElements[name] = attr(part, "element");
169
+ }
170
+ const bindings = [];
171
+ for (const binding of childrenByLocalName(definitions, "binding")) {
172
+ const bindingName = attr(binding, "name") ?? "";
173
+ let soapVersion = "1.1";
174
+ for (const c of children(binding)) {
175
+ if ((c.localName ?? c.tagName) === "binding") {
176
+ if (c.namespaceURI === NS_SOAP12) soapVersion = "1.2";
177
+ else if (c.namespaceURI === NS_SOAP) soapVersion = "1.1";
178
+ }
179
+ }
180
+ const ops = [];
181
+ for (const op of childrenByLocalName(binding, "operation")) {
182
+ const opName = attr(op, "name");
183
+ if (!opName) continue;
184
+ let soapAction;
185
+ for (const c of children(op)) {
186
+ if ((c.localName ?? c.tagName) === "operation" && (c.namespaceURI === NS_SOAP || c.namespaceURI === NS_SOAP12)) {
187
+ soapAction = attr(c, "soapAction");
188
+ }
189
+ }
190
+ ops.push({ name: opName, soapAction });
191
+ }
192
+ bindings.push({ name: bindingName, soapVersion, operations: ops });
193
+ }
194
+ const endpoints = [];
195
+ for (const svc of childrenByLocalName(definitions, "service")) {
196
+ for (const port of childrenByLocalName(svc, "port")) {
197
+ const bindingRef = attr(port, "binding");
198
+ if (!bindingRef) continue;
199
+ let address;
200
+ let addrVersion = "1.1";
201
+ for (const c of children(port)) {
202
+ if ((c.localName ?? c.tagName) === "address") {
203
+ address = attr(c, "location");
204
+ if (c.namespaceURI === NS_SOAP12) addrVersion = "1.2";
205
+ }
206
+ }
207
+ if (address) {
208
+ endpoints.push({ binding: localOfQName(bindingRef), address, soapVersion: addrVersion });
209
+ }
210
+ }
211
+ }
212
+ const operations = [];
213
+ const seenOpNames = /* @__PURE__ */ new Set();
214
+ for (const binding of bindings) {
215
+ const endpoint = endpoints.find((e) => e.binding === binding.name)?.address;
216
+ for (const op of binding.operations) {
217
+ if (seenOpNames.has(op.name + "@" + binding.name)) continue;
218
+ seenOpNames.add(op.name + "@" + binding.name);
219
+ const messageQName = portTypeInputs[op.name];
220
+ const messageLocal = messageQName ? localOfQName(messageQName) : void 0;
221
+ const elementQName = messageLocal ? messageElements[messageLocal] : void 0;
222
+ const elementLocal = elementQName ? localOfQName(elementQName) : op.name;
223
+ const schemaEl = schemaIdx.get(elementLocal);
224
+ let params = [];
225
+ if (schemaEl) {
226
+ const ct = resolveComplexType(schemaEl, schemaIdx);
227
+ if (ct) params = buildParams(ct, schemaIdx);
228
+ }
229
+ operations.push({
230
+ name: op.name,
231
+ binding: binding.name,
232
+ soapAction: op.soapAction,
233
+ soapVersion: binding.soapVersion,
234
+ endpoint,
235
+ inputTemplate: buildEnvelopeTemplate(elementLocal, targetNamespace, params, binding.soapVersion),
236
+ params
237
+ });
238
+ }
239
+ }
240
+ if (operations.length === 0) {
241
+ const regex = /<(?:wsdl:)?operation\s+name\s*=\s*["']([^"']+)["']/g;
242
+ const seen = /* @__PURE__ */ new Set();
243
+ let m;
244
+ while ((m = regex.exec(wsdlText)) !== null) seen.add(m[1]);
245
+ const soapActionByName = {};
246
+ const blockRx = /<(?:wsdl:)?operation\s+name\s*=\s*["']([^"']+)["'][^>]*>([\s\S]*?)<\/(?:wsdl:)?operation>/g;
247
+ while ((m = blockRx.exec(wsdlText)) !== null) {
248
+ const sa = m[2].match(/soapAction\s*=\s*["']([^"']*)["']/);
249
+ if (sa) soapActionByName[m[1]] = sa[1];
250
+ }
251
+ for (const name of seen) {
252
+ operations.push({
253
+ name,
254
+ soapAction: soapActionByName[name],
255
+ soapVersion: "1.1",
256
+ inputTemplate: buildEnvelopeTemplate(name, targetNamespace, [], "1.1"),
257
+ params: []
258
+ });
259
+ }
260
+ }
261
+ return { targetNamespace, endpoints, operations };
262
+ }
263
+ function buildResponseEnvelope(operationName, namespace, soapVersion = "1.1") {
264
+ const envNs = soapVersion === "1.2" ? "http://www.w3.org/2003/05/soap-envelope" : "http://schemas.xmlsoap.org/soap/envelope/";
265
+ return `<?xml version="1.0" encoding="utf-8"?>
266
+ <soap:Envelope
267
+ xmlns:soap="${envNs}"
268
+ xmlns:tns="${namespace}">
269
+ <soap:Body>
270
+ <tns:${operationName}Response>
271
+ <tns:${operationName}Result><!-- replace with mock value --></tns:${operationName}Result>
272
+ </tns:${operationName}Response>
273
+ </soap:Body>
274
+ </soap:Envelope>`;
275
+ }
276
+ function buildMockDispatchScript(_opMap) {
277
+ return `// Auto-generated by Import WSDL — dispatches per SOAP operation.
278
+ // Envelopes come from route metadata.soapEnvelopes (set by the WSDL importer).
279
+ const envelopes = (metadata && metadata.soapEnvelopes) || {};
280
+ const ct = (metadata && metadata.soapVersion === '1.2')
281
+ ? 'application/soap+xml; charset=utf-8'
282
+ : 'text/xml; charset=utf-8';
283
+
284
+ const headers = request.headers || {};
285
+ const sa = String(headers['soapaction'] || headers['SOAPAction'] || '').replace(/"/g, '');
286
+ const body = request.body || '';
287
+
288
+ let opName = null;
289
+ for (const k of Object.keys(envelopes)) {
290
+ if (sa && (sa === k || sa.endsWith('/' + k) || sa.endsWith(':' + k))) { opName = k; break; }
291
+ if (body.indexOf('<' + k + ' ') !== -1 || body.indexOf('<' + k + '>') !== -1
292
+ || body.indexOf(':' + k + ' ') !== -1 || body.indexOf(':' + k + '>') !== -1) {
293
+ opName = k; break;
294
+ }
295
+ }
296
+
297
+ response.headers['Content-Type'] = ct;
298
+ if (opName) {
299
+ response.statusCode = 200;
300
+ response.body = envelopes[opName];
301
+ } else {
302
+ response.statusCode = 500;
303
+ response.body = '<?xml version="1.0"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><soap:Fault><faultstring>Unknown SOAP operation</faultstring></soap:Fault></soap:Body></soap:Envelope>';
304
+ }
305
+ `;
306
+ }
307
+ function registerSoapHandlers(ipc) {
308
+ ipc.handle("wsdl:fetch", async (_event, url, extraHeaders = {}) => {
309
+ const { validateWsdlFetchUrl } = await Promise.resolve().then(() => require("./ipc-validate-CscN4HfG.js"));
310
+ validateWsdlFetchUrl(url);
311
+ const wsdlText = await fetchUrl(url, extraHeaders);
312
+ return parseWsdl(wsdlText);
313
+ });
314
+ ipc.handle("wsdl:import", async (_event, opts) => {
315
+ const { validateWsdlImport } = await Promise.resolve().then(() => require("./ipc-validate-CscN4HfG.js"));
316
+ validateWsdlImport(opts);
317
+ const { importWsdl } = await Promise.resolve().then(() => require("./import-C9qdkBCH.js"));
318
+ const wsdlText = opts.xml ?? (opts.url ? await fetchUrl(opts.url) : "");
319
+ if (!wsdlText) throw new Error("wsdl:import requires either `url` or `xml`");
320
+ return importWsdl(wsdlText, { name: opts.name, existingMockPorts: opts.existingMockPorts });
321
+ });
322
+ }
323
+ exports.buildMockDispatchScript = buildMockDispatchScript;
324
+ exports.buildResponseEnvelope = buildResponseEnvelope;
325
+ exports.parseWsdl = parseWsdl;
326
+ exports.registerSoapHandlers = registerSoapHandlers;