@ondc/automation-mock-runner 1.3.42 → 1.3.44
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/dist/lib/MockRunner.d.ts +12 -2
- package/dist/lib/MockRunner.js +53 -13
- package/dist/lib/configHelper.js +21 -64
- package/dist/lib/constants/function-registry.js +1 -1
- package/dist/lib/helpers/default-helpers.d.ts +13 -0
- package/dist/lib/helpers/default-helpers.js +173 -0
- package/dist/lib/helpers/default-helpers.test.d.ts +9 -0
- package/dist/lib/helpers/default-helpers.test.js +257 -0
- package/dist/lib/helpers/index.d.ts +1 -0
- package/dist/lib/helpers/index.js +47 -0
- package/dist/lib/runners/node-runner.d.ts +15 -6
- package/dist/lib/runners/node-runner.js +4 -0
- package/dist/lib/runners/runner-factory.d.ts +3 -2
- package/dist/lib/validators/code-validator.js +3 -1
- package/dist/test/GenerateFetchAndTimeout.test.d.ts +4 -0
- package/dist/test/GenerateFetchAndTimeout.test.js +240 -0
- package/dist/test/MockRunner.test.js +64 -0
- package/package.json +2 -2
- package/public/node-worker.js +98 -6
|
@@ -655,4 +655,68 @@ describe("MockRunner", () => {
|
|
|
655
655
|
expect(result.result.message.discountPercent).toBe(20);
|
|
656
656
|
});
|
|
657
657
|
});
|
|
658
|
+
describe("baseActionId extraction (GENERATED# prefix support)", () => {
|
|
659
|
+
let prefixedRunner;
|
|
660
|
+
beforeEach(async () => {
|
|
661
|
+
const baseConfig = {
|
|
662
|
+
meta: {
|
|
663
|
+
domain: "ONDC:TRV14",
|
|
664
|
+
version: "2.0.0",
|
|
665
|
+
flowId: "prefix-test",
|
|
666
|
+
},
|
|
667
|
+
transaction_data: {
|
|
668
|
+
transaction_id: "prefix-test-txn-id",
|
|
669
|
+
latest_timestamp: "1970-01-01T00:00:00.000Z",
|
|
670
|
+
},
|
|
671
|
+
steps: [],
|
|
672
|
+
transaction_history: [],
|
|
673
|
+
validationLib: "",
|
|
674
|
+
helperLib: "",
|
|
675
|
+
};
|
|
676
|
+
const base = new MockRunner_1.MockRunner(baseConfig, true);
|
|
677
|
+
base.getConfig().steps.push(base.getDefaultStep("search", "search_0"));
|
|
678
|
+
prefixedRunner = new MockRunner_1.MockRunner(await (0, configHelper_1.createOptimizedMockConfig)(base.getConfig()), true);
|
|
679
|
+
});
|
|
680
|
+
it("runGeneratePayload: GENERATED#1#search_0 resolves to search_0", async () => {
|
|
681
|
+
const result = await prefixedRunner.runGeneratePayload("GENERATED#1#search_0", {});
|
|
682
|
+
expect(result.success).toBe(true);
|
|
683
|
+
});
|
|
684
|
+
it("runGeneratePayload: plain search_0 still works", async () => {
|
|
685
|
+
const result = await prefixedRunner.runGeneratePayload("search_0", {});
|
|
686
|
+
expect(result.success).toBe(true);
|
|
687
|
+
});
|
|
688
|
+
it("runGeneratePayloadWithSession: GENERATED#1#search_0 resolves to search_0", async () => {
|
|
689
|
+
const result = await prefixedRunner.runGeneratePayloadWithSession("GENERATED#1#search_0", { transaction_id: "some-txn" });
|
|
690
|
+
expect(result.success).toBe(true);
|
|
691
|
+
});
|
|
692
|
+
it("runValidatePayload: GENERATED#1#search_0 resolves to search_0", async () => {
|
|
693
|
+
const payload = {
|
|
694
|
+
context: { domain: "ONDC:TRV14", action: "search" },
|
|
695
|
+
message: {},
|
|
696
|
+
};
|
|
697
|
+
const result = await prefixedRunner.runValidatePayload("GENERATED#1#search_0", payload);
|
|
698
|
+
expect(result.success).toBe(true);
|
|
699
|
+
});
|
|
700
|
+
it("runValidatePayloadWithSession: GENERATED#1#search_0 resolves to search_0", async () => {
|
|
701
|
+
const result = await prefixedRunner.runValidatePayloadWithSession("GENERATED#1#search_0", { context: {}, message: {} }, {});
|
|
702
|
+
expect(result.success).toBe(true);
|
|
703
|
+
});
|
|
704
|
+
it("runMeetRequirements: GENERATED#1#search_0 resolves to search_0", async () => {
|
|
705
|
+
const result = await prefixedRunner.runMeetRequirements("GENERATED#1#search_0");
|
|
706
|
+
expect(result.success).toBe(true);
|
|
707
|
+
});
|
|
708
|
+
it("runMeetRequirementsWithSession: GENERATED#1#search_0 resolves to search_0", async () => {
|
|
709
|
+
const result = await prefixedRunner.runMeetRequirementsWithSession("GENERATED#1#search_0", {});
|
|
710
|
+
expect(result.success).toBe(true);
|
|
711
|
+
});
|
|
712
|
+
it("unknown prefixed action ID returns failure", async () => {
|
|
713
|
+
const result = await prefixedRunner.runGeneratePayload("GENERATED#1#unknown_action", {});
|
|
714
|
+
expect(result.success).toBe(false);
|
|
715
|
+
expect(result.error?.message).toContain("unknown_action");
|
|
716
|
+
});
|
|
717
|
+
it("deeply prefixed ID (multiple #) uses only the last segment", async () => {
|
|
718
|
+
const result = await prefixedRunner.runGeneratePayload("PREFIX#ANOTHER#search_0", {});
|
|
719
|
+
expect(result.success).toBe(true);
|
|
720
|
+
});
|
|
721
|
+
});
|
|
658
722
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ondc/automation-mock-runner",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.44",
|
|
4
4
|
"description": "A TypeScript library for ONDC automation mock runner",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"author": "ONDC Development Team <dev@ondc.org>",
|
|
46
46
|
"license": "ISC",
|
|
47
47
|
"engines": {
|
|
48
|
-
"node": ">=
|
|
48
|
+
"node": ">=18.0.0"
|
|
49
49
|
},
|
|
50
50
|
"repository": {
|
|
51
51
|
"type": "git",
|
package/public/node-worker.js
CHANGED
|
@@ -1,8 +1,73 @@
|
|
|
1
|
-
const { parentPort } = require("worker_threads");
|
|
1
|
+
const { parentPort, workerData } = require("worker_threads");
|
|
2
2
|
const vm = require("vm");
|
|
3
3
|
|
|
4
|
+
const ALLOWED_FETCH_BASE_URLS = Array.isArray(workerData?.allowedFetchBaseUrls)
|
|
5
|
+
? workerData.allowedFetchBaseUrls
|
|
6
|
+
: [];
|
|
7
|
+
|
|
8
|
+
// Parse + normalize allowlist entries once per worker.
|
|
9
|
+
// Each entry contributes { origin, pathname } where pathname has no trailing
|
|
10
|
+
// slash; matching requires request.origin === entry.origin AND the request
|
|
11
|
+
// pathname is a strict segment-prefix of entry.pathname (so `/v1` matches
|
|
12
|
+
// `/v1` and `/v1/foo` but NOT `/v10/foo`).
|
|
13
|
+
const PARSED_ALLOWLIST = ALLOWED_FETCH_BASE_URLS.map((raw) => {
|
|
14
|
+
try {
|
|
15
|
+
const u = new URL(raw);
|
|
16
|
+
let pathname = u.pathname;
|
|
17
|
+
if (pathname.endsWith("/") && pathname !== "/") {
|
|
18
|
+
pathname = pathname.slice(0, -1);
|
|
19
|
+
}
|
|
20
|
+
return { origin: u.origin, pathname };
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}).filter(Boolean);
|
|
25
|
+
|
|
26
|
+
function isFetchAllowed(requestUrl) {
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = new URL(requestUrl);
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
let reqPath = parsed.pathname;
|
|
34
|
+
if (reqPath.endsWith("/") && reqPath !== "/") {
|
|
35
|
+
reqPath = reqPath.slice(0, -1);
|
|
36
|
+
}
|
|
37
|
+
for (const entry of PARSED_ALLOWLIST) {
|
|
38
|
+
if (parsed.origin !== entry.origin) continue;
|
|
39
|
+
if (entry.pathname === "" || entry.pathname === "/") return true;
|
|
40
|
+
if (reqPath === entry.pathname) return true;
|
|
41
|
+
if (reqPath.startsWith(entry.pathname + "/")) return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function makeScopedFetch() {
|
|
47
|
+
if (typeof globalThis.fetch !== "function") {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
if (PARSED_ALLOWLIST.length === 0) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return async function scopedFetch(input, init) {
|
|
54
|
+
const requestUrl =
|
|
55
|
+
typeof input === "string"
|
|
56
|
+
? input
|
|
57
|
+
: input && typeof input.url === "string"
|
|
58
|
+
? input.url
|
|
59
|
+
: String(input);
|
|
60
|
+
if (!isFetchAllowed(requestUrl)) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`fetch blocked: ${requestUrl} is not in the configured allowlist`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return globalThis.fetch(input, { ...(init || {}), redirect: "error" });
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
4
69
|
// Create a secure sandbox context
|
|
5
|
-
function createSandbox() {
|
|
70
|
+
function createSandbox(functionName) {
|
|
6
71
|
const logs = [];
|
|
7
72
|
|
|
8
73
|
// Safe console implementation that captures logs
|
|
@@ -108,12 +173,17 @@ function createSandbox() {
|
|
|
108
173
|
decodeURIComponent,
|
|
109
174
|
// Utility functions for ONDC operations
|
|
110
175
|
setTimeout: (fn, delay) => {
|
|
111
|
-
if (delay < 1 || delay >
|
|
112
|
-
throw new Error("Timeout must be between 1-
|
|
176
|
+
if (delay < 1 || delay > 45 * 1000) {
|
|
177
|
+
throw new Error("Timeout must be between 1-45000ms");
|
|
113
178
|
}
|
|
114
179
|
return setTimeout(fn, delay);
|
|
115
180
|
},
|
|
116
181
|
clearTimeout,
|
|
182
|
+
// AbortController is a pure control-flow primitive with no I/O of its
|
|
183
|
+
// own — safe to expose unconditionally. Needed by helpers that pair
|
|
184
|
+
// `fetch` with a timeout (see generateConsentHandler).
|
|
185
|
+
AbortController,
|
|
186
|
+
AbortSignal,
|
|
117
187
|
// Blocked globals
|
|
118
188
|
require: undefined,
|
|
119
189
|
process: undefined,
|
|
@@ -128,6 +198,28 @@ function createSandbox() {
|
|
|
128
198
|
Function: undefined,
|
|
129
199
|
};
|
|
130
200
|
|
|
201
|
+
// Only `generate` gets outbound HTTP — validate/meetsRequirements/getSave
|
|
202
|
+
// stay pure. Fetch itself is still gated by the allowlist inside the wrapper.
|
|
203
|
+
if (functionName === "generate") {
|
|
204
|
+
const scopedFetch = makeScopedFetch();
|
|
205
|
+
if (scopedFetch) {
|
|
206
|
+
sandbox.fetch = scopedFetch;
|
|
207
|
+
if (typeof globalThis.URL === "function") sandbox.URL = globalThis.URL;
|
|
208
|
+
if (typeof globalThis.URLSearchParams === "function") {
|
|
209
|
+
sandbox.URLSearchParams = globalThis.URLSearchParams;
|
|
210
|
+
}
|
|
211
|
+
if (typeof globalThis.Headers === "function") {
|
|
212
|
+
sandbox.Headers = globalThis.Headers;
|
|
213
|
+
}
|
|
214
|
+
if (typeof globalThis.Request === "function") {
|
|
215
|
+
sandbox.Request = globalThis.Request;
|
|
216
|
+
}
|
|
217
|
+
if (typeof globalThis.Response === "function") {
|
|
218
|
+
sandbox.Response = globalThis.Response;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
131
223
|
return { sandbox, logs };
|
|
132
224
|
}
|
|
133
225
|
|
|
@@ -138,7 +230,7 @@ parentPort?.on("message", async (message) => {
|
|
|
138
230
|
|
|
139
231
|
try {
|
|
140
232
|
// Create fresh sandbox for each execution
|
|
141
|
-
const { sandbox, logs } = createSandbox();
|
|
233
|
+
const { sandbox, logs } = createSandbox(functionName);
|
|
142
234
|
|
|
143
235
|
// Create VM context with timeout
|
|
144
236
|
const context = vm.createContext(sandbox);
|
|
@@ -151,7 +243,7 @@ parentPort?.on("message", async (message) => {
|
|
|
151
243
|
|
|
152
244
|
// Execute the script
|
|
153
245
|
script.runInContext(context, {
|
|
154
|
-
timeout: timeout ||
|
|
246
|
+
timeout: timeout || 45000,
|
|
155
247
|
breakOnSigint: true,
|
|
156
248
|
});
|
|
157
249
|
|