code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/votes_test.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package sqlstore_test 17 18 import ( 19 "context" 20 "fmt" 21 "math/rand" 22 "testing" 23 "time" 24 25 "code.vegaprotocol.io/vega/datanode/entities" 26 "code.vegaprotocol.io/vega/datanode/sqlstore" 27 "code.vegaprotocol.io/vega/protos/vega" 28 29 "github.com/google/go-cmp/cmp" 30 "github.com/google/go-cmp/cmp/cmpopts" 31 "github.com/shopspring/decimal" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 ) 35 36 func addTestVote(t *testing.T, 37 ctx context.Context, 38 vs *sqlstore.Votes, 39 party entities.Party, 40 proposal entities.Proposal, 41 value entities.VoteValue, 42 block entities.Block, 43 txHash entities.TxHash, 44 ) entities.Vote { 45 t.Helper() 46 r := entities.Vote{ 47 PartyID: party.ID, 48 ProposalID: proposal.ID, 49 Value: value, 50 TotalGovernanceTokenBalance: decimal.NewFromInt(100), 51 TotalGovernanceTokenWeight: decimal.NewFromFloat(0.1), 52 TotalEquityLikeShareWeight: decimal.NewFromFloat(0.3), 53 VegaTime: block.VegaTime, 54 InitialTime: block.VegaTime, 55 TxHash: txHash, 56 } 57 err := vs.Add(ctx, r) 58 require.NoError(t, err) 59 return r 60 } 61 62 func voteLessThan(x, y entities.Vote) bool { 63 if x.PartyID.String() != y.PartyID.String() { 64 return x.PartyID.String() < y.PartyID.String() 65 } 66 return x.ProposalID.String() < y.ProposalID.String() 67 } 68 69 func assertVotesMatch(t *testing.T, expected, actual []entities.Vote) { 70 t.Helper() 71 assert.Empty(t, cmp.Diff(actual, expected, cmpopts.SortSlices(voteLessThan))) 72 } 73 74 func TestVotes(t *testing.T) { 75 ctx := tempTransaction(t) 76 77 partyStore := sqlstore.NewParties(connectionSource) 78 propStore := sqlstore.NewProposals(connectionSource) 79 voteStore := sqlstore.NewVotes(connectionSource) 80 blockStore := sqlstore.NewBlocks(connectionSource) 81 block1 := addTestBlock(t, ctx, blockStore) 82 block2 := addTestBlock(t, ctx, blockStore) 83 84 party1 := addTestParty(t, ctx, partyStore, block1) 85 party2 := addTestParty(t, ctx, partyStore, block1) 86 prop1 := addTestProposal(t, ctx, propStore, GenerateID(), party1, GenerateID(), block1, entities.ProposalStateEnacted, entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl1.com", Description: "desc"}}, entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{}}}, entities.ProposalErrorUnspecified, nil, entities.BatchProposalTerms{}) 87 prop2 := addTestProposal(t, ctx, propStore, GenerateID(), party1, GenerateID(), block1, entities.ProposalStateEnacted, entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl2.com", Description: "desc"}}, entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{}}}, entities.ProposalErrorUnspecified, nil, entities.BatchProposalTerms{}) 88 89 party1ID := party1.ID.String() 90 prop1ID := prop1.ID.String() 91 92 txHash := txHashFromString("tx_vote_1") 93 txHash2 := txHashFromString("tx_vote_2") 94 95 vote1 := addTestVote(t, ctx, voteStore, party1, prop1, entities.VoteValueYes, block1, txHash) 96 vote2 := addTestVote(t, ctx, voteStore, party1, prop2, entities.VoteValueYes, block1, txHash2) 97 // Change vote in same block 98 vote3 := addTestVote(t, ctx, voteStore, party2, prop1, entities.VoteValueYes, block1, txHashFromString("tx_vote_3")) 99 vote3b := addTestVote(t, ctx, voteStore, party2, prop1, entities.VoteValueNo, block1, txHashFromString("tx_vote_4")) 100 // Change vote in next block 101 vote4 := addTestVote(t, ctx, voteStore, party2, prop2, entities.VoteValueYes, block1, txHashFromString("tx_vote_5")) 102 vote4b := addTestVote(t, ctx, voteStore, party2, prop2, entities.VoteValueNo, block2, txHashFromString("tx_vote_6")) 103 104 _ = vote3 105 _ = vote4 106 107 t.Run("GetAll", func(t *testing.T) { 108 expected := []entities.Vote{vote1, vote2, vote3b, vote4b} 109 actual, err := voteStore.Get(ctx, nil, nil, nil) 110 require.NoError(t, err) 111 assertVotesMatch(t, expected, actual) 112 }) 113 114 t.Run("GetByTxHash", func(t *testing.T) { 115 expected := []entities.Vote{vote1} 116 actual, err := voteStore.GetByTxHash(ctx, txHash) 117 require.NoError(t, err) 118 assertVotesMatch(t, expected, actual) 119 120 expected = []entities.Vote{vote2} 121 actual, err = voteStore.GetByTxHash(ctx, txHash2) 122 require.NoError(t, err) 123 assertVotesMatch(t, expected, actual) 124 }) 125 126 t.Run("GetByProposal", func(t *testing.T) { 127 expected := []entities.Vote{vote1, vote3b} 128 actual, err := voteStore.Get(ctx, &prop1ID, nil, nil) 129 require.NoError(t, err) 130 assertVotesMatch(t, expected, actual) 131 }) 132 133 t.Run("GetByParty", func(t *testing.T) { 134 expected := []entities.Vote{vote1, vote2} 135 actual, err := voteStore.Get(ctx, nil, &party1ID, nil) 136 require.NoError(t, err) 137 assertVotesMatch(t, expected, actual) 138 }) 139 140 t.Run("GetByValue", func(t *testing.T) { 141 expected := []entities.Vote{vote3b, vote4b} 142 no := entities.VoteValueNo 143 actual, err := voteStore.Get(ctx, nil, nil, &no) 144 require.NoError(t, err) 145 assertVotesMatch(t, expected, actual) 146 }) 147 148 t.Run("GetByEverything", func(t *testing.T) { 149 expected := []entities.Vote{vote1} 150 yes := entities.VoteValueYes 151 actual, err := voteStore.Get(ctx, &prop1ID, &party1ID, &yes) 152 require.NoError(t, err) 153 assertVotesMatch(t, expected, actual) 154 }) 155 156 t.Run("GetConnectionByEverything", func(t *testing.T) { 157 expected := []entities.Vote{vote1} 158 actual, _, err := voteStore.GetConnection(ctx, &prop1ID, &party1ID, entities.DefaultCursorPagination(true)) 159 require.NoError(t, err) 160 assertVotesMatch(t, expected, actual) 161 }) 162 } 163 164 func setupPaginationTestVotes(t *testing.T, ctx context.Context) (*sqlstore.Votes, entities.Party, []entities.Vote) { 165 t.Helper() 166 votes := make([]entities.Vote, 0, 10) 167 168 partyStore := sqlstore.NewParties(connectionSource) 169 propStore := sqlstore.NewProposals(connectionSource) 170 voteStore := sqlstore.NewVotes(connectionSource) 171 blockStore := sqlstore.NewBlocks(connectionSource) 172 173 blockTime := time.Now() 174 block := addTestBlockForTime(t, ctx, blockStore, blockTime) 175 party := addTestParty(t, ctx, partyStore, block) 176 177 rand.Seed(time.Now().UnixNano()) 178 179 for i := 0; i < 10; i++ { 180 blockTime = blockTime.Add(time.Second) 181 block = addTestBlockForTime(t, ctx, blockStore, blockTime) 182 prop := addTestProposal(t, 183 ctx, 184 propStore, 185 GenerateID(), 186 party, 187 GenerateID(), 188 block, 189 entities.ProposalStateEnacted, 190 entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: fmt.Sprintf("myurl%02d.com", i+1), Description: "desc"}}, 191 entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{}}}, 192 entities.ProposalErrorUnspecified, 193 nil, entities.BatchProposalTerms{}, 194 ) 195 196 voteValue := entities.VoteValueYes 197 if rand.Intn(100)%2 == 0 { 198 voteValue = entities.VoteValueNo 199 } 200 201 vote := addTestVote(t, ctx, voteStore, party, prop, voteValue, block, txHashFromString("tx_hash_1")) 202 votes = append(votes, vote) 203 } 204 205 return voteStore, party, votes 206 } 207 208 func TestVotesCursorPagination(t *testing.T) { 209 t.Run("Should return all votes if no pagination is provided", testVotesCursorPaginationNoPagination) 210 t.Run("Should return first page of votes if first is provided no after cursor", testVotesCursorPaginationFirstNoAfter) 211 t.Run("Should return requested page of votes if first is provided with after cursor", testVotesCursorPaginationFirstWithAfter) 212 t.Run("Should return last page of votes if last is provided no before cursor", testVotesCursorPaginationLastNoBefore) 213 t.Run("Should return requested page of votes if last is provided with before cursor", testVotesCursorPaginationLastWithBefore) 214 215 t.Run("Should return all votes if no pagination is provided - newest first", testVotesCursorPaginationNoPaginationNewestFirst) 216 t.Run("Should return first page of votes if first is provided no after cursor - newest first", testVotesCursorPaginationFirstNoAfterNewestFirst) 217 t.Run("Should return requested page of votes if first is provided with after cursor - newest first", testVotesCursorPaginationFirstWithAfterNewestFirst) 218 t.Run("Should return last page of votes if last is provided no before cursor - newest first", testVotesCursorPaginationLastNoBeforeNewestFirst) 219 t.Run("Should return requested page of votes if last is provided with before cursor - newest first", testVotesCursorPaginationLastWithBeforeNewestFirst) 220 } 221 222 func testVotesCursorPaginationNoPagination(t *testing.T) { 223 ctx := tempTransaction(t) 224 225 vs, party, votes := setupPaginationTestVotes(t, ctx) 226 pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, false) 227 require.NoError(t, err) 228 got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination) 229 require.NoError(t, err) 230 require.Equal(t, votes, got) 231 require.Equal(t, entities.PageInfo{ 232 HasNextPage: false, 233 HasPreviousPage: false, 234 StartCursor: votes[0].Cursor().Encode(), 235 EndCursor: votes[len(votes)-1].Cursor().Encode(), 236 }, pageInfo) 237 } 238 239 func testVotesCursorPaginationFirstNoAfter(t *testing.T) { 240 ctx := tempTransaction(t) 241 242 vs, party, votes := setupPaginationTestVotes(t, ctx) 243 first := int32(3) 244 pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false) 245 require.NoError(t, err) 246 got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination) 247 require.NoError(t, err) 248 require.Equal(t, votes[:3], got) 249 require.Equal(t, entities.PageInfo{ 250 HasNextPage: true, 251 HasPreviousPage: false, 252 StartCursor: votes[0].Cursor().Encode(), 253 EndCursor: votes[2].Cursor().Encode(), 254 }, pageInfo) 255 } 256 257 func testVotesCursorPaginationFirstWithAfter(t *testing.T) { 258 ctx := tempTransaction(t) 259 260 vs, party, votes := setupPaginationTestVotes(t, ctx) 261 first := int32(3) 262 after := votes[2].Cursor().Encode() 263 pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, false) 264 require.NoError(t, err) 265 got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination) 266 require.NoError(t, err) 267 require.Equal(t, votes[3:6], got) 268 require.Equal(t, entities.PageInfo{ 269 HasNextPage: true, 270 HasPreviousPage: true, 271 StartCursor: votes[3].Cursor().Encode(), 272 EndCursor: votes[5].Cursor().Encode(), 273 }, pageInfo) 274 } 275 276 func testVotesCursorPaginationLastNoBefore(t *testing.T) { 277 ctx := tempTransaction(t) 278 279 vs, party, votes := setupPaginationTestVotes(t, ctx) 280 last := int32(3) 281 pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, false) 282 require.NoError(t, err) 283 got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination) 284 require.NoError(t, err) 285 require.Equal(t, votes[7:], got) 286 require.Equal(t, entities.PageInfo{ 287 HasNextPage: false, 288 HasPreviousPage: true, 289 StartCursor: votes[7].Cursor().Encode(), 290 EndCursor: votes[9].Cursor().Encode(), 291 }, pageInfo) 292 } 293 294 func testVotesCursorPaginationLastWithBefore(t *testing.T) { 295 ctx := tempTransaction(t) 296 297 vs, party, votes := setupPaginationTestVotes(t, ctx) 298 last := int32(3) 299 before := votes[7].Cursor().Encode() 300 pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, false) 301 require.NoError(t, err) 302 got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination) 303 require.NoError(t, err) 304 require.Equal(t, votes[4:7], got) 305 require.Equal(t, entities.PageInfo{ 306 HasNextPage: true, 307 HasPreviousPage: true, 308 StartCursor: votes[4].Cursor().Encode(), 309 EndCursor: votes[6].Cursor().Encode(), 310 }, pageInfo) 311 } 312 313 func testVotesCursorPaginationNoPaginationNewestFirst(t *testing.T) { 314 ctx := tempTransaction(t) 315 316 vs, party, votes := setupPaginationTestVotes(t, ctx) 317 votes = entities.ReverseSlice(votes) 318 pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true) 319 require.NoError(t, err) 320 got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination) 321 require.NoError(t, err) 322 require.Equal(t, votes, got) 323 require.Equal(t, entities.PageInfo{ 324 HasNextPage: false, 325 HasPreviousPage: false, 326 StartCursor: votes[0].Cursor().Encode(), 327 EndCursor: votes[len(votes)-1].Cursor().Encode(), 328 }, pageInfo) 329 } 330 331 func testVotesCursorPaginationFirstNoAfterNewestFirst(t *testing.T) { 332 ctx := tempTransaction(t) 333 334 vs, party, votes := setupPaginationTestVotes(t, ctx) 335 votes = entities.ReverseSlice(votes) 336 first := int32(3) 337 pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true) 338 require.NoError(t, err) 339 got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination) 340 require.NoError(t, err) 341 require.Equal(t, votes[:3], got) 342 require.Equal(t, entities.PageInfo{ 343 HasNextPage: true, 344 HasPreviousPage: false, 345 StartCursor: votes[0].Cursor().Encode(), 346 EndCursor: votes[2].Cursor().Encode(), 347 }, pageInfo) 348 } 349 350 func testVotesCursorPaginationFirstWithAfterNewestFirst(t *testing.T) { 351 ctx := tempTransaction(t) 352 353 vs, party, votes := setupPaginationTestVotes(t, ctx) 354 votes = entities.ReverseSlice(votes) 355 first := int32(3) 356 after := votes[2].Cursor().Encode() 357 pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true) 358 require.NoError(t, err) 359 got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination) 360 require.NoError(t, err) 361 require.Equal(t, votes[3:6], got) 362 require.Equal(t, entities.PageInfo{ 363 HasNextPage: true, 364 HasPreviousPage: true, 365 StartCursor: votes[3].Cursor().Encode(), 366 EndCursor: votes[5].Cursor().Encode(), 367 }, pageInfo) 368 } 369 370 func testVotesCursorPaginationLastNoBeforeNewestFirst(t *testing.T) { 371 ctx := tempTransaction(t) 372 373 vs, party, votes := setupPaginationTestVotes(t, ctx) 374 votes = entities.ReverseSlice(votes) 375 last := int32(3) 376 pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true) 377 require.NoError(t, err) 378 got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination) 379 require.NoError(t, err) 380 require.Equal(t, votes[7:], got) 381 require.Equal(t, entities.PageInfo{ 382 HasNextPage: false, 383 HasPreviousPage: true, 384 StartCursor: votes[7].Cursor().Encode(), 385 EndCursor: votes[9].Cursor().Encode(), 386 }, pageInfo) 387 } 388 389 func testVotesCursorPaginationLastWithBeforeNewestFirst(t *testing.T) { 390 ctx := tempTransaction(t) 391 392 vs, party, votes := setupPaginationTestVotes(t, ctx) 393 votes = entities.ReverseSlice(votes) 394 last := int32(3) 395 before := votes[7].Cursor().Encode() 396 pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true) 397 require.NoError(t, err) 398 got, pageInfo, err := vs.GetByPartyConnection(ctx, party.ID.String(), pagination) 399 require.NoError(t, err) 400 require.Equal(t, votes[4:7], got) 401 require.Equal(t, entities.PageInfo{ 402 HasNextPage: true, 403 HasPreviousPage: true, 404 StartCursor: votes[4].Cursor().Encode(), 405 EndCursor: votes[6].Cursor().Encode(), 406 }, pageInfo) 407 } 408 409 func TestVoteValueEnum(t *testing.T) { 410 var voteValue vega.Vote_Value 411 values := getEnums(t, voteValue) 412 assert.Len(t, values, 3) 413 for v, value := range values { 414 t.Run(value, func(t *testing.T) { 415 ctx := tempTransaction(t) 416 417 partyStore := sqlstore.NewParties(connectionSource) 418 propStore := sqlstore.NewProposals(connectionSource) 419 voteStore := sqlstore.NewVotes(connectionSource) 420 blockStore := sqlstore.NewBlocks(connectionSource) 421 block := addTestBlock(t, ctx, blockStore) 422 423 party := addTestParty(t, ctx, partyStore, block) 424 proposal := addTestProposal(t, ctx, propStore, GenerateID(), party, GenerateID(), block, entities.ProposalStateEnacted, entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl1.com", Description: "desc"}}, entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{}}}, entities.ProposalErrorUnspecified, nil, entities.BatchProposalTerms{}) 425 426 txHash := txHashFromString("tx_vote_1") 427 want := entities.Vote{ 428 PartyID: party.ID, 429 ProposalID: proposal.ID, 430 Value: entities.VoteValue(v), 431 TotalGovernanceTokenBalance: decimal.NewFromInt(100), 432 TotalGovernanceTokenWeight: decimal.NewFromFloat(0.1), 433 TotalEquityLikeShareWeight: decimal.NewFromFloat(0.3), 434 VegaTime: block.VegaTime, 435 InitialTime: block.VegaTime, 436 TxHash: txHash, 437 } 438 require.NoError(t, voteStore.Add(ctx, want)) 439 got, err := voteStore.GetByTxHash(ctx, txHash) 440 require.NoError(t, err) 441 assert.Len(t, got, 1) 442 assert.Equal(t, want.Value, got[0].Value) 443 }) 444 } 445 }