359 lines
10 KiB
Go
359 lines
10 KiB
Go
package orm
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
"github.com/cosmos/cosmos-sdk/codec/types"
|
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
"github.com/cosmos/cosmos-sdk/types/query"
|
|
"github.com/cosmos/cosmos-sdk/x/group/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestReadAll(t *testing.T) {
|
|
specs := map[string]struct {
|
|
srcIT Iterator
|
|
destSlice func() ModelSlicePtr
|
|
expErr *sdkerrors.Error
|
|
expIDs []RowID
|
|
expResult ModelSlicePtr
|
|
}{
|
|
"all good with object slice": {
|
|
srcIT: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}),
|
|
destSlice: func() ModelSlicePtr {
|
|
x := make([]testdata.TableModel, 1)
|
|
return &x
|
|
},
|
|
expIDs: []RowID{EncodeSequence(1)},
|
|
expResult: &[]testdata.TableModel{{Name: "test"}},
|
|
},
|
|
"all good with pointer slice": {
|
|
srcIT: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}),
|
|
destSlice: func() ModelSlicePtr {
|
|
x := make([]*testdata.TableModel, 1)
|
|
return &x
|
|
},
|
|
expIDs: []RowID{EncodeSequence(1)},
|
|
expResult: &[]*testdata.TableModel{{Name: "test"}},
|
|
},
|
|
"dest slice empty": {
|
|
srcIT: mockIter(EncodeSequence(1), &testdata.TableModel{}),
|
|
destSlice: func() ModelSlicePtr {
|
|
x := make([]testdata.TableModel, 0)
|
|
return &x
|
|
},
|
|
expIDs: []RowID{EncodeSequence(1)},
|
|
expResult: &[]testdata.TableModel{{}},
|
|
},
|
|
"dest pointer with nil value": {
|
|
srcIT: mockIter(EncodeSequence(1), &testdata.TableModel{}),
|
|
destSlice: func() ModelSlicePtr {
|
|
return (*[]testdata.TableModel)(nil)
|
|
},
|
|
expErr: errors.ErrORMInvalidArgument,
|
|
},
|
|
"iterator is nil": {
|
|
srcIT: nil,
|
|
destSlice: func() ModelSlicePtr { return new([]testdata.TableModel) },
|
|
expErr: errors.ErrORMInvalidArgument,
|
|
},
|
|
"dest slice is nil": {
|
|
srcIT: noopIter(),
|
|
destSlice: func() ModelSlicePtr { return nil },
|
|
expErr: errors.ErrORMInvalidArgument,
|
|
},
|
|
"dest slice is not a pointer": {
|
|
srcIT: IteratorFunc(nil),
|
|
destSlice: func() ModelSlicePtr { return make([]testdata.TableModel, 1) },
|
|
expErr: errors.ErrORMInvalidArgument,
|
|
},
|
|
"error on loadNext is returned": {
|
|
srcIT: NewInvalidIterator(),
|
|
destSlice: func() ModelSlicePtr {
|
|
x := make([]testdata.TableModel, 1)
|
|
return &x
|
|
},
|
|
expErr: errors.ErrORMInvalidIterator,
|
|
},
|
|
}
|
|
for msg, spec := range specs {
|
|
t.Run(msg, func(t *testing.T) {
|
|
loaded := spec.destSlice()
|
|
ids, err := ReadAll(spec.srcIT, loaded)
|
|
require.True(t, spec.expErr.Is(err), "expected %s but got %s", spec.expErr, err)
|
|
assert.Equal(t, spec.expIDs, ids)
|
|
if err == nil {
|
|
assert.Equal(t, spec.expResult, loaded)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLimitedIterator(t *testing.T) {
|
|
specs := map[string]struct {
|
|
parent Iterator
|
|
max int
|
|
expectErr bool
|
|
expectedErr string
|
|
exp []testdata.TableModel
|
|
}{
|
|
"nil parent": {
|
|
parent: nil,
|
|
max: 0,
|
|
expectErr: true,
|
|
expectedErr: "parent iterator must not be nil",
|
|
},
|
|
"negative max": {
|
|
parent: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}),
|
|
max: -1,
|
|
expectErr: true,
|
|
expectedErr: "quantity must not be negative",
|
|
},
|
|
"all from range with max > length": {
|
|
parent: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}),
|
|
max: 2,
|
|
exp: []testdata.TableModel{{Name: "test"}},
|
|
},
|
|
"up to max": {
|
|
parent: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}),
|
|
max: 1,
|
|
exp: []testdata.TableModel{{Name: "test"}},
|
|
},
|
|
"none when max = 0": {
|
|
parent: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}),
|
|
max: 0,
|
|
exp: []testdata.TableModel{},
|
|
},
|
|
}
|
|
for msg, spec := range specs {
|
|
t.Run(msg, func(t *testing.T) {
|
|
src, err := LimitIterator(spec.parent, spec.max)
|
|
if spec.expectErr {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), spec.expectedErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
var loaded []testdata.TableModel
|
|
_, err := ReadAll(src, &loaded)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, spec.exp, loaded)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFirst(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
iterator Iterator
|
|
dest codec.ProtoMarshaler
|
|
expectErr bool
|
|
expectedErr string
|
|
expectedRowID RowID
|
|
expectedDest codec.ProtoMarshaler
|
|
}{
|
|
{
|
|
name: "nil iterator",
|
|
iterator: nil,
|
|
dest: &testdata.TableModel{},
|
|
expectErr: true,
|
|
expectedErr: "iterator must not be nil",
|
|
},
|
|
{
|
|
name: "nil dest",
|
|
iterator: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}),
|
|
dest: nil,
|
|
expectErr: true,
|
|
expectedErr: "destination object must not be nil",
|
|
},
|
|
{
|
|
name: "all not nil",
|
|
iterator: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}),
|
|
dest: &testdata.TableModel{},
|
|
expectErr: false,
|
|
expectedRowID: EncodeSequence(1),
|
|
expectedDest: &testdata.TableModel{Name: "test"},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
rowID, err := First(tc.iterator, tc.dest)
|
|
if tc.expectErr {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), tc.expectedErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expectedRowID, rowID)
|
|
require.Equal(t, tc.expectedDest, tc.dest)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPaginate(t *testing.T) {
|
|
interfaceRegistry := types.NewInterfaceRegistry()
|
|
cdc := codec.NewProtoCodec(interfaceRegistry)
|
|
|
|
tb, err := NewAutoUInt64Table(AutoUInt64TablePrefix, AutoUInt64TableSeqPrefix, &testdata.TableModel{}, cdc)
|
|
require.NoError(t, err)
|
|
idx, err := NewIndex(tb, AutoUInt64TableModelByMetadataPrefix, func(val interface{}) ([]interface{}, error) {
|
|
return []interface{}{[]byte(val.(*testdata.TableModel).Metadata)}, nil
|
|
}, testdata.TableModel{}.Metadata)
|
|
require.NoError(t, err)
|
|
|
|
ctx := NewMockContext()
|
|
store := ctx.KVStore(sdk.NewKVStoreKey("test"))
|
|
|
|
metadata := []byte("metadata")
|
|
t1 := testdata.TableModel{
|
|
Id: 1,
|
|
Name: "my test 1",
|
|
Metadata: metadata,
|
|
}
|
|
t2 := testdata.TableModel{
|
|
Id: 2,
|
|
Name: "my test 2",
|
|
Metadata: metadata,
|
|
}
|
|
t3 := testdata.TableModel{
|
|
Id: 3,
|
|
Name: "my test 3",
|
|
Metadata: []byte("other-metadata"),
|
|
}
|
|
t4 := testdata.TableModel{
|
|
Id: 4,
|
|
Name: "my test 4",
|
|
Metadata: metadata,
|
|
}
|
|
t5 := testdata.TableModel{
|
|
Id: 5,
|
|
Name: "my test 5",
|
|
Metadata: []byte("other-metadata"),
|
|
}
|
|
|
|
for _, g := range []testdata.TableModel{t1, t2, t3, t4, t5} {
|
|
_, err := tb.Create(store, &g)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
specs := map[string]struct {
|
|
pageReq *query.PageRequest
|
|
expPageRes *query.PageResponse
|
|
exp []testdata.TableModel
|
|
key []byte
|
|
expErr bool
|
|
}{
|
|
"one item": {
|
|
pageReq: &query.PageRequest{Key: nil, Limit: 1},
|
|
exp: []testdata.TableModel{t1},
|
|
expPageRes: &query.PageResponse{Total: 0, NextKey: EncodeSequence(2)},
|
|
key: metadata,
|
|
},
|
|
"with both key and offset": {
|
|
pageReq: &query.PageRequest{Key: EncodeSequence(2), Offset: 1},
|
|
expErr: true,
|
|
key: metadata,
|
|
},
|
|
"up to max": {
|
|
pageReq: &query.PageRequest{Key: nil, Limit: 3, CountTotal: true},
|
|
exp: []testdata.TableModel{t1, t2, t4},
|
|
expPageRes: &query.PageResponse{Total: 3, NextKey: nil},
|
|
key: metadata,
|
|
},
|
|
"no results": {
|
|
pageReq: &query.PageRequest{Key: nil, Limit: 2, CountTotal: true},
|
|
exp: []testdata.TableModel{},
|
|
expPageRes: &query.PageResponse{Total: 0, NextKey: nil},
|
|
key: sdk.AccAddress([]byte("no-group-address")),
|
|
},
|
|
"with offset and count total": {
|
|
pageReq: &query.PageRequest{Key: nil, Offset: 1, Limit: 2, CountTotal: true},
|
|
exp: []testdata.TableModel{t2, t4},
|
|
expPageRes: &query.PageResponse{Total: 3, NextKey: nil},
|
|
key: metadata,
|
|
},
|
|
"nil/default page req (limit = 100 > number of items)": {
|
|
pageReq: nil,
|
|
exp: []testdata.TableModel{t1, t2, t4},
|
|
expPageRes: &query.PageResponse{Total: 3, NextKey: nil},
|
|
key: metadata,
|
|
},
|
|
"with key and limit < number of elem (count total is ignored in this case)": {
|
|
pageReq: &query.PageRequest{Key: EncodeSequence(2), Limit: 1, CountTotal: true},
|
|
exp: []testdata.TableModel{t2},
|
|
expPageRes: &query.PageResponse{Total: 0, NextKey: EncodeSequence(4)},
|
|
key: metadata,
|
|
},
|
|
"with key and limit >= number of elem": {
|
|
pageReq: &query.PageRequest{Key: EncodeSequence(2), Limit: 2},
|
|
exp: []testdata.TableModel{t2, t4},
|
|
expPageRes: &query.PageResponse{Total: 0, NextKey: nil},
|
|
key: metadata,
|
|
},
|
|
"with nothing left to iterate from key": {
|
|
pageReq: &query.PageRequest{Key: EncodeSequence(5)},
|
|
exp: []testdata.TableModel{},
|
|
expPageRes: &query.PageResponse{Total: 0, NextKey: nil},
|
|
key: metadata,
|
|
},
|
|
}
|
|
for msg, spec := range specs {
|
|
t.Run(msg, func(t *testing.T) {
|
|
var loaded []testdata.TableModel
|
|
|
|
it, err := idx.GetPaginated(store, spec.key, spec.pageReq)
|
|
require.NoError(t, err)
|
|
|
|
res, err := Paginate(it, spec.pageReq, &loaded)
|
|
if spec.expErr {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, spec.exp, loaded)
|
|
assert.EqualValues(t, spec.expPageRes.Total, res.Total)
|
|
assert.EqualValues(t, spec.expPageRes.NextKey, res.NextKey)
|
|
}
|
|
|
|
})
|
|
}
|
|
|
|
t.Run("nil iterator", func(t *testing.T) {
|
|
var loaded []testdata.TableModel
|
|
res, err := Paginate(nil, &query.PageRequest{}, &loaded)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "iterator must not be nil")
|
|
require.Nil(t, res)
|
|
})
|
|
|
|
t.Run("non-slice destination", func(t *testing.T) {
|
|
var loaded testdata.TableModel
|
|
res, err := Paginate(
|
|
mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}),
|
|
&query.PageRequest{},
|
|
&loaded,
|
|
)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "destination must point to a slice")
|
|
require.Nil(t, res)
|
|
})
|
|
}
|
|
|
|
// mockIter encodes + decodes value object.
|
|
func mockIter(rowID RowID, val codec.ProtoMarshaler) Iterator {
|
|
b, err := val.Marshal()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return NewSingleValueIterator(rowID, b)
|
|
}
|
|
|
|
func noopIter() Iterator {
|
|
return IteratorFunc(func(dest codec.ProtoMarshaler) (RowID, error) {
|
|
return nil, nil
|
|
})
|
|
}
|