Files
Ayush KumarandGitHub 21640b55cd Refactor: sort map keys before rendering in CUE generation functions (#7073)
* Refactor: sort map keys before rendering in CUE generation functions

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Feat: add tests for alphabetical ordering of labels and outputs in CUE generation

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Feat: add tests for alphabetical ordering of computed fields and output groups in CUE generation

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

---------

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>
2026-03-18 16:00:37 +00:00

441 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_test
import (
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/oam-dev/kubevela/pkg/definition/defkit"
)
// simpleRenderValue is a test helper that renders Values the same way cuegen does for Ref types.
func simpleRenderValue(v defkit.Value) string {
if cr, ok := v.(defkit.CUERenderer); ok {
return cr.RenderCUE(simpleRenderValue)
}
// For *Ref, use the Path() method via the interface trick
type pather interface{ Path() string }
if p, ok := v.(pather); ok {
return p.Path()
}
return "_"
}
var _ = Describe("Operation Builders", func() {
Describe("KubeRead", func() {
It("should render a basic kube.#Read with name and namespace", func() {
b := defkit.KubeRead("v1", "Secret").
Name(defkit.Reference("parameter.name")).
Namespace(defkit.Reference("context.namespace"))
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("kube.#Read & {"))
Expect(cue).To(ContainSubstring(`apiVersion: "v1"`))
Expect(cue).To(ContainSubstring(`kind: "Secret"`))
Expect(cue).To(ContainSubstring("name: parameter.name"))
Expect(cue).To(ContainSubstring("namespace: context.namespace"))
Expect(cue).To(ContainSubstring("$params: value: {"))
})
It("should render kube.#Read with cluster parameter", func() {
b := defkit.KubeRead("v1", "ConfigMap").
Name(defkit.Reference("parameter.name")).
Namespace(defkit.Reference("context.namespace")).
Cluster(defkit.Reference("parameter.cluster"))
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("kube.#Read & {"))
Expect(cue).To(ContainSubstring("cluster: parameter.cluster"))
Expect(cue).To(ContainSubstring(`apiVersion: "v1"`))
Expect(cue).To(ContainSubstring(`kind: "ConfigMap"`))
// When cluster is set, $params uses expanded form
Expect(cue).NotTo(ContainSubstring("$params: value: {"))
Expect(cue).To(ContainSubstring("$params: {"))
})
It("should render without namespace when not set", func() {
b := defkit.KubeRead("apps/v1", "Deployment").
Name(defkit.Reference("parameter.name"))
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("name: parameter.name"))
Expect(cue).NotTo(ContainSubstring("namespace:"))
})
It("should render with different apiVersion and kind", func() {
b := defkit.KubeRead("core.oam.dev/v1beta1", "Application").
Name(defkit.Reference("context.name")).
Namespace(defkit.Reference("context.namespace"))
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring(`apiVersion: "core.oam.dev/v1beta1"`))
Expect(cue).To(ContainSubstring(`kind: "Application"`))
})
It("should render NamespaceIf with condition through CUE generator", func() {
ws := defkit.NewWorkflowStep("test").
Params(defkit.String("name").Required()).
Template(func(tpl *defkit.WorkflowStepTemplate) {
tpl.Set("read", defkit.KubeRead("v1", "Secret").
Name(defkit.Reference("parameter.name")).
NamespaceIf(
defkit.PathExists("parameter.namespace"),
defkit.Reference("parameter.namespace"),
),
)
})
cue := ws.ToCue()
Expect(cue).To(ContainSubstring("if parameter.namespace != _|_ {"))
Expect(cue).To(ContainSubstring("namespace: parameter.namespace"))
})
It("should render NamespaceIf without condition renderer as unconditional", func() {
b := defkit.KubeRead("v1", "Secret").
Name(defkit.Reference("parameter.name")).
NamespaceIf(
defkit.PathExists("parameter.namespace"),
defkit.Reference("parameter.namespace"),
)
// RenderCUE without condition renderer falls back to unconditional
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("namespace: parameter.namespace"))
})
It("should implement Value interface", func() {
var v defkit.Value = defkit.KubeRead("v1", "Pod").
Name(defkit.Reference("name"))
Expect(v).NotTo(BeNil())
})
})
Describe("KubeApply", func() {
It("should render a basic kube.#Apply", func() {
b := defkit.KubeApply(defkit.Reference("deployment"))
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("kube.#Apply & {"))
Expect(cue).To(ContainSubstring("value: deployment"))
Expect(cue).To(ContainSubstring("$params: {"))
})
It("should render kube.#Apply with cluster", func() {
b := defkit.KubeApply(defkit.Reference("configMap")).
Cluster(defkit.Reference("parameter.cluster"))
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("value: configMap"))
Expect(cue).To(ContainSubstring("cluster: parameter.cluster"))
})
It("should render kube.#Apply without cluster", func() {
b := defkit.KubeApply(defkit.Reference("jobValue"))
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).NotTo(ContainSubstring("cluster:"))
})
It("should implement Value interface", func() {
var v defkit.Value = defkit.KubeApply(defkit.Reference("obj"))
Expect(v).NotTo(BeNil())
})
})
Describe("HTTPPost", func() {
It("should render a basic POST request", func() {
b := defkit.HTTPPost(defkit.Reference("parameter.url")).
Body(defkit.Reference("data.value")).
Header("Content-Type", "application/json")
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("http.#HTTPDo & {"))
Expect(cue).To(ContainSubstring(`method: "POST"`))
Expect(cue).To(ContainSubstring("url: parameter.url"))
Expect(cue).To(ContainSubstring("body: data.value"))
Expect(cue).To(ContainSubstring(`header: "Content-Type": "application/json"`))
})
It("should render without body when not set", func() {
b := defkit.HTTPPost(defkit.Reference("parameter.url")).
Header("Content-Type", "application/json")
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).NotTo(ContainSubstring("body:"))
Expect(cue).To(ContainSubstring("request: {"))
})
It("should render with computed URL", func() {
b := defkit.HTTPPost(defkit.Reference("stringValue.$returns.str")).
Body(defkit.Reference("data.value")).
Header("Content-Type", "application/json")
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("url: stringValue.$returns.str"))
})
It("should implement Value interface", func() {
var v defkit.Value = defkit.HTTPPost(defkit.Reference("url"))
Expect(v).NotTo(BeNil())
})
})
Describe("ConvertString", func() {
It("should render util.#ConvertString", func() {
b := defkit.ConvertString(
defkit.Reference("base64.Decode(null, read.$returns.value.data[key])"),
)
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("util.#ConvertString & {"))
Expect(cue).To(ContainSubstring("$params: bt: base64.Decode(null, read.$returns.value.data[key])"))
})
It("should implement Value interface", func() {
var v defkit.Value = defkit.ConvertString(defkit.Reference("input"))
Expect(v).NotTo(BeNil())
})
})
Describe("WaitUntil", func() {
It("should render simple conditional wait", func() {
b := defkit.WaitUntil(
defkit.Reference("output.$returns.value.status.readyReplicas == parameter.replicas"),
)
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("builtin.#ConditionalWait & {"))
Expect(cue).To(ContainSubstring("$params: continue: output.$returns.value.status.readyReplicas == parameter.replicas"))
Expect(cue).NotTo(ContainSubstring("if "))
})
It("should render with single guard", func() {
b := defkit.WaitUntil(
defkit.Reference(`apply.$returns.value.status.apply.state == "Available"`),
).Guard(
defkit.Reference("apply.$returns.value.status"),
)
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("if apply.$returns.value.status != _|_"))
Expect(cue).To(ContainSubstring(`$params: continue: apply.$returns.value.status.apply.state == "Available"`))
})
It("should render with chained guards", func() {
b := defkit.WaitUntil(
defkit.Reference(`apply.$returns.value.status.apply.state == "Available"`),
).Guard(
defkit.Reference("apply.$returns.value.status"),
defkit.Reference("apply.$returns.value.status.apply"),
)
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("if apply.$returns.value.status != _|_ if apply.$returns.value.status.apply != _|_"))
})
It("should render MessageIf with condition through CUE generator", func() {
ws := defkit.NewWorkflowStep("test").
Params(defkit.String("name").Required()).
Template(func(tpl *defkit.WorkflowStepTemplate) {
tpl.Set("wait", defkit.WaitUntil(
defkit.Reference("job.$returns.value.status.succeeded > 0"),
).MessageIf(
defkit.PathExists("job.$returns.value.status.conditions"),
defkit.Reference("job.$returns.value.status.conditions[0].message"),
))
})
cue := ws.ToCue()
Expect(cue).To(ContainSubstring("if job.$returns.value.status.conditions != _|_ {"))
Expect(cue).To(ContainSubstring("$params: message: job.$returns.value.status.conditions[0].message"))
})
It("should implement Value interface", func() {
var v defkit.Value = defkit.WaitUntil(defkit.Reference("true"))
Expect(v).NotTo(BeNil())
})
})
Describe("Fail", func() {
It("should render builtin.#Fail with message", func() {
b := defkit.Fail(defkit.Reference("check.$returns.message"))
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring("builtin.#Fail & {"))
Expect(cue).To(ContainSubstring("$params: message: check.$returns.message"))
})
It("should render with literal message", func() {
b := defkit.Fail(defkit.Reference(`"failed to execute command"`))
cue := b.RenderCUE(simpleRenderValue)
Expect(cue).To(ContainSubstring(`$params: message: "failed to execute command"`))
})
It("should implement Value interface", func() {
var v defkit.Value = defkit.Fail(defkit.Reference("msg"))
Expect(v).NotTo(BeNil())
})
})
Describe("HTTPPost headers ordering", func() {
It("should render headers sorted alphabetically in CUE", func() {
b := defkit.HTTPPost(defkit.Reference("parameter.url")).
Header("Z-Custom", "z-val").
Header("Authorization", "Bearer token").
Header("Content-Type", "application/json")
cue := b.RenderCUE(simpleRenderValue)
authIdx := strings.Index(cue, `"Authorization"`)
ctIdx := strings.Index(cue, `"Content-Type"`)
zIdx := strings.Index(cue, `"Z-Custom"`)
Expect(authIdx).To(BeNumerically(">=", 0))
Expect(ctIdx).To(BeNumerically(">=", 0))
Expect(zIdx).To(BeNumerically(">=", 0))
Expect(authIdx).To(BeNumerically("<", ctIdx))
Expect(ctIdx).To(BeNumerically("<", zIdx))
})
})
Describe("CUERenderer integration via valueToCUE", func() {
It("should render KubeRead through CUE generator", func() {
ws := defkit.NewWorkflowStep("test").
Params(defkit.String("name").Required()).
Template(func(tpl *defkit.WorkflowStepTemplate) {
tpl.Set("output", defkit.KubeRead("v1", "Pod").
Name(defkit.Reference("parameter.name")).
Namespace(defkit.Reference("context.namespace")),
)
})
cue := ws.ToCue()
Expect(cue).To(ContainSubstring("kube.#Read & {"))
Expect(cue).To(ContainSubstring(`apiVersion: "v1"`))
Expect(cue).To(ContainSubstring(`kind: "Pod"`))
Expect(cue).To(ContainSubstring("name: parameter.name"))
Expect(cue).To(ContainSubstring("namespace: context.namespace"))
})
It("should render HTTPPost through CUE generator", func() {
ws := defkit.NewWorkflowStep("test").
Params(defkit.String("url").Required()).
Template(func(tpl *defkit.WorkflowStepTemplate) {
tpl.Set("req", defkit.HTTPPost(defkit.Reference("parameter.url")).
Body(defkit.Reference("data.value")).
Header("Content-Type", "application/json"),
)
})
cue := ws.ToCue()
Expect(cue).To(ContainSubstring("http.#HTTPDo & {"))
Expect(cue).To(ContainSubstring(`method: "POST"`))
Expect(cue).To(ContainSubstring("url: parameter.url"))
})
It("should render ConvertString through CUE generator", func() {
ws := defkit.NewWorkflowStep("test").
Params(defkit.String("name").Required()).
Template(func(tpl *defkit.WorkflowStepTemplate) {
tpl.Set("convert", defkit.ConvertString(
defkit.Reference("base64.Decode(null, data)"),
))
})
cue := ws.ToCue()
Expect(cue).To(ContainSubstring("util.#ConvertString & {"))
Expect(cue).To(ContainSubstring("$params: bt: base64.Decode(null, data)"))
})
It("should render WaitUntil with Guard through CUE generator", func() {
ws := defkit.NewWorkflowStep("test").
Params(defkit.String("name").Required()).
Template(func(tpl *defkit.WorkflowStepTemplate) {
tpl.Set("wait", defkit.WaitUntil(
defkit.Reference("job.$returns.value.status.succeeded > 0"),
).Guard(
defkit.Reference("job.$returns.value.status"),
defkit.Reference("job.$returns.value.status.succeeded"),
))
})
cue := ws.ToCue()
Expect(cue).To(ContainSubstring("builtin.#ConditionalWait & {"))
Expect(cue).To(ContainSubstring("if job.$returns.value.status != _|_ if job.$returns.value.status.succeeded != _|_"))
Expect(cue).To(ContainSubstring("$params: continue: job.$returns.value.status.succeeded > 0"))
})
It("should render WaitUntil without Guard through CUE generator", func() {
ws := defkit.NewWorkflowStep("test").
Params(defkit.String("name").Required()).
Template(func(tpl *defkit.WorkflowStepTemplate) {
tpl.Set("wait", defkit.WaitUntil(
defkit.Reference("output.$returns.value.status.ready == true"),
))
})
cue := ws.ToCue()
Expect(cue).To(ContainSubstring("builtin.#ConditionalWait & {"))
Expect(cue).To(ContainSubstring("$params: continue: output.$returns.value.status.ready == true"))
Expect(cue).NotTo(ContainSubstring("if "))
})
It("should render Fail through CUE generator", func() {
ws := defkit.NewWorkflowStep("test").
Params(defkit.String("name").Required()).
Template(func(tpl *defkit.WorkflowStepTemplate) {
tpl.Set("error", defkit.Fail(
defkit.Reference(`"operation failed"`),
))
})
cue := ws.ToCue()
Expect(cue).To(ContainSubstring("builtin.#Fail & {"))
Expect(cue).To(ContainSubstring(`$params: message: "operation failed"`))
})
})
})