Feat(utils): Enhance unit test coverage and quality for pkg/utils (#6884)

* feat(env): Add comprehensive unit tests for environment management

This commit introduces a comprehensive suite of unit tests for the environment management functions in `pkg/utils/env`.

Key changes include:
- Refactoring the test setup to use `TestMain` for better test environment control.
- Adding new test cases for `CreateEnv`, `GetEnvByName`, `ListEnvs`, `SetCurrentEnv`, `SetEnvLabels`, and `DeleteEnv`.

These tests improve the overall test coverage and ensure the correctness and reliability of environment-related operations.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

* feat(errors): Add unit tests for error handling utilities

This commit introduces new unit tests for the error handling utilities located in `pkg/utils/errors/`.

Specifically, new test files have been added for:
- `crd_test.go`: Tests for CRD-related error checks.
- `list_test.go`: Tests for error list aggregation.
- `reason_test.go`: Tests for specific error reasons like label conflicts and CUE path not found.
- `resourcetracker_test.go`: Tests for resource tracker errors.

These additions improve the test coverage and ensure the robustness of KubeVela's error handling mechanisms.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

* refactor(schema): Refactor ui_schema_test.go to use testify/assert and add new test cases

This commit refactors `pkg/utils/schema/ui_schema_test.go` to improve its readability and maintainability.

Key changes include:
- Migrating from Ginkgo/Gomega to testify/assert for assertions.
- Restructuring `TestGetDefaultUIType` into a table-driven test.
- Adding new comprehensive test cases for `Condition_Validate` function.

These changes enhance the test suite for UI schema utilities, making it more robust and easier to extend.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

* refactor(system): Refactor system_test.go and add comprehensive unit tests

This commit refactors `pkg/utils/system/system_test.go` to improve its structure, readability, and test coverage.

Key changes include:
- Converting existing tests to a table-driven format using `testify/assert`.
- Adding new comprehensive test cases for:
    - `CreateIfNotExist`
    - `GetVelaHomeDir`
    - `GetDirectoryFunctions` (e.g., `GetCapCenterDir`, `GetCapabilityDir`)
    - `InitFunctions` (e.g., `InitCapabilityDir`, `InitCapCenterDir`, `InitDirs`)
    - `BindEnvironmentVariables`

These changes enhance the test suite for system utilities, ensuring their correctness and robustness.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

* feat(types): Add unit tests for QL types

This commit introduces new unit tests for the types defined in `pkg/utils/types`, specifically focusing on types related to KubeVela Query Language (QL).

New test cases cover:
- `ServiceEndpoint.String()`: Verifies the string representation of service endpoints, including various protocols, ports, and paths.
- `AppliedResource.GroupVersionKind()`: Ensures correct extraction of GroupVersionKind from applied resources.
- `ResourceTreeNode.GroupVersionKind()`: Verifies correct extraction of GroupVersionKind from resource tree nodes.

These tests improve the coverage and reliability of core data structures used in KubeVela.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

* feat(util): Add and refactor unit tests for utility functions

This commit introduces new unit tests and refactors existing ones within the `pkg/utils/util` package.

Key changes include:
- **`pkg/utils/util/cmd_test.go`**: Adds comprehensive tests for `IOStreams` and its print functions, as well as `NewDefaultIOStreams` and `NewTestIOStreams`.
- **`pkg/utils/util/factory_test.go`**: Refactors the `GenerateLeaderElectionID` test to a table-driven format and adds new tests for `computeDiscoverCacheDir` and `RestConfigGetter` methods, ensuring the correctness of Kubernetes client configuration and discovery.

These additions and refactorings enhance the test coverage and reliability of core utility functions.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

* feat(utils): Add and refactor unit tests for json, jwt, parse, and strings utilities

This commit introduces new unit tests and refactors existing ones across several utility packages within `pkg/utils/`.

Key changes include:
- **`pkg/utils/json`**: Adds tests for `StrictUnmarshal` to ensure proper JSON unmarshaling.
- **`pkg/utils/jwt`**: Adds tests for JWT token subject extraction and certificate subject retrieval.
- **`pkg/utils/parse`**: Expands test coverage for URL parsing functions (`Parse`, `ParseGitlab`).
- **`pkg/utils/strings`**: Refactors existing tests to a table-driven format and adds tests for box drawing string generation.

These additions and refactorings significantly improve the test coverage and reliability of KubeVela's utility functions.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

* fix(tests): Address test and error handling issues

This commit addresses several issues identified in unit tests and error handling utilities, improving test reliability and code safety.

Key fixes and improvements include:
- **`pkg/utils/errors`**:
    - Added nil check to `IsCuePathNotFound` to prevent panics.
    - Corrected `fmt.Errorf` usage to `errors.New` in `reason_test.go` (SA1006 fix).
    - Used `assert.EqualError` for clearer error message comparisons in `resourcetracker_test.go`.
- **`pkg/utils/jwt_test.go`**: Marked `generateTestCert` as a test helper using `t.Helper()` for better error reporting.
- **`pkg/utils/system_test.go`**:
    - Removed unused `verifyCleanup` field.
    - Modified `TestGetVelaHomeDir` to use a temporary home directory, preventing destructive operations on the user's system.
- **`pkg/utils/util/cmd_test.go`**: Swapped `assert.Equal` arguments to follow `expected, actual` convention.

These changes enhance the robustness and correctness of the test suite and related utility functions.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

* chore(tests): Apply gofmt and import ordering to test files

This commit applies standard Go formatting (`gofmt`) and corrects import ordering in several test files.

Affected files:
- `pkg/utils/schema/ui_schema_test.go`: Added missing newline at EOF.
- `pkg/utils/system/system_test.go`: Corrected import ordering.
- `pkg/utils/util/factory_test.go`: Corrected import ordering.

These changes ensure consistency with project coding standards.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

---------

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>
This commit is contained in:
AshvinBambhaniya2003
2025-09-09 03:05:59 +05:30
committed by GitHub
parent 70e6c9a49f
commit 2758afb1b2
15 changed files with 2018 additions and 205 deletions

View File

@@ -17,85 +17,317 @@ limitations under the License.
package env
import (
"context"
"encoding/json"
"os"
"path/filepath"
"testing"
"time"
"path/filepath"
"github.com/google/go-cmp/cmp"
"github.com/kubevela/pkg/util/singleton"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"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/apis/types"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/system"
)
var testEnv *envtest.Environment
var cfg *rest.Config
var rawClient client.Client
var k8sClient client.Client
var testScheme = runtime.NewScheme()
func TestCreateEnv(t *testing.T) {
testEnv = &envtest.Environment{
func TestMain(m *testing.M) {
testEnv := &envtest.Environment{
ControlPlaneStartTimeout: time.Minute,
ControlPlaneStopTimeout: time.Minute,
CRDDirectoryPaths: []string{
filepath.Join("../../..", "charts/vela-core/crds"), // this has all the required CRDs,
filepath.Join("../../..", "charts/vela-core/crds"),
},
}
var err error
cfg, err = testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
assert.NoError(t, clientgoscheme.AddToScheme(testScheme))
rawClient, err = client.New(cfg, client.Options{Scheme: testScheme})
assert.NoError(t, err)
type want struct {
data string
}
testcases := []struct {
name string
envMeta *types.EnvMeta
want want
}{
{
name: "env-application",
envMeta: &types.EnvMeta{
Name: "env-application",
Namespace: "default",
},
want: want{
data: "",
},
},
{
name: "default",
envMeta: &types.EnvMeta{
Name: "default",
Namespace: "default",
},
want: want{
data: "the namespace default was already assigned to env env-application",
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
singleton.KubeClient.Set(rawClient)
err = CreateEnv(tc.envMeta)
if err != nil && cmp.Diff(tc.want.data, err.Error()) != "" {
t.Errorf("CreateEnv(...): \n -want: \n%s,\n +got:\n%s", tc.want.data, err.Error())
}
})
if err != nil {
panic(err)
}
if err := clientgoscheme.AddToScheme(testScheme); err != nil {
panic(err)
}
if err := v1beta1.AddToScheme(testScheme); err != nil {
panic(err)
}
k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme})
if err != nil {
panic(err)
}
singleton.KubeClient.Set(k8sClient)
code := m.Run()
if err := testEnv.Stop(); err != nil {
panic(err)
}
os.Exit(code)
}
func createTestNamespace(ctx context.Context, t *testing.T, nsName string) {
ns := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: nsName}}
err := k8sClient.Create(ctx, ns)
if err != nil && !apierrors.IsAlreadyExists(err) {
assert.NoError(t, err)
}
}
func TestCreateEnv(t *testing.T) {
ctx := context.Background()
t.Run("create a new env successfully", func(t *testing.T) {
nsName := "create-env-ns-1"
createTestNamespace(ctx, t, nsName)
envMeta := &types.EnvMeta{Name: "env-create-1", Namespace: nsName}
err := CreateEnv(envMeta)
assert.NoError(t, err)
var createdNs v1.Namespace
err = k8sClient.Get(ctx, client.ObjectKey{Name: nsName}, &createdNs)
assert.NoError(t, err)
assert.Equal(t, "env-create-1", createdNs.Labels[oam.LabelNamespaceOfEnvName])
assert.Equal(t, oam.VelaNamespaceUsageEnv, createdNs.Labels[oam.LabelControlPlaneNamespaceUsage])
})
t.Run("create env with a name that already exists", func(t *testing.T) {
nsName1, nsName2 := "create-env-ns-2", "create-env-ns-3"
createTestNamespace(ctx, t, nsName1)
createTestNamespace(ctx, t, nsName2)
envMeta1 := &types.EnvMeta{Name: "env-exist", Namespace: nsName1}
err := CreateEnv(envMeta1)
assert.NoError(t, err)
envMeta2 := &types.EnvMeta{Name: "env-exist", Namespace: nsName2}
err = CreateEnv(envMeta2)
assert.Error(t, err)
assert.Equal(t, "env name env-exist already exists", err.Error())
})
t.Run("create env in a namespace that is already used by another env", func(t *testing.T) {
nsName := "create-env-ns-4"
createTestNamespace(ctx, t, nsName)
envMeta1 := &types.EnvMeta{Name: "env-ns-used-1", Namespace: nsName}
err := CreateEnv(envMeta1)
assert.NoError(t, err)
envMeta2 := &types.EnvMeta{Name: "env-ns-used-2", Namespace: nsName}
err = CreateEnv(envMeta2)
assert.Error(t, err)
assert.Equal(t, "the namespace create-env-ns-4 was already assigned to env env-ns-used-1", err.Error())
})
}
func TestGetEnvByName(t *testing.T) {
ctx := context.Background()
nsName := "get-env-ns"
envName := "test-get"
createTestNamespace(ctx, t, nsName)
assert.NoError(t, CreateEnv(&types.EnvMeta{Name: envName, Namespace: nsName}))
t.Run("get existing env", func(t *testing.T) {
env, err := GetEnvByName(envName)
assert.NoError(t, err)
assert.Equal(t, envName, env.Name)
assert.Equal(t, nsName, env.Namespace)
})
t.Run("get default env", func(t *testing.T) {
env, err := GetEnvByName("default")
assert.NoError(t, err)
assert.Equal(t, "default", env.Name)
assert.Equal(t, "default", env.Namespace)
})
t.Run("get non-existent env", func(t *testing.T) {
_, err := GetEnvByName("non-existent")
assert.Error(t, err)
assert.Contains(t, err.Error(), "not exist")
})
}
func TestListAndCurrentEnvs(t *testing.T) {
ctx := context.Background()
// Setup a temp home for current env file
tmpDir, err := os.MkdirTemp("", "vela-home")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
t.Setenv("HOME", tmpDir)
// Create test envs
nsList1, envList1 := "list-env-ns-1", "test-list-1"
createTestNamespace(ctx, t, nsList1)
assert.NoError(t, CreateEnv(&types.EnvMeta{Name: envList1, Namespace: nsList1}))
nsList2, envList2 := "list-env-ns-2", "test-list-2"
createTestNamespace(ctx, t, nsList2)
assert.NoError(t, CreateEnv(&types.EnvMeta{Name: envList2, Namespace: nsList2}))
t.Run("list specific env", func(t *testing.T) {
envs, err := ListEnvs(envList1)
assert.NoError(t, err)
assert.Equal(t, 1, len(envs))
assert.Equal(t, envList1, envs[0].Name)
})
t.Run("list non-existent env", func(t *testing.T) {
_, err := ListEnvs("non-existent-list")
assert.Error(t, err)
})
t.Run("list all envs and check current marker", func(t *testing.T) {
envs, err := ListEnvs("")
assert.NoError(t, err)
found1, found2, current := false, false, false
for _, e := range envs {
if e.Name == envList1 {
found1 = true
}
if e.Name == envList2 {
found2 = true
}
if e.Current == "*" {
current = true
}
}
assert.True(t, found1)
assert.True(t, found2)
assert.True(t, current) // ListEnvs should set a default current env
})
t.Run("set and get current env", func(t *testing.T) {
testEnvForCurrent := &types.EnvMeta{Name: envList2, Namespace: nsList2}
err := SetCurrentEnv(testEnvForCurrent)
assert.NoError(t, err)
curEnv, err := GetCurrentEnv()
assert.NoError(t, err)
assert.Equal(t, envList2, curEnv.Name)
// Verify ListEnvs reflects the new current env
envs, err := ListEnvs("")
assert.NoError(t, err)
for _, e := range envs {
if e.Name == envList2 {
assert.Equal(t, "*", e.Current)
} else {
assert.Equal(t, "", e.Current)
}
}
})
t.Run("get current env with legacy format", func(t *testing.T) {
envPath, err := system.GetCurrentEnvPath()
assert.NoError(t, err)
err = os.WriteFile(envPath, []byte(envList1), 0644)
assert.NoError(t, err)
curEnv, err := GetCurrentEnv()
assert.NoError(t, err)
assert.Equal(t, envList1, curEnv.Name)
assert.Equal(t, nsList1, curEnv.Namespace)
// Check if the format was updated
data, err := os.ReadFile(filepath.Clean(envPath))
assert.NoError(t, err)
var envMeta types.EnvMeta
assert.NoError(t, json.Unmarshal(data, &envMeta))
assert.Equal(t, envList1, envMeta.Name)
})
}
func TestSetEnvLabels(t *testing.T) {
ctx := context.Background()
nsName := "set-labels-ns"
envName := "test-labels"
createTestNamespace(ctx, t, nsName)
assert.NoError(t, CreateEnv(&types.EnvMeta{Name: envName, Namespace: nsName}))
t.Run("set valid labels", func(t *testing.T) {
envMeta := &types.EnvMeta{Name: envName, Labels: "foo=bar,hello=world"}
err := SetEnvLabels(envMeta)
assert.NoError(t, err)
ns := &v1.Namespace{}
err = k8sClient.Get(ctx, client.ObjectKey{Name: nsName}, ns)
assert.NoError(t, err)
assert.Equal(t, "bar", ns.Labels["foo"])
assert.Equal(t, "world", ns.Labels["hello"])
})
t.Run("set labels on non-existent env", func(t *testing.T) {
envMeta := &types.EnvMeta{Name: "non-existent-labels"}
err := SetEnvLabels(envMeta)
assert.Error(t, err)
})
t.Run("set invalid labels", func(t *testing.T) {
envMeta := &types.EnvMeta{Name: envName, Labels: "invalid-label"}
err := SetEnvLabels(envMeta)
assert.Error(t, err)
})
}
func TestDeleteEnv(t *testing.T) {
ctx := context.Background()
t.Run("delete env with application", func(t *testing.T) {
nsName := "delete-env-ns-with-app"
envName := "test-delete-with-app"
createTestNamespace(ctx, t, nsName)
assert.NoError(t, CreateEnv(&types.EnvMeta{Name: envName, Namespace: nsName}))
app := &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test-app", Namespace: nsName},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{Name: "c1", Type: "worker"}},
},
}
err := k8sClient.Create(ctx, app)
assert.NoError(t, err)
_, err = DeleteEnv(envName)
assert.Error(t, err)
assert.Contains(t, err.Error(), "you can't delete this environment")
})
t.Run("delete empty env", func(t *testing.T) {
nsName := "delete-env-ns-empty"
envName := "test-delete-empty"
createTestNamespace(ctx, t, nsName)
assert.NoError(t, CreateEnv(&types.EnvMeta{Name: envName, Namespace: nsName}))
msg, err := DeleteEnv(envName)
assert.NoError(t, err)
assert.Equal(t, "env "+envName+" deleted", msg)
ns := &v1.Namespace{}
err = k8sClient.Get(ctx, client.ObjectKey{Name: nsName}, ns)
assert.NoError(t, err)
assert.Equal(t, "", ns.Labels[oam.LabelNamespaceOfEnvName])
assert.Equal(t, "", ns.Labels[oam.LabelControlPlaneNamespaceUsage])
})
t.Run("delete non-existent env", func(t *testing.T) {
_, err := DeleteEnv("non-existent-delete")
assert.Error(t, err)
})
}

View File

@@ -0,0 +1,69 @@
/*
Copyright 2020-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 errors
import (
"fmt"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestIsCRDNotExists(t *testing.T) {
noKindMatchErr := &meta.NoKindMatchError{
GroupKind: schema.GroupKind{Group: "testgroup", Kind: "testkind"},
}
wrappedErr := errors.Wrap(noKindMatchErr, "wrapped")
otherErr := fmt.Errorf("some other error")
testCases := []struct {
name string
err error
expected bool
}{
{
name: "is a NoKindMatchError",
err: noKindMatchErr,
expected: true,
},
{
name: "is a wrapped NoKindMatchError",
err: wrappedErr,
expected: true,
},
{
name: "is another error",
err: otherErr,
expected: false,
},
{
name: "is nil",
err: nil,
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := IsCRDNotExists(tc.err)
assert.Equal(t, tc.expected, result)
})
}
}

View File

@@ -0,0 +1,95 @@
/*
Copyright 2021 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 errors
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestErrorList(t *testing.T) {
t.Run("HasError", func(t *testing.T) {
var nilList ErrorList
assert.False(t, nilList.HasError())
emptyList := ErrorList{}
assert.False(t, emptyList.HasError())
listWithErr := ErrorList{fmt.Errorf("err1")}
assert.True(t, listWithErr.HasError())
})
t.Run("Error", func(t *testing.T) {
var nilList ErrorList
assert.Equal(t, "", nilList.Error())
emptyList := ErrorList{}
assert.Equal(t, "", emptyList.Error())
listWithOneErr := ErrorList{fmt.Errorf("err1")}
assert.Equal(t, "Found 1 errors. [(err1)]", listWithOneErr.Error())
listWithTwoErrs := ErrorList{fmt.Errorf("err1"), fmt.Errorf("err2")}
assert.Equal(t, "Found 2 errors. [(err1), (err2)]", listWithTwoErrs.Error())
})
}
func TestAggregateErrors(t *testing.T) {
err1 := fmt.Errorf("err1")
err2 := fmt.Errorf("err2")
testCases := []struct {
name string
errs []error
expected error
}{
{
name: "multiple non-nil errors",
errs: []error{err1, err2},
expected: ErrorList{err1, err2},
},
{
name: "some nil errors",
errs: []error{nil, err1, nil, err2, nil},
expected: ErrorList{err1, err2},
},
{
name: "only nil errors",
errs: []error{nil, nil, nil},
expected: nil,
},
{
name: "empty slice",
errs: []error{},
expected: nil,
},
{
name: "nil slice",
errs: nil,
expected: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := AggregateErrors(tc.errs)
assert.Equal(t, tc.expected, result)
})
}
}

View File

@@ -38,5 +38,8 @@ func IsLabelConflict(err error) bool {
// IsCuePathNotFound checks if the error is cue path not found error
func IsCuePathNotFound(err error) bool {
if err == nil {
return false
}
return strings.Contains(err.Error(), "failed to lookup value") && strings.Contains(err.Error(), "not exist")
}

View File

@@ -0,0 +1,102 @@
/*
Copyright 2021 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 errors
import (
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsLabelConflict(t *testing.T) {
testCases := []struct {
name string
err error
expected bool
}{
{
name: "error contains LabelConflict",
err: fmt.Errorf("this is a LabelConflict error"),
expected: true,
},
{
name: "error is exactly LabelConflict",
err: errors.New(LabelConflict),
expected: true,
},
{
name: "error does not contain LabelConflict",
err: fmt.Errorf("some other error"),
expected: false,
},
{
name: "error is nil",
err: nil,
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := IsLabelConflict(tc.err)
assert.Equal(t, tc.expected, result)
})
}
}
func TestIsCuePathNotFound(t *testing.T) {
testCases := []struct {
name string
err error
expected bool
}{
{
name: "error contains both substrings",
err: fmt.Errorf("failed to lookup value: the path a.b.c does not exist"),
expected: true,
},
{
name: "error contains only first substring",
err: fmt.Errorf("failed to lookup value"),
expected: false,
},
{
name: "error contains only second substring",
err: fmt.Errorf("the path does not exist"),
expected: false,
},
{
name: "error contains neither substring",
err: fmt.Errorf("some other error"),
expected: false,
},
{
name: "is nil",
err: nil,
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := IsCuePathNotFound(tc.err)
assert.Equal(t, tc.expected, result)
})
}
}

View File

@@ -0,0 +1,28 @@
/*
Copyright 2021 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 errors
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestManagedResourceHasNoDataError(t *testing.T) {
err := ManagedResourceHasNoDataError{}
assert.EqualError(t, err, "ManagedResource has no data")
}

86
pkg/utils/json_test.go Normal file
View File

@@ -0,0 +1,86 @@
/*
Copyright 2021 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 utils
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestStrictUnmarshal(t *testing.T) {
t.Parallel()
type sampleStruct struct {
Name string `json:"name"`
Age int `json:"age"`
}
testCases := []struct {
name string
json string
dest interface{}
expectErr bool
assertFunc func(*testing.T, interface{})
}{
{
name: "valid json",
json: `{"name": "test", "age": 10}`,
dest: &sampleStruct{},
expectErr: false,
assertFunc: func(t *testing.T, dest interface{}) {
s := dest.(*sampleStruct)
require.Equal(t, "test", s.Name)
require.Equal(t, 10, s.Age)
},
},
{
name: "unknown field",
json: `{"name": "test", "age": 10, "extra": "field"}`,
dest: &sampleStruct{},
expectErr: true,
},
{
name: "invalid json",
json: `{"name": "test", "age": 10,}`,
dest: &sampleStruct{},
expectErr: true,
},
{
name: "empty json",
json: ``,
dest: &sampleStruct{},
expectErr: true, // EOF
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := StrictUnmarshal([]byte(tc.json), tc.dest)
if tc.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if tc.assertFunc != nil {
tc.assertFunc(t, tc.dest)
}
}
})
}
}

166
pkg/utils/jwt_test.go Normal file
View File

@@ -0,0 +1,166 @@
/*
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 utils
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"testing"
"time"
"github.com/form3tech-oss/jwt-go"
"github.com/stretchr/testify/require"
)
func TestGetTokenSubject(t *testing.T) {
t.Parallel()
tokenWithSub := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{"sub": "test-user"})
tokenWithSubStr, err := tokenWithSub.SignedString([]byte("secret"))
require.NoError(t, err)
tokenWithoutSub := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{"user": "test-user"})
tokenWithoutSubStr, err := tokenWithoutSub.SignedString([]byte("secret"))
require.NoError(t, err)
testCases := []struct {
name string
token string
expectedSub string
expectErr bool
}{
{
name: "valid token with sub",
token: tokenWithSubStr,
expectedSub: "test-user",
// An error is returned because the signature cannot be verified without a key func,
// but the subject should still be extracted.
expectErr: true,
},
{
name: "valid token without sub",
token: tokenWithoutSubStr,
expectedSub: "",
expectErr: true,
},
{
name: "malformed token",
token: "a.b.c",
expectedSub: "",
expectErr: true,
},
{
name: "empty token",
token: "",
expectedSub: "",
expectErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
sub, err := GetTokenSubject(tc.token)
if tc.expectErr {
require.Error(t, err)
}
require.Equal(t, tc.expectedSub, sub)
})
}
}
func generateTestCert(t *testing.T, subject pkix.Name) []byte {
t.Helper()
priv, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: subject,
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
require.NoError(t, err)
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
return pemBytes
}
func TestGetCertificateSubject(t *testing.T) {
t.Parallel()
subject := pkix.Name{CommonName: "test.example.com"}
certPEM := generateTestCert(t, subject)
testCases := []struct {
name string
certBytes []byte
expectedSubject *pkix.Name
expectErr bool
}{
{
name: "valid cert",
certBytes: certPEM,
expectedSubject: &subject,
expectErr: false,
},
{
name: "empty bytes",
certBytes: []byte{},
expectedSubject: nil,
expectErr: false,
},
{
name: "not a pem block",
certBytes: []byte("not pem"),
expectedSubject: nil,
expectErr: false,
},
{
name: "invalid cert bytes in pem",
certBytes: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: []byte("invalid")}),
expectedSubject: nil,
expectErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
s, err := GetCertificateSubject(tc.certBytes)
if tc.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if tc.expectedSubject == nil {
require.Nil(t, s)
} else {
require.NotNil(t, s)
require.Equal(t, tc.expectedSubject.String(), s.String())
}
}
})
}
}

View File

@@ -51,3 +51,142 @@ func TestByteCountIEC(t *testing.T) {
})
}
}
func TestParse(t *testing.T) {
testCases := []struct {
name string
addr string
wantType string
wantContent *Content
wantErr bool
}{
{
name: "github url with branch",
addr: "https://github.com/kubevela/catalog/tree/master/addons/fluxcd",
wantType: TypeGithub,
wantContent: &Content{GithubContent: GithubContent{Owner: "kubevela", Repo: "catalog", Path: "addons/fluxcd", Ref: "master"}},
},
{
name: "github url without branch",
addr: "https://github.com/kubevela/catalog/addons/fluxcd",
wantType: TypeGithub,
wantContent: &Content{GithubContent: GithubContent{Owner: "kubevela", Repo: "catalog", Path: "addons/fluxcd", Ref: ""}},
},
{
name: "github api url with single segment path",
addr: "https://api.github.com/repos/kubevela/catalog/contents/my-addon?ref=master",
wantType: TypeGithub,
wantContent: &Content{GithubContent: GithubContent{Owner: "kubevela", Repo: "catalog", Path: "my-addon", Ref: "master"}},
},
{
name: "gitee url with branch",
addr: "https://gitee.com/kubevela/catalog/tree/master/addons/fluxcd",
wantType: TypeGitee,
wantContent: &Content{GiteeContent: GiteeContent{Owner: "kubevela", Repo: "catalog", Path: "addons/fluxcd", Ref: "master"}},
},
{
name: "gitee url without branch",
addr: "https://gitee.com/kubevela/catalog/addons/fluxcd",
wantType: TypeGitee,
wantContent: &Content{GiteeContent: GiteeContent{Owner: "kubevela", Repo: "catalog", Path: "addons/fluxcd", Ref: ""}},
},
{
name: "oss url",
addr: "oss://kubevela-contrib/registry",
wantType: TypeOss,
wantContent: &Content{OssContent: OssContent{EndPoint: "kubevela-contrib", Bucket: "/registry"}},
},
{
name: "local url",
addr: "file:///Users/somebody/addons",
wantType: TypeLocal,
wantContent: &Content{LocalContent: LocalContent{AbsDir: "/Users/somebody/addons"}},
},
{
name: "invalid github url",
addr: "https://github.com/kubevela",
wantErr: true,
},
{
name: "unsupported git url",
addr: "https://bitbucket.org/foo/bar",
wantErr: true,
},
{
name: "malformed url",
addr: "://abc",
wantErr: true,
},
{
name: "unknown type",
addr: "myscheme://foo/bar",
wantType: TypeUnknown,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
gotType, gotContent, err := Parse(tc.addr)
if tc.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.wantType, gotType)
require.Equal(t, tc.wantContent, gotContent)
})
}
}
func TestParseGitlab(t *testing.T) {
testCases := []struct {
name string
addr string
repo string
wantType string
wantContent *Content
wantErr bool
}{
{
name: "gitlab url without branch",
addr: "https://gitlab.com/kubevela/catalog",
repo: "catalog",
wantType: TypeGitlab,
wantContent: &Content{GitlabContent: GitlabContent{Host: "https://gitlab.com", Owner: "kubevela", Repo: "catalog", Ref: ""}},
},
{
name: "gitlab url with branch",
addr: "https://gitlab.com/kubevela/catalog/tree/master",
repo: "catalog",
wantType: TypeGitlab,
wantContent: &Content{GitlabContent: GitlabContent{Host: "https://gitlab.com", Owner: "kubevela", Repo: "catalog", Ref: "master"}},
},
{
name: "invalid gitlab url repo not match",
addr: "https://gitlab.com/kubevela/catalog",
repo: "wrong-repo",
wantErr: true,
},
{
name: "malformed gitlab url",
addr: "://abc",
repo: "repo",
wantErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
gotType, gotContent, err := ParseGitlab(tc.addr, tc.repo)
if tc.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.wantType, gotType)
require.Equal(t, tc.wantContent, gotContent)
})
}
}

View File

@@ -19,100 +19,105 @@ package schema
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
)
var _ = Describe("Test ui schema utils", func() {
It("Test GetDefaultUIType function", func() {
testCase := []map[string]interface{}{{
"apiType": "string",
"haveOptions": true,
"subType": "",
"haveSub": true,
"result": "Select",
}, {
"apiType": "string",
"haveOptions": false,
"subType": "",
"haveSub": true,
"result": "Input",
}, {
"apiType": "number",
"haveOptions": false,
"subType": "",
"haveSub": true,
"result": "Number",
}, {
"apiType": "integer",
"haveOptions": false,
"subType": "",
"haveSub": true,
"result": "Number",
}, {
"apiType": "boolean",
"haveOptions": false,
"subType": "",
"haveSub": true,
"result": "Switch",
func TestGetDefaultUIType(t *testing.T) {
testCases := []struct {
name string
apiType string
haveOptions bool
subType string
haveSub bool
expected string
}{
{
name: "string with options",
apiType: "string",
haveOptions: true,
expected: "Select",
},
{
"apiType": "array",
"haveOptions": false,
"subType": "string",
"haveSub": true,
"result": "Strings",
},
{
"apiType": "array",
"haveOptions": false,
"subType": "",
"haveSub": true,
"result": "Structs",
},
{
"apiType": "object",
"haveOptions": false,
"subType": "",
"haveSub": true,
"result": "Group",
},
{
"apiType": "object",
"haveOptions": false,
"subType": "",
"haveSub": false,
"result": "KV",
},
}
for _, tc := range testCase {
uiType := GetDefaultUIType(tc["apiType"].(string), tc["haveOptions"].(bool), tc["subType"].(string), tc["haveSub"].(bool))
Expect(uiType).Should(Equal(tc["result"]))
}
})
})
{
name: "string without options",
apiType: "string",
haveOptions: false,
expected: "Input",
},
{
name: "number",
apiType: "number",
expected: "Number",
},
{
name: "integer",
apiType: "integer",
expected: "Number",
},
{
name: "boolean",
apiType: "boolean",
expected: "Switch",
},
{
name: "array of strings",
apiType: "array",
subType: "string",
expected: "Strings",
},
{
name: "array of numbers",
apiType: "array",
subType: "number",
expected: "Numbers",
},
{
name: "array of integers",
apiType: "array",
subType: "integer",
expected: "Numbers",
},
{
name: "array of structs",
apiType: "array",
subType: "object",
expected: "Structs",
},
{
name: "object with sub-parameters",
apiType: "object",
haveSub: true,
expected: "Group",
},
{
name: "object without sub-parameters",
apiType: "object",
haveSub: false,
expected: "KV",
},
{
name: "unknown type",
apiType: "unknown",
expected: "Input",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
uiType := GetDefaultUIType(tc.apiType, tc.haveOptions, tc.subType, tc.haveSub)
assert.Equal(t, tc.expected, uiType)
})
}
}
func TestUISchema_Validate(t *testing.T) {
tests := []struct {
name string
u UISchema
wantErr bool
name string
u UISchema
wantErr bool
errContains string
}{
{
name: "test validate ui schema error",
u: UISchema{
{
Conditions: []Condition{
{
JSONKey: "",
},
},
},
},
wantErr: true,
},
{
name: "test validate ui schema success",
name: "valid schema",
u: UISchema{
{
Conditions: []Condition{
@@ -126,11 +131,148 @@ func TestUISchema_Validate(t *testing.T) {
},
wantErr: false,
},
{
name: "invalid condition with empty jsonkey",
u: UISchema{
{
Conditions: []Condition{
{
JSONKey: "",
},
},
},
},
wantErr: true,
errContains: "the json key of the condition can not be empty",
},
{
name: "invalid condition with bad action",
u: UISchema{
{
Conditions: []Condition{
{
JSONKey: "key",
Action: "badAction",
},
},
},
},
wantErr: true,
errContains: "the action of the condition only supports enable, disable or leave it empty",
},
{
name: "invalid condition with bad op",
u: UISchema{
{
Conditions: []Condition{
{
JSONKey: "key",
Op: "badOp",
},
},
},
},
wantErr: true,
errContains: "the op of the condition must be `==` 、`!=` and `in`",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.u.Validate(); (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
err := tt.u.Validate()
if tt.wantErr {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.errContains)
} else {
assert.NoError(t, err)
}
})
}
}
func TestCondition_Validate(t *testing.T) {
tests := []struct {
name string
c Condition
wantErr bool
errContains string
}{
{
name: "valid case with defaults",
c: Condition{
JSONKey: "myKey",
Value: "myValue",
},
wantErr: false,
},
{
name: "valid case with all fields",
c: Condition{
JSONKey: "myKey",
Op: "==",
Value: "myValue",
Action: "enable",
},
wantErr: false,
},
{
name: "valid op !=",
c: Condition{
JSONKey: "myKey",
Op: "!=",
},
wantErr: false,
},
{
name: "valid op in",
c: Condition{
JSONKey: "myKey",
Op: "in",
},
wantErr: false,
},
{
name: "valid action disable",
c: Condition{
JSONKey: "myKey",
Action: "disable",
},
wantErr: false,
},
{
name: "invalid empty jsonkey",
c: Condition{
JSONKey: "",
},
wantErr: true,
errContains: "the json key of the condition can not be empty",
},
{
name: "invalid action",
c: Condition{
JSONKey: "myKey",
Action: "invalidAction",
},
wantErr: true,
errContains: "the action of the condition only supports enable, disable or leave it empty",
},
{
name: "invalid op",
c: Condition{
JSONKey: "myKey",
Op: ">",
},
wantErr: true,
errContains: "the op of the condition must be `==` 、`!=` and `in`",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.c.Validate()
if tt.wantErr {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.errContains)
} else {
assert.NoError(t, err)
}
})
}

View File

@@ -17,17 +17,157 @@
package utils
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestSanitize(t *testing.T) {
s := "abc\ndef\rgh"
require.Equal(t, "abcdefgh", Sanitize(s))
t.Parallel()
testCases := []struct {
name string
input string
want string
}{
{
name: "with newlines and carriage returns",
input: "abc\ndef\rgh",
want: "abcdefgh",
},
{
name: "empty string",
input: "",
want: "",
},
{
name: "no special characters",
input: "cleanstring",
want: "cleanstring",
},
{
name: "only special characters",
input: "\n\r\n",
want: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := Sanitize(tc.input)
require.Equal(t, tc.want, got)
})
}
}
func TestIgnoreVPrefix(t *testing.T) {
require.Equal(t, "1.2.0", IgnoreVPrefix("v1.2.0"))
require.Equal(t, "1.2.0", IgnoreVPrefix("1.2.0"))
t.Parallel()
testCases := []struct {
name string
input string
want string
}{
{
name: "with lowercase v prefix",
input: "v1.2.0",
want: "1.2.0",
},
{
name: "without v prefix",
input: "1.2.0",
want: "1.2.0",
},
{
name: "with uppercase V prefix",
input: "V1.3.0",
want: "V1.3.0",
},
{
name: "empty string",
input: "",
want: "",
},
{
name: "string is just v",
input: "v",
want: "",
},
{
name: "string is just V",
input: "V",
want: "V",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := IgnoreVPrefix(tc.input)
require.Equal(t, tc.want, got)
})
}
}
func TestGetBoxDrawingString_Chars(t *testing.T) {
t.Parallel()
tests := []struct {
name string
up, down bool
left, right bool
want rune
}{
{"cross ┼", true, true, true, true, '┼'},
{"up down left ┤", true, true, true, false, '┤'},
{"up down right ├", true, true, false, true, '├'},
{"up down only │", true, true, false, false, '│'},
{"up left right ┴", true, false, true, true, '┴'},
{"up left ┘", true, false, true, false, '┘'},
{"up right └", true, false, false, true, '└'},
{"up only ╵", true, false, false, false, '╵'},
{"down left right ┬", false, true, true, true, '┬'},
{"down left ┐", false, true, true, false, '┐'},
{"down right ┌", false, true, false, true, '┌'},
{"down only ╷", false, true, false, false, '╷'},
{"left right ─", false, false, true, true, '─'},
{"left only ╴", false, false, true, false, '╴'},
{"right only ╶", false, false, false, true, '╶'},
{"none space", false, false, false, false, ' '},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := GetBoxDrawingString(tc.up, tc.down, tc.left, tc.right, 0, 0)
require.Equal(t, string(tc.want), got)
})
}
}
func TestGetBoxDrawingString_Padding(t *testing.T) {
t.Parallel()
tests := []struct {
name string
up, down, left, right bool
padLeft, padRight int
center rune
leftPadChar rune
rightPadChar rune
}{
{"no connectors -> spaces", false, false, false, false, 2, 3, ' ', ' ', ' '},
{"left connector pads with lines", false, false, true, false, 2, 1, '╴', '─', ' '},
{"right connector pads with lines", false, false, false, true, 1, 2, '╶', ' ', '─'},
{"both connectors pads with lines", false, false, true, true, 2, 2, '─', '─', '─'},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := GetBoxDrawingString(tc.up, tc.down, tc.left, tc.right, tc.padLeft, tc.padRight)
leftPad := strings.Repeat(string(tc.leftPadChar), tc.padLeft)
rightPad := strings.Repeat(string(tc.rightPadChar), tc.padRight)
expected := leftPad + string(tc.center) + rightPad
require.Equal(t, expected, got)
})
}
}

View File

@@ -22,69 +22,245 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/oam"
)
func TestCreateIfNotExist(t *testing.T) {
testDir := "TestCreateIfNotExist"
defer os.RemoveAll(testDir)
normalCreate := filepath.Join(testDir, "normalCase")
_, err := CreateIfNotExist(normalCreate)
assert.NoError(t, err)
fi, err := os.Stat(normalCreate)
assert.NoError(t, err)
assert.Equal(t, true, fi.IsDir())
normalNestCreate := filepath.Join(testDir, "nested", "normalCase")
_, err = CreateIfNotExist(normalNestCreate)
assert.NoError(t, err)
fi, err = os.Stat(normalNestCreate)
assert.NoError(t, err)
assert.Equal(t, true, fi.IsDir())
}
func TestGetVelaHomeDir(t *testing.T) {
tests := []struct {
name string
want string
preFunc func()
postFun func()
wantErr assert.ErrorAssertionFunc
testCases := []struct {
name string
setup func(t *testing.T) string
wantExisted bool
wantErr bool
}{
{
name: "test get vela home dir from env",
preFunc: func() {
_ = os.Setenv(VelaHomeEnv, "/tmp")
name: "directory does not exist",
setup: func(t *testing.T) string {
return filepath.Join(t.TempDir(), "new-dir")
},
want: "/tmp",
wantErr: assert.NoError,
wantExisted: false,
wantErr: false,
},
{
name: "test use default vela home dir",
preFunc: func() {
_ = os.Unsetenv(VelaHomeEnv)
},
want: filepath.Join(os.Getenv("HOME"), defaultVelaHome),
wantErr: assert.NoError,
postFun: func() {
_ = os.RemoveAll(filepath.Join(os.Getenv("HOME"), defaultVelaHome))
name: "directory already exists",
setup: func(t *testing.T) string {
return t.TempDir()
},
wantExisted: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.preFunc != nil {
tt.preFunc()
}
defer func() {
_ = os.Unsetenv(VelaHomeEnv)
}()
got, err := GetVelaHomeDir()
if !tt.wantErr(t, err, "GetVelaHomeDir()") {
return
}
assert.Equalf(t, tt.want, got, "GetVelaHomeDir()")
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
path := tc.setup(t)
existed, err := CreateIfNotExist(path)
assert.Equal(t, tc.wantExisted, existed)
if tc.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
_, statErr := os.Stat(path)
assert.NoError(t, statErr)
}
})
}
}
func TestGetVelaHomeDir(t *testing.T) {
testCases := []struct {
name string
setup func(t *testing.T) (expectedPath string, cleanup func())
wantErr bool
}{
{
name: "from VELA_HOME env var",
setup: func(t *testing.T) (string, func()) {
tmpDir := t.TempDir()
t.Setenv(VelaHomeEnv, tmpDir)
return tmpDir, func() {}
},
wantErr: false,
},
{
name: "from default user home",
setup: func(t *testing.T) (string, func()) {
tmpHome := t.TempDir()
t.Setenv("HOME", tmpHome)
t.Setenv(VelaHomeEnv, "") // Ensure VELA_HOME is not set, so it falls back to HOME
expectedPath := filepath.Join(tmpHome, defaultVelaHome)
return expectedPath, func() {} // t.TempDir() handles cleanup
},
wantErr: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
expectedPath, cleanup := tc.setup(t)
defer cleanup()
got, err := GetVelaHomeDir()
if tc.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, expectedPath, got)
_, statErr := os.Stat(got)
assert.NoError(t, statErr)
}
})
}
}
func TestGetDirectoryFunctions(t *testing.T) {
tmpDir := t.TempDir()
t.Setenv(VelaHomeEnv, tmpDir)
testCases := []struct {
name string
getFunc func() (string, error)
expectedPath string
}{
{"GetCapCenterDir", GetCapCenterDir, filepath.Join(tmpDir, "centers")},
{"GetRepoConfig", GetRepoConfig, filepath.Join(tmpDir, "centers", "config.yaml")},
{"GetCapabilityDir", GetCapabilityDir, filepath.Join(tmpDir, "capabilities")},
{"GetCurrentEnvPath", GetCurrentEnvPath, filepath.Join(tmpDir, "curenv")},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got, err := tc.getFunc()
assert.NoError(t, err)
assert.Equal(t, tc.expectedPath, got)
})
}
}
func TestInitFunctions(t *testing.T) {
testCases := []struct {
name string
initFunc func() error
verifyFunc func(t *testing.T, velaHome string)
}{
{
name: "InitCapabilityDir",
initFunc: InitCapabilityDir,
verifyFunc: func(t *testing.T, velaHome string) {
dir := filepath.Join(velaHome, "capabilities")
_, err := os.Stat(dir)
assert.NoError(t, err)
},
},
{
name: "InitCapCenterDir",
initFunc: InitCapCenterDir,
verifyFunc: func(t *testing.T, velaHome string) {
dir := filepath.Join(velaHome, "centers", ".tmp")
_, err := os.Stat(dir)
assert.NoError(t, err)
},
},
{
name: "InitDirs",
initFunc: InitDirs,
verifyFunc: func(t *testing.T, velaHome string) {
capDir := filepath.Join(velaHome, "capabilities")
centerDir := filepath.Join(velaHome, "centers", ".tmp")
_, err := os.Stat(capDir)
assert.NoError(t, err)
_, err = os.Stat(centerDir)
assert.NoError(t, err)
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
velaHome := t.TempDir()
t.Setenv(VelaHomeEnv, velaHome)
err := tc.initFunc()
assert.NoError(t, err)
tc.verifyFunc(t, velaHome)
})
}
}
func TestBindEnvironmentVariables(t *testing.T) {
originalVelaNS := types.DefaultKubeVelaNS
originalDefNS := oam.SystemDefinitionNamespace
defer func() {
types.DefaultKubeVelaNS = originalVelaNS
oam.SystemDefinitionNamespace = originalDefNS
}()
testCases := []struct {
name string
setup func(t *testing.T)
wantVelaNS string
wantDefNS string
}{
{
name: "primary env vars",
setup: func(t *testing.T) {
t.Setenv(KubeVelaSystemNamespaceEnv, "vela-system-primary")
t.Setenv(KubeVelaDefinitionNamespaceEnv, "vela-def-primary")
t.Setenv(LegacyKubeVelaSystemNamespaceEnv, "vela-system-legacy")
},
wantVelaNS: "vela-system-primary",
wantDefNS: "vela-def-primary",
},
{
name: "legacy fallback for vela ns",
setup: func(t *testing.T) {
t.Setenv(KubeVelaSystemNamespaceEnv, "")
t.Setenv(LegacyKubeVelaSystemNamespaceEnv, "vela-system-legacy")
t.Setenv(KubeVelaDefinitionNamespaceEnv, "vela-def-primary")
},
wantVelaNS: "vela-system-legacy",
wantDefNS: "vela-def-primary",
},
{
name: "system ns fallback for definition ns",
setup: func(t *testing.T) {
t.Setenv(KubeVelaSystemNamespaceEnv, "vela-system-fallback")
t.Setenv(KubeVelaDefinitionNamespaceEnv, "")
},
wantVelaNS: "vela-system-fallback",
wantDefNS: "vela-system-fallback",
},
{
name: "no env vars set",
setup: func(t *testing.T) {
t.Setenv(KubeVelaSystemNamespaceEnv, "")
t.Setenv(LegacyKubeVelaSystemNamespaceEnv, "")
t.Setenv(KubeVelaDefinitionNamespaceEnv, "")
},
wantVelaNS: "default-vela",
wantDefNS: "default-def",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Reset to known state before each test
if tc.name == "no env vars set" {
types.DefaultKubeVelaNS = "default-vela"
oam.SystemDefinitionNamespace = "default-def"
} else {
types.DefaultKubeVelaNS = originalVelaNS
oam.SystemDefinitionNamespace = originalDefNS
}
tc.setup(t)
BindEnvironmentVariables()
assert.Equal(t, tc.wantVelaNS, types.DefaultKubeVelaNS)
assert.Equal(t, tc.wantDefNS, oam.SystemDefinitionNamespace)
})
}
}

View File

@@ -0,0 +1,180 @@
/*
Copyright 2021 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 types
import (
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestServiceEndpoint_String(t *testing.T) {
strPtr := func(s string) *string { return &s }
testCases := []struct {
name string
endpoint ServiceEndpoint
expected string
}{
{
name: "empty endpoint",
endpoint: ServiceEndpoint{},
expected: "-",
},
{
name: "http default port",
endpoint: ServiceEndpoint{
Endpoint: Endpoint{Host: "example.com", Port: 80, Protocol: corev1.ProtocolTCP, AppProtocol: strPtr("http")},
},
expected: "http://example.com",
},
{
name: "https default port",
endpoint: ServiceEndpoint{
Endpoint: Endpoint{Host: "example.com", Port: 443, Protocol: corev1.ProtocolTCP, AppProtocol: strPtr("https")},
},
expected: "https://example.com",
},
{
name: "http non-default port",
endpoint: ServiceEndpoint{
Endpoint: Endpoint{Host: "example.com", Port: 8080, Protocol: corev1.ProtocolTCP, AppProtocol: strPtr("http")},
},
expected: "http://example.com:8080",
},
{
name: "https non-default port with path",
endpoint: ServiceEndpoint{
Endpoint: Endpoint{Host: "example.com", Port: 8443, Protocol: corev1.ProtocolTCP, AppProtocol: strPtr("https"), Path: "/foo"},
},
expected: "https://example.com:8443/foo",
},
{
name: "path is root",
endpoint: ServiceEndpoint{
Endpoint: Endpoint{Host: "example.com", Port: 8080, Protocol: corev1.ProtocolTCP, AppProtocol: strPtr("http"), Path: "/"},
},
expected: "http://example.com:8080",
},
{
name: "tcp protocol",
endpoint: ServiceEndpoint{
Endpoint: Endpoint{Host: "127.0.0.1", Port: 3306, Protocol: corev1.ProtocolTCP},
},
expected: "127.0.0.1:3306",
},
{
name: "tcp protocol with path",
endpoint: ServiceEndpoint{
Endpoint: Endpoint{Host: "127.0.0.1", Port: 3306, Protocol: corev1.ProtocolTCP, Path: "/"},
},
expected: "127.0.0.1:3306",
},
{
name: "app protocol overrides protocol",
endpoint: ServiceEndpoint{
Endpoint: Endpoint{Host: "mydb", Port: 3306, Protocol: corev1.ProtocolTCP, AppProtocol: strPtr("mysql")},
},
expected: "mysql://mydb:3306",
},
{
name: "port is zero",
endpoint: ServiceEndpoint{
Endpoint: Endpoint{Host: "example.com", Port: 0, Protocol: corev1.ProtocolTCP, AppProtocol: strPtr("http")},
},
expected: "http://example.com",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.endpoint.String())
})
}
}
func TestAppliedResource_GroupVersionKind(t *testing.T) {
testCases := []struct {
name string
resource AppliedResource
expected schema.GroupVersionKind
}{
{
name: "apps resource",
resource: AppliedResource{APIVersion: "apps/v1", Kind: "Deployment"},
expected: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
},
{
name: "core resource",
resource: AppliedResource{APIVersion: "v1", Kind: "Service"},
expected: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"},
},
{
name: "custom resource",
resource: AppliedResource{APIVersion: "core.oam.dev/v1beta1", Kind: "Application"},
expected: schema.GroupVersionKind{Group: "core.oam.dev", Version: "v1beta1", Kind: "Application"},
},
{
name: "empty resource",
resource: AppliedResource{},
expected: schema.GroupVersionKind{Group: "", Version: "", Kind: ""},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.resource.GroupVersionKind())
})
}
}
func TestResourceTreeNode_GroupVersionKind(t *testing.T) {
testCases := []struct {
name string
node ResourceTreeNode
expected schema.GroupVersionKind
}{
{
name: "apps resource",
node: ResourceTreeNode{APIVersion: "apps/v1", Kind: "Deployment"},
expected: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
},
{
name: "core resource",
node: ResourceTreeNode{APIVersion: "v1", Kind: "Service"},
expected: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"},
},
{
name: "custom resource",
node: ResourceTreeNode{APIVersion: "core.oam.dev/v1beta1", Kind: "Application"},
expected: schema.GroupVersionKind{Group: "core.oam.dev", Version: "v1beta1", Kind: "Application"},
},
{
name: "empty resource",
node: ResourceTreeNode{},
expected: schema.GroupVersionKind{Group: "", Version: "", Kind: ""},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.node.GroupVersionKind())
})
}
}

105
pkg/utils/util/cmd_test.go Normal file
View File

@@ -0,0 +1,105 @@
/*
Copyright 2021 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 util
import (
"bytes"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestIOStreams_PrintFunctions(t *testing.T) {
type printFunc func(streams IOStreams)
type checkFunc func(t *testing.T, buf *bytes.Buffer)
testCases := []struct {
name string
printFunc printFunc
checkFunc checkFunc
}{
{
name: "Infonln",
printFunc: func(streams IOStreams) {
streams.Infonln("hello", " ", "world")
},
checkFunc: func(t *testing.T, buf *bytes.Buffer) {
assert.Equal(t, "hello world", buf.String())
},
},
{
name: "Info",
printFunc: func(streams IOStreams) {
streams.Info("hello")
},
checkFunc: func(t *testing.T, buf *bytes.Buffer) {
assert.Equal(t, "hello\n", buf.String())
},
},
{
name: "Infof",
printFunc: func(streams IOStreams) {
streams.Infof("hello %s", "world")
},
checkFunc: func(t *testing.T, buf *bytes.Buffer) {
assert.Equal(t, "hello world", buf.String())
},
},
{
name: "Error",
printFunc: func(streams IOStreams) {
streams.Error("error")
},
checkFunc: func(t *testing.T, buf *bytes.Buffer) {
assert.Equal(t, "error\n", buf.String())
},
},
{
name: "Errorf",
printFunc: func(streams IOStreams) {
streams.Errorf("error %s", "world")
},
checkFunc: func(t *testing.T, buf *bytes.Buffer) {
assert.Equal(t, "error world", buf.String())
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
streams, buf := NewTestIOStreams()
tc.printFunc(streams)
tc.checkFunc(t, buf)
})
}
}
func TestNewDefaultIOStreams(t *testing.T) {
streams := NewDefaultIOStreams()
assert.Equal(t, os.Stdin, streams.In)
assert.Equal(t, os.Stdout, streams.Out)
assert.Equal(t, os.Stderr, streams.ErrOut)
}
func TestNewTestIOStreams(t *testing.T) {
streams, buf := NewTestIOStreams()
assert.NotNil(t, streams.In)
assert.IsType(t, &bytes.Buffer{}, streams.In)
assert.Equal(t, buf, streams.Out)
assert.Equal(t, buf, streams.ErrOut)
}

View File

@@ -17,19 +17,169 @@ limitations under the License.
package util
import (
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"github.com/oam-dev/kubevela/version"
)
func TestGenerateLeaderElectionID(t *testing.T) {
version.VelaVersion = "v10.13.0"
if id := GenerateLeaderElectionID("kubevela", true); id != "kubevela-v10-13-0" {
t.Errorf("id is not as expected(%s != kubevela-v10-13-0)", id)
return
orig := version.VelaVersion
defer func() { version.VelaVersion = orig }()
testCases := []struct {
name string
ver string
versioned bool
want string
}{
{name: "versioned", ver: "v10.13.0", versioned: true, want: "kubevela-v10-13-0"},
{name: "unversioned", ver: "v10.13.0", versioned: false, want: "kubevela"},
}
if id := GenerateLeaderElectionID("kubevela", false); id != "kubevela" {
t.Errorf("id is not as expected(%s != kubevela)", id)
return
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
version.VelaVersion = tc.ver
got := GenerateLeaderElectionID("kubevela", tc.versioned)
require.Equal(t, tc.want, got)
})
}
}
func TestComputeDiscoverCacheDir(t *testing.T) {
t.Parallel()
parent := "/parent"
testCases := []struct {
name string
host string
want string
}{
{name: "https scheme removed", host: "https://example.com:6443", want: "example.com_6443"},
{name: "http scheme removed", host: "http://example.com:6443", want: "example.com_6443"},
{name: "no scheme", host: "k8s.example.io:443", want: "k8s.example.io_443"},
{name: "sanitize special", host: "exa_mple.com:6443?x=y#frag", want: "exa_mple.com_6443_x_y_frag"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := computeDiscoverCacheDir(parent, tc.host)
want := filepath.Join(parent, tc.want)
require.Equal(t, want, got)
})
}
}
func TestRestConfigGetter(t *testing.T) {
t.Parallel()
t.Run("ToRESTConfig returns same pointer", func(t *testing.T) {
t.Parallel()
cfg := &rest.Config{Host: "https://cluster.local"}
r := &restConfigGetter{config: cfg, namespace: "ns"}
got, err := r.ToRESTConfig()
require.NoError(t, err)
require.Same(t, cfg, got)
})
t.Run("NewRestConfigGetterByConfig sets fields", func(t *testing.T) {
t.Parallel()
cfg := &rest.Config{Host: "https://cluster.local"}
g := NewRestConfigGetterByConfig(cfg, "my-ns")
r, ok := g.(*restConfigGetter)
require.True(t, ok, "unexpected underlying type for RESTClientGetter")
require.Equal(t, "my-ns", r.namespace)
require.Same(t, cfg, r.config)
})
}
func TestRestConfigGetter_ToRawKubeConfigLoader(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
config *rest.Config
namespace string
assertFunc func(*testing.T, clientcmd.ClientConfig)
}{
{
name: "simple config",
config: &rest.Config{
Host: "https://my-cluster.local",
},
namespace: "my-ns",
assertFunc: func(t *testing.T, clientConfig clientcmd.ClientConfig) {
ns, _, err := clientConfig.Namespace()
require.NoError(t, err)
require.Equal(t, "my-ns", ns)
},
},
{
name: "full config",
config: &rest.Config{
Host: "https://my-cluster.local:6443",
Username: "my-user",
Password: "my-pass",
BearerToken: "my-token",
TLSClientConfig: rest.TLSClientConfig{
CertFile: "/path/to/cert",
KeyFile: "/path/to/key",
CAFile: "/path/to/ca",
Insecure: true,
},
Timeout: 30 * time.Second,
Impersonate: rest.ImpersonationConfig{
UserName: "impersonate-user",
Groups: []string{"group1", "group2"},
Extra: map[string][]string{"extra": {"val"}},
},
},
namespace: "full-ns",
assertFunc: func(t *testing.T, clientConfig clientcmd.ClientConfig) {
ns, _, err := clientConfig.Namespace()
require.NoError(t, err)
require.Equal(t, "full-ns", ns)
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
getter := NewRestConfigGetterByConfig(tc.config, tc.namespace)
clientConfig := getter.ToRawKubeConfigLoader()
require.NotNil(t, clientConfig)
tc.assertFunc(t, clientConfig)
})
}
}
func TestRestConfigGetter_DiscoveryAndMapper(t *testing.T) {
tmpHome := t.TempDir()
t.Setenv("HOME", tmpHome)
originalDefaultCacheDir := defaultCacheDir
defaultCacheDir = filepath.Join(tmpHome, ".kube", "http-cache")
defer func() { defaultCacheDir = originalDefaultCacheDir }()
cfg := &rest.Config{Host: "http://127.0.0.1:1"}
getter := NewRestConfigGetterByConfig(cfg, "default")
t.Run("ToDiscoveryClient", func(t *testing.T) {
discoveryClient, err := getter.ToDiscoveryClient()
require.NoError(t, err)
require.NotNil(t, discoveryClient)
})
t.Run("ToRESTMapper", func(t *testing.T) {
mapper, err := getter.ToRESTMapper()
require.NoError(t, err)
require.NotNil(t, mapper)
})
}