@ondc/automation-mock-runner 1.3.52 → 1.3.54

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.
@@ -617,11 +617,19 @@ class MockRunner {
617
617
  }
618
618
  static encodeBase64(input) {
619
619
  const bytes = new TextEncoder().encode(input);
620
- return btoa(String.fromCharCode(...bytes));
620
+ const CHUNK = 0x8000;
621
+ let binary = "";
622
+ for (let i = 0; i < bytes.length; i += CHUNK) {
623
+ binary += String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK));
624
+ }
625
+ return btoa(binary);
621
626
  }
622
627
  static decodeBase64(encoded) {
623
628
  const binaryString = atob(encoded);
624
- const bytes = new Uint8Array([...binaryString].map((char) => char.charCodeAt(0)));
629
+ const bytes = new Uint8Array(binaryString.length);
630
+ for (let i = 0; i < binaryString.length; i++) {
631
+ bytes[i] = binaryString.charCodeAt(i);
632
+ }
625
633
  return new TextDecoder().decode(bytes);
626
634
  }
627
635
  static resolveBaseActionId(actionId) {
@@ -40,117 +40,118 @@ function createInitialMockConfig(domain, version, flowId) {
40
40
  helperLib: MockRunner_1.MockRunner.encodeBase64(helpers_1.DEFAULT_HELPER_LIB),
41
41
  };
42
42
  }
43
- function convertToFlowConfig(config) {
44
- const flowConfig = {};
45
- flowConfig.id = config.meta.flowId;
46
- flowConfig.description = "";
47
- flowConfig.sequence = [];
48
- let index = 0;
49
- for (const step of config.steps) {
50
- const pair = config.steps.find((s) => s.responseFor === step.action_id)?.action_id ||
51
- null;
52
- let flowStep = {};
53
- const isFormStep = [
54
- "HTML_FORM",
55
- "DYNAMIC_FORM",
56
- "HTML_FORM_MULTI",
57
- "dynamic_form",
58
- "html_form",
59
- ];
60
- // Check if previous step was a form step
61
- const previousStep = index > 0 ? config.steps[index - 1] : null;
62
- const isPreviousStepForm = previousStep !== null && isFormStep.includes(previousStep.api);
63
- // Check if current step has no inputs
64
- const hasNoInputs = step.mock.inputs === undefined ||
65
- step.mock.inputs === null ||
66
- Object.keys(step.mock.inputs).length === 0;
67
- if (step.api === "dynamic_form") {
68
- flowStep = {
69
- key: step.action_id,
70
- type: "DYNAMIC_FORM",
71
- owner: step.owner,
72
- description: step.description || "",
73
- label: step.description || "FORM",
74
- unsolicited: step.unsolicited,
75
- pair: pair,
76
- repeat: step.repeatCount || 1,
77
- input: [
78
- {
79
- name: "form_submission_id",
80
- label: "Enter form submission ID",
81
- type: "DYNAMIC_FORM",
82
- payloadField: "form_submission_id",
83
- reference: `$.reference_data.${step.action_id}`,
84
- },
85
- ],
86
- };
87
- }
88
- else if (step.api === "HTML_FORM" || step.api === "html_form") {
89
- flowStep = {
90
- key: step.action_id,
91
- type: "HTML_FORM",
92
- owner: step.owner,
93
- description: step.description || "",
94
- label: step.description || "FORM",
95
- unsolicited: step.unsolicited,
96
- pair: pair,
97
- repeat: step.repeatCount || 1,
98
- input: [
99
- {
100
- name: "form_submission_id",
101
- label: "Enter form submission ID",
102
- type: "HTML_FORM",
103
- payloadField: "form_submission_id",
104
- reference: `$.reference_data.${step.action_id}`,
105
- },
106
- ],
107
- };
43
+ function buildFlowStep(step, index, steps) {
44
+ const pair = steps.find((s) => s.responseFor === step.action_id)?.action_id || null;
45
+ let flowStep = {};
46
+ const isFormStep = [
47
+ "HTML_FORM",
48
+ "DYNAMIC_FORM",
49
+ "HTML_FORM_MULTI",
50
+ "dynamic_form",
51
+ "html_form",
52
+ ];
53
+ // Check if previous step was a form step
54
+ const previousStep = index > 0 ? steps[index - 1] : null;
55
+ const isPreviousStepForm = previousStep !== null && isFormStep.includes(previousStep.api);
56
+ // Check if current step has no inputs
57
+ const hasNoInputs = step.mock.inputs === undefined ||
58
+ step.mock.inputs === null ||
59
+ Object.keys(step.mock.inputs).length === 0;
60
+ if (step.api === "dynamic_form") {
61
+ flowStep = {
62
+ key: step.action_id,
63
+ type: "DYNAMIC_FORM",
64
+ owner: step.owner,
65
+ description: step.description || "",
66
+ label: step.description || "FORM",
67
+ unsolicited: step.unsolicited,
68
+ pair: pair,
69
+ repeat: step.repeatCount || 1,
70
+ input: [
71
+ {
72
+ name: "form_submission_id",
73
+ label: "Enter form submission ID",
74
+ type: "DYNAMIC_FORM",
75
+ payloadField: "form_submission_id",
76
+ reference: `$.reference_data.${step.action_id}`,
77
+ },
78
+ ],
79
+ };
80
+ }
81
+ else if (step.api === "HTML_FORM" || step.api === "html_form") {
82
+ flowStep = {
83
+ key: step.action_id,
84
+ type: "HTML_FORM",
85
+ owner: step.owner,
86
+ description: step.description || "",
87
+ label: step.description || "FORM",
88
+ unsolicited: step.unsolicited,
89
+ pair: pair,
90
+ repeat: step.repeatCount || 1,
91
+ input: [
92
+ {
93
+ name: "form_submission_id",
94
+ label: "Enter form submission ID",
95
+ type: "HTML_FORM",
96
+ payloadField: "form_submission_id",
97
+ reference: `$.reference_data.${step.action_id}`,
98
+ },
99
+ ],
100
+ };
101
+ }
102
+ else {
103
+ flowStep = {
104
+ key: step.action_id,
105
+ type: step.api,
106
+ owner: step.owner,
107
+ description: step.description || "",
108
+ expect: index === 0 ? true : false,
109
+ unsolicited: step.unsolicited,
110
+ pair: pair,
111
+ repeat: step.repeatCount || 1,
112
+ };
113
+ }
114
+ if (step.mock.inputs !== undefined &&
115
+ step.mock.inputs !== null &&
116
+ Object.keys(step.mock.inputs).length > 0) {
117
+ if (step.mock.inputs.id == "finvu_verification") {
118
+ flowStep.input = [
119
+ {
120
+ name: "finvu_verification",
121
+ label: "Complete Account Aggregator Verification",
122
+ type: "FINVU_REDIRECT",
123
+ payloadField: "$.context.aa_consent_verified",
124
+ },
125
+ ];
108
126
  }
109
127
  else {
110
- flowStep = {
111
- key: step.action_id,
112
- type: step.api,
113
- owner: step.owner,
114
- description: step.description || "",
115
- expect: index === 0 ? true : false,
116
- unsolicited: step.unsolicited,
117
- pair: pair,
118
- repeat: step.repeatCount || 1,
119
- };
120
- }
121
- if (step.mock.inputs !== undefined &&
122
- step.mock.inputs !== null &&
123
- Object.keys(step.mock.inputs).length > 0) {
124
- if (step.mock.inputs.id == "finvu_verification") {
125
- flowStep.input = [
126
- {
127
- name: "finvu_verification",
128
- label: "Complete Account Aggregator Verification",
129
- type: "FINVU_REDIRECT",
130
- payloadField: "$.context.aa_consent_verified",
131
- },
132
- ];
133
- }
134
- else {
135
- flowStep.input = [
136
- {
137
- name: step.mock.inputs.id,
138
- type: step.mock.inputs.id,
139
- schema: step.mock.inputs.jsonSchema,
140
- },
141
- ];
142
- }
143
- }
144
- if (step.mock.inputs?.oldInputs) {
145
- flowStep.input = step.mock.inputs.oldInputs;
128
+ flowStep.input = [
129
+ {
130
+ name: step.mock.inputs.id,
131
+ type: step.mock.inputs.id,
132
+ schema: step.mock.inputs.jsonSchema,
133
+ },
134
+ ];
146
135
  }
147
- // Add force_proceed if previous step was a form and current step has no inputs
148
- if (isPreviousStepForm && hasNoInputs) {
149
- flowStep.force_proceed = true;
150
- }
151
- flowConfig.sequence.push(flowStep);
152
- index++;
153
136
  }
137
+ if (step.mock.inputs?.oldInputs) {
138
+ flowStep.input = step.mock.inputs.oldInputs;
139
+ }
140
+ // Add force_proceed if previous step was a form and current step has no inputs
141
+ if (isPreviousStepForm && hasNoInputs) {
142
+ flowStep.force_proceed = true;
143
+ }
144
+ return flowStep;
145
+ }
146
+ function buildFlowSequence(steps) {
147
+ return steps.map((step, index) => buildFlowStep(step, index, steps));
148
+ }
149
+ function convertToFlowConfig(config) {
150
+ const flowConfig = {};
151
+ flowConfig.id = config.meta.flowId;
152
+ flowConfig.description = "";
153
+ flowConfig.sequence = buildFlowSequence(config.steps);
154
+ flowConfig.extraSequence = buildFlowSequence(config.extra_steps?.steps || []);
154
155
  return flowConfig;
155
156
  }
156
157
  async function createOptimizedMockConfig(config) {
@@ -22,6 +22,12 @@ export declare class CodeValidator {
22
22
  * functions/arrows are also skipped (they aren't the contract).
23
23
  * Falls back to the whole AST when no named match is found.
24
24
  */
25
+ /**
26
+ * Returns the function name the schema's template wraps user code with
27
+ * (e.g. "generate", "validate", "meetsRequirements"). Returns undefined for
28
+ * raw-code schemas like `getSave` whose template is the identity function.
29
+ */
30
+ private static getExpectedDeclarationName;
25
31
  private static collectTopLevelReturns;
26
32
  private static validateReturnStructure;
27
33
  /**
@@ -137,14 +137,29 @@ class CodeValidator {
137
137
  dangerousPatterns.forEach((pattern) => {
138
138
  errors.push(`[Line ${pattern.line}] ${pattern.message}`);
139
139
  });
140
- // 5. Validate return type structure (for validate and meetsRequirements)
141
- if (schema.returnType.properties) {
140
+ // 5. Require the top-level function declaration the schema wraps with
141
+ // (skipped for raw-code schemas like `getSave` whose template doesn't
142
+ // define a named function). Without this, a typo'd name silently
143
+ // degrades to a misleading "Function should return an object..." warning.
144
+ const expectedName = this.getExpectedDeclarationName(schema);
145
+ const hasNamedDecl = expectedName
146
+ ? (ast.body || []).some((n) => n.type === "FunctionDeclaration" && n.id?.name === expectedName)
147
+ : true;
148
+ if (expectedName && !hasNamedDecl) {
149
+ errors.push(`Expected a top-level function declaration named '${expectedName}' (check for typos or a missing/wrapping function)`);
150
+ }
151
+ // 6. Validate return type structure (for validate and meetsRequirements).
152
+ // Skipped when the named declaration is missing — the message would
153
+ // duplicate the clearer error above.
154
+ if (schema.returnType.properties && hasNamedDecl) {
142
155
  const returnValidation = this.validateReturnStructure(ast, schema.returnType.properties, schema.name);
143
156
  errors.push(...returnValidation);
144
157
  }
145
- // 6. Check for best practices
146
- const practiceWarnings = this.checkBestPractices(ast, schema);
147
- warnings.push(...practiceWarnings);
158
+ // 7. Check for best practices (skipped when declaration is missing)
159
+ if (hasNamedDecl) {
160
+ const practiceWarnings = this.checkBestPractices(ast, schema);
161
+ warnings.push(...practiceWarnings);
162
+ }
148
163
  }
149
164
  catch (e) {
150
165
  // Syntax error during parsing
@@ -179,6 +194,27 @@ class CodeValidator {
179
194
  * functions/arrows are also skipped (they aren't the contract).
180
195
  * Falls back to the whole AST when no named match is found.
181
196
  */
197
+ /**
198
+ * Returns the function name the schema's template wraps user code with
199
+ * (e.g. "generate", "validate", "meetsRequirements"). Returns undefined for
200
+ * raw-code schemas like `getSave` whose template is the identity function.
201
+ */
202
+ static getExpectedDeclarationName(schema) {
203
+ try {
204
+ const ast = acorn.parse(schema.template(""), {
205
+ ecmaVersion: 2020,
206
+ sourceType: "script",
207
+ });
208
+ const body = Array.isArray(ast.body)
209
+ ? ast.body
210
+ : [];
211
+ const fn = body.find((n) => n.type === "FunctionDeclaration" && n.id?.name === schema.name);
212
+ return fn ? schema.name : undefined;
213
+ }
214
+ catch {
215
+ return undefined;
216
+ }
217
+ }
182
218
  static collectTopLevelReturns(ast, targetFnName) {
183
219
  const returns = [];
184
220
  const programBody = Array.isArray(ast.body)
@@ -117,7 +117,7 @@ describe("BrowserRunner", () => {
117
117
  const result = await browserRunner.execute(wrongFunctionCode, schema, args);
118
118
  expect(result.success).toBe(false);
119
119
  expect(result.error).toBeDefined();
120
- expect(result.error.message).toContain("generate is not defined");
120
+ expect(result.error.message).toContain("Expected a top-level function declaration named 'generate'");
121
121
  });
122
122
  it("should handle runtime errors in functions", async () => {
123
123
  const errorCode = `
@@ -124,3 +124,325 @@ describe("CodeValidator.validate — return structure", () => {
124
124
  expect(result.warnings.some((w) => w.includes("should return a value"))).toBe(true);
125
125
  });
126
126
  });
127
+ describe("CodeValidator.validate — meetsRequirements return structure", () => {
128
+ it("accepts an outer return with the full expected shape", () => {
129
+ const code = `
130
+ function meetsRequirements(sessionData) {
131
+ return { valid: true, code: 200, description: "Requirements met" };
132
+ }
133
+ `;
134
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
135
+ expect(result.isValid).toBe(true);
136
+ expect(result.errors).toEqual([]);
137
+ });
138
+ it("ignores nested arrow helper returns inside meetsRequirements", () => {
139
+ const code = `
140
+ function meetsRequirements(sessionData) {
141
+ const ok = (x) => { return x.length > 0; };
142
+ const ids = (sessionData.items || []).filter(i => { return i.id; });
143
+ return { valid: ok("hi"), code: 200, description: "Requirements met" };
144
+ }
145
+ `;
146
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
147
+ expect(result.isValid).toBe(true);
148
+ expect(result.errors).toEqual([]);
149
+ });
150
+ it("ignores nested function declaration returns inside meetsRequirements", () => {
151
+ const code = `
152
+ function meetsRequirements(sessionData) {
153
+ function buildMsg(x) { return "req: " + x; }
154
+ return { valid: false, code: 400, description: buildMsg("nope") };
155
+ }
156
+ `;
157
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
158
+ expect(result.isValid).toBe(true);
159
+ expect(result.errors).toEqual([]);
160
+ });
161
+ it("flags missing 'description' on the outer return", () => {
162
+ const code = `
163
+ function meetsRequirements(sessionData) {
164
+ return { valid: true, code: 200 };
165
+ }
166
+ `;
167
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
168
+ expect(result.isValid).toBe(false);
169
+ expect(result.errors.some((e) => e.includes("description"))).toBe(true);
170
+ });
171
+ it("flags missing 'valid' on the outer return", () => {
172
+ const code = `
173
+ function meetsRequirements(sessionData) {
174
+ return { code: 200, description: "ok" };
175
+ }
176
+ `;
177
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
178
+ expect(result.isValid).toBe(false);
179
+ expect(result.errors.some((e) => e.includes("valid"))).toBe(true);
180
+ });
181
+ it("flags missing 'code' on the outer return", () => {
182
+ const code = `
183
+ function meetsRequirements(sessionData) {
184
+ return { valid: true, description: "ok" };
185
+ }
186
+ `;
187
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
188
+ expect(result.isValid).toBe(false);
189
+ expect(result.errors.some((e) => e.includes("code"))).toBe(true);
190
+ });
191
+ it("flags an outer return that is not an object literal (boolean)", () => {
192
+ const code = `
193
+ function meetsRequirements(sessionData) {
194
+ return true;
195
+ }
196
+ `;
197
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
198
+ expect(result.isValid).toBe(false);
199
+ expect(result.errors.some((e) => e.includes("Function should return an object literal"))).toBe(true);
200
+ });
201
+ it("flags an outer return that is not an object literal (string)", () => {
202
+ const code = `
203
+ function meetsRequirements(sessionData) {
204
+ return "ok";
205
+ }
206
+ `;
207
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
208
+ expect(result.isValid).toBe(false);
209
+ expect(result.errors.some((e) => e.includes("Function should return an object literal"))).toBe(true);
210
+ });
211
+ it("accepts a minified conditional return: return c ? {...} : {...}", () => {
212
+ const code = `function meetsRequirements(s){return s.ok?{valid:!0,code:200,description:"Requirements met"}:{valid:!1,code:400,description:"not met"}}`;
213
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
214
+ expect(result.errors).toEqual([]);
215
+ expect(result.isValid).toBe(true);
216
+ });
217
+ it("accepts multiple return paths inside if/else, both objects", () => {
218
+ const code = `
219
+ function meetsRequirements(sessionData) {
220
+ if (!sessionData) {
221
+ return { valid: false, code: 400, description: "no session" };
222
+ }
223
+ if (sessionData.bad) {
224
+ return { valid: false, code: 401, description: "bad" };
225
+ }
226
+ return { valid: true, code: 200, description: "Requirements met" };
227
+ }
228
+ `;
229
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
230
+ expect(result.errors).toEqual([]);
231
+ expect(result.isValid).toBe(true);
232
+ });
233
+ it("flags when one of multiple return paths is missing a property", () => {
234
+ const code = `
235
+ function meetsRequirements(sessionData) {
236
+ if (!sessionData) {
237
+ return { valid: false, code: 400 };
238
+ }
239
+ return { valid: true, code: 200, description: "Requirements met" };
240
+ }
241
+ `;
242
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
243
+ expect(result.isValid).toBe(false);
244
+ expect(result.errors.some((e) => e.includes("description"))).toBe(true);
245
+ });
246
+ it("flags when one of multiple return paths is not an object", () => {
247
+ const code = `
248
+ function meetsRequirements(sessionData) {
249
+ if (!sessionData) {
250
+ return false;
251
+ }
252
+ return { valid: true, code: 200, description: "Requirements met" };
253
+ }
254
+ `;
255
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
256
+ expect(result.isValid).toBe(false);
257
+ expect(result.errors.some((e) => e.includes("Function should return an object literal"))).toBe(true);
258
+ });
259
+ it("flags unexpected/extra properties on the outer return", () => {
260
+ const code = `
261
+ function meetsRequirements(sessionData) {
262
+ return { valid: true, code: 200, description: "ok", extra: "nope" };
263
+ }
264
+ `;
265
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
266
+ expect(result.isValid).toBe(false);
267
+ expect(result.errors.some((e) => e.includes("unexpected property"))).toBe(true);
268
+ });
269
+ it("does not warn 'should return a value' when meetsRequirements has a return", () => {
270
+ const code = `
271
+ function meetsRequirements(sessionData) {
272
+ return { valid: true, code: 200, description: "Requirements met" };
273
+ }
274
+ `;
275
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
276
+ expect(result.warnings.some((w) => w.includes("should return a value"))).toBe(false);
277
+ });
278
+ it("warns when only a nested helper returns and meetsRequirements has no return", () => {
279
+ const code = `
280
+ function meetsRequirements(sessionData) {
281
+ function helper() { return 42; }
282
+ helper();
283
+ }
284
+ `;
285
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
286
+ expect(result.warnings.some((w) => w.includes("should return a value"))).toBe(true);
287
+ });
288
+ it("ignores sibling top-level helper returns even when they return non-objects", () => {
289
+ const code = `
290
+ function helper() { return 123; }
291
+ function anotherHelper() { return "string"; }
292
+ function meetsRequirements(sessionData) {
293
+ const a = helper();
294
+ const b = anotherHelper();
295
+ return { valid: a > 0 && !!b, code: 200, description: "Requirements met" };
296
+ }
297
+ `;
298
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
299
+ expect(result.errors).toEqual([]);
300
+ expect(result.warnings).toEqual([]);
301
+ expect(result.isValid).toBe(true);
302
+ });
303
+ it("ignores returns inside try/catch helpers nested in meetsRequirements", () => {
304
+ const code = `
305
+ function meetsRequirements(sessionData) {
306
+ const safe = (fn) => {
307
+ try { return fn(); } catch (e) { return null; }
308
+ };
309
+ const v = safe(() => sessionData.x);
310
+ return { valid: !!v, code: 200, description: "Requirements met" };
311
+ }
312
+ `;
313
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
314
+ expect(result.isValid).toBe(true);
315
+ expect(result.errors).toEqual([]);
316
+ });
317
+ it("flags forbidden global usage inside meetsRequirements", () => {
318
+ const code = `
319
+ function meetsRequirements(sessionData) {
320
+ eval("1+1");
321
+ return { valid: true, code: 200, description: "Requirements met" };
322
+ }
323
+ `;
324
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
325
+ expect(result.isValid).toBe(false);
326
+ expect(result.errors.some((e) => e.includes("eval"))).toBe(true);
327
+ });
328
+ it("flags infinite loops inside meetsRequirements", () => {
329
+ const code = `
330
+ function meetsRequirements(sessionData) {
331
+ while (true) { break; }
332
+ return { valid: true, code: 200, description: "Requirements met" };
333
+ }
334
+ `;
335
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
336
+ expect(result.isValid).toBe(false);
337
+ expect(result.errors.some((e) => e.includes("infinite loop"))).toBe(true);
338
+ });
339
+ it("accepts return inside switch branches when all branches return objects", () => {
340
+ const code = `
341
+ function meetsRequirements(sessionData) {
342
+ switch (sessionData.kind) {
343
+ case "a":
344
+ return { valid: true, code: 200, description: "Requirements met" };
345
+ case "b":
346
+ return { valid: false, code: 400, description: "bad b" };
347
+ default:
348
+ return { valid: false, code: 400, description: "unknown" };
349
+ }
350
+ }
351
+ `;
352
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
353
+ expect(result.errors).toEqual([]);
354
+ expect(result.isValid).toBe(true);
355
+ });
356
+ it("errors when the function name is misspelled (meetRequirements vs meetsRequirements)", () => {
357
+ const code = `
358
+ function meetRequirements(sessionData) {
359
+ if (!sessionData.selected_items) {
360
+ return { valid: false, code: "MISSING", description: "no items" };
361
+ }
362
+ return { valid: true, code: "200", description: "ok" };
363
+ }
364
+ `;
365
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
366
+ expect(result.isValid).toBe(false);
367
+ expect(result.errors.some((e) => e.includes("Expected a top-level function declaration named 'meetsRequirements'"))).toBe(true);
368
+ // Suppresses the older misleading "should return an object with properties" message
369
+ expect(result.errors.some((e) => e.includes("Function should return an object with properties"))).toBe(false);
370
+ // Suppresses the "should return a value" warning too
371
+ expect(result.warnings.some((w) => w.includes("should return a value"))).toBe(false);
372
+ });
373
+ it("accepts return inside try/finally in meetsRequirements", () => {
374
+ const code = `
375
+ function meetsRequirements(sessionData) {
376
+ try {
377
+ return { valid: true, code: 200, description: "Requirements met" };
378
+ } catch (e) {
379
+ return { valid: false, code: 500, description: String(e) };
380
+ }
381
+ }
382
+ `;
383
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
384
+ expect(result.errors).toEqual([]);
385
+ expect(result.isValid).toBe(true);
386
+ });
387
+ });
388
+ describe("CodeValidator.validate — required top-level declaration name", () => {
389
+ const getSaveSchema = (0, function_registry_1.getFunctionSchema)("getSave");
390
+ it("errors when 'validate' is misspelled (validatee)", () => {
391
+ const code = `
392
+ function validatee(targetPayload, sessionData) {
393
+ return { valid: true, code: 200, description: "Valid request" };
394
+ }
395
+ `;
396
+ const result = code_validator_1.CodeValidator.validate(code, validateSchema);
397
+ expect(result.isValid).toBe(false);
398
+ expect(result.errors.some((e) => e.includes("Expected a top-level function declaration named 'validate'"))).toBe(true);
399
+ });
400
+ it("errors when 'generate' is misspelled (genrate)", () => {
401
+ const code = `
402
+ async function genrate(defaultPayload, sessionData) {
403
+ return defaultPayload;
404
+ }
405
+ `;
406
+ const result = code_validator_1.CodeValidator.validate(code, generateSchema);
407
+ expect(result.isValid).toBe(false);
408
+ expect(result.errors.some((e) => e.includes("Expected a top-level function declaration named 'generate'"))).toBe(true);
409
+ });
410
+ it("accepts async generate (async function declaration counts)", () => {
411
+ const code = `
412
+ async function generate(defaultPayload, sessionData) {
413
+ return defaultPayload;
414
+ }
415
+ `;
416
+ const result = code_validator_1.CodeValidator.validate(code, generateSchema);
417
+ expect(result.errors).toEqual([]);
418
+ expect(result.isValid).toBe(true);
419
+ });
420
+ it("errors when function is defined as const arrow instead of declaration", () => {
421
+ const code = `
422
+ const validate = (targetPayload, sessionData) => {
423
+ return { valid: true, code: 200, description: "Valid request" };
424
+ };
425
+ `;
426
+ const result = code_validator_1.CodeValidator.validate(code, validateSchema);
427
+ expect(result.isValid).toBe(false);
428
+ expect(result.errors.some((e) => e.includes("Expected a top-level function declaration named 'validate'"))).toBe(true);
429
+ });
430
+ it("errors when only helpers are defined and the target function is missing entirely", () => {
431
+ const code = `
432
+ function helper() { return 1; }
433
+ `;
434
+ const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
435
+ expect(result.isValid).toBe(false);
436
+ expect(result.errors.some((e) => e.includes("Expected a top-level function declaration named 'meetsRequirements'"))).toBe(true);
437
+ });
438
+ it("does NOT require a function declaration for getSave (raw code allowed)", () => {
439
+ const code = `return payload.context.transaction_id;`;
440
+ const result = code_validator_1.CodeValidator.validate(code, getSaveSchema);
441
+ expect(result.errors.some((e) => e.includes("Expected a top-level function declaration"))).toBe(false);
442
+ });
443
+ it("accepts getSave wrapped in an IIFE (no named declaration needed)", () => {
444
+ const code = `const id = payload.context.transaction_id; return id;`;
445
+ const result = code_validator_1.CodeValidator.validate(code, getSaveSchema);
446
+ expect(result.errors.some((e) => e.includes("Expected a top-level function declaration"))).toBe(false);
447
+ });
448
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ondc/automation-mock-runner",
3
- "version": "1.3.52",
3
+ "version": "1.3.54",
4
4
  "description": "A TypeScript library for ONDC automation mock runner",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",