diff --git a/changelogs/unreleased/6296-Lyndon-Li b/changelogs/unreleased/6296-Lyndon-Li new file mode 100644 index 000000000..69c0b5ed3 --- /dev/null +++ b/changelogs/unreleased/6296-Lyndon-Li @@ -0,0 +1 @@ +Add UT cases for pkg/repository \ No newline at end of file diff --git a/pkg/repository/config/azure_test.go b/pkg/repository/config/azure_test.go index f32f87ce0..87cd31bf1 100644 --- a/pkg/repository/config/azure_test.go +++ b/pkg/repository/config/azure_test.go @@ -132,3 +132,47 @@ func TestGetStorageDomainFromCloudName(t *testing.T) { }) } } + +func TestGetRequiredValues(t *testing.T) { + testCases := []struct { + name string + mp map[string]string + keys []string + err string + }{ + { + name: "with miss", + mp: map[string]string{ + "key1": "value1", + }, + keys: []string{"key1", "key2", "key3"}, + err: "the following keys do not have values: key2, key3", + }, + { + name: "without miss", + mp: map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + keys: []string{"key1", "key2", "key3"}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := getRequiredValues(func(key string) string { + if tc.mp == nil { + return "" + } else { + return tc.mp[key] + } + }, tc.keys...) + + if err == nil { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.err) + } + }) + } +} diff --git a/pkg/repository/ensurer_test.go b/pkg/repository/ensurer_test.go new file mode 100644 index 000000000..c9e71ea8c --- /dev/null +++ b/pkg/repository/ensurer_test.go @@ -0,0 +1,198 @@ +/* +Copyright the Velero contributors. + +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 repository + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + velerotest "github.com/vmware-tanzu/velero/pkg/test" +) + +func TestEnsureRepo(t *testing.T) { + bkRepoObj := newBackupRepository(velerov1.DefaultNamespace, BackupRepositoryKey{ + VolumeNamespace: "fake-ns", + BackupLocation: "fake-bsl", + RepositoryType: "fake-repo-type", + }) + + bkRepoObj.Status.Phase = velerov1.BackupRepositoryPhaseReady + + scheme := runtime.NewScheme() + velerov1.AddToScheme(scheme) + + tests := []struct { + name string + namespace string + bsl string + repositoryType string + kubeClientObj []runtime.Object + runtimeScheme *runtime.Scheme + expectedRepo *velerov1.BackupRepository + err string + }{ + { + name: "namespace is empty", + bsl: "fake-bsl", + repositoryType: "fake-repo-type", + err: "wrong parameters, namespace \"\", backup storage location \"fake-bsl\", repository type \"fake-repo-type\"", + }, + { + name: "bsl is empty", + namespace: "fake-ns", + repositoryType: "fake-repo-type", + err: "wrong parameters, namespace \"fake-ns\", backup storage location \"\", repository type \"fake-repo-type\"", + }, + { + name: "repositoryType is empty", + namespace: "fake-ns", + bsl: "fake-bsl", + err: "wrong parameters, namespace \"fake-ns\", backup storage location \"fake-bsl\", repository type \"\"", + }, + { + name: "get repo fail", + namespace: "fake-ns", + bsl: "fake-bsl", + repositoryType: "fake-repo-type", + err: "error getting backup repository list: no kind is registered for the type v1.BackupRepositoryList in scheme \"pkg/runtime/scheme.go:100\"", + }, + { + name: "success on existing repo", + namespace: "fake-ns", + bsl: "fake-bsl", + repositoryType: "fake-repo-type", + kubeClientObj: []runtime.Object{ + bkRepoObj, + }, + runtimeScheme: scheme, + expectedRepo: bkRepoObj, + }, + { + name: "create fail", + namespace: "fake-ns", + bsl: "fake-bsl", + repositoryType: "fake-repo-type", + runtimeScheme: scheme, + err: "failed to wait BackupRepository: timed out waiting for the condition", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeClientBuilder := fake.NewClientBuilder() + if test.runtimeScheme != nil { + fakeClientBuilder = fakeClientBuilder.WithScheme(test.runtimeScheme) + } + + fakeClient := fakeClientBuilder.WithRuntimeObjects(test.kubeClientObj...).Build() + + ensurer := NewEnsurer(fakeClient, velerotest.NewLogger(), time.Millisecond) + + repo, err := ensurer.EnsureRepo(context.Background(), velerov1.DefaultNamespace, test.namespace, test.bsl, test.repositoryType) + if err != nil { + assert.EqualError(t, err, test.err) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, test.expectedRepo, repo) + }) + } +} + +func TestCreateBackupRepositoryAndWait(t *testing.T) { + bkRepoObj := newBackupRepository(velerov1.DefaultNamespace, BackupRepositoryKey{ + VolumeNamespace: "fake-ns", + BackupLocation: "fake-bsl", + RepositoryType: "fake-repo-type", + }) + + bkRepoObj.Status.Phase = velerov1.BackupRepositoryPhaseReady + + scheme := runtime.NewScheme() + velerov1.AddToScheme(scheme) + + tests := []struct { + name string + namespace string + bsl string + repositoryType string + kubeClientObj []runtime.Object + runtimeScheme *runtime.Scheme + expectedRepo *velerov1.BackupRepository + err string + }{ + { + name: "create fail", + namespace: "fake-ns", + bsl: "fake-bsl", + repositoryType: "fake-repo-type", + err: "unable to create backup repository resource: no kind is registered for the type v1.BackupRepository in scheme \"pkg/runtime/scheme.go:100\"", + }, + { + name: "get repo fail", + namespace: "fake-ns", + bsl: "fake-bsl", + repositoryType: "fake-repo-type", + kubeClientObj: []runtime.Object{ + bkRepoObj, + }, + runtimeScheme: scheme, + err: "failed to wait BackupRepository: more than one BackupRepository found for workload namespace \"fake-ns\", backup storage location \"fake-bsl\", repository type \"fake-repo-type\"", + }, + { + name: "wait repo fail", + namespace: "fake-ns", + bsl: "fake-bsl", + repositoryType: "fake-repo-type", + runtimeScheme: scheme, + err: "failed to wait BackupRepository: timed out waiting for the condition", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeClientBuilder := fake.NewClientBuilder() + if test.runtimeScheme != nil { + fakeClientBuilder = fakeClientBuilder.WithScheme(test.runtimeScheme) + } + + fakeClient := fakeClientBuilder.WithRuntimeObjects(test.kubeClientObj...).Build() + + ensurer := NewEnsurer(fakeClient, velerotest.NewLogger(), time.Millisecond) + + repo, err := ensurer.createBackupRepositoryAndWait(context.Background(), velerov1.DefaultNamespace, BackupRepositoryKey{ + VolumeNamespace: test.namespace, + BackupLocation: test.bsl, + RepositoryType: test.repositoryType, + }) + if err != nil { + assert.EqualError(t, err, test.err) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, test.expectedRepo, repo) + }) + } +} diff --git a/pkg/repository/udmrepo/kopialib/lib_repo_test.go b/pkg/repository/udmrepo/kopialib/lib_repo_test.go index 19b2089a8..8c01827ae 100644 --- a/pkg/repository/udmrepo/kopialib/lib_repo_test.go +++ b/pkg/repository/udmrepo/kopialib/lib_repo_test.go @@ -405,3 +405,311 @@ func TestShouldLog(t *testing.T) { }) } } + +func TestOpenObject(t *testing.T) { + testCases := []struct { + name string + rawRepo *repomocks.DirectRepository + objectID string + retErr error + expectedErr string + }{ + { + name: "raw repo is nil", + expectedErr: "repo is closed or not open", + }, + { + name: "objectID is invalid", + rawRepo: repomocks.NewDirectRepository(t), + objectID: "fake-id", + expectedErr: "error to parse object ID from fake-id: malformed content ID: \"fake-id\": invalid content prefix", + }, + { + name: "raw open fail", + rawRepo: repomocks.NewDirectRepository(t), + retErr: errors.New("fake-open-error"), + expectedErr: "error to open object: fake-open-error", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + kr := &kopiaRepository{} + + if tc.rawRepo != nil { + if tc.retErr != nil { + tc.rawRepo.On("OpenObject", mock.Anything, mock.Anything).Return(nil, tc.retErr) + } + + kr.rawRepo = tc.rawRepo + } + + _, err := kr.OpenObject(context.Background(), udmrepo.ID(tc.objectID)) + + if tc.expectedErr == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.expectedErr) + } + }) + } +} + +func TestGetManifest(t *testing.T) { + testCases := []struct { + name string + rawRepo *repomocks.DirectRepository + retErr error + expectedErr string + }{ + { + name: "raw repo is nil", + expectedErr: "repo is closed or not open", + }, + { + name: "raw get fail", + rawRepo: repomocks.NewDirectRepository(t), + retErr: errors.New("fake-get-error"), + expectedErr: "error to get manifest: fake-get-error", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + kr := &kopiaRepository{} + + if tc.rawRepo != nil { + if tc.retErr != nil { + tc.rawRepo.On("GetManifest", mock.Anything, mock.Anything, mock.Anything).Return(nil, tc.retErr) + } + + kr.rawRepo = tc.rawRepo + } + + err := kr.GetManifest(context.Background(), udmrepo.ID(""), &udmrepo.RepoManifest{}) + + if tc.expectedErr == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.expectedErr) + } + }) + } +} + +func TestFindManifests(t *testing.T) { + testCases := []struct { + name string + rawRepo *repomocks.DirectRepository + retErr error + expectedErr string + }{ + { + name: "raw repo is nil", + expectedErr: "repo is closed or not open", + }, + { + name: "raw find fail", + rawRepo: repomocks.NewDirectRepository(t), + retErr: errors.New("fake-find-error"), + expectedErr: "error to find manifests: fake-find-error", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + kr := &kopiaRepository{} + + if tc.rawRepo != nil { + tc.rawRepo.On("FindManifests", mock.Anything, mock.Anything).Return(nil, tc.retErr) + kr.rawRepo = tc.rawRepo + } + + _, err := kr.FindManifests(context.Background(), udmrepo.ManifestFilter{}) + + if tc.expectedErr == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.expectedErr) + } + }) + } +} + +func TestClose(t *testing.T) { + testCases := []struct { + name string + rawRepo *repomocks.DirectRepository + rawWriter *repomocks.DirectRepositoryWriter + rawRepoRetErr error + rawWriterRetErr error + expectedErr string + }{ + { + name: "both nil", + }, + { + name: "writer is not nil", + rawWriter: repomocks.NewDirectRepositoryWriter(t), + }, + { + name: "repo is not nil", + rawRepo: repomocks.NewDirectRepository(t), + }, + { + name: "writer close error", + rawWriter: repomocks.NewDirectRepositoryWriter(t), + rawWriterRetErr: errors.New("fake-writer-close-error"), + expectedErr: "error to close repo writer: fake-writer-close-error", + }, + { + name: "repo is not nil", + rawRepo: repomocks.NewDirectRepository(t), + rawRepoRetErr: errors.New("fake-repo-close-error"), + expectedErr: "error to close repo: fake-repo-close-error", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + kr := &kopiaRepository{} + + if tc.rawRepo != nil { + tc.rawRepo.On("Close", mock.Anything).Return(tc.rawRepoRetErr) + kr.rawRepo = tc.rawRepo + } + + if tc.rawWriter != nil { + tc.rawWriter.On("Close", mock.Anything).Return(tc.rawWriterRetErr) + kr.rawWriter = tc.rawWriter + } + + err := kr.Close(context.Background()) + + if tc.expectedErr == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.expectedErr) + } + }) + } +} + +func TestPutManifest(t *testing.T) { + testCases := []struct { + name string + rawWriter *repomocks.DirectRepositoryWriter + rawWriterRetErr error + expectedErr string + }{ + { + name: "raw writer is nil", + expectedErr: "repo writer is closed or not open", + }, + { + name: "raw put fail", + rawWriter: repomocks.NewDirectRepositoryWriter(t), + rawWriterRetErr: errors.New("fake-writer-put-error"), + expectedErr: "error to put manifest: fake-writer-put-error", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + kr := &kopiaRepository{} + + if tc.rawWriter != nil { + tc.rawWriter.On("PutManifest", mock.Anything, mock.Anything, mock.Anything).Return(manifest.ID(""), tc.rawWriterRetErr) + kr.rawWriter = tc.rawWriter + } + + _, err := kr.PutManifest(context.Background(), udmrepo.RepoManifest{ + Metadata: &udmrepo.ManifestEntryMetadata{}, + }) + + if tc.expectedErr == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.expectedErr) + } + }) + } +} + +func TestDeleteManifest(t *testing.T) { + testCases := []struct { + name string + rawWriter *repomocks.DirectRepositoryWriter + rawWriterRetErr error + expectedErr string + }{ + { + name: "raw writer is nil", + expectedErr: "repo writer is closed or not open", + }, + { + name: "raw delete fail", + rawWriter: repomocks.NewDirectRepositoryWriter(t), + rawWriterRetErr: errors.New("fake-writer-delete-error"), + expectedErr: "error to delete manifest: fake-writer-delete-error", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + kr := &kopiaRepository{} + + if tc.rawWriter != nil { + tc.rawWriter.On("DeleteManifest", mock.Anything, mock.Anything).Return(tc.rawWriterRetErr) + kr.rawWriter = tc.rawWriter + } + + err := kr.DeleteManifest(context.Background(), udmrepo.ID("")) + + if tc.expectedErr == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.expectedErr) + } + }) + } +} + +func TestFlush(t *testing.T) { + testCases := []struct { + name string + rawWriter *repomocks.DirectRepositoryWriter + rawWriterRetErr error + expectedErr string + }{ + { + name: "raw writer is nil", + expectedErr: "repo writer is closed or not open", + }, + { + name: "raw flush fail", + rawWriter: repomocks.NewDirectRepositoryWriter(t), + rawWriterRetErr: errors.New("fake-writer-flush-error"), + expectedErr: "error to flush repo: fake-writer-flush-error", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + kr := &kopiaRepository{} + + if tc.rawWriter != nil { + tc.rawWriter.On("Flush", mock.Anything).Return(tc.rawWriterRetErr) + kr.rawWriter = tc.rawWriter + } + + err := kr.Flush(context.Background()) + + if tc.expectedErr == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.expectedErr) + } + }) + } +}