feat: api - GET /api/v2/versions/{versionCode}/servers/{serverKey}/players - new query param 'name' (#9)
Reviewed-on: twhelp/corev3#9
This commit is contained in:
parent
0be010ab50
commit
bf2b3b178c
|
@ -182,6 +182,7 @@ paths:
|
|||
- $ref: "#/components/parameters/LimitQueryParam"
|
||||
- $ref: "#/components/parameters/PlayerDeletedQueryParam"
|
||||
- $ref: "#/components/parameters/PlayerSortQueryParam"
|
||||
- $ref: "#/components/parameters/PlayerNameQueryParam"
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/ListPlayersResponse"
|
||||
|
@ -1141,6 +1142,16 @@ components:
|
|||
- deletedAt:ASC
|
||||
- deletedAt:DESC
|
||||
maxItems: 2
|
||||
PlayerNameQueryParam:
|
||||
name: name
|
||||
in: query
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 150
|
||||
maxItems: 100
|
||||
VersionCodePathParam:
|
||||
in: path
|
||||
name: versionCode
|
||||
|
|
|
@ -168,6 +168,10 @@ func (a listPlayersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
|||
q = q.Where("player.server_key IN (?)", bun.In(serverKeys))
|
||||
}
|
||||
|
||||
if names := a.params.Names(); len(names) > 0 {
|
||||
q = q.Where("player.name IN (?)", bun.In(names))
|
||||
}
|
||||
|
||||
if deleted := a.params.Deleted(); deleted.Valid {
|
||||
if deleted.V {
|
||||
q = q.Where("player.deleted_at IS NOT NULL")
|
||||
|
|
|
@ -357,7 +357,7 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
|
|||
serverKeys := params.ServerKeys()
|
||||
|
||||
players := res.Players()
|
||||
assert.NotEmpty(t, players)
|
||||
assert.Len(t, players, len(ids))
|
||||
for _, p := range players {
|
||||
assert.True(t, slices.Contains(ids, p.ID()))
|
||||
assert.True(t, slices.Contains(serverKeys, p.ServerKey()))
|
||||
|
@ -368,6 +368,41 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
|
|||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: names serverKeys",
|
||||
params: func(t *testing.T) domain.ListPlayersParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListPlayersParams()
|
||||
|
||||
res, err := repos.player.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, len(res.Players()))
|
||||
randPlayer := res.Players()[0]
|
||||
|
||||
require.NoError(t, params.SetNames([]string{randPlayer.Name()}))
|
||||
require.NoError(t, params.SetServerKeys([]string{randPlayer.ServerKey()}))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListPlayersParams, res domain.ListPlayersResult) {
|
||||
t.Helper()
|
||||
|
||||
names := params.Names()
|
||||
serverKeys := params.ServerKeys()
|
||||
|
||||
players := res.Players()
|
||||
assert.Len(t, players, len(names))
|
||||
for _, p := range players {
|
||||
assert.True(t, slices.Contains(names, p.Name()))
|
||||
assert.True(t, slices.Contains(serverKeys, p.ServerKey()))
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: deleted=true",
|
||||
params: func(t *testing.T) domain.ListPlayersParams {
|
||||
|
|
|
@ -449,7 +449,7 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories)
|
|||
serverKeys := params.ServerKeys()
|
||||
|
||||
tribes := res.Tribes()
|
||||
assert.NotEmpty(t, tribes)
|
||||
assert.Len(t, tribes, len(ids))
|
||||
for _, tr := range tribes {
|
||||
assert.True(t, slices.Contains(ids, tr.ID()))
|
||||
assert.True(t, slices.Contains(serverKeys, tr.ServerKey()))
|
||||
|
@ -484,7 +484,7 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories)
|
|||
serverKeys := params.ServerKeys()
|
||||
|
||||
tribes := res.Tribes()
|
||||
assert.NotEmpty(t, tribes)
|
||||
assert.Len(t, tribes, len(tags))
|
||||
for _, tr := range tribes {
|
||||
assert.True(t, slices.Contains(tags, tr.Tag()))
|
||||
assert.True(t, slices.Contains(serverKeys, tr.ServerKey()))
|
||||
|
|
|
@ -629,6 +629,7 @@ func (pc PlayerCursor) Encode() string {
|
|||
type ListPlayersParams struct {
|
||||
ids []int
|
||||
serverKeys []string
|
||||
names []string
|
||||
deleted NullBool
|
||||
sort []PlayerSort
|
||||
cursor PlayerCursor
|
||||
|
@ -680,6 +681,40 @@ func (params *ListPlayersParams) SetServerKeys(serverKeys []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (params *ListPlayersParams) Names() []string {
|
||||
return params.names
|
||||
}
|
||||
|
||||
const (
|
||||
playerNamesMinLength = 1
|
||||
playerNamesMaxLength = 100
|
||||
)
|
||||
|
||||
func (params *ListPlayersParams) SetNames(names []string) error {
|
||||
if err := validateSliceLen(names, playerNamesMinLength, playerNamesMaxLength); err != nil {
|
||||
return ValidationError{
|
||||
Model: listPlayersParamsModelName,
|
||||
Field: "names",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
for i, n := range names {
|
||||
if err := validateStringLen(n, playerNameMinLength, playerNameMaxLength); err != nil {
|
||||
return SliceElementValidationError{
|
||||
Model: listPlayersParamsModelName,
|
||||
Field: "names",
|
||||
Index: i,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.names = names
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListPlayersParams) Deleted() NullBool {
|
||||
return params.deleted
|
||||
}
|
||||
|
|
|
@ -474,6 +474,124 @@ func TestListPlayersParams_SetIDs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListPlayersParams_SetNames(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
names []string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
names: []string{
|
||||
gofakeit.LetterN(50),
|
||||
gofakeit.LetterN(50),
|
||||
gofakeit.LetterN(50),
|
||||
gofakeit.LetterN(50),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(names) < 1",
|
||||
args: args{
|
||||
names: nil,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListPlayersParams",
|
||||
Field: "names",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 100,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(names) > 100",
|
||||
args: args{
|
||||
names: func() []string {
|
||||
names := make([]string, 101)
|
||||
for i := range names {
|
||||
names[i] = gofakeit.LetterN(50)
|
||||
}
|
||||
return names
|
||||
}(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListPlayersParams",
|
||||
Field: "names",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 100,
|
||||
Current: 101,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(name[1]) < 1",
|
||||
args: args{
|
||||
names: []string{
|
||||
gofakeit.LetterN(50),
|
||||
"",
|
||||
gofakeit.LetterN(50),
|
||||
gofakeit.LetterN(50),
|
||||
},
|
||||
},
|
||||
expectedErr: domain.SliceElementValidationError{
|
||||
Model: "ListPlayersParams",
|
||||
Field: "names",
|
||||
Index: 1,
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 150,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(name[2]) > 150",
|
||||
args: args{
|
||||
names: []string{
|
||||
gofakeit.LetterN(50),
|
||||
gofakeit.LetterN(50),
|
||||
gofakeit.LetterN(151),
|
||||
gofakeit.LetterN(50),
|
||||
},
|
||||
},
|
||||
expectedErr: domain.SliceElementValidationError{
|
||||
Model: "ListPlayersParams",
|
||||
Field: "names",
|
||||
Index: 2,
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 150,
|
||||
Current: 151,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListPlayersParams()
|
||||
|
||||
require.ErrorIs(t, params.SetNames(tt.args.names), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.names, params.Names())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPlayersParams_SetSort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -35,6 +35,13 @@ func (h *apiHTTPHandler) ListPlayers(
|
|||
return
|
||||
}
|
||||
|
||||
if params.Name != nil {
|
||||
if err := domainParams.SetNames(*params.Name); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListPlayersErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if params.Deleted != nil {
|
||||
if err := domainParams.SetDeleted(domain.NullBool{
|
||||
V: *params.Deleted,
|
||||
|
@ -80,6 +87,12 @@ func formatListPlayersErrorPath(segments []errorPathSegment) []string {
|
|||
return []string{"$query", "limit"}
|
||||
case "deleted":
|
||||
return []string{"$query", "deleted"}
|
||||
case "names":
|
||||
path := []string{"$query", "name"}
|
||||
if segments[0].index >= 0 {
|
||||
path = append(path, strconv.Itoa(segments[0].index))
|
||||
}
|
||||
return path
|
||||
case "sort":
|
||||
path := []string{"$query", "sort"}
|
||||
if segments[0].index >= 0 {
|
||||
|
|
|
@ -176,6 +176,43 @@ func TestListPlayers(t *testing.T) {
|
|||
}))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: tag",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
|
||||
q := req.URL.Query()
|
||||
|
||||
for _, p := range players {
|
||||
if p.Server.Key == server.Key {
|
||||
q.Add("name", p.Name)
|
||||
}
|
||||
|
||||
if len(q["name"]) == 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
require.NotEmpty(t, q["name"])
|
||||
|
||||
req.URL.RawQuery = q.Encode()
|
||||
},
|
||||
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
||||
t.Helper()
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
// body
|
||||
body := decodeJSON[apimodel.ListPlayersResponse](t, resp.Body)
|
||||
assert.NotZero(t, body.Cursor.Self)
|
||||
assert.NotZero(t, body.Data)
|
||||
names := req.URL.Query()["name"]
|
||||
assert.Len(t, body.Data, len(names))
|
||||
for _, p := range body.Data {
|
||||
assert.True(t, slices.Contains(names, p.Name))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: deleted=false",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
|
@ -490,6 +527,118 @@ func TestListPlayers(t *testing.T) {
|
|||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(name) > 100",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
q := req.URL.Query()
|
||||
for range 101 {
|
||||
q.Add("name", gofakeit.LetterN(50))
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
},
|
||||
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
||||
t.Helper()
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
|
||||
// body
|
||||
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
||||
domainErr := domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 100,
|
||||
Current: len(req.URL.Query()["name"]),
|
||||
}
|
||||
assert.Equal(t, apimodel.ErrorResponse{
|
||||
Errors: []apimodel.Error{
|
||||
{
|
||||
Code: domainErr.Code(),
|
||||
Message: domainErr.Error(),
|
||||
Params: map[string]any{
|
||||
"current": float64(domainErr.Current),
|
||||
"max": float64(domainErr.Max),
|
||||
"min": float64(domainErr.Min),
|
||||
},
|
||||
Path: []string{"$query", "name"},
|
||||
},
|
||||
},
|
||||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(name[1]) < 1",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
q := req.URL.Query()
|
||||
q.Add("name", gofakeit.LetterN(50))
|
||||
q.Add("name", "")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
},
|
||||
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
||||
t.Helper()
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
|
||||
// body
|
||||
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
||||
domainErr := domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 150,
|
||||
Current: len(req.URL.Query()["name"][1]),
|
||||
}
|
||||
assert.Equal(t, apimodel.ErrorResponse{
|
||||
Errors: []apimodel.Error{
|
||||
{
|
||||
Code: domainErr.Code(),
|
||||
Message: domainErr.Error(),
|
||||
Params: map[string]any{
|
||||
"current": float64(domainErr.Current),
|
||||
"max": float64(domainErr.Max),
|
||||
"min": float64(domainErr.Min),
|
||||
},
|
||||
Path: []string{"$query", "name", "1"},
|
||||
},
|
||||
},
|
||||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(name[1]) > 150",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
q := req.URL.Query()
|
||||
q.Add("name", gofakeit.LetterN(50))
|
||||
q.Add("name", gofakeit.LetterN(151))
|
||||
req.URL.RawQuery = q.Encode()
|
||||
},
|
||||
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
||||
t.Helper()
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
|
||||
// body
|
||||
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
||||
domainErr := domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 150,
|
||||
Current: len(req.URL.Query()["name"][1]),
|
||||
}
|
||||
assert.Equal(t, apimodel.ErrorResponse{
|
||||
Errors: []apimodel.Error{
|
||||
{
|
||||
Code: domainErr.Code(),
|
||||
Message: domainErr.Error(),
|
||||
Params: map[string]any{
|
||||
"current": float64(domainErr.Current),
|
||||
"max": float64(domainErr.Max),
|
||||
"min": float64(domainErr.Min),
|
||||
},
|
||||
Path: []string{"$query", "name", "1"},
|
||||
},
|
||||
},
|
||||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: deleted is not a valid boolean",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user