feat: player - sort by most points (#54)
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful

Reviewed-on: #54
This commit is contained in:
Dawid Wysokiński 2024-06-02 11:33:50 +00:00
parent cf3eb9053d
commit 4db3fee03f
10 changed files with 2036 additions and 997 deletions

View File

@ -1834,6 +1834,8 @@ components:
- odScoreTotal:DESC
- points:ASC
- points:DESC
- mostPoints:ASC
- mostPoints:DESC
- deletedAt:ASC
- deletedAt:DESC
maxItems: 2

View File

@ -280,6 +280,9 @@ func (a listPlayersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQue
case domain.PlayerSortPointsASC,
domain.PlayerSortPointsDESC:
el.value = cursor.Points()
case domain.PlayerSortMostPointsASC,
domain.PlayerSortMostPointsDESC:
el.value = cursor.MostPoints()
case domain.PlayerSortDeletedAtASC,
domain.PlayerSortDeletedAtDESC:
el.value = cursor.DeletedAt()
@ -326,6 +329,10 @@ func (a listPlayersParamsApplier) sortToColumnAndDirection(
return "player.points", sortDirectionASC, nil
case domain.PlayerSortPointsDESC:
return "player.points", sortDirectionDESC, nil
case domain.PlayerSortMostPointsASC:
return "player.most_points", sortDirectionASC, nil
case domain.PlayerSortMostPointsDESC:
return "player.most_points", sortDirectionDESC, nil
case domain.PlayerSortDeletedAtASC:
return "COALESCE(player.deleted_at, '0001-01-01 00:00:00+00:00')", sortDirectionASC, nil
case domain.PlayerSortDeletedAtDESC:

View File

@ -276,6 +276,56 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
}))
},
},
{
name: "OK: sort=[mostPoints ASC, serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListPlayersParams {
t.Helper()
params := domain.NewListPlayersParams()
require.NoError(t, params.SetSort([]domain.PlayerSort{
domain.PlayerSortMostPointsASC,
domain.PlayerSortServerKeyASC,
domain.PlayerSortIDASC,
}))
return params
},
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
players := res.Players()
assert.NotEmpty(t, players)
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
return cmp.Or(
cmp.Compare(a.MostPoints(), b.MostPoints()),
cmp.Compare(a.ServerKey(), b.ServerKey()),
cmp.Compare(a.ID(), b.ID()),
)
}))
},
},
{
name: "OK: sort=[mostPoints DESC, serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListPlayersParams {
t.Helper()
params := domain.NewListPlayersParams()
require.NoError(t, params.SetSort([]domain.PlayerSort{
domain.PlayerSortMostPointsDESC,
domain.PlayerSortServerKeyASC,
domain.PlayerSortIDASC,
}))
return params
},
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
players := res.Players()
assert.NotEmpty(t, players)
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
return cmp.Or(
cmp.Compare(a.MostPoints(), b.MostPoints())*-1,
cmp.Compare(a.ServerKey(), b.ServerKey()),
cmp.Compare(a.ID(), b.ID()),
)
}))
},
},
{
name: "OK: sort=[deletedAt ASC, serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListPlayersParams {

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@ type PlayerCursorConfig struct {
ODScoreSup int
ODScoreTotal int
Points int
MostPoints int
DeletedAt time.Time
}
@ -31,6 +32,7 @@ func NewPlayerCursor(tb TestingTB, opts ...func(cfg *PlayerCursorConfig)) domain
ODScoreSup: gofakeit.IntRange(0, math.MaxInt),
ODScoreTotal: gofakeit.IntRange(0, math.MaxInt),
Points: gofakeit.IntRange(0, math.MaxInt),
MostPoints: gofakeit.IntRange(0, math.MaxInt),
DeletedAt: time.Time{},
}
@ -46,6 +48,7 @@ func NewPlayerCursor(tb TestingTB, opts ...func(cfg *PlayerCursorConfig)) domain
cfg.ODScoreSup,
cfg.ODScoreTotal,
cfg.Points,
cfg.MostPoints,
cfg.DeletedAt,
)
require.NoError(tb, err)

View File

@ -200,6 +200,7 @@ func (p Player) ToCursor() (PlayerCursor, error) {
p.od.scoreSup,
p.od.scoreTotal,
p.points,
p.mostPoints,
p.deletedAt,
)
}
@ -552,6 +553,8 @@ const (
PlayerSortODScoreTotalDESC
PlayerSortPointsASC
PlayerSortPointsDESC
PlayerSortMostPointsASC
PlayerSortMostPointsDESC
PlayerSortDeletedAtASC
PlayerSortDeletedAtDESC
)
@ -592,6 +595,10 @@ func (s PlayerSort) String() string {
return "points:ASC"
case PlayerSortPointsDESC:
return "points:DESC"
case PlayerSortMostPointsASC:
return "mostPoints:ASC"
case PlayerSortMostPointsDESC:
return "mostPoints:DESC"
case PlayerSortDeletedAtASC:
return "deletedAt:ASC"
case PlayerSortDeletedAtDESC:
@ -609,6 +616,7 @@ type PlayerCursor struct {
odScoreSup int
odScoreTotal int
points int
mostPoints int
deletedAt time.Time
}
@ -622,6 +630,7 @@ func NewPlayerCursor(
odScoreSup int,
odScoreTotal int,
points int,
mostPoints int,
deletedAt time.Time,
) (PlayerCursor, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
@ -680,6 +689,14 @@ func NewPlayerCursor(
}
}
if err := validateIntInRange(mostPoints, 0, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "mostPoints",
Err: err,
}
}
return PlayerCursor{
id: id,
serverKey: serverKey,
@ -688,6 +705,7 @@ func NewPlayerCursor(
odScoreSup: odScoreSup,
odScoreTotal: odScoreTotal,
points: points,
mostPoints: mostPoints,
deletedAt: deletedAt,
}, nil
}
@ -734,6 +752,11 @@ func decodePlayerCursor(encoded string) (PlayerCursor, error) {
return PlayerCursor{}, ErrInvalidCursor
}
mostPoints, err := m.int("mostPoints")
if err != nil {
return PlayerCursor{}, ErrInvalidCursor
}
deletedAt, err := m.time("deletedAt")
if err != nil {
return PlayerCursor{}, ErrInvalidCursor
@ -747,6 +770,7 @@ func decodePlayerCursor(encoded string) (PlayerCursor, error) {
odScoreSup,
odScoreTotal,
points,
mostPoints,
deletedAt,
)
if err != nil {
@ -784,6 +808,10 @@ func (pc PlayerCursor) Points() int {
return pc.points
}
func (pc PlayerCursor) MostPoints() int {
return pc.mostPoints
}
func (pc PlayerCursor) DeletedAt() time.Time {
return pc.deletedAt
}
@ -805,6 +833,7 @@ func (pc PlayerCursor) Encode() string {
{"odScoreSup", pc.odScoreSup},
{"odScoreTotal", pc.odScoreTotal},
{"points", pc.points},
{"mostPoints", pc.mostPoints},
{"deletedAt", pc.deletedAt},
})
}

View File

@ -303,6 +303,13 @@ func TestPlayerSort_IsInConflict(t *testing.T) {
},
expectedRes: true,
},
{
name: "OK: mostPoints:ASC mostPoints:DESC",
args: args{
sorts: [2]domain.PlayerSort{domain.PlayerSortMostPointsASC, domain.PlayerSortMostPointsDESC},
},
expectedRes: true,
},
{
name: "OK: deletedAt:ASC deletedAt:DESC",
args: args{
@ -334,6 +341,7 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup int
odScoreTotal int
points int
mostPoints int
deletedAt time.Time
}
@ -354,6 +362,7 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: nil,
@ -368,6 +377,7 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
@ -389,6 +399,7 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
@ -410,6 +421,7 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
@ -431,6 +443,7 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: -1,
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
@ -452,6 +465,7 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: -1,
points: validPlayerCursor.Points(),
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
@ -473,6 +487,7 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: -1,
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
@ -484,6 +499,28 @@ func TestNewPlayerCursor(t *testing.T) {
},
},
},
{
name: "ERR: mostPoints < 0",
args: args{
id: validPlayerCursor.ID(),
serverKey: validPlayerCursor.ServerKey(),
odScoreAtt: validPlayerCursor.ODScoreAtt(),
odScoreDef: validPlayerCursor.ODScoreDef(),
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
mostPoints: -1,
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "mostPoints",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
}
for _, serverKeyTest := range newServerKeyValidationTests() {
@ -518,6 +555,7 @@ func TestNewPlayerCursor(t *testing.T) {
tt.args.odScoreSup,
tt.args.odScoreTotal,
tt.args.points,
tt.args.mostPoints,
tt.args.deletedAt,
)
require.ErrorIs(t, err, tt.expectedErr)
@ -531,6 +569,7 @@ func TestNewPlayerCursor(t *testing.T) {
assert.Equal(t, tt.args.odScoreSup, pc.ODScoreSup())
assert.Equal(t, tt.args.odScoreTotal, pc.ODScoreTotal())
assert.Equal(t, tt.args.points, pc.Points())
assert.Equal(t, tt.args.mostPoints, pc.MostPoints())
assert.Equal(t, tt.args.deletedAt, pc.DeletedAt())
assert.NotEmpty(t, pc.Encode())
})

View File

@ -24,6 +24,8 @@ var apiPlayerSortAllowedValues = []domain.PlayerSort{
domain.PlayerSortODScoreTotalDESC,
domain.PlayerSortPointsASC,
domain.PlayerSortPointsDESC,
domain.PlayerSortMostPointsASC,
domain.PlayerSortMostPointsDESC,
domain.PlayerSortDeletedAtASC,
domain.PlayerSortDeletedAtDESC,
}

View File

@ -181,6 +181,33 @@ func TestListVersionPlayers(t *testing.T) {
}))
},
},
{
name: "OK: sort=[mostPoints:DESC]",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
q.Set("sort", "mostPoints:DESC")
req.URL.RawQuery = q.Encode()
},
assertResp: func(t *testing.T, _ *http.Request, resp *http.Response) {
t.Helper()
assert.Equal(t, http.StatusOK, resp.StatusCode)
// body
body := decodeJSON[apimodel.ListPlayersWithServersResponse](t, resp.Body)
assert.Zero(t, body.Cursor.Next)
assert.NotZero(t, body.Cursor.Self)
assert.NotZero(t, body.Data)
assert.True(t, slices.IsSortedFunc(body.Data, func(a, b apimodel.PlayerWithServer) int {
return cmp.Or(
cmp.Compare(a.Server.Key, b.Server.Key),
cmp.Compare(a.MostPoints, b.MostPoints)*-1,
cmp.Compare(a.Id, b.Id),
)
}))
},
},
{
name: "OK: name",
reqModifier: func(t *testing.T, req *http.Request) {
@ -1023,6 +1050,32 @@ func TestListServerPlayers(t *testing.T) {
}))
},
},
{
name: "OK: sort=[mostPoints:DESC]",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
q.Set("sort", "mostPoints:DESC")
req.URL.RawQuery = q.Encode()
},
assertResp: func(t *testing.T, _ *http.Request, resp *http.Response) {
t.Helper()
assert.Equal(t, http.StatusOK, resp.StatusCode)
// body
body := decodeJSON[apimodel.ListPlayersWithServersResponse](t, resp.Body)
assert.Zero(t, body.Cursor.Next)
assert.NotZero(t, body.Cursor.Self)
assert.NotZero(t, body.Data)
assert.True(t, slices.IsSortedFunc(body.Data, func(a, b apimodel.PlayerWithServer) int {
return cmp.Or(
cmp.Compare(a.MostPoints, b.MostPoints)*-1,
cmp.Compare(a.Id, b.Id),
)
}))
},
},
{
name: "OK: name",
reqModifier: func(t *testing.T, req *http.Request) {
@ -1863,6 +1916,32 @@ func TestListTribeMembers(t *testing.T) {
}))
},
},
{
name: "OK: sort=[mostPoints:DESC]",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
q.Set("sort", "mostPoints:DESC")
req.URL.RawQuery = q.Encode()
},
assertResp: func(t *testing.T, _ *http.Request, resp *http.Response) {
t.Helper()
assert.Equal(t, http.StatusOK, resp.StatusCode)
// body
body := decodeJSON[apimodel.ListPlayersWithServersResponse](t, resp.Body)
assert.Zero(t, body.Cursor.Next)
assert.NotZero(t, body.Cursor.Self)
assert.NotZero(t, body.Data)
assert.True(t, slices.IsSortedFunc(body.Data, func(a, b apimodel.PlayerWithServer) int {
return cmp.Or(
cmp.Compare(a.MostPoints, b.MostPoints)*-1,
cmp.Compare(a.Id, b.Id),
)
}))
},
},
{
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {

File diff suppressed because it is too large Load Diff