code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/teams_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  	"encoding/json"
    20  	"fmt"
    21  	"math/rand"
    22  	"sort"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"code.vegaprotocol.io/vega/datanode/entities"
    28  	"code.vegaprotocol.io/vega/datanode/sqlstore"
    29  	vgcrypto "code.vegaprotocol.io/vega/libs/crypto"
    30  	"code.vegaprotocol.io/vega/libs/num"
    31  	"code.vegaprotocol.io/vega/libs/ptr"
    32  	vegapb "code.vegaprotocol.io/vega/protos/vega"
    33  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    34  
    35  	"github.com/georgysavva/scany/pgxscan"
    36  	"github.com/shopspring/decimal"
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  	"golang.org/x/exp/slices"
    40  )
    41  
    42  func TestTeams_AddTeams(t *testing.T) {
    43  	bs, ts, ps := setupTeamsTest(t)
    44  	ctx := tempTransaction(t)
    45  
    46  	block := addTestBlock(t, ctx, bs)
    47  	referrer := addTestParty(t, ctx, ps, block)
    48  
    49  	team := entities.Team{
    50  		ID:             entities.TeamID(GenerateID()),
    51  		Referrer:       referrer.ID,
    52  		Name:           "Test Team",
    53  		TeamURL:        nil,
    54  		AvatarURL:      nil,
    55  		CreatedAt:      block.VegaTime,
    56  		CreatedAtEpoch: 1,
    57  		VegaTime:       block.VegaTime,
    58  		Closed:         true,
    59  		AllowList:      []string{GenerateID(), GenerateID()},
    60  	}
    61  
    62  	t.Run("Should add a new if it does not already exist", func(t *testing.T) {
    63  		err := ts.AddTeam(ctx, &team)
    64  
    65  		require.NoError(t, err)
    66  
    67  		var teamFromDB entities.Team
    68  		err = pgxscan.Get(ctx, connectionSource, &teamFromDB, `SELECT * FROM teams WHERE id=$1`, team.ID)
    69  		require.NoError(t, err)
    70  		require.Equal(t, team, teamFromDB)
    71  	})
    72  	t.Run("Should error if team already exists", func(t *testing.T) {
    73  		err := ts.AddTeam(ctx, &team)
    74  
    75  		require.Error(t, err)
    76  		assert.Contains(t, err.Error(), "duplicate key value violates unique constraint")
    77  	})
    78  }
    79  
    80  func TestTeams_UpdateTeam(t *testing.T) {
    81  	bs, ts, ps := setupTeamsTest(t)
    82  	ctx := tempTransaction(t)
    83  
    84  	block := addTestBlock(t, ctx, bs)
    85  	referrer := addTestParty(t, ctx, ps, block)
    86  
    87  	team := entities.Team{
    88  		ID:        entities.TeamID(GenerateID()),
    89  		Referrer:  referrer.ID,
    90  		Name:      "Test Team",
    91  		TeamURL:   nil,
    92  		AvatarURL: nil,
    93  		CreatedAt: block.VegaTime,
    94  		VegaTime:  block.VegaTime,
    95  		Closed:    true,
    96  		AllowList: []string{GenerateID(), GenerateID()},
    97  	}
    98  
    99  	err := ts.AddTeam(ctx, &team)
   100  	require.NoError(t, err)
   101  
   102  	t.Run("Should update a team if it exists", func(t *testing.T) {
   103  		nextBlock := addTestBlock(t, ctx, bs)
   104  
   105  		updateTeam := entities.TeamUpdated{
   106  			ID:        team.ID,
   107  			Name:      team.Name,
   108  			TeamURL:   ptr.From("https://surely-you-cant-be-serio.us"),
   109  			AvatarURL: ptr.From("https://dont-call-me-shirl.ee"),
   110  			VegaTime:  nextBlock.VegaTime,
   111  			Closed:    true,
   112  			AllowList: []string{GenerateID(), GenerateID()},
   113  		}
   114  
   115  		err := ts.UpdateTeam(ctx, &updateTeam)
   116  		require.NoError(t, err)
   117  
   118  		want := entities.Team{
   119  			ID:        team.ID,
   120  			Referrer:  team.Referrer,
   121  			Name:      team.Name,
   122  			TeamURL:   updateTeam.TeamURL,
   123  			AvatarURL: updateTeam.AvatarURL,
   124  			Closed:    updateTeam.Closed,
   125  			AllowList: updateTeam.AllowList,
   126  			CreatedAt: team.CreatedAt,
   127  			VegaTime:  team.VegaTime,
   128  		}
   129  
   130  		var got entities.Team
   131  
   132  		err = pgxscan.Get(ctx, connectionSource, &got, `SELECT * FROM teams WHERE id=$1`, team.ID)
   133  		require.NoError(t, err)
   134  
   135  		assert.Equal(t, want, got)
   136  	})
   137  
   138  	t.Run("Should error if team does not exist", func(t *testing.T) {
   139  		nextBlock := addTestBlock(t, ctx, bs)
   140  
   141  		updateTeam := entities.TeamUpdated{
   142  			ID:        entities.TeamID(GenerateID()),
   143  			Name:      team.Name,
   144  			TeamURL:   ptr.From("https://surely-you-cant-be-serio.us"),
   145  			AvatarURL: ptr.From("https://dont-call-me-shirl.ee"),
   146  			Closed:    false,
   147  			VegaTime:  nextBlock.VegaTime,
   148  		}
   149  
   150  		err := ts.UpdateTeam(ctx, &updateTeam)
   151  		require.Error(t, err)
   152  	})
   153  }
   154  
   155  func TestTeams_RefereeJoinedTeam(t *testing.T) {
   156  	t.Run("Should add a new referee for the team", testTeamsShouldAddReferee)
   157  	t.Run("Should show joined team as current team", testTeamsShouldShowJoinedTeamAsCurrentTeam)
   158  }
   159  
   160  func testTeamsShouldAddReferee(t *testing.T) {
   161  	bs, ts, ps := setupTeamsTest(t)
   162  	ctx := tempTransaction(t)
   163  
   164  	block := addTestBlock(t, ctx, bs)
   165  	referrer := addTestParty(t, ctx, ps, block)
   166  
   167  	team := entities.Team{
   168  		ID:        entities.TeamID(GenerateID()),
   169  		Referrer:  referrer.ID,
   170  		Name:      "Test Team",
   171  		TeamURL:   nil,
   172  		AvatarURL: nil,
   173  		CreatedAt: block.VegaTime,
   174  		VegaTime:  block.VegaTime,
   175  	}
   176  
   177  	require.NoError(t, ts.AddTeam(ctx, &team))
   178  
   179  	referee := addTestParty(t, ctx, ps, block)
   180  
   181  	joinEvent := &eventspb.RefereeJoinedTeam{
   182  		TeamId:   team.ID.String(),
   183  		Referee:  referee.ID.String(),
   184  		JoinedAt: block.VegaTime.UnixNano(),
   185  	}
   186  
   187  	teamReferee := entities.TeamRefereeFromProto(joinEvent, block.VegaTime)
   188  	assert.NoError(t, ts.RefereeJoinedTeam(ctx, teamReferee))
   189  
   190  	var got entities.TeamMember
   191  	require.NoError(t, pgxscan.Get(ctx, connectionSource, &got, `SELECT * FROM team_members WHERE team_id=$1 AND party_id=$2`, team.ID, referee.ID))
   192  	assert.Equal(t, teamReferee, &got)
   193  }
   194  
   195  func testTeamsShouldShowJoinedTeamAsCurrentTeam(t *testing.T) {
   196  	bs, ts, ps := setupTeamsTest(t)
   197  	ctx := tempTransaction(t)
   198  
   199  	block := addTestBlock(t, ctx, bs)
   200  	referrer1 := addTestParty(t, ctx, ps, block)
   201  	referrer2 := addTestParty(t, ctx, ps, block)
   202  
   203  	team1 := entities.Team{
   204  		ID:             entities.TeamID(GenerateID()),
   205  		Referrer:       referrer1.ID,
   206  		Name:           "Test Team 1",
   207  		TeamURL:        nil,
   208  		AvatarURL:      nil,
   209  		CreatedAt:      block.VegaTime,
   210  		CreatedAtEpoch: 1,
   211  		VegaTime:       block.VegaTime,
   212  	}
   213  	require.NoError(t, ts.AddTeam(ctx, &team1))
   214  
   215  	team2 := entities.Team{
   216  		ID:             entities.TeamID(GenerateID()),
   217  		Referrer:       referrer2.ID,
   218  		Name:           "Test Team 2",
   219  		TeamURL:        nil,
   220  		AvatarURL:      nil,
   221  		CreatedAt:      block.VegaTime,
   222  		CreatedAtEpoch: 1,
   223  		VegaTime:       block.VegaTime,
   224  	}
   225  	require.NoError(t, ts.AddTeam(ctx, &team2))
   226  
   227  	referee1 := addTestParty(t, ctx, ps, block)
   228  
   229  	joinEvent1 := &eventspb.RefereeJoinedTeam{
   230  		TeamId:   team1.ID.String(),
   231  		Referee:  referee1.ID.String(),
   232  		JoinedAt: block.VegaTime.UnixNano(),
   233  		AtEpoch:  2,
   234  	}
   235  	assert.NoError(t, ts.RefereeJoinedTeam(ctx, entities.TeamRefereeFromProto(joinEvent1, block.VegaTime)))
   236  
   237  	var got1 entities.TeamMember
   238  	require.NoError(t, pgxscan.Get(ctx, connectionSource, &got1, `SELECT * FROM current_team_members WHERE party_id=$1`, referee1.ID))
   239  	assert.Equal(t, team1.ID, (&got1).TeamID)
   240  
   241  	referee2 := addTestParty(t, ctx, ps, block)
   242  
   243  	joinEvent2 := &eventspb.RefereeJoinedTeam{
   244  		TeamId:   team2.ID.String(),
   245  		Referee:  referee2.ID.String(),
   246  		JoinedAt: block.VegaTime.UnixNano(),
   247  		AtEpoch:  3,
   248  	}
   249  	assert.NoError(t, ts.RefereeJoinedTeam(ctx, entities.TeamRefereeFromProto(joinEvent2, block.VegaTime)))
   250  
   251  	var got2 entities.TeamMember
   252  	require.NoError(t, pgxscan.Get(ctx, connectionSource, &got2, `SELECT * FROM current_team_members WHERE party_id=$1`, referee2.ID))
   253  	assert.Equal(t, team2.ID, (&got2).TeamID)
   254  }
   255  
   256  func TestTeams_RefereeSwitchedTeam(t *testing.T) {
   257  	t.Run("Should show last joined team as current team", testTeamsShouldShowLastJoinedTeamAsCurrentTeam)
   258  }
   259  
   260  func testTeamsShouldShowLastJoinedTeamAsCurrentTeam(t *testing.T) {
   261  	bs, ts, ps := setupTeamsTest(t)
   262  	ctx := tempTransaction(t)
   263  
   264  	block := addTestBlock(t, ctx, bs)
   265  	referrer1 := addTestParty(t, ctx, ps, block)
   266  	referrer2 := addTestParty(t, ctx, ps, block)
   267  
   268  	team1 := entities.Team{
   269  		ID:             entities.TeamID(GenerateID()),
   270  		Referrer:       referrer1.ID,
   271  		Name:           "Test Team 1",
   272  		TeamURL:        nil,
   273  		AvatarURL:      nil,
   274  		CreatedAt:      block.VegaTime,
   275  		CreatedAtEpoch: 1,
   276  		VegaTime:       block.VegaTime,
   277  	}
   278  	require.NoError(t, ts.AddTeam(ctx, &team1))
   279  
   280  	team2 := entities.Team{
   281  		ID:             entities.TeamID(GenerateID()),
   282  		Referrer:       referrer2.ID,
   283  		Name:           "Test Team 2",
   284  		TeamURL:        nil,
   285  		AvatarURL:      nil,
   286  		CreatedAt:      block.VegaTime,
   287  		CreatedAtEpoch: 1,
   288  		VegaTime:       block.VegaTime,
   289  	}
   290  	require.NoError(t, ts.AddTeam(ctx, &team2))
   291  
   292  	referee := addTestParty(t, ctx, ps, block)
   293  
   294  	joinEvent1 := &eventspb.RefereeJoinedTeam{
   295  		TeamId:   team1.ID.String(),
   296  		Referee:  referee.ID.String(),
   297  		JoinedAt: block.VegaTime.UnixNano(),
   298  		AtEpoch:  2,
   299  	}
   300  	assert.NoError(t, ts.RefereeJoinedTeam(ctx, entities.TeamRefereeFromProto(joinEvent1, block.VegaTime)))
   301  
   302  	var got1 entities.TeamMember
   303  	require.NoError(t, pgxscan.Get(ctx, connectionSource, &got1, `SELECT * FROM current_team_members WHERE party_id=$1`, referee.ID))
   304  	assert.Equal(t, team1.ID, (&got1).TeamID)
   305  
   306  	joinEvent2 := &eventspb.RefereeJoinedTeam{
   307  		TeamId:   team2.ID.String(),
   308  		Referee:  referee.ID.String(),
   309  		JoinedAt: block.VegaTime.UnixNano(),
   310  		AtEpoch:  3,
   311  	}
   312  	assert.NoError(t, ts.RefereeJoinedTeam(ctx, entities.TeamRefereeFromProto(joinEvent2, block.VegaTime)))
   313  
   314  	var got2 entities.TeamMember
   315  	require.NoError(t, pgxscan.Get(ctx, connectionSource, &got2, `SELECT * FROM current_team_members WHERE party_id=$1`, referee.ID))
   316  	assert.Equal(t, team2.ID, (&got2).TeamID)
   317  }
   318  
   319  func TestTeams_GetTeams(t *testing.T) {
   320  	t.Run("Should return a team if the team ID is provided", testShouldReturnTeamIfTeamIDProvided)
   321  	t.Run("Should return a team if a referrer party  ID is provided", testShouldReturnTeamIfReferrerPartyIDProvided)
   322  	t.Run("Should return a team if a referee party ID is provided", testShouldReturnTeamIfRefereePartyIDProvided)
   323  	t.Run("Should return an error if no team ID or party ID is provided", testShouldReturnErrorIfNoTeamIDOrPartyIDProvided)
   324  }
   325  
   326  func testShouldReturnTeamIfTeamIDProvided(t *testing.T) {
   327  	bs, ts, ps := setupTeamsTest(t)
   328  	ctx := tempTransaction(t)
   329  
   330  	teams, _ := setupTeams(t, ctx, bs, ps, ts)
   331  
   332  	want := teams[rand.Intn(len(teams))]
   333  	got, err := ts.GetTeam(ctx, want.ID, "")
   334  	require.NoError(t, err)
   335  	require.NotNil(t, got)
   336  	assert.Equal(t, want, *got)
   337  }
   338  
   339  func testShouldReturnTeamIfReferrerPartyIDProvided(t *testing.T) {
   340  	bs, ts, ps := setupTeamsTest(t)
   341  	ctx := tempTransaction(t)
   342  
   343  	teams, _ := setupTeams(t, ctx, bs, ps, ts)
   344  
   345  	want := teams[rand.Intn(len(teams))]
   346  
   347  	got, err := ts.GetTeam(ctx, "", want.Referrer)
   348  	require.NoError(t, err)
   349  	require.NotNil(t, got)
   350  	assert.Equal(t, want, *got)
   351  }
   352  
   353  func testShouldReturnTeamIfRefereePartyIDProvided(t *testing.T) {
   354  	bs, ts, ps := setupTeamsTest(t)
   355  
   356  	ctx := tempTransaction(t)
   357  
   358  	teams, teamsHistory := setupTeams(t, ctx, bs, ps, ts)
   359  
   360  	wantTeam := teams[rand.Intn(len(teams))]
   361  	referees := currentRefereesForTeam(teamsHistory, wantTeam.ID)
   362  	wantMember := referees[rand.Intn(len(referees))]
   363  
   364  	got, err := ts.GetTeam(ctx, "", wantMember.PartyID)
   365  	require.NoError(t, err)
   366  	require.NotNil(t, got)
   367  	assert.Equal(t, wantTeam, *got)
   368  }
   369  
   370  func testShouldReturnErrorIfNoTeamIDOrPartyIDProvided(t *testing.T) {
   371  	bs, ts, ps := setupTeamsTest(t)
   372  	ctx := tempTransaction(t)
   373  
   374  	setupTeams(t, ctx, bs, ps, ts)
   375  
   376  	_, err := ts.GetTeam(ctx, "", "")
   377  	require.Error(t, err)
   378  }
   379  
   380  func TestTeams_ListTeams(t *testing.T) {
   381  	t.Run("Should return a page of teams if no pagination is provided", testShouldReturnPageOfTeamsIfNoPaginationProvided)
   382  	t.Run("Should return a page of teams if no pagination is provided newest first", testShouldReturnPageOfTeamsIfNoPaginationProvidedNewestFirst)
   383  	t.Run("Should return the first page of teams if first N is requested", testShouldReturnFirstPageOfTeamsIfFirstNRequested)
   384  	t.Run("Should return the last page of teams if last N is requested", testShouldReturnLastPageOfTeamsIfLastNRequested)
   385  	t.Run("Should return the page of teams given the provided pagination", testShouldReturnPageOfTeamsGivenPagination)
   386  }
   387  
   388  func testShouldReturnPageOfTeamsIfNoPaginationProvided(t *testing.T) {
   389  	bs, ts, ps := setupTeamsTest(t)
   390  	ctx := tempTransaction(t)
   391  
   392  	teams, _ := setupTeams(t, ctx, bs, ps, ts)
   393  
   394  	got, pageInfo, err := ts.ListTeams(ctx, entities.CursorPagination{})
   395  	require.NoError(t, err)
   396  	assert.Equal(t, teams, got)
   397  	assert.Equal(t, entities.PageInfo{
   398  		HasNextPage:     false,
   399  		HasPreviousPage: false,
   400  		StartCursor:     teams[0].Cursor().Encode(),
   401  		EndCursor:       teams[len(teams)-1].Cursor().Encode(),
   402  	}, pageInfo)
   403  }
   404  
   405  func testShouldReturnPageOfTeamsIfNoPaginationProvidedNewestFirst(t *testing.T) {
   406  	bs, ts, ps := setupTeamsTest(t)
   407  	ctx := tempTransaction(t)
   408  
   409  	teams, _ := setupTeams(t, ctx, bs, ps, ts)
   410  
   411  	got, pageInfo, err := ts.ListTeams(ctx, entities.CursorPagination{NewestFirst: true})
   412  	require.NoError(t, err)
   413  
   414  	sort.Slice(teams, func(i, j int) bool {
   415  		return teams[i].CreatedAt.After(teams[j].CreatedAt)
   416  	})
   417  
   418  	assert.Equal(t, teams, got)
   419  	assert.Equal(t, entities.PageInfo{
   420  		HasNextPage:     false,
   421  		HasPreviousPage: false,
   422  		StartCursor:     teams[0].Cursor().Encode(),
   423  		EndCursor:       teams[len(teams)-1].Cursor().Encode(),
   424  	}, pageInfo)
   425  }
   426  
   427  func testShouldReturnFirstPageOfTeamsIfFirstNRequested(t *testing.T) {
   428  	bs, ts, ps := setupTeamsTest(t)
   429  	ctx := tempTransaction(t)
   430  
   431  	teams, _ := setupTeams(t, ctx, bs, ps, ts)
   432  
   433  	pagination, err := entities.NewCursorPagination(ptr.From(int32(3)), nil, nil, nil, false)
   434  	require.NoError(t, err)
   435  
   436  	got, pageInfo, err := ts.ListTeams(ctx, pagination)
   437  	require.NoError(t, err)
   438  
   439  	want := teams[:3]
   440  
   441  	assert.Equal(t, want, got)
   442  	assert.Equal(t, entities.PageInfo{
   443  		HasNextPage:     true,
   444  		HasPreviousPage: false,
   445  		StartCursor:     want[0].Cursor().Encode(),
   446  		EndCursor:       want[len(want)-1].Cursor().Encode(),
   447  	}, pageInfo)
   448  }
   449  
   450  func testShouldReturnLastPageOfTeamsIfLastNRequested(t *testing.T) {
   451  	bs, ts, ps := setupTeamsTest(t)
   452  	ctx := tempTransaction(t)
   453  
   454  	teams, _ := setupTeams(t, ctx, bs, ps, ts)
   455  
   456  	pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(3)), nil, false)
   457  	require.NoError(t, err)
   458  
   459  	got, pageInfo, err := ts.ListTeams(ctx, pagination)
   460  	require.NoError(t, err)
   461  
   462  	want := teams[len(teams)-3:]
   463  
   464  	assert.Equal(t, want, got)
   465  	assert.Equal(t, entities.PageInfo{
   466  		HasNextPage:     false,
   467  		HasPreviousPage: true,
   468  		StartCursor:     want[0].Cursor().Encode(),
   469  		EndCursor:       want[len(want)-1].Cursor().Encode(),
   470  	}, pageInfo)
   471  }
   472  
   473  func testShouldReturnPageOfTeamsGivenPagination(t *testing.T) {
   474  	bs, ts, ps := setupTeamsTest(t)
   475  	ctx := tempTransaction(t)
   476  
   477  	teams, _ := setupTeams(t, ctx, bs, ps, ts)
   478  
   479  	t.Run("first after", func(t *testing.T) {
   480  		pagination, err := entities.NewCursorPagination(ptr.From(int32(3)), ptr.From(teams[2].Cursor().Encode()), nil, nil, false)
   481  		require.NoError(t, err)
   482  
   483  		got, pageInfo, err := ts.ListTeams(ctx, pagination)
   484  		require.NoError(t, err)
   485  
   486  		want := teams[3:6]
   487  
   488  		assert.Equal(t, want, got)
   489  		assert.Equal(t, entities.PageInfo{
   490  			HasNextPage:     true,
   491  			HasPreviousPage: true,
   492  			StartCursor:     want[0].Cursor().Encode(),
   493  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   494  		}, pageInfo)
   495  	})
   496  
   497  	t.Run("last before", func(t *testing.T) {
   498  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(3)), ptr.From(teams[7].Cursor().Encode()), false)
   499  		require.NoError(t, err)
   500  
   501  		got, pageInfo, err := ts.ListTeams(ctx, pagination)
   502  		require.NoError(t, err)
   503  
   504  		want := teams[4:7]
   505  
   506  		assert.Equal(t, want, got)
   507  		assert.Equal(t, entities.PageInfo{
   508  			HasNextPage:     true,
   509  			HasPreviousPage: true,
   510  			StartCursor:     want[0].Cursor().Encode(),
   511  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   512  		}, pageInfo)
   513  	})
   514  }
   515  
   516  func TestTeams_ListReferees(t *testing.T) {
   517  	t.Run("Should return an error if no team ID is provided", testShouldReturnErrorIfNoTeamIDProvided)
   518  	t.Run("Should return a page of referees if no pagination is provided", testShouldReturnPageOfRefereesIfNoPaginationProvided)
   519  	t.Run("Should return the first page of referees if first N is requested", testShouldReturnFirstPageOfRefereesIfFirstNRequested)
   520  	t.Run("Should return the last page of referees if last N is requested", testShouldReturnLastPageOfRefereesIfLastNRequested)
   521  	t.Run("Should return the page of referees given the provided pagination", testShouldReturnPageOfRefereesGivenPagination)
   522  }
   523  
   524  func testShouldReturnErrorIfNoTeamIDProvided(t *testing.T) {
   525  	_, ts, _ := setupTeamsTest(t)
   526  	ctx := tempTransaction(t)
   527  
   528  	_, _, err := ts.ListReferees(ctx, "", entities.CursorPagination{})
   529  	require.Error(t, err)
   530  }
   531  
   532  func testShouldReturnPageOfRefereesIfNoPaginationProvided(t *testing.T) {
   533  	bs, ts, ps := setupTeamsTest(t)
   534  	ctx := tempTransaction(t)
   535  
   536  	teams, teamsHistory := setupTeams(t, ctx, bs, ps, ts)
   537  	team := teams[rand.Intn(len(teams))]
   538  
   539  	referees := currentRefereesForTeam(teamsHistory, team.ID)
   540  
   541  	got, pageInfo, err := ts.ListReferees(ctx, team.ID, entities.CursorPagination{})
   542  	require.NoError(t, err)
   543  	assert.Equal(t, referees, got)
   544  	assert.Equal(t, entities.PageInfo{
   545  		HasNextPage:     false,
   546  		HasPreviousPage: false,
   547  		StartCursor:     referees[0].Cursor().Encode(),
   548  		EndCursor:       referees[len(referees)-1].Cursor().Encode(),
   549  	}, pageInfo)
   550  }
   551  
   552  func testShouldReturnFirstPageOfRefereesIfFirstNRequested(t *testing.T) {
   553  	bs, ts, ps := setupTeamsTest(t)
   554  	ctx := tempTransaction(t)
   555  
   556  	teams, teamsHistory := setupTeams(t, ctx, bs, ps, ts)
   557  
   558  	team := teams[rand.Intn(len(teams))]
   559  
   560  	referees := currentRefereesForTeam(teamsHistory, team.ID)
   561  	pagination, err := entities.NewCursorPagination(ptr.From(int32(3)), nil, nil, nil, false)
   562  	require.NoError(t, err)
   563  	got, pageInfo, err := ts.ListReferees(ctx, team.ID, pagination)
   564  	require.NoError(t, err)
   565  	want := referees[:3]
   566  	assert.Equal(t, want, got)
   567  	assert.Equal(t, entities.PageInfo{
   568  		HasNextPage:     true,
   569  		HasPreviousPage: false,
   570  		StartCursor:     referees[0].Cursor().Encode(),
   571  		EndCursor:       referees[2].Cursor().Encode(),
   572  	}, pageInfo)
   573  }
   574  
   575  func testShouldReturnLastPageOfRefereesIfLastNRequested(t *testing.T) {
   576  	bs, ts, ps := setupTeamsTest(t)
   577  	ctx := tempTransaction(t)
   578  
   579  	teams, teamsHistory := setupTeams(t, ctx, bs, ps, ts)
   580  
   581  	team := teams[rand.Intn(len(teams))]
   582  
   583  	referees := currentRefereesForTeam(teamsHistory, team.ID)
   584  	pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(3)), nil, false)
   585  	require.NoError(t, err)
   586  	got, pageInfo, err := ts.ListReferees(ctx, team.ID, pagination)
   587  	require.NoError(t, err)
   588  	want := referees[len(referees)-3:]
   589  	assert.Equal(t, want, got)
   590  	assert.Equal(t, entities.PageInfo{
   591  		HasNextPage:     false,
   592  		HasPreviousPage: true,
   593  		StartCursor:     want[0].Cursor().Encode(),
   594  		EndCursor:       want[len(want)-1].Cursor().Encode(),
   595  	}, pageInfo)
   596  }
   597  
   598  func testShouldReturnPageOfRefereesGivenPagination(t *testing.T) {
   599  	bs, ts, ps := setupTeamsTest(t)
   600  	ctx := tempTransaction(t)
   601  
   602  	teams, teamsHistory := setupTeams(t, ctx, bs, ps, ts)
   603  
   604  	team := teams[rand.Intn(len(teams))]
   605  
   606  	referees := currentRefereesForTeam(teamsHistory, team.ID)
   607  
   608  	t.Run("first after", func(t *testing.T) {
   609  		pagination, err := entities.NewCursorPagination(ptr.From(int32(3)), ptr.From(referees[2].Cursor().Encode()), nil, nil, false)
   610  		require.NoError(t, err)
   611  		got, pageInfo, err := ts.ListReferees(ctx, team.ID, pagination)
   612  		require.NoError(t, err)
   613  
   614  		want := referees[3:6]
   615  		assert.Equal(t, want, got)
   616  		assert.Equal(t, entities.PageInfo{
   617  			HasNextPage:     true,
   618  			HasPreviousPage: true,
   619  			StartCursor:     referees[3].Cursor().Encode(),
   620  			EndCursor:       referees[5].Cursor().Encode(),
   621  		}, pageInfo)
   622  	})
   623  
   624  	t.Run("last before", func(t *testing.T) {
   625  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(3)), ptr.From(referees[7].Cursor().Encode()), false)
   626  		require.NoError(t, err)
   627  		got, pageInfo, err := ts.ListReferees(ctx, team.ID, pagination)
   628  		require.NoError(t, err)
   629  		want := referees[4:7]
   630  		assert.Equal(t, want, got)
   631  		assert.Equal(t, entities.PageInfo{
   632  			HasNextPage:     true,
   633  			HasPreviousPage: true,
   634  			StartCursor:     want[0].Cursor().Encode(),
   635  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   636  		}, pageInfo)
   637  	})
   638  }
   639  
   640  func TestTeams_ListRefereeHistory(t *testing.T) {
   641  	t.Run("Should return an error if the referee is not provided", testShouldReturnErrorIfRefereeNotProvided)
   642  	t.Run("Should return a page of referee history if no pagination is provided", testShouldReturnPageOfRefereeHistoryIfNoPaginationProvided)
   643  	t.Run("Should return a page of referee history if no pagination is provided newest first", testShouldReturnPageOfRefereeHistoryIfNoPaginationProvidedNewestFirst)
   644  	t.Run("Should return the first page of referee history if first N is requested", testShouldReturnFirstPageOfRefereeHistoryIfFirstNRequested)
   645  	t.Run("Should return the last page of referee history if last N is requested", testShouldReturnLastPageOfRefereeHistoryIfLastNRequested)
   646  	t.Run("Should return the page of referee history given the provided pagination", testShouldReturnPageOfRefereeHistoryGivenPagination)
   647  }
   648  
   649  func testShouldReturnErrorIfRefereeNotProvided(t *testing.T) {
   650  	_, ts, _ := setupTeamsTest(t)
   651  	ctx := tempTransaction(t)
   652  
   653  	_, _, err := ts.ListRefereeHistory(ctx, "", entities.CursorPagination{})
   654  	require.Error(t, err)
   655  }
   656  
   657  func testShouldReturnPageOfRefereeHistoryIfNoPaginationProvided(t *testing.T) {
   658  	bs, ts, ps := setupTeamsTest(t)
   659  	ctx := tempTransaction(t)
   660  
   661  	teams, teamsHistory := setupTeams(t, ctx, bs, ps, ts)
   662  	referee := teamsHistory[len(teams)] // the first n elements (== len(teams) are the referrers)
   663  
   664  	refereeHistory := historyForReferee(teamsHistory, referee.PartyID)
   665  
   666  	got, pageInfo, err := ts.ListRefereeHistory(ctx, referee.PartyID, entities.CursorPagination{})
   667  	require.NoError(t, err)
   668  	assert.Equal(t, refereeHistory, got)
   669  	assert.Equal(t, entities.PageInfo{
   670  		HasNextPage:     false,
   671  		HasPreviousPage: false,
   672  		StartCursor:     refereeHistory[0].Cursor().Encode(),
   673  		EndCursor:       refereeHistory[len(refereeHistory)-1].Cursor().Encode(),
   674  	}, pageInfo)
   675  }
   676  
   677  func testShouldReturnPageOfRefereeHistoryIfNoPaginationProvidedNewestFirst(t *testing.T) {
   678  	bs, ts, ps := setupTeamsTest(t)
   679  	ctx := tempTransaction(t)
   680  
   681  	teams, teamsHistory := setupTeams(t, ctx, bs, ps, ts)
   682  	referee := teamsHistory[len(teams)] // the first n elements (== len(teams) are the referrers)
   683  
   684  	got, pageInfo, err := ts.ListRefereeHistory(ctx, referee.PartyID, entities.CursorPagination{NewestFirst: true})
   685  	require.NoError(t, err)
   686  
   687  	refereeHistory := historyForReferee(teamsHistory, referee.PartyID)
   688  	slices.SortStableFunc(refereeHistory, func(a, b entities.TeamMemberHistory) int {
   689  		return -compareUint64(a.JoinedAtEpoch, b.JoinedAtEpoch)
   690  	})
   691  
   692  	assert.Equal(t, refereeHistory, got)
   693  	assert.Equal(t, entities.PageInfo{
   694  		HasNextPage:     false,
   695  		HasPreviousPage: false,
   696  		StartCursor:     refereeHistory[0].Cursor().Encode(),
   697  		EndCursor:       refereeHistory[len(refereeHistory)-1].Cursor().Encode(),
   698  	}, pageInfo)
   699  }
   700  
   701  func testShouldReturnFirstPageOfRefereeHistoryIfFirstNRequested(t *testing.T) {
   702  	bs, ts, ps := setupTeamsTest(t)
   703  	ctx := tempTransaction(t)
   704  
   705  	teams, teamsHistory := setupTeams(t, ctx, bs, ps, ts)
   706  	referee := teamsHistory[len(teams)] // the first n elements (== len(teams) are the referrers)
   707  
   708  	pagination, err := entities.NewCursorPagination(ptr.From(int32(3)), nil, nil, nil, false)
   709  	require.NoError(t, err)
   710  	got, pageInfo, err := ts.ListRefereeHistory(ctx, referee.PartyID, pagination)
   711  	require.NoError(t, err)
   712  	want := historyForReferee(teamsHistory, referee.PartyID)[:3]
   713  	assert.Equal(t, want, got)
   714  	assert.Equal(t, entities.PageInfo{
   715  		HasNextPage:     true,
   716  		HasPreviousPage: false,
   717  		StartCursor:     want[0].Cursor().Encode(),
   718  		EndCursor:       want[len(want)-1].Cursor().Encode(),
   719  	}, pageInfo)
   720  }
   721  
   722  func testShouldReturnLastPageOfRefereeHistoryIfLastNRequested(t *testing.T) {
   723  	bs, ts, ps := setupTeamsTest(t)
   724  	ctx := tempTransaction(t)
   725  
   726  	teams, teamsHistory := setupTeams(t, ctx, bs, ps, ts)
   727  
   728  	referee := teamsHistory[len(teams)] // the first n elements (== len(teams) are the referrers)
   729  	refereeHistory := historyForReferee(teamsHistory, referee.PartyID)
   730  
   731  	pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(3)), nil, false)
   732  	require.NoError(t, err)
   733  	got, pageInfo, err := ts.ListRefereeHistory(ctx, referee.PartyID, pagination)
   734  	require.NoError(t, err)
   735  	want := refereeHistory[len(refereeHistory)-3:]
   736  	assert.Equal(t, want, got)
   737  	assert.Equal(t, entities.PageInfo{
   738  		HasNextPage:     false,
   739  		HasPreviousPage: true,
   740  		StartCursor:     want[0].Cursor().Encode(),
   741  		EndCursor:       want[len(want)-1].Cursor().Encode(),
   742  	}, pageInfo)
   743  }
   744  
   745  func testShouldReturnPageOfRefereeHistoryGivenPagination(t *testing.T) {
   746  	bs, ts, ps := setupTeamsTest(t)
   747  	ctx := tempTransaction(t)
   748  
   749  	teams, teamsHistory := setupTeams(t, ctx, bs, ps, ts)
   750  
   751  	referee := teamsHistory[len(teams)] // the first n elements (== len(teams) are the referrers)
   752  	refereeHistory := historyForReferee(teamsHistory, referee.PartyID)
   753  
   754  	t.Run("first after", func(t *testing.T) {
   755  		pagination, err := entities.NewCursorPagination(ptr.From(int32(3)), ptr.From(refereeHistory[2].Cursor().Encode()), nil, nil, false)
   756  		require.NoError(t, err)
   757  		got, pageInfo, err := ts.ListRefereeHistory(ctx, referee.PartyID, pagination)
   758  		require.NoError(t, err)
   759  		want := refereeHistory[3:6]
   760  		assert.Equal(t, want, got)
   761  		assert.Equal(t, entities.PageInfo{
   762  			HasNextPage:     true,
   763  			HasPreviousPage: true,
   764  			StartCursor:     want[0].Cursor().Encode(),
   765  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   766  		}, pageInfo)
   767  	})
   768  
   769  	t.Run("last before", func(t *testing.T) {
   770  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(3)), ptr.From(refereeHistory[7].Cursor().Encode()), false)
   771  		require.NoError(t, err)
   772  		got, pageInfo, err := ts.ListRefereeHistory(ctx, referee.PartyID, pagination)
   773  		require.NoError(t, err)
   774  		want := refereeHistory[4:7]
   775  		assert.Equal(t, want, got)
   776  		assert.Equal(t, entities.PageInfo{
   777  			HasNextPage:     true,
   778  			HasPreviousPage: true,
   779  			StartCursor:     want[0].Cursor().Encode(),
   780  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   781  		}, pageInfo)
   782  	})
   783  }
   784  
   785  func TestListTeamStatistics(t *testing.T) {
   786  	ctx := tempTransaction(t)
   787  
   788  	accountsStore := sqlstore.NewAccounts(connectionSource)
   789  	transfersStore := sqlstore.NewTransfers(connectionSource)
   790  	teamsStore := sqlstore.NewTeams(connectionSource)
   791  	blocksStore := sqlstore.NewBlocks(connectionSource)
   792  	rewardsStore := sqlstore.NewRewards(ctx, connectionSource)
   793  	epochStore := sqlstore.NewEpochs(connectionSource)
   794  
   795  	member11 := entities.PartyID(GenerateID())
   796  	member12 := entities.PartyID(GenerateID())
   797  	member21 := entities.PartyID(GenerateID())
   798  	member22 := entities.PartyID(GenerateID())
   799  	member31 := entities.PartyID(GenerateID())
   800  	member32 := entities.PartyID(GenerateID())
   801  	member41 := entities.PartyID(GenerateID())
   802  	member42 := entities.PartyID(GenerateID())
   803  
   804  	team1 := entities.TeamID(GenerateID())
   805  	team2 := entities.TeamID(GenerateID())
   806  	team3 := entities.TeamID(GenerateID())
   807  	team4 := entities.TeamID(GenerateID())
   808  
   809  	teams := map[entities.TeamID][]entities.PartyID{
   810  		team1: {member11, member12},
   811  		team2: {member21, member22},
   812  		team3: {member31, member32},
   813  		team4: {member41, member42},
   814  	}
   815  
   816  	teamIDs := []entities.TeamID{team1, team2, team3, team4}
   817  	gameIDs := []entities.GameID{
   818  		entities.GameID("11" + GenerateID()),
   819  		entities.GameID("22" + GenerateID()),
   820  		entities.GameID("33" + GenerateID()),
   821  		entities.GameID("44" + GenerateID()),
   822  	}
   823  
   824  	startTime := time.Now()
   825  
   826  	for _, gameID := range gameIDs {
   827  		fromAccount := &entities.Account{
   828  			PartyID:  entities.PartyID(GenerateID()),
   829  			AssetID:  entities.AssetID(GenerateID()),
   830  			MarketID: entities.MarketID(GenerateID()),
   831  			Type:     vegapb.AccountType_ACCOUNT_TYPE_GENERAL,
   832  			TxHash:   generateTxHash(),
   833  			VegaTime: startTime,
   834  		}
   835  		require.NoError(t, accountsStore.Add(ctx, fromAccount))
   836  
   837  		toAccount := &entities.Account{
   838  			PartyID:  entities.PartyID(GenerateID()),
   839  			AssetID:  entities.AssetID(GenerateID()),
   840  			MarketID: entities.MarketID(GenerateID()),
   841  			Type:     vegapb.AccountType_ACCOUNT_TYPE_GENERAL,
   842  			TxHash:   generateTxHash(),
   843  			VegaTime: startTime,
   844  		}
   845  		require.NoError(t, accountsStore.Add(ctx, toAccount))
   846  
   847  		require.NoError(t, transfersStore.Upsert(ctx, &entities.Transfer{
   848  			ID:            entities.TransferID(GenerateID()),
   849  			TxHash:        generateTxHash(),
   850  			VegaTime:      startTime,
   851  			FromAccountID: sqlstore.DeterministicIDFromAccount(fromAccount),
   852  			ToAccountID:   sqlstore.DeterministicIDFromAccount(toAccount),
   853  			TransferType:  2,
   854  			DispatchStrategy: &vegapb.DispatchStrategy{
   855  				EntityScope: 2,
   856  			},
   857  			GameID: gameID,
   858  		}))
   859  	}
   860  
   861  	for team, members := range teams {
   862  		require.NoError(t, teamsStore.AddTeam(ctx, &entities.Team{
   863  			ID:             team,
   864  			Referrer:       entities.PartyID(GenerateID()),
   865  			Name:           "Name",
   866  			Closed:         false,
   867  			CreatedAt:      startTime,
   868  			CreatedAtEpoch: 1,
   869  			VegaTime:       startTime,
   870  		}))
   871  
   872  		for _, member := range members {
   873  			require.NoError(t, teamsStore.RefereeJoinedTeam(ctx, &entities.TeamMember{
   874  				TeamID:        team,
   875  				PartyID:       member,
   876  				JoinedAt:      startTime,
   877  				JoinedAtEpoch: 1,
   878  				VegaTime:      startTime,
   879  			}))
   880  		}
   881  	}
   882  
   883  	for epoch := int64(1); epoch < 4; epoch++ {
   884  		blockTime := startTime.Add(time.Duration(epoch) * time.Minute).Truncate(time.Microsecond)
   885  		block := entities.Block{
   886  			VegaTime: blockTime,
   887  			Height:   epoch,
   888  			Hash:     []byte(vgcrypto.RandomHash()),
   889  		}
   890  		require.NoError(t, blocksStore.Add(ctx, block))
   891  		require.NoError(t, epochStore.Add(ctx, entities.Epoch{
   892  			ID:         epoch,
   893  			StartTime:  blockTime.Add(-1 * time.Minute),
   894  			ExpireTime: blockTime,
   895  			EndTime:    ptr.From(blockTime),
   896  			TxHash:     generateTxHash(),
   897  			VegaTime:   blockTime,
   898  			FirstBlock: ptr.From(epoch),
   899  			LastBlock:  ptr.From(epoch),
   900  		}))
   901  
   902  		j := 0
   903  		evt := &eventspb.TeamsStatsUpdated{
   904  			AtEpoch: uint64(epoch),
   905  		}
   906  		for _, teamID := range teamIDs {
   907  			membersStats := []*eventspb.TeamMemberStats{}
   908  
   909  			for i, member := range teams[teamID] {
   910  				membersStats = append(membersStats, &eventspb.TeamMemberStats{
   911  					PartyId:        string(member),
   912  					NotionalVolume: fmt.Sprintf("%d", i+j),
   913  				})
   914  			}
   915  			// Add some notional volume.
   916  			// It's done before the rewards as this is what will happen in the core as the
   917  			// MarketActivityTracker will send teams stats before the reward engine sends the
   918  			// the reward payout.
   919  			evt.Stats = append(evt.Stats, &eventspb.TeamStats{
   920  				TeamId:       string(teamID),
   921  				MembersStats: membersStats,
   922  			})
   923  
   924  			j += 1
   925  		}
   926  		require.NoError(t, teamsStore.TeamsStatsUpdated(ctx, evt))
   927  
   928  		seqNum := uint64(0)
   929  		for teamIdx, teamID := range teamIDs {
   930  			for _, member := range teams[teamID] {
   931  				seqNum += 1
   932  
   933  				require.NoError(t, rewardsStore.Add(ctx, entities.Reward{
   934  					PartyID:            member,
   935  					AssetID:            entities.AssetID(GenerateID()),
   936  					MarketID:           entities.MarketID(GenerateID()),
   937  					EpochID:            epoch,
   938  					Amount:             decimal.NewFromInt(int64(seqNum)),
   939  					QuantumAmount:      decimal.NewFromInt(epoch + int64(seqNum)),
   940  					PercentOfTotal:     0.1 * float64(epoch),
   941  					RewardType:         "NICE_BOY",
   942  					Timestamp:          blockTime,
   943  					TxHash:             generateTxHash(),
   944  					VegaTime:           blockTime,
   945  					SeqNum:             seqNum,
   946  					LockedUntilEpochID: epoch,
   947  					GameID:             ptr.From(gameIDs[teamIdx]),
   948  				}))
   949  			}
   950  		}
   951  
   952  		// Add non-game rewards to ensure we only account for game rewards.
   953  		for _, teamID := range teamIDs {
   954  			for _, member := range teams[teamID] {
   955  				seqNum += 1
   956  
   957  				require.NoError(t, rewardsStore.Add(ctx, entities.Reward{
   958  					PartyID:            member,
   959  					AssetID:            entities.AssetID(GenerateID()),
   960  					MarketID:           entities.MarketID(GenerateID()),
   961  					EpochID:            epoch,
   962  					Amount:             decimal.NewFromInt(int64(seqNum)),
   963  					QuantumAmount:      decimal.NewFromInt(epoch + int64(seqNum)),
   964  					PercentOfTotal:     0.1 * float64(epoch),
   965  					RewardType:         "NICE_BOY",
   966  					Timestamp:          blockTime,
   967  					TxHash:             generateTxHash(),
   968  					VegaTime:           blockTime.Add(time.Second),
   969  					SeqNum:             seqNum,
   970  					LockedUntilEpochID: epoch,
   971  				}))
   972  			}
   973  		}
   974  	}
   975  
   976  	t.Run("Getting all stats from the last 2 epochs", func(t *testing.T) {
   977  		stats, _, err := teamsStore.ListTeamsStatistics(ctx, entities.DefaultCursorPagination(false), sqlstore.ListTeamsStatisticsFilters{
   978  			AggregationEpochs: 2,
   979  		})
   980  
   981  		require.NoError(t, err)
   982  		expectedStats := []entities.TeamsStatistics{
   983  			{
   984  				TeamID:              team1,
   985  				TotalQuantumRewards: decimal.NewFromInt(16),
   986  				QuantumRewards: []entities.QuantumRewardsPerEpoch{
   987  					{
   988  						Epoch: 2,
   989  						Total: decimal.NewFromInt(3),
   990  					}, {
   991  						Epoch: 2,
   992  						Total: decimal.NewFromInt(4),
   993  					}, {
   994  						Epoch: 3,
   995  						Total: decimal.NewFromInt(4),
   996  					}, {
   997  						Epoch: 3,
   998  						Total: decimal.NewFromInt(5),
   999  					},
  1000  				},
  1001  				TotalQuantumVolumes: num.NewUint(2),
  1002  				QuantumVolumes: []entities.QuantumVolumesPerEpoch{
  1003  					{
  1004  						Epoch: 2,
  1005  						Total: num.NewUint(0),
  1006  					}, {
  1007  						Epoch: 2,
  1008  						Total: num.NewUint(1),
  1009  					}, {
  1010  						Epoch: 3,
  1011  						Total: num.NewUint(0),
  1012  					}, {
  1013  						Epoch: 3,
  1014  						Total: num.NewUint(1),
  1015  					},
  1016  				},
  1017  				TotalGamesPlayed: 1,
  1018  				GamesPlayed:      []entities.GameID{gameIDs[0]},
  1019  			},
  1020  			{
  1021  				TeamID:              team2,
  1022  				TotalQuantumRewards: decimal.NewFromInt(24),
  1023  				QuantumRewards: []entities.QuantumRewardsPerEpoch{
  1024  					{
  1025  						Epoch: 2,
  1026  						Total: decimal.NewFromInt(5),
  1027  					}, {
  1028  						Epoch: 2,
  1029  						Total: decimal.NewFromInt(6),
  1030  					}, {
  1031  						Epoch: 3,
  1032  						Total: decimal.NewFromInt(6),
  1033  					}, {
  1034  						Epoch: 3,
  1035  						Total: decimal.NewFromInt(7),
  1036  					},
  1037  				},
  1038  				TotalQuantumVolumes: num.NewUint(6),
  1039  				QuantumVolumes: []entities.QuantumVolumesPerEpoch{
  1040  					{
  1041  						Epoch: 2,
  1042  						Total: num.NewUint(1),
  1043  					}, {
  1044  						Epoch: 2,
  1045  						Total: num.NewUint(2),
  1046  					}, {
  1047  						Epoch: 3,
  1048  						Total: num.NewUint(1),
  1049  					}, {
  1050  						Epoch: 3,
  1051  						Total: num.NewUint(2),
  1052  					},
  1053  				},
  1054  				TotalGamesPlayed: 1,
  1055  				GamesPlayed:      []entities.GameID{gameIDs[1]},
  1056  			},
  1057  			{
  1058  				TeamID:              team3,
  1059  				TotalQuantumRewards: decimal.NewFromInt(32),
  1060  				QuantumRewards: []entities.QuantumRewardsPerEpoch{
  1061  					{
  1062  						Epoch: 2,
  1063  						Total: decimal.NewFromInt(7),
  1064  					}, {
  1065  						Epoch: 2,
  1066  						Total: decimal.NewFromInt(8),
  1067  					}, {
  1068  						Epoch: 3,
  1069  						Total: decimal.NewFromInt(8),
  1070  					}, {
  1071  						Epoch: 3,
  1072  						Total: decimal.NewFromInt(9),
  1073  					},
  1074  				},
  1075  				TotalQuantumVolumes: num.NewUint(10),
  1076  				QuantumVolumes: []entities.QuantumVolumesPerEpoch{
  1077  					{
  1078  						Epoch: 2,
  1079  						Total: num.NewUint(2),
  1080  					}, {
  1081  						Epoch: 2,
  1082  						Total: num.NewUint(3),
  1083  					}, {
  1084  						Epoch: 3,
  1085  						Total: num.NewUint(2),
  1086  					}, {
  1087  						Epoch: 3,
  1088  						Total: num.NewUint(3),
  1089  					},
  1090  				},
  1091  				TotalGamesPlayed: 1,
  1092  				GamesPlayed:      []entities.GameID{gameIDs[2]},
  1093  			},
  1094  			{
  1095  				TeamID:              team4,
  1096  				TotalQuantumRewards: decimal.NewFromInt(40),
  1097  				QuantumRewards: []entities.QuantumRewardsPerEpoch{
  1098  					{
  1099  						Epoch: 2,
  1100  						Total: decimal.NewFromInt(9),
  1101  					}, {
  1102  						Epoch: 2,
  1103  						Total: decimal.NewFromInt(10),
  1104  					}, {
  1105  						Epoch: 3,
  1106  						Total: decimal.NewFromInt(10),
  1107  					}, {
  1108  						Epoch: 3,
  1109  						Total: decimal.NewFromInt(11),
  1110  					},
  1111  				},
  1112  				TotalQuantumVolumes: num.NewUint(14),
  1113  				QuantumVolumes: []entities.QuantumVolumesPerEpoch{
  1114  					{
  1115  						Epoch: 2,
  1116  						Total: num.NewUint(3),
  1117  					}, {
  1118  						Epoch: 2,
  1119  						Total: num.NewUint(4),
  1120  					}, {
  1121  						Epoch: 3,
  1122  						Total: num.NewUint(3),
  1123  					}, {
  1124  						Epoch: 3,
  1125  						Total: num.NewUint(4),
  1126  					},
  1127  				},
  1128  				TotalGamesPlayed: 1,
  1129  				GamesPlayed:      []entities.GameID{gameIDs[3]},
  1130  			},
  1131  		}
  1132  		slices.SortStableFunc(expectedStats, func(a, b entities.TeamsStatistics) int {
  1133  			return strings.Compare(string(a.TeamID), string(b.TeamID))
  1134  		})
  1135  
  1136  		// Ugly hack to bypass deep-equal limitation with assert.Equal().
  1137  		expectedStatsJson, _ := json.Marshal(expectedStats)
  1138  		statsJson, _ := json.Marshal(stats)
  1139  		assert.JSONEq(t, string(expectedStatsJson), string(statsJson))
  1140  	})
  1141  
  1142  	t.Run("Getting stats from a given team from the last 2 epochs", func(t *testing.T) {
  1143  		stats, _, err := teamsStore.ListTeamsStatistics(ctx, entities.DefaultCursorPagination(false), sqlstore.ListTeamsStatisticsFilters{
  1144  			TeamID:            ptr.From(entities.TeamID(team1.String())),
  1145  			AggregationEpochs: 2,
  1146  		})
  1147  
  1148  		require.NoError(t, err)
  1149  		expectedStats := []entities.TeamsStatistics{
  1150  			{
  1151  				TeamID:              team1,
  1152  				TotalQuantumRewards: decimal.NewFromInt(16),
  1153  				QuantumRewards: []entities.QuantumRewardsPerEpoch{
  1154  					{
  1155  						Epoch: 2,
  1156  						Total: decimal.NewFromInt(3),
  1157  					}, {
  1158  						Epoch: 2,
  1159  						Total: decimal.NewFromInt(4),
  1160  					}, {
  1161  						Epoch: 3,
  1162  						Total: decimal.NewFromInt(4),
  1163  					}, {
  1164  						Epoch: 3,
  1165  						Total: decimal.NewFromInt(5),
  1166  					},
  1167  				},
  1168  				TotalQuantumVolumes: num.NewUint(2),
  1169  				QuantumVolumes: []entities.QuantumVolumesPerEpoch{
  1170  					{
  1171  						Epoch: 2,
  1172  						Total: num.NewUint(0),
  1173  					}, {
  1174  						Epoch: 2,
  1175  						Total: num.NewUint(1),
  1176  					}, {
  1177  						Epoch: 3,
  1178  						Total: num.NewUint(0),
  1179  					}, {
  1180  						Epoch: 3,
  1181  						Total: num.NewUint(1),
  1182  					},
  1183  				},
  1184  				TotalGamesPlayed: 1,
  1185  				GamesPlayed:      []entities.GameID{gameIDs[0]},
  1186  			},
  1187  		}
  1188  
  1189  		// Ugly hack to bypass deep-equal limitation with assert.Equal().
  1190  		expectedStatsJson, _ := json.Marshal(expectedStats)
  1191  		statsJson, _ := json.Marshal(stats)
  1192  		assert.JSONEq(t, string(expectedStatsJson), string(statsJson))
  1193  	})
  1194  
  1195  	t.Run("Getting all members stats from the last 2 epochs", func(t *testing.T) {
  1196  		stats, _, err := teamsStore.ListTeamMembersStatistics(ctx, entities.DefaultCursorPagination(false), sqlstore.ListTeamMembersStatisticsFilters{
  1197  			TeamID:            team2,
  1198  			AggregationEpochs: 2,
  1199  		})
  1200  
  1201  		require.NoError(t, err)
  1202  		expectedStats := []entities.TeamMembersStatistics{
  1203  			{
  1204  				PartyID:             member21,
  1205  				TotalQuantumRewards: decimal.NewFromInt(11),
  1206  				QuantumRewards: []entities.QuantumRewardsPerEpoch{
  1207  					{
  1208  						Epoch: 2,
  1209  						Total: decimal.NewFromInt(5),
  1210  					}, {
  1211  						Epoch: 3,
  1212  						Total: decimal.NewFromInt(6),
  1213  					},
  1214  				},
  1215  				TotalQuantumVolumes: num.NewUint(2),
  1216  				QuantumVolumes: []entities.QuantumVolumesPerEpoch{
  1217  					{
  1218  						Epoch: 2,
  1219  						Total: num.NewUint(1),
  1220  					}, {
  1221  						Epoch: 3,
  1222  						Total: num.NewUint(1),
  1223  					},
  1224  				},
  1225  				TotalGamesPlayed: 1,
  1226  				GamesPlayed:      []entities.GameID{gameIDs[1]},
  1227  			},
  1228  			{
  1229  				PartyID:             member22,
  1230  				TotalQuantumRewards: decimal.NewFromInt(13),
  1231  				QuantumRewards: []entities.QuantumRewardsPerEpoch{
  1232  					{
  1233  						Epoch: 2,
  1234  						Total: decimal.NewFromInt(6),
  1235  					}, {
  1236  						Epoch: 3,
  1237  						Total: decimal.NewFromInt(7),
  1238  					},
  1239  				},
  1240  				TotalQuantumVolumes: num.NewUint(4),
  1241  				QuantumVolumes: []entities.QuantumVolumesPerEpoch{
  1242  					{
  1243  						Epoch: 2,
  1244  						Total: num.NewUint(2),
  1245  					}, {
  1246  						Epoch: 3,
  1247  						Total: num.NewUint(2),
  1248  					},
  1249  				},
  1250  				TotalGamesPlayed: 1,
  1251  				GamesPlayed:      []entities.GameID{gameIDs[1]},
  1252  			},
  1253  		}
  1254  		slices.SortStableFunc(expectedStats, func(a, b entities.TeamMembersStatistics) int {
  1255  			return strings.Compare(string(a.PartyID), string(b.PartyID))
  1256  		})
  1257  
  1258  		// Ugly hack to bypass deep-equal limitation with assert.Equal().
  1259  		expectedStatsJson, _ := json.Marshal(expectedStats)
  1260  		statsJson, _ := json.Marshal(stats)
  1261  		assert.JSONEq(t, string(expectedStatsJson), string(statsJson))
  1262  	})
  1263  
  1264  	t.Run("Getting stats from a given party from the last 2 epochs", func(t *testing.T) {
  1265  		stats, _, err := teamsStore.ListTeamMembersStatistics(ctx, entities.DefaultCursorPagination(false), sqlstore.ListTeamMembersStatisticsFilters{
  1266  			TeamID:            team2,
  1267  			PartyID:           ptr.From(member21),
  1268  			AggregationEpochs: 2,
  1269  		})
  1270  
  1271  		require.NoError(t, err)
  1272  		expectedStats := []entities.TeamMembersStatistics{
  1273  			{
  1274  				PartyID:             member21,
  1275  				TotalQuantumRewards: decimal.NewFromInt(11),
  1276  				QuantumRewards: []entities.QuantumRewardsPerEpoch{
  1277  					{
  1278  						Epoch: 2,
  1279  						Total: decimal.NewFromInt(5),
  1280  					}, {
  1281  						Epoch: 3,
  1282  						Total: decimal.NewFromInt(6),
  1283  					},
  1284  				},
  1285  				TotalQuantumVolumes: num.NewUint(2),
  1286  				QuantumVolumes: []entities.QuantumVolumesPerEpoch{
  1287  					{
  1288  						Epoch: 2,
  1289  						Total: num.NewUint(1),
  1290  					}, {
  1291  						Epoch: 3,
  1292  						Total: num.NewUint(1),
  1293  					},
  1294  				},
  1295  				TotalGamesPlayed: 1,
  1296  				GamesPlayed:      []entities.GameID{gameIDs[1]},
  1297  			},
  1298  		}
  1299  
  1300  		// Ugly hack to bypass deep-equal limitation with assert.Equal().
  1301  		expectedStatsJson, _ := json.Marshal(expectedStats)
  1302  		statsJson, _ := json.Marshal(stats)
  1303  		assert.JSONEq(t, string(expectedStatsJson), string(statsJson))
  1304  	})
  1305  }