From 914ccdf4c6d7ea017147a97a85ca17779b99f58b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?= Date: Mon, 5 Jun 2023 23:15:24 +0800 Subject: [PATCH] Improve the unit test coverage of pkg/cmd/server package (#6342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve the unit test coverage of pkg/cmd/server package Signed-off-by: Wenkai Yin(尹文开) --- Makefile | 3 + pkg/client/factory.go | 2 + pkg/client/mocks/Factory.go | 202 ++++++++++++++++++++++++++++++++++ pkg/cmd/server/server_test.go | 202 ++++++++++++++++++++++++++++++++++ pkg/discovery/helper.go | 2 + pkg/discovery/mocks/Helper.go | 156 ++++++++++++++++++++++++++ 6 files changed, 567 insertions(+) create mode 100644 pkg/client/mocks/Factory.go create mode 100644 pkg/discovery/mocks/Helper.go diff --git a/Makefile b/Makefile index f9229ffc9..d188df41e 100644 --- a/Makefile +++ b/Makefile @@ -353,3 +353,6 @@ gen-docs: .PHONY: test-e2e test-e2e: local $(MAKE) -e VERSION=$(VERSION) -C test/e2e run + +go-generate: + go generate ./pkg/... \ No newline at end of file diff --git a/pkg/client/factory.go b/pkg/client/factory.go index 36a6a0459..e57ddc4fe 100644 --- a/pkg/client/factory.go +++ b/pkg/client/factory.go @@ -36,6 +36,8 @@ import ( clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned" ) +//go:generate mockery --name Factory + // Factory knows how to create a VeleroClient and Kubernetes client. type Factory interface { // BindFlags binds common flags (--kubeconfig, --namespace) to the passed-in FlagSet. diff --git a/pkg/client/mocks/Factory.go b/pkg/client/mocks/Factory.go new file mode 100644 index 000000000..008782706 --- /dev/null +++ b/pkg/client/mocks/Factory.go @@ -0,0 +1,202 @@ +// Code generated by mockery v2.28.1. DO NOT EDIT. + +package mocks + +import ( + dynamic "k8s.io/client-go/dynamic" + kubernetes "k8s.io/client-go/kubernetes" + + mock "github.com/stretchr/testify/mock" + + pflag "github.com/spf13/pflag" + + pkgclient "sigs.k8s.io/controller-runtime/pkg/client" + + rest "k8s.io/client-go/rest" + + versioned "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned" +) + +// Factory is an autogenerated mock type for the Factory type +type Factory struct { + mock.Mock +} + +// BindFlags provides a mock function with given fields: flags +func (_m *Factory) BindFlags(flags *pflag.FlagSet) { + _m.Called(flags) +} + +// Client provides a mock function with given fields: +func (_m *Factory) Client() (versioned.Interface, error) { + ret := _m.Called() + + var r0 versioned.Interface + var r1 error + if rf, ok := ret.Get(0).(func() (versioned.Interface, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() versioned.Interface); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(versioned.Interface) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ClientConfig provides a mock function with given fields: +func (_m *Factory) ClientConfig() (*rest.Config, error) { + ret := _m.Called() + + var r0 *rest.Config + var r1 error + if rf, ok := ret.Get(0).(func() (*rest.Config, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() *rest.Config); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*rest.Config) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DynamicClient provides a mock function with given fields: +func (_m *Factory) DynamicClient() (dynamic.Interface, error) { + ret := _m.Called() + + var r0 dynamic.Interface + var r1 error + if rf, ok := ret.Get(0).(func() (dynamic.Interface, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() dynamic.Interface); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(dynamic.Interface) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// KubeClient provides a mock function with given fields: +func (_m *Factory) KubeClient() (kubernetes.Interface, error) { + ret := _m.Called() + + var r0 kubernetes.Interface + var r1 error + if rf, ok := ret.Get(0).(func() (kubernetes.Interface, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() kubernetes.Interface); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(kubernetes.Interface) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// KubebuilderClient provides a mock function with given fields: +func (_m *Factory) KubebuilderClient() (pkgclient.Client, error) { + ret := _m.Called() + + var r0 pkgclient.Client + var r1 error + if rf, ok := ret.Get(0).(func() (pkgclient.Client, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() pkgclient.Client); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(pkgclient.Client) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Namespace provides a mock function with given fields: +func (_m *Factory) Namespace() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// SetBasename provides a mock function with given fields: _a0 +func (_m *Factory) SetBasename(_a0 string) { + _m.Called(_a0) +} + +// SetClientBurst provides a mock function with given fields: _a0 +func (_m *Factory) SetClientBurst(_a0 int) { + _m.Called(_a0) +} + +// SetClientQPS provides a mock function with given fields: _a0 +func (_m *Factory) SetClientQPS(_a0 float32) { + _m.Called(_a0) +} + +type mockConstructorTestingTNewFactory interface { + mock.TestingT + Cleanup(func()) +} + +// NewFactory creates a new instance of Factory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewFactory(t mockConstructorTestingTNewFactory) *Factory { + mock := &Factory{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/cmd/server/server_test.go b/pkg/cmd/server/server_test.go index bab98b42f..4d7944434 100644 --- a/pkg/cmd/server/server_test.go +++ b/pkg/cmd/server/server_test.go @@ -17,14 +17,28 @@ limitations under the License. package server import ( + "context" + "errors" "testing" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + kubefake "k8s.io/client-go/kubernetes/fake" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/client/mocks" "github.com/vmware-tanzu/velero/pkg/controller" + discovery_mocks "github.com/vmware-tanzu/velero/pkg/discovery/mocks" velerotest "github.com/vmware-tanzu/velero/pkg/test" + "github.com/vmware-tanzu/velero/pkg/uploader" ) func TestVeleroResourcesExist(t *testing.T) { @@ -151,3 +165,191 @@ func TestRemoveControllers(t *testing.T) { }) } } + +func TestNewCommand(t *testing.T) { + assert.NotNil(t, NewCommand(nil)) +} + +func Test_newServer(t *testing.T) { + factory := &mocks.Factory{} + logger := logrus.New() + + // invalid uploader type + _, err := newServer(factory, serverConfig{ + uploaderType: "invalid", + }, logger) + assert.NotNil(t, err) + + // invalid clientQPS + _, err = newServer(factory, serverConfig{ + uploaderType: uploader.KopiaType, + clientQPS: -1, + }, logger) + assert.NotNil(t, err) + + // invalid clientBurst + factory.On("SetClientQPS", mock.Anything).Return() + _, err = newServer(factory, serverConfig{ + uploaderType: uploader.KopiaType, + clientQPS: 1, + clientBurst: -1, + }, logger) + assert.NotNil(t, err) + + // invalid clientBclientPageSizeurst + factory.On("SetClientQPS", mock.Anything).Return(). + On("SetClientBurst", mock.Anything).Return() + _, err = newServer(factory, serverConfig{ + uploaderType: uploader.KopiaType, + clientQPS: 1, + clientBurst: 1, + clientPageSize: -1, + }, logger) + assert.NotNil(t, err) + + // got error when creating client + factory.On("SetClientQPS", mock.Anything).Return(). + On("SetClientBurst", mock.Anything).Return(). + On("KubeClient").Return(nil, nil). + On("Client").Return(nil, nil). + On("DynamicClient").Return(nil, errors.New("error")) + _, err = newServer(factory, serverConfig{ + uploaderType: uploader.KopiaType, + clientQPS: 1, + clientBurst: 1, + clientPageSize: 100, + }, logger) + assert.NotNil(t, err) +} + +func Test_namespaceExists(t *testing.T) { + client := kubefake.NewSimpleClientset(&corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "velero", + }, + }) + server := &server{ + kubeClient: client, + logger: logrus.New(), + } + + // namespace doesn't exist + assert.NotNil(t, server.namespaceExists("not-exist")) + + // namespace exists + assert.Nil(t, server.namespaceExists("velero")) +} + +func Test_veleroResourcesExist(t *testing.T) { + helper := &discovery_mocks.Helper{} + server := &server{ + discoveryHelper: helper, + logger: logrus.New(), + } + + // velero resources don't exist + helper.On("Resources").Return(nil) + assert.NotNil(t, server.veleroResourcesExist()) + + // velero resources exist + helper.On("Resources").Unset() + helper.On("Resources").Return([]*metav1.APIResourceList{ + { + GroupVersion: velerov1api.SchemeGroupVersion.String(), + APIResources: []metav1.APIResource{ + {Kind: "Backup"}, + {Kind: "Restore"}, + {Kind: "Schedule"}, + {Kind: "DownloadRequest"}, + {Kind: "DeleteBackupRequest"}, + {Kind: "PodVolumeBackup"}, + {Kind: "PodVolumeRestore"}, + {Kind: "BackupRepository"}, + {Kind: "BackupStorageLocation"}, + {Kind: "VolumeSnapshotLocation"}, + {Kind: "ServerStatusRequest"}, + }, + }, + }) + assert.Nil(t, server.veleroResourcesExist()) +} + +func Test_markInProgressBackupsFailed(t *testing.T) { + scheme := runtime.NewScheme() + velerov1api.AddToScheme(scheme) + + c := fake.NewClientBuilder(). + WithScheme(scheme). + WithLists(&velerov1api.BackupList{ + Items: []velerov1api.Backup{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "velero", + Name: "backup01", + }, + Status: velerov1api.BackupStatus{ + Phase: velerov1api.BackupPhaseInProgress, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "velero", + Name: "backup02", + }, + Status: velerov1api.BackupStatus{ + Phase: velerov1api.BackupPhaseCompleted, + }, + }, + }, + }). + Build() + markInProgressBackupsFailed(context.Background(), c, "velero", logrus.New()) + + backup01 := &velerov1api.Backup{} + require.Nil(t, c.Get(context.Background(), client.ObjectKey{Namespace: "velero", Name: "backup01"}, backup01)) + assert.Equal(t, velerov1api.BackupPhaseFailed, backup01.Status.Phase) + + backup02 := &velerov1api.Backup{} + require.Nil(t, c.Get(context.Background(), client.ObjectKey{Namespace: "velero", Name: "backup02"}, backup02)) + assert.Equal(t, velerov1api.BackupPhaseCompleted, backup02.Status.Phase) +} + +func Test_markInProgressRestoresFailed(t *testing.T) { + scheme := runtime.NewScheme() + velerov1api.AddToScheme(scheme) + + c := fake.NewClientBuilder(). + WithScheme(scheme). + WithLists(&velerov1api.RestoreList{ + Items: []velerov1api.Restore{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "velero", + Name: "restore01", + }, + Status: velerov1api.RestoreStatus{ + Phase: velerov1api.RestorePhaseInProgress, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "velero", + Name: "restore02", + }, + Status: velerov1api.RestoreStatus{ + Phase: velerov1api.RestorePhaseCompleted, + }, + }, + }, + }). + Build() + markInProgressRestoresFailed(context.Background(), c, "velero", logrus.New()) + + restore01 := &velerov1api.Restore{} + require.Nil(t, c.Get(context.Background(), client.ObjectKey{Namespace: "velero", Name: "restore01"}, restore01)) + assert.Equal(t, velerov1api.RestorePhaseFailed, restore01.Status.Phase) + + restore02 := &velerov1api.Restore{} + require.Nil(t, c.Get(context.Background(), client.ObjectKey{Namespace: "velero", Name: "restore02"}, restore02)) + assert.Equal(t, velerov1api.RestorePhaseCompleted, restore02.Status.Phase) +} diff --git a/pkg/discovery/helper.go b/pkg/discovery/helper.go index 7c1926c9a..e925d27d4 100644 --- a/pkg/discovery/helper.go +++ b/pkg/discovery/helper.go @@ -34,6 +34,8 @@ import ( kcmdutil "github.com/vmware-tanzu/velero/third_party/kubernetes/pkg/kubectl/cmd/util" ) +//go:generate mockery --name Helper + // Helper exposes functions for interacting with the Kubernetes discovery // API. type Helper interface { diff --git a/pkg/discovery/mocks/Helper.go b/pkg/discovery/mocks/Helper.go new file mode 100644 index 000000000..a6b9bd74e --- /dev/null +++ b/pkg/discovery/mocks/Helper.go @@ -0,0 +1,156 @@ +// Code generated by mockery v2.28.1. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + schema "k8s.io/apimachinery/pkg/runtime/schema" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + version "k8s.io/apimachinery/pkg/version" +) + +// Helper is an autogenerated mock type for the Helper type +type Helper struct { + mock.Mock +} + +// APIGroups provides a mock function with given fields: +func (_m *Helper) APIGroups() []v1.APIGroup { + ret := _m.Called() + + var r0 []v1.APIGroup + if rf, ok := ret.Get(0).(func() []v1.APIGroup); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]v1.APIGroup) + } + } + + return r0 +} + +// KindFor provides a mock function with given fields: input +func (_m *Helper) KindFor(input schema.GroupVersionKind) (schema.GroupVersionResource, v1.APIResource, error) { + ret := _m.Called(input) + + var r0 schema.GroupVersionResource + var r1 v1.APIResource + var r2 error + if rf, ok := ret.Get(0).(func(schema.GroupVersionKind) (schema.GroupVersionResource, v1.APIResource, error)); ok { + return rf(input) + } + if rf, ok := ret.Get(0).(func(schema.GroupVersionKind) schema.GroupVersionResource); ok { + r0 = rf(input) + } else { + r0 = ret.Get(0).(schema.GroupVersionResource) + } + + if rf, ok := ret.Get(1).(func(schema.GroupVersionKind) v1.APIResource); ok { + r1 = rf(input) + } else { + r1 = ret.Get(1).(v1.APIResource) + } + + if rf, ok := ret.Get(2).(func(schema.GroupVersionKind) error); ok { + r2 = rf(input) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// Refresh provides a mock function with given fields: +func (_m *Helper) Refresh() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ResourceFor provides a mock function with given fields: input +func (_m *Helper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, v1.APIResource, error) { + ret := _m.Called(input) + + var r0 schema.GroupVersionResource + var r1 v1.APIResource + var r2 error + if rf, ok := ret.Get(0).(func(schema.GroupVersionResource) (schema.GroupVersionResource, v1.APIResource, error)); ok { + return rf(input) + } + if rf, ok := ret.Get(0).(func(schema.GroupVersionResource) schema.GroupVersionResource); ok { + r0 = rf(input) + } else { + r0 = ret.Get(0).(schema.GroupVersionResource) + } + + if rf, ok := ret.Get(1).(func(schema.GroupVersionResource) v1.APIResource); ok { + r1 = rf(input) + } else { + r1 = ret.Get(1).(v1.APIResource) + } + + if rf, ok := ret.Get(2).(func(schema.GroupVersionResource) error); ok { + r2 = rf(input) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// Resources provides a mock function with given fields: +func (_m *Helper) Resources() []*v1.APIResourceList { + ret := _m.Called() + + var r0 []*v1.APIResourceList + if rf, ok := ret.Get(0).(func() []*v1.APIResourceList); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*v1.APIResourceList) + } + } + + return r0 +} + +// ServerVersion provides a mock function with given fields: +func (_m *Helper) ServerVersion() *version.Info { + ret := _m.Called() + + var r0 *version.Info + if rf, ok := ret.Get(0).(func() *version.Info); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*version.Info) + } + } + + return r0 +} + +type mockConstructorTestingTNewHelper interface { + mock.TestingT + Cleanup(func()) +} + +// NewHelper creates a new instance of Helper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewHelper(t mockConstructorTestingTNewHelper) *Helper { + mock := &Helper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +}