Compare commits

...

7 Commits

Author SHA1 Message Date
AshvinBambhaniya2003 e674b840b6
Merge 4a886b14b7 into ebf73d03c2 2025-10-17 02:21:56 +02:00
Brian Kane ebf73d03c2
Chore: Add codeowners (#6946)
CodeQL / Analyze (go) (push) Waiting to run Details
Definition-Lint / definition-doc (push) Waiting to run Details
E2E MultiCluster Test / detect-noop (push) Waiting to run Details
E2E MultiCluster Test / e2e-multi-cluster-tests (v1.31.9) (push) Blocked by required conditions Details
E2E Test / detect-noop (push) Waiting to run Details
E2E Test / e2e-tests (v1.31) (push) Blocked by required conditions Details
Go / detect-noop (push) Waiting to run Details
Go / staticcheck (push) Blocked by required conditions Details
Go / lint (push) Blocked by required conditions Details
Go / check-diff (push) Blocked by required conditions Details
Go / check-windows (push) Blocked by required conditions Details
Go / check-core-image-build (push) Blocked by required conditions Details
Go / check-cli-image-build (push) Blocked by required conditions Details
license / Check for unapproved licenses (push) Waiting to run Details
Registry / Build and Push Vela Images (push) Waiting to run Details
Registry / Generate and Push Provenance to GCHR (${{ needs.publish-vela-images.outputs.vela_cli_digest }}, ${{ needs.publish-vela-images.outputs.vela_cli_image }}, Vela CLI Image) (push) Blocked by required conditions Details
Registry / Generate and Push Provenance to GCHR (${{ needs.publish-vela-images.outputs.vela_core_digest }}, ${{ needs.publish-vela-images.outputs.vela_core_image }}, Vela Core Image) (push) Blocked by required conditions Details
Registry / Generate and Push Provenance to DockerHub (${{ needs.publish-vela-images.outputs.vela_cli_digest }}, ${{ needs.publish-vela-images.outputs.vela_cli_dockerhub_image }}, Vela CLI Image) (push) Blocked by required conditions Details
Registry / Generate and Push Provenance to DockerHub (${{ needs.publish-vela-images.outputs.vela_core_digest }}, ${{ needs.publish-vela-images.outputs.vela_core_dockerhub_image }}, Vela Core Image) (push) Blocked by required conditions Details
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run Details
Unit-Test / detect-noop (push) Waiting to run Details
Unit-Test / unit-tests (push) Blocked by required conditions Details
Webhook Upgrade Validation / webhook-upgrade-check (push) Waiting to run Details
Signed-off-by: Brian Kane <briankane1@gmail.com>
2025-10-16 08:33:49 -07:00
Ashvin Bambhaniya 4a886b14b7 fix(velaql): Improve error handling in ParseVelaQLFromPath test
This commit addresses an issue in the TestParseVelaQLFromPath function where file read errors were being silently ignored. The changes include:

- Removing the unused expectedView field from test cases
- Replacing conditional error checking with require.NoError to ensure file read operations are properly validated
- Ensuring that test failures are properly reported when file reading fails

This fix improves the reliability of the test suite by making sure that any file I/O errors are properly caught and reported rather than silently ignored.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>
2025-09-10 19:32:45 +05:30
Ashvin Bambhaniya 9901e73fe5 feat(workflow): Add unit tests for OAM apply and query utilities
This commit introduces new unit tests for workflow provider functions in pkg/workflow/providers to improve test coverage and ensure correctness.

Key additions include:
  - `pkg/workflow/providers/oam/apply_test.go`: Adds TestRenderComponent function with comprehensive test cases for component rendering
  - `pkg/workflow/providers/query/utils_test.go`: Adds:
    * TestBuildResourceArray function with comprehensive test cases covering simple, nested, and complex resource tree scenarios
    * TestBuildResourceItem function with test cases for resources with and without annotations

These additions improve the overall test coverage and ensure the robustness of the workflow provider functions for OAM applications.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>
2025-09-10 18:56:45 +05:30
Ashvin Bambhaniya a8393c40de feat(webhook): Add unit tests for ValidateDefinitionRevision function
This commit introduces new unit tests for the ValidateDefinitionRevision function in pkg/webhook/utils to improve test coverage and ensure correctness of definition revision validation.

Key additions include:
  - `pkg/webhook/utils/utils_test.go`: Adds TestValidateDefinitionRevision function with comprehensive test cases covering:
    * Success scenarios with matching definition revisions
    * Success scenarios when definition revision does not exist
    * Failure scenarios with revision hash mismatches
    * Failure scenarios with spec mismatches
    * Failure scenarios with invalid definition revision names

These additions improve the overall test coverage and ensure the robustness of the webhook utility functions for validating definition revisions.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>
2025-09-10 18:36:50 +05:30
Ashvin Bambhaniya cbe8439d0b feat(velaql): Add comprehensive unit tests for ParseVelaQLFromPath
This commit introduces new unit tests for the ParseVelaQLFromPath function in pkg/velaql, along with test data files to improve test coverage and ensure correctness.

Key additions include:
  - `pkg/velaql/parse_test.go`: Adds TestParseVelaQLFromPath function with comprehensive test cases covering:
    * Valid CUE files with and without export fields
    * Nonexistent and empty file paths
    * Invalid CUE content
    * Files with invalid export types
  - Test data files in pkg/velaql/testdata/:
    * simple-valid.cue: Valid CUE file with export field
    * simple-no-export.cue: Valid CUE file without export field
    * empty.cue: Empty CUE file
    * invalid-cue-content.cue: CUE file with invalid syntax
    * invalid-export.cue: CUE file with invalid export type

These additions improve the overall test coverage and ensure the robustness of the VELAQL parsing functionality.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>
2025-09-10 18:33:39 +05:30
Ashvin Bambhaniya 66c47910f7 feat(monitor): Add unit tests for application metrics watcher
This commit introduces a new test file with comprehensive unit tests for the application metrics watcher functionality in pkg/monitor/watcher.

Key additions include:
  - Test cases for the application metrics watcher's inc() method covering add, delete, and update operations
  - Test cases for report() method that verifies dirty flags are cleared
  - Test cases for helper functions getPhase() and getApp()

These additions improve the overall test coverage and ensure the correctness of the application metrics monitoring functionality.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>
2025-09-10 18:28:30 +05:30
12 changed files with 751 additions and 16 deletions

30
.github/CODEOWNERS vendored
View File

@ -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

View File

@ -11,4 +11,7 @@ wangyuan249
chivalryq
FogDong
leejanee
barnettZQG
barnettZQG
anoop2811
briankane
jguionnet

View File

@ -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)
})
}

View File

@ -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
pkg/velaql/testdata/empty.cue vendored Normal file
View File

View File

@ -0,0 +1,4 @@
invalid cue syntax [
missing: colon
invalid: brackets
}

View File

@ -0,0 +1,9 @@
parameter: {
name: "test"
}
output: {
message: "hello " + parameter.name
}
export: 123 // Invalid export type (should be string)

View File

@ -0,0 +1,7 @@
parameter: {
name: "test"
}
output: {
message: "hello " + parameter.name
}

9
pkg/velaql/testdata/simple-valid.cue vendored Normal file
View File

@ -0,0 +1,9 @@
parameter: {
name: "test"
}
output: {
message: "hello " + parameter.name
}
export: "output.message"

View File

@ -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

View File

@ -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()

View File

@ -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)
})
}