github.com/cosmos/cosmos-sdk@v0.50.10/x/group/internal/orm/iterator_test.go (about) 1 package orm 2 3 import ( 4 "testing" 5 6 "github.com/cosmos/gogoproto/proto" 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9 10 errorsmod "cosmossdk.io/errors" 11 storetypes "cosmossdk.io/store/types" 12 13 "github.com/cosmos/cosmos-sdk/codec" 14 "github.com/cosmos/cosmos-sdk/codec/types" 15 "github.com/cosmos/cosmos-sdk/testutil/testdata" 16 sdk "github.com/cosmos/cosmos-sdk/types" 17 "github.com/cosmos/cosmos-sdk/types/query" 18 "github.com/cosmos/cosmos-sdk/x/group/errors" 19 ) 20 21 func TestReadAll(t *testing.T) { 22 specs := map[string]struct { 23 srcIT Iterator 24 destSlice func() ModelSlicePtr 25 expErr *errorsmod.Error 26 expIDs []RowID 27 expResult ModelSlicePtr 28 }{ 29 "all good with object slice": { 30 srcIT: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}), 31 destSlice: func() ModelSlicePtr { 32 x := make([]testdata.TableModel, 1) 33 return &x 34 }, 35 expIDs: []RowID{EncodeSequence(1)}, 36 expResult: &[]testdata.TableModel{{Name: "test"}}, 37 }, 38 "all good with pointer slice": { 39 srcIT: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}), 40 destSlice: func() ModelSlicePtr { 41 x := make([]*testdata.TableModel, 1) 42 return &x 43 }, 44 expIDs: []RowID{EncodeSequence(1)}, 45 expResult: &[]*testdata.TableModel{{Name: "test"}}, 46 }, 47 "dest slice empty": { 48 srcIT: mockIter(EncodeSequence(1), &testdata.TableModel{}), 49 destSlice: func() ModelSlicePtr { 50 x := make([]testdata.TableModel, 0) 51 return &x 52 }, 53 expIDs: []RowID{EncodeSequence(1)}, 54 expResult: &[]testdata.TableModel{{}}, 55 }, 56 "dest pointer with nil value": { 57 srcIT: mockIter(EncodeSequence(1), &testdata.TableModel{}), 58 destSlice: func() ModelSlicePtr { 59 return (*[]testdata.TableModel)(nil) 60 }, 61 expErr: errors.ErrORMInvalidArgument, 62 }, 63 "iterator is nil": { 64 srcIT: nil, 65 destSlice: func() ModelSlicePtr { return new([]testdata.TableModel) }, 66 expErr: errors.ErrORMInvalidArgument, 67 }, 68 "dest slice is nil": { 69 srcIT: noopIter(), 70 destSlice: func() ModelSlicePtr { return nil }, 71 expErr: errors.ErrORMInvalidArgument, 72 }, 73 "dest slice is not a pointer": { 74 srcIT: IteratorFunc(nil), 75 destSlice: func() ModelSlicePtr { return make([]testdata.TableModel, 1) }, 76 expErr: errors.ErrORMInvalidArgument, 77 }, 78 "error on loadNext is returned": { 79 srcIT: NewInvalidIterator(), 80 destSlice: func() ModelSlicePtr { 81 x := make([]testdata.TableModel, 1) 82 return &x 83 }, 84 expErr: errors.ErrORMInvalidIterator, 85 }, 86 } 87 for msg, spec := range specs { 88 t.Run(msg, func(t *testing.T) { 89 loaded := spec.destSlice() 90 ids, err := ReadAll(spec.srcIT, loaded) 91 require.True(t, spec.expErr.Is(err), "expected %s but got %s", spec.expErr, err) 92 assert.Equal(t, spec.expIDs, ids) 93 if err == nil { 94 assert.Equal(t, spec.expResult, loaded) 95 } 96 }) 97 } 98 } 99 100 func TestLimitedIterator(t *testing.T) { 101 specs := map[string]struct { 102 parent Iterator 103 max int 104 expectErr bool 105 expectedErr string 106 exp []testdata.TableModel 107 }{ 108 "nil parent": { 109 parent: nil, 110 max: 0, 111 expectErr: true, 112 expectedErr: "parent iterator must not be nil", 113 }, 114 "negative max": { 115 parent: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}), 116 max: -1, 117 expectErr: true, 118 expectedErr: "quantity must not be negative", 119 }, 120 "all from range with max > length": { 121 parent: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}), 122 max: 2, 123 exp: []testdata.TableModel{{Name: "test"}}, 124 }, 125 "up to max": { 126 parent: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}), 127 max: 1, 128 exp: []testdata.TableModel{{Name: "test"}}, 129 }, 130 "none when max = 0": { 131 parent: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}), 132 max: 0, 133 exp: []testdata.TableModel{}, 134 }, 135 } 136 for msg, spec := range specs { 137 t.Run(msg, func(t *testing.T) { 138 src, err := LimitIterator(spec.parent, spec.max) 139 if spec.expectErr { 140 require.Error(t, err) 141 require.Contains(t, err.Error(), spec.expectedErr) 142 } else { 143 require.NoError(t, err) 144 var loaded []testdata.TableModel 145 _, err := ReadAll(src, &loaded) 146 require.NoError(t, err) 147 assert.EqualValues(t, spec.exp, loaded) 148 } 149 }) 150 } 151 } 152 153 func TestFirst(t *testing.T) { 154 testCases := []struct { 155 name string 156 iterator Iterator 157 dest proto.Message 158 expectErr bool 159 expectedErr string 160 expectedRowID RowID 161 expectedDest proto.Message 162 }{ 163 { 164 name: "nil iterator", 165 iterator: nil, 166 dest: &testdata.TableModel{}, 167 expectErr: true, 168 expectedErr: "iterator must not be nil", 169 }, 170 { 171 name: "nil dest", 172 iterator: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}), 173 dest: nil, 174 expectErr: true, 175 expectedErr: "destination object must not be nil", 176 }, 177 { 178 name: "all not nil", 179 iterator: mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}), 180 dest: &testdata.TableModel{}, 181 expectErr: false, 182 expectedRowID: EncodeSequence(1), 183 expectedDest: &testdata.TableModel{Name: "test"}, 184 }, 185 } 186 for _, tc := range testCases { 187 t.Run(tc.name, func(t *testing.T) { 188 rowID, err := First(tc.iterator, tc.dest) 189 if tc.expectErr { 190 require.Error(t, err) 191 require.Contains(t, err.Error(), tc.expectedErr) 192 } else { 193 require.NoError(t, err) 194 require.Equal(t, tc.expectedRowID, rowID) 195 require.Equal(t, tc.expectedDest, tc.dest) 196 } 197 }) 198 } 199 } 200 201 func TestPaginate(t *testing.T) { 202 interfaceRegistry := types.NewInterfaceRegistry() 203 cdc := codec.NewProtoCodec(interfaceRegistry) 204 205 tb, err := NewAutoUInt64Table(AutoUInt64TablePrefix, AutoUInt64TableSeqPrefix, &testdata.TableModel{}, cdc) 206 require.NoError(t, err) 207 idx, err := NewIndex(tb, AutoUInt64TableModelByMetadataPrefix, func(val interface{}) ([]interface{}, error) { 208 return []interface{}{val.(*testdata.TableModel).Metadata}, nil 209 }, testdata.TableModel{}.Metadata) 210 require.NoError(t, err) 211 212 ctx := NewMockContext() 213 store := ctx.KVStore(storetypes.NewKVStoreKey("test")) 214 215 metadata := []byte("metadata") 216 t1 := testdata.TableModel{ 217 Id: 1, 218 Name: "my test 1", 219 Metadata: metadata, 220 } 221 t2 := testdata.TableModel{ 222 Id: 2, 223 Name: "my test 2", 224 Metadata: metadata, 225 } 226 t3 := testdata.TableModel{ 227 Id: 3, 228 Name: "my test 3", 229 Metadata: []byte("other-metadata"), 230 } 231 t4 := testdata.TableModel{ 232 Id: 4, 233 Name: "my test 4", 234 Metadata: metadata, 235 } 236 t5 := testdata.TableModel{ 237 Id: 5, 238 Name: "my test 5", 239 Metadata: []byte("other-metadata"), 240 } 241 242 for _, g := range []testdata.TableModel{t1, t2, t3, t4, t5} { 243 g := g 244 _, err := tb.Create(store, &g) 245 require.NoError(t, err) 246 } 247 248 specs := map[string]struct { 249 pageReq *query.PageRequest 250 expPageRes *query.PageResponse 251 exp []testdata.TableModel 252 key []byte 253 expErr bool 254 }{ 255 "one item": { 256 pageReq: &query.PageRequest{Key: nil, Limit: 1}, 257 exp: []testdata.TableModel{t1}, 258 expPageRes: &query.PageResponse{Total: 0, NextKey: EncodeSequence(2)}, 259 key: metadata, 260 }, 261 "with both key and offset": { 262 pageReq: &query.PageRequest{Key: EncodeSequence(2), Offset: 1}, 263 expErr: true, 264 key: metadata, 265 }, 266 "up to max": { 267 pageReq: &query.PageRequest{Key: nil, Limit: 3, CountTotal: true}, 268 exp: []testdata.TableModel{t1, t2, t4}, 269 expPageRes: &query.PageResponse{Total: 3, NextKey: nil}, 270 key: metadata, 271 }, 272 "no results": { 273 pageReq: &query.PageRequest{Key: nil, Limit: 2, CountTotal: true}, 274 exp: []testdata.TableModel{}, 275 expPageRes: &query.PageResponse{Total: 0, NextKey: nil}, 276 key: sdk.AccAddress([]byte("no-group-address")), 277 }, 278 "with offset and count total": { 279 pageReq: &query.PageRequest{Key: nil, Offset: 1, Limit: 2, CountTotal: true}, 280 exp: []testdata.TableModel{t2, t4}, 281 expPageRes: &query.PageResponse{Total: 3, NextKey: nil}, 282 key: metadata, 283 }, 284 "nil/default page req (limit = 100 > number of items)": { 285 pageReq: nil, 286 exp: []testdata.TableModel{t1, t2, t4}, 287 expPageRes: &query.PageResponse{Total: 3, NextKey: nil}, 288 key: metadata, 289 }, 290 "with key and limit < number of elem (count total is ignored in this case)": { 291 pageReq: &query.PageRequest{Key: EncodeSequence(2), Limit: 1, CountTotal: true}, 292 exp: []testdata.TableModel{t2}, 293 expPageRes: &query.PageResponse{Total: 0, NextKey: EncodeSequence(4)}, 294 key: metadata, 295 }, 296 "with key and limit >= number of elem": { 297 pageReq: &query.PageRequest{Key: EncodeSequence(2), Limit: 2}, 298 exp: []testdata.TableModel{t2, t4}, 299 expPageRes: &query.PageResponse{Total: 0, NextKey: nil}, 300 key: metadata, 301 }, 302 "with nothing left to iterate from key": { 303 pageReq: &query.PageRequest{Key: EncodeSequence(5)}, 304 exp: []testdata.TableModel{}, 305 expPageRes: &query.PageResponse{Total: 0, NextKey: nil}, 306 key: metadata, 307 }, 308 } 309 for msg, spec := range specs { 310 t.Run(msg, func(t *testing.T) { 311 var loaded []testdata.TableModel 312 313 it, err := idx.GetPaginated(store, spec.key, spec.pageReq) 314 require.NoError(t, err) 315 316 res, err := Paginate(it, spec.pageReq, &loaded) 317 if spec.expErr { 318 require.Error(t, err) 319 } else { 320 require.NoError(t, err) 321 assert.EqualValues(t, spec.exp, loaded) 322 assert.EqualValues(t, spec.expPageRes.Total, res.Total) 323 assert.EqualValues(t, spec.expPageRes.NextKey, res.NextKey) 324 } 325 }) 326 } 327 328 t.Run("nil iterator", func(t *testing.T) { 329 var loaded []testdata.TableModel 330 res, err := Paginate(nil, &query.PageRequest{}, &loaded) 331 require.Error(t, err) 332 require.Contains(t, err.Error(), "iterator must not be nil") 333 require.Nil(t, res) 334 }) 335 336 t.Run("non-slice destination", func(t *testing.T) { 337 var loaded testdata.TableModel 338 res, err := Paginate( 339 mockIter(EncodeSequence(1), &testdata.TableModel{Name: "test"}), 340 &query.PageRequest{}, 341 &loaded, 342 ) 343 require.Error(t, err) 344 require.Contains(t, err.Error(), "destination must point to a slice") 345 require.Nil(t, res) 346 }) 347 } 348 349 // mockIter encodes + decodes value object. 350 func mockIter(rowID RowID, val proto.Message) Iterator { 351 b, err := proto.Marshal(val) 352 if err != nil { 353 panic(err) 354 } 355 return NewSingleValueIterator(rowID, b) 356 } 357 358 func noopIter() Iterator { 359 return IteratorFunc(func(dest proto.Message) (RowID, error) { 360 return nil, nil 361 }) 362 }