github.com/cosmos/cosmos-sdk@v0.50.10/x/group/internal/orm/indexer_test.go (about)

     1  package orm
     2  
     3  import (
     4  	stdErrors "errors"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  
    11  	errorsmod "cosmossdk.io/errors"
    12  	"cosmossdk.io/store/prefix"
    13  	storetypes "cosmossdk.io/store/types"
    14  
    15  	"github.com/cosmos/cosmos-sdk/x/group/errors"
    16  )
    17  
    18  func TestNewIndexer(t *testing.T) {
    19  	testCases := []struct {
    20  		name        string
    21  		indexerFunc IndexerFunc
    22  		expectErr   bool
    23  		expectedErr string
    24  	}{
    25  		{
    26  			name:        "nil indexer func",
    27  			indexerFunc: nil,
    28  			expectErr:   true,
    29  			expectedErr: "Indexer func must not be nil",
    30  		},
    31  		{
    32  			name:        "all not nil",
    33  			indexerFunc: func(interface{}) ([]interface{}, error) { return nil, nil },
    34  			expectErr:   false,
    35  		},
    36  	}
    37  	for _, tc := range testCases {
    38  		t.Run(tc.name, func(t *testing.T) {
    39  			indexer, err := NewIndexer(tc.indexerFunc)
    40  			if tc.expectErr {
    41  				require.Error(t, err)
    42  				require.Contains(t, err.Error(), tc.expectedErr)
    43  			} else {
    44  				require.NoError(t, err)
    45  				require.NotNil(t, indexer)
    46  			}
    47  		})
    48  	}
    49  }
    50  
    51  func TestNewUniqueIndexer(t *testing.T) {
    52  	testCases := []struct {
    53  		name        string
    54  		indexerFunc UniqueIndexerFunc
    55  		expectErr   bool
    56  		expectedErr string
    57  	}{
    58  		{
    59  			name:        "nil indexer func",
    60  			indexerFunc: nil,
    61  			expectErr:   true,
    62  			expectedErr: "Indexer func must not be nil",
    63  		},
    64  		{
    65  			name:        "all not nil",
    66  			indexerFunc: func(interface{}) (interface{}, error) { return nil, nil },
    67  			expectErr:   false,
    68  		},
    69  	}
    70  	for _, tc := range testCases {
    71  		t.Run(tc.name, func(t *testing.T) {
    72  			indexer, err := NewUniqueIndexer(tc.indexerFunc)
    73  			if tc.expectErr {
    74  				require.Error(t, err)
    75  				require.Contains(t, err.Error(), tc.expectedErr)
    76  			} else {
    77  				require.NoError(t, err)
    78  				require.NotNil(t, indexer)
    79  			}
    80  		})
    81  	}
    82  }
    83  
    84  func TestIndexerOnCreate(t *testing.T) {
    85  	var myRowID RowID = EncodeSequence(1)
    86  
    87  	specs := map[string]struct {
    88  		srcFunc          IndexerFunc
    89  		expIndexKeys     []interface{}
    90  		expRowIDs        []RowID
    91  		expAddFuncCalled bool
    92  		expErr           error
    93  	}{
    94  		"single key": {
    95  			srcFunc: func(value interface{}) ([]interface{}, error) {
    96  				return []interface{}{uint64(1)}, nil
    97  			},
    98  			expAddFuncCalled: true,
    99  			expIndexKeys:     []interface{}{uint64(1)},
   100  			expRowIDs:        []RowID{myRowID},
   101  		},
   102  		"multi key": {
   103  			srcFunc: func(value interface{}) ([]interface{}, error) {
   104  				return []interface{}{uint64(1), uint64(128)}, nil
   105  			},
   106  			expAddFuncCalled: true,
   107  			expIndexKeys:     []interface{}{uint64(1), uint64(128)},
   108  			expRowIDs:        []RowID{myRowID, myRowID},
   109  		},
   110  		"empty key in slice": {
   111  			srcFunc: func(value interface{}) ([]interface{}, error) {
   112  				return []interface{}{[]byte{}}, nil
   113  			},
   114  			expAddFuncCalled: false,
   115  		},
   116  		"nil key in slice": {
   117  			srcFunc: func(value interface{}) ([]interface{}, error) {
   118  				return []interface{}{nil}, nil
   119  			},
   120  			expErr:           fmt.Errorf("type %T not allowed as key part", nil),
   121  			expAddFuncCalled: false,
   122  		},
   123  		"empty key": {
   124  			srcFunc: func(value interface{}) ([]interface{}, error) {
   125  				return []interface{}{}, nil
   126  			},
   127  			expAddFuncCalled: false,
   128  		},
   129  		"nil key": {
   130  			srcFunc: func(value interface{}) ([]interface{}, error) {
   131  				return nil, nil
   132  			},
   133  			expAddFuncCalled: false,
   134  		},
   135  		"error case": {
   136  			srcFunc: func(value interface{}) ([]interface{}, error) {
   137  				return nil, stdErrors.New("test")
   138  			},
   139  			expErr:           stdErrors.New("test"),
   140  			expAddFuncCalled: false,
   141  		},
   142  	}
   143  	for msg, spec := range specs {
   144  		t.Run(msg, func(t *testing.T) {
   145  			mockPolicy := &addFuncRecorder{}
   146  			idx, err := NewIndexer(spec.srcFunc)
   147  			require.NoError(t, err)
   148  			idx.addFunc = mockPolicy.add
   149  
   150  			err = idx.OnCreate(nil, myRowID, nil)
   151  			if spec.expErr != nil {
   152  				require.Equal(t, spec.expErr, err)
   153  				return
   154  			}
   155  			require.NoError(t, err)
   156  			assert.Equal(t, spec.expIndexKeys, mockPolicy.secondaryIndexKeys)
   157  			assert.Equal(t, spec.expRowIDs, mockPolicy.rowIDs)
   158  			assert.Equal(t, spec.expAddFuncCalled, mockPolicy.called)
   159  		})
   160  	}
   161  }
   162  
   163  func TestIndexerOnDelete(t *testing.T) {
   164  	myRowID := EncodeSequence(1)
   165  
   166  	var multiKeyIndex MultiKeyIndex
   167  	ctx := NewMockContext()
   168  	storeKey := storetypes.NewKVStoreKey("test")
   169  	store := prefix.NewStore(ctx.KVStore(storeKey), []byte{multiKeyIndex.prefix})
   170  
   171  	specs := map[string]struct {
   172  		srcFunc        IndexerFunc
   173  		expDeletedKeys []RowID
   174  		expErr         error
   175  	}{
   176  		"single key": {
   177  			srcFunc: func(value interface{}) ([]interface{}, error) {
   178  				return []interface{}{uint64(1)}, nil
   179  			},
   180  			expDeletedKeys: []RowID{append(EncodeSequence(1), myRowID...)},
   181  		},
   182  		"multi key": {
   183  			srcFunc: func(value interface{}) ([]interface{}, error) {
   184  				return []interface{}{uint64(1), uint64(128)}, nil
   185  			},
   186  			expDeletedKeys: []RowID{
   187  				append(EncodeSequence(1), myRowID...),
   188  				append(EncodeSequence(128), myRowID...),
   189  			},
   190  		},
   191  		"empty key": {
   192  			srcFunc: func(value interface{}) ([]interface{}, error) {
   193  				return []interface{}{}, nil
   194  			},
   195  		},
   196  		"nil key": {
   197  			srcFunc: func(value interface{}) ([]interface{}, error) {
   198  				return nil, nil
   199  			},
   200  		},
   201  		"empty key in slice": {
   202  			srcFunc: func(value interface{}) ([]interface{}, error) {
   203  				return []interface{}{[]byte{}}, nil
   204  			},
   205  		},
   206  		"nil key in slice": {
   207  			srcFunc: func(value interface{}) ([]interface{}, error) {
   208  				return []interface{}{nil}, nil
   209  			},
   210  			expErr: fmt.Errorf("type %T not allowed as key part", nil),
   211  		},
   212  		"error case": {
   213  			srcFunc: func(value interface{}) ([]interface{}, error) {
   214  				return nil, stdErrors.New("test")
   215  			},
   216  			expErr: stdErrors.New("test"),
   217  		},
   218  	}
   219  	for msg, spec := range specs {
   220  		t.Run(msg, func(t *testing.T) {
   221  			idx, err := NewIndexer(spec.srcFunc)
   222  			require.NoError(t, err)
   223  
   224  			if spec.expErr == nil {
   225  				err = idx.OnCreate(store, myRowID, nil)
   226  				require.NoError(t, err)
   227  				for _, key := range spec.expDeletedKeys {
   228  					require.Equal(t, true, store.Has(key))
   229  				}
   230  			}
   231  
   232  			err = idx.OnDelete(store, myRowID, nil)
   233  			if spec.expErr != nil {
   234  				require.Equal(t, spec.expErr, err)
   235  				return
   236  			}
   237  			require.NoError(t, err)
   238  			for _, key := range spec.expDeletedKeys {
   239  				require.Equal(t, false, store.Has(key))
   240  			}
   241  		})
   242  	}
   243  }
   244  
   245  func TestIndexerOnUpdate(t *testing.T) {
   246  	myRowID := EncodeSequence(1)
   247  
   248  	var multiKeyIndex MultiKeyIndex
   249  	ctx := NewMockContext()
   250  	storeKey := storetypes.NewKVStoreKey("test")
   251  	store := prefix.NewStore(ctx.KVStore(storeKey), []byte{multiKeyIndex.prefix})
   252  
   253  	specs := map[string]struct {
   254  		srcFunc        IndexerFunc
   255  		expAddedKeys   []RowID
   256  		expDeletedKeys []RowID
   257  		expErr         error
   258  		addFunc        func(storetypes.KVStore, interface{}, RowID) error
   259  	}{
   260  		"single key - same key, no update": {
   261  			srcFunc: func(value interface{}) ([]interface{}, error) {
   262  				return []interface{}{uint64(1)}, nil
   263  			},
   264  		},
   265  		"single key - different key, replaced": {
   266  			srcFunc: func(value interface{}) ([]interface{}, error) {
   267  				keys := []uint64{1, 2}
   268  				return []interface{}{keys[value.(int)]}, nil
   269  			},
   270  			expAddedKeys: []RowID{
   271  				append(EncodeSequence(2), myRowID...),
   272  			},
   273  			expDeletedKeys: []RowID{
   274  				append(EncodeSequence(1), myRowID...),
   275  			},
   276  		},
   277  		"multi key - same key, no update": {
   278  			srcFunc: func(value interface{}) ([]interface{}, error) {
   279  				return []interface{}{uint64(1), uint64(2)}, nil
   280  			},
   281  		},
   282  		"multi key - replaced": {
   283  			srcFunc: func(value interface{}) ([]interface{}, error) {
   284  				keys := []uint64{1, 2, 3, 4}
   285  				return []interface{}{keys[value.(int)], keys[value.(int)+2]}, nil
   286  			},
   287  			expAddedKeys: []RowID{
   288  				append(EncodeSequence(2), myRowID...),
   289  				append(EncodeSequence(4), myRowID...),
   290  			},
   291  			expDeletedKeys: []RowID{
   292  				append(EncodeSequence(1), myRowID...),
   293  				append(EncodeSequence(3), myRowID...),
   294  			},
   295  		},
   296  		"empty key": {
   297  			srcFunc: func(value interface{}) ([]interface{}, error) {
   298  				return []interface{}{}, nil
   299  			},
   300  		},
   301  		"nil key": {
   302  			srcFunc: func(value interface{}) ([]interface{}, error) {
   303  				return nil, nil
   304  			},
   305  		},
   306  		"empty key in slice": {
   307  			srcFunc: func(value interface{}) ([]interface{}, error) {
   308  				return []interface{}{[]byte{}}, nil
   309  			},
   310  		},
   311  		"nil key in slice": {
   312  			srcFunc: func(value interface{}) ([]interface{}, error) {
   313  				return []interface{}{nil}, nil
   314  			},
   315  			expErr: fmt.Errorf("type %T not allowed as key part", nil),
   316  		},
   317  		"error case with new value": {
   318  			srcFunc: func(value interface{}) ([]interface{}, error) {
   319  				return nil, stdErrors.New("test")
   320  			},
   321  			expErr: stdErrors.New("test"),
   322  		},
   323  		"error case with old value": {
   324  			srcFunc: func(value interface{}) ([]interface{}, error) {
   325  				var err error
   326  				if value.(int)%2 == 1 {
   327  					err = stdErrors.New("test")
   328  				}
   329  				return []interface{}{uint64(1)}, err
   330  			},
   331  			expErr: stdErrors.New("test"),
   332  		},
   333  		"error case on persisting new keys": {
   334  			srcFunc: func(value interface{}) ([]interface{}, error) {
   335  				keys := []uint64{1, 2}
   336  				return []interface{}{keys[value.(int)]}, nil
   337  			},
   338  			addFunc: func(_ storetypes.KVStore, _ interface{}, _ RowID) error {
   339  				return stdErrors.New("test")
   340  			},
   341  			expErr: stdErrors.New("test"),
   342  		},
   343  	}
   344  	for msg, spec := range specs {
   345  		t.Run(msg, func(t *testing.T) {
   346  			idx, err := NewIndexer(spec.srcFunc)
   347  			require.NoError(t, err)
   348  
   349  			if spec.expErr == nil {
   350  				err = idx.OnCreate(store, myRowID, 0)
   351  				require.NoError(t, err)
   352  			}
   353  
   354  			if spec.addFunc != nil {
   355  				idx.addFunc = spec.addFunc
   356  			}
   357  			err = idx.OnUpdate(store, myRowID, 1, 0)
   358  			if spec.expErr != nil {
   359  				require.Equal(t, spec.expErr, err)
   360  				return
   361  			}
   362  			require.NoError(t, err)
   363  			for _, key := range spec.expAddedKeys {
   364  				require.Equal(t, true, store.Has(key))
   365  			}
   366  			for _, key := range spec.expDeletedKeys {
   367  				require.Equal(t, false, store.Has(key))
   368  			}
   369  		})
   370  	}
   371  }
   372  
   373  func TestUniqueKeyAddFunc(t *testing.T) {
   374  	myRowID := EncodeSequence(1)
   375  	presetKeyPart := []byte("my-preset-key")
   376  	presetKey := append(AddLengthPrefix(presetKeyPart), myRowID...)
   377  
   378  	specs := map[string]struct {
   379  		srcKey           []byte
   380  		expErr           *errorsmod.Error
   381  		expExistingEntry []byte
   382  	}{
   383  		"create when not exists": {
   384  			srcKey:           []byte("my-index-key"),
   385  			expExistingEntry: append(AddLengthPrefix([]byte("my-index-key")), myRowID...),
   386  		},
   387  		"error when exists already": {
   388  			srcKey: presetKeyPart,
   389  			expErr: errors.ErrORMUniqueConstraint,
   390  		},
   391  		"nil key not allowed": {
   392  			srcKey: nil,
   393  			expErr: errors.ErrORMInvalidArgument,
   394  		},
   395  		"empty key not allowed": {
   396  			srcKey: []byte{},
   397  			expErr: errors.ErrORMInvalidArgument,
   398  		},
   399  	}
   400  	for msg, spec := range specs {
   401  		t.Run(msg, func(t *testing.T) {
   402  			storeKey := storetypes.NewKVStoreKey("test")
   403  			store := NewMockContext().KVStore(storeKey)
   404  			store.Set(presetKey, []byte{})
   405  
   406  			err := uniqueKeysAddFunc(store, spec.srcKey, myRowID)
   407  			require.True(t, spec.expErr.Is(err))
   408  			if spec.expErr != nil {
   409  				return
   410  			}
   411  			assert.True(t, store.Has(spec.expExistingEntry), "not found")
   412  		})
   413  	}
   414  }
   415  
   416  func TestMultiKeyAddFunc(t *testing.T) {
   417  	myRowID := EncodeSequence(1)
   418  	presetKeyPart := []byte("my-preset-key")
   419  	presetKey := append(AddLengthPrefix(presetKeyPart), myRowID...)
   420  
   421  	specs := map[string]struct {
   422  		srcKey           []byte
   423  		expErr           *errorsmod.Error
   424  		expExistingEntry []byte
   425  	}{
   426  		"create when not exists": {
   427  			srcKey:           []byte("my-index-key"),
   428  			expExistingEntry: append(AddLengthPrefix([]byte("my-index-key")), myRowID...),
   429  		},
   430  		"noop when exists already": {
   431  			srcKey:           presetKeyPart,
   432  			expExistingEntry: presetKey,
   433  		},
   434  		"nil key not allowed": {
   435  			srcKey: nil,
   436  			expErr: errors.ErrORMInvalidArgument,
   437  		},
   438  		"empty key not allowed": {
   439  			srcKey: []byte{},
   440  			expErr: errors.ErrORMInvalidArgument,
   441  		},
   442  	}
   443  	for msg, spec := range specs {
   444  		t.Run(msg, func(t *testing.T) {
   445  			storeKey := storetypes.NewKVStoreKey("test")
   446  			store := NewMockContext().KVStore(storeKey)
   447  			store.Set(presetKey, []byte{})
   448  
   449  			err := multiKeyAddFunc(store, spec.srcKey, myRowID)
   450  			require.True(t, spec.expErr.Is(err))
   451  			if spec.expErr != nil {
   452  				return
   453  			}
   454  			assert.True(t, store.Has(spec.expExistingEntry))
   455  		})
   456  	}
   457  }
   458  
   459  func TestDifference(t *testing.T) {
   460  	specs := map[string]struct {
   461  		srcA      []interface{}
   462  		srcB      []interface{}
   463  		expResult []interface{}
   464  		expErr    bool
   465  	}{
   466  		"all of A": {
   467  			srcA:      []interface{}{"a", "b"},
   468  			srcB:      []interface{}{"c"},
   469  			expResult: []interface{}{"a", "b"},
   470  		},
   471  		"A - B": {
   472  			srcA:      []interface{}{"a", "b"},
   473  			srcB:      []interface{}{"b", "c", "d"},
   474  			expResult: []interface{}{"a"},
   475  		},
   476  		"type in A not allowed": {
   477  			srcA:   []interface{}{1},
   478  			srcB:   []interface{}{"b", "c", "d"},
   479  			expErr: true,
   480  		},
   481  		"type in B not allowed": {
   482  			srcA:   []interface{}{"b", "c", "d"},
   483  			srcB:   []interface{}{1},
   484  			expErr: true,
   485  		},
   486  	}
   487  	for msg, spec := range specs {
   488  		t.Run(msg, func(t *testing.T) {
   489  			got, err := difference(spec.srcA, spec.srcB)
   490  			if spec.expErr {
   491  				require.Error(t, err)
   492  			} else {
   493  				require.NoError(t, err)
   494  				assert.Equal(t, spec.expResult, got)
   495  			}
   496  		})
   497  	}
   498  }
   499  
   500  func TestPruneEmptyKeys(t *testing.T) {
   501  	specs := map[string]struct {
   502  		srcFunc   IndexerFunc
   503  		expResult []interface{}
   504  		expError  error
   505  	}{
   506  		"non empty": {
   507  			srcFunc: func(v interface{}) ([]interface{}, error) {
   508  				return []interface{}{uint64(0), uint64(1)}, nil
   509  			},
   510  			expResult: []interface{}{uint64(0), uint64(1)},
   511  		},
   512  		"empty": {
   513  			srcFunc: func(v interface{}) ([]interface{}, error) {
   514  				return []interface{}{}, nil
   515  			},
   516  			expResult: []interface{}{},
   517  		},
   518  		"nil": {
   519  			srcFunc: func(v interface{}) ([]interface{}, error) {
   520  				return nil, nil
   521  			},
   522  		},
   523  		"empty in the beginning": {
   524  			srcFunc: func(v interface{}) ([]interface{}, error) {
   525  				return []interface{}{[]byte{}, uint64(0), uint64(1)}, nil
   526  			},
   527  			expResult: []interface{}{uint64(0), uint64(1)},
   528  		},
   529  		"empty in the middle": {
   530  			srcFunc: func(v interface{}) ([]interface{}, error) {
   531  				return []interface{}{uint64(0), []byte{}, uint64(1)}, nil
   532  			},
   533  			expResult: []interface{}{uint64(0), uint64(1)},
   534  		},
   535  		"empty at the end": {
   536  			srcFunc: func(v interface{}) ([]interface{}, error) {
   537  				return []interface{}{uint64(0), uint64(1), []byte{}}, nil
   538  			},
   539  			expResult: []interface{}{uint64(0), uint64(1)},
   540  		},
   541  		"error passed": {
   542  			srcFunc: func(v interface{}) ([]interface{}, error) {
   543  				return nil, stdErrors.New("test")
   544  			},
   545  			expError: stdErrors.New("test"),
   546  		},
   547  	}
   548  	for msg, spec := range specs {
   549  		t.Run(msg, func(t *testing.T) {
   550  			r, err := pruneEmptyKeys(spec.srcFunc)(nil)
   551  			require.Equal(t, spec.expError, err)
   552  			if spec.expError != nil {
   553  				return
   554  			}
   555  			assert.Equal(t, spec.expResult, r)
   556  		})
   557  	}
   558  }
   559  
   560  type addFuncRecorder struct {
   561  	secondaryIndexKeys []interface{}
   562  	rowIDs             []RowID
   563  	called             bool
   564  }
   565  
   566  func (c *addFuncRecorder) add(_ storetypes.KVStore, key interface{}, rowID RowID) error {
   567  	c.secondaryIndexKeys = append(c.secondaryIndexKeys, key)
   568  	c.rowIDs = append(c.rowIDs, rowID)
   569  	c.called = true
   570  	return nil
   571  }