mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-20 04:56:39 +00:00
* Fix: update predicateToCUE to honor custom iteration variable and ensure Filter, Map, and Dedupe stages are rendered correctly Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * Chore: remove accidentally committed .claude-session-head Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * Fix: ensure let bindings are emitted before output block and handle guard conditions in list comprehensions Signed-off-by: Vaibhav Agrawal <vaibhav.agrawal0096@gmail.com> Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * Fix: update ArrayConcat to use list.Concat for CUE v0.11 compatibility and auto-detect required imports Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * Fix: enhance test coverage for writeMapBody field variants and import detection in trait templates Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * Fix: ensure required imports are detected for traits without a Template() function Signed-off-by: Vishal Kumar <vishal210893@gmail.com> --------- Signed-off-by: Vishal Kumar <vishal210893@gmail.com> Signed-off-by: Vaibhav Agrawal <vaibhav.agrawal0096@gmail.com> Co-authored-by: Vishal Kumar <vishal210893@gmail.com>
533 lines
15 KiB
Go
533 lines
15 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
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestStatusFieldDefault(t *testing.T) {
|
|
s := Status()
|
|
field := s.Field("status.readyReplicas").Default(0)
|
|
|
|
preamble := field.Preamble()
|
|
if !strings.Contains(preamble, "_readyReplicas:") {
|
|
t.Errorf("Expected preamble to contain _readyReplicas, got: %s", preamble)
|
|
}
|
|
if !strings.Contains(preamble, "*0 | int") {
|
|
t.Errorf("Expected preamble to have default *0 | int, got: %s", preamble)
|
|
}
|
|
if !strings.Contains(preamble, "context.output.status.readyReplicas") {
|
|
t.Errorf("Expected preamble to reference context.output.status.readyReplicas, got: %s", preamble)
|
|
}
|
|
}
|
|
|
|
func TestStatusFieldNoDefault(t *testing.T) {
|
|
s := Status()
|
|
field := s.Field("status.phase")
|
|
|
|
// No default means no preamble
|
|
preamble := field.Preamble()
|
|
if preamble != "" {
|
|
t.Errorf("Expected empty preamble for field without default, got: %s", preamble)
|
|
}
|
|
|
|
cue := field.ToCUE()
|
|
if !strings.Contains(cue, "context.output.status.phase") {
|
|
t.Errorf("Expected ToCUE to reference field directly, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestStatusSpecField(t *testing.T) {
|
|
s := Status()
|
|
field := s.SpecField("spec.replicas")
|
|
|
|
cue := field.ToCUE()
|
|
if !strings.Contains(cue, "context.output.spec.replicas") {
|
|
t.Errorf("Expected ToCUE to reference spec.replicas, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestStatusConditionMessage(t *testing.T) {
|
|
s := Status()
|
|
condExpr := s.Condition("Ready").Message()
|
|
|
|
preamble := condExpr.Preamble()
|
|
if !strings.Contains(preamble, "_readyCond:") {
|
|
t.Errorf("Expected preamble to contain _readyCond, got: %s", preamble)
|
|
}
|
|
if !strings.Contains(preamble, `cond.type == "Ready"`) {
|
|
t.Errorf("Expected preamble to filter by Ready type, got: %s", preamble)
|
|
}
|
|
if !strings.Contains(preamble, "_readyMessage:") {
|
|
t.Errorf("Expected preamble to define _readyMessage, got: %s", preamble)
|
|
}
|
|
|
|
cue := condExpr.ToCUE()
|
|
if !strings.Contains(cue, "_readyMessage") {
|
|
t.Errorf("Expected ToCUE to reference _readyMessage, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestStatusConditionStatus(t *testing.T) {
|
|
s := Status()
|
|
condExpr := s.Condition("Ready").StatusValue()
|
|
|
|
preamble := condExpr.Preamble()
|
|
if !strings.Contains(preamble, "_readyStatus:") {
|
|
t.Errorf("Expected preamble to define _readyStatus, got: %s", preamble)
|
|
}
|
|
|
|
cue := condExpr.ToCUE()
|
|
if !strings.Contains(cue, "_readyStatus") {
|
|
t.Errorf("Expected ToCUE to reference _readyStatus, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestStatusConditionReason(t *testing.T) {
|
|
s := Status()
|
|
condExpr := s.Condition("Ready").Reason()
|
|
|
|
preamble := condExpr.Preamble()
|
|
if !strings.Contains(preamble, "_readyReason:") {
|
|
t.Errorf("Expected preamble to define _readyReason, got: %s", preamble)
|
|
}
|
|
}
|
|
|
|
func TestStatusLiteral(t *testing.T) {
|
|
s := Status()
|
|
lit := s.Literal("Service is running")
|
|
|
|
if lit.Preamble() != "" {
|
|
t.Errorf("Literal should have empty preamble, got: %s", lit.Preamble())
|
|
}
|
|
|
|
cue := lit.ToCUE()
|
|
if cue != `"Service is running"` {
|
|
t.Errorf("Expected literal string, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestStatusConcat(t *testing.T) {
|
|
s := Status()
|
|
expr := s.Concat("Ready: ", s.Field("status.ready").Default(0), "/", s.SpecField("spec.replicas"))
|
|
|
|
preamble := expr.Preamble()
|
|
if !strings.Contains(preamble, "_ready:") {
|
|
t.Errorf("Expected preamble to contain field definition, got: %s", preamble)
|
|
}
|
|
|
|
cue := expr.ToCUE()
|
|
if !strings.Contains(cue, "Ready:") {
|
|
t.Errorf("Expected concat to include literal 'Ready:', got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestStatusFormat(t *testing.T) {
|
|
s := Status()
|
|
ready := s.Field("status.ready").Default(0)
|
|
total := s.SpecField("spec.replicas")
|
|
expr := s.Format("Ready: %v/%v", ready, total)
|
|
|
|
preamble := expr.Preamble()
|
|
if !strings.Contains(preamble, "_ready:") {
|
|
t.Errorf("Expected preamble to contain field definition, got: %s", preamble)
|
|
}
|
|
|
|
cue := expr.ToCUE()
|
|
if !strings.Contains(cue, "Ready:") {
|
|
t.Errorf("Expected format to include 'Ready:', got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestStatusExists(t *testing.T) {
|
|
s := Status()
|
|
expr := s.Exists("status.endpoint")
|
|
|
|
cond := expr.ToCUECondition()
|
|
expected := `context.output.status.endpoint != _|_`
|
|
if cond != expected {
|
|
t.Errorf("Expected %s, got: %s", expected, cond)
|
|
}
|
|
}
|
|
|
|
func TestStatusNotExists(t *testing.T) {
|
|
s := Status()
|
|
expr := s.NotExists("status.error")
|
|
|
|
cond := expr.ToCUECondition()
|
|
expected := `context.output.status.error == _|_`
|
|
if cond != expected {
|
|
t.Errorf("Expected %s, got: %s", expected, cond)
|
|
}
|
|
}
|
|
|
|
func TestStatusFieldConditionEq(t *testing.T) {
|
|
s := Status()
|
|
field := s.Field("status.phase")
|
|
cond := field.Eq("Running")
|
|
|
|
condCUE := cond.ToCUECondition()
|
|
expected := `context.output.status.phase == "Running"`
|
|
if condCUE != expected {
|
|
t.Errorf("Expected %s, got: %s", expected, condCUE)
|
|
}
|
|
}
|
|
|
|
func TestStatusFieldConditionGt(t *testing.T) {
|
|
s := Status()
|
|
field := s.Field("status.replicas")
|
|
cond := field.Gt(0)
|
|
|
|
condCUE := cond.ToCUECondition()
|
|
expected := `context.output.status.replicas > 0`
|
|
if condCUE != expected {
|
|
t.Errorf("Expected %s, got: %s", expected, condCUE)
|
|
}
|
|
}
|
|
|
|
func TestStatusConditionIs(t *testing.T) {
|
|
s := Status()
|
|
cond := s.Condition("Ready").Is("True")
|
|
|
|
condCUE := cond.ToCUECondition()
|
|
if !strings.Contains(condCUE, `status == "True"`) {
|
|
t.Errorf("Expected condition to check status == True, got: %s", condCUE)
|
|
}
|
|
if !strings.Contains(condCUE, "len(_readyCond) > 0") {
|
|
t.Errorf("Expected condition to check length, got: %s", condCUE)
|
|
}
|
|
}
|
|
|
|
func TestStatusSwitch(t *testing.T) {
|
|
s := Status()
|
|
expr := s.Switch(
|
|
s.Case(s.Field("status.phase").Eq("Running"), "Service is running"),
|
|
s.Case(s.Field("status.phase").Eq("Pending"), "Service is starting..."),
|
|
s.Default("Unknown status"),
|
|
)
|
|
|
|
cue := expr.(*statusSwitchExpr).BuildFull()
|
|
|
|
if !strings.Contains(cue, `message: *"Unknown status"`) {
|
|
t.Errorf("Expected default message, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, `status.phase == "Running"`) {
|
|
t.Errorf("Expected Running case, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, `status.phase == "Pending"`) {
|
|
t.Errorf("Expected Pending case, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, `"Service is running"`) {
|
|
t.Errorf("Expected running message, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestStatusHealthAware(t *testing.T) {
|
|
s := Status()
|
|
expr := s.HealthAware(
|
|
"All systems operational",
|
|
s.Concat("Degraded: ", s.Condition("Ready").Message()),
|
|
)
|
|
|
|
cue := expr.(*statusHealthAwareExpr).BuildFull()
|
|
|
|
if !strings.Contains(cue, "context.status.healthy") {
|
|
t.Errorf("Expected health-aware to check context.status.healthy, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, `"All systems operational"`) {
|
|
t.Errorf("Expected healthy message, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestStatusPolicy(t *testing.T) {
|
|
s := Status()
|
|
expr := s.Concat("Ready: ", s.Field("status.ready").Default(0))
|
|
|
|
policy := StatusPolicy(expr)
|
|
|
|
if !strings.Contains(policy, "message:") {
|
|
t.Errorf("Expected policy to contain message:, got: %s", policy)
|
|
}
|
|
if !strings.Contains(policy, "_ready:") {
|
|
t.Errorf("Expected policy to contain preamble, got: %s", policy)
|
|
}
|
|
}
|
|
|
|
func TestStatusPolicyNoPreamble(t *testing.T) {
|
|
s := Status()
|
|
policy := StatusPolicy(s.Literal("Always OK"))
|
|
|
|
expected := `message: "Always OK"`
|
|
if policy != expected {
|
|
t.Errorf("Expected %s, got: %s", expected, policy)
|
|
}
|
|
}
|
|
|
|
func TestCustomStatusExpr(t *testing.T) {
|
|
s := Status()
|
|
cue := CustomStatusExpr(s.Literal("Service ready"))
|
|
|
|
if !strings.Contains(cue, "message:") {
|
|
t.Errorf("Expected CustomStatusExpr to generate message:, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestCustomStatusExprWithSwitch(t *testing.T) {
|
|
s := Status()
|
|
cue := CustomStatusExpr(s.Switch(
|
|
s.Case(s.Field("status.phase").Eq("Running"), "OK"),
|
|
s.Default("Unknown"),
|
|
))
|
|
|
|
if !strings.Contains(cue, "message:") {
|
|
t.Errorf("Expected CustomStatusExpr to generate message:, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, `"Running"`) {
|
|
t.Errorf("Expected switch case in output, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestStatusBuilderStatusExpr(t *testing.T) {
|
|
s := Status()
|
|
s.StatusExpr(s.Concat("State: ", s.Field("status.state").Default("unknown")))
|
|
|
|
cue := s.Build()
|
|
|
|
if !strings.Contains(cue, "message:") {
|
|
t.Errorf("Expected Build to return status expression, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, "_state:") {
|
|
t.Errorf("Expected preamble in output, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestStatusBuilderRawCUE(t *testing.T) {
|
|
s := Status()
|
|
s.RawCUE(`message: "Custom CUE"`)
|
|
|
|
cue := s.Build()
|
|
expected := `message: "Custom CUE"`
|
|
if cue != expected {
|
|
t.Errorf("Expected %s, got: %s", expected, cue)
|
|
}
|
|
}
|
|
|
|
func TestStatusBuilderRawCUEOverridesFields(t *testing.T) {
|
|
s := Status()
|
|
// Set both fields and rawCUE - rawCUE should win
|
|
s.IntField("ready.replicas", "status.readyReplicas", 0)
|
|
s.Message("Ready: \\(ready.replicas)")
|
|
s.RawCUE(`message: "Override"`)
|
|
|
|
cue := s.Build()
|
|
expected := `message: "Override"`
|
|
if cue != expected {
|
|
t.Errorf("RawCUE should override field-based building, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestComplexStatusExpression(t *testing.T) {
|
|
s := Status()
|
|
// Real-world example: Database CRD status
|
|
expr := s.Concat(
|
|
"State: ", s.Field("status.state").Default("initializing"),
|
|
" | Connections: ", s.Field("status.connections").Default(0),
|
|
" | Endpoint: ", s.Field("status.endpoint").Default("pending"),
|
|
)
|
|
|
|
cue := CustomStatusExpr(expr)
|
|
|
|
// Should have all field preambles
|
|
if !strings.Contains(cue, "_state:") {
|
|
t.Errorf("Expected _state preamble, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, "_connections:") {
|
|
t.Errorf("Expected _connections preamble, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, "_endpoint:") {
|
|
t.Errorf("Expected _endpoint preamble, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, "message:") {
|
|
t.Errorf("Expected message: in output, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestCrossplaneStyleStatus(t *testing.T) {
|
|
s := Status()
|
|
// Crossplane-style status with conditions
|
|
expr := s.Switch(
|
|
s.Case(s.Condition("Ready").Is("True"),
|
|
s.Concat("Ready: ", s.Condition("Ready").Message())),
|
|
s.Case(s.Condition("Synced").Is("False"),
|
|
s.Concat("Syncing: ", s.Condition("Synced").Message())),
|
|
s.Default(s.Concat(
|
|
"Ready: ", s.Condition("Ready").StatusValue(),
|
|
" | Synced: ", s.Condition("Synced").StatusValue(),
|
|
)),
|
|
)
|
|
|
|
cue := CustomStatusExpr(expr)
|
|
|
|
// Should have condition preambles
|
|
if !strings.Contains(cue, "_readyCond:") {
|
|
t.Errorf("Expected _readyCond preamble, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, "_syncedCond:") {
|
|
t.Errorf("Expected _syncedCond preamble, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
func TestComponentDefinitionWithStatusExpr(t *testing.T) {
|
|
s := Status()
|
|
|
|
// Test that StatusExpr integrates with ComponentDefinition
|
|
comp := NewComponent("test-component").
|
|
Description("A test component").
|
|
CustomStatus(CustomStatusExpr(s.Concat(
|
|
"Ready: ", s.Field("status.ready").Default(0),
|
|
)))
|
|
|
|
status := comp.GetCustomStatus()
|
|
|
|
if !strings.Contains(status, "message:") {
|
|
t.Errorf("Expected custom status to contain message:, got: %s", status)
|
|
}
|
|
if !strings.Contains(status, "_ready:") {
|
|
t.Errorf("Expected custom status preamble, got: %s", status)
|
|
}
|
|
}
|
|
|
|
func TestStatusFieldStringDefault(t *testing.T) {
|
|
s := Status()
|
|
field := s.Field("status.phase").Default("Unknown")
|
|
|
|
preamble := field.Preamble()
|
|
if !strings.Contains(preamble, `*"Unknown" | string`) {
|
|
t.Errorf("Expected string default with quotes, got: %s", preamble)
|
|
}
|
|
}
|
|
|
|
func TestStatusFieldBoolDefault(t *testing.T) {
|
|
s := Status()
|
|
field := s.Field("status.ready").Default(false)
|
|
|
|
preamble := field.Preamble()
|
|
if !strings.Contains(preamble, `*false | bool`) {
|
|
t.Errorf("Expected bool default, got: %s", preamble)
|
|
}
|
|
}
|
|
|
|
func TestWithDetails(t *testing.T) {
|
|
s := Status()
|
|
expr := s.WithDetails(
|
|
s.Format("Ready: %v/%v", s.Field("status.readyReplicas").Default(0), s.SpecField("spec.replicas").Default(1)),
|
|
s.Detail("endpoint", s.Field("status.endpoint").Default("pending")),
|
|
s.Detail("version", s.Field("status.version").Default("unknown")),
|
|
)
|
|
|
|
// Verify it returns a valid StatusExpression
|
|
if expr == nil {
|
|
t.Fatal("WithDetails returned nil")
|
|
}
|
|
|
|
// Verify ToCUE returns the message part
|
|
cue := expr.ToCUE()
|
|
if cue == "" {
|
|
t.Error("Expected ToCUE to return non-empty string")
|
|
}
|
|
|
|
// Verify preamble includes field extractions from message and details
|
|
preamble := expr.Preamble()
|
|
if !strings.Contains(preamble, "_readyReplicas:") {
|
|
t.Errorf("Expected preamble to include _readyReplicas, got: %s", preamble)
|
|
}
|
|
if !strings.Contains(preamble, "_endpoint:") {
|
|
t.Errorf("Expected preamble to include _endpoint, got: %s", preamble)
|
|
}
|
|
if !strings.Contains(preamble, "_version:") {
|
|
t.Errorf("Expected preamble to include _version, got: %s", preamble)
|
|
}
|
|
}
|
|
|
|
func TestWithDetailsInCustomStatusExpr(t *testing.T) {
|
|
s := Status()
|
|
cue := CustomStatusExpr(s.WithDetails(
|
|
s.Literal("Service ready"),
|
|
s.Detail("port", s.Field("status.port").Default(80)),
|
|
))
|
|
|
|
if !strings.Contains(cue, "message:") {
|
|
t.Errorf("Expected message in output, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, "_port:") {
|
|
t.Errorf("Expected _port preamble in output, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
// TestFix_StatusPolicySwitch covers Bug 4: StatusPolicy(Switch(...)) used to
|
|
// emit only the default branch because buildMessageBlock ignored the cases.
|
|
// Now it must delegate to BuildFull() so every case becomes an `if cond {
|
|
// message: ... }` block.
|
|
func TestFix_StatusPolicySwitch(t *testing.T) {
|
|
s := Status()
|
|
cue := StatusPolicy(s.Switch(
|
|
s.Case(s.Field("status.phase").Eq("Running"), "Service is running"),
|
|
s.Case(s.Field("status.phase").Eq("Pending"), "Service is starting..."),
|
|
s.Default("Unknown status"),
|
|
))
|
|
|
|
if !strings.Contains(cue, `*"Unknown status" | string`) {
|
|
t.Errorf("Expected default to remain as the message base, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, `if context.output.status.phase == "Running"`) {
|
|
t.Errorf("Expected Running case block, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, `message: "Service is running"`) {
|
|
t.Errorf("Expected Running case message, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, `if context.output.status.phase == "Pending"`) {
|
|
t.Errorf("Expected Pending case block, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, `message: "Service is starting..."`) {
|
|
t.Errorf("Expected Pending case message, got: %s", cue)
|
|
}
|
|
}
|
|
|
|
// TestFix_StatusPolicyHealthAware covers the HealthAware half of Bug 4:
|
|
// StatusPolicy(HealthAware(...)) used to emit only the healthy message
|
|
// (because ToCUE() returned healthyMsg.ToCUE()) and never produced the
|
|
// `if context.status.healthy` override.
|
|
func TestFix_StatusPolicyHealthAware(t *testing.T) {
|
|
s := Status()
|
|
cue := StatusPolicy(s.HealthAware(
|
|
"All systems operational",
|
|
"Service degraded",
|
|
))
|
|
|
|
if !strings.Contains(cue, `*"Service degraded" | string`) {
|
|
t.Errorf("Expected unhealthy default, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, "if context.status.healthy") {
|
|
t.Errorf("Expected healthy override block, got: %s", cue)
|
|
}
|
|
if !strings.Contains(cue, `message: "All systems operational"`) {
|
|
t.Errorf("Expected healthy message inside override, got: %s", cue)
|
|
}
|
|
}
|