Files
kubevela/pkg/definition/defkit/validator_test.go
Ayush Kumar 012a134829 Feat: extend fluent builder API validator patterns (#7092)
* 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>
2026-04-09 14:25:06 +01:00

628 lines
22 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("Validator", func() {
var gen *defkit.CUEGenerator
BeforeEach(func() {
gen = defkit.NewCUEGenerator()
})
// --- Validator builder and accessors ---
Context("Builder and Accessors", func() {
It("should create a validator with message", func() {
v := defkit.Validate("field is required")
Expect(v.Message()).To(Equal("field is required"))
Expect(v.CUEName()).To(BeEmpty())
Expect(v.GuardCondition()).To(BeNil())
Expect(v.FailCondition()).To(BeNil())
})
It("should set and return WithName", func() {
v := defkit.Validate("msg").WithName("_validateFoo")
Expect(v.CUEName()).To(Equal("_validateFoo"))
})
It("should set and return FailWhen condition", func() {
cond := defkit.LocalField("x").Eq("")
v := defkit.Validate("msg").FailWhen(cond)
Expect(v.FailCondition()).To(Equal(cond))
})
It("should set and return OnlyWhen guard condition", func() {
guard := defkit.Bool("flag").Eq(true)
v := defkit.Validate("msg").OnlyWhen(guard)
Expect(v.GuardCondition()).To(Equal(guard))
})
It("should support full builder chain returning all accessors correctly", func() {
guard := defkit.Bool("x").Eq(true)
fail := defkit.LocalField("y").Eq("")
v := defkit.Validate("y is required").
WithName("_validateY").
OnlyWhen(guard).
FailWhen(fail)
Expect(v.Message()).To(Equal("y is required"))
Expect(v.CUEName()).To(Equal("_validateY"))
Expect(v.GuardCondition()).To(Equal(guard))
Expect(v.FailCondition()).To(Equal(fail))
})
})
// --- Validator CUE generation ---
Context("Unguarded Validator CUE Generation", func() {
It("should generate a basic unguarded validator block", func() {
v := defkit.Validate("tenantName must not end with a hyphen").
WithName("_validateTenantName").
FailWhen(defkit.LocalField("tenantName").Matches(".*-$"))
comp := defkit.NewComponent("test").
Params(defkit.String("tenantName")).
Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("_validateTenantName:"))
Expect(cue).To(ContainSubstring(`"tenantName must not end with a hyphen": true`))
Expect(cue).To(ContainSubstring(`tenantName =~ ".*-$"`))
Expect(cue).To(ContainSubstring(`"tenantName must not end with a hyphen": false`))
})
It("should use fallback _validate name when no name is set", func() {
v := defkit.Validate("something is wrong")
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("_validate:"))
})
It("should generate validator with no fail condition", func() {
v := defkit.Validate("always passes").WithName("_validateAlways")
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring(`_validateAlways:`))
Expect(cue).To(ContainSubstring(`"always passes": true`))
Expect(cue).NotTo(ContainSubstring(`"always passes": false`))
})
})
Context("Guarded Validator CUE Generation", func() {
It("should wrap validator in guard condition", func() {
replConfig := defkit.Object("replicationConfiguration").Optional()
objectLock := defkit.Object("objectLock").Optional()
versioningEnabled := defkit.Bool("versioningEnabled").Default(true)
v := defkit.Validate("Require versioningEnabled to be true when replication or object lock is configured").
WithName("_validateVersioning").
OnlyWhen(defkit.Or(replConfig.IsSet(), objectLock.IsSet())).
FailWhen(versioningEnabled.Eq(false))
comp := defkit.NewComponent("test").
Params(replConfig, objectLock, versioningEnabled).
Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring(`replicationConfiguration"] != _|_`))
Expect(cue).To(ContainSubstring(`objectLock"] != _|_`))
Expect(cue).To(ContainSubstring("parameter.versioningEnabled == false"))
Expect(cue).To(ContainSubstring("_validateVersioning:"))
})
})
Context("Validator inside MapParam", func() {
It("should emit validator inside struct", func() {
v := defkit.Validate("name is required").
WithName("_validateName").
FailWhen(defkit.LocalField("name").Eq(""))
mp := defkit.Object("governance").WithFields(
defkit.String("name"),
defkit.String("department"),
).Validators(v)
comp := defkit.NewComponent("test").Params(mp)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("governance: {"))
Expect(cue).To(ContainSubstring("_validateName:"))
Expect(cue).To(ContainSubstring(`name == ""`))
})
It("should emit mutual exclusion validator inside struct", func() {
v := defkit.Validate("Principal and NotPrincipal cannot be used together").
WithName("_validatePrincipal").
FailWhen(defkit.And(defkit.LocalField("Principal").IsSet(), defkit.LocalField("NotPrincipal").IsSet()))
comp := defkit.NewComponent("test").
Params(
defkit.Object("statement").WithFields(
defkit.String("Principal").Optional(),
defkit.String("NotPrincipal").Optional(),
).Validators(v),
)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("_validatePrincipal:"))
Expect(cue).To(ContainSubstring("Principal != _|_"))
Expect(cue).To(ContainSubstring("NotPrincipal != _|_"))
})
})
Context("Validator inside ArrayParam", func() {
It("should emit validator inside array element struct", func() {
v := defkit.Validate("action is required").
WithName("_validateAction").
FailWhen(defkit.LocalField("action").Eq(""))
arr := defkit.Array("rules").WithFields(
defkit.String("action"),
defkit.String("resource"),
).Validators(v)
comp := defkit.NewComponent("test").Params(arr)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("[...{"))
Expect(cue).To(ContainSubstring("_validateAction:"))
})
It("should emit non-empty length validator inside array element struct", func() {
arr := defkit.Array("corsRules").Optional().WithFields(
defkit.Array("allowedMethods").OfEnum("GET", "PUT", "HEAD", "POST", "DELETE"),
).Validators(
defkit.Validate("allowedMethods cannot be empty").
WithName("_validateAllowedMethods").
FailWhen(defkit.LocalField("allowedMethods").IsEmpty()),
)
comp := defkit.NewComponent("test").Params(arr)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("len(allowedMethods) == 0"))
})
})
// --- LocalFieldRef ---
Context("LocalFieldRef", func() {
It("should return the field name", func() {
ref := defkit.LocalField("tenantName")
Expect(ref.Name()).To(Equal("tenantName"))
})
It("should support dot-path names", func() {
ref := defkit.LocalField("Principal.AWS")
Expect(ref.Name()).To(Equal("Principal.AWS"))
})
It("should support array-indexed names", func() {
ref := defkit.LocalField("expiration[0].date")
Expect(ref.Name()).To(Equal("expiration[0].date"))
})
It("should implement Value interface", func() {
ref := defkit.LocalField("test")
var v defkit.Value = ref
Expect(v).NotTo(BeNil())
})
It("Matches should generate regex match CUE condition", func() {
v := defkit.Validate("bad pattern").
WithName("_v").
FailWhen(defkit.LocalField("name").Matches(".*-$"))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring(`name =~ ".*-$"`))
})
It("Eq should generate equality CUE condition", func() {
v := defkit.Validate("check").
WithName("_v").
FailWhen(defkit.LocalField("type").Eq("disabled"))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring(`type == "disabled"`))
})
It("Ne should generate inequality CUE condition", func() {
v := defkit.Validate("check").
WithName("_v").
FailWhen(defkit.LocalField("type").Ne("enabled"))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring(`type != "enabled"`))
})
It("IsSet should generate path-exists CUE condition", func() {
v := defkit.Validate("check").
WithName("_v").
FailWhen(defkit.Not(defkit.LocalField("role").IsSet()))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("role == _|_"))
})
It("NotSet should generate path-not-exists CUE condition", func() {
v := defkit.Validate("role must be set").
WithName("_v").
FailWhen(defkit.LocalField("role").NotSet())
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("role == _|_"))
})
It("LenEq should generate length equality CUE condition", func() {
v := defkit.Validate("check").
WithName("_v").
FailWhen(defkit.LocalField("Principal.AWS").LenEq(0))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("len(Principal.AWS) == 0"))
})
It("LenGt should generate length greater-than CUE condition", func() {
v := defkit.Validate("too many").
WithName("_v").
FailWhen(defkit.LocalField("items").LenGt(10))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("len(items) > 10"))
})
It("IsEmpty should generate length == 0 CUE condition", func() {
v := defkit.Validate("empty").
WithName("_v").
FailWhen(defkit.LocalField("list").IsEmpty())
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("len(list) == 0"))
})
It("Gte should generate >= comparison between two local fields", func() {
v := defkit.Validate("days must be >= minDays").
WithName("_v").
FailWhen(defkit.Not(defkit.LocalField("days").Gte(defkit.LocalField("minDays"))))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("days >= minDays"))
})
})
// --- LenOfExpr ---
Context("LenOfExpr", func() {
It("Gt should generate len(value) > n", func() {
v := defkit.Validate("too long").
WithName("_v").
FailWhen(defkit.LenOf(defkit.LocalField("name")).Gt(63))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("len(name) > 63"))
})
It("Gte should generate len(value) >= n", func() {
v := defkit.Validate("too long").
WithName("_v").
FailWhen(defkit.LenOf(defkit.LocalField("data")).Gte(100))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("len(data) >= 100"))
})
It("Eq should generate len(value) == n", func() {
v := defkit.Validate("wrong length").
WithName("_v").
FailWhen(defkit.Not(defkit.LenOf(defkit.LocalField("code")).Eq(3)))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("len(code) == 3"))
})
It("should work with complex expressions like Plus", func() {
v := defkit.Validate("combined name too long").
WithName("_v").
FailWhen(defkit.LenOf(defkit.Plus(
defkit.Lit("tenant-"),
defkit.Reference("parameter.name"),
)).Gt(63))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring(`len("tenant-" + parameter.name) > 63`))
})
})
// --- TimeParse ---
Context("TimeParse", func() {
It("should return correct accessors", func() {
tp := defkit.TimeParse("2006-01-02T15:04:05Z", defkit.LocalField("startDate"))
Expect(tp.Layout()).To(Equal("2006-01-02T15:04:05Z"))
Expect(tp.FieldName()).To(Equal("startDate"))
})
It("should generate time.Parse CUE expression in validator", func() {
v := defkit.Validate("start must be before end").
WithName("_v").
FailWhen(defkit.TimeParse("2006-01-02T15:04:05Z", defkit.LocalField("startDate")).
Gte(defkit.TimeParse("2006-01-02T15:04:05Z", defkit.LocalField("endDate"))))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring(`time.Parse("2006-01-02T15:04:05Z", startDate)`))
Expect(cue).To(ContainSubstring(`time.Parse("2006-01-02T15:04:05Z", endDate)`))
Expect(cue).To(ContainSubstring(">="))
})
It("should use different layout formats", func() {
v := defkit.Validate("check").
WithName("_v").
FailWhen(defkit.TimeParse("2006-01-02", defkit.LocalField("d1")).
Gte(defkit.TimeParse("2006-01-02", defkit.LocalField("d2"))))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring(`time.Parse("2006-01-02", d1)`))
Expect(cue).To(ContainSubstring(`time.Parse("2006-01-02", d2)`))
})
})
// --- RawCUECondition / CUEExpr ---
Context("CUEExpr", func() {
It("should return raw expression from accessor", func() {
c := defkit.CUEExpr(`len(x) > 5`)
Expect(c.Expr()).To(Equal(`len(x) > 5`))
})
It("should emit raw expression in guarded validator", func() {
existingResources := defkit.Bool("existingResources").Default(false)
v := defkit.Validate("Combined name must be less than 64 characters").
WithName("_validateNameLength").
OnlyWhen(existingResources.Eq(false)).
FailWhen(defkit.CUEExpr(`len("tenant-"+parameter.governance.tenantName+"-"+name) > 63`))
comp := defkit.NewComponent("test").
Params(existingResources).
Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring(`len("tenant-"+parameter.governance.tenantName+"-"+name) > 63`))
Expect(cue).To(ContainSubstring("if parameter.existingResources == false"))
})
It("should work inside And condition", func() {
v := defkit.Validate("complex check").
WithName("_validateComplex").
FailWhen(defkit.And(
defkit.CUEExpr(`len(parameter.name) > 10`),
defkit.CUEExpr(`parameter.name =~ "^test"`),
))
comp := defkit.NewComponent("test").Validators(v)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("len(parameter.name) > 10"))
Expect(cue).To(ContainSubstring(`parameter.name =~ "^test"`))
})
})
// --- ConditionalParams CUE generation ---
Context("ConditionalParams CUE Generation", func() {
It("should generate conditional parameter blocks", func() {
existingResources := defkit.Bool("existingResources").Default(false)
comp := defkit.NewComponent("test").
Params(existingResources).
ConditionalParams(defkit.ConditionalParams(
defkit.WhenParam(existingResources.Eq(false)).Params(
defkit.Bool("forceDestroy").Default(false),
defkit.String("sseAlgorithm").Default("AES256").Values("AES256", "aws:kms"),
),
defkit.WhenParam(existingResources.Eq(true)).Params(
defkit.Bool("forceDestroy").Optional(),
defkit.String("sseAlgorithm").Optional().Values("AES256", "aws:kms"),
),
))
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("if parameter.existingResources == false"))
Expect(cue).To(ContainSubstring("if parameter.existingResources == true"))
Expect(cue).To(ContainSubstring(`forceDestroy: *false | bool`))
Expect(cue).To(ContainSubstring("forceDestroy?: bool"))
})
It("should generate validators inside conditional blocks", func() {
existingResources := defkit.Bool("existingResources").Default(false)
kmsMasterKeyId := defkit.String("kmsMasterKeyId").Optional()
comp := defkit.NewComponent("test").
Params(existingResources, kmsMasterKeyId).
ConditionalParams(defkit.ConditionalParams(
defkit.WhenParam(existingResources.Eq(false)).Params(
defkit.String("sseAlgorithm").Default("AES256").Values("AES256", "aws:kms"),
).Validators(
defkit.Validate("kmsMasterKeyId can only be specified when sseAlgorithm is aws:kms").
WithName("_validateKms").
FailWhen(defkit.And(
defkit.LocalField("sseAlgorithm").Ne("aws:kms"),
kmsMasterKeyId.IsSet(),
)),
),
))
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("_validateKms:"))
Expect(cue).To(ContainSubstring(`sseAlgorithm != "aws:kms"`))
})
})
Context("ConditionalFields Inside MapParam CUE Generation", func() {
It("should generate conditional fields inside struct", func() {
existingResources := defkit.Bool("existingResources").Default(false)
objectLock := defkit.Object("objectLock").Optional().ConditionalFields(
defkit.WhenParam(existingResources.Eq(false)).Params(
defkit.Int("retentionDays").Optional().Default(45).Min(1),
defkit.String("retentionMode").Optional().Default("GOVERNANCE").Values("GOVERNANCE", "COMPLIANCE"),
),
defkit.WhenParam(existingResources.Eq(true)).Params(
defkit.Int("retentionDays").Min(1),
defkit.String("retentionMode").Values("GOVERNANCE", "COMPLIANCE"),
),
)
comp := defkit.NewComponent("test").Params(existingResources, objectLock)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("objectLock?: {"))
Expect(cue).To(ContainSubstring("if parameter.existingResources == false"))
Expect(cue).To(ContainSubstring(`retentionDays?: *45 | int & >=1`))
})
})
// --- ConditionalStruct on Resource CUE generation ---
Context("ConditionalStruct CUE Generation", func() {
It("should generate conditional struct block in output", func() {
replConfig := defkit.Object("replicationConfiguration").Optional()
comp := defkit.NewComponent("test").
Params(replConfig).
Workload("apps/v1", "Deployment").
Template(func(tpl *defkit.Template) {
output := tpl.Output(defkit.NewResource("v1", "ConfigMap"))
output.Set("metadata.name", defkit.Lit("test")).
ConditionalStruct(replConfig.IsSet(), "spec.replicationConfiguration", func(b *defkit.OutputStructBuilder) {
b.Set("role", defkit.Reference("parameter.replicationConfiguration.role"))
b.SetIf(replConfig.IsSet(), "enabled", defkit.Lit(true))
})
})
cue := gen.GenerateTemplate(comp)
Expect(cue).To(ContainSubstring(`if parameter["replicationConfiguration"] != _|_`))
Expect(cue).To(ContainSubstring("replicationConfiguration:"))
Expect(cue).To(ContainSubstring("role: parameter.replicationConfiguration.role"))
Expect(cue).To(ContainSubstring("enabled:"))
})
It("should generate conditional struct with SetIf inside", func() {
existingResources := defkit.Bool("existingResources").Default(false)
replConfig := defkit.Object("replicationConfiguration").Optional()
comp := defkit.NewComponent("test").
Params(existingResources, replConfig).
Workload("apps/v1", "Deployment").
Template(func(tpl *defkit.Template) {
output := tpl.Output(defkit.NewResource("v1", "ConfigMap"))
output.Set("metadata.name", defkit.Lit("test")).
ConditionalStruct(replConfig.IsSet(), "spec.replication", func(b *defkit.OutputStructBuilder) {
b.Set("role", defkit.Reference("parameter.replicationConfiguration.role"))
b.SetIf(existingResources.Eq(false), "destinationBucketName", defkit.Lit("replica-bucket"))
})
})
cue := gen.GenerateTemplate(comp)
Expect(cue).To(ContainSubstring("parameter.existingResources == false"))
Expect(cue).To(ContainSubstring("destinationBucketName:"))
})
})
// --- Integration: all features combined ---
Context("All Features Integrated", func() {
It("should combine validators, conditional params, closed structs, and CUE expressions", func() {
existingResources := defkit.Bool("existingResources").Default(false)
governance := defkit.Object("governance").Closed().WithFields(
defkit.String("tenantName").NotEmpty(),
defkit.String("departmentCode").NotEmpty(),
).Validators(
defkit.Validate("tenantName must not end with a hyphen").
WithName("_validateTenant").
FailWhen(defkit.LocalField("tenantName").Matches(".*-$")),
)
comp := defkit.NewComponent("s3-bucket").
Params(existingResources, governance).
ConditionalParams(defkit.ConditionalParams(
defkit.WhenParam(existingResources.Eq(false)).Params(
defkit.Bool("forceDestroy").Default(false),
),
defkit.WhenParam(existingResources.Eq(true)).Params(
defkit.Bool("forceDestroy").Optional(),
),
)).
Validators(
defkit.Validate("Combined name check").
WithName("_validateName").
OnlyWhen(existingResources.Eq(false)).
FailWhen(defkit.CUEExpr(`len(parameter.governance.tenantName) > 63`)),
)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("existingResources: *false | bool"))
Expect(cue).To(ContainSubstring("governance: close({"))
Expect(cue).To(ContainSubstring(`!=""`))
// Validator uses Matches which emits =~ (positive match inside fail block)
Expect(cue).To(ContainSubstring(`tenantName =~ ".*-$"`))
Expect(cue).To(ContainSubstring("_validateTenant:"))
Expect(cue).To(ContainSubstring("if parameter.existingResources == false"))
Expect(cue).To(ContainSubstring("if parameter.existingResources == true"))
Expect(cue).To(ContainSubstring("forceDestroy: *false | bool"))
Expect(cue).To(ContainSubstring("forceDestroy?: bool"))
Expect(cue).To(ContainSubstring("_validateName:"))
Expect(cue).To(ContainSubstring(`len(parameter.governance.tenantName) > 63`))
})
})
})