diff --git a/src/deploy/functions/params.spec.ts b/src/deploy/functions/params.spec.ts index ca7ee49b792..ee8ea52749e 100644 --- a/src/deploy/functions/params.spec.ts +++ b/src/deploy/functions/params.spec.ts @@ -299,3 +299,140 @@ describe("resolveParams", () => { await expect(params.resolveParams(paramsToResolve, fakeConfig, {})).to.eventually.be.rejected; }); }); + +describe("ParamValue.asList()", () => { + describe("basic usage", () => { + it("should parse JSON-style array with double quotes", () => { + const paramValue = new params.ParamValue('["1", "2", "3"]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["1", "2", "3"]); + }); + + it("should split comma-separated values", () => { + const paramValue = new params.ParamValue("a,b,c", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["a", "b", "c"]); + }); + + it("should handle empty array", () => { + const paramValue = new params.ParamValue("[]", false, { list: true }); + expect(paramValue.asList()).to.deep.equal([]); + }); + + it("should handle a single value", () => { + const paramValue = new params.ParamValue("foo", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["foo"]); + }); + + it("should handle whitespace around values", () => { + const paramValue = new params.ParamValue(" a , b , c ", false, { list: true }); + expect(paramValue.asList()).to.deep.equal([" a ", " b ", " c "]); + }); + }); + + describe("delimiter handling", () => { + it("should use custom delimiter", () => { + const paramValue = new params.ParamValue("a;b;c", false, { list: true }); + paramValue.setDelimiter(";"); + expect(paramValue.asList()).to.deep.equal(["a", "b", "c"]); + }); + }); + + describe("robustness", () => { + it("should handle values with commas inside JSON array", () => { + const paramValue = new params.ParamValue('["hello, world", "foo, bar"]', false, { + list: true, + }); + expect(paramValue.asList()).to.deep.equal(["hello, world", "foo, bar"]); + }); + + it("should fall back to delimiter splitting for malformed JSON", () => { + const paramValue = new params.ParamValue("[a, b, c]", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["a", " b", " c"]); + }); + + it("should handle special characters", () => { + const paramValue = new params.ParamValue('["@#$%", "^&*()"]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["@#$%", "^&*()"]); + }); + + it("should work with fromList static method", () => { + const originalList = ["apple", "banana", "cherry"]; + const paramValue = params.ParamValue.fromList(originalList); + expect(paramValue.asList()).to.deep.equal(originalList); + }); + + it("should handle square brackets in the middle of the string", () => { + const paramValue = new params.ParamValue('["]", "[", "]"]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["]", "[", "]"]); + }); + + it("should handle quotes, apostrophes, and backticks in the middle of the string", () => { + const paramValue = new params.ParamValue('["a\\"b", "c`d", "e\'f"]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(['a"b', "c`d", "e'f"]); + }); + + it("should handle empty string", () => { + const paramValue = new params.ParamValue("", false, { list: true }); + expect(paramValue.asList()).to.deep.equal([""]); + }); + + it("should handle whitespace-only string", () => { + const paramValue = new params.ParamValue(" ", false, { list: true }); + expect(paramValue.asList()).to.deep.equal([" "]); + }); + + it("should handle JSON array with numbers", () => { + const paramValue = new params.ParamValue("[1, 2, 3]", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["1", "2", "3"]); + }); + + it("should handle JSON array with booleans", () => { + const paramValue = new params.ParamValue("[true, false, true]", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["true", "false", "true"]); + }); + + it("should handle JSON array with nulls", () => { + const paramValue = new params.ParamValue('["a", null, "b"]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["a", "null", "b"]); + }); + + it("should handle trailing delimiter", () => { + const paramValue = new params.ParamValue("a,b,c,", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["a", "b", "c", ""]); + }); + + it("should handle leading delimiter", () => { + const paramValue = new params.ParamValue(",a,b,c", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["", "a", "b", "c"]); + }); + + it("should handle consecutive delimiters", () => { + const paramValue = new params.ParamValue("a,,b", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["a", "", "b"]); + }); + + it("should handle only delimiters", () => { + const paramValue = new params.ParamValue(",,,", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["", "", "", ""]); + }); + + it("should handle JSON array with empty strings", () => { + const paramValue = new params.ParamValue('["", "a", ""]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["", "a", ""]); + }); + + it("should handle JSON array with whitespace strings", () => { + const paramValue = new params.ParamValue('[" ", "a", " "]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal([" ", "a", " "]); + }); + + it("should handle JSON array with nested arrays", () => { + const paramValue = new params.ParamValue('[["a", "b"], ["c"]]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(['["a","b"]', '["c"]']); + }); + + it("should handle JSON array with objects", () => { + const paramValue = new params.ParamValue('[{"foo":1}, {"bar":2}]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(['{"foo":1}', '{"bar":2}']); + }); + }); +}); diff --git a/src/deploy/functions/params.ts b/src/deploy/functions/params.ts index 4946b63592f..c332a7376fd 100644 --- a/src/deploy/functions/params.ts +++ b/src/deploy/functions/params.ts @@ -291,15 +291,37 @@ export class ParamValue { } asList(): string[] { - // Handle something like "['a', 'b', 'c']" - if (this.rawValue.includes("[")) { - // Convert quotes to apostrophes - const unquoted = this.rawValue.replace(/'/g, '"'); - return JSON.parse(unquoted); + let modifiedValue = this.rawValue; + + // Handle something like "["a", "b", "c"]" + if (modifiedValue.startsWith("[") && modifiedValue.endsWith("]")) { + try { + const parsed = JSON.parse(modifiedValue); + if (Array.isArray(parsed)) { + // The return type is string[], so we must convert all elements to strings. + return parsed.map((elem: any) => { + if (elem === null) { + // String(null) is "null", which is what we want. + return "null"; + } + if (typeof elem === "object") { + // String(obj) is "[object Object]", JSON.stringify is more useful. + // Avoid inserting spaces after commas for objects/arrays + return JSON.stringify(elem); + } + return String(elem); + }); + } + // It's valid JSON but not an array (e.g. a string literal "foo,bar"). + // Fall through to splitting by delimiter. + } catch (e) { + // Malformed JSON, e.g. "[a, b, c]". Remove brackets and fall through. + modifiedValue = modifiedValue.slice(1, -1); + } } // Continue to handle something like "a,b,c" - return this.rawValue.split(this.delimiter); + return modifiedValue.split(this.delimiter); } asNumber(): number {