@ondc/automation-mock-runner 1.3.48 → 1.3.50
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.
|
@@ -15,9 +15,12 @@ export declare class CodeValidator {
|
|
|
15
15
|
* Validate that return statements match expected structure
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
|
-
* Collect ReturnStatement arguments belonging only to the
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* Collect ReturnStatement arguments belonging only to the target function.
|
|
19
|
+
* When `targetFnName` is given and a top-level `function <name>(...)` exists,
|
|
20
|
+
* we walk only that body — sibling helpers like `function x(){return "hi"}`
|
|
21
|
+
* declared next to `validate` are ignored. Returns inside nested
|
|
22
|
+
* functions/arrows are also skipped (they aren't the contract).
|
|
23
|
+
* Falls back to the whole AST when no named match is found.
|
|
21
24
|
*/
|
|
22
25
|
private static collectTopLevelReturns;
|
|
23
26
|
private static validateReturnStructure;
|
|
@@ -139,7 +139,7 @@ class CodeValidator {
|
|
|
139
139
|
});
|
|
140
140
|
// 5. Validate return type structure (for validate and meetsRequirements)
|
|
141
141
|
if (schema.returnType.properties) {
|
|
142
|
-
const returnValidation = this.validateReturnStructure(ast, schema.returnType.properties);
|
|
142
|
+
const returnValidation = this.validateReturnStructure(ast, schema.returnType.properties, schema.name);
|
|
143
143
|
errors.push(...returnValidation);
|
|
144
144
|
}
|
|
145
145
|
// 6. Check for best practices
|
|
@@ -172,24 +172,27 @@ class CodeValidator {
|
|
|
172
172
|
* Validate that return statements match expected structure
|
|
173
173
|
*/
|
|
174
174
|
/**
|
|
175
|
-
* Collect ReturnStatement arguments belonging only to the
|
|
176
|
-
*
|
|
177
|
-
*
|
|
175
|
+
* Collect ReturnStatement arguments belonging only to the target function.
|
|
176
|
+
* When `targetFnName` is given and a top-level `function <name>(...)` exists,
|
|
177
|
+
* we walk only that body — sibling helpers like `function x(){return "hi"}`
|
|
178
|
+
* declared next to `validate` are ignored. Returns inside nested
|
|
179
|
+
* functions/arrows are also skipped (they aren't the contract).
|
|
180
|
+
* Falls back to the whole AST when no named match is found.
|
|
178
181
|
*/
|
|
179
|
-
static collectTopLevelReturns(ast) {
|
|
182
|
+
static collectTopLevelReturns(ast, targetFnName) {
|
|
180
183
|
const returns = [];
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
};
|
|
189
|
-
walk.recursive(
|
|
190
|
-
FunctionDeclaration:
|
|
191
|
-
FunctionExpression:
|
|
192
|
-
ArrowFunctionExpression:
|
|
184
|
+
const programBody = Array.isArray(ast.body)
|
|
185
|
+
? ast.body
|
|
186
|
+
: [];
|
|
187
|
+
const targetFn = targetFnName
|
|
188
|
+
? programBody.find((n) => n.type === "FunctionDeclaration" && n.id?.name === targetFnName)
|
|
189
|
+
: undefined;
|
|
190
|
+
const walkRoot = targetFn ? targetFn.body : ast;
|
|
191
|
+
const skip = () => { };
|
|
192
|
+
walk.recursive(walkRoot, null, {
|
|
193
|
+
FunctionDeclaration: skip,
|
|
194
|
+
FunctionExpression: skip,
|
|
195
|
+
ArrowFunctionExpression: skip,
|
|
193
196
|
ReturnStatement(node) {
|
|
194
197
|
if (node.argument)
|
|
195
198
|
returns.push(node.argument);
|
|
@@ -197,35 +200,60 @@ class CodeValidator {
|
|
|
197
200
|
});
|
|
198
201
|
return returns;
|
|
199
202
|
}
|
|
200
|
-
static validateReturnStructure(ast, expectedProperties) {
|
|
203
|
+
static validateReturnStructure(ast, expectedProperties, targetFnName) {
|
|
201
204
|
const warnings = [];
|
|
202
|
-
const foundReturns = this.collectTopLevelReturns(ast);
|
|
205
|
+
const foundReturns = this.collectTopLevelReturns(ast, targetFnName);
|
|
203
206
|
// Check if we have return statements
|
|
204
207
|
if (foundReturns.length === 0) {
|
|
205
208
|
warnings.push(`Function should return an object with properties: ${Object.keys(expectedProperties).join(", ")}`);
|
|
206
209
|
return warnings;
|
|
207
210
|
}
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
211
|
+
// Flatten conditional/logical/sequence/parenthesized wrappers so that
|
|
212
|
+
// minifier output like `return cond ? {a:1} : {a:2}` or `return x && {...}`
|
|
213
|
+
// is treated as the set of object-literal branches it actually returns.
|
|
214
|
+
const objectBranches = [];
|
|
215
|
+
const stack = [...foundReturns];
|
|
216
|
+
let nonObjectFound = false;
|
|
217
|
+
while (stack.length) {
|
|
218
|
+
const node = stack.pop();
|
|
219
|
+
switch (node.type) {
|
|
220
|
+
case "ObjectExpression":
|
|
221
|
+
objectBranches.push(node);
|
|
222
|
+
break;
|
|
223
|
+
case "ConditionalExpression":
|
|
224
|
+
stack.push(node.consequent, node.alternate);
|
|
225
|
+
break;
|
|
226
|
+
case "LogicalExpression":
|
|
227
|
+
stack.push(node.left, node.right);
|
|
228
|
+
break;
|
|
229
|
+
case "SequenceExpression":
|
|
230
|
+
stack.push(node.expressions[node.expressions.length - 1]);
|
|
231
|
+
break;
|
|
232
|
+
case "ParenthesizedExpression":
|
|
233
|
+
stack.push(node.expression);
|
|
234
|
+
break;
|
|
235
|
+
default:
|
|
236
|
+
nonObjectFound = true;
|
|
228
237
|
}
|
|
238
|
+
}
|
|
239
|
+
if (objectBranches.length === 0 || nonObjectFound) {
|
|
240
|
+
warnings.push(`Function should return an object literal with properties: ${Object.keys(expectedProperties).join(", ")}`);
|
|
241
|
+
}
|
|
242
|
+
objectBranches.forEach((returnArg) => {
|
|
243
|
+
const returnedProps = new Set(returnArg.properties.map((p) => p.key.name));
|
|
244
|
+
const expectedProps = Object.keys(expectedProperties);
|
|
245
|
+
// Check for missing properties
|
|
246
|
+
expectedProps.forEach((prop) => {
|
|
247
|
+
if (!returnedProps.has(prop)) {
|
|
248
|
+
warnings.push(`Return object is missing property '${prop}' (expected: ${expectedProperties[prop].type})`);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
// Check for extra properties
|
|
252
|
+
returnedProps.forEach((prop) => {
|
|
253
|
+
if (!expectedProps.includes(prop)) {
|
|
254
|
+
warnings.push(`Return object has unexpected property '${prop}'`);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
229
257
|
});
|
|
230
258
|
return warnings;
|
|
231
259
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const code_validator_1 = require("../lib/validators/code-validator");
|
|
4
4
|
const function_registry_1 = require("../lib/constants/function-registry");
|
|
5
5
|
const validateSchema = (0, function_registry_1.getFunctionSchema)("validate");
|
|
6
|
+
const meetsRequirementsSchema = (0, function_registry_1.getFunctionSchema)("meetsRequirements");
|
|
6
7
|
describe("CodeValidator.validate — return structure", () => {
|
|
7
8
|
it("accepts an outer return with the full expected shape", () => {
|
|
8
9
|
const code = `
|
|
@@ -57,6 +58,40 @@ describe("CodeValidator.validate — return structure", () => {
|
|
|
57
58
|
expect(result.isValid).toBe(false);
|
|
58
59
|
expect(result.errors.some((e) => e.includes("Function should return an object literal"))).toBe(true);
|
|
59
60
|
});
|
|
61
|
+
it("accepts a minified conditional return: return i ? {...} : {...}", () => {
|
|
62
|
+
const code = `function validate(i,d){return i?{valid:!0,code:200,description:"Valid request"}:{valid:!1,code:200,description:"oh no"}}`;
|
|
63
|
+
const result = code_validator_1.CodeValidator.validate(code, validateSchema);
|
|
64
|
+
expect(result.errors).toEqual([]);
|
|
65
|
+
expect(result.isValid).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
it("ignores sibling top-level helper function returns (validate)", () => {
|
|
68
|
+
const code = `
|
|
69
|
+
function x() {
|
|
70
|
+
return "hello";
|
|
71
|
+
}
|
|
72
|
+
function validate(targetPayload, sessionData) {
|
|
73
|
+
let some = x();
|
|
74
|
+
return { valid: true, code: 200, description: "Valid request" };
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
77
|
+
const result = code_validator_1.CodeValidator.validate(code, validateSchema);
|
|
78
|
+
expect(result.errors).toEqual([]);
|
|
79
|
+
expect(result.isValid).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
it("ignores sibling top-level helper function returns (meetsRequirements)", () => {
|
|
82
|
+
const code = `
|
|
83
|
+
function helper() {
|
|
84
|
+
return 123;
|
|
85
|
+
}
|
|
86
|
+
function meetsRequirements(sessionData) {
|
|
87
|
+
const n = helper();
|
|
88
|
+
return { valid: true, code: 200, description: "Requirements met" };
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
const result = code_validator_1.CodeValidator.validate(code, meetsRequirementsSchema);
|
|
92
|
+
expect(result.errors).toEqual([]);
|
|
93
|
+
expect(result.isValid).toBe(true);
|
|
94
|
+
});
|
|
60
95
|
it("warns when only a nested helper returns and the outer function has no return", () => {
|
|
61
96
|
const code = `
|
|
62
97
|
function validate(targetPayload, sessionData) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const MockRunner_1 = require("../lib/MockRunner");
|
|
4
|
+
describe("scratch: user-supplied validate fn", () => {
|
|
5
|
+
afterEach(async () => {
|
|
6
|
+
const shared = MockRunner_1.MockRunner.sharedRunner;
|
|
7
|
+
if (shared?.terminate)
|
|
8
|
+
await shared.terminate();
|
|
9
|
+
MockRunner_1.MockRunner.sharedRunner = undefined;
|
|
10
|
+
});
|
|
11
|
+
it("runs the user's validate() against a payload and a null payload", async () => {
|
|
12
|
+
const validateSrc = `function validate(targetPayload, sessionData) {
|
|
13
|
+
if(!targetPayload){
|
|
14
|
+
return {valid: false, code: 200, description: "oh no"};
|
|
15
|
+
}
|
|
16
|
+
return { valid: true, code: 200, description: "Valid request" };
|
|
17
|
+
}`;
|
|
18
|
+
const config = {
|
|
19
|
+
meta: { domain: "ONDC:TRV14", version: "2.0.0", flowId: "testing" },
|
|
20
|
+
transaction_data: {
|
|
21
|
+
transaction_id: "e9e0b5cb-3f15-48a1-9d86-d4d643f0909d",
|
|
22
|
+
latest_timestamp: "1970-01-01T00:00:00.000Z",
|
|
23
|
+
},
|
|
24
|
+
steps: [],
|
|
25
|
+
transaction_history: [],
|
|
26
|
+
validationLib: "",
|
|
27
|
+
helperLib: "",
|
|
28
|
+
};
|
|
29
|
+
const runner = new MockRunner_1.MockRunner(config);
|
|
30
|
+
const step = runner.getDefaultStep("search", "search_0");
|
|
31
|
+
step.mock.validate = MockRunner_1.MockRunner.encodeBase64(validateSrc);
|
|
32
|
+
runner.getConfig().steps.push(step);
|
|
33
|
+
const r1 = await runner.runValidatePayloadWithSession("search_0", { context: {} }, {});
|
|
34
|
+
console.log("WITH_PAYLOAD:", JSON.stringify(r1, null, 2));
|
|
35
|
+
const r2 = await runner.runValidatePayloadWithSession("search_0", null, {});
|
|
36
|
+
console.log("WITH_NULL:", JSON.stringify(r2, null, 2));
|
|
37
|
+
expect(r1.success).toBe(true);
|
|
38
|
+
expect(r2.success).toBe(true);
|
|
39
|
+
}, 30000);
|
|
40
|
+
});
|