mirror of https://github.com/kubevela/kubevela.git
Compare commits
7 Commits
e3d9733d81
...
e674b840b6
| Author | SHA1 | Date |
|---|---|---|
|
|
e674b840b6 | |
|
|
ebf73d03c2 | |
|
|
4a886b14b7 | |
|
|
9901e73fe5 | |
|
|
a8393c40de | |
|
|
cbe8439d0b | |
|
|
66c47910f7 |
|
|
@ -1,35 +1,35 @@
|
|||
# This file is a github code protect rule follow the codeowners https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners#example-of-a-codeowners-file
|
||||
|
||||
* @barnettZQG @wonderflow @leejanee @Somefive @jefree-cat @FogDong @wangyikewxgm @chivalryq @anoop2811
|
||||
design/ @barnettZQG @leejanee @wonderflow @Somefive @jefree-cat @FogDong @anoop2811
|
||||
* @barnettZQG @wonderflow @leejanee @Somefive @jefree-cat @FogDong @wangyikewxgm @chivalryq @anoop2811 @briankane @jguionnet
|
||||
design/ @barnettZQG @leejanee @wonderflow @Somefive @jefree-cat @FogDong @anoop2811 @briankane @jguionnet
|
||||
|
||||
# Owner of Core Controllers
|
||||
pkg/controller/core.oam.dev @Somefive @FogDong @barnettZQG @wonderflow @wangyikewxgm @chivalryq @anoop2811
|
||||
pkg/controller/core.oam.dev @Somefive @FogDong @barnettZQG @wonderflow @wangyikewxgm @chivalryq @anoop2811 @briankane @jguionnet
|
||||
|
||||
# Owner of Standard Controllers
|
||||
pkg/controller/standard.oam.dev @wangyikewxgm @barnettZQG @wonderflow @Somefive @anoop2811 @FogDong
|
||||
pkg/controller/standard.oam.dev @wangyikewxgm @barnettZQG @wonderflow @Somefive @anoop2811 @FogDong @briankane @jguionnet
|
||||
|
||||
# Owner of CUE
|
||||
pkg/cue @leejanee @FogDong @Somefive @anoop2811
|
||||
pkg/stdlib @leejanee @FogDong @Somefive @anoop2811
|
||||
pkg/cue @leejanee @FogDong @Somefive @anoop2811 @briankane @jguionnet
|
||||
pkg/stdlib @leejanee @FogDong @Somefive @anoop2811 @briankane @jguionnet
|
||||
|
||||
# Owner of Workflow
|
||||
pkg/workflow @leejanee @FogDong @Somefive @wangyikewxgm @chivalryq @anoop2811
|
||||
pkg/workflow @leejanee @FogDong @Somefive @wangyikewxgm @chivalryq @anoop2811 @briankane @jguionnet
|
||||
|
||||
# Owner of vela templates
|
||||
vela-templates/ @Somefive @barnettZQG @wonderflow @FogDong @wangyikewxgm @chivalryq @anoop2811
|
||||
vela-templates/ @Somefive @barnettZQG @wonderflow @FogDong @wangyikewxgm @chivalryq @anoop2811 @briankane @jguionnet
|
||||
|
||||
# Owner of vela CLI
|
||||
references/cli/ @Somefive @StevenLeiZhang @charlie0129 @wangyikewxgm @chivalryq @anoop2811 @FogDong
|
||||
references/cli/ @Somefive @StevenLeiZhang @charlie0129 @wangyikewxgm @chivalryq @anoop2811 @FogDong @briankane @jguionnet
|
||||
|
||||
# Owner of vela addon framework
|
||||
pkg/addon/ @wangyikewxgm @wonderflow @charlie0129 @anoop2811 @FogDong
|
||||
pkg/addon/ @wangyikewxgm @wonderflow @charlie0129 @anoop2811 @FogDong @briankane @jguionnet
|
||||
|
||||
# Owner of resource keeper and tracker
|
||||
pkg/resourcekeeper @Somefive @FogDong @chivalryq @anoop2811
|
||||
pkg/resourcetracker @Somefive @FogDong @chivalryq @anoop2811
|
||||
pkg/resourcekeeper @Somefive @FogDong @chivalryq @anoop2811 @briankane @jguionnet
|
||||
pkg/resourcetracker @Somefive @FogDong @chivalryq @anoop2811 @briankane @jguionnet
|
||||
|
||||
.github/ @chivalryq @wonderflow @Somefive @FogDong @wangyikewxgm @anoop2811
|
||||
makefiles @chivalryq @wonderflow @Somefive @FogDong @wangyikewxgm @anoop2811
|
||||
go.* @chivalryq @wonderflow @Somefive @FogDong @wangyikewxgm @anoop2811
|
||||
.github/ @chivalryq @wonderflow @Somefive @FogDong @wangyikewxgm @anoop2811 @briankane @jguionnet
|
||||
makefiles @chivalryq @wonderflow @Somefive @FogDong @wangyikewxgm @anoop2811 @briankane @jguionnet
|
||||
go.* @chivalryq @wonderflow @Somefive @FogDong @wangyikewxgm @anoop2811 @briankane @jguionnet
|
||||
|
||||
|
|
|
|||
|
|
@ -11,4 +11,7 @@ wangyuan249
|
|||
chivalryq
|
||||
FogDong
|
||||
leejanee
|
||||
barnettZQG
|
||||
barnettZQG
|
||||
anoop2811
|
||||
briankane
|
||||
jguionnet
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
Copyright 2022 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 watcher
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
func TestApplicationMetricsWatcher(t *testing.T) {
|
||||
|
||||
appRunning := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "app-running"},
|
||||
Status: common.AppStatus{
|
||||
Phase: common.ApplicationRunning,
|
||||
},
|
||||
}
|
||||
appRendering := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "app-rendering"},
|
||||
Status: common.AppStatus{
|
||||
Phase: common.ApplicationRendering,
|
||||
},
|
||||
}
|
||||
appWithWorkflow := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "app-with-workflow"},
|
||||
Status: common.AppStatus{
|
||||
Phase: common.ApplicationRunning,
|
||||
Workflow: &common.WorkflowStatus{
|
||||
Steps: []workflowv1alpha1.WorkflowStepStatus{
|
||||
{
|
||||
StepStatus: workflowv1alpha1.StepStatus{
|
||||
Name: "step1",
|
||||
Type: "apply-component",
|
||||
Phase: workflowv1alpha1.WorkflowStepPhaseSucceeded,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
app *v1beta1.Application
|
||||
op int
|
||||
wantPC map[string]int
|
||||
wantSC map[string]int
|
||||
wantPD map[string]struct{}
|
||||
wantSD map[string]struct{}
|
||||
}{
|
||||
"Add an application": {
|
||||
app: appRunning,
|
||||
op: 1,
|
||||
wantPC: map[string]int{"running": 1},
|
||||
wantSC: map[string]int{},
|
||||
wantPD: map[string]struct{}{"running": {}},
|
||||
wantSD: map[string]struct{}{},
|
||||
},
|
||||
"Add an application with workflow": {
|
||||
app: appWithWorkflow,
|
||||
op: 1,
|
||||
wantPC: map[string]int{"running": 1},
|
||||
wantSC: map[string]int{"apply-component/succeeded#": 1},
|
||||
wantPD: map[string]struct{}{"running": {}},
|
||||
wantSD: map[string]struct{}{"apply-component/succeeded#": {}},
|
||||
},
|
||||
"Delete an application": {
|
||||
app: appRunning,
|
||||
op: -1,
|
||||
wantPC: map[string]int{"running": -1},
|
||||
wantSC: map[string]int{},
|
||||
wantPD: map[string]struct{}{"running": {}},
|
||||
wantSD: map[string]struct{}{},
|
||||
},
|
||||
"Update an application": {
|
||||
app: appRendering,
|
||||
op: -1,
|
||||
wantPC: map[string]int{"rendering": -1},
|
||||
wantSC: map[string]int{},
|
||||
wantPD: map[string]struct{}{"rendering": {}},
|
||||
wantSD: map[string]struct{}{},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
watcher := &applicationMetricsWatcher{
|
||||
phaseCounter: map[string]int{},
|
||||
stepPhaseCounter: map[string]int{},
|
||||
phaseDirty: map[string]struct{}{},
|
||||
stepPhaseDirty: map[string]struct{}{},
|
||||
}
|
||||
watcher.inc(tc.app, tc.op)
|
||||
assert.Equal(t, tc.wantPC, watcher.phaseCounter)
|
||||
assert.Equal(t, tc.wantSC, watcher.stepPhaseCounter)
|
||||
assert.Equal(t, tc.wantPD, watcher.phaseDirty)
|
||||
assert.Equal(t, tc.wantSD, watcher.stepPhaseDirty)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Report should clear dirty flags", func(t *testing.T) {
|
||||
watcher := &applicationMetricsWatcher{
|
||||
phaseCounter: map[string]int{},
|
||||
stepPhaseCounter: map[string]int{},
|
||||
phaseDirty: map[string]struct{}{"running": {}},
|
||||
stepPhaseDirty: map[string]struct{}{"apply-component/succeeded#": {}},
|
||||
}
|
||||
watcher.report()
|
||||
assert.Empty(t, watcher.phaseDirty)
|
||||
assert.Empty(t, watcher.stepPhaseDirty)
|
||||
})
|
||||
|
||||
t.Run("getPhase helper function", func(t *testing.T) {
|
||||
watcher := &applicationMetricsWatcher{}
|
||||
assert.Equal(t, "-", watcher.getPhase(""))
|
||||
assert.Equal(t, "running", watcher.getPhase("running"))
|
||||
})
|
||||
|
||||
t.Run("getApp helper function", func(t *testing.T) {
|
||||
watcher := &applicationMetricsWatcher{}
|
||||
inputApp := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "test-ns",
|
||||
},
|
||||
Status: common.AppStatus{
|
||||
Phase: common.ApplicationRunning,
|
||||
},
|
||||
}
|
||||
resultApp := watcher.getApp(inputApp)
|
||||
assert.NotNil(t, resultApp)
|
||||
assert.Equal(t, "test-app", resultApp.Name)
|
||||
assert.Equal(t, "test-ns", resultApp.Namespace)
|
||||
assert.Equal(t, common.ApplicationRunning, resultApp.Status.Phase)
|
||||
})
|
||||
}
|
||||
|
|
@ -17,10 +17,14 @@
|
|||
package velaql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseVelaQL(t *testing.T) {
|
||||
|
|
@ -134,3 +138,87 @@ func TestParseParameter(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVelaQLFromPath(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
testdataDir, err := filepath.Abs("testdata")
|
||||
require.NoError(t, err)
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
path string
|
||||
expectedExport string
|
||||
expectError bool
|
||||
errorContains string
|
||||
}{
|
||||
{
|
||||
name: "Simple valid CUE file with export field",
|
||||
path: filepath.Join(testdataDir, "simple-valid.cue"),
|
||||
expectedExport: "output.message",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Simple valid CUE file without export field",
|
||||
path: filepath.Join(testdataDir, "simple-no-export.cue"),
|
||||
expectedExport: DefaultExportValue,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Nonexistent file path",
|
||||
path: filepath.Join(testdataDir, "nonexistent.cue"),
|
||||
expectError: true,
|
||||
errorContains: "read view file from",
|
||||
},
|
||||
{
|
||||
name: "Empty file path",
|
||||
path: "",
|
||||
expectError: true,
|
||||
errorContains: "read view file from",
|
||||
},
|
||||
{
|
||||
name: "Invalid CUE content",
|
||||
path: filepath.Join(testdataDir, "invalid-cue-content.cue"),
|
||||
expectError: true,
|
||||
errorContains: "error when parsing view",
|
||||
},
|
||||
{
|
||||
name: "File with invalid export type - should fallback to default",
|
||||
path: filepath.Join(testdataDir, "invalid-export.cue"),
|
||||
expectedExport: DefaultExportValue,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Empty CUE file",
|
||||
path: filepath.Join(testdataDir, "empty.cue"),
|
||||
expectedExport: DefaultExportValue,
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := ParseVelaQLFromPath(ctx, tc.path)
|
||||
|
||||
if tc.expectError {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, result)
|
||||
if tc.errorContains != "" {
|
||||
assert.Contains(t, err.Error(), tc.errorContains)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
|
||||
if tc.path != "" {
|
||||
expectedContent, readErr := os.ReadFile(tc.path)
|
||||
require.NoError(t, readErr)
|
||||
assert.Equal(t, string(expectedContent), result.View)
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.expectedExport, result.Export)
|
||||
assert.Nil(t, result.Parameter)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
invalid cue syntax [
|
||||
missing: colon
|
||||
invalid: brackets
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
parameter: {
|
||||
name: "test"
|
||||
}
|
||||
|
||||
output: {
|
||||
message: "hello " + parameter.name
|
||||
}
|
||||
|
||||
export: 123 // Invalid export type (should be string)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
parameter: {
|
||||
name: "test"
|
||||
}
|
||||
|
||||
output: {
|
||||
message: "hello " + parameter.name
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
parameter: {
|
||||
name: "test"
|
||||
}
|
||||
|
||||
output: {
|
||||
message: "hello " + parameter.name
|
||||
}
|
||||
|
||||
export: "output.message"
|
||||
|
|
@ -32,8 +32,119 @@ import (
|
|||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1beta1/core"
|
||||
)
|
||||
|
||||
func TestValidateDefinitionRevision(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(scheme)
|
||||
|
||||
baseCompDef := &v1beta1.ComponentDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-def",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1beta1.ComponentDefinitionSpec{
|
||||
Workload: common.WorkloadTypeDescriptor{
|
||||
Definition: common.WorkloadGVK{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: `
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: name: context.name
|
||||
}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedDefRev, _, err := core.GatherRevisionInfo(baseCompDef)
|
||||
assert.NoError(t, err, "Setup: failed to gather revision info")
|
||||
expectedDefRev.Name = "test-def-v1"
|
||||
expectedDefRev.Namespace = "default"
|
||||
|
||||
mismatchedHashDefRev := expectedDefRev.DeepCopy()
|
||||
mismatchedHashDefRev.Spec.RevisionHash = "different-hash"
|
||||
|
||||
mismatchedSpecDefRev := expectedDefRev.DeepCopy()
|
||||
mismatchedSpecDefRev.Spec.ComponentDefinition.Spec.Workload.Definition.Kind = "StatefulSet"
|
||||
|
||||
testCases := map[string]struct {
|
||||
def runtime.Object
|
||||
defRevName types.NamespacedName
|
||||
existingObjs []runtime.Object
|
||||
expectErr bool
|
||||
expectedErrContains string
|
||||
}{
|
||||
"Success with matching definition revision": {
|
||||
def: baseCompDef,
|
||||
defRevName: types.NamespacedName{Name: "test-def-v1", Namespace: "default"},
|
||||
existingObjs: []runtime.Object{expectedDefRev},
|
||||
expectErr: false,
|
||||
},
|
||||
"Success when definition revision does not exist": {
|
||||
def: baseCompDef,
|
||||
defRevName: types.NamespacedName{Name: "test-def-v1", Namespace: "default"},
|
||||
existingObjs: []runtime.Object{},
|
||||
expectErr: false,
|
||||
},
|
||||
"Failure with revision hash mismatch": {
|
||||
def: baseCompDef,
|
||||
defRevName: types.NamespacedName{Name: "test-def-v1", Namespace: "default"},
|
||||
existingObjs: []runtime.Object{mismatchedHashDefRev},
|
||||
expectErr: true,
|
||||
expectedErrContains: "the definition's spec is different with existing definitionRevision's spec",
|
||||
},
|
||||
"Failure with spec mismatch (DeepEqual)": {
|
||||
def: baseCompDef,
|
||||
defRevName: types.NamespacedName{Name: "test-def-v1", Namespace: "default"},
|
||||
existingObjs: []runtime.Object{mismatchedSpecDefRev},
|
||||
expectErr: true,
|
||||
expectedErrContains: "the definition's spec is different with existing definitionRevision's spec",
|
||||
},
|
||||
"Failure with invalid definition revision name": {
|
||||
def: baseCompDef,
|
||||
defRevName: types.NamespacedName{Name: "invalid!name", Namespace: "default"},
|
||||
existingObjs: []runtime.Object{},
|
||||
expectErr: true,
|
||||
expectedErrContains: "invalid definitionRevision name",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
cli := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithRuntimeObjects(tc.existingObjs...).
|
||||
Build()
|
||||
|
||||
err := ValidateDefinitionRevision(context.Background(), cli, tc.def, tc.defRevName)
|
||||
|
||||
if tc.expectErr {
|
||||
assert.Error(t, err)
|
||||
if tc.expectedErrContains != "" {
|
||||
assert.Contains(t, err.Error(), tc.expectedErrContains)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCueTemplate(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
cueTemplate string
|
||||
|
|
|
|||
|
|
@ -106,6 +106,64 @@ func TestParser(t *testing.T) {
|
|||
r.Equal(act.Phase, "Wait")
|
||||
}
|
||||
|
||||
func TestRenderComponent(t *testing.T) {
|
||||
r := require.New(t)
|
||||
ctx := context.Background()
|
||||
cuectx := cuecontext.New()
|
||||
cli := setupClient(ctx, t)
|
||||
|
||||
v := cuectx.CompileString(`$params: {
|
||||
value: {
|
||||
name: "test-render",
|
||||
type: "webservice",
|
||||
}
|
||||
}`)
|
||||
r.NoError(v.Err())
|
||||
|
||||
mockComponentRender := func(ctx context.Context, comp common.ApplicationComponent, patcher *cue.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, error) {
|
||||
r.Equal("test-render", comp.Name)
|
||||
workload := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-workload",
|
||||
},
|
||||
},
|
||||
}
|
||||
trait := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-trait",
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/resource": "mytrait",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return workload, []*unstructured.Unstructured{trait}, nil
|
||||
}
|
||||
|
||||
res, err := RenderComponent(ctx, &oamprovidertypes.Params[cue.Value]{
|
||||
Params: v,
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
KubeClient: cli,
|
||||
ComponentRender: mockComponentRender,
|
||||
},
|
||||
})
|
||||
r.NoError(err)
|
||||
|
||||
output, err := res.LookupPath(cue.ParsePath("$returns.output.metadata.name")).String()
|
||||
r.NoError(err)
|
||||
r.Equal("test-workload", output)
|
||||
|
||||
outputs, err := res.LookupPath(cue.ParsePath("$returns.outputs.mytrait.metadata.name")).String()
|
||||
r.NoError(err)
|
||||
r.Equal("test-workload", outputs)
|
||||
}
|
||||
|
||||
func TestLoadComponent(t *testing.T) {
|
||||
r := require.New(t)
|
||||
ctx := context.Background()
|
||||
|
|
|
|||
|
|
@ -15,3 +15,293 @@
|
|||
*/
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
querytypes "github.com/oam-dev/kubevela/pkg/utils/types"
|
||||
)
|
||||
|
||||
func TestBuildResourceArray(t *testing.T) {
|
||||
// Define common objects used across tests
|
||||
pod1 := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "pod1",
|
||||
},
|
||||
},
|
||||
}
|
||||
pod2 := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "pod2",
|
||||
"annotations": map[string]interface{}{
|
||||
oam.AnnotationPublishVersion: "v2.0.0-pod",
|
||||
oam.AnnotationDeployVersion: "rev2-pod",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
deployment := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-app",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Define common tree nodes
|
||||
parentWorkloadNode := &querytypes.ResourceTreeNode{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "my-app",
|
||||
Namespace: "default",
|
||||
Object: deployment,
|
||||
}
|
||||
|
||||
pod1Node := &querytypes.ResourceTreeNode{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
Name: "pod1",
|
||||
Namespace: "default",
|
||||
Object: pod1,
|
||||
}
|
||||
|
||||
pod2Node := &querytypes.ResourceTreeNode{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
Name: "pod2",
|
||||
Namespace: "default",
|
||||
Object: pod2,
|
||||
}
|
||||
|
||||
replicaSetNode := &querytypes.ResourceTreeNode{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "my-app-rs",
|
||||
Namespace: "default",
|
||||
LeafNodes: []*querytypes.ResourceTreeNode{pod1Node, pod2Node},
|
||||
}
|
||||
|
||||
// Define test cases
|
||||
testCases := map[string]struct {
|
||||
res querytypes.AppliedResource
|
||||
parent *querytypes.ResourceTreeNode
|
||||
node *querytypes.ResourceTreeNode
|
||||
kind string
|
||||
apiVersion string
|
||||
expected []querytypes.ResourceItem
|
||||
}{
|
||||
"simple case with one matching pod": {
|
||||
res: querytypes.AppliedResource{
|
||||
Cluster: "local",
|
||||
Component: "my-comp",
|
||||
PublishVersion: "v1.0.0",
|
||||
DeployVersion: "rev1",
|
||||
},
|
||||
parent: parentWorkloadNode,
|
||||
node: pod1Node,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
expected: []querytypes.ResourceItem{
|
||||
{
|
||||
Cluster: "local",
|
||||
Component: "my-comp",
|
||||
Workload: querytypes.Workload{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "my-app",
|
||||
Namespace: "default",
|
||||
},
|
||||
Object: pod1,
|
||||
PublishVersion: "v1.0.0",
|
||||
DeployVersion: "rev1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"nested case with multiple matching pods": {
|
||||
res: querytypes.AppliedResource{
|
||||
Cluster: "remote",
|
||||
Component: "my-comp-2",
|
||||
PublishVersion: "v2.0.0",
|
||||
DeployVersion: "rev2",
|
||||
},
|
||||
parent: parentWorkloadNode,
|
||||
node: replicaSetNode,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
expected: []querytypes.ResourceItem{
|
||||
{
|
||||
Cluster: "remote",
|
||||
Component: "my-comp-2",
|
||||
Workload: querytypes.Workload{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "my-app-rs",
|
||||
Namespace: "default",
|
||||
},
|
||||
Object: pod1,
|
||||
PublishVersion: "v2.0.0",
|
||||
DeployVersion: "rev2",
|
||||
},
|
||||
{
|
||||
Cluster: "remote",
|
||||
Component: "my-comp-2",
|
||||
Workload: querytypes.Workload{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "my-app-rs",
|
||||
Namespace: "default",
|
||||
},
|
||||
Object: pod2,
|
||||
PublishVersion: "v2.0.0-pod", // From annotation
|
||||
DeployVersion: "rev2-pod", // From annotation
|
||||
},
|
||||
},
|
||||
},
|
||||
"no matching nodes": {
|
||||
res: querytypes.AppliedResource{},
|
||||
parent: parentWorkloadNode,
|
||||
node: pod1Node,
|
||||
kind: "Service",
|
||||
apiVersion: "v1",
|
||||
expected: nil,
|
||||
},
|
||||
"empty node": {
|
||||
res: querytypes.AppliedResource{},
|
||||
parent: parentWorkloadNode,
|
||||
node: &querytypes.ResourceTreeNode{},
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
expected: nil,
|
||||
},
|
||||
"complex tree with mixed resources": {
|
||||
res: querytypes.AppliedResource{
|
||||
Cluster: "local",
|
||||
Component: "my-comp",
|
||||
PublishVersion: "v1.0.0",
|
||||
DeployVersion: "rev1",
|
||||
},
|
||||
parent: parentWorkloadNode,
|
||||
node: &querytypes.ResourceTreeNode{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "my-app-rs",
|
||||
Namespace: "default",
|
||||
LeafNodes: []*querytypes.ResourceTreeNode{
|
||||
pod1Node,
|
||||
{
|
||||
APIVersion: "v1",
|
||||
Kind: "Service",
|
||||
Name: "my-service",
|
||||
},
|
||||
pod2Node,
|
||||
},
|
||||
},
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
expected: []querytypes.ResourceItem{
|
||||
{
|
||||
Cluster: "local",
|
||||
Component: "my-comp",
|
||||
Workload: querytypes.Workload{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "my-app-rs",
|
||||
Namespace: "default",
|
||||
},
|
||||
Object: pod1,
|
||||
PublishVersion: "v1.0.0",
|
||||
DeployVersion: "rev1",
|
||||
},
|
||||
{
|
||||
Cluster: "local",
|
||||
Component: "my-comp",
|
||||
Workload: querytypes.Workload{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "my-app-rs",
|
||||
Namespace: "default",
|
||||
},
|
||||
Object: pod2,
|
||||
PublishVersion: "v2.0.0-pod",
|
||||
DeployVersion: "rev2-pod",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
result := buildResourceArray(tc.res, tc.parent, tc.node, tc.kind, tc.apiVersion)
|
||||
assert.ElementsMatch(t, tc.expected, result, "The returned resource items should match the expected ones")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildResourceItem(t *testing.T) {
|
||||
pod := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-pod",
|
||||
},
|
||||
},
|
||||
}
|
||||
podWithAnnotations := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-pod-annotated",
|
||||
"annotations": map[string]interface{}{
|
||||
oam.AnnotationPublishVersion: "v2.0.0-annotated",
|
||||
oam.AnnotationDeployVersion: "rev2-annotated",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res := querytypes.AppliedResource{
|
||||
Cluster: "test-cluster",
|
||||
Component: "test-comp",
|
||||
PublishVersion: "v1.0.0-res",
|
||||
DeployVersion: "rev1-res",
|
||||
}
|
||||
workload := querytypes.Workload{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "test-workload",
|
||||
Namespace: "test-ns",
|
||||
}
|
||||
|
||||
t.Run("without annotations", func(t *testing.T) {
|
||||
item := buildResourceItem(res, workload, pod)
|
||||
assert.Equal(t, "test-cluster", item.Cluster)
|
||||
assert.Equal(t, "test-comp", item.Component)
|
||||
assert.Equal(t, workload, item.Workload)
|
||||
assert.Equal(t, pod, item.Object)
|
||||
assert.Equal(t, "v1.0.0-res", item.PublishVersion)
|
||||
assert.Equal(t, "rev1-res", item.DeployVersion)
|
||||
})
|
||||
|
||||
t.Run("with annotations", func(t *testing.T) {
|
||||
item := buildResourceItem(res, workload, podWithAnnotations)
|
||||
assert.Equal(t, "test-cluster", item.Cluster)
|
||||
assert.Equal(t, "test-comp", item.Component)
|
||||
assert.Equal(t, workload, item.Workload)
|
||||
assert.Equal(t, podWithAnnotations, item.Object)
|
||||
assert.Equal(t, "v2.0.0-annotated", item.PublishVersion)
|
||||
assert.Equal(t, "rev2-annotated", item.DeployVersion)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue