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 }