mirror of
https://github.com/kubevela/kubevela.git
synced 2026-05-19 07:46:51 +00:00
* Feat: Add NotEmpty and NegativePattern constraints to StringParam; implement Closed for MapParam Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * feat: add validation support for array and map parameters - Introduced validators for ArrayParam and MapParam, allowing for cross-field validation within structured parameters. - Added NonEmpty validation for ArrayParam to ensure arrays are not empty. - Implemented ConditionalStructOp for conditional struct generation based on specified conditions. - Created a new Validator type for defining validation rules with optional guard conditions. - Added tests for various validation scenarios, including mutual exclusion and conditional parameters. - Enhanced the CUE generation logic to incorporate new validation features and conditional struct handling. Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * feat: extend fluent API with new scoped field conditions and improve validation checks Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * feat: enhance ArrayParam with NotEmpty constraint and update ScopedField documentation Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * feat: rename ScopedField to LocalField for improved clarity in condition building Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * feat: refactor local field conditions to use RegexMatch and streamline condition building Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * feat: simplify condition handling by removing unused comparison types and refactoring NotCondition usage Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * refactor: remove unused raw CUE block handling from baseDefinition and ComponentDefinition Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * test: update condition handling in parameter tests to use NotExpr and Cond methods Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * refactor: remove negative pattern handling from StringParam and related tests Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * feat: add support for emitting raw header blocks in template generation Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * refactor: remove non-empty check from ArrayParam and update related tests Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * refactor: convert parameter constraint tests to use Ginkgo and Gomega for improved readability and maintainability Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * feat: extend fluent APIs for OAM with new CUE generation tests and condition evaluations Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * refactor: clean up whitespace in component, cuegen, expr, param, and resource files Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * feat: enhance CUE generation by adding support for new expression types and iterator references Signed-off-by: Ayush Kumar <aykumar@guidewire.com> Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * refactor: remove unnecessary whitespace in cuegen.go Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * refactor: rename LenOf to LenOfExpr for clarity in comparison methods Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * feat: enhance CUE generation and validation for string arrays in ArrayParam Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * ci: retrigger checks Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> --------- Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> Signed-off-by: Ayush Kumar <aykumar@guidewire.com> Co-authored-by: Ayush Kumar <aykumar@guidewire.com>
452 lines
13 KiB
Go
452 lines
13 KiB
Go
/*
|
|
Copyright 2025 The KubeVela Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package defkit_test
|
|
|
|
import (
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/definition/defkit"
|
|
)
|
|
|
|
var _ = Describe("Expressions", func() {
|
|
|
|
Context("Literal", func() {
|
|
It("should create string literal", func() {
|
|
lit := defkit.Lit("hello")
|
|
Expect(lit.Val()).To(Equal("hello"))
|
|
})
|
|
|
|
It("should create integer literal", func() {
|
|
lit := defkit.Lit(42)
|
|
Expect(lit.Val()).To(Equal(42))
|
|
})
|
|
|
|
It("should create boolean literal", func() {
|
|
lit := defkit.Lit(true)
|
|
Expect(lit.Val()).To(Equal(true))
|
|
})
|
|
|
|
It("should create float literal", func() {
|
|
lit := defkit.Lit(3.14)
|
|
Expect(lit.Val()).To(Equal(3.14))
|
|
})
|
|
|
|
It("should create nil literal", func() {
|
|
lit := defkit.Lit(nil)
|
|
Expect(lit.Val()).To(BeNil())
|
|
})
|
|
})
|
|
|
|
Context("Comparisons", func() {
|
|
var left, right defkit.Expr
|
|
|
|
BeforeEach(func() {
|
|
left = defkit.String("count")
|
|
right = defkit.Lit(10)
|
|
})
|
|
|
|
It("should create equality comparison", func() {
|
|
cmp := defkit.Eq(left, right)
|
|
Expect(cmp.Op()).To(Equal(defkit.OpEq))
|
|
Expect(cmp.Left()).To(Equal(left))
|
|
Expect(cmp.Right()).To(Equal(right))
|
|
})
|
|
|
|
It("should create inequality comparison", func() {
|
|
cmp := defkit.Ne(left, right)
|
|
Expect(cmp.Op()).To(Equal(defkit.OpNe))
|
|
})
|
|
|
|
It("should create less-than comparison", func() {
|
|
cmp := defkit.Lt(left, right)
|
|
Expect(cmp.Op()).To(Equal(defkit.OpLt))
|
|
})
|
|
|
|
It("should create less-than-or-equal comparison", func() {
|
|
cmp := defkit.Le(left, right)
|
|
Expect(cmp.Op()).To(Equal(defkit.OpLe))
|
|
})
|
|
|
|
It("should create greater-than comparison", func() {
|
|
cmp := defkit.Gt(left, right)
|
|
Expect(cmp.Op()).To(Equal(defkit.OpGt))
|
|
})
|
|
|
|
It("should create greater-than-or-equal comparison", func() {
|
|
cmp := defkit.Ge(left, right)
|
|
Expect(cmp.Op()).To(Equal(defkit.OpGe))
|
|
})
|
|
|
|
It("should support comparison with two parameters", func() {
|
|
p1 := defkit.Int("min")
|
|
p2 := defkit.Int("max")
|
|
cmp := defkit.Lt(p1, p2)
|
|
Expect(cmp.Left()).To(Equal(p1))
|
|
Expect(cmp.Right()).To(Equal(p2))
|
|
})
|
|
|
|
It("should support comparison with two literals", func() {
|
|
l1 := defkit.Lit(5)
|
|
l2 := defkit.Lit(10)
|
|
cmp := defkit.Lt(l1, l2)
|
|
Expect(cmp.Left()).To(Equal(l1))
|
|
Expect(cmp.Right()).To(Equal(l2))
|
|
})
|
|
})
|
|
|
|
Context("Logical Operators", func() {
|
|
var cond1, cond2, cond3 defkit.Condition
|
|
|
|
BeforeEach(func() {
|
|
p := defkit.Int("count")
|
|
cond1 = defkit.Gt(p, defkit.Lit(0))
|
|
cond2 = defkit.Lt(p, defkit.Lit(100))
|
|
cond3 = defkit.Ne(p, defkit.Lit(50))
|
|
})
|
|
|
|
Context("And", func() {
|
|
It("should create AND of two conditions", func() {
|
|
and := defkit.And(cond1, cond2)
|
|
Expect(and.Op()).To(Equal(defkit.OpAnd))
|
|
Expect(and.Conditions()).To(HaveLen(2))
|
|
Expect(and.Conditions()[0]).To(Equal(cond1))
|
|
Expect(and.Conditions()[1]).To(Equal(cond2))
|
|
})
|
|
|
|
It("should create AND of multiple conditions", func() {
|
|
and := defkit.And(cond1, cond2, cond3)
|
|
Expect(and.Op()).To(Equal(defkit.OpAnd))
|
|
Expect(and.Conditions()).To(HaveLen(3))
|
|
})
|
|
|
|
It("should support empty conditions", func() {
|
|
and := defkit.And()
|
|
Expect(and.Conditions()).To(BeEmpty())
|
|
})
|
|
})
|
|
|
|
Context("Or", func() {
|
|
It("should create OR of two conditions", func() {
|
|
or := defkit.Or(cond1, cond2)
|
|
Expect(or.Op()).To(Equal(defkit.OpOr))
|
|
Expect(or.Conditions()).To(HaveLen(2))
|
|
})
|
|
|
|
It("should create OR of multiple conditions", func() {
|
|
or := defkit.Or(cond1, cond2, cond3)
|
|
Expect(or.Conditions()).To(HaveLen(3))
|
|
})
|
|
})
|
|
|
|
Context("Not", func() {
|
|
It("should create NOT of a condition", func() {
|
|
not := defkit.Not(cond1)
|
|
Expect(not.Cond()).To(Equal(cond1))
|
|
})
|
|
|
|
It("should support negating a comparison", func() {
|
|
p := defkit.Bool("enabled")
|
|
enabled := defkit.Eq(p, defkit.Lit(true))
|
|
not := defkit.Not(enabled)
|
|
Expect(not.Cond()).To(Equal(enabled))
|
|
})
|
|
})
|
|
|
|
Context("Nested Logical Expressions", func() {
|
|
It("should support And within Or", func() {
|
|
inner := defkit.And(cond1, cond2)
|
|
outer := defkit.Or(inner, cond3)
|
|
Expect(outer.Op()).To(Equal(defkit.OpOr))
|
|
Expect(outer.Conditions()).To(HaveLen(2))
|
|
Expect(outer.Conditions()[0]).To(Equal(inner))
|
|
})
|
|
|
|
It("should support Not within And", func() {
|
|
notCond := defkit.Not(cond3)
|
|
and := defkit.And(cond1, cond2, notCond)
|
|
Expect(and.Conditions()).To(HaveLen(3))
|
|
Expect(and.Conditions()[2]).To(Equal(notCond))
|
|
})
|
|
|
|
It("should support complex nesting", func() {
|
|
// (cond1 AND cond2) OR (NOT cond3)
|
|
left := defkit.And(cond1, cond2)
|
|
right := defkit.Not(cond3)
|
|
result := defkit.Or(left, right)
|
|
Expect(result.Conditions()).To(HaveLen(2))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("CueFunc expressions", func() {
|
|
It("should create StrconvFormatInt function with correct args", func() {
|
|
num := defkit.Int("port")
|
|
fn := defkit.StrconvFormatInt(num, 10)
|
|
Expect(fn.Package()).To(Equal("strconv"))
|
|
Expect(fn.Function()).To(Equal("FormatInt"))
|
|
Expect(fn.Args()).To(HaveLen(2))
|
|
Expect(fn.Args()[0]).To(Equal(num))
|
|
Expect(fn.Args()[1]).To(Equal(defkit.Lit(10)))
|
|
})
|
|
|
|
It("should create StringsToLower function with correct arg", func() {
|
|
str := defkit.String("name")
|
|
fn := defkit.StringsToLower(str)
|
|
Expect(fn.Package()).To(Equal("strings"))
|
|
Expect(fn.Function()).To(Equal("ToLower"))
|
|
Expect(fn.Args()).To(HaveLen(1))
|
|
Expect(fn.Args()[0]).To(Equal(str))
|
|
})
|
|
|
|
It("should create StringsToUpper function with correct arg", func() {
|
|
str := defkit.String("name")
|
|
fn := defkit.StringsToUpper(str)
|
|
Expect(fn.Package()).To(Equal("strings"))
|
|
Expect(fn.Function()).To(Equal("ToUpper"))
|
|
Expect(fn.Args()).To(HaveLen(1))
|
|
Expect(fn.Args()[0]).To(Equal(str))
|
|
})
|
|
|
|
It("should create StringsHasPrefix function with correct args", func() {
|
|
str := defkit.String("path")
|
|
fn := defkit.StringsHasPrefix(str, "/api")
|
|
Expect(fn.Package()).To(Equal("strings"))
|
|
Expect(fn.Function()).To(Equal("HasPrefix"))
|
|
Expect(fn.Args()).To(HaveLen(2))
|
|
Expect(fn.Args()[0]).To(Equal(str))
|
|
Expect(fn.Args()[1]).To(Equal(defkit.Lit("/api")))
|
|
})
|
|
|
|
It("should create StringsHasSuffix function with correct args", func() {
|
|
str := defkit.String("file")
|
|
fn := defkit.StringsHasSuffix(str, ".yaml")
|
|
Expect(fn.Package()).To(Equal("strings"))
|
|
Expect(fn.Function()).To(Equal("HasSuffix"))
|
|
Expect(fn.Args()).To(HaveLen(2))
|
|
Expect(fn.Args()[0]).To(Equal(str))
|
|
Expect(fn.Args()[1]).To(Equal(defkit.Lit(".yaml")))
|
|
})
|
|
|
|
It("should create ListConcat function with correct args", func() {
|
|
list1 := defkit.List("list1")
|
|
list2 := defkit.List("list2")
|
|
fn := defkit.ListConcat(list1, list2)
|
|
Expect(fn.Package()).To(Equal("list"))
|
|
Expect(fn.Function()).To(Equal("Concat"))
|
|
Expect(fn.Args()).To(HaveLen(2))
|
|
Expect(fn.Args()[0]).To(Equal(list1))
|
|
Expect(fn.Args()[1]).To(Equal(list2))
|
|
})
|
|
})
|
|
|
|
Context("ArrayElement", func() {
|
|
It("should create new array element", func() {
|
|
elem := defkit.NewArrayElement()
|
|
Expect(elem).NotTo(BeNil())
|
|
Expect(elem.Ops()).To(BeEmpty())
|
|
Expect(elem.Fields()).To(BeEmpty())
|
|
})
|
|
|
|
It("should set fields on array element", func() {
|
|
nameLit := defkit.Lit("test")
|
|
portLit := defkit.Lit(8080)
|
|
elem := defkit.NewArrayElement().
|
|
Set("name", nameLit).
|
|
Set("port", portLit)
|
|
Expect(elem.Fields()).To(HaveLen(2))
|
|
Expect(elem.Fields()["name"]).To(Equal(nameLit))
|
|
Expect(elem.Fields()["port"]).To(Equal(portLit))
|
|
})
|
|
|
|
It("should set conditional fields on array element", func() {
|
|
enabled := defkit.Bool("enabled")
|
|
elem := defkit.NewArrayElement().
|
|
SetIf(enabled.IsTrue(), "active", defkit.Lit(true))
|
|
Expect(elem.Ops()).To(HaveLen(1))
|
|
})
|
|
|
|
It("should return fields from array element", func() {
|
|
elem := defkit.NewArrayElement().
|
|
Set("name", defkit.Lit("test"))
|
|
fields := elem.Fields()
|
|
Expect(fields).To(HaveLen(1))
|
|
})
|
|
|
|
It("should track field insertion order", func() {
|
|
elem := defkit.NewArrayElement().
|
|
Set("charlie", defkit.Lit(3)).
|
|
Set("alpha", defkit.Lit(1)).
|
|
Set("bravo", defkit.Lit(2))
|
|
Expect(elem.FieldOrder()).To(Equal([]string{"charlie", "alpha", "bravo"}))
|
|
})
|
|
|
|
It("should not duplicate field order on overwrite", func() {
|
|
elem := defkit.NewArrayElement().
|
|
Set("name", defkit.Lit("old")).
|
|
Set("port", defkit.Lit(80)).
|
|
Set("name", defkit.Lit("new"))
|
|
Expect(elem.FieldOrder()).To(Equal([]string{"name", "port"}))
|
|
Expect(elem.Fields()["name"]).To(Equal(defkit.Lit("new")))
|
|
})
|
|
})
|
|
|
|
Context("ForEachMap", func() {
|
|
It("should create ForEachMap expression", func() {
|
|
forEach := defkit.ForEachMap()
|
|
Expect(forEach).NotTo(BeNil())
|
|
Expect(forEach.Source()).To(Equal("parameter"))
|
|
Expect(forEach.KeyVar()).To(Equal("k"))
|
|
Expect(forEach.ValVar()).To(Equal("v"))
|
|
Expect(forEach.KeyExpr()).To(BeEmpty())
|
|
Expect(forEach.ValExpr()).To(BeEmpty())
|
|
})
|
|
|
|
It("should set source and variable names", func() {
|
|
forEach := defkit.ForEachMap().
|
|
Over("parameter.labels").
|
|
WithVars("k", "v")
|
|
Expect(forEach.KeyVar()).To(Equal("k"))
|
|
Expect(forEach.ValVar()).To(Equal("v"))
|
|
Expect(forEach.Source()).To(Equal("parameter.labels"))
|
|
})
|
|
|
|
It("should set key expression", func() {
|
|
forEach := defkit.ForEachMap().
|
|
Over("parameter.labels").
|
|
WithKeyExpr("k")
|
|
Expect(forEach.KeyExpr()).To(Equal("k"))
|
|
})
|
|
|
|
It("should set value expression", func() {
|
|
forEach := defkit.ForEachMap().
|
|
Over("parameter.labels").
|
|
WithValExpr("v")
|
|
Expect(forEach.ValExpr()).To(Equal("v"))
|
|
})
|
|
})
|
|
|
|
Context("ParamRef", func() {
|
|
It("should create parameter reference", func() {
|
|
ref := defkit.ParamRef("image")
|
|
Expect(ref).NotTo(BeNil())
|
|
Expect(ref.Path()).To(Equal("parameter.image"))
|
|
})
|
|
|
|
It("should reference nested parameter", func() {
|
|
ref := defkit.ParamRef("config.port")
|
|
Expect(ref).NotTo(BeNil())
|
|
Expect(ref.Path()).To(Equal("parameter.config.port"))
|
|
})
|
|
})
|
|
|
|
Context("RegexMatch", func() {
|
|
It("should create a RegexMatchCondition with source and pattern", func() {
|
|
ref := defkit.LocalField("name")
|
|
rm := defkit.RegexMatch(ref, "^test-")
|
|
Expect(rm.Pattern()).To(Equal("^test-"))
|
|
Expect(rm.Source()).To(Equal(ref))
|
|
})
|
|
|
|
It("should work with StringParam as source", func() {
|
|
p := defkit.String("host")
|
|
rm := defkit.RegexMatch(p, `^prod-.*$`)
|
|
Expect(rm.Pattern()).To(Equal(`^prod-.*$`))
|
|
Expect(rm.Source()).To(Equal(p))
|
|
})
|
|
|
|
It("should be produced by StringParam.Matches", func() {
|
|
p := defkit.String("name")
|
|
cond := p.Matches("^prod-")
|
|
rm, ok := cond.(*defkit.RegexMatchCondition)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(rm.Pattern()).To(Equal("^prod-"))
|
|
Expect(rm.Source()).To(Equal(p))
|
|
})
|
|
|
|
It("should be produced by LocalFieldRef.Matches", func() {
|
|
ref := defkit.LocalField("tenantName")
|
|
cond := ref.Matches(".*-$")
|
|
rm, ok := cond.(*defkit.RegexMatchCondition)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(rm.Pattern()).To(Equal(".*-$"))
|
|
Expect(rm.Source()).To(Equal(ref))
|
|
})
|
|
})
|
|
|
|
Context("NotExpr replaces NotCondition", func() {
|
|
It("should be returned by baseParam.NotSet", func() {
|
|
p := defkit.Int("replicas")
|
|
cond := p.NotSet()
|
|
notExpr, ok := cond.(*defkit.NotExpr)
|
|
Expect(ok).To(BeTrue(), "expected *NotExpr, got %T", cond)
|
|
inner, ok := notExpr.Cond().(*defkit.IsSetCondition)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(inner.ParamName()).To(Equal("replicas"))
|
|
})
|
|
|
|
It("should be returned by ParamNotSet", func() {
|
|
ne := defkit.ParamNotSet("defaults")
|
|
inner, ok := ne.Cond().(*defkit.IsSetCondition)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(inner.ParamName()).To(Equal("defaults"))
|
|
})
|
|
|
|
It("should be returned by LocalFieldRef.NotSet", func() {
|
|
cond := defkit.LocalField("role").NotSet()
|
|
notExpr, ok := cond.(*defkit.NotExpr)
|
|
Expect(ok).To(BeTrue())
|
|
_, ok = notExpr.Cond().(*defkit.PathExistsCondition)
|
|
Expect(ok).To(BeTrue())
|
|
})
|
|
})
|
|
|
|
Context("InlineArrayValue", func() {
|
|
It("should create inline array with fields and store correct values", func() {
|
|
port := defkit.Int("port")
|
|
arr := defkit.InlineArray(map[string]defkit.Value{
|
|
"containerPort": port,
|
|
})
|
|
Expect(arr.Fields()).To(HaveLen(1))
|
|
Expect(arr.Fields()).To(HaveKey("containerPort"))
|
|
Expect(arr.Fields()["containerPort"]).To(Equal(port))
|
|
})
|
|
|
|
It("should create inline array with multiple fields and store correct values", func() {
|
|
port := defkit.Int("port")
|
|
protocol := defkit.Lit("TCP")
|
|
arr := defkit.InlineArray(map[string]defkit.Value{
|
|
"containerPort": port,
|
|
"protocol": protocol,
|
|
})
|
|
Expect(arr.Fields()).To(HaveLen(2))
|
|
Expect(arr.Fields()["containerPort"]).To(Equal(port))
|
|
Expect(arr.Fields()["protocol"]).To(Equal(protocol))
|
|
})
|
|
|
|
It("should implement Value interface", func() {
|
|
port := defkit.Int("port")
|
|
arr := defkit.InlineArray(map[string]defkit.Value{
|
|
"containerPort": port,
|
|
})
|
|
var v defkit.Value = arr // compile-time check
|
|
Expect(v).NotTo(BeNil())
|
|
Expect(arr.Fields()["containerPort"]).To(Equal(port))
|
|
})
|
|
})
|
|
})
|