feat: api - /api/v2/versions/{versionCode}/players - new query param "id"
ci/woodpecker/push/govulncheck Pipeline was successful Details
ci/woodpecker/push/test Pipeline was successful Details

This commit is contained in:
Dawid Wysokiński 2024-03-27 08:03:54 +01:00
parent 5cd6c33082
commit aeee5df100
Signed by: Kichiyaki
GPG Key ID: B5445E357FB8B892
11 changed files with 251 additions and 17 deletions

View File

@ -78,6 +78,7 @@ paths:
- $ref: "#/components/parameters/PlayerDeletedQueryParam"
- $ref: "#/components/parameters/PlayerSortQueryParam"
- $ref: "#/components/parameters/PlayerNameQueryParam"
- $ref: "#/components/parameters/PlayerIdQueryParam"
responses:
200:
$ref: "#/components/responses/ListPlayersWithServersResponse"
@ -214,6 +215,7 @@ paths:
- $ref: "#/components/parameters/PlayerDeletedQueryParam"
- $ref: "#/components/parameters/PlayerSortQueryParam"
- $ref: "#/components/parameters/PlayerNameQueryParam"
- $ref: "#/components/parameters/PlayerIdQueryParam"
responses:
200:
$ref: "#/components/responses/ListPlayersResponse"
@ -1766,6 +1768,13 @@ components:
minLength: 1
maxLength: 150
maxItems: 100
PlayerIdQueryParam:
in: query
name: id
schema:
type: array
items:
$ref: "#/components/schemas/IntId"
VillageCoordsQueryParam:
name: coords
in: query

View File

@ -213,7 +213,7 @@ func TestListEnnoblements(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
@ -821,7 +821,7 @@ func TestListPlayerEnnoblements(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
@ -1487,7 +1487,7 @@ func TestListTribeEnnoblements(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
@ -2140,7 +2140,7 @@ func TestListVillageEnnoblements(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()

View File

@ -63,6 +63,13 @@ func (h *apiHTTPHandler) ListVersionPlayers(
return
}
if params.Id != nil {
if err := domainParams.SetIDs(*params.Id); err != nil {
h.errorRenderer.withErrorPathFormatter(formatListPlayersErrorPath).render(w, r, err)
return
}
}
if params.Name != nil {
if err := domainParams.SetNames(*params.Name); err != nil {
h.errorRenderer.withErrorPathFormatter(formatListPlayersErrorPath).render(w, r, err)
@ -134,6 +141,13 @@ func (h *apiHTTPHandler) ListServerPlayers(
return
}
if params.Id != nil {
if err := domainParams.SetIDs(*params.Id); err != nil {
h.errorRenderer.withErrorPathFormatter(formatListPlayersErrorPath).render(w, r, err)
return
}
}
if params.Name != nil {
if err := domainParams.SetNames(*params.Name); err != nil {
h.errorRenderer.withErrorPathFormatter(formatListPlayersErrorPath).render(w, r, err)
@ -287,6 +301,7 @@ func playerFromContext(ctx context.Context) (domain.PlayerWithRelations, bool) {
return p, ok
}
//nolint:gocyclo
func formatListPlayersErrorPath(segments []domain.ErrorPathSegment) []string {
if segments[0].Model != "ListPlayersParams" {
return nil
@ -305,6 +320,12 @@ func formatListPlayersErrorPath(segments []domain.ErrorPathSegment) []string {
path = append(path, strconv.Itoa(segments[0].Index))
}
return path
case "ids":
path := []string{"$query", "id"}
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 {

View File

@ -147,7 +147,7 @@ func TestListPlayerPlayerSnapshots(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()

View File

@ -218,6 +218,43 @@ func TestListVersionPlayers(t *testing.T) {
}
},
},
{
name: "OK: id",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
for _, p := range players {
if p.Server.Version.Code == version.Code {
q.Add("id", strconv.Itoa(p.Id))
}
if len(q["id"]) == 2 {
break
}
}
require.NotEmpty(t, q["id"])
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.ListPlayersWithServersResponse](t, resp.Body)
assert.NotZero(t, body.Cursor.Self)
assert.NotZero(t, body.Data)
ids := req.URL.Query()["id"]
assert.Len(t, body.Data, len(ids))
for _, p := range body.Data {
assert.True(t, slices.Contains(ids, strconv.Itoa(p.Id)))
}
},
},
{
name: "OK: deleted=false",
reqModifier: func(t *testing.T, req *http.Request) {
@ -261,7 +298,7 @@ func TestListVersionPlayers(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
@ -570,6 +607,71 @@ func TestListVersionPlayers(t *testing.T) {
}, body)
},
},
{
name: "ERR: id is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
q.Set("id", "asd")
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)
assert.Equal(t, apimodel.ErrorResponse{
Errors: []apimodel.Error{
{
Code: "invalid-param-format",
Message: fmt.Sprintf(
"error setting array element: error binding string parameter: strconv.ParseInt: parsing \"%s\": invalid syntax",
req.URL.Query().Get("id"),
),
Path: []string{"$query", "id"},
},
},
}, body)
},
},
{
name: "ERR: id < 1",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
q.Set("id", "0")
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)
id, err := strconv.Atoi(req.URL.Query().Get("id"))
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
Min: 1,
Current: id,
}
assert.Equal(t, apimodel.ErrorResponse{
Errors: []apimodel.Error{
{
Code: apimodel.ErrorCode(domainErr.Code()),
Message: domainErr.Error(),
Params: map[string]any{
"min": float64(domainErr.Min),
"current": float64(domainErr.Current),
},
Path: []string{"$query", "id", "0"},
},
},
}, body)
},
},
{
name: "ERR: len(name) > 100",
reqModifier: func(t *testing.T, req *http.Request) {
@ -958,6 +1060,43 @@ func TestListServerPlayers(t *testing.T) {
}
},
},
{
name: "OK: id",
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("id", strconv.Itoa(p.Id))
}
if len(q["id"]) == 2 {
break
}
}
require.NotEmpty(t, q["id"])
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)
ids := req.URL.Query()["id"]
assert.Len(t, body.Data, len(ids))
for _, p := range body.Data {
assert.True(t, slices.Contains(ids, strconv.Itoa(p.Id)))
}
},
},
{
name: "OK: deleted=false",
reqModifier: func(t *testing.T, req *http.Request) {
@ -1001,7 +1140,7 @@ func TestListServerPlayers(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
@ -1310,6 +1449,71 @@ func TestListServerPlayers(t *testing.T) {
}, body)
},
},
{
name: "ERR: id is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
q.Set("id", "asd")
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)
assert.Equal(t, apimodel.ErrorResponse{
Errors: []apimodel.Error{
{
Code: "invalid-param-format",
Message: fmt.Sprintf(
"error setting array element: error binding string parameter: strconv.ParseInt: parsing \"%s\": invalid syntax",
req.URL.Query().Get("id"),
),
Path: []string{"$query", "id"},
},
},
}, body)
},
},
{
name: "ERR: id < 1",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
q.Set("id", "0")
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)
id, err := strconv.Atoi(req.URL.Query().Get("id"))
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
Min: 1,
Current: id,
}
assert.Equal(t, apimodel.ErrorResponse{
Errors: []apimodel.Error{
{
Code: apimodel.ErrorCode(domainErr.Code()),
Message: domainErr.Error(),
Params: map[string]any{
"min": float64(domainErr.Min),
"current": float64(domainErr.Current),
},
Path: []string{"$query", "id", "0"},
},
},
}, body)
},
},
{
name: "ERR: len(name) > 100",
reqModifier: func(t *testing.T, req *http.Request) {
@ -1660,7 +1864,7 @@ func TestListTribeMembers(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()

View File

@ -153,7 +153,7 @@ func TestListServers(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()

View File

@ -228,7 +228,7 @@ func TestListPlayerTribeChanges(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
@ -920,7 +920,7 @@ func TestListTribeMemberChanges(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()

View File

@ -147,7 +147,7 @@ func TestListTribeTribeSnapshots(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()

View File

@ -256,7 +256,7 @@ func TestListTribes(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()

View File

@ -101,7 +101,7 @@ func TestListVersions(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()

View File

@ -140,7 +140,7 @@ func TestListVillages(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
@ -584,7 +584,7 @@ func TestListPlayerVillages(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
@ -997,7 +997,7 @@ func TestListTribeVillages(t *testing.T) {
},
},
{
name: "ERR: limit is not a string",
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()