@testsmith/api-spector 0.4.6 → 0.4.7
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.
- package/out/main/agents.js +2 -1
- package/out/main/chunks/{cli-common-CDQY1erJ.js → cli-common-BKYgsbGB.js} +1 -0
- package/out/main/chunks/{handle-BGYDylL2.js → handle-rimXXdJH.js} +26 -1
- package/out/main/chunks/{import-CUcjlmSK.js → import-DcenB5Q_.js} +2 -2
- package/out/main/chunks/{request-exec-CBi2kL6s.js → request-exec-BH-M3KqZ.js} +201 -6
- package/out/main/chunks/{snapshots-BDe-B5iQ.js → snapshots-CtYxkSLz.js} +556 -6
- package/out/main/chunks/{soap-handler-CAyUjzxB.js → soap-handler-BXx842MN.js} +2 -2
- package/out/main/contract.js +265 -296
- package/out/main/index.js +365 -19
- package/out/main/lib.js +2 -2
- package/out/main/mock.js +1 -1
- package/out/main/record.js +1 -1
- package/out/main/runner.js +4 -4
- package/out/main/wsdl.js +5 -4
- package/out/preload/index.js +45 -1
- package/out/renderer/assets/{index-B55aZQcN.js → index-B-OQJk1G.js} +2609 -787
- package/out/renderer/assets/index-B_xyaTDo.css +2 -0
- package/out/renderer/index.html +2 -2
- package/package.json +8 -5
- package/out/renderer/assets/index-DkZ0bxof.css +0 -2
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const undici = require("undici");
|
|
3
|
-
const requestExec = require("./request-exec-CBi2kL6s.js");
|
|
4
2
|
const promises = require("fs/promises");
|
|
5
3
|
const path = require("path");
|
|
4
|
+
const crypto = require("crypto");
|
|
5
|
+
const undici = require("undici");
|
|
6
|
+
const requestExec = require("./request-exec-BH-M3KqZ.js");
|
|
6
7
|
const Ajv = require("ajv");
|
|
7
8
|
const jsYaml = require("js-yaml");
|
|
8
|
-
const crypto = require("crypto");
|
|
9
9
|
const MATCH_KEY = "__match";
|
|
10
|
-
function isMatcher(node) {
|
|
10
|
+
function isMatcher$1(node) {
|
|
11
11
|
return typeof node === "object" && node !== null && !Array.isArray(node) && typeof node[MATCH_KEY] === "string";
|
|
12
12
|
}
|
|
13
13
|
function compileMatcher(m) {
|
|
@@ -44,7 +44,7 @@ function compileMatcher(m) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
function compileMatcherExample(node, exact = true) {
|
|
47
|
-
if (isMatcher(node)) return compileMatcher(node);
|
|
47
|
+
if (isMatcher$1(node)) return compileMatcher(node);
|
|
48
48
|
if (node === null) return { type: "null" };
|
|
49
49
|
const t = typeof node;
|
|
50
50
|
if (t === "boolean") return exact ? { const: node } : { type: "boolean" };
|
|
@@ -68,6 +68,552 @@ function compileMatcherExample(node, exact = true) {
|
|
|
68
68
|
}
|
|
69
69
|
return {};
|
|
70
70
|
}
|
|
71
|
+
function parsePath(path2) {
|
|
72
|
+
const tokens = [];
|
|
73
|
+
const re = /\.([^.[\]]+)|\['([^']*)'\]|\[(\d+|\*)\]/g;
|
|
74
|
+
let m;
|
|
75
|
+
while ((m = re.exec(path2)) !== null) {
|
|
76
|
+
if (m[1] !== void 0) tokens.push({ key: m[1] });
|
|
77
|
+
else if (m[2] !== void 0) tokens.push({ key: m[2] });
|
|
78
|
+
else if (m[3] !== void 0) tokens.push({ index: m[3] === "*" ? "*" : Number(m[3]) });
|
|
79
|
+
}
|
|
80
|
+
return tokens;
|
|
81
|
+
}
|
|
82
|
+
function ruleToMatcher(value, rule) {
|
|
83
|
+
switch (rule.match) {
|
|
84
|
+
case "type":
|
|
85
|
+
if (Array.isArray(value)) {
|
|
86
|
+
return { [MATCH_KEY]: "eachLike", value: value[0] ?? {}, min: rule.min ?? 1 };
|
|
87
|
+
}
|
|
88
|
+
return { [MATCH_KEY]: "type", value };
|
|
89
|
+
case "regex":
|
|
90
|
+
return { [MATCH_KEY]: "regex", regex: rule.regex ?? ".*", value };
|
|
91
|
+
case "integer":
|
|
92
|
+
return { [MATCH_KEY]: "integer", value };
|
|
93
|
+
case "number":
|
|
94
|
+
case "decimal":
|
|
95
|
+
return { [MATCH_KEY]: "decimal", value };
|
|
96
|
+
case "boolean":
|
|
97
|
+
return { [MATCH_KEY]: "boolean", value };
|
|
98
|
+
case "null":
|
|
99
|
+
return { [MATCH_KEY]: "null", value: null };
|
|
100
|
+
case "datetime":
|
|
101
|
+
case "timestamp":
|
|
102
|
+
return { [MATCH_KEY]: "datetime", value, format: rule.format };
|
|
103
|
+
case "date":
|
|
104
|
+
return { [MATCH_KEY]: "date", value };
|
|
105
|
+
case "time":
|
|
106
|
+
return { [MATCH_KEY]: "time", value };
|
|
107
|
+
default:
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function applyAtPath(root, tokens, transform) {
|
|
112
|
+
if (tokens.length === 0) return transform(root);
|
|
113
|
+
const [head, ...rest] = tokens;
|
|
114
|
+
if ("index" in head) {
|
|
115
|
+
if (!Array.isArray(root)) return root;
|
|
116
|
+
if (head.index === "*") {
|
|
117
|
+
return root.map((el) => applyAtPath(el, rest, transform));
|
|
118
|
+
}
|
|
119
|
+
const arr = [...root];
|
|
120
|
+
if (head.index < arr.length) arr[head.index] = applyAtPath(arr[head.index], rest, transform);
|
|
121
|
+
return arr;
|
|
122
|
+
}
|
|
123
|
+
if (typeof root !== "object" || root === null || Array.isArray(root)) return root;
|
|
124
|
+
const obj = { ...root };
|
|
125
|
+
if (head.key in obj) obj[head.key] = applyAtPath(obj[head.key], rest, transform);
|
|
126
|
+
return obj;
|
|
127
|
+
}
|
|
128
|
+
function bodyWithMatchers(body, matchingRules) {
|
|
129
|
+
const bodyRules = matchingRules?.["body"] ?? matchingRules?.["content"];
|
|
130
|
+
if (!bodyRules || body === void 0) return body;
|
|
131
|
+
const entries = Object.entries(bodyRules).sort((a, b) => parsePath(b[0]).length - parsePath(a[0]).length);
|
|
132
|
+
let result = body;
|
|
133
|
+
for (const [path2, def] of entries) {
|
|
134
|
+
const rule = def?.matchers?.[0];
|
|
135
|
+
if (!rule) continue;
|
|
136
|
+
const tokens = parsePath(path2);
|
|
137
|
+
result = applyAtPath(result, tokens, (v) => ruleToMatcher(v, rule));
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
function providerStatesOf(interaction) {
|
|
142
|
+
const v3 = interaction["providerStates"];
|
|
143
|
+
if (Array.isArray(v3)) return v3.map((s) => s?.name ?? "").filter(Boolean);
|
|
144
|
+
const v2 = interaction["providerState"] ?? interaction["provider_state"];
|
|
145
|
+
return typeof v2 === "string" && v2 ? [v2] : [];
|
|
146
|
+
}
|
|
147
|
+
function queryToParams(query) {
|
|
148
|
+
const params = [];
|
|
149
|
+
if (!query) return params;
|
|
150
|
+
if (typeof query === "string") {
|
|
151
|
+
for (const pair of query.split("&")) {
|
|
152
|
+
const [k, v = ""] = pair.split("=");
|
|
153
|
+
if (k) params.push({ key: decodeURIComponent(k), value: decodeURIComponent(v), enabled: true });
|
|
154
|
+
}
|
|
155
|
+
} else if (typeof query === "object") {
|
|
156
|
+
for (const [k, vals] of Object.entries(query)) {
|
|
157
|
+
const list = Array.isArray(vals) ? vals : [vals];
|
|
158
|
+
for (const v of list) params.push({ key: k, value: String(v ?? ""), enabled: true });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return params;
|
|
162
|
+
}
|
|
163
|
+
function headerEntries(headers) {
|
|
164
|
+
if (!headers || typeof headers !== "object") return [];
|
|
165
|
+
return Object.entries(headers).map(([key, value]) => ({
|
|
166
|
+
key,
|
|
167
|
+
value: Array.isArray(value) ? value.join(", ") : String(value ?? "")
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
function headersToKv(headers) {
|
|
171
|
+
return headerEntries(headers).map((h) => ({ ...h, enabled: true }));
|
|
172
|
+
}
|
|
173
|
+
function headersToContract(headers) {
|
|
174
|
+
return headerEntries(headers).map((h) => ({ ...h, required: true }));
|
|
175
|
+
}
|
|
176
|
+
function importInteraction(interaction) {
|
|
177
|
+
const request = interaction["request"] ?? {};
|
|
178
|
+
const response = interaction["response"] ?? {};
|
|
179
|
+
const method = String(request["method"] ?? "GET").toUpperCase();
|
|
180
|
+
const path2 = String(request["path"] ?? "/");
|
|
181
|
+
const reqBody = request["body"];
|
|
182
|
+
const body = reqBody === void 0 ? { mode: "none" } : typeof reqBody === "string" ? { mode: "raw", raw: reqBody } : { mode: "json", json: JSON.stringify(reqBody, null, 2) };
|
|
183
|
+
const contract = {};
|
|
184
|
+
if (typeof response["status"] === "number") contract.statusCode = response["status"];
|
|
185
|
+
const respHeaders = headersToContract(response["headers"]);
|
|
186
|
+
if (respHeaders?.length) contract.headers = respHeaders;
|
|
187
|
+
if (response["body"] !== void 0) {
|
|
188
|
+
const example = bodyWithMatchers(response["body"], response["matchingRules"]);
|
|
189
|
+
contract.bodyMatcher = JSON.stringify(example, null, 2);
|
|
190
|
+
}
|
|
191
|
+
const states = providerStatesOf(interaction);
|
|
192
|
+
if (states.length) contract.providerStates = states;
|
|
193
|
+
return {
|
|
194
|
+
id: crypto.randomUUID(),
|
|
195
|
+
name: String(interaction["description"] ?? `${method} ${path2}`),
|
|
196
|
+
method,
|
|
197
|
+
url: `{{baseUrl}}${path2}`,
|
|
198
|
+
headers: headersToKv(request["headers"]),
|
|
199
|
+
params: queryToParams(request["query"]),
|
|
200
|
+
auth: { type: "none" },
|
|
201
|
+
body,
|
|
202
|
+
contract,
|
|
203
|
+
meta: { tags: ["pact"] }
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function importPact(json) {
|
|
207
|
+
const pact = typeof json === "string" ? JSON.parse(json) : json;
|
|
208
|
+
const consumer = pact["consumer"]?.name ?? "consumer";
|
|
209
|
+
const provider = pact["provider"]?.name ?? "provider";
|
|
210
|
+
const metadata = pact["metadata"];
|
|
211
|
+
const specVersion = String(
|
|
212
|
+
metadata?.["pactSpecification"]?.version ?? metadata?.["pact-specification"]?.version ?? "3.0.0"
|
|
213
|
+
);
|
|
214
|
+
const interactions = pact["interactions"] ?? [];
|
|
215
|
+
const httpInteractions = interactions.filter((i) => {
|
|
216
|
+
const type = i["type"];
|
|
217
|
+
return type === void 0 || type === "Synchronous/HTTP" || type === "HTTP";
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
consumer,
|
|
221
|
+
provider,
|
|
222
|
+
specVersion,
|
|
223
|
+
requests: httpInteractions.map(importInteraction)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function pactToCollection(result) {
|
|
227
|
+
const requests = {};
|
|
228
|
+
for (const r of result.requests) requests[r.id] = r;
|
|
229
|
+
return {
|
|
230
|
+
version: "1.0",
|
|
231
|
+
id: crypto.randomUUID(),
|
|
232
|
+
name: `${result.consumer} → ${result.provider}`,
|
|
233
|
+
description: `Imported from Pact (spec ${result.specVersion})`,
|
|
234
|
+
rootFolder: {
|
|
235
|
+
id: crypto.randomUUID(),
|
|
236
|
+
name: "root",
|
|
237
|
+
folders: [],
|
|
238
|
+
requestIds: result.requests.map((r) => r.id)
|
|
239
|
+
},
|
|
240
|
+
requests,
|
|
241
|
+
collectionVariables: { baseUrl: "" }
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function isMatcher(node) {
|
|
245
|
+
return typeof node === "object" && node !== null && !Array.isArray(node) && typeof node[MATCH_KEY] === "string";
|
|
246
|
+
}
|
|
247
|
+
function exampleToPactBody(node) {
|
|
248
|
+
const rules = {};
|
|
249
|
+
function walk(n, path2) {
|
|
250
|
+
if (isMatcher(n)) {
|
|
251
|
+
const kind = n[MATCH_KEY];
|
|
252
|
+
switch (kind) {
|
|
253
|
+
case "type":
|
|
254
|
+
rules[path2] = { matchers: [{ match: "type" }] };
|
|
255
|
+
return walk(n.value, path2);
|
|
256
|
+
case "eachLike": {
|
|
257
|
+
rules[path2] = { matchers: [{ match: "type", min: n.min ?? 1 }] };
|
|
258
|
+
return [walk(n.value, `${path2}[*]`)];
|
|
259
|
+
}
|
|
260
|
+
case "regex":
|
|
261
|
+
rules[path2] = { matchers: [{ match: "regex", regex: n.regex ?? ".*" }] };
|
|
262
|
+
return n.value ?? "";
|
|
263
|
+
case "integer":
|
|
264
|
+
rules[path2] = { matchers: [{ match: "integer" }] };
|
|
265
|
+
return n.value ?? 0;
|
|
266
|
+
case "decimal":
|
|
267
|
+
case "number":
|
|
268
|
+
rules[path2] = { matchers: [{ match: "decimal" }] };
|
|
269
|
+
return n.value ?? 0;
|
|
270
|
+
case "boolean":
|
|
271
|
+
rules[path2] = { matchers: [{ match: "boolean" }] };
|
|
272
|
+
return n.value ?? false;
|
|
273
|
+
case "string":
|
|
274
|
+
rules[path2] = { matchers: [{ match: "type" }] };
|
|
275
|
+
return n.value ?? "";
|
|
276
|
+
case "null":
|
|
277
|
+
rules[path2] = { matchers: [{ match: "null" }] };
|
|
278
|
+
return null;
|
|
279
|
+
case "datetime":
|
|
280
|
+
case "timestamp":
|
|
281
|
+
rules[path2] = { matchers: [{ match: "datetime", format: n.format }] };
|
|
282
|
+
return n.value ?? "";
|
|
283
|
+
case "date":
|
|
284
|
+
rules[path2] = { matchers: [{ match: "date" }] };
|
|
285
|
+
return n.value ?? "";
|
|
286
|
+
case "time":
|
|
287
|
+
rules[path2] = { matchers: [{ match: "time" }] };
|
|
288
|
+
return n.value ?? "";
|
|
289
|
+
default:
|
|
290
|
+
return n.value;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (Array.isArray(n)) return n.map((el, i) => walk(el, `${path2}[${i}]`));
|
|
294
|
+
if (typeof n === "object" && n !== null) {
|
|
295
|
+
const out = {};
|
|
296
|
+
for (const [k, v] of Object.entries(n)) out[k] = walk(v, `${path2}.${k}`);
|
|
297
|
+
return out;
|
|
298
|
+
}
|
|
299
|
+
return n;
|
|
300
|
+
}
|
|
301
|
+
const body = walk(node, "$");
|
|
302
|
+
return { body, rules };
|
|
303
|
+
}
|
|
304
|
+
function kvToHeaders$1(kv) {
|
|
305
|
+
if (!kv?.length) return void 0;
|
|
306
|
+
const out = {};
|
|
307
|
+
for (const h of kv) if (h.enabled !== false && h.key) out[h.key] = h.value;
|
|
308
|
+
return Object.keys(out).length ? out : void 0;
|
|
309
|
+
}
|
|
310
|
+
const V4_SYNC_HTTP = "Synchronous/HTTP";
|
|
311
|
+
function interactionKey(identity) {
|
|
312
|
+
let h = 2166136261;
|
|
313
|
+
for (let i = 0; i < identity.length; i++) {
|
|
314
|
+
h ^= identity.charCodeAt(i);
|
|
315
|
+
h = Math.imul(h, 16777619);
|
|
316
|
+
}
|
|
317
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
318
|
+
}
|
|
319
|
+
function exportPact(consumer, provider, requests) {
|
|
320
|
+
const interactions = requests.filter((r) => r.contract).map((r) => {
|
|
321
|
+
const c = r.contract;
|
|
322
|
+
const path2 = r.url.replace(/^\{\{baseUrl\}\}/, "").replace(/^https?:\/\/[^/]+/, "") || "/";
|
|
323
|
+
const query = {};
|
|
324
|
+
for (const p of r.params ?? []) {
|
|
325
|
+
if (p.enabled === false || !p.key) continue;
|
|
326
|
+
(query[p.key] ??= []).push(p.value);
|
|
327
|
+
}
|
|
328
|
+
const response = {};
|
|
329
|
+
if (c.statusCode !== void 0) response["status"] = c.statusCode;
|
|
330
|
+
const respHeaders = kvToHeaders$1(c.headers);
|
|
331
|
+
if (respHeaders) response["headers"] = respHeaders;
|
|
332
|
+
if (c.bodyMatcher?.trim()) {
|
|
333
|
+
try {
|
|
334
|
+
const { body, rules } = exampleToPactBody(JSON.parse(c.bodyMatcher));
|
|
335
|
+
response["body"] = body;
|
|
336
|
+
if (Object.keys(rules).length) response["matchingRules"] = { body: rules };
|
|
337
|
+
} catch {
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const request = { method: r.method, path: path2 };
|
|
341
|
+
if (Object.keys(query).length) request["query"] = query;
|
|
342
|
+
const reqHeaders = kvToHeaders$1(r.headers);
|
|
343
|
+
if (reqHeaders) request["headers"] = reqHeaders;
|
|
344
|
+
if (r.body?.mode === "json" && r.body.json) {
|
|
345
|
+
try {
|
|
346
|
+
request["body"] = JSON.parse(r.body.json);
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
} else if (r.body?.mode === "raw" && r.body.raw) request["body"] = r.body.raw;
|
|
350
|
+
const interaction = {
|
|
351
|
+
type: V4_SYNC_HTTP,
|
|
352
|
+
key: interactionKey(`${r.method}|${path2}|${c.statusCode ?? ""}|${r.name}`),
|
|
353
|
+
description: r.name,
|
|
354
|
+
request,
|
|
355
|
+
response
|
|
356
|
+
};
|
|
357
|
+
if (c.providerStates?.length) interaction["providerStates"] = c.providerStates.map((name) => ({ name }));
|
|
358
|
+
return interaction;
|
|
359
|
+
});
|
|
360
|
+
return {
|
|
361
|
+
consumer: { name: consumer },
|
|
362
|
+
provider: { name: provider },
|
|
363
|
+
interactions,
|
|
364
|
+
metadata: {
|
|
365
|
+
pactSpecification: { version: "4.0" },
|
|
366
|
+
client: { name: "api-spector" }
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function kvToHeaders(kv) {
|
|
371
|
+
const out = {};
|
|
372
|
+
for (const p of kv ?? []) {
|
|
373
|
+
if (p.enabled === false || !p.key) continue;
|
|
374
|
+
out[p.key] = p.value;
|
|
375
|
+
}
|
|
376
|
+
return Object.keys(out).length ? out : void 0;
|
|
377
|
+
}
|
|
378
|
+
function kvToQuery(kv) {
|
|
379
|
+
const out = {};
|
|
380
|
+
for (const p of kv ?? []) {
|
|
381
|
+
if (p.enabled === false || !p.key) continue;
|
|
382
|
+
(out[p.key] ??= []).push(p.value);
|
|
383
|
+
}
|
|
384
|
+
return Object.keys(out).length ? out : void 0;
|
|
385
|
+
}
|
|
386
|
+
function parseJson(s) {
|
|
387
|
+
if (!s || !s.trim()) return void 0;
|
|
388
|
+
try {
|
|
389
|
+
return JSON.parse(s);
|
|
390
|
+
} catch {
|
|
391
|
+
return void 0;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function typeMatchingRules(body) {
|
|
395
|
+
const rules = {};
|
|
396
|
+
const walk = (node, path2) => {
|
|
397
|
+
if (Array.isArray(node)) {
|
|
398
|
+
rules[path2] = { matchers: [{ match: "type", min: node.length > 0 ? 1 : 0 }] };
|
|
399
|
+
if (node.length > 0) walk(node[0], `${path2}[*]`);
|
|
400
|
+
} else if (node !== null && typeof node === "object") {
|
|
401
|
+
for (const [k, v] of Object.entries(node)) walk(v, `${path2}.${k}`);
|
|
402
|
+
} else {
|
|
403
|
+
rules[path2] = { matchers: [{ match: "type" }] };
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
walk(body, "$");
|
|
407
|
+
return rules;
|
|
408
|
+
}
|
|
409
|
+
function scalarFromName(name) {
|
|
410
|
+
switch (name.toLowerCase()) {
|
|
411
|
+
case "integer":
|
|
412
|
+
case "int":
|
|
413
|
+
return { kind: "scalar", type: "integer" };
|
|
414
|
+
case "number":
|
|
415
|
+
case "float":
|
|
416
|
+
case "double":
|
|
417
|
+
case "decimal":
|
|
418
|
+
return { kind: "scalar", type: "number" };
|
|
419
|
+
case "boolean":
|
|
420
|
+
case "bool":
|
|
421
|
+
return { kind: "scalar", type: "boolean" };
|
|
422
|
+
case "null":
|
|
423
|
+
return { kind: "scalar", type: "null" };
|
|
424
|
+
default:
|
|
425
|
+
return { kind: "scalar", type: "string" };
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function tokenizeShape(src) {
|
|
429
|
+
const tokens = [];
|
|
430
|
+
const re = /\s*([{}[\]:,]|[A-Za-z0-9_$-]+|"[^"]*")\s*/y;
|
|
431
|
+
let i = 0;
|
|
432
|
+
while (i < src.length) {
|
|
433
|
+
re.lastIndex = i;
|
|
434
|
+
const m = re.exec(src);
|
|
435
|
+
if (!m || m.index !== i) return void 0;
|
|
436
|
+
tokens.push(m[1].startsWith('"') ? m[1].slice(1, -1) : m[1]);
|
|
437
|
+
i = re.lastIndex;
|
|
438
|
+
}
|
|
439
|
+
return tokens;
|
|
440
|
+
}
|
|
441
|
+
function parseShape(src) {
|
|
442
|
+
if (!src || !src.trim()) return void 0;
|
|
443
|
+
const tokens = tokenizeShape(src);
|
|
444
|
+
if (!tokens || tokens.length === 0) return void 0;
|
|
445
|
+
let pos = 0;
|
|
446
|
+
const peek = () => tokens[pos];
|
|
447
|
+
const structural = /* @__PURE__ */ new Set(["{", "}", "[", "]", ":", ","]);
|
|
448
|
+
const parseValue = () => {
|
|
449
|
+
const tok = peek();
|
|
450
|
+
if (tok === "{") return parseObject();
|
|
451
|
+
if (tok === "[") return parseArray();
|
|
452
|
+
if (tok === void 0 || structural.has(tok)) return void 0;
|
|
453
|
+
pos++;
|
|
454
|
+
return scalarFromName(tok);
|
|
455
|
+
};
|
|
456
|
+
const parseObject = () => {
|
|
457
|
+
pos++;
|
|
458
|
+
const fields = [];
|
|
459
|
+
while (peek() !== void 0 && peek() !== "}") {
|
|
460
|
+
const name = peek();
|
|
461
|
+
if (structural.has(name)) return void 0;
|
|
462
|
+
pos++;
|
|
463
|
+
let shape2;
|
|
464
|
+
if (peek() === ":") {
|
|
465
|
+
pos++;
|
|
466
|
+
const s = parseValue();
|
|
467
|
+
if (!s) return void 0;
|
|
468
|
+
shape2 = s;
|
|
469
|
+
} else {
|
|
470
|
+
shape2 = { kind: "scalar", type: "string" };
|
|
471
|
+
}
|
|
472
|
+
fields.push({ name, shape: shape2 });
|
|
473
|
+
if (peek() === ",") pos++;
|
|
474
|
+
}
|
|
475
|
+
if (peek() !== "}") return void 0;
|
|
476
|
+
pos++;
|
|
477
|
+
return { kind: "object", fields };
|
|
478
|
+
};
|
|
479
|
+
const parseArray = () => {
|
|
480
|
+
pos++;
|
|
481
|
+
let item;
|
|
482
|
+
if (peek() === "]") {
|
|
483
|
+
item = { kind: "scalar", type: "string" };
|
|
484
|
+
} else {
|
|
485
|
+
const s = parseValue();
|
|
486
|
+
if (!s) return void 0;
|
|
487
|
+
item = s;
|
|
488
|
+
}
|
|
489
|
+
if (peek() !== "]") return void 0;
|
|
490
|
+
pos++;
|
|
491
|
+
return { kind: "array", item };
|
|
492
|
+
};
|
|
493
|
+
const shape = parseValue();
|
|
494
|
+
if (!shape || pos !== tokens.length) return void 0;
|
|
495
|
+
return shape;
|
|
496
|
+
}
|
|
497
|
+
function compileShape(shape) {
|
|
498
|
+
const rules = {};
|
|
499
|
+
const build = (s, path2) => {
|
|
500
|
+
if (s.kind === "array") {
|
|
501
|
+
rules[path2] = { matchers: [{ match: "type", min: 1 }] };
|
|
502
|
+
return [build(s.item, `${path2}[*]`)];
|
|
503
|
+
}
|
|
504
|
+
if (s.kind === "object") {
|
|
505
|
+
const obj = {};
|
|
506
|
+
for (const f of s.fields) obj[f.name] = build(f.shape, `${path2}.${f.name}`);
|
|
507
|
+
return obj;
|
|
508
|
+
}
|
|
509
|
+
switch (s.type) {
|
|
510
|
+
case "integer":
|
|
511
|
+
rules[path2] = { matchers: [{ match: "integer" }] };
|
|
512
|
+
return 0;
|
|
513
|
+
case "number":
|
|
514
|
+
rules[path2] = { matchers: [{ match: "number" }] };
|
|
515
|
+
return 0;
|
|
516
|
+
case "boolean":
|
|
517
|
+
rules[path2] = { matchers: [{ match: "boolean" }] };
|
|
518
|
+
return false;
|
|
519
|
+
case "null":
|
|
520
|
+
rules[path2] = { matchers: [{ match: "null" }] };
|
|
521
|
+
return null;
|
|
522
|
+
default:
|
|
523
|
+
rules[path2] = { matchers: [{ match: "type" }] };
|
|
524
|
+
return "string";
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
const body = build(shape, "$");
|
|
528
|
+
return { body, rules };
|
|
529
|
+
}
|
|
530
|
+
function compileResponseBody(text, loose = true) {
|
|
531
|
+
if (!text || !text.trim()) return void 0;
|
|
532
|
+
try {
|
|
533
|
+
const json = JSON.parse(text);
|
|
534
|
+
const rules = loose ? typeMatchingRules(json) : {};
|
|
535
|
+
return { body: json, rules: Object.keys(rules).length ? rules : void 0 };
|
|
536
|
+
} catch {
|
|
537
|
+
}
|
|
538
|
+
const shape = parseShape(text);
|
|
539
|
+
if (shape) {
|
|
540
|
+
const { body, rules } = compileShape(shape);
|
|
541
|
+
return { body, rules: Object.keys(rules).length ? rules : void 0 };
|
|
542
|
+
}
|
|
543
|
+
return void 0;
|
|
544
|
+
}
|
|
545
|
+
function interactionToPact(it) {
|
|
546
|
+
const request = {
|
|
547
|
+
method: (it.request.method || "GET").toUpperCase(),
|
|
548
|
+
path: it.request.path || "/"
|
|
549
|
+
};
|
|
550
|
+
const query = kvToQuery(it.request.query);
|
|
551
|
+
if (query) request["query"] = query;
|
|
552
|
+
const reqHeaders = kvToHeaders(it.request.headers);
|
|
553
|
+
if (reqHeaders) request["headers"] = reqHeaders;
|
|
554
|
+
const reqBody = parseJson(it.request.body);
|
|
555
|
+
if (reqBody !== void 0) request["body"] = reqBody;
|
|
556
|
+
const response = { status: it.response.status };
|
|
557
|
+
const respHeaders = kvToHeaders(it.response.headers);
|
|
558
|
+
if (respHeaders) response["headers"] = respHeaders;
|
|
559
|
+
const respBody = compileResponseBody(it.response.body, it.looseMatch !== false);
|
|
560
|
+
if (respBody) {
|
|
561
|
+
response["body"] = respBody.body;
|
|
562
|
+
if (respBody.rules) response["matchingRules"] = { body: respBody.rules };
|
|
563
|
+
}
|
|
564
|
+
const interaction = {
|
|
565
|
+
type: V4_SYNC_HTTP,
|
|
566
|
+
key: interactionKey(`${(it.request.method || "GET").toUpperCase()}|${it.request.path || "/"}|${it.response.status}|${it.description ?? ""}`),
|
|
567
|
+
description: it.description || `${it.request.method} ${it.request.path}`,
|
|
568
|
+
request,
|
|
569
|
+
response
|
|
570
|
+
};
|
|
571
|
+
if (it.providerState?.trim()) interaction["providerStates"] = [{ name: it.providerState.trim() }];
|
|
572
|
+
return interaction;
|
|
573
|
+
}
|
|
574
|
+
function designContractToPact(cc) {
|
|
575
|
+
return {
|
|
576
|
+
consumer: { name: cc.consumer },
|
|
577
|
+
provider: { name: cc.provider },
|
|
578
|
+
interactions: cc.interactions.map(interactionToPact),
|
|
579
|
+
metadata: {
|
|
580
|
+
pactSpecification: { version: "4.0" },
|
|
581
|
+
client: { name: "api-spector", designFirst: true }
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
async function loadDesignContractRequests(workspace, dir) {
|
|
586
|
+
const out = [];
|
|
587
|
+
const seen = /* @__PURE__ */ new Set();
|
|
588
|
+
const add = (requests) => {
|
|
589
|
+
for (const r of requests) {
|
|
590
|
+
const key = `${r.method} ${r.url} ${r.name}`;
|
|
591
|
+
if (seen.has(key)) continue;
|
|
592
|
+
seen.add(key);
|
|
593
|
+
out.push(r);
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
for (const cc of workspace.designContracts ?? []) {
|
|
597
|
+
try {
|
|
598
|
+
add(importPact(designContractToPact(cc)).requests);
|
|
599
|
+
} catch {
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (dir) {
|
|
603
|
+
let files = [];
|
|
604
|
+
try {
|
|
605
|
+
files = (await promises.readdir(path.join(dir, "pacts"))).filter((f) => f.endsWith(".json"));
|
|
606
|
+
} catch {
|
|
607
|
+
}
|
|
608
|
+
for (const f of files.sort()) {
|
|
609
|
+
try {
|
|
610
|
+
add(importPact(await promises.readFile(path.join(dir, "pacts", f), "utf8")).requests);
|
|
611
|
+
} catch {
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return out;
|
|
616
|
+
}
|
|
71
617
|
const ajv$2 = new Ajv({ allErrors: true, strict: false });
|
|
72
618
|
function hasContract(contract) {
|
|
73
619
|
return !!contract && (contract.statusCode !== void 0 || !!contract.bodySchema || !!contract.bodyMatcher || !!contract.headers?.length);
|
|
@@ -1636,17 +2182,21 @@ async function deleteSnapshot(workspaceDir, relPath) {
|
|
|
1636
2182
|
} catch {
|
|
1637
2183
|
}
|
|
1638
2184
|
}
|
|
1639
|
-
exports.MATCH_KEY = MATCH_KEY;
|
|
1640
2185
|
exports.canIDeploy = canIDeploy;
|
|
1641
2186
|
exports.captureSnapshot = captureSnapshot;
|
|
1642
2187
|
exports.dashboardToHtml = dashboardToHtml;
|
|
1643
2188
|
exports.deleteSnapshot = deleteSnapshot;
|
|
2189
|
+
exports.designContractToPact = designContractToPact;
|
|
2190
|
+
exports.exportPact = exportPact;
|
|
1644
2191
|
exports.fuzzReportToHtml = fuzzReportToHtml;
|
|
1645
2192
|
exports.hasContract = hasContract;
|
|
2193
|
+
exports.importPact = importPact;
|
|
1646
2194
|
exports.listEnvironments = listEnvironments;
|
|
1647
2195
|
exports.listResults = listResults;
|
|
1648
2196
|
exports.listSnapshots = listSnapshots;
|
|
2197
|
+
exports.loadDesignContractRequests = loadDesignContractRequests;
|
|
1649
2198
|
exports.loadSnapshot = loadSnapshot;
|
|
2199
|
+
exports.pactToCollection = pactToCollection;
|
|
1650
2200
|
exports.recordDeployment = recordDeployment;
|
|
1651
2201
|
exports.recordResult = recordResult;
|
|
1652
2202
|
exports.relPathOf = relPathOf;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const handle = require("./handle-
|
|
2
|
+
const handle = require("./handle-rimXXdJH.js");
|
|
3
3
|
const https = require("https");
|
|
4
4
|
const http = require("http");
|
|
5
5
|
const xmldom = require("@xmldom/xmldom");
|
|
@@ -315,7 +315,7 @@ function registerSoapHandlers(ipc) {
|
|
|
315
315
|
handle.handleIpc(ipc, handle.IPC.wsdl.import, async (_event, opts) => {
|
|
316
316
|
const { validateWsdlImport } = await Promise.resolve().then(() => require("./ipc-validate-k6KI8adf.js"));
|
|
317
317
|
validateWsdlImport(opts);
|
|
318
|
-
const { importWsdl } = await Promise.resolve().then(() => require("./import-
|
|
318
|
+
const { importWsdl } = await Promise.resolve().then(() => require("./import-DcenB5Q_.js"));
|
|
319
319
|
const wsdlText = opts.xml ?? (opts.url ? await fetchUrl(opts.url) : "");
|
|
320
320
|
if (!wsdlText) throw new Error("wsdl:import requires either `url` or `xml`");
|
|
321
321
|
return importWsdl(wsdlText, { name: opts.name, existingMockPorts: opts.existingMockPorts });
|