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/ServerKeyPathParam"
|
||||||
- $ref: "#/components/parameters/CursorQueryParam"
|
- $ref: "#/components/parameters/CursorQueryParam"
|
||||||
- $ref: "#/components/parameters/LimitQueryParam"
|
- $ref: "#/components/parameters/LimitQueryParam"
|
||||||
|
- $ref: "#/components/parameters/EnnoblementSortQueryParam"
|
||||||
|
- $ref: "#/components/parameters/SinceQueryParam"
|
||||||
|
- $ref: "#/components/parameters/BeforeQueryParam"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/responses/ListEnnoblementsResponse"
|
$ref: "#/components/responses/ListEnnoblementsResponse"
|
||||||
|
@ -1426,6 +1429,34 @@ components:
|
||||||
type: string
|
type: string
|
||||||
example: 500|500
|
example: 500|500
|
||||||
maxItems: 200
|
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:
|
VersionCodePathParam:
|
||||||
in: path
|
in: path
|
||||||
name: versionCode
|
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))
|
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() {
|
for _, s := range a.params.Sort() {
|
||||||
switch s {
|
switch s {
|
||||||
case domain.EnnoblementSortCreatedAtASC:
|
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
|
params func(t *testing.T) domain.ListEnnoblementsParams
|
||||||
assertResult func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult)
|
assertResult func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult)
|
||||||
assertError func(t *testing.T, err error)
|
assertError func(t *testing.T, err error)
|
||||||
assertTotal func(t *testing.T, params domain.ListEnnoblementsParams, total int)
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "OK: default params",
|
name: "OK: default params",
|
||||||
|
@ -154,10 +153,6 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
||||||
t.Helper()
|
t.Helper()
|
||||||
require.NoError(t, err)
|
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]",
|
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()
|
t.Helper()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
assertTotal: func(t *testing.T, _ domain.ListEnnoblementsParams, total int) {
|
|
||||||
t.Helper()
|
|
||||||
assert.NotEmpty(t, total)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "OK: sort=[id ASC]",
|
name: "OK: sort=[id ASC]",
|
||||||
|
@ -214,10 +205,6 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
||||||
t.Helper()
|
t.Helper()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
assertTotal: func(t *testing.T, _ domain.ListEnnoblementsParams, total int) {
|
|
||||||
t.Helper()
|
|
||||||
assert.NotEmpty(t, total)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "OK: sort=[id DESC]",
|
name: "OK: sort=[id DESC]",
|
||||||
|
@ -241,10 +228,6 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
||||||
t.Helper()
|
t.Helper()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
assertTotal: func(t *testing.T, _ domain.ListEnnoblementsParams, total int) {
|
|
||||||
t.Helper()
|
|
||||||
assert.NotEmpty(t, total)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "OK: serverKeys",
|
name: "OK: serverKeys",
|
||||||
|
@ -277,9 +260,162 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
||||||
t.Helper()
|
t.Helper()
|
||||||
require.NoError(t, err)
|
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()
|
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()
|
t.Helper()
|
||||||
require.NoError(t, err)
|
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"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -230,6 +231,23 @@ const (
|
||||||
EnnoblementSortServerKeyDESC
|
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).
|
// IsInConflict returns true if two sorts can't be used together (e.g. EnnoblementSortIDASC and EnnoblementSortIDDESC).
|
||||||
func (s EnnoblementSort) IsInConflict(s2 EnnoblementSort) bool {
|
func (s EnnoblementSort) IsInConflict(s2 EnnoblementSort) bool {
|
||||||
ss := []EnnoblementSort{s, s2}
|
ss := []EnnoblementSort{s, s2}
|
||||||
|
@ -354,6 +372,11 @@ func (ec EnnoblementCursor) Encode() string {
|
||||||
|
|
||||||
type ListEnnoblementsParams struct {
|
type ListEnnoblementsParams struct {
|
||||||
serverKeys []string
|
serverKeys []string
|
||||||
|
villageIDs []int
|
||||||
|
playerIDs []int // Ennoblement.NewOwnerID or Ennoblement.OldOwnerID
|
||||||
|
tribeIDs []int // Ennoblement.NewTribeID or Ennoblement.OldTribeID
|
||||||
|
since NullTime
|
||||||
|
before NullTime
|
||||||
sort []EnnoblementSort
|
sort []EnnoblementSort
|
||||||
cursor EnnoblementCursor
|
cursor EnnoblementCursor
|
||||||
limit int
|
limit int
|
||||||
|
@ -396,6 +419,87 @@ func (params *ListEnnoblementsParams) SetServerKeys(serverKeys []string) error {
|
||||||
return nil
|
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 {
|
func (params *ListEnnoblementsParams) Sort() []EnnoblementSort {
|
||||||
return params.sort
|
return params.sort
|
||||||
}
|
}
|
||||||
|
@ -419,6 +523,37 @@ func (params *ListEnnoblementsParams) SetSort(sort []EnnoblementSort) error {
|
||||||
return nil
|
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 {
|
func (params *ListEnnoblementsParams) Cursor() EnnoblementCursor {
|
||||||
return params.cursor
|
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) {
|
func TestListEnnoblementsParams_SetSort(t *testing.T) {
|
||||||
t.Parallel()
|
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) {
|
func TestListEnnoblementsParams_SetEncodedCursor(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ func (s ErrorPathSegment) String() string {
|
||||||
path += "."
|
path += "."
|
||||||
}
|
}
|
||||||
path += s.Field
|
path += s.Field
|
||||||
if s.Index > 0 {
|
if s.Index >= 0 {
|
||||||
path += "[" + strconv.Itoa(s.Index) + "]"
|
path += "[" + strconv.Itoa(s.Index) + "]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
package port
|
package port
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
func (h *apiHTTPHandler) ListEnnoblements(
|
func (h *apiHTTPHandler) ListEnnoblements(
|
||||||
w http.ResponseWriter,
|
w http.ResponseWriter,
|
||||||
r *http.Request,
|
r *http.Request,
|
||||||
|
@ -17,19 +18,48 @@ func (h *apiHTTPHandler) ListEnnoblements(
|
||||||
) {
|
) {
|
||||||
domainParams := domain.NewListEnnoblementsParams()
|
domainParams := domain.NewListEnnoblementsParams()
|
||||||
|
|
||||||
if err := domainParams.SetSort([]domain.EnnoblementSort{
|
if err := domainParams.SetSort([]domain.EnnoblementSort{domain.EnnoblementSortIDASC}); err != nil {
|
||||||
domain.EnnoblementSortCreatedAtASC,
|
|
||||||
domain.EnnoblementSortIDASC,
|
|
||||||
}); err != nil {
|
|
||||||
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
||||||
return
|
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 {
|
if err := domainParams.SetServerKeys([]string{serverKey}); err != nil {
|
||||||
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
||||||
return
|
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 params.Limit != nil {
|
||||||
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
||||||
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
h.errorRenderer.withErrorPathFormatter(formatListEnnoblementsErrorPath).render(w, r, err)
|
||||||
|
@ -46,7 +76,6 @@ func (h *apiHTTPHandler) ListEnnoblements(
|
||||||
|
|
||||||
res, err := h.ennoblementSvc.ListWithRelations(r.Context(), domainParams)
|
res, err := h.ennoblementSvc.ListWithRelations(r.Context(), domainParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
|
||||||
h.errorRenderer.render(w, r, err)
|
h.errorRenderer.render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -64,6 +93,12 @@ func formatListEnnoblementsErrorPath(segments []domain.ErrorPathSegment) []strin
|
||||||
return []string{"$query", "cursor"}
|
return []string{"$query", "cursor"}
|
||||||
case "limit":
|
case "limit":
|
||||||
return []string{"$query", "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:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
||||||
|
@ -105,6 +106,112 @@ func TestListEnnoblements(t *testing.T) {
|
||||||
assert.Len(t, body.Data, limit)
|
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",
|
name: "ERR: limit is not a string",
|
||||||
reqModifier: func(t *testing.T, req *http.Request) {
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
@ -306,6 +413,174 @@ func TestListEnnoblements(t *testing.T) {
|
||||||
}, body)
|
}, 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",
|
name: "ERR: version not found",
|
||||||
reqModifier: func(t *testing.T, req *http.Request) {
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user