cosmos-sdk/x/group/internal/orm/index_test.go

435 lines
12 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"
)
var _ Indexable = &nilRowGetterBuilder{}
type nilRowGetterBuilder struct{}
func (b *nilRowGetterBuilder) RowGetter() RowGetter {
return nil
}
func (b *nilRowGetterBuilder) AddAfterSetInterceptor(AfterSetInterceptor) {}
func (b *nilRowGetterBuilder) AddAfterDeleteInterceptor(AfterDeleteInterceptor) {}
func TestNewIndex(t *testing.T) {
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
myTable, err := NewAutoUInt64Table(AutoUInt64TablePrefix, AutoUInt64TableSeqPrefix, &testdata.TableModel{}, cdc)
require.NoError(t, err)
indexer := func(val interface{}) ([]interface{}, error) {
return []interface{}{val.(*testdata.TableModel).Metadata}, nil
}
testCases := []struct {
name string
table Indexable
expectErr bool
expectedErr string
indexKey interface{}
}{
{
name: "nil indexKey",
table: myTable,
expectErr: true,
expectedErr: "indexKey must not be nil",
indexKey: nil,
},
{
name: "nil rowGetter",
table: &nilRowGetterBuilder{},
expectErr: true,
expectedErr: "rowGetter must not be nil",
indexKey: []byte{},
},
{
name: "all not nil",
table: myTable,
expectErr: false,
indexKey: []byte{},
},
{
name: "index key type not allowed",
table: myTable,
expectErr: true,
indexKey: 1,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
index, err := NewIndex(tc.table, AutoUInt64TableSeqPrefix, indexer, tc.indexKey)
if tc.expectErr {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr)
} else {
require.NoError(t, err)
require.NotEmpty(t, index)
}
})
}
}
func TestIndexPrefixScan(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) {
i := []interface{}{val.(*testdata.TableModel).Metadata}
return i, nil
}, testdata.TableModel{}.Metadata)
require.NoError(t, err)
strIdx, err := NewIndex(tb, 0x1, func(val interface{}) ([]interface{}, error) {
i := []interface{}{val.(*testdata.TableModel).Name}
return i, nil
}, testdata.TableModel{}.Name)
require.NoError(t, err)
ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))
g1 := testdata.TableModel{
Id: 1,
Name: "my test 1",
Metadata: []byte("metadata-a"),
}
g2 := testdata.TableModel{
Id: 2,
Name: "my test 2",
Metadata: []byte("metadata-b"),
}
g3 := testdata.TableModel{
Id: 3,
Name: "my test 3",
Metadata: []byte("metadata-b"),
}
for _, g := range []testdata.TableModel{g1, g2, g3} {
_, err := tb.Create(store, &g)
require.NoError(t, err)
}
specs := map[string]struct {
start, end interface{}
expResult []testdata.TableModel
expRowIDs []RowID
expError *sdkerrors.Error
method func(store sdk.KVStore, start, end interface{}) (Iterator, error)
}{
"exact match with a single result": {
start: []byte("metadata-a"),
end: []byte("metadata-b"),
method: idx.PrefixScan,
expResult: []testdata.TableModel{g1},
expRowIDs: []RowID{EncodeSequence(1)},
},
"one result by prefix": {
start: []byte("metadata"),
end: []byte("metadata-b"),
method: idx.PrefixScan,
expResult: []testdata.TableModel{g1},
expRowIDs: []RowID{EncodeSequence(1)},
},
"multi key elements by exact match": {
start: []byte("metadata-b"),
end: []byte("metadata-c"),
method: idx.PrefixScan,
expResult: []testdata.TableModel{g2, g3},
expRowIDs: []RowID{EncodeSequence(2), EncodeSequence(3)},
},
"open end query": {
start: []byte("metadata-b"),
end: nil,
method: idx.PrefixScan,
expResult: []testdata.TableModel{g2, g3},
expRowIDs: []RowID{EncodeSequence(2), EncodeSequence(3)},
},
"open start query": {
start: nil,
end: []byte("metadata-b"),
method: idx.PrefixScan,
expResult: []testdata.TableModel{g1},
expRowIDs: []RowID{EncodeSequence(1)},
},
"open start and end query": {
start: nil,
end: nil,
method: idx.PrefixScan,
expResult: []testdata.TableModel{g1, g2, g3},
expRowIDs: []RowID{EncodeSequence(1), EncodeSequence(2), EncodeSequence(3)},
},
"all matching prefix": {
start: []byte("admin"),
end: nil,
method: idx.PrefixScan,
expResult: []testdata.TableModel{g1, g2, g3},
expRowIDs: []RowID{EncodeSequence(1), EncodeSequence(2), EncodeSequence(3)},
},
"non matching prefix": {
start: []byte("metadata-c"),
end: nil,
method: idx.PrefixScan,
expResult: []testdata.TableModel{},
},
"start equals end": {
start: []byte("any"),
end: []byte("any"),
method: idx.PrefixScan,
expError: errors.ErrORMInvalidArgument,
},
"start after end": {
start: []byte("b"),
end: []byte("a"),
method: idx.PrefixScan,
expError: errors.ErrORMInvalidArgument,
},
"reverse: exact match with a single result": {
start: []byte("metadata-a"),
end: []byte("metadata-b"),
method: idx.ReversePrefixScan,
expResult: []testdata.TableModel{g1},
expRowIDs: []RowID{EncodeSequence(1)},
},
"reverse: one result by prefix": {
start: []byte("metadata"),
end: []byte("metadata-b"),
method: idx.ReversePrefixScan,
expResult: []testdata.TableModel{g1},
expRowIDs: []RowID{EncodeSequence(1)},
},
"reverse: multi key elements by exact match": {
start: []byte("metadata-b"),
end: []byte("metadata-c"),
method: idx.ReversePrefixScan,
expResult: []testdata.TableModel{g3, g2},
expRowIDs: []RowID{EncodeSequence(3), EncodeSequence(2)},
},
"reverse: open end query": {
start: []byte("metadata-b"),
end: nil,
method: idx.ReversePrefixScan,
expResult: []testdata.TableModel{g3, g2},
expRowIDs: []RowID{EncodeSequence(3), EncodeSequence(2)},
},
"reverse: open start query": {
start: nil,
end: []byte("metadata-b"),
method: idx.ReversePrefixScan,
expResult: []testdata.TableModel{g1},
expRowIDs: []RowID{EncodeSequence(1)},
},
"reverse: open start and end query": {
start: nil,
end: nil,
method: idx.ReversePrefixScan,
expResult: []testdata.TableModel{g3, g2, g1},
expRowIDs: []RowID{EncodeSequence(3), EncodeSequence(2), EncodeSequence(1)},
},
"reverse: all matching prefix": {
start: []byte("admin"),
end: nil,
method: idx.ReversePrefixScan,
expResult: []testdata.TableModel{g3, g2, g1},
expRowIDs: []RowID{EncodeSequence(3), EncodeSequence(2), EncodeSequence(1)},
},
"reverse: non matching prefix": {
start: []byte("metadata-c"),
end: nil,
method: idx.ReversePrefixScan,
expResult: []testdata.TableModel{},
},
"reverse: start equals end": {
start: []byte("any"),
end: []byte("any"),
method: idx.ReversePrefixScan,
expError: errors.ErrORMInvalidArgument,
},
"reverse: start after end": {
start: []byte("b"),
end: []byte("a"),
method: idx.ReversePrefixScan,
expError: errors.ErrORMInvalidArgument,
},
"exact match with a single result using string based index": {
start: "my test 1",
end: "my test 2",
method: strIdx.PrefixScan,
expResult: []testdata.TableModel{g1},
expRowIDs: []RowID{EncodeSequence(1)},
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
it, err := spec.method(store, spec.start, spec.end)
require.True(t, spec.expError.Is(err), "expected #+v but got #+v", spec.expError, err)
if spec.expError != nil {
return
}
var loaded []testdata.TableModel
rowIDs, err := ReadAll(it, &loaded)
require.NoError(t, err)
assert.Equal(t, spec.expResult, loaded)
assert.Equal(t, spec.expRowIDs, rowIDs)
})
}
}
func TestUniqueIndex(t *testing.T) {
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
myTable, err := NewPrimaryKeyTable(PrimaryKeyTablePrefix, &testdata.TableModel{}, cdc)
require.NoError(t, err)
uniqueIdx, err := NewUniqueIndex(myTable, 0x10, func(val interface{}) (interface{}, error) {
return []byte{val.(*testdata.TableModel).Metadata[0]}, nil
}, []byte{})
require.NoError(t, err)
ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))
m := testdata.TableModel{
Id: 1,
Name: "my test",
Metadata: []byte("metadata"),
}
err = myTable.Create(store, &m)
require.NoError(t, err)
indexedKey := []byte{'m'}
// Has
exists, err := uniqueIdx.Has(store, indexedKey)
require.NoError(t, err)
assert.True(t, exists)
// Get
it, err := uniqueIdx.Get(store, indexedKey)
require.NoError(t, err)
var loaded testdata.TableModel
rowID, err := it.LoadNext(&loaded)
require.NoError(t, err)
require.Equal(t, RowID(PrimaryKey(&m)), rowID)
require.Equal(t, m, loaded)
// GetPaginated
cases := map[string]struct {
pageReq *query.PageRequest
expErr bool
}{
"nil key": {
pageReq: &query.PageRequest{Key: nil},
expErr: false,
},
"after indexed key": {
pageReq: &query.PageRequest{Key: indexedKey},
expErr: true,
},
}
for testName, tc := range cases {
t.Run(testName, func(t *testing.T) {
it, err := uniqueIdx.GetPaginated(store, indexedKey, tc.pageReq)
require.NoError(t, err)
rowID, err := it.LoadNext(&loaded)
if tc.expErr { // iterator done
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, RowID(PrimaryKey(&m)), rowID)
require.Equal(t, m, loaded)
}
})
}
// PrefixScan match
it, err = uniqueIdx.PrefixScan(store, indexedKey, nil)
require.NoError(t, err)
rowID, err = it.LoadNext(&loaded)
require.NoError(t, err)
require.Equal(t, RowID(PrimaryKey(&m)), rowID)
require.Equal(t, m, loaded)
// PrefixScan no match
it, err = uniqueIdx.PrefixScan(store, []byte{byte('n')}, nil)
require.NoError(t, err)
_, err = it.LoadNext(&loaded)
require.Error(t, errors.ErrORMIteratorDone, err)
// ReversePrefixScan match
it, err = uniqueIdx.ReversePrefixScan(store, indexedKey, nil)
require.NoError(t, err)
rowID, err = it.LoadNext(&loaded)
require.NoError(t, err)
require.Equal(t, RowID(PrimaryKey(&m)), rowID)
require.Equal(t, m, loaded)
// ReversePrefixScan no match
it, err = uniqueIdx.ReversePrefixScan(store, []byte{byte('l')}, nil)
require.NoError(t, err)
_, err = it.LoadNext(&loaded)
require.Error(t, errors.ErrORMIteratorDone, err)
// create with same index key should fail
new := testdata.TableModel{
Id: 1,
Name: "my test",
Metadata: []byte("my-metadata"),
}
err = myTable.Create(store, &new)
require.Error(t, errors.ErrORMUniqueConstraint, err)
// and when delete
err = myTable.Delete(store, &m)
require.NoError(t, err)
// then no persistent element
exists, err = uniqueIdx.Has(store, indexedKey)
require.NoError(t, err)
assert.False(t, exists)
}
func TestPrefixRange(t *testing.T) {
cases := map[string]struct {
src []byte
expStart []byte
expEnd []byte
expPanic bool
}{
"normal": {src: []byte{1, 3, 4}, expStart: []byte{1, 3, 4}, expEnd: []byte{1, 3, 5}},
"normal short": {src: []byte{79}, expStart: []byte{79}, expEnd: []byte{80}},
"empty case": {src: []byte{}},
"roll-over example 1": {src: []byte{17, 28, 255}, expStart: []byte{17, 28, 255}, expEnd: []byte{17, 29, 0}},
"roll-over example 2": {src: []byte{15, 42, 255, 255}, expStart: []byte{15, 42, 255, 255}, expEnd: []byte{15, 43, 0, 0}},
"pathological roll-over": {src: []byte{255, 255, 255, 255}, expStart: []byte{255, 255, 255, 255}},
"nil prohibited": {expPanic: true},
}
for testName, tc := range cases {
t.Run(testName, func(t *testing.T) {
if tc.expPanic {
require.Panics(t, func() {
PrefixRange(tc.src)
})
return
}
start, end := PrefixRange(tc.src)
assert.Equal(t, tc.expStart, start)
assert.Equal(t, tc.expEnd, end)
})
}
}