corso/src/internal/data/item_test.go
ashmrtn 6f25be4ad2
Rename generic item structs and functions (#4421)
`unindexedPrefetchedItem` -> `prefetchedItem`
`prefetchedItem` -> `prefetchedItemWithInfo`
`unindexedLazyItem` -> `lazyItem`
`lazyItem` -> `lazyItemWithInfo`

---

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

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [x] 🧹 Tech Debt/Cleanup

#### Issue(s)

* #4328

#### Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [x] 💚 E2E
2023-10-09 19:12:35 +00:00

416 lines
9.2 KiB
Go

package data_test
import (
"bytes"
"context"
"io"
"testing"
"time"
"github.com/alcionai/clues"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/common/readers"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
)
type errReader struct {
io.ReadCloser
readCount int
errAfter int
err error
}
func (er *errReader) Read(p []byte) (int, error) {
if er.err != nil && er.readCount == er.errAfter {
return 0, er.err
}
toRead := len(p)
if er.readCount+toRead > er.errAfter {
toRead = er.errAfter - er.readCount
}
n, err := er.ReadCloser.Read(p[:toRead])
er.readCount += n
return n, err
}
type ItemUnitSuite struct {
tester.Suite
}
func TestItemUnitSuite(t *testing.T) {
suite.Run(t, &ItemUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *ItemUnitSuite) TestUnindexedPrefetchedItem() {
prefetch, err := data.NewPrefetchedItem(
io.NopCloser(bytes.NewReader([]byte{})),
"foo",
time.Time{})
require.NoError(suite.T(), err, clues.ToCore(err))
var item data.Item = prefetch
_, ok := item.(data.ItemInfo)
assert.False(suite.T(), ok, "unindexedPrefetchedItem implements Info()")
}
func (suite *ItemUnitSuite) TestUnindexedLazyItem() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
lazy := data.NewLazyItem(
ctx,
nil,
"foo",
time.Time{},
fault.New(true))
var item data.Item = lazy
_, ok := item.(data.ItemInfo)
assert.False(t, ok, "unindexedLazyItem implements Info()")
}
func (suite *ItemUnitSuite) TestDeletedItem() {
var (
t = suite.T()
id = "foo"
item = data.NewDeletedItem(id)
)
assert.Equal(t, id, item.ID(), "ID")
assert.True(t, item.Deleted(), "deleted")
}
func (suite *ItemUnitSuite) TestPrefetchedItem() {
var (
id = "foo"
now = time.Now()
baseData = []byte("hello world")
)
table := []struct {
name string
reader io.ReadCloser
info details.ItemInfo
readErr require.ErrorAssertionFunc
expectData []byte
}{
{
name: "EmptyReader",
reader: io.NopCloser(bytes.NewReader([]byte{})),
info: details.ItemInfo{Exchange: &details.ExchangeInfo{Modified: now}},
readErr: require.NoError,
expectData: []byte{},
},
{
name: "ReaderWithData",
reader: io.NopCloser(bytes.NewReader(baseData)),
info: details.ItemInfo{Exchange: &details.ExchangeInfo{Modified: now}},
readErr: require.NoError,
expectData: baseData,
},
{
name: "ReaderWithData DifferentService",
reader: io.NopCloser(bytes.NewReader(baseData)),
info: details.ItemInfo{OneDrive: &details.OneDriveInfo{Modified: now}},
readErr: require.NoError,
expectData: baseData,
},
{
name: "ReaderWithData ReadError",
reader: &errReader{
ReadCloser: io.NopCloser(bytes.NewReader(baseData)),
errAfter: 5,
err: assert.AnError,
},
info: details.ItemInfo{Exchange: &details.ExchangeInfo{Modified: now}},
readErr: require.Error,
expectData: baseData[:5],
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
item, err := data.NewPrefetchedItemWithInfo(test.reader, id, test.info)
require.NoError(t, err, clues.ToCore(err))
assert.Equal(t, id, item.ID(), "ID")
assert.False(t, item.Deleted(), "deleted")
assert.Equal(
t,
test.info.Modified(),
item.ModTime(),
"mod time")
r, err := readers.NewVersionedRestoreReader(item.ToReader())
require.NoError(t, err, "version error: %v", clues.ToCore(err))
if err != nil {
return
}
assert.Equal(t, readers.DefaultSerializationVersion, r.Format().Version)
assert.False(t, r.Format().DelInFlight)
readData, err := io.ReadAll(r)
test.readErr(t, err, "read error: %v", clues.ToCore(err))
assert.Equal(t, test.expectData, readData, "read data")
})
}
}
type mockItemDataGetter struct {
getCalled bool
reader io.ReadCloser
info *details.ItemInfo
delInFlight bool
err error
}
func (mid *mockItemDataGetter) check(t *testing.T, expectCalled bool) {
assert.Equal(t, expectCalled, mid.getCalled, "GetData() called")
}
func (mid *mockItemDataGetter) GetData(
ctx context.Context,
errs *fault.Bus,
) (io.ReadCloser, *details.ItemInfo, bool, error) {
mid.getCalled = true
if mid.err != nil {
errs.AddRecoverable(ctx, mid.err)
}
return mid.reader, mid.info, mid.delInFlight, mid.err
}
func (suite *ItemUnitSuite) TestLazyItem() {
var (
id = "foo"
now = time.Now()
baseData = []byte("hello world")
)
table := []struct {
name string
mid *mockItemDataGetter
versionErr assert.ErrorAssertionFunc
readErr assert.ErrorAssertionFunc
infoErr assert.ErrorAssertionFunc
expectData []byte
expectBusErr bool
}{
{
name: "EmptyReader",
mid: &mockItemDataGetter{
reader: io.NopCloser(bytes.NewReader([]byte{})),
info: &details.ItemInfo{Exchange: &details.ExchangeInfo{Modified: now}},
},
versionErr: assert.NoError,
readErr: assert.NoError,
infoErr: assert.NoError,
expectData: []byte{},
},
{
name: "ReaderWithData",
mid: &mockItemDataGetter{
reader: io.NopCloser(bytes.NewReader(baseData)),
info: &details.ItemInfo{Exchange: &details.ExchangeInfo{Modified: now}},
},
versionErr: assert.NoError,
readErr: assert.NoError,
infoErr: assert.NoError,
expectData: baseData,
},
{
name: "ReaderWithData",
mid: &mockItemDataGetter{
reader: io.NopCloser(bytes.NewReader(baseData)),
info: &details.ItemInfo{OneDrive: &details.OneDriveInfo{Modified: now}},
},
versionErr: assert.NoError,
readErr: assert.NoError,
infoErr: assert.NoError,
expectData: baseData,
},
{
name: "ReaderWithData GetDataError",
mid: &mockItemDataGetter{
err: assert.AnError,
},
versionErr: assert.Error,
readErr: assert.Error,
infoErr: assert.Error,
expectData: []byte{},
expectBusErr: true,
},
{
name: "ReaderWithData ReadError",
mid: &mockItemDataGetter{
reader: &errReader{
ReadCloser: io.NopCloser(bytes.NewReader(baseData)),
errAfter: 5,
err: assert.AnError,
},
info: &details.ItemInfo{OneDrive: &details.OneDriveInfo{Modified: now}},
},
versionErr: assert.NoError,
readErr: assert.Error,
infoErr: assert.NoError,
expectData: baseData[:5],
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
errs := fault.New(true)
defer test.mid.check(t, true)
item := data.NewLazyItemWithInfo(
ctx,
test.mid,
id,
now,
errs)
assert.Equal(t, id, item.ID(), "ID")
assert.False(t, item.Deleted(), "deleted")
assert.Equal(
t,
now,
item.ModTime(),
"mod time")
// Read data to execute lazy reader.
r, err := readers.NewVersionedRestoreReader(item.ToReader())
test.versionErr(t, err, "version error: %v", clues.ToCore(err))
if err != nil {
return
}
assert.Equal(t, readers.DefaultSerializationVersion, r.Format().Version)
assert.False(t, r.Format().DelInFlight)
readData, err := io.ReadAll(r)
test.readErr(t, err, clues.ToCore(err), "read error")
assert.Equal(t, test.expectData, readData, "read data")
_, err = item.Info()
test.infoErr(t, err, "Info(): %v", clues.ToCore(err))
e := errs.Errors()
if !test.expectBusErr {
assert.Nil(t, e.Failure, "hard failure")
assert.Empty(t, e.Recovered, "recovered")
return
}
assert.NotNil(t, e.Failure, "hard failure")
})
}
}
func (suite *ItemUnitSuite) TestLazyItem_DeletedInFlight() {
var (
id = "foo"
now = time.Now()
)
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
errs := fault.New(true)
mid := &mockItemDataGetter{delInFlight: true}
defer mid.check(t, true)
item := data.NewLazyItemWithInfo(ctx, mid, id, now, errs)
assert.Equal(t, id, item.ID(), "ID")
assert.False(t, item.Deleted(), "deleted")
assert.Equal(
t,
now,
item.ModTime(),
"mod time")
// Read data to execute lazy reader.
r, err := readers.NewVersionedRestoreReader(item.ToReader())
require.NoError(t, err, "version error: %v", clues.ToCore(err))
assert.Equal(t, readers.DefaultSerializationVersion, r.Format().Version)
assert.True(t, r.Format().DelInFlight)
readData, err := io.ReadAll(r)
require.NoError(t, err, clues.ToCore(err), "read error")
assert.Empty(t, readData, "read data")
_, err = item.Info()
assert.ErrorIs(t, err, data.ErrNotFound, "Info() error")
e := errs.Errors()
assert.Nil(t, e.Failure, "hard failure")
assert.Empty(t, e.Recovered, "recovered")
}
func (suite *ItemUnitSuite) TestLazyItem_InfoBeforeReadErrors() {
var (
id = "foo"
now = time.Now()
)
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
errs := fault.New(true)
mid := &mockItemDataGetter{}
defer mid.check(t, false)
item := data.NewLazyItemWithInfo(ctx, mid, id, now, errs)
assert.Equal(t, id, item.ID(), "ID")
assert.False(t, item.Deleted(), "deleted")
assert.Equal(
t,
now,
item.ModTime(),
"mod time")
_, err := item.Info()
assert.Error(t, err, "Info() error")
}