Files
velero/pkg/uploader/kopia/shim_test.go
Lyndon-Li 34f8b73507 bump up kopia to v0.21.1
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2025-07-22 15:56:04 +08:00

351 lines
10 KiB
Go

/*
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 kopia
import (
"context"
"errors"
"testing"
"time"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/content"
"github.com/kopia/kopia/repo/manifest"
"github.com/kopia/kopia/repo/object"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/vmware-tanzu/velero/pkg/repository/udmrepo"
"github.com/vmware-tanzu/velero/pkg/repository/udmrepo/mocks"
)
func TestShimRepo(t *testing.T) {
ctx := t.Context()
backupRepo := &mocks.BackupRepo{}
backupRepo.On("Time").Return(time.Time{})
shim := NewShimRepo(backupRepo)
// All below calls put together for the implementation are empty or just very simple, and just want to cover testing
// If wanting to write unit tests for some functions could remove it and with writing new function alone
shim.VerifyObject(ctx, object.ID{})
shim.Time()
shim.ClientOptions()
shim.Refresh(ctx)
shim.ContentInfo(ctx, content.ID{})
shim.PrefetchContents(ctx, []content.ID{}, "hint")
shim.PrefetchObjects(ctx, []object.ID{}, "hint")
shim.UpdateDescription("desc")
shim.NewWriter(ctx, repo.WriteSessionOptions{})
shim.OnSuccessfulFlush(func(ctx context.Context, w repo.RepositoryWriter) error { return nil })
backupRepo.On("Close", mock.Anything).Return(nil)
NewShimRepo(backupRepo).Close(ctx)
var id udmrepo.ID
backupRepo.On("PutManifest", mock.Anything, mock.Anything).Return(id, nil)
NewShimRepo(backupRepo).PutManifest(ctx, map[string]string{}, nil)
var mf manifest.ID
backupRepo.On("DeleteManifest", mock.Anything, mock.Anything).Return(nil)
NewShimRepo(backupRepo).DeleteManifest(ctx, mf)
backupRepo.On("Flush", mock.Anything).Return(nil)
NewShimRepo(backupRepo).Flush(ctx)
backupRepo.On("NewObjectWriter", mock.Anything, mock.Anything).Return(nil)
NewShimRepo(backupRepo).NewObjectWriter(ctx, object.WriterOptions{})
}
func TestOpenObject(t *testing.T) {
tests := []struct {
name string
backupRepo *mocks.BackupRepo
isOpenObjectError bool
isReaderNil bool
}{
{
name: "Success",
backupRepo: func() *mocks.BackupRepo {
backupRepo := &mocks.BackupRepo{}
backupRepo.On("OpenObject", mock.Anything, mock.Anything).Return(&shimObjectReader{}, nil)
return backupRepo
}(),
},
{
name: "Open object error",
backupRepo: func() *mocks.BackupRepo {
backupRepo := &mocks.BackupRepo{}
backupRepo.On("OpenObject", mock.Anything, mock.Anything).Return(&shimObjectReader{}, errors.New("Error open object"))
return backupRepo
}(),
isOpenObjectError: true,
},
{
name: "Get nil reader",
backupRepo: func() *mocks.BackupRepo {
backupRepo := &mocks.BackupRepo{}
backupRepo.On("OpenObject", mock.Anything, mock.Anything).Return(nil, nil)
return backupRepo
}(),
isReaderNil: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx := t.Context()
reader, err := NewShimRepo(tc.backupRepo).OpenObject(ctx, object.ID{})
if tc.isOpenObjectError {
require.ErrorContains(t, err, "failed to open object")
} else if tc.isReaderNil {
assert.Nil(t, reader)
} else {
assert.NotNil(t, reader)
assert.NoError(t, err)
}
})
}
}
func TestFindManifests(t *testing.T) {
meta := []*udmrepo.ManifestEntryMetadata{}
tests := []struct {
name string
backupRepo *mocks.BackupRepo
isGetManifestError bool
}{
{
name: "Success",
backupRepo: func() *mocks.BackupRepo {
backupRepo := &mocks.BackupRepo{}
backupRepo.On("FindManifests", mock.Anything, mock.Anything).Return(meta, nil)
return backupRepo
}(),
},
{
name: "Failed to find manifest",
isGetManifestError: true,
backupRepo: func() *mocks.BackupRepo {
backupRepo := &mocks.BackupRepo{}
backupRepo.On("FindManifests", mock.Anything, mock.Anything).Return(meta,
errors.New("failed to find manifest"))
return backupRepo
}(),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx := t.Context()
_, err := NewShimRepo(tc.backupRepo).FindManifests(ctx, map[string]string{})
if tc.isGetManifestError {
require.ErrorContains(t, err, "failed")
} else {
assert.NoError(t, err)
}
})
}
}
func TestShimObjReader(t *testing.T) {
reader := new(shimObjectReader)
objReader := &mocks.ObjectReader{}
reader.repoReader = objReader
// All below calls put together for the implementation are empty or just very simple, and just want to cover testing
// If wanting to write unit tests for some functions could remove it and with writing new function alone
objReader.On("Seek", mock.Anything, mock.Anything).Return(int64(0), nil)
reader.Seek(int64(0), 0)
objReader.On("Read", mock.Anything).Return(0, nil)
reader.Read(nil)
objReader.On("Close").Return(nil)
reader.Close()
objReader.On("Length").Return(int64(0))
reader.Length()
}
func TestShimObjWriter(t *testing.T) {
writer := new(shimObjectWriter)
objWriter := &mocks.ObjectWriter{}
writer.repoWriter = objWriter
// All below calls put together for the implementation are empty or just very simple, and just want to cover testing
// If wanting to write unit tests for some functions could remove it and with writing new function alone
var id udmrepo.ID
objWriter.On("Checkpoint").Return(id, nil)
writer.Checkpoint()
objWriter.On("Result").Return(id, nil)
writer.Result()
objWriter.On("Write", mock.Anything).Return(0, nil)
writer.Write(nil)
objWriter.On("Close").Return(nil)
writer.Close()
}
func TestReplaceManifests(t *testing.T) {
meta1 := udmrepo.ManifestEntryMetadata{
ID: "mani-1",
}
meta2 := udmrepo.ManifestEntryMetadata{
ID: "mani-2",
}
tests := []struct {
name string
backupRepo *mocks.BackupRepo
isGetManifestError bool
expectedError string
expectedID manifest.ID
}{
{
name: "Failed to find manifest",
isGetManifestError: true,
backupRepo: func() *mocks.BackupRepo {
backupRepo := &mocks.BackupRepo{}
backupRepo.On("FindManifests", mock.Anything, mock.Anything).Return([]*udmrepo.ManifestEntryMetadata{},
errors.New("fake-find-error"))
return backupRepo
}(),
expectedError: "unable to load manifests: failed to get manifests with labels map[]: fake-find-error",
},
{
name: "Failed to delete manifest",
isGetManifestError: true,
backupRepo: func() *mocks.BackupRepo {
backupRepo := &mocks.BackupRepo{}
backupRepo.On("FindManifests", mock.Anything, mock.Anything).Return([]*udmrepo.ManifestEntryMetadata{
&meta1,
&meta2,
}, nil)
backupRepo.On("Time").Return(time.Now())
backupRepo.On("DeleteManifest", mock.Anything, mock.Anything).Return(errors.New("fake-delete-error"))
return backupRepo
}(),
expectedError: "unable to delete previous manifest mani-1: fake-delete-error",
},
{
name: "Failed to put manifest",
backupRepo: func() *mocks.BackupRepo {
backupRepo := &mocks.BackupRepo{}
backupRepo.On("FindManifests", mock.Anything, mock.Anything).Return([]*udmrepo.ManifestEntryMetadata{
&meta1,
&meta2,
}, nil)
backupRepo.On("Time").Return(time.Now())
backupRepo.On("DeleteManifest", mock.Anything, mock.Anything).Return(nil)
backupRepo.On("PutManifest", mock.Anything, mock.Anything).Return(udmrepo.ID(""), errors.New("fake-put-error"))
return backupRepo
}(),
expectedError: "fake-put-error",
},
{
name: "Success",
backupRepo: func() *mocks.BackupRepo {
backupRepo := &mocks.BackupRepo{}
backupRepo.On("FindManifests", mock.Anything, mock.Anything).Return([]*udmrepo.ManifestEntryMetadata{
&meta1,
&meta2,
}, nil)
backupRepo.On("Time").Return(time.Now())
backupRepo.On("DeleteManifest", mock.Anything, mock.Anything).Return(nil)
backupRepo.On("PutManifest", mock.Anything, mock.Anything).Return(udmrepo.ID("fake-id"), nil)
return backupRepo
}(),
expectedID: manifest.ID("fake-id"),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx := t.Context()
id, err := NewShimRepo(tc.backupRepo).ReplaceManifests(ctx, map[string]string{}, nil)
if tc.expectedError != "" {
require.EqualError(t, err, tc.expectedError)
} else {
require.NoError(t, err)
}
assert.Equal(t, tc.expectedID, id)
})
}
}
func TestConcatenateObjects(t *testing.T) {
tests := []struct {
name string
backupRepo *mocks.BackupRepo
objectIDs []object.ID
expectedError string
}{
{
name: "empty object list",
expectedError: "object list is empty",
},
{
name: "concatenate error",
backupRepo: func() *mocks.BackupRepo {
backupRepo := &mocks.BackupRepo{}
backupRepo.On("ConcatenateObjects", mock.Anything, mock.Anything).Return(udmrepo.ID(""), errors.New("fake-concatenate-error"))
return backupRepo
}(),
objectIDs: []object.ID{
{},
},
expectedError: "fake-concatenate-error",
},
{
name: "parse error",
backupRepo: func() *mocks.BackupRepo {
backupRepo := &mocks.BackupRepo{}
backupRepo.On("ConcatenateObjects", mock.Anything, mock.Anything).Return(udmrepo.ID("fake-id"), nil)
return backupRepo
}(),
objectIDs: []object.ID{
{},
},
expectedError: "malformed content ID: \"fake-id\": invalid content prefix",
},
{
name: "success",
backupRepo: func() *mocks.BackupRepo {
backupRepo := &mocks.BackupRepo{}
backupRepo.On("ConcatenateObjects", mock.Anything, mock.Anything).Return(udmrepo.ID("I123456"), nil)
return backupRepo
}(),
objectIDs: []object.ID{
{},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx := t.Context()
_, err := NewShimRepo(tc.backupRepo).ConcatenateObjects(ctx, tc.objectIDs, repo.ConcatenateOptions{})
if tc.expectedError != "" {
assert.EqualError(t, err, tc.expectedError)
} else {
assert.NoError(t, err)
}
})
}
}