From 86746296befdb325fdd3dd17456c6b18d47d379f Mon Sep 17 00:00:00 2001 From: MAN$I VERMA Date: Sat, 4 Jul 2026 16:50:33 +0530 Subject: [PATCH] feat: Implement automated option-type ingredient validation (#2625) --- src/core/Ingredient.mjs | 40 +++++++++++--- .../operations/AutomatedValidationTestOp.mjs | 6 +++ src/core/operations/SM4Encrypt.mjs | 2 +- src/node/api.mjs | 2 +- .../operations/tests/AutomatedValidation.mjs | 53 +++++++++++++++---- tests/operations/tests/CharEnc.mjs | 4 +- tests/operations/tests/GenerateLoremIpsum.mjs | 4 +- tests/operations/tests/Image.mjs | 2 +- tests/operations/tests/SM2.mjs | 2 +- 9 files changed, 90 insertions(+), 25 deletions(-) diff --git a/src/core/Ingredient.mjs b/src/core/Ingredient.mjs index 4f81f83b..1c0f0cc3 100644 --- a/src/core/Ingredient.mjs +++ b/src/core/Ingredient.mjs @@ -76,8 +76,15 @@ class Ingredient { if (this.disabled) return true; let checkVal = val; - if (this.type === "toggleString" && val && typeof val === "object" && "string" in val) { - checkVal = val.string; + if (checkVal === null || checkVal === undefined) { + checkVal = this.defaultValue; + } + + if (this.type === "toggleString" && checkVal && typeof checkVal === "object" && "string" in checkVal) { + checkVal = checkVal.string; + } + if (this.type === "option" && Array.isArray(checkVal)) { + checkVal = checkVal[this.defaultIndex ?? 0]; } // 1. check if empty @@ -89,7 +96,11 @@ class Ingredient { } if (isEmpty) { - if (this.allowEmpty === false) { + let isAllowedOptionEmpty = false; + if (this.type === "option" && Array.isArray(this.defaultValue)) { + isAllowedOptionEmpty = this.defaultValue.includes(""); + } + if (this.allowEmpty === false || (this.type === "option" && !isAllowedOptionEmpty)) { throw new OperationError(`${this.name} cannot be empty.`); } return true; @@ -110,20 +121,35 @@ class Ingredient { // 3. number checks if (this.type === "number") { - if (val === null || val === undefined || isNaN(val)) { + if (checkVal === null || checkVal === undefined || isNaN(checkVal)) { throw new OperationError(`${this.name} must be a number.`); } - if (this.integer && !Number.isInteger(val)) { + if (this.integer && !Number.isInteger(checkVal)) { throw new OperationError(`${this.name} must be an integer.`); } - if (typeof this.min === "number" && val < this.min) { + if (typeof this.min === "number" && checkVal < this.min) { throw new OperationError(`${this.name} must be greater than or equal to ${this.min}.`); } - if (typeof this.max === "number" && val > this.max) { + if (typeof this.max === "number" && checkVal > this.max) { throw new OperationError(`${this.name} must be less than or equal to ${this.max}.`); } } + // 4. option checks + if (this.type === "option") { + if (Array.isArray(this.defaultValue)) { + const permittedOptions = this.defaultValue.filter(opt => { + if (typeof opt !== "string") return false; + return !opt.match(/^\[\/?[a-z0-9 -()^]+\]$/i); + }); + const valStr = (checkVal !== null && checkVal !== undefined) ? String(checkVal).toLowerCase() : ""; + const matchedOption = permittedOptions.find(opt => opt.toLowerCase() === valStr); + if (!matchedOption) { + throw new OperationError(`${this.name} must be one of the following: ${permittedOptions.join(", ")}.`); + } + } + } + return true; } diff --git a/src/core/operations/AutomatedValidationTestOp.mjs b/src/core/operations/AutomatedValidationTestOp.mjs index 315eb417..92f803ae 100644 --- a/src/core/operations/AutomatedValidationTestOp.mjs +++ b/src/core/operations/AutomatedValidationTestOp.mjs @@ -60,6 +60,12 @@ class AutomatedValidationTestOp extends Operation { }, "toggleValues": ["Option A", "Option B"], "allowEmpty": false + }, + { + "name": "Option Ingredient", + "type": "option", + "value": ["[Group 1]", "Option 1", "Option 2", "[/Group 1]", "[Group 2]", "Option 3", "[/Group 2]"], + "allowEmpty": false } ]; } diff --git a/src/core/operations/SM4Encrypt.mjs b/src/core/operations/SM4Encrypt.mjs index 0a58dfb9..69c414eb 100644 --- a/src/core/operations/SM4Encrypt.mjs +++ b/src/core/operations/SM4Encrypt.mjs @@ -43,7 +43,7 @@ class SM4Encrypt extends Operation { { "name": "Mode", "type": "option", - "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + "value": ["CBC", "CFB", "OFB", "CTR", "ECB", "CBC/NoPadding", "ECB/NoPadding"] }, { "name": "Input", diff --git a/src/node/api.mjs b/src/node/api.mjs index f41feb23..83163d37 100644 --- a/src/node/api.mjs +++ b/src/node/api.mjs @@ -74,7 +74,7 @@ function transformArgs(opArgsList, newArgs) { return opArgs.map((arg) => { if (arg.type === "option") { // pick default option if not already chosen - return typeof arg.value === "string" ? arg.value : arg.value[arg.defaultIndex ?? 0]; + return !Array.isArray(arg.value) ? arg.value : arg.value[arg.defaultIndex ?? 0]; } if (arg.type === "editableOption") { diff --git a/tests/operations/tests/AutomatedValidation.mjs b/tests/operations/tests/AutomatedValidation.mjs index da84de11..f55efa01 100644 --- a/tests/operations/tests/AutomatedValidation.mjs +++ b/tests/operations/tests/AutomatedValidation.mjs @@ -15,7 +15,7 @@ TestRegister.addTests([ recipeConfig: [ { op: "Automated Validation Test Op", - args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }] + args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"] } ] }, @@ -26,7 +26,7 @@ TestRegister.addTests([ recipeConfig: [ { op: "Automated Validation Test Op", - args: [4, 1.5, "hello", "", { "option": "Option A", "string": "test" }] + args: [4, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"] } ] }, @@ -37,7 +37,7 @@ TestRegister.addTests([ recipeConfig: [ { op: "Automated Validation Test Op", - args: [11, 1.5, "hello", "", { "option": "Option A", "string": "test" }] + args: [11, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"] } ] }, @@ -48,7 +48,7 @@ TestRegister.addTests([ recipeConfig: [ { op: "Automated Validation Test Op", - args: [5.5, 1.5, "hello", "", { "option": "Option A", "string": "test" }] + args: [5.5, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"] } ] }, @@ -59,7 +59,7 @@ TestRegister.addTests([ recipeConfig: [ { op: "Automated Validation Test Op", - args: [5, 1.4, "hello", "", { "option": "Option A", "string": "test" }] + args: [5, 1.4, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"] } ] }, @@ -70,7 +70,7 @@ TestRegister.addTests([ recipeConfig: [ { op: "Automated Validation Test Op", - args: [5, 5.6, "hello", "", { "option": "Option A", "string": "test" }] + args: [5, 5.6, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"] } ] }, @@ -81,7 +81,7 @@ TestRegister.addTests([ recipeConfig: [ { op: "Automated Validation Test Op", - args: [5, 1.5, "helloooo", "", { "option": "Option A", "string": "test" }] + args: [5, 1.5, "helloooo", "", { "option": "Option A", "string": "test" }, "Option 1"] } ] }, @@ -92,7 +92,7 @@ TestRegister.addTests([ recipeConfig: [ { op: "Automated Validation Test Op", - args: [5, 1.5, "", "", { "option": "Option A", "string": "test" }] + args: [5, 1.5, "", "", { "option": "Option A", "string": "test" }, "Option 1"] } ] }, @@ -103,7 +103,7 @@ TestRegister.addTests([ recipeConfig: [ { op: "Automated Validation Test Op", - args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }] + args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"] } ] }, @@ -114,7 +114,40 @@ TestRegister.addTests([ recipeConfig: [ { op: "Automated Validation Test Op", - args: [5, 1.5, "hello", "", { "option": "Option A", "string": "" }] + args: [5, 1.5, "hello", "", { "option": "Option A", "string": "" }, "Option 1"] + } + ] + }, + { + name: "Automated Validation: Invalid Option value", + input: "test", + expectedOutput: "Option Ingredient must be one of the following: Option 1, Option 2, Option 3.", + recipeConfig: [ + { + op: "Automated Validation Test Op", + args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "Option 4"] + } + ] + }, + { + name: "Automated Validation: Option value as optgroup heading (invalid)", + input: "test", + expectedOutput: "Option Ingredient must be one of the following: Option 1, Option 2, Option 3.", + recipeConfig: [ + { + op: "Automated Validation Test Op", + args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "[Group 1]"] + } + ] + }, + { + name: "Automated Validation: Option value empty (invalid)", + input: "test", + expectedOutput: "Option Ingredient cannot be empty.", + recipeConfig: [ + { + op: "Automated Validation Test Op", + args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }, ""] } ] } diff --git a/tests/operations/tests/CharEnc.mjs b/tests/operations/tests/CharEnc.mjs index 83f71ca9..88991761 100644 --- a/tests/operations/tests/CharEnc.mjs +++ b/tests/operations/tests/CharEnc.mjs @@ -71,7 +71,7 @@ TestRegister.addTests([ { name: "Encode text: empty encoding", input: "hello", - expectedOutput: "Invalid encoding", + expectedOutput: "Encoding cannot be empty.", recipeConfig: [ { "op": "Encode text", @@ -82,7 +82,7 @@ TestRegister.addTests([ { name: "Decode text: empty encoding", input: "68 65 6c 6c 6f", - expectedOutput: "Invalid encoding", + expectedOutput: "Encoding cannot be empty.", recipeConfig: [ { "op": "From Hex", diff --git a/tests/operations/tests/GenerateLoremIpsum.mjs b/tests/operations/tests/GenerateLoremIpsum.mjs index c42bf8da..6861752c 100644 --- a/tests/operations/tests/GenerateLoremIpsum.mjs +++ b/tests/operations/tests/GenerateLoremIpsum.mjs @@ -67,12 +67,12 @@ TestRegister.addTests([ { name: "Generate Lorem Ipsum: Incorrect lengthType", input: "", - expectedOutput: "Invalid length type", + expectedOutput: "Length in must be one of the following: Paragraphs, Sentences, Words, Bytes.", recipeConfig: [ { "op": "Generate Lorem Ipsum", "args": [999_999, "Novels"] - }, + } ], }, diff --git a/tests/operations/tests/Image.mjs b/tests/operations/tests/Image.mjs index 7da6c65d..56f3fa2b 100644 --- a/tests/operations/tests/Image.mjs +++ b/tests/operations/tests/Image.mjs @@ -48,7 +48,7 @@ TestRegister.addTests([ { name: "Generate Image: empty mode", input: "", - expectedOutput: "Unsupported Mode: ()", + expectedOutput: "Mode cannot be empty.", recipeConfig: [ { op: "Generate Image", diff --git a/tests/operations/tests/SM2.mjs b/tests/operations/tests/SM2.mjs index a3d6fd2c..53ee22ca 100644 --- a/tests/operations/tests/SM2.mjs +++ b/tests/operations/tests/SM2.mjs @@ -113,7 +113,7 @@ TestRegister.addTests([ }, { "op": "SM2 Decrypt", - "args": [PRIVATE_K, "C1C2C2", CURVE] + "args": [PRIVATE_K, "C1C2C3", CURVE] } ] },