feat: api - /api/v2/versions/{versionCode}/servers/{serverKey}/ennoblements - new query params #23
|
@ -317,6 +317,9 @@ paths:
|
|||
- $ref: "#/components/parameters/ServerKeyPathParam"
|
||||
- $ref: "#/components/parameters/CursorQueryParam"
|
||||
- $ref: "#/components/parameters/LimitQueryParam"
|
||||
- $ref: "#/components/parameters/EnnoblementSortQueryParam"
|
||||
- $ref: "#/components/parameters/SinceQueryParam"
|
||||
- $ref: "#/components/parameters/BeforeQueryParam"
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/ListEnnoblementsResponse"
|
||||
|
@ -1426,6 +1429,34 @@ components:
|
|||
type: string
|
||||
example: 500|500
|
||||
maxItems: 200
|
||||
EnnoblementSortQueryParam:
|
||||
name: sort
|
||||
in: query
|
||||
description: Order matters!
|
||||
schema:
|
||||
type: array
|
||||
default:
|
||||
- createdAt:ASC
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- createdAt:ASC
|
||||
- createdAt:DESC
|
||||
maxItems: 1
|
||||
SinceQueryParam:
|
||||
name: since
|
||||
in: query
|
||||
description: only items created since the provided time are returned, this is a timestamp in RFC 3339 format
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
BeforeQueryParam:
|
||||
name: before
|
||||
in: query
|
||||
description: only items created before the provided time are returned, this is a timestamp in RFC 3339 format
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
VersionCodePathParam:
|
||||
in: path
|
||||
name: versionCode
|
||||
|
|
|
@ -118,6 +118,32 @@ func (a listEnnoblementsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuer
|
|||
q = q.Where("ennoblement.server_key IN (?)", bun.In(serverKeys))
|
||||
}
|
||||
|
||||
if villageIDs := a.params.PlayerIDs(); len(villageIDs) > 0 {
|
||||
q = q.Where("ennoblement.village_id IN (?)", bun.In(villageIDs))
|
||||
}
|
||||
|
||||
if playerIDs := a.params.PlayerIDs(); len(playerIDs) > 0 {
|
||||
q = q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
return q.Where("ennoblement.new_owner_id IN (?)", bun.In(playerIDs)).
|
||||
WhereOr("ennoblement.old_owner_id IN (?)", bun.In(playerIDs))
|
||||
})
|
||||
}
|
||||
|
||||
if tribeIDs := a.params.TribeIDs(); len(tribeIDs) > 0 {
|
||||
q = q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
return q.Where("ennoblement.new_tribe_id IN (?)", bun.In(tribeIDs)).
|
||||
WhereOr("ennoblement.old_tribe_id IN (?)", bun.In(tribeIDs))
|
||||
})
|
||||
}
|
||||
|
||||
if since := a.params.Since(); since.Valid {
|
||||
q = q.Where("ennoblement.created_at >= ?", since.V)
|
||||
}
|
||||
|
||||
if before := a.params.Before(); before.Valid {
|
||||
q = q.Where("ennoblement.created_at < ?", before.V)
|
||||
}
|
||||
|
||||
for _, s := range a.params.Sort() {
|
||||
switch s {
|
||||
case domain.EnnoblementSortCreatedAtASC:
|
||||
|
|
|
@ -128,7 +128,6 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
params func(t *testing.T) domain.ListEnnoblementsParams
|
||||
assertResult func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult)
|
||||
assertError func(t *testing.T, err error)
|
||||
assertTotal func(t *testing.T, params domain.ListEnnoblementsParams, total int)
|
||||
}{
|
||||
{
|
||||
name: "OK: default params",
|
||||
|
@ -154,10 +153,6 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, _ domain.ListEnnoblementsParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[serverKey DESC, createdAt DESC, id ASC]",
|
||||
|
@ -187,10 +182,6 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, _ domain.ListEnnoblementsParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[id ASC]",
|
||||
|
@ -214,10 +205,6 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, _ domain.ListEnnoblementsParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[id DESC]",
|
||||
|
@ -241,10 +228,6 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, _ domain.ListEnnoblementsParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: serverKeys",
|
||||
|
@ -277,9 +260,162 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, _ domain.ListEnnoblementsParams, total int) {
|
||||
},
|
||||
{
|
||||
name: "OK: villageIDs serverKeys",
|
||||
params: func(t *testing.T) domain.ListEnnoblementsParams {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
|
||||
res, err := repos.ennoblement.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res.Ennoblements())
|
||||
randEnnoblement := res.Ennoblements()[0]
|
||||
|
||||
require.NoError(t, params.SetServerKeys([]string{randEnnoblement.ServerKey()}))
|
||||
require.NoError(t, params.SetVillageIDs([]int{randEnnoblement.VillageID()}))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
|
||||
serverKeys := params.ServerKeys()
|
||||
villageIDs := params.VillageIDs()
|
||||
|
||||
ennoblements := res.Ennoblements()
|
||||
assert.NotZero(t, ennoblements)
|
||||
for _, e := range ennoblements {
|
||||
assert.True(t, slices.Contains(serverKeys, e.ServerKey()))
|
||||
assert.True(t, slices.Contains(villageIDs, e.VillageID()))
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: playerIDs (new owner) serverKeys",
|
||||
params: func(t *testing.T) domain.ListEnnoblementsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
|
||||
res, err := repos.ennoblement.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res.Ennoblements())
|
||||
randEnnoblement := res.Ennoblements()[0]
|
||||
|
||||
require.NoError(t, params.SetServerKeys([]string{randEnnoblement.ServerKey()}))
|
||||
require.NoError(t, params.SetPlayerIDs([]int{randEnnoblement.NewOwnerID()}))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
|
||||
serverKeys := params.ServerKeys()
|
||||
playerIDs := params.PlayerIDs()
|
||||
|
||||
ennoblements := res.Ennoblements()
|
||||
assert.NotZero(t, ennoblements)
|
||||
for _, e := range ennoblements {
|
||||
assert.True(t, slices.Contains(serverKeys, e.ServerKey()))
|
||||
assert.True(t, slices.Contains(playerIDs, e.NewOwnerID()) || slices.Contains(playerIDs, e.OldOwnerID()))
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: playerIDs (old owner) serverKeys",
|
||||
params: func(t *testing.T) domain.ListEnnoblementsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
|
||||
res, err := repos.ennoblement.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res.Ennoblements())
|
||||
|
||||
var randEnnoblement domain.Ennoblement
|
||||
for _, e := range res.Ennoblements() {
|
||||
if e.OldOwnerID() > 0 {
|
||||
randEnnoblement = e
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
require.NoError(t, params.SetServerKeys([]string{randEnnoblement.ServerKey()}))
|
||||
require.NoError(t, params.SetPlayerIDs([]int{randEnnoblement.OldOwnerID()}))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
|
||||
serverKeys := params.ServerKeys()
|
||||
playerIDs := params.PlayerIDs()
|
||||
|
||||
ennoblements := res.Ennoblements()
|
||||
assert.NotZero(t, ennoblements)
|
||||
for _, e := range ennoblements {
|
||||
assert.True(t, slices.Contains(serverKeys, e.ServerKey()))
|
||||
assert.True(t, slices.Contains(playerIDs, e.NewOwnerID()) || slices.Contains(playerIDs, e.OldOwnerID()))
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: since before",
|
||||
params: func(t *testing.T) domain.ListEnnoblementsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
require.NoError(t, params.SetSort([]domain.EnnoblementSort{
|
||||
domain.EnnoblementSortCreatedAtASC,
|
||||
domain.EnnoblementSortIDASC,
|
||||
}))
|
||||
|
||||
res, err := repos.ennoblement.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.GreaterOrEqual(t, len(res.Ennoblements()), 4)
|
||||
|
||||
params = domain.NewListEnnoblementsParams()
|
||||
require.NoError(t, params.SetSince(domain.NullTime{
|
||||
V: res.Ennoblements()[1].CreatedAt(),
|
||||
Valid: true,
|
||||
}))
|
||||
require.NoError(t, params.SetBefore(domain.NullTime{
|
||||
V: res.Ennoblements()[3].CreatedAt(),
|
||||
Valid: true,
|
||||
}))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
|
||||
since := params.Since().V
|
||||
before := params.Before().V
|
||||
|
||||
ennoblements := res.Ennoblements()
|
||||
assert.NotZero(t, ennoblements)
|
||||
for _, e := range ennoblements {
|
||||
assert.True(t, e.CreatedAt().After(since) || e.CreatedAt().Equal(since))
|
||||
assert.True(t, e.CreatedAt().Before(before))
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -432,10 +568,6 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, _ domain.ListEnnoblementsParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -230,6 +231,23 @@ const (
|
|||
EnnoblementSortServerKeyDESC
|
||||
)
|
||||
|
||||
func newEnnoblementSortFromString(s string) (EnnoblementSort, error) {
|
||||
allowed := []EnnoblementSort{
|
||||
EnnoblementSortCreatedAtASC,
|
||||
EnnoblementSortCreatedAtDESC,
|
||||
}
|
||||
|
||||
for _, a := range allowed {
|
||||
if strings.EqualFold(a.String(), s) {
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, UnsupportedSortStringError{
|
||||
Sort: s,
|
||||
}
|
||||
}
|
||||
|
||||
// IsInConflict returns true if two sorts can't be used together (e.g. EnnoblementSortIDASC and EnnoblementSortIDDESC).
|
||||
func (s EnnoblementSort) IsInConflict(s2 EnnoblementSort) bool {
|
||||
ss := []EnnoblementSort{s, s2}
|
||||
|
@ -354,6 +372,11 @@ func (ec EnnoblementCursor) Encode() string {
|
|||
|
||||
type ListEnnoblementsParams struct {
|
||||
serverKeys []string
|
||||
villageIDs []int
|
||||
playerIDs []int // Ennoblement.NewOwnerID or Ennoblement.OldOwnerID
|
||||
tribeIDs []int // Ennoblement.NewTribeID or Ennoblement.OldTribeID
|
||||
since NullTime
|
||||
before NullTime
|
||||
sort []EnnoblementSort
|
||||
cursor EnnoblementCursor
|
||||
limit int
|
||||
|
@ -396,6 +419,87 @@ func (params *ListEnnoblementsParams) SetServerKeys(serverKeys []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) VillageIDs() []int {
|
||||
return params.villageIDs
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) SetVillageIDs(villageIDs []int) error {
|
||||
for i, id := range villageIDs {
|
||||
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||
return SliceElementValidationError{
|
||||
Model: listEnnoblementsParamsModelName,
|
||||
Field: "villageIDs",
|
||||
Index: i,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.villageIDs = villageIDs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) PlayerIDs() []int {
|
||||
return params.playerIDs
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) SetPlayerIDs(playerIDs []int) error {
|
||||
for i, id := range playerIDs {
|
||||
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||
return SliceElementValidationError{
|
||||
Model: listEnnoblementsParamsModelName,
|
||||
Field: "playerIDs",
|
||||
Index: i,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.playerIDs = playerIDs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) TribeIDs() []int {
|
||||
return params.tribeIDs
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) SetTribeIDs(tribeIDs []int) error {
|
||||
for i, id := range tribeIDs {
|
||||
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||
return SliceElementValidationError{
|
||||
Model: listEnnoblementsParamsModelName,
|
||||
Field: "tribeIDs",
|
||||
Index: i,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.tribeIDs = tribeIDs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) Since() NullTime {
|
||||
return params.since
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) SetSince(since NullTime) error {
|
||||
params.since = since
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) Before() NullTime {
|
||||
return params.before
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) SetBefore(before NullTime) error {
|
||||
params.before = before
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) Sort() []EnnoblementSort {
|
||||
return params.sort
|
||||
}
|
||||
|
@ -419,6 +523,37 @@ func (params *ListEnnoblementsParams) SetSort(sort []EnnoblementSort) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) PrependSortString(sort []string) error {
|
||||
if err := validateSliceLen(
|
||||
sort,
|
||||
ennoblementSortMinLength,
|
||||
max(ennoblementSortMaxLength-len(params.sort), 0),
|
||||
); err != nil {
|
||||
return ValidationError{
|
||||
Model: listEnnoblementsParamsModelName,
|
||||
Field: "sort",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
toPrepend := make([]EnnoblementSort, 0, len(sort))
|
||||
|
||||
for i, s := range sort {
|
||||
converted, err := newEnnoblementSortFromString(s)
|
||||
if err != nil {
|
||||
return SliceElementValidationError{
|
||||
Model: listEnnoblementsParamsModelName,
|
||||
Field: "sort",
|
||||
Index: i,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
toPrepend = append(toPrepend, converted)
|
||||
}
|
||||
|
||||
return params.SetSort(append(toPrepend, params.sort...))
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) Cursor() EnnoblementCursor {
|
||||
return params.cursor
|
||||
}
|
||||
|
|
|
@ -239,6 +239,186 @@ func TestListEnnoblementsParams_SetServerKeys(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListEnnoblementsParams_SetVillageIDs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
villageIDs []int
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
villageIDs: []int{
|
||||
domaintest.RandID(),
|
||||
domaintest.RandID(),
|
||||
domaintest.RandID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: value < 1",
|
||||
args: args{
|
||||
villageIDs: []int{
|
||||
domaintest.RandID(),
|
||||
domaintest.RandID(),
|
||||
domaintest.RandID(),
|
||||
0,
|
||||
domaintest.RandID(),
|
||||
},
|
||||
},
|
||||
expectedErr: domain.SliceElementValidationError{
|
||||
Model: "ListEnnoblementsParams",
|
||||
Field: "villageIDs",
|
||||
Index: 3,
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
|
||||
require.ErrorIs(t, params.SetVillageIDs(tt.args.villageIDs), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.villageIDs, params.VillageIDs())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListEnnoblementsParams_SetPlayerIDs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
playerIDs []int
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
playerIDs: []int{
|
||||
domaintest.RandID(),
|
||||
domaintest.RandID(),
|
||||
domaintest.RandID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: value < 1",
|
||||
args: args{
|
||||
playerIDs: []int{
|
||||
domaintest.RandID(),
|
||||
domaintest.RandID(),
|
||||
domaintest.RandID(),
|
||||
0,
|
||||
domaintest.RandID(),
|
||||
},
|
||||
},
|
||||
expectedErr: domain.SliceElementValidationError{
|
||||
Model: "ListEnnoblementsParams",
|
||||
Field: "playerIDs",
|
||||
Index: 3,
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
|
||||
require.ErrorIs(t, params.SetPlayerIDs(tt.args.playerIDs), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.playerIDs, params.PlayerIDs())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListEnnoblementsParams_SetTribeIDs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
tribeIDs []int
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
tribeIDs: []int{
|
||||
domaintest.RandID(),
|
||||
domaintest.RandID(),
|
||||
domaintest.RandID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: value < 1",
|
||||
args: args{
|
||||
tribeIDs: []int{
|
||||
domaintest.RandID(),
|
||||
domaintest.RandID(),
|
||||
domaintest.RandID(),
|
||||
0,
|
||||
domaintest.RandID(),
|
||||
},
|
||||
},
|
||||
expectedErr: domain.SliceElementValidationError{
|
||||
Model: "ListEnnoblementsParams",
|
||||
Field: "tribeIDs",
|
||||
Index: 3,
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
|
||||
require.ErrorIs(t, params.SetTribeIDs(tt.args.tribeIDs), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.tribeIDs, params.TribeIDs())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListEnnoblementsParams_SetSort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -329,6 +509,143 @@ func TestListEnnoblementsParams_SetSort(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListEnnoblementsParams_PrependSortString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultNewParams := func(t *testing.T) domain.ListEnnoblementsParams {
|
||||
t.Helper()
|
||||
return domain.ListEnnoblementsParams{}
|
||||
}
|
||||
|
||||
type args struct {
|
||||
sort []string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
newParams func(t *testing.T) domain.ListEnnoblementsParams
|
||||
args args
|
||||
expectedSort []domain.EnnoblementSort
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK: [createdAt:ASC]",
|
||||
args: args{
|
||||
sort: []string{
|
||||
"createdAt:ASC",
|
||||
},
|
||||
},
|
||||
expectedSort: []domain.EnnoblementSort{
|
||||
domain.EnnoblementSortCreatedAtASC,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: [createdAt:DESC]",
|
||||
args: args{
|
||||
sort: []string{
|
||||
"createdAt:DESC",
|
||||
},
|
||||
},
|
||||
expectedSort: []domain.EnnoblementSort{
|
||||
domain.EnnoblementSortCreatedAtDESC,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(sort) < 1",
|
||||
args: args{
|
||||
sort: nil,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListEnnoblementsParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 3,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: custom params + len(sort) > 1",
|
||||
newParams: func(t *testing.T) domain.ListEnnoblementsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
require.NoError(t, params.SetSort([]domain.EnnoblementSort{
|
||||
domain.EnnoblementSortServerKeyASC,
|
||||
domain.EnnoblementSortIDASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
args: args{
|
||||
sort: []string{
|
||||
"createdAt:ASC",
|
||||
"createdAt:DESC",
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListEnnoblementsParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 1,
|
||||
Current: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: unsupported sort string",
|
||||
newParams: defaultNewParams,
|
||||
args: args{
|
||||
sort: []string{
|
||||
"createdAt:",
|
||||
},
|
||||
},
|
||||
expectedErr: domain.SliceElementValidationError{
|
||||
Model: "ListEnnoblementsParams",
|
||||
Field: "sort",
|
||||
Index: 0,
|
||||
Err: domain.UnsupportedSortStringError{
|
||||
Sort: "createdAt:",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: conflict",
|
||||
args: args{
|
||||
sort: []string{
|
||||
"createdAt:ASC",
|
||||
"createdAt:DESC",
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListEnnoblementsParams",
|
||||
Field: "sort",
|
||||
Err: domain.SortConflictError{
|
||||
Sort: [2]string{domain.EnnoblementSortCreatedAtASC.String(), domain.EnnoblementSortCreatedAtDESC.String()},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
newParams := defaultNewParams
|
||||
if tt.newParams != nil {
|
||||
newParams = tt.newParams
|
||||
}
|
||||
params := newParams(t)
|
||||
|
||||
require.ErrorIs(t, params.PrependSortString(tt.args.sort), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.expectedSort, params.Sort())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListEnnoblementsParams_SetEncodedCursor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ func (s ErrorPathSegment) String() string {
|
|||
path += "."
|
||||
}
|
||||
path += s.Field
|
||||
if s.Index > 0 {
|
||||
if s.Index >= 0 {
|
||||
path += "[" + strconv.Itoa(s.Index) + "]"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package port
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
||||
)
|
||||
|
||||
//nolint:gocyclo
|
||||
func (h *apiHTTPHandler) ListEnnoblements(
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
|
@ -17,19 +18,48 @@ func (h *apiHTTPHandler) ListEnnoblements(
|
|||
) {
|
||||
domainParams := domain.NewListEnnoblementsParams()
|
||||
|
||||
if err := domainParams.SetSort([]domain.EnnoblementSort{
|
||||
domain.EnnoblementSortCreatedAtASC,
|
||||
domain.EnnoblementSortIDASC,
|
||||
}); err != nil {
|
||||
if err := domainParams.SetSort([]domain.EnnoblementSort{domain.EnnoblementSortIDASC}); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if params.Sort != nil {
|
||||
if err := domainParams.PrependSortString(*params.Sort); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := domainParams.PrependSortString([]string{domain.EnnoblementSortCreatedAtASC.String()}); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := domainParams.SetServerKeys([]string{serverKey}); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if params.Before != nil {
|
||||
if err := domainParams.SetBefore(domain.NullTime{
|
||||
V: *params.Before,
|
||||
Valid: true,
|
||||
}); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if params.Since != nil {
|
||||
if err := domainParams.SetSince(domain.NullTime{
|
||||
V: *params.Since,
|
||||
Valid: true,
|
||||
}); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if params.Limit != nil {
|
||||
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
||||
|
@ -46,7 +76,6 @@ func (h *apiHTTPHandler) ListEnnoblements(
|
|||
|
||||
res, err := h.ennoblementSvc.ListWithRelations(r.Context(), domainParams)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
h.errorRenderer.render(w, r, err)
|
||||
return
|
||||
}
|
||||
|
@ -64,6 +93,12 @@ func formatListEnnoblementsErrorPath(segments []domain.ErrorPathSegment) []strin
|
|||
return []string{"$query", "cursor"}
|
||||
case "limit":
|
||||
return []string{"$query", "limit"}
|
||||
case "sort":
|
||||
path := []string{"$query", "sort"}
|
||||
if segments[0].Index >= 0 {
|
||||
path = append(path, strconv.Itoa(segments[0].Index))
|
||||
}
|
||||
return path
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
||||
|
@ -105,6 +106,112 @@ func TestListEnnoblements(t *testing.T) {
|
|||
assert.Len(t, body.Data, limit)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[createdAt:DESC]",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
q := req.URL.Query()
|
||||
q.Set("sort", "createdAt: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.ListPlayersResponse](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.Player) int {
|
||||
return cmp.Or(
|
||||
a.CreatedAt.Compare(b.CreatedAt)*-1,
|
||||
cmp.Compare(a.Id, b.Id),
|
||||
)
|
||||
}))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: since",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
|
||||
var filtered []ennoblementWithServer
|
||||
for _, e := range ennoblements {
|
||||
if e.Server.Key == server.Key {
|
||||
filtered = append(filtered, e)
|
||||
}
|
||||
}
|
||||
slices.SortFunc(filtered, func(a, b ennoblementWithServer) int {
|
||||
return cmp.Or(
|
||||
a.CreatedAt.Compare(b.CreatedAt),
|
||||
cmp.Compare(a.Id, b.Id),
|
||||
)
|
||||
})
|
||||
require.GreaterOrEqual(t, len(filtered), 2)
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Set("since", filtered[1].CreatedAt.Format(time.RFC3339))
|
||||
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.ListEnnoblementsResponse](t, resp.Body)
|
||||
assert.Zero(t, body.Cursor.Next)
|
||||
assert.NotZero(t, body.Cursor.Self)
|
||||
assert.NotZero(t, body.Data)
|
||||
since, err := time.Parse(time.RFC3339, req.URL.Query().Get("since"))
|
||||
require.NoError(t, err)
|
||||
for _, e := range body.Data {
|
||||
assert.True(t, e.CreatedAt.After(since) || e.CreatedAt.Equal(since))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: after",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
|
||||
var filtered []ennoblementWithServer
|
||||
for _, e := range ennoblements {
|
||||
if e.Server.Key == server.Key {
|
||||
filtered = append(filtered, e)
|
||||
}
|
||||
}
|
||||
slices.SortFunc(filtered, func(a, b ennoblementWithServer) int {
|
||||
return cmp.Or(
|
||||
a.CreatedAt.Compare(b.CreatedAt),
|
||||
cmp.Compare(a.Id, b.Id),
|
||||
)
|
||||
})
|
||||
require.GreaterOrEqual(t, len(filtered), 2)
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Set("before", filtered[1].CreatedAt.Format(time.RFC3339))
|
||||
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.ListEnnoblementsResponse](t, resp.Body)
|
||||
assert.Zero(t, body.Cursor.Next)
|
||||
assert.NotZero(t, body.Cursor.Self)
|
||||
assert.NotZero(t, body.Data)
|
||||
before, err := time.Parse(time.RFC3339, req.URL.Query().Get("before"))
|
||||
require.NoError(t, err)
|
||||
for _, e := range body.Data {
|
||||
assert.True(t, e.CreatedAt.Before(before))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: limit is not a string",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
|
@ -306,6 +413,174 @@ func TestListEnnoblements(t *testing.T) {
|
|||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(sort) > 2",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
q := req.URL.Query()
|
||||
q.Add("sort", "createdAt:DESC")
|
||||
q.Add("sort", "createdAt:ASC")
|
||||
q.Add("sort", "createdAt:ASC")
|
||||
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: 2,
|
||||
Current: len(req.URL.Query()["sort"]),
|
||||
}
|
||||
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", "sort"},
|
||||
},
|
||||
},
|
||||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: invalid sort",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
q := req.URL.Query()
|
||||
q.Add("sort", "createdAt:")
|
||||
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.UnsupportedSortStringError{
|
||||
Sort: req.URL.Query()["sort"][0],
|
||||
}
|
||||
assert.Equal(t, apimodel.ErrorResponse{
|
||||
Errors: []apimodel.Error{
|
||||
{
|
||||
Code: domainErr.Code(),
|
||||
Message: domainErr.Error(),
|
||||
Params: map[string]any{
|
||||
"sort": domainErr.Sort,
|
||||
},
|
||||
Path: []string{"$query", "sort", "0"},
|
||||
},
|
||||
},
|
||||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: sort conflict",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
q := req.URL.Query()
|
||||
q.Add("sort", "createdAt:DESC")
|
||||
q.Add("sort", "createdAt:ASC")
|
||||
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)
|
||||
q := req.URL.Query()
|
||||
domainErr := domain.SortConflictError{
|
||||
Sort: [2]string{q["sort"][0], q["sort"][1]},
|
||||
}
|
||||
paramSort := make([]any, len(domainErr.Sort))
|
||||
for i, s := range domainErr.Sort {
|
||||
paramSort[i] = s
|
||||
}
|
||||
assert.Equal(t, apimodel.ErrorResponse{
|
||||
Errors: []apimodel.Error{
|
||||
{
|
||||
Code: domainErr.Code(),
|
||||
Message: domainErr.Error(),
|
||||
Params: map[string]any{
|
||||
"sort": paramSort,
|
||||
},
|
||||
Path: []string{"$query", "sort"},
|
||||
},
|
||||
},
|
||||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: invalid since",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
q := req.URL.Query()
|
||||
q.Set("since", gofakeit.LetterN(100))
|
||||
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)
|
||||
var domainErr domain.Error
|
||||
require.ErrorAs(t, domain.ErrInvalidCursor, &domainErr)
|
||||
since := req.URL.Query().Get("since")
|
||||
assert.Equal(t, apimodel.ErrorResponse{
|
||||
Errors: []apimodel.Error{
|
||||
{
|
||||
Code: "invalid-param-format",
|
||||
//nolint:lll
|
||||
Message: fmt.Sprintf("error parsing '%s' as RFC3339 or 2006-01-02 time: parsing time \"%s\" as \"2006-01-02\": cannot parse \"%s\" as \"2006\"", since, since, since),
|
||||
Path: []string{"$query", "since"},
|
||||
},
|
||||
},
|
||||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: invalid before",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
q := req.URL.Query()
|
||||
q.Set("before", gofakeit.LetterN(100))
|
||||
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)
|
||||
var domainErr domain.Error
|
||||
require.ErrorAs(t, domain.ErrInvalidCursor, &domainErr)
|
||||
before := req.URL.Query().Get("before")
|
||||
assert.Equal(t, apimodel.ErrorResponse{
|
||||
Errors: []apimodel.Error{
|
||||
{
|
||||
Code: "invalid-param-format",
|
||||
//nolint:lll
|
||||
Message: fmt.Sprintf("error parsing '%s' as RFC3339 or 2006-01-02 time: parsing time \"%s\" as \"2006-01-02\": cannot parse \"%s\" as \"2006\"", before, before, before),
|
||||
Path: []string{"$query", "before"},
|
||||
},
|
||||
},
|
||||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: version not found",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user