mukan-sdk/x/group/internal/orm/indexer_test.go
Mukan Erkin Törük 20afb5db80
Some checks failed
Build SimApp / build (amd64) (push) Waiting to run
Build SimApp / build (arm64) (push) Waiting to run
CodeQL / Analyze (push) Waiting to run
Build & Push / build (push) Waiting to run
Run Gosec / Gosec (push) Waiting to run
Lint / golangci-lint (push) Waiting to run
Checks dependencies and mocks generation / Check go mod tidy (push) Waiting to run
Checks dependencies and mocks generation / Check up to date mocks (push) Waiting to run
System Tests / setup (push) Waiting to run
System Tests / test-system (push) Blocked by required conditions
System Tests / test-system-legacy (push) Blocked by required conditions
Tests / Code Coverage / split-test-files (push) Waiting to run
Tests / Code Coverage / tests (00) (push) Blocked by required conditions
Tests / Code Coverage / tests (01) (push) Blocked by required conditions
Tests / Code Coverage / tests (02) (push) Blocked by required conditions
Tests / Code Coverage / tests (03) (push) Blocked by required conditions
Tests / Code Coverage / test-integration (push) Waiting to run
Tests / Code Coverage / test-e2e (push) Waiting to run
Tests / Code Coverage / repo-analysis (push) Blocked by required conditions
Tests / Code Coverage / test-sim-nondeterminism (push) Waiting to run
Tests / Code Coverage / test-clientv2 (push) Waiting to run
Tests / Code Coverage / test-core (push) Waiting to run
Tests / Code Coverage / test-depinject (push) Waiting to run
Tests / Code Coverage / test-errors (push) Waiting to run
Tests / Code Coverage / test-math (push) Waiting to run
Tests / Code Coverage / test-schema (push) Waiting to run
Tests / Code Coverage / test-collections (push) Waiting to run
Tests / Code Coverage / test-cosmovisor (push) Waiting to run
Tests / Code Coverage / test-confix (push) Waiting to run
Tests / Code Coverage / test-store (push) Waiting to run
Tests / Code Coverage / test-log (push) Waiting to run
Tests / Code Coverage / test-x-tx (push) Waiting to run
Tests / Code Coverage / test-x-nft (push) Waiting to run
Tests / Code Coverage / test-x-circuit (push) Waiting to run
Tests / Code Coverage / test-x-feegrant (push) Waiting to run
Tests / Code Coverage / test-x-evidence (push) Waiting to run
Tests / Code Coverage / test-x-upgrade (push) Waiting to run
Tests / Code Coverage / test-tools-benchmark (push) Waiting to run
Build & Push SDK Proto Builder / build (push) Has been cancelled
initial: sovereign Mukan Network fork
2026-05-11 03:18:24 +03:00

571 lines
14 KiB
Go

package orm
import (
stdErrors "errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/store/prefix"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/x/group/errors"
)
func TestNewIndexer(t *testing.T) {
testCases := []struct {
name string
indexerFunc IndexerFunc
expectErr bool
expectedErr string
}{
{
name: "nil indexer func",
indexerFunc: nil,
expectErr: true,
expectedErr: "Indexer func must not be nil",
},
{
name: "all not nil",
indexerFunc: func(any) ([]any, error) { return nil, nil },
expectErr: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
indexer, err := NewIndexer(tc.indexerFunc)
if tc.expectErr {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr)
} else {
require.NoError(t, err)
require.NotNil(t, indexer)
}
})
}
}
func TestNewUniqueIndexer(t *testing.T) {
testCases := []struct {
name string
indexerFunc UniqueIndexerFunc
expectErr bool
expectedErr string
}{
{
name: "nil indexer func",
indexerFunc: nil,
expectErr: true,
expectedErr: "Indexer func must not be nil",
},
{
name: "all not nil",
indexerFunc: func(any) (any, error) { return nil, nil },
expectErr: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
indexer, err := NewUniqueIndexer(tc.indexerFunc)
if tc.expectErr {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr)
} else {
require.NoError(t, err)
require.NotNil(t, indexer)
}
})
}
}
func TestIndexerOnCreate(t *testing.T) {
var myRowID RowID = EncodeSequence(1)
specs := map[string]struct {
srcFunc IndexerFunc
expIndexKeys []any
expRowIDs []RowID
expAddFuncCalled bool
expErr error
}{
"single key": {
srcFunc: func(value any) ([]any, error) {
return []any{uint64(1)}, nil
},
expAddFuncCalled: true,
expIndexKeys: []any{uint64(1)},
expRowIDs: []RowID{myRowID},
},
"multi key": {
srcFunc: func(value any) ([]any, error) {
return []any{uint64(1), uint64(128)}, nil
},
expAddFuncCalled: true,
expIndexKeys: []any{uint64(1), uint64(128)},
expRowIDs: []RowID{myRowID, myRowID},
},
"empty key in slice": {
srcFunc: func(value any) ([]any, error) {
return []any{[]byte{}}, nil
},
expAddFuncCalled: false,
},
"nil key in slice": {
srcFunc: func(value any) ([]any, error) {
return []any{nil}, nil
},
expErr: fmt.Errorf("type %T not allowed as key part", nil),
expAddFuncCalled: false,
},
"empty key": {
srcFunc: func(value any) ([]any, error) {
return []any{}, nil
},
expAddFuncCalled: false,
},
"nil key": {
srcFunc: func(value any) ([]any, error) {
return nil, nil
},
expAddFuncCalled: false,
},
"error case": {
srcFunc: func(value any) ([]any, error) {
return nil, stdErrors.New("test")
},
expErr: stdErrors.New("test"),
expAddFuncCalled: false,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
mockPolicy := &addFuncRecorder{}
idx, err := NewIndexer(spec.srcFunc)
require.NoError(t, err)
idx.addFunc = mockPolicy.add
err = idx.OnCreate(nil, myRowID, nil)
if spec.expErr != nil {
require.Equal(t, spec.expErr, err)
return
}
require.NoError(t, err)
assert.Equal(t, spec.expIndexKeys, mockPolicy.secondaryIndexKeys)
assert.Equal(t, spec.expRowIDs, mockPolicy.rowIDs)
assert.Equal(t, spec.expAddFuncCalled, mockPolicy.called)
})
}
}
func TestIndexerOnDelete(t *testing.T) {
myRowID := EncodeSequence(1)
var multiKeyIndex MultiKeyIndex
ctx := NewMockContext()
storeKey := storetypes.NewKVStoreKey("test")
store := prefix.NewStore(ctx.KVStore(storeKey), []byte{multiKeyIndex.prefix})
specs := map[string]struct {
srcFunc IndexerFunc
expDeletedKeys []RowID
expErr error
}{
"single key": {
srcFunc: func(value any) ([]any, error) {
return []any{uint64(1)}, nil
},
expDeletedKeys: []RowID{append(EncodeSequence(1), myRowID...)},
},
"multi key": {
srcFunc: func(value any) ([]any, error) {
return []any{uint64(1), uint64(128)}, nil
},
expDeletedKeys: []RowID{
append(EncodeSequence(1), myRowID...),
append(EncodeSequence(128), myRowID...),
},
},
"empty key": {
srcFunc: func(value any) ([]any, error) {
return []any{}, nil
},
},
"nil key": {
srcFunc: func(value any) ([]any, error) {
return nil, nil
},
},
"empty key in slice": {
srcFunc: func(value any) ([]any, error) {
return []any{[]byte{}}, nil
},
},
"nil key in slice": {
srcFunc: func(value any) ([]any, error) {
return []any{nil}, nil
},
expErr: fmt.Errorf("type %T not allowed as key part", nil),
},
"error case": {
srcFunc: func(value any) ([]any, error) {
return nil, stdErrors.New("test")
},
expErr: stdErrors.New("test"),
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
idx, err := NewIndexer(spec.srcFunc)
require.NoError(t, err)
if spec.expErr == nil {
err = idx.OnCreate(store, myRowID, nil)
require.NoError(t, err)
for _, key := range spec.expDeletedKeys {
require.Equal(t, true, store.Has(key))
}
}
err = idx.OnDelete(store, myRowID, nil)
if spec.expErr != nil {
require.Equal(t, spec.expErr, err)
return
}
require.NoError(t, err)
for _, key := range spec.expDeletedKeys {
require.Equal(t, false, store.Has(key))
}
})
}
}
func TestIndexerOnUpdate(t *testing.T) {
myRowID := EncodeSequence(1)
var multiKeyIndex MultiKeyIndex
ctx := NewMockContext()
storeKey := storetypes.NewKVStoreKey("test")
store := prefix.NewStore(ctx.KVStore(storeKey), []byte{multiKeyIndex.prefix})
specs := map[string]struct {
srcFunc IndexerFunc
expAddedKeys []RowID
expDeletedKeys []RowID
expErr error
addFunc func(storetypes.KVStore, any, RowID) error
}{
"single key - same key, no update": {
srcFunc: func(value any) ([]any, error) {
return []any{uint64(1)}, nil
},
},
"single key - different key, replaced": {
srcFunc: func(value any) ([]any, error) {
keys := []uint64{1, 2}
return []any{keys[value.(int)]}, nil
},
expAddedKeys: []RowID{
append(EncodeSequence(2), myRowID...),
},
expDeletedKeys: []RowID{
append(EncodeSequence(1), myRowID...),
},
},
"multi key - same key, no update": {
srcFunc: func(value any) ([]any, error) {
return []any{uint64(1), uint64(2)}, nil
},
},
"multi key - replaced": {
srcFunc: func(value any) ([]any, error) {
keys := []uint64{1, 2, 3, 4}
return []any{keys[value.(int)], keys[value.(int)+2]}, nil
},
expAddedKeys: []RowID{
append(EncodeSequence(2), myRowID...),
append(EncodeSequence(4), myRowID...),
},
expDeletedKeys: []RowID{
append(EncodeSequence(1), myRowID...),
append(EncodeSequence(3), myRowID...),
},
},
"empty key": {
srcFunc: func(value any) ([]any, error) {
return []any{}, nil
},
},
"nil key": {
srcFunc: func(value any) ([]any, error) {
return nil, nil
},
},
"empty key in slice": {
srcFunc: func(value any) ([]any, error) {
return []any{[]byte{}}, nil
},
},
"nil key in slice": {
srcFunc: func(value any) ([]any, error) {
return []any{nil}, nil
},
expErr: fmt.Errorf("type %T not allowed as key part", nil),
},
"error case with new value": {
srcFunc: func(value any) ([]any, error) {
return nil, stdErrors.New("test")
},
expErr: stdErrors.New("test"),
},
"error case with old value": {
srcFunc: func(value any) ([]any, error) {
var err error
if value.(int)%2 == 1 {
err = stdErrors.New("test")
}
return []any{uint64(1)}, err
},
expErr: stdErrors.New("test"),
},
"error case on persisting new keys": {
srcFunc: func(value any) ([]any, error) {
keys := []uint64{1, 2}
return []any{keys[value.(int)]}, nil
},
addFunc: func(_ storetypes.KVStore, _ any, _ RowID) error {
return stdErrors.New("test")
},
expErr: stdErrors.New("test"),
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
idx, err := NewIndexer(spec.srcFunc)
require.NoError(t, err)
if spec.expErr == nil {
err = idx.OnCreate(store, myRowID, 0)
require.NoError(t, err)
}
if spec.addFunc != nil {
idx.addFunc = spec.addFunc
}
err = idx.OnUpdate(store, myRowID, 1, 0)
if spec.expErr != nil {
require.Equal(t, spec.expErr, err)
return
}
require.NoError(t, err)
for _, key := range spec.expAddedKeys {
require.Equal(t, true, store.Has(key))
}
for _, key := range spec.expDeletedKeys {
require.Equal(t, false, store.Has(key))
}
})
}
}
func TestUniqueKeyAddFunc(t *testing.T) {
myRowID := EncodeSequence(1)
presetKeyPart := []byte("my-preset-key")
presetKey := append(AddLengthPrefix(presetKeyPart), myRowID...)
specs := map[string]struct {
srcKey []byte
expErr *errorsmod.Error
expExistingEntry []byte
}{
"create when not exists": {
srcKey: []byte("my-index-key"),
expExistingEntry: append(AddLengthPrefix([]byte("my-index-key")), myRowID...),
},
"error when exists already": {
srcKey: presetKeyPart,
expErr: errors.ErrORMUniqueConstraint,
},
"nil key not allowed": {
srcKey: nil,
expErr: errors.ErrORMInvalidArgument,
},
"empty key not allowed": {
srcKey: []byte{},
expErr: errors.ErrORMInvalidArgument,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
storeKey := storetypes.NewKVStoreKey("test")
store := NewMockContext().KVStore(storeKey)
store.Set(presetKey, []byte{})
err := uniqueKeysAddFunc(store, spec.srcKey, myRowID)
require.True(t, spec.expErr.Is(err))
if spec.expErr != nil {
return
}
assert.True(t, store.Has(spec.expExistingEntry), "not found")
})
}
}
func TestMultiKeyAddFunc(t *testing.T) {
myRowID := EncodeSequence(1)
presetKeyPart := []byte("my-preset-key")
presetKey := append(AddLengthPrefix(presetKeyPart), myRowID...)
specs := map[string]struct {
srcKey []byte
expErr *errorsmod.Error
expExistingEntry []byte
}{
"create when not exists": {
srcKey: []byte("my-index-key"),
expExistingEntry: append(AddLengthPrefix([]byte("my-index-key")), myRowID...),
},
"noop when exists already": {
srcKey: presetKeyPart,
expExistingEntry: presetKey,
},
"nil key not allowed": {
srcKey: nil,
expErr: errors.ErrORMInvalidArgument,
},
"empty key not allowed": {
srcKey: []byte{},
expErr: errors.ErrORMInvalidArgument,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
storeKey := storetypes.NewKVStoreKey("test")
store := NewMockContext().KVStore(storeKey)
store.Set(presetKey, []byte{})
err := multiKeyAddFunc(store, spec.srcKey, myRowID)
require.True(t, spec.expErr.Is(err))
if spec.expErr != nil {
return
}
assert.True(t, store.Has(spec.expExistingEntry))
})
}
}
func TestDifference(t *testing.T) {
specs := map[string]struct {
srcA []any
srcB []any
expResult []any
expErr bool
}{
"all of A": {
srcA: []any{"a", "b"},
srcB: []any{"c"},
expResult: []any{"a", "b"},
},
"A - B": {
srcA: []any{"a", "b"},
srcB: []any{"b", "c", "d"},
expResult: []any{"a"},
},
"type in A not allowed": {
srcA: []any{1},
srcB: []any{"b", "c", "d"},
expErr: true,
},
"type in B not allowed": {
srcA: []any{"b", "c", "d"},
srcB: []any{1},
expErr: true,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
got, err := difference(spec.srcA, spec.srcB)
if spec.expErr {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, spec.expResult, got)
}
})
}
}
func TestPruneEmptyKeys(t *testing.T) {
specs := map[string]struct {
srcFunc IndexerFunc
expResult []any
expError error
}{
"non empty": {
srcFunc: func(v any) ([]any, error) {
return []any{uint64(0), uint64(1)}, nil
},
expResult: []any{uint64(0), uint64(1)},
},
"empty": {
srcFunc: func(v any) ([]any, error) {
return []any{}, nil
},
expResult: []any{},
},
"nil": {
srcFunc: func(v any) ([]any, error) {
return nil, nil
},
},
"empty in the beginning": {
srcFunc: func(v any) ([]any, error) {
return []any{[]byte{}, uint64(0), uint64(1)}, nil
},
expResult: []any{uint64(0), uint64(1)},
},
"empty in the middle": {
srcFunc: func(v any) ([]any, error) {
return []any{uint64(0), []byte{}, uint64(1)}, nil
},
expResult: []any{uint64(0), uint64(1)},
},
"empty at the end": {
srcFunc: func(v any) ([]any, error) {
return []any{uint64(0), uint64(1), []byte{}}, nil
},
expResult: []any{uint64(0), uint64(1)},
},
"error passed": {
srcFunc: func(v any) ([]any, error) {
return nil, stdErrors.New("test")
},
expError: stdErrors.New("test"),
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
r, err := pruneEmptyKeys(spec.srcFunc)(nil)
require.Equal(t, spec.expError, err)
if spec.expError != nil {
return
}
assert.Equal(t, spec.expResult, r)
})
}
}
type addFuncRecorder struct {
secondaryIndexKeys []any
rowIDs []RowID
called bool
}
func (c *addFuncRecorder) add(_ storetypes.KVStore, key any, rowID RowID) error {
c.secondaryIndexKeys = append(c.secondaryIndexKeys, key)
c.rowIDs = append(c.rowIDs, rowID)
c.called = true
return nil
}