corso/src/internal/kopia/data_collection_test.go
Keepers 86f9d60bf7
relocate connector mocks (#3048)
It makes more sense for mock data types to be
owned by the service package, not by the connector package as a whole.

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🧹 Tech Debt/Cleanup

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
2023-04-17 17:54:19 +00:00

296 lines
7.0 KiB
Go

package kopia
import (
"bytes"
"context"
"io"
"testing"
"github.com/alcionai/clues"
"github.com/kopia/kopia/fs"
"github.com/kopia/kopia/fs/virtualfs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path"
)
// ---------------
// unit tests
// ---------------
type KopiaDataCollectionUnitSuite struct {
tester.Suite
}
func TestKopiaDataCollectionUnitSuite(t *testing.T) {
suite.Run(t, &KopiaDataCollectionUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *KopiaDataCollectionUnitSuite) TestReturnsPath() {
t := suite.T()
pth, err := path.Build(
"a-tenant",
"a-user",
path.ExchangeService,
path.EmailCategory,
false,
"some", "path", "for", "data")
require.NoError(t, err, clues.ToCore(err))
c := kopiaDataCollection{
streams: []data.Stream{},
path: pth,
}
assert.Equal(t, pth, c.FullPath())
}
func (suite *KopiaDataCollectionUnitSuite) TestReturnsStreams() {
testData := [][]byte{
[]byte("abcdefghijklmnopqrstuvwxyz"),
[]byte("zyxwvutsrqponmlkjihgfedcba"),
}
uuids := []string{
"a-file",
"another-file",
}
table := []struct {
name string
streams []data.Stream
}{
{
name: "SingleStream",
streams: []data.Stream{
&kopiaDataStream{
reader: io.NopCloser(bytes.NewReader(testData[0])),
uuid: uuids[0],
size: int64(len(testData[0])),
},
},
},
{
name: "MultipleStreams",
streams: []data.Stream{
&kopiaDataStream{
reader: io.NopCloser(bytes.NewReader(testData[0])),
uuid: uuids[0],
size: int64(len(testData[0])),
},
&kopiaDataStream{
reader: io.NopCloser(bytes.NewReader(testData[1])),
uuid: uuids[1],
size: int64(len(testData[1])),
},
},
},
}
for _, test := range table {
suite.Run(test.name, func() {
ctx, flush := tester.NewContext()
defer flush()
t := suite.T()
c := kopiaDataCollection{
streams: test.streams,
path: nil,
}
count := 0
for returnedStream := range c.Items(ctx, fault.New(true)) {
require.Less(t, count, len(test.streams))
assert.Equal(t, returnedStream.UUID(), uuids[count])
buf, err := io.ReadAll(returnedStream.ToReader())
require.NoError(t, err, clues.ToCore(err))
assert.Equal(t, buf, testData[count])
require.Implements(t, (*data.StreamSize)(nil), returnedStream)
ss := returnedStream.(data.StreamSize)
assert.Equal(t, len(buf), int(ss.Size()))
count++
}
assert.Equal(t, len(test.streams), count)
})
}
}
// These types are needed because we check that a fs.File was returned.
// Unfortunately fs.StreamingFile and fs.File have different interfaces so we
// have to fake things.
type mockSeeker struct{}
func (s mockSeeker) Seek(offset int64, whence int) (int64, error) {
return 0, clues.New("not implemented")
}
type mockReader struct {
io.ReadCloser
mockSeeker
}
func (r mockReader) Entry() (fs.Entry, error) {
return nil, clues.New("not implemented")
}
type mockFile struct {
// Use for Entry interface.
fs.StreamingFile
r io.ReadCloser
}
func (f *mockFile) Open(ctx context.Context) (fs.Reader, error) {
return mockReader{ReadCloser: f.r}, nil
}
func (suite *KopiaDataCollectionUnitSuite) TestFetch() {
var (
tenant = "a-tenant"
user = "a-user"
service = path.ExchangeService.String()
category = path.EmailCategory
folder1 = "folder1"
folder2 = "folder2"
noErrFileName = "noError"
errFileName = "error"
noErrFileData = "foo bar baz"
errReader = &exchMock.Data{
ReadErr: assert.AnError,
}
)
// Needs to be a function so we can switch the serialization version as
// needed.
getLayout := func(serVersion uint32) fs.Entry {
return virtualfs.NewStaticDirectory(encodeAsPath(tenant), []fs.Entry{
virtualfs.NewStaticDirectory(encodeAsPath(service), []fs.Entry{
virtualfs.NewStaticDirectory(encodeAsPath(user), []fs.Entry{
virtualfs.NewStaticDirectory(encodeAsPath(category.String()), []fs.Entry{
virtualfs.NewStaticDirectory(encodeAsPath(folder1), []fs.Entry{
virtualfs.NewStaticDirectory(encodeAsPath(folder2), []fs.Entry{
&mockFile{
StreamingFile: virtualfs.StreamingFileFromReader(
encodeAsPath(noErrFileName),
nil,
),
r: newBackupStreamReader(
serVersion,
io.NopCloser(bytes.NewReader([]byte(noErrFileData))),
),
},
&mockFile{
StreamingFile: virtualfs.StreamingFileFromReader(
encodeAsPath(errFileName),
nil,
),
r: newBackupStreamReader(
serVersion,
errReader.ToReader(),
),
},
}),
}),
}),
}),
}),
})
}
pth, err := path.Build(
tenant,
user,
path.ExchangeService,
category,
false,
folder1, folder2)
require.NoError(suite.T(), err, clues.ToCore(err))
table := []struct {
name string
inputName string
inputSerializationVersion uint32
expectedData []byte
lookupErr assert.ErrorAssertionFunc
readErr assert.ErrorAssertionFunc
notFoundErr bool
}{
{
name: "FileFound_NoError",
inputName: noErrFileName,
inputSerializationVersion: serializationVersion,
expectedData: []byte(noErrFileData),
lookupErr: assert.NoError,
readErr: assert.NoError,
},
{
name: "FileFound_ReadError",
inputName: errFileName,
inputSerializationVersion: serializationVersion,
lookupErr: assert.NoError,
readErr: assert.Error,
},
{
name: "FileFound_VersionError",
inputName: noErrFileName,
inputSerializationVersion: serializationVersion + 1,
lookupErr: assert.NoError,
readErr: assert.Error,
},
{
name: "FileNotFound",
inputName: "foo",
inputSerializationVersion: serializationVersion + 1,
lookupErr: assert.Error,
notFoundErr: true,
},
}
for _, test := range table {
suite.Run(test.name, func() {
ctx, flush := tester.NewContext()
defer flush()
t := suite.T()
root := getLayout(test.inputSerializationVersion)
c := &i64counter{}
col := &kopiaDataCollection{path: pth, snapshotRoot: root, counter: c}
s, err := col.Fetch(ctx, test.inputName)
test.lookupErr(t, err)
if err != nil {
if test.notFoundErr {
assert.ErrorIs(t, err, data.ErrNotFound, clues.ToCore(err))
}
return
}
fileData, err := io.ReadAll(s.ToReader())
test.readErr(t, err, clues.ToCore(err))
if err != nil {
return
}
assert.Equal(t, test.expectedData, fileData)
})
}
}