Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
- Batched function deletions across instances when uninstalling a Function Kit (#11189).
- Fixed an issue where 2nd-gen functions with parameterized trigger event filters failed default region resolution (#11020).
- Fixed `functions:lifecycle:list` and `functions:lifecycle:run` failing to detect Function Kit instances (#11240).
- Fixed nested ternary CEL expressions in function parameters, which previously failed to load or selected the wrong branch. (#7755)
254 changes: 254 additions & 0 deletions src/deploy/functions/cel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,24 @@ describe("CEL evaluation", () => {
}),
).to.be.true;
});

it("raises when a comparison is resolved as a type other than boolean", () => {
expect(() => {
resolveExpression("string", '{{ params.FOO == "bar" }}', { FOO: stringV("bar") });
}).to.throw(ExprParseError);
expect(() => {
resolveExpression("number", "{{ params.FOO == params.BAR }}", {
FOO: numberV(22),
BAR: numberV(22),
});
}).to.throw(ExprParseError);
// An unescaped quote hides the " ? ", so this reads as a comparison.
expect(() => {
resolveExpression("string", '{{ params.FOO == "a"b" ? "x" : "y" }}', {
FOO: stringV('a"b'),
});
}).to.throw(ExprParseError);
});
});

describe("Dual comparison expressions", () => {
Expand Down Expand Up @@ -1018,6 +1036,24 @@ describe("CEL evaluation", () => {
).to.equal("baz");
});

it("resolves a ternary nested in a branch of a dual comparison ternary", () => {
const expr = '{{ params.FOO == params.BAR ? "a" : params.FOO == params.BAZ ? "b" : "c" }}';
expect(
resolveExpression("string", expr, {
FOO: stringV("a"),
BAR: stringV("q"),
BAZ: stringV("a"),
}),
).to.equal("b");
expect(
resolveExpression("string", expr, {
FOO: stringV("a"),
BAR: stringV("q"),
BAZ: stringV("r"),
}),
).to.equal("c");
});

it("it knows how to handle non-== comparisons by delegating to the Comparison expression evaluators", () => {
expect(
resolveExpression("number", "{{ params.FOO != params.BAR ? params.IF_T : params.IF_F }}", {
Expand Down Expand Up @@ -1170,5 +1206,223 @@ describe("CEL evaluation", () => {
}),
).to.equal("baz");
});

it("resolves a ternary nested in a branch of a boolean conditioned ternary", () => {
const expr = '{{ params.FLAG ? "a" : params.OTHER ? "b" : "c" }}';
expect(resolveExpression("string", expr, { FLAG: boolV(true), OTHER: boolV(true) })).to.equal(
"a",
);
expect(
resolveExpression("string", expr, { FLAG: boolV(false), OTHER: boolV(true) }),
).to.equal("b");
expect(
resolveExpression("string", expr, { FLAG: boolV(false), OTHER: boolV(false) }),
).to.equal("c");
});
});

describe("Nested ternary expressions", () => {
it("resolves a ternary nested in the false branch", () => {
const expr =
'{{ params.PROJECT_ID == "xxx" ? "aaa" : params.PROJECT_ID == "yyy" ? "bbb" : "ccc" }}';
expect(resolveExpression("string", expr, { PROJECT_ID: stringV("xxx") })).to.equal("aaa");
expect(resolveExpression("string", expr, { PROJECT_ID: stringV("yyy") })).to.equal("bbb");
expect(resolveExpression("string", expr, { PROJECT_ID: stringV("zzz") })).to.equal("ccc");
});

it("resolves a nested ternary with number branches", () => {
const expr = '{{ params.PROJECT_ID == "xxx" ? 1 : params.PROJECT_ID == "yyy" ? 2 : 3 }}';
expect(resolveExpression("number", expr, { PROJECT_ID: stringV("xxx") })).to.equal(1);
expect(resolveExpression("number", expr, { PROJECT_ID: stringV("yyy") })).to.equal(2);
expect(resolveExpression("number", expr, { PROJECT_ID: stringV("zzz") })).to.equal(3);
});

it("resolves a ternary nested in the true branch", () => {
const expr = '{{ params.FOO == "x" ? params.BAR == "u" ? "a" : "b" : "c" }}';
expect(resolveExpression("string", expr, { FOO: stringV("x"), BAR: stringV("u") })).to.equal(
"a",
);
expect(resolveExpression("string", expr, { FOO: stringV("x"), BAR: stringV("v") })).to.equal(
"b",
);
expect(resolveExpression("string", expr, { FOO: stringV("y"), BAR: stringV("u") })).to.equal(
"c",
);
});

Comment thread
Om-singhaI marked this conversation as resolved.
it("resolves an expression with ternaries nested in both branches", () => {
const expr = '{{ params.A ? params.B ? "1" : "2" : params.C ? "3" : "4" }}';
expect(
resolveExpression("string", expr, { A: boolV(true), B: boolV(true), C: boolV(false) }),
).to.equal("1");
expect(
resolveExpression("string", expr, { A: boolV(true), B: boolV(false), C: boolV(false) }),
).to.equal("2");
expect(
resolveExpression("string", expr, { A: boolV(false), B: boolV(false), C: boolV(true) }),
).to.equal("3");
expect(
resolveExpression("string", expr, { A: boolV(false), B: boolV(false), C: boolV(false) }),
).to.equal("4");
});

it("resolves a chain three levels deep", () => {
const expr =
'{{ params.FOO == "a" ? 1 : params.FOO == "b" ? 2 : params.FOO == "c" ? 3 : 4 }}';
expect(resolveExpression("number", expr, { FOO: stringV("a") })).to.equal(1);
expect(resolveExpression("number", expr, { FOO: stringV("b") })).to.equal(2);
expect(resolveExpression("number", expr, { FOO: stringV("c") })).to.equal(3);
expect(resolveExpression("number", expr, { FOO: stringV("d") })).to.equal(4);
});

it("provides resolved parameters from a nested branch", () => {
const expr =
'{{ params.FOO == "x" ? params.IF_A : params.FOO == "y" ? params.IF_B : params.IF_C }}';
const params = {
FOO: stringV("y"),
IF_A: numberV(1),
IF_B: numberV(2),
IF_C: numberV(3),
};
expect(resolveExpression("number", expr, params)).to.equal(2);
});

it("resolves list branches in a nested ternary", () => {
const expr = '{{ params.FOO == "x" ? ["a"] : params.FOO == "y" ? [params.BAR] : [] }}';
expect(
resolveExpression("string[]", expr, { FOO: stringV("y"), BAR: stringV("b") }),
).to.deep.equal(["b"]);
expect(
resolveExpression("string[]", expr, { FOO: stringV("z"), BAR: stringV("b") }),
).to.deep.equal([]);
});

it("resolves a list branch holding a value that contains a double quote", () => {
expect(
resolveExpression("string[]", '{{ params.FOO == "x" ? [params.Q] : [] }}', {
FOO: stringV("x"),
Q: stringV('a"b'),
}),
).to.deep.equal(['a"b']);
expect(
resolveExpression("string[]", "{{ params.FLAG ? [params.Q] : [] }}", {
FLAG: boolV(true),
Q: stringV('a"b'),
}),
).to.deep.equal(['a"b']);
});

it("doesn't end a literal at an escaped double quote", () => {
expect(
resolveExpression("string", '{{ params.FLAG ? "a\\"b" : "c" }}', {
FLAG: boolV(false),
}),
).to.equal("c");
});

it("doesn't split on a ? or a : inside a string literal", () => {
expect(
resolveExpression("string", '{{ params.FOO == "q" ? "x : y" : "z" }}', {
FOO: stringV("a"),
}),
).to.equal("z");
expect(
resolveExpression("string", '{{ params.FOO == "a" ? "x ? y" : "z" }}', {
FOO: stringV("a"),
}),
).to.equal("x ? y");
expect(
resolveExpression("string", '{{ params.FOO == "a : b" ? "z" : "w" }}', {
FOO: stringV("a : b"),
}),
).to.equal("z");
expect(
resolveExpression("string", '{{ params.FOO == "a" ? "x ? y : z" : "w" }}', {
FOO: stringV("a"),
}),
).to.equal("x ? y : z");
expect(
resolveExpression("string", '{{ params.FOO == "q" ? "z" : "x : y" }}', {
FOO: stringV("a"),
}),
).to.equal("x : y");
expect(
resolveExpression("boolean", '{{ params.FOO == "a ? b" }}', { FOO: stringV("a ? b") }),
).to.equal(true);
});

it("raises when a nested branch references a missing param", () => {
expect(() => {
resolveExpression(
"string",
'{{ params.FOO == "x" ? "a" : params.FOO == "y" ? params.MISSING : "c" }}',
{ FOO: stringV("y") },
);
}).to.throw(ExprParseError);
expect(() => {
resolveExpression(
"string",
'{{ params.FOO == "x" ? "a" : params.MISSING == "y" ? "b" : "c" }}',
{ FOO: stringV("z") },
);
}).to.throw(ExprParseError);
});

it("raises when a nested branch resolves to a param of the wrong type", () => {
expect(() => {
resolveExpression(
"string",
'{{ params.FOO == "x" ? "a" : params.FOO == "y" ? params.NUM : "c" }}',
{ FOO: stringV("y"), NUM: numberV(2) },
);
}).to.throw(ExprParseError);
});

it("raises when a nested branch isn't a legal literal", () => {
expect(() => {
resolveExpression("number", '{{ params.FOO == "x" ? 1 : params.FOO == "y" ? abc : 3 }}', {
FOO: stringV("y"),
});
}).to.throw(ExprParseError);
expect(() => {
resolveExpression(
"string",
'{{ params.FOO == "x" ? "a" : params.FOO == "y" ? bare : "c" }}',
{ FOO: stringV("y") },
);
}).to.throw(ExprParseError);
});

it("raises on ternaries with a missing or a stray delimiter", () => {
expect(() => {
resolveExpression("number", "{{ }}", {});
}).to.throw(ExprParseError);
expect(() => {
resolveExpression("number", "{{ params.FOO ?? 10 : 0 }}", { FOO: numberV(22) });
}).to.throw(ExprParseError);
expect(() => {
resolveExpression("number", "{{ params.FOO == 22 : 0 }}", { FOO: numberV(22) });
}).to.throw(ExprParseError);
expect(() => {
resolveExpression("number", "{{ params.FOO == 22 ? 10 }}", { FOO: numberV(22) });
}).to.throw(ExprParseError);
expect(() => {
resolveExpression("number", "{{ params.FOO == 22 ? 10 : }}", { FOO: numberV(22) });
}).to.throw(ExprParseError);
expect(() => {
resolveExpression("string", '{{ params.FOO == "a" ? "x" }}', { FOO: stringV("a") });
}).to.throw(ExprParseError);
expect(() => {
resolveExpression("string", '{{ params.FOO == "a" ? "x" ? "y" : "z" }}', {
FOO: stringV("a"),
});
}).to.throw(ExprParseError);
expect(() => {
resolveExpression("string", '{{ params.FOO ? "x" : "y" ? "z" }}', { FOO: boolV(false) });
}).to.throw(ExprParseError);
expect(() => {
resolveExpression("string", '{{ params.FOO == "a" : "b" }}', { FOO: stringV("a") });
}).to.throw(ExprParseError);
});
});
});
Loading
Loading