feat: tribe - sort by most points (#55)
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/tag/deployment Pipeline was successful
ci/woodpecker/cron/govulncheck Pipeline was successful

Reviewed-on: #55
This commit is contained in:
Dawid Wysokiński 2024-06-02 11:51:38 +00:00
parent 4db3fee03f
commit 51a6270812
8 changed files with 158 additions and 0 deletions

View File

@ -1794,6 +1794,8 @@ components:
- odScoreTotal:DESC - odScoreTotal:DESC
- points:ASC - points:ASC
- points:DESC - points:DESC
- mostPoints:ASC
- mostPoints:DESC
- dominance:ASC - dominance:ASC
- dominance:DESC - dominance:DESC
- deletedAt:ASC - deletedAt:ASC

View File

@ -241,6 +241,9 @@ func (a listTribesParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuer
case domain.TribeSortPointsASC, case domain.TribeSortPointsASC,
domain.TribeSortPointsDESC: domain.TribeSortPointsDESC:
el.value = cursor.Points() el.value = cursor.Points()
case domain.TribeSortMostPointsASC,
domain.TribeSortMostPointsDESC:
el.value = cursor.MostPoints()
case domain.TribeSortDominanceASC, case domain.TribeSortDominanceASC,
domain.TribeSortDominanceDESC: domain.TribeSortDominanceDESC:
el.value = cursor.Dominance() el.value = cursor.Dominance()
@ -286,6 +289,10 @@ func (a listTribesParamsApplier) sortToColumnAndDirection(
return "tribe.points", sortDirectionASC, nil return "tribe.points", sortDirectionASC, nil
case domain.TribeSortPointsDESC: case domain.TribeSortPointsDESC:
return "tribe.points", sortDirectionDESC, nil return "tribe.points", sortDirectionDESC, nil
case domain.TribeSortMostPointsASC:
return "tribe.most_points", sortDirectionASC, nil
case domain.TribeSortMostPointsDESC:
return "tribe.most_points", sortDirectionDESC, nil
case domain.TribeSortDominanceASC: case domain.TribeSortDominanceASC:
return "tribe.dominance", sortDirectionASC, nil return "tribe.dominance", sortDirectionASC, nil
case domain.TribeSortDominanceDESC: case domain.TribeSortDominanceDESC:

View File

@ -339,6 +339,56 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories)
})) }))
}, },
}, },
{
name: "OK: sort=[mostPoints ASC, serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListTribesParams {
t.Helper()
params := domain.NewListTribesParams()
require.NoError(t, params.SetSort([]domain.TribeSort{
domain.TribeSortMostPointsASC,
domain.TribeSortServerKeyASC,
domain.TribeSortIDASC,
}))
return params
},
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
t.Helper()
tribes := res.Tribes()
assert.NotEmpty(t, tribes)
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) 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.ListTribesParams {
t.Helper()
params := domain.NewListTribesParams()
require.NoError(t, params.SetSort([]domain.TribeSort{
domain.TribeSortMostPointsDESC,
domain.TribeSortServerKeyASC,
domain.TribeSortIDASC,
}))
return params
},
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
t.Helper()
tribes := res.Tribes()
assert.NotEmpty(t, tribes)
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) 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]", name: "OK: sort=[deletedAt ASC, serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListTribesParams { params: func(t *testing.T) domain.ListTribesParams {

View File

@ -20,6 +20,7 @@ type TribeCursorConfig struct {
ODScoreDef int ODScoreDef int
ODScoreTotal int ODScoreTotal int
Points int Points int
MostPoints int
Dominance float64 Dominance float64
DeletedAt time.Time DeletedAt time.Time
} }
@ -34,6 +35,7 @@ func NewTribeCursor(tb TestingTB, opts ...func(cfg *TribeCursorConfig)) domain.T
ODScoreDef: gofakeit.IntRange(0, math.MaxInt), ODScoreDef: gofakeit.IntRange(0, math.MaxInt),
ODScoreTotal: gofakeit.IntRange(0, math.MaxInt), ODScoreTotal: gofakeit.IntRange(0, math.MaxInt),
Points: gofakeit.IntRange(0, math.MaxInt), Points: gofakeit.IntRange(0, math.MaxInt),
MostPoints: gofakeit.IntRange(0, math.MaxInt),
Dominance: gofakeit.Float64Range(0.1, 99.9), Dominance: gofakeit.Float64Range(0.1, 99.9),
DeletedAt: time.Time{}, DeletedAt: time.Time{},
} }
@ -49,6 +51,7 @@ func NewTribeCursor(tb TestingTB, opts ...func(cfg *TribeCursorConfig)) domain.T
cfg.ODScoreDef, cfg.ODScoreDef,
cfg.ODScoreTotal, cfg.ODScoreTotal,
cfg.Points, cfg.Points,
cfg.MostPoints,
cfg.Dominance, cfg.Dominance,
cfg.DeletedAt, cfg.DeletedAt,
) )

View File

@ -207,6 +207,7 @@ func (t Tribe) ToCursor() (TribeCursor, error) {
t.od.scoreDef, t.od.scoreDef,
t.od.scoreTotal, t.od.scoreTotal,
t.points, t.points,
t.mostPoints,
t.dominance, t.dominance,
t.deletedAt, t.deletedAt,
) )
@ -472,6 +473,8 @@ const (
TribeSortODScoreTotalDESC TribeSortODScoreTotalDESC
TribeSortPointsASC TribeSortPointsASC
TribeSortPointsDESC TribeSortPointsDESC
TribeSortMostPointsASC
TribeSortMostPointsDESC
TribeSortDominanceASC TribeSortDominanceASC
TribeSortDominanceDESC TribeSortDominanceDESC
TribeSortDeletedAtASC TribeSortDeletedAtASC
@ -510,6 +513,10 @@ func (s TribeSort) String() string {
return "points:ASC" return "points:ASC"
case TribeSortPointsDESC: case TribeSortPointsDESC:
return "points:DESC" return "points:DESC"
case TribeSortMostPointsASC:
return "mostPoints:ASC"
case TribeSortMostPointsDESC:
return "mostPoints:DESC"
case TribeSortDominanceASC: case TribeSortDominanceASC:
return "dominance:ASC" return "dominance:ASC"
case TribeSortDominanceDESC: case TribeSortDominanceDESC:
@ -530,6 +537,7 @@ type TribeCursor struct {
odScoreDef int odScoreDef int
odScoreTotal int odScoreTotal int
points int points int
mostPoints int
dominance float64 dominance float64
deletedAt time.Time deletedAt time.Time
} }
@ -543,6 +551,7 @@ func NewTribeCursor(
odScoreDef int, odScoreDef int,
odScoreTotal int, odScoreTotal int,
points int, points int,
mostPoints int,
dominance float64, dominance float64,
deletedAt time.Time, deletedAt time.Time,
) (TribeCursor, error) { ) (TribeCursor, error) {
@ -594,6 +603,14 @@ func NewTribeCursor(
} }
} }
if err := validateIntInRange(mostPoints, 0, math.MaxInt); err != nil {
return TribeCursor{}, ValidationError{
Model: tribeCursorModelName,
Field: "mostPoints",
Err: err,
}
}
return TribeCursor{ return TribeCursor{
id: id, id: id,
serverKey: serverKey, serverKey: serverKey,
@ -601,6 +618,7 @@ func NewTribeCursor(
odScoreDef: odScoreDef, odScoreDef: odScoreDef,
odScoreTotal: odScoreTotal, odScoreTotal: odScoreTotal,
points: points, points: points,
mostPoints: mostPoints,
dominance: dominance, dominance: dominance,
deletedAt: deletedAt, deletedAt: deletedAt,
}, nil }, nil
@ -643,6 +661,11 @@ func decodeTribeCursor(encoded string) (TribeCursor, error) {
return TribeCursor{}, ErrInvalidCursor return TribeCursor{}, ErrInvalidCursor
} }
mostPoints, err := m.int("mostPoints")
if err != nil {
return TribeCursor{}, ErrInvalidCursor
}
dominance, err := m.float64("dominance") dominance, err := m.float64("dominance")
if err != nil { if err != nil {
return TribeCursor{}, ErrInvalidCursor return TribeCursor{}, ErrInvalidCursor
@ -660,6 +683,7 @@ func decodeTribeCursor(encoded string) (TribeCursor, error) {
odScoreDef, odScoreDef,
odScoreTotal, odScoreTotal,
points, points,
mostPoints,
dominance, dominance,
deletedAt, deletedAt,
) )
@ -694,6 +718,10 @@ func (tc TribeCursor) Points() int {
return tc.points return tc.points
} }
func (tc TribeCursor) MostPoints() int {
return tc.mostPoints
}
func (tc TribeCursor) Dominance() float64 { func (tc TribeCursor) Dominance() float64 {
return tc.dominance return tc.dominance
} }
@ -718,6 +746,7 @@ func (tc TribeCursor) Encode() string {
{"odScoreDef", tc.odScoreDef}, {"odScoreDef", tc.odScoreDef},
{"odScoreTotal", tc.odScoreTotal}, {"odScoreTotal", tc.odScoreTotal},
{"points", tc.points}, {"points", tc.points},
{"mostPoints", tc.mostPoints},
{"dominance", tc.dominance}, {"dominance", tc.dominance},
{"deletedAt", tc.deletedAt}, {"deletedAt", tc.deletedAt},
}) })

View File

@ -255,6 +255,13 @@ func TestTribeSort_IsInConflict(t *testing.T) {
}, },
expectedRes: true, expectedRes: true,
}, },
{
name: "OK: mostPoints:ASC mostPoints:DESC",
args: args{
sorts: [2]domain.TribeSort{domain.TribeSortMostPointsASC, domain.TribeSortMostPointsDESC},
},
expectedRes: true,
},
{ {
name: "OK: dominance:ASC dominance:DESC", name: "OK: dominance:ASC dominance:DESC",
args: args{ args: args{
@ -292,6 +299,7 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef int odScoreDef int
odScoreTotal int odScoreTotal int
points int points int
mostPoints int
dominance float64 dominance float64
deletedAt time.Time deletedAt time.Time
} }
@ -312,6 +320,7 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: validTribeCursor.ODScoreDef(), odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: validTribeCursor.ODScoreTotal(), odScoreTotal: validTribeCursor.ODScoreTotal(),
points: validTribeCursor.Points(), points: validTribeCursor.Points(),
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(), dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(), deletedAt: validTribeCursor.DeletedAt(),
}, },
@ -326,6 +335,7 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: validTribeCursor.ODScoreDef(), odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: validTribeCursor.ODScoreTotal(), odScoreTotal: validTribeCursor.ODScoreTotal(),
points: validTribeCursor.Points(), points: validTribeCursor.Points(),
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(), dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(), deletedAt: validTribeCursor.DeletedAt(),
}, },
@ -347,6 +357,7 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: validTribeCursor.ODScoreDef(), odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: validTribeCursor.ODScoreTotal(), odScoreTotal: validTribeCursor.ODScoreTotal(),
points: validTribeCursor.Points(), points: validTribeCursor.Points(),
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(), dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(), deletedAt: validTribeCursor.DeletedAt(),
}, },
@ -368,6 +379,7 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: -1, odScoreDef: -1,
odScoreTotal: validTribeCursor.ODScoreTotal(), odScoreTotal: validTribeCursor.ODScoreTotal(),
points: validTribeCursor.Points(), points: validTribeCursor.Points(),
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(), dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(), deletedAt: validTribeCursor.DeletedAt(),
}, },
@ -389,6 +401,7 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: validTribeCursor.ODScoreDef(), odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: -1, odScoreTotal: -1,
points: validTribeCursor.Points(), points: validTribeCursor.Points(),
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(), dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(), deletedAt: validTribeCursor.DeletedAt(),
}, },
@ -410,6 +423,7 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: validTribeCursor.ODScoreDef(), odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: validTribeCursor.ODScoreTotal(), odScoreTotal: validTribeCursor.ODScoreTotal(),
points: -1, points: -1,
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(), dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(), deletedAt: validTribeCursor.DeletedAt(),
}, },
@ -422,6 +436,28 @@ func TestNewTribeCursor(t *testing.T) {
}, },
}, },
}, },
{
name: "ERR: points < 0",
args: args{
id: validTribeCursor.ID(),
serverKey: validTribeCursor.ServerKey(),
odScoreAtt: validTribeCursor.ODScoreAtt(),
odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: validTribeCursor.ODScoreTotal(),
points: validTribeCursor.Points(),
mostPoints: -1,
dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "TribeCursor",
Field: "mostPoints",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
} }
for _, serverKeyTest := range newServerKeyValidationTests() { for _, serverKeyTest := range newServerKeyValidationTests() {
@ -434,6 +470,7 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: validTribeCursor.ODScoreDef(), odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: validTribeCursor.ODScoreTotal(), odScoreTotal: validTribeCursor.ODScoreTotal(),
points: validTribeCursor.Points(), points: validTribeCursor.Points(),
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(), dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(), deletedAt: validTribeCursor.DeletedAt(),
}, },
@ -456,6 +493,7 @@ func TestNewTribeCursor(t *testing.T) {
tt.args.odScoreDef, tt.args.odScoreDef,
tt.args.odScoreTotal, tt.args.odScoreTotal,
tt.args.points, tt.args.points,
tt.args.mostPoints,
tt.args.dominance, tt.args.dominance,
tt.args.deletedAt, tt.args.deletedAt,
) )
@ -469,6 +507,7 @@ func TestNewTribeCursor(t *testing.T) {
assert.Equal(t, tt.args.odScoreDef, tc.ODScoreDef()) assert.Equal(t, tt.args.odScoreDef, tc.ODScoreDef())
assert.Equal(t, tt.args.odScoreTotal, tc.ODScoreTotal()) assert.Equal(t, tt.args.odScoreTotal, tc.ODScoreTotal())
assert.Equal(t, tt.args.points, tc.Points()) assert.Equal(t, tt.args.points, tc.Points())
assert.Equal(t, tt.args.mostPoints, tc.MostPoints())
assert.InDelta(t, tt.args.dominance, tc.Dominance(), 0.001) assert.InDelta(t, tt.args.dominance, tc.Dominance(), 0.001)
assert.Equal(t, tt.args.deletedAt, tc.DeletedAt()) assert.Equal(t, tt.args.deletedAt, tc.DeletedAt())
assert.NotEmpty(t, tc.Encode()) assert.NotEmpty(t, tc.Encode())

View File

@ -22,6 +22,8 @@ var apiTribeSortAllowedValues = []domain.TribeSort{
domain.TribeSortODScoreTotalDESC, domain.TribeSortODScoreTotalDESC,
domain.TribeSortPointsASC, domain.TribeSortPointsASC,
domain.TribeSortPointsDESC, domain.TribeSortPointsDESC,
domain.TribeSortMostPointsASC,
domain.TribeSortMostPointsDESC,
domain.TribeSortDominanceASC, domain.TribeSortDominanceASC,
domain.TribeSortDominanceDESC, domain.TribeSortDominanceDESC,
domain.TribeSortDeletedAtASC, domain.TribeSortDeletedAtASC,

View File

@ -176,6 +176,32 @@ func TestListTribes(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.ListTribesResponse](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.Tribe) int {
return cmp.Or(
cmp.Compare(a.MostPoints, b.MostPoints)*-1,
cmp.Compare(a.Id, b.Id),
)
}))
},
},
{ {
name: "OK: tag", name: "OK: tag",
reqModifier: func(t *testing.T, req *http.Request) { reqModifier: func(t *testing.T, req *http.Request) {