mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 10:00:06 +00:00
Feat(tests): Add unit test coverage for core packages (#6889)
* test(resourcekeeper): add unit tests for resource management This commit introduces new unit tests to improve the test coverage of the `resourcekeeper` package. - A new test file `containsresources_test.go` is added, which includes a comprehensive table-driven test for the `ContainsResources` function. - A new table-driven test, `TestUpdateSharedManagedResourceOwner`, is added to `gc_test.go` to verify the logic for updating ownership of shared resources. These tests follow Go best practices and enhance the robustness of the resourcekeeper functionality. Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com> * feat(resourcetracker): add unit tests for tree display logic This commit enhances the test coverage for the resource tree display logic in the `pkg/resourcetracker` package. - Refactors `TestResourceTreePrintOption_getWidthForDetails` to cover more cases and improve test clarity. - Adds a comprehensive test for `TestPrintResourceTree` to verify the output of the resource tree printing. - Introduces a new test for the `tableRoundTripper` to ensure the HTTP `Accept` header is correctly mutated. - Adds tests for helper functions like `TestLoadResourceRows`, `TestSortRows`, and `TestFillResourceRows` to ensure each part of the tree building logic is working as expected. These changes improve the overall quality and reliability of the resource tracker's tree view functionality. Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com> * feat(envbinding): add unit tests for placement logic This commit enhances the test coverage for the `envbinding` policy package. - Adds a new test for `WritePlacementDecisions` to verify the logic of writing placement decisions to the application status. This includes scenarios for adding new policies, updating existing ones, and handling malformed data. Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com> * feat(schema): add unit tests for schema parsing and conversion This commit enhances the test coverage for the `pkg/schema` package by adding unit tests for CUE parsing and OpenAPI schema conversion. - Adds a new test for `ParsePropertiesToSchema` to verify that CUE parameter definitions are correctly parsed into OpenAPI schemas. - Introduces a new test for `ConvertOpenAPISchema2SwaggerObject` to ensure the conversion from a raw OpenAPI v3 schema to a Swagger object is handled correctly, including error cases. These tests improve the reliability of the schema generation and conversion logic, which is critical for capability definitions. Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com> --------- Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>
This commit is contained in:
committed by
GitHub
parent
2758afb1b2
commit
d6ad578070
@@ -17,9 +17,11 @@ limitations under the License.
|
||||
package envbinding
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
@@ -63,3 +65,124 @@ func TestUpdateClusterConnections(t *testing.T) {
|
||||
r.Equal(conn.LastActiveRevision, _conn.LastActiveRevision)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWritePlacementDecisions(t *testing.T) {
|
||||
policyName := "test-policy"
|
||||
envName1 := "env-1"
|
||||
envName2 := "env-2"
|
||||
decisions1 := []v1alpha1.PlacementDecision{{Cluster: "cluster-1"}}
|
||||
decisions2 := []v1alpha1.PlacementDecision{{Cluster: "cluster-2"}}
|
||||
|
||||
makeAppWithPolicy := func(t *testing.T) *v1beta1.Application {
|
||||
app := &v1beta1.Application{}
|
||||
err := WritePlacementDecisions(app, policyName, envName1, decisions1)
|
||||
require.NoError(t, err)
|
||||
return app
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
setupApp func(t *testing.T) *v1beta1.Application
|
||||
envName string
|
||||
decisions []v1alpha1.PlacementDecision
|
||||
wantErr bool
|
||||
verify func(t *testing.T, app *v1beta1.Application)
|
||||
}{
|
||||
{
|
||||
name: "add to empty policy status",
|
||||
setupApp: func(t *testing.T) *v1beta1.Application {
|
||||
return &v1beta1.Application{}
|
||||
},
|
||||
envName: envName1,
|
||||
decisions: decisions1,
|
||||
wantErr: false,
|
||||
verify: func(t *testing.T, app *v1beta1.Application) {
|
||||
r := require.New(t)
|
||||
r.Len(app.Status.PolicyStatus, 1)
|
||||
r.Equal(policyName, app.Status.PolicyStatus[0].Name)
|
||||
r.Equal(v1alpha1.EnvBindingPolicyType, app.Status.PolicyStatus[0].Type)
|
||||
status := &v1alpha1.EnvBindingStatus{}
|
||||
err := json.Unmarshal(app.Status.PolicyStatus[0].Status.Raw, status)
|
||||
r.NoError(err)
|
||||
r.Len(status.Envs, 1)
|
||||
r.Equal(envName1, status.Envs[0].Env)
|
||||
r.Equal(decisions1, status.Envs[0].Placements)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update existing env in existing policy",
|
||||
setupApp: makeAppWithPolicy,
|
||||
envName: envName1,
|
||||
decisions: decisions2,
|
||||
wantErr: false,
|
||||
verify: func(t *testing.T, app *v1beta1.Application) {
|
||||
r := require.New(t)
|
||||
r.Len(app.Status.PolicyStatus, 1)
|
||||
status := &v1alpha1.EnvBindingStatus{}
|
||||
err := json.Unmarshal(app.Status.PolicyStatus[0].Status.Raw, status)
|
||||
r.NoError(err)
|
||||
r.Len(status.Envs, 1)
|
||||
r.Equal(envName1, status.Envs[0].Env)
|
||||
r.Equal(decisions2, status.Envs[0].Placements)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add new env to existing policy",
|
||||
setupApp: makeAppWithPolicy,
|
||||
envName: envName2,
|
||||
decisions: decisions2,
|
||||
wantErr: false,
|
||||
verify: func(t *testing.T, app *v1beta1.Application) {
|
||||
r := require.New(t)
|
||||
r.Len(app.Status.PolicyStatus, 1)
|
||||
status := &v1alpha1.EnvBindingStatus{}
|
||||
err := json.Unmarshal(app.Status.PolicyStatus[0].Status.Raw, status)
|
||||
r.NoError(err)
|
||||
r.Len(status.Envs, 2)
|
||||
envMap := make(map[string][]v1alpha1.PlacementDecision)
|
||||
for _, envStatus := range status.Envs {
|
||||
envMap[envStatus.Env] = envStatus.Placements
|
||||
}
|
||||
r.Equal(decisions1, envMap[envName1])
|
||||
r.Equal(decisions2, envMap[envName2])
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "handle malformed existing status",
|
||||
setupApp: func(t *testing.T) *v1beta1.Application {
|
||||
return &v1beta1.Application{
|
||||
Status: common.AppStatus{
|
||||
PolicyStatus: []common.PolicyStatus{
|
||||
{
|
||||
Name: policyName,
|
||||
Type: v1alpha1.EnvBindingPolicyType,
|
||||
Status: &runtime.RawExtension{Raw: []byte("this is not json")},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
envName: envName1,
|
||||
decisions: decisions1,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
app := tc.setupApp(t)
|
||||
err := WritePlacementDecisions(app, policyName, tc.envName, tc.decisions)
|
||||
|
||||
if tc.wantErr {
|
||||
r.Error(err)
|
||||
} else {
|
||||
r.NoError(err)
|
||||
}
|
||||
|
||||
if tc.verify != nil {
|
||||
tc.verify(t, app)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
156
pkg/resourcekeeper/containsresources_test.go
Normal file
156
pkg/resourcekeeper/containsresources_test.go
Normal 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 resourcekeeper
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
// newUnstructured is a helper to create a simple unstructured object for testing.
|
||||
func newUnstructured(name string) *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": name,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsResources(t *testing.T) {
|
||||
res1 := newUnstructured("res-1")
|
||||
res2 := newUnstructured("res-2")
|
||||
res3 := newUnstructured("res-3")
|
||||
res4Missing := newUnstructured("res-4-missing")
|
||||
|
||||
baseKeeperSetup := func() *resourceKeeper {
|
||||
return &resourceKeeper{
|
||||
Client: fake.NewClientBuilder().WithScheme(common.Scheme).Build(),
|
||||
_currentRT: &v1beta1.ResourceTracker{},
|
||||
_rootRT: &v1beta1.ResourceTracker{},
|
||||
}
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
keeper *resourceKeeper
|
||||
input []*unstructured.Unstructured
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "all resources exist across both trackers",
|
||||
keeper: func() *resourceKeeper {
|
||||
k := baseKeeperSetup()
|
||||
k._currentRT.AddManagedResource(res1, true, false, "")
|
||||
k._rootRT.AddManagedResource(res2, true, false, "")
|
||||
k._rootRT.AddManagedResource(res3, true, false, "")
|
||||
return k
|
||||
}(),
|
||||
input: []*unstructured.Unstructured{res1, res2, res3},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "one resource exists in currentRT",
|
||||
keeper: func() *resourceKeeper {
|
||||
k := baseKeeperSetup()
|
||||
k._currentRT.AddManagedResource(res1, true, false, "")
|
||||
return k
|
||||
}(),
|
||||
input: []*unstructured.Unstructured{res1},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "one resource exists in rootRT",
|
||||
keeper: func() *resourceKeeper {
|
||||
k := baseKeeperSetup()
|
||||
k._rootRT.AddManagedResource(res2, true, false, "")
|
||||
return k
|
||||
}(),
|
||||
input: []*unstructured.Unstructured{res2},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "one resource is missing",
|
||||
keeper: func() *resourceKeeper {
|
||||
k := baseKeeperSetup()
|
||||
k._currentRT.AddManagedResource(res1, true, false, "")
|
||||
return k
|
||||
}(),
|
||||
input: []*unstructured.Unstructured{res1, res4Missing},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty input slice should return true",
|
||||
keeper: &resourceKeeper{
|
||||
Client: fake.NewClientBuilder().WithScheme(common.Scheme).Build(),
|
||||
},
|
||||
input: []*unstructured.Unstructured{},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "trackers are nil",
|
||||
keeper: &resourceKeeper{
|
||||
Client: fake.NewClientBuilder().WithScheme(common.Scheme).Build(),
|
||||
},
|
||||
input: []*unstructured.Unstructured{res1},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "only rootRT is nil, resource in currentRT",
|
||||
keeper: func() *resourceKeeper {
|
||||
k := &resourceKeeper{
|
||||
Client: fake.NewClientBuilder().WithScheme(common.Scheme).Build(),
|
||||
_currentRT: &v1beta1.ResourceTracker{},
|
||||
}
|
||||
k._currentRT.AddManagedResource(res1, true, false, "")
|
||||
return k
|
||||
}(),
|
||||
input: []*unstructured.Unstructured{res1},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "only rootRT is nil, resource not in currentRT",
|
||||
keeper: func() *resourceKeeper {
|
||||
k := &resourceKeeper{
|
||||
Client: fake.NewClientBuilder().WithScheme(common.Scheme).Build(),
|
||||
_currentRT: &v1beta1.ResourceTracker{},
|
||||
}
|
||||
k._currentRT.AddManagedResource(res1, true, false, "")
|
||||
return k
|
||||
}(),
|
||||
input: []*unstructured.Unstructured{res2},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
result := tc.keeper.ContainsResources(tc.input)
|
||||
r.Equal(tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
"github.com/stretchr/testify/require"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
@@ -347,3 +348,105 @@ func TestEnableMarkStageGCOnWorkflowFailure(t *testing.T) {
|
||||
cfg = h.buildGCConfig(WithPhase(context.Background(), apicommon.ApplicationWorkflowFailed), options...)
|
||||
require.False(t, cfg.disableMark)
|
||||
}
|
||||
|
||||
func TestUpdateSharedManagedResourceOwner(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
baseCM := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "shared-cm",
|
||||
"namespace": "test-ns",
|
||||
"labels": map[string]interface{}{
|
||||
oam.LabelAppName: "old-app",
|
||||
oam.LabelAppNamespace: "old-ns",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockUpdateErr := fmt.Errorf("mock update error")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
setup func(t *testing.T) (client.Client, *unstructured.Unstructured)
|
||||
newSharedBy string
|
||||
wantErr error
|
||||
verify func(t *testing.T, cli client.Client, cm *unstructured.Unstructured)
|
||||
}{
|
||||
{
|
||||
name: "update with multi-tenant sharer",
|
||||
setup: func(t *testing.T) (client.Client, *unstructured.Unstructured) {
|
||||
r := require.New(t)
|
||||
cli := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
|
||||
cm := baseCM.DeepCopy()
|
||||
r.NoError(cli.Create(ctx, cm))
|
||||
return cli, cm
|
||||
},
|
||||
newSharedBy: "new-ns/new-app,other-ns/other-app",
|
||||
verify: func(t *testing.T, cli client.Client, cm *unstructured.Unstructured) {
|
||||
r := require.New(t)
|
||||
updatedCM := &unstructured.Unstructured{}
|
||||
updatedCM.SetGroupVersionKind(cm.GroupVersionKind())
|
||||
r.NoError(cli.Get(ctx, client.ObjectKeyFromObject(cm), updatedCM))
|
||||
r.Equal("new-ns/new-app,other-ns/other-app", updatedCM.GetAnnotations()[oam.AnnotationAppSharedBy])
|
||||
r.Equal("new-app", updatedCM.GetLabels()[oam.LabelAppName])
|
||||
r.Equal("new-ns", updatedCM.GetLabels()[oam.LabelAppNamespace])
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update with single-tenant sharer",
|
||||
setup: func(t *testing.T) (client.Client, *unstructured.Unstructured) {
|
||||
r := require.New(t)
|
||||
cli := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
|
||||
cm := baseCM.DeepCopy()
|
||||
r.NoError(cli.Create(ctx, cm))
|
||||
return cli, cm
|
||||
},
|
||||
newSharedBy: "just-an-app",
|
||||
verify: func(t *testing.T, cli client.Client, cm *unstructured.Unstructured) {
|
||||
r := require.New(t)
|
||||
updatedCM := &unstructured.Unstructured{}
|
||||
updatedCM.SetGroupVersionKind(cm.GroupVersionKind())
|
||||
r.NoError(cli.Get(ctx, client.ObjectKeyFromObject(cm), updatedCM))
|
||||
r.Equal("just-an-app", updatedCM.GetAnnotations()[oam.AnnotationAppSharedBy])
|
||||
r.Equal("just-an-app", updatedCM.GetLabels()[oam.LabelAppName])
|
||||
r.Equal("default", updatedCM.GetLabels()[oam.LabelAppNamespace])
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "client update fails",
|
||||
setup: func(t *testing.T) (client.Client, *unstructured.Unstructured) {
|
||||
cli := &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(mockUpdateErr),
|
||||
}
|
||||
cm := baseCM.DeepCopy()
|
||||
return cli, cm
|
||||
},
|
||||
newSharedBy: "any/sharer",
|
||||
wantErr: mockUpdateErr,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
cli, cm := tc.setup(t)
|
||||
|
||||
err := UpdateSharedManagedResourceOwner(ctx, cli, cm, tc.newSharedBy)
|
||||
|
||||
if tc.wantErr != nil {
|
||||
r.Error(err)
|
||||
r.Equal(tc.wantErr, err)
|
||||
} else {
|
||||
r.NoError(err)
|
||||
}
|
||||
|
||||
if tc.verify != nil {
|
||||
tc.verify(t, cli, cm)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,24 +17,63 @@ limitations under the License.
|
||||
package resourcetracker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
apicommon "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
)
|
||||
|
||||
func TestResourceTreePrintOption_getWidthForDetails(t *testing.T) {
|
||||
r := require.New(t)
|
||||
options := &ResourceTreePrintOptions{}
|
||||
r.Equal(math.MaxInt, options._getWidthForDetails(nil))
|
||||
options.MaxWidth = ptr.To(50 + applyTimeWidth)
|
||||
r.Equal(30, options._getWidthForDetails([]int{10, 10}))
|
||||
r.Equal(math.MaxInt, options._getWidthForDetails([]int{20, 20}))
|
||||
testCases := []struct {
|
||||
name string
|
||||
options *ResourceTreePrintOptions
|
||||
colsWidth []int
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "nil max width",
|
||||
options: &ResourceTreePrintOptions{},
|
||||
colsWidth: nil,
|
||||
expected: math.MaxInt,
|
||||
},
|
||||
{
|
||||
name: "sufficient width",
|
||||
options: &ResourceTreePrintOptions{MaxWidth: ptr.To(50 + applyTimeWidth)},
|
||||
colsWidth: []int{10, 10},
|
||||
expected: 30,
|
||||
},
|
||||
{
|
||||
name: "insufficient width",
|
||||
options: &ResourceTreePrintOptions{MaxWidth: ptr.To(50 + applyTimeWidth)},
|
||||
colsWidth: []int{20, 20},
|
||||
expected: math.MaxInt,
|
||||
},
|
||||
{
|
||||
name: "no columns",
|
||||
options: &ResourceTreePrintOptions{MaxWidth: ptr.To(50 + applyTimeWidth)},
|
||||
colsWidth: []int{},
|
||||
expected: 50,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
result := tc.options._getWidthForDetails(tc.colsWidth)
|
||||
r.Equal(tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceTreePrintOptions_wrapDetails(t *testing.T) {
|
||||
@@ -76,7 +115,7 @@ func TestBuildResourceRow(t *testing.T) {
|
||||
|
||||
for name, c := range cases {
|
||||
mr := v1beta1.ManagedResource{
|
||||
ClusterObjectReference: common.ClusterObjectReference{
|
||||
ClusterObjectReference: apicommon.ClusterObjectReference{
|
||||
Cluster: c.Cluster,
|
||||
},
|
||||
}
|
||||
@@ -86,3 +125,243 @@ func TestBuildResourceRow(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// newManagedResource is a helper for creating a ManagedResource for tests
|
||||
func newManagedResource(cluster, namespace, kind, name string) v1beta1.ManagedResource {
|
||||
return v1beta1.ManagedResource{
|
||||
ClusterObjectReference: apicommon.ClusterObjectReference{
|
||||
Cluster: cluster,
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Namespace: namespace,
|
||||
Kind: kind,
|
||||
Name: name,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadResourceRows(t *testing.T) {
|
||||
r := require.New(t)
|
||||
options := &ResourceTreePrintOptions{}
|
||||
|
||||
mr1 := newManagedResource("c1", "ns1", "Deployment", "d1")
|
||||
mr2 := newManagedResource("c1", "ns1", "Service", "s1")
|
||||
mr3 := newManagedResource("c2", "ns2", "ConfigMap", "cm1")
|
||||
mrDeleted := newManagedResource("c1", "ns1", "Secret", "sec1")
|
||||
mrDeleted.Deleted = true
|
||||
|
||||
currentRT := &v1beta1.ResourceTracker{
|
||||
Spec: v1beta1.ResourceTrackerSpec{
|
||||
ManagedResources: []v1beta1.ManagedResource{mr1, mrDeleted},
|
||||
},
|
||||
}
|
||||
historyRTs := []*v1beta1.ResourceTracker{{
|
||||
Spec: v1beta1.ResourceTrackerSpec{
|
||||
ManagedResources: []v1beta1.ManagedResource{mr1, mr2, mr3},
|
||||
},
|
||||
}}
|
||||
|
||||
rows := options.loadResourceRows(currentRT, historyRTs)
|
||||
r.Len(rows, 3)
|
||||
|
||||
statusMap := map[string]string{}
|
||||
for _, row := range rows {
|
||||
statusMap[row.mr.ResourceKey()] = row.status
|
||||
}
|
||||
|
||||
r.Equal(resourceRowStatusUpdated, statusMap[mr1.ResourceKey()])
|
||||
r.Equal(resourceRowStatusOutdated, statusMap[mr2.ResourceKey()])
|
||||
r.Equal(resourceRowStatusOutdated, statusMap[mr3.ResourceKey()])
|
||||
_, exists := statusMap[mrDeleted.ResourceKey()]
|
||||
r.False(exists, "Deleted resource should not be loaded")
|
||||
}
|
||||
|
||||
func TestSortRows(t *testing.T) {
|
||||
r := require.New(t)
|
||||
options := &ResourceTreePrintOptions{}
|
||||
|
||||
rows := []*resourceRow{
|
||||
{mr: &v1beta1.ManagedResource{ClusterObjectReference: apicommon.ClusterObjectReference{Cluster: "c2", ObjectReference: corev1.ObjectReference{Name: "res1"}}}},
|
||||
{mr: &v1beta1.ManagedResource{ClusterObjectReference: apicommon.ClusterObjectReference{Cluster: "c1", ObjectReference: corev1.ObjectReference{Namespace: "ns2", Name: "res2"}}}},
|
||||
{mr: &v1beta1.ManagedResource{ClusterObjectReference: apicommon.ClusterObjectReference{Cluster: "c1", ObjectReference: corev1.ObjectReference{Namespace: "ns1", Name: "res3"}}}},
|
||||
{mr: &v1beta1.ManagedResource{ClusterObjectReference: apicommon.ClusterObjectReference{Cluster: "c1", ObjectReference: corev1.ObjectReference{Namespace: "ns2", Name: "res1"}}}},
|
||||
}
|
||||
|
||||
options.sortRows(rows)
|
||||
|
||||
r.Equal("c1", rows[0].mr.Cluster)
|
||||
r.Equal("ns1", rows[0].mr.Namespace)
|
||||
|
||||
r.Equal("c1", rows[1].mr.Cluster)
|
||||
r.Equal("ns2", rows[1].mr.Namespace)
|
||||
r.Equal("res1", rows[1].mr.Name)
|
||||
|
||||
r.Equal("c1", rows[2].mr.Cluster)
|
||||
r.Equal("ns2", rows[2].mr.Namespace)
|
||||
r.Equal("res2", rows[2].mr.Name)
|
||||
|
||||
r.Equal("c2", rows[3].mr.Cluster)
|
||||
}
|
||||
|
||||
func TestAddNonExistingPlacementToRows(t *testing.T) {
|
||||
r := require.New(t)
|
||||
options := &ResourceTreePrintOptions{}
|
||||
|
||||
rows := []*resourceRow{
|
||||
{mr: &v1beta1.ManagedResource{ClusterObjectReference: apicommon.ClusterObjectReference{Cluster: "cluster-a"}}},
|
||||
}
|
||||
placements := []v1alpha1.PlacementDecision{
|
||||
{Cluster: "cluster-a"},
|
||||
{Cluster: "cluster-b"},
|
||||
}
|
||||
|
||||
newRows := options.addNonExistingPlacementToRows(placements, rows)
|
||||
r.Len(newRows, 2)
|
||||
r.Equal("cluster-a", newRows[0].mr.Cluster)
|
||||
r.Equal("cluster-b", newRows[1].mr.Cluster)
|
||||
r.Equal(resourceRowStatusNotDeployed, newRows[1].status)
|
||||
}
|
||||
|
||||
// MockClusterNameMapper is a mock for testing
|
||||
type MockClusterNameMapper struct{}
|
||||
|
||||
func (m MockClusterNameMapper) GetClusterName(cluster string) string { return cluster }
|
||||
|
||||
func TestFillResourceRows(t *testing.T) {
|
||||
r := require.New(t)
|
||||
options := &ResourceTreePrintOptions{ClusterNameMapper: MockClusterNameMapper{}}
|
||||
|
||||
rows := []*resourceRow{
|
||||
{mr: &v1beta1.ManagedResource{ClusterObjectReference: apicommon.ClusterObjectReference{Cluster: "c1", ObjectReference: corev1.ObjectReference{Namespace: "ns1", Kind: "Deployment", Name: "d1"}}}},
|
||||
{mr: &v1beta1.ManagedResource{ClusterObjectReference: apicommon.ClusterObjectReference{Cluster: "c1", ObjectReference: corev1.ObjectReference{Namespace: "ns1", Kind: "Service", Name: "s1"}}}},
|
||||
{mr: &v1beta1.ManagedResource{ClusterObjectReference: apicommon.ClusterObjectReference{Cluster: "c1", ObjectReference: corev1.ObjectReference{Namespace: "ns2", Kind: "ConfigMap", Name: "cm1"}}}},
|
||||
{mr: &v1beta1.ManagedResource{ClusterObjectReference: apicommon.ClusterObjectReference{Cluster: "c2", ObjectReference: corev1.ObjectReference{Namespace: "ns1", Kind: "Ingress", Name: "i1"}}}},
|
||||
}
|
||||
colsWidth := make([]int, 4)
|
||||
options.fillResourceRows(rows, colsWidth)
|
||||
|
||||
// Check blanking
|
||||
r.Equal("c1", rows[0].cluster)
|
||||
r.Equal("ns1", rows[0].namespace)
|
||||
r.Equal("", rows[1].cluster, "cluster should be blanked for same group")
|
||||
r.Equal("", rows[1].namespace, "namespace should be blanked for same group")
|
||||
r.Equal("", rows[2].cluster, "cluster should be blanked for same group")
|
||||
r.Equal("ns2", rows[2].namespace, "namespace should not be blanked for different ns")
|
||||
r.Equal("c2", rows[3].cluster, "cluster should not be blanked for different cluster")
|
||||
|
||||
// Check connectors for row 1 (second item in ns1)
|
||||
r.True(rows[1].connectClusterUp)
|
||||
r.True(rows[0].connectClusterDown)
|
||||
r.True(rows[1].connectNamespaceUp)
|
||||
r.True(rows[0].connectNamespaceDown)
|
||||
|
||||
// Check connectors for row 2 (first item in ns2)
|
||||
r.True(rows[2].connectClusterUp)
|
||||
r.True(rows[1].connectClusterDown)
|
||||
r.False(rows[2].connectNamespaceUp)
|
||||
r.False(rows[1].connectNamespaceDown)
|
||||
}
|
||||
|
||||
// mockRoundTripper for testing http clients
|
||||
type mockRoundTripper struct {
|
||||
roundTripFunc func(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
func (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if m.roundTripFunc != nil {
|
||||
return m.roundTripFunc(req)
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK}, nil
|
||||
}
|
||||
|
||||
func TestPrintResourceTree(t *testing.T) {
|
||||
color.NoColor = true
|
||||
defer func() { color.NoColor = false }()
|
||||
|
||||
mr1 := newManagedResource("c1", "ns1", "Deployment", "d1")
|
||||
mr2 := newManagedResource("c1", "ns1", "Service", "s1")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
options *ResourceTreePrintOptions
|
||||
placements []v1alpha1.PlacementDecision
|
||||
currentRT *v1beta1.ResourceTracker
|
||||
historyRT []*v1beta1.ResourceTracker
|
||||
expectedSubstrings []string
|
||||
}{
|
||||
{
|
||||
name: "simple case with current and history",
|
||||
options: &ResourceTreePrintOptions{ClusterNameMapper: MockClusterNameMapper{}},
|
||||
currentRT: &v1beta1.ResourceTracker{Spec: v1beta1.ResourceTrackerSpec{ManagedResources: []v1beta1.ManagedResource{mr1}}},
|
||||
historyRT: []*v1beta1.ResourceTracker{{Spec: v1beta1.ResourceTrackerSpec{ManagedResources: []v1beta1.ManagedResource{mr1, mr2}}}},
|
||||
expectedSubstrings: []string{"CLUSTER", "NAMESPACE", "RESOURCE", "STATUS", "c1", "ns1", "Deployment/d1", "updated", "Service/s1", "outdated"},
|
||||
},
|
||||
{
|
||||
name: "with not-deployed placement",
|
||||
options: &ResourceTreePrintOptions{ClusterNameMapper: MockClusterNameMapper{}},
|
||||
placements: []v1alpha1.PlacementDecision{{Cluster: "c2"}},
|
||||
expectedSubstrings: []string{"c2", "not-deployed"},
|
||||
},
|
||||
{
|
||||
name: "with detail retriever",
|
||||
options: &ResourceTreePrintOptions{
|
||||
ClusterNameMapper: MockClusterNameMapper{},
|
||||
DetailRetriever: func(row *resourceRow, format string) error {
|
||||
row.applyTime = "2021-01-01"
|
||||
row.details = "detail-info"
|
||||
return nil
|
||||
},
|
||||
},
|
||||
currentRT: &v1beta1.ResourceTracker{Spec: v1beta1.ResourceTrackerSpec{ManagedResources: []v1beta1.ManagedResource{mr1}}},
|
||||
expectedSubstrings: []string{"APPLY_TIME", "DETAIL", "2021-01-01", "detail-info"},
|
||||
},
|
||||
{
|
||||
name: "with detail retriever error",
|
||||
options: &ResourceTreePrintOptions{
|
||||
ClusterNameMapper: MockClusterNameMapper{},
|
||||
DetailRetriever: func(row *resourceRow, format string) error {
|
||||
return fmt.Errorf("mock error")
|
||||
},
|
||||
},
|
||||
currentRT: &v1beta1.ResourceTracker{Spec: v1beta1.ResourceTrackerSpec{ManagedResources: []v1beta1.ManagedResource{mr1}}},
|
||||
expectedSubstrings: []string{"Error: mock error"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
var buf bytes.Buffer
|
||||
tc.options.PrintResourceTree(&buf, tc.placements, tc.currentRT, tc.historyRT)
|
||||
output := buf.String()
|
||||
for _, sub := range tc.expectedSubstrings {
|
||||
r.Contains(output, sub)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableRoundTripper(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
var capturedReq *http.Request
|
||||
|
||||
mockRT := &mockRoundTripper{
|
||||
roundTripFunc: func(req *http.Request) (*http.Response, error) {
|
||||
capturedReq = req
|
||||
return &http.Response{StatusCode: http.StatusOK}, nil
|
||||
},
|
||||
}
|
||||
|
||||
tableRT := tableRoundTripper{rt: mockRT}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://localhost", nil)
|
||||
r.NoError(err)
|
||||
|
||||
_, err = tableRT.RoundTrip(req)
|
||||
r.NoError(err)
|
||||
|
||||
r.NotNil(capturedReq)
|
||||
expectedAccept := "application/json;as=Table;v=v1;g=meta.k8s.io,application/json"
|
||||
r.Equal(expectedAccept, capturedReq.Header.Get("Accept"))
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/cue/process"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
const TestDir = "testdata"
|
||||
@@ -59,3 +61,138 @@ func TestFixOpenAPISchema(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePropertiesToSchema(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cases := []struct {
|
||||
name string
|
||||
cue string
|
||||
path []string
|
||||
wantErr bool
|
||||
checkFunc func(t *testing.T, schema *openapi3.Schema)
|
||||
}{
|
||||
{
|
||||
name: "happy path no path",
|
||||
cue: `parameter: {
|
||||
name: string
|
||||
age: int
|
||||
}`,
|
||||
wantErr: false,
|
||||
checkFunc: func(t *testing.T, schema *openapi3.Schema) {
|
||||
r := assert.New(t)
|
||||
r.NotNil(schema)
|
||||
r.Contains(schema.Properties, "name")
|
||||
r.Contains(schema.Properties, "age")
|
||||
r.Equal("string", (*schema.Properties["name"].Value.Type)[0])
|
||||
r.Equal("integer", (*schema.Properties["age"].Value.Type)[0])
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with path",
|
||||
cue: `
|
||||
template: {
|
||||
parameter: {
|
||||
address: string
|
||||
}
|
||||
}`,
|
||||
path: []string{"template"},
|
||||
wantErr: false,
|
||||
checkFunc: func(t *testing.T, schema *openapi3.Schema) {
|
||||
r := assert.New(t)
|
||||
r.NotNil(schema)
|
||||
r.Contains(schema.Properties, "address")
|
||||
r.Len(schema.Properties, 1)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid cue string",
|
||||
cue: `parameter: { name: }`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid path",
|
||||
cue: `parameter: { name: string }`,
|
||||
path: []string{"bad-path"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
schema, err := ParsePropertiesToSchema(ctx, tc.cue, tc.path...)
|
||||
if tc.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
if tc.checkFunc != nil {
|
||||
tc.checkFunc(t, schema)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAPISchema2SwaggerObject(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
jsonData []byte
|
||||
wantErr bool
|
||||
errString string
|
||||
checkFunc func(*testing.T, *openapi3.Schema)
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
jsonData: []byte(`{
|
||||
"openapi": "3.0.0",
|
||||
"info": { "title": "My API", "version": "1.0.0" },
|
||||
"paths": {},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"parameter": {
|
||||
"type": "object",
|
||||
"properties": { "name": { "type": "string" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
wantErr: false,
|
||||
checkFunc: func(t *testing.T, schema *openapi3.Schema) {
|
||||
assert.NotNil(t, schema)
|
||||
assert.Contains(t, schema.Properties, "name")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
jsonData: []byte(`{"bad"`),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing parameter schema",
|
||||
jsonData: []byte(`{
|
||||
"openapi": "3.0.0",
|
||||
"info": { "title": "My API", "version": "1.0.0" },
|
||||
"paths": {},
|
||||
"components": { "schemas": { "other": {} } }
|
||||
}`),
|
||||
wantErr: true,
|
||||
errString: util.ErrGenerateOpenAPIV2JSONSchemaForCapability,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
schema, err := ConvertOpenAPISchema2SwaggerObject(tc.jsonData)
|
||||
if tc.wantErr {
|
||||
assert.Error(t, err)
|
||||
if tc.errString != "" {
|
||||
assert.EqualError(t, err, tc.errString)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
if tc.checkFunc != nil {
|
||||
tc.checkFunc(t, schema)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user