feat: add a new endpoint - GET /api/v2/versions (#53)
Reviewed-on: twhelp/corev3#53
This commit is contained in:
parent
aff2b077c0
commit
1409e0a1be
|
@ -20,7 +20,7 @@ servers:
|
|||
- http
|
||||
- https
|
||||
hostname:
|
||||
default: localhost:9913
|
||||
default: localhost:9234
|
||||
paths:
|
||||
/v2/versions:
|
||||
get:
|
||||
|
@ -77,29 +77,24 @@ components:
|
|||
type: object
|
||||
properties:
|
||||
self:
|
||||
description: Pagination link pointing to the current page.
|
||||
description: Cursor pointing to the current page.
|
||||
type: string
|
||||
format: uri
|
||||
x-go-type-skip-optional-pointer: true
|
||||
first:
|
||||
description: Pagination link pointing to the first page.
|
||||
description: Cursor pointing to the first page.
|
||||
type: string
|
||||
format: uri
|
||||
x-go-type-skip-optional-pointer: true
|
||||
prev:
|
||||
description: Pagination link pointing to the previous page.
|
||||
description: Cursor pointing to the previous page.
|
||||
type: string
|
||||
format: uri
|
||||
x-go-type-skip-optional-pointer: true
|
||||
next:
|
||||
description: Pagination link pointing to the next page.
|
||||
description: Cursor pointing to the next page.
|
||||
type: string
|
||||
format: uri
|
||||
x-go-type-skip-optional-pointer: true
|
||||
last:
|
||||
description: Pagination link pointing to the last page.
|
||||
description: Cursor pointing to the last page.
|
||||
type: string
|
||||
format: uri
|
||||
x-go-type-skip-optional-pointer: true
|
||||
parameters:
|
||||
CursorQueryParam:
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/app"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/chislog"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/health"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port"
|
||||
|
@ -87,6 +89,7 @@ var cmdServe = &cli.Command{
|
|||
Action: func(c *cli.Context) error {
|
||||
logger := loggerFromCtx(c.Context)
|
||||
|
||||
// deps
|
||||
bunDB, err := newBunDBFromFlags(c)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -100,6 +103,13 @@ var cmdServe = &cli.Command{
|
|||
}
|
||||
}()
|
||||
|
||||
// adapters
|
||||
versionRepo := adapter.NewVersionBunRepository(bunDB)
|
||||
|
||||
// services
|
||||
versionSvc := app.NewVersionService(versionRepo)
|
||||
|
||||
// health
|
||||
h := health.New()
|
||||
|
||||
server, err := newHTTPServer(
|
||||
|
@ -120,7 +130,10 @@ var cmdServe = &cli.Command{
|
|||
|
||||
r.Mount(metaBasePath, port.NewMetaHTTPHandler(h))
|
||||
|
||||
r.Mount(apiBasePath, port.NewAPIHTTPHandler(port.WithOpenAPIConfig(oapiCfg)))
|
||||
r.Mount(apiBasePath, port.NewAPIHTTPHandler(
|
||||
versionSvc,
|
||||
port.WithOpenAPIConfig(oapiCfg),
|
||||
))
|
||||
|
||||
return nil
|
||||
},
|
||||
|
|
8
go.mod
8
go.mod
|
@ -8,11 +8,11 @@ require (
|
|||
github.com/brianvoe/gofakeit/v6 v6.28.0
|
||||
github.com/cenkalti/backoff/v4 v4.2.1
|
||||
github.com/elliotchance/phpserialize v1.3.3
|
||||
github.com/getkin/kin-openapi v0.122.0
|
||||
github.com/getkin/kin-openapi v0.123.0
|
||||
github.com/go-chi/chi/v5 v5.0.11
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/oapi-codegen/runtime v1.1.1
|
||||
github.com/ory/dockertest/v3 v3.10.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
|
@ -42,8 +42,8 @@ require (
|
|||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.8 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
|
|
24
go.sum
24
go.sum
|
@ -30,7 +30,6 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
|
@ -52,17 +51,16 @@ github.com/elliotchance/phpserialize v1.3.3/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lc
|
|||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10=
|
||||
github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw=
|
||||
github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8=
|
||||
github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
|
||||
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
|
||||
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
||||
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
|
||||
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
|
||||
github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw=
|
||||
github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
|
@ -83,8 +81,8 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S3
|
|||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
|
@ -157,8 +155,8 @@ github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc
|
|||
github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
|
@ -172,12 +170,10 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
|
|
|
@ -12,3 +12,14 @@ func appendODSetClauses(q *bun.InsertQuery) *bun.InsertQuery {
|
|||
Set("rank_total = EXCLUDED.rank_total").
|
||||
Set("score_total = EXCLUDED.score_total")
|
||||
}
|
||||
|
||||
func separateListResultAndNext[T any](res []T, limit int) ([]T, T) {
|
||||
var next T
|
||||
|
||||
if len(res) > limit {
|
||||
next = res[limit]
|
||||
res = res[:limit]
|
||||
}
|
||||
|
||||
return res, next
|
||||
}
|
||||
|
|
|
@ -19,29 +19,25 @@ func NewVersionBunRepository(db bun.IDB) *VersionBunRepository {
|
|||
return &VersionBunRepository{db: db}
|
||||
}
|
||||
|
||||
func (repo *VersionBunRepository) List(ctx context.Context, params domain.ListVersionsParams) (domain.Versions, error) {
|
||||
func (repo *VersionBunRepository) List(
|
||||
ctx context.Context,
|
||||
params domain.ListVersionsParams,
|
||||
) (domain.ListVersionsResult, error) {
|
||||
var versions bunmodel.Versions
|
||||
|
||||
if err := repo.db.NewSelect().
|
||||
Model(&versions).
|
||||
Apply(listVersionsParamsApplier{params: params}.apply).
|
||||
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("couldn't select versions from the database: %w", err)
|
||||
return domain.ListVersionsResult{}, fmt.Errorf("couldn't select versions from the database: %w", err)
|
||||
}
|
||||
|
||||
return versions.ToDomain()
|
||||
}
|
||||
|
||||
func (repo *VersionBunRepository) ListCount(
|
||||
ctx context.Context,
|
||||
params domain.ListVersionsParams,
|
||||
) (domain.Versions, int, error) {
|
||||
versions, err := repo.List(ctx, params)
|
||||
converted, err := versions.ToDomain()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return domain.ListVersionsResult{}, err
|
||||
}
|
||||
|
||||
return versions, len(versions), nil
|
||||
return domain.NewListVersionsResult(separateListResultAndNext(converted, params.Limit()))
|
||||
}
|
||||
|
||||
type listVersionsParamsApplier struct {
|
||||
|
@ -50,15 +46,25 @@ type listVersionsParamsApplier struct {
|
|||
|
||||
func (a listVersionsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
for _, s := range a.params.Sort() {
|
||||
codeCursor := a.params.Cursor().Code()
|
||||
|
||||
switch s {
|
||||
case domain.VersionSortCodeASC:
|
||||
if codeCursor.Valid {
|
||||
q = q.Where("version.code >= ?", codeCursor.Value)
|
||||
}
|
||||
|
||||
q = q.Order("version.code ASC")
|
||||
case domain.VersionSortCodeDESC:
|
||||
if codeCursor.Valid {
|
||||
q = q.Where("version.code <= ?", codeCursor.Value)
|
||||
}
|
||||
|
||||
q = q.Order("version.code DESC")
|
||||
default:
|
||||
return q.Err(errors.New("unsupported sort value"))
|
||||
}
|
||||
}
|
||||
|
||||
return q
|
||||
return q.Limit(a.params.Limit() + 1)
|
||||
}
|
||||
|
|
|
@ -57,10 +57,10 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
|
|||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
versions, err := repos.version.List(ctx, domain.NewListVersionsParams())
|
||||
listVersionsRes, err := repos.version.List(ctx, domain.NewListVersionsParams())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, versions)
|
||||
version := versions[0]
|
||||
require.NotEmpty(t, listVersionsRes)
|
||||
version := listVersionsRes.Versions()[0]
|
||||
|
||||
serversToCreate := domain.BaseServers{
|
||||
domaintest.NewBaseServer(t),
|
||||
|
|
|
@ -12,11 +12,7 @@ import (
|
|||
)
|
||||
|
||||
type versionRepository interface {
|
||||
List(ctx context.Context, params domain.ListVersionsParams) (domain.Versions, error)
|
||||
ListCount(
|
||||
ctx context.Context,
|
||||
params domain.ListVersionsParams,
|
||||
) (domain.Versions, int, error)
|
||||
List(ctx context.Context, params domain.ListVersionsParams) (domain.ListVersionsResult, error)
|
||||
}
|
||||
|
||||
type serverRepository interface {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -16,17 +17,16 @@ func testVersionRepository(t *testing.T, newRepos func(t *testing.T) repositorie
|
|||
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("List & ListCount", func(t *testing.T) {
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repos := newRepos(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
params func(t *testing.T) domain.ListVersionsParams
|
||||
assertVersions func(t *testing.T, versions domain.Versions)
|
||||
assertError func(t *testing.T, err error)
|
||||
assertTotal func(t *testing.T, total int)
|
||||
name string
|
||||
params func(t *testing.T) domain.ListVersionsParams
|
||||
assertResult func(t *testing.T, params domain.ListVersionsParams, res domain.ListVersionsResult)
|
||||
assertError func(t *testing.T, err error)
|
||||
}{
|
||||
{
|
||||
name: "OK: default params",
|
||||
|
@ -34,21 +34,18 @@ func testVersionRepository(t *testing.T, newRepos func(t *testing.T) repositorie
|
|||
t.Helper()
|
||||
return domain.NewListVersionsParams()
|
||||
},
|
||||
assertVersions: func(t *testing.T, versions domain.Versions) {
|
||||
assertResult: func(t *testing.T, params domain.ListVersionsParams, res domain.ListVersionsResult) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, len(versions))
|
||||
assert.True(t, slices.IsSortedFunc(versions, func(a, b domain.Version) int {
|
||||
assert.NotEmpty(t, res.Versions())
|
||||
assert.True(t, slices.IsSortedFunc(res.Versions(), func(a, b domain.Version) int {
|
||||
return cmp.Compare(a.Code(), b.Code())
|
||||
}))
|
||||
assert.True(t, res.Next().IsZero())
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[code DESC]",
|
||||
|
@ -58,10 +55,10 @@ func testVersionRepository(t *testing.T, newRepos func(t *testing.T) repositorie
|
|||
require.NoError(t, params.SetSort([]domain.VersionSort{domain.VersionSortCodeDESC}))
|
||||
return params
|
||||
},
|
||||
assertVersions: func(t *testing.T, versions domain.Versions) {
|
||||
assertResult: func(t *testing.T, params domain.ListVersionsParams, res domain.ListVersionsResult) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, len(versions))
|
||||
assert.True(t, slices.IsSortedFunc(versions, func(a, b domain.Version) int {
|
||||
assert.NotEmpty(t, res.Versions())
|
||||
assert.True(t, slices.IsSortedFunc(res.Versions(), func(a, b domain.Version) int {
|
||||
return cmp.Compare(a.Code(), b.Code()) * -1
|
||||
}))
|
||||
},
|
||||
|
@ -69,9 +66,95 @@ func testVersionRepository(t *testing.T, newRepos func(t *testing.T) repositorie
|
|||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, total int) {
|
||||
},
|
||||
{
|
||||
name: "OK: cursor sort=[code ASC]",
|
||||
params: func(t *testing.T) domain.ListVersionsParams {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
|
||||
params := domain.NewListVersionsParams()
|
||||
require.NoError(t, params.SetSort([]domain.VersionSort{domain.VersionSortCodeASC}))
|
||||
|
||||
res, err := repos.version.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Versions()), 2)
|
||||
|
||||
require.NoError(t, params.SetCursor(domaintest.NewVersionCursor(t, func(cfg *domaintest.VersionCursorConfig) {
|
||||
cfg.Code = domain.NullString{
|
||||
Value: res.Versions()[1].Code(),
|
||||
Valid: true,
|
||||
}
|
||||
})))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListVersionsParams, res domain.ListVersionsResult) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, res.Versions())
|
||||
assert.True(t, slices.IsSortedFunc(res.Versions(), func(a, b domain.Version) int {
|
||||
return cmp.Compare(a.Code(), b.Code())
|
||||
}))
|
||||
for _, v := range res.Versions() {
|
||||
assert.GreaterOrEqual(t, v.Code(), params.Cursor().Code().Value, v.Code())
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: cursor sort=[code DESC]",
|
||||
params: func(t *testing.T) domain.ListVersionsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListVersionsParams()
|
||||
require.NoError(t, params.SetSort([]domain.VersionSort{domain.VersionSortCodeDESC}))
|
||||
|
||||
res, err := repos.version.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Versions()), 2)
|
||||
|
||||
require.NoError(t, params.SetCursor(domaintest.NewVersionCursor(t, func(cfg *domaintest.VersionCursorConfig) {
|
||||
cfg.Code = domain.NullString{
|
||||
Value: res.Versions()[1].Code(),
|
||||
Valid: true,
|
||||
}
|
||||
})))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListVersionsParams, res domain.ListVersionsResult) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, res.Versions())
|
||||
assert.True(t, slices.IsSortedFunc(res.Versions(), func(a, b domain.Version) int {
|
||||
return cmp.Compare(a.Code(), b.Code()) * -1
|
||||
}))
|
||||
for _, v := range res.Versions() {
|
||||
assert.LessOrEqual(t, v.Code(), params.Cursor().Code().Value, v.Code())
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: limit=5",
|
||||
params: func(t *testing.T) domain.ListVersionsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListVersionsParams()
|
||||
require.NoError(t, params.SetLimit(5))
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListVersionsParams, res domain.ListVersionsResult) {
|
||||
t.Helper()
|
||||
assert.Len(t, res.Versions(), params.Limit())
|
||||
assert.False(t, res.Next().IsZero())
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -86,12 +169,7 @@ func testVersionRepository(t *testing.T, newRepos func(t *testing.T) repositorie
|
|||
|
||||
res, err := repos.version.List(ctx, params)
|
||||
tt.assertError(t, err)
|
||||
tt.assertVersions(t, res)
|
||||
|
||||
res, count, err := repos.version.ListCount(ctx, params)
|
||||
tt.assertError(t, err)
|
||||
tt.assertVersions(t, res)
|
||||
tt.assertTotal(t, count)
|
||||
tt.assertResult(t, params, res)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -29,10 +29,11 @@ func NewDataSyncService(
|
|||
}
|
||||
|
||||
func (svc *DataSyncService) Sync(ctx context.Context) error {
|
||||
versions, err := svc.versionSvc.List(ctx, domain.NewListVersionsParams())
|
||||
listVersionsRes, err := svc.versionSvc.List(ctx, domain.NewListVersionsParams())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
versions := listVersionsRes.Versions()
|
||||
|
||||
payloads := make([]domain.SyncServersCmdPayload, 0, len(versions))
|
||||
|
||||
|
@ -49,10 +50,11 @@ func (svc *DataSyncService) Sync(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (svc *DataSyncService) SyncEnnoblements(ctx context.Context) error {
|
||||
versions, err := svc.versionSvc.List(ctx, domain.NewListVersionsParams())
|
||||
listVersionsRes, err := svc.versionSvc.List(ctx, domain.NewListVersionsParams())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
versions := listVersionsRes.Versions()
|
||||
|
||||
for _, v := range versions {
|
||||
if err = svc.syncEnnoblementsForVersion(ctx, v); err != nil {
|
||||
|
|
|
@ -31,10 +31,11 @@ func NewSnapshotService(
|
|||
}
|
||||
|
||||
func (svc *SnapshotService) Create(ctx context.Context) error {
|
||||
versions, err := svc.versionSvc.List(ctx, domain.NewListVersionsParams())
|
||||
listVersionsRes, err := svc.versionSvc.List(ctx, domain.NewListVersionsParams())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
versions := listVersionsRes.Versions()
|
||||
|
||||
for _, v := range versions {
|
||||
loc, loopErr := time.LoadLocation(v.Timezone())
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
type VersionRepository interface {
|
||||
List(ctx context.Context, params domain.ListVersionsParams) (domain.Versions, error)
|
||||
List(ctx context.Context, params domain.ListVersionsParams) (domain.ListVersionsResult, error)
|
||||
}
|
||||
|
||||
type VersionService struct {
|
||||
|
@ -18,6 +18,9 @@ func NewVersionService(repo VersionRepository) *VersionService {
|
|||
return &VersionService{repo: repo}
|
||||
}
|
||||
|
||||
func (svc *VersionService) List(ctx context.Context, params domain.ListVersionsParams) (domain.Versions, error) {
|
||||
func (svc *VersionService) List(
|
||||
ctx context.Context,
|
||||
params domain.ListVersionsParams,
|
||||
) (domain.ListVersionsResult, error) {
|
||||
return svc.repo.List(ctx, params)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,30 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type VersionCursorConfig struct {
|
||||
Code domain.NullString
|
||||
}
|
||||
|
||||
func NewVersionCursor(tb TestingTB, opts ...func(cfg *VersionCursorConfig)) domain.VersionCursor {
|
||||
tb.Helper()
|
||||
|
||||
cfg := &VersionCursorConfig{
|
||||
Code: domain.NullString{
|
||||
Value: RandVersionCode(),
|
||||
Valid: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
vc, err := domain.NewVersionCursor(cfg.Code)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return vc
|
||||
}
|
||||
|
||||
type VersionConfig struct {
|
||||
Code string
|
||||
}
|
||||
|
@ -21,7 +45,7 @@ func NewVersion(tb TestingTB, opts ...func(cfg *VersionConfig)) domain.Version {
|
|||
opt(cfg)
|
||||
}
|
||||
|
||||
s, err := domain.UnmarshalVersionFromDatabase(
|
||||
v, err := domain.UnmarshalVersionFromDatabase(
|
||||
cfg.Code,
|
||||
gofakeit.LetterN(10),
|
||||
gofakeit.DomainName(),
|
||||
|
@ -29,7 +53,7 @@ func NewVersion(tb TestingTB, opts ...func(cfg *VersionConfig)) domain.Version {
|
|||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return s
|
||||
return v
|
||||
}
|
||||
|
||||
func RandVersionCode() string {
|
||||
|
|
|
@ -165,8 +165,6 @@ const (
|
|||
EnnoblementSortServerKeyDESC
|
||||
)
|
||||
|
||||
const EnnoblementListMaxLimit = 200
|
||||
|
||||
type ListEnnoblementsParams struct {
|
||||
serverKeys []string
|
||||
sort []EnnoblementSort
|
||||
|
@ -174,7 +172,10 @@ type ListEnnoblementsParams struct {
|
|||
offset int
|
||||
}
|
||||
|
||||
const listEnnoblementsParamsModelName = "ListEnnoblementsParams"
|
||||
const (
|
||||
EnnoblementListMaxLimit = 200
|
||||
listEnnoblementsParamsModelName = "ListEnnoblementsParams"
|
||||
)
|
||||
|
||||
func NewListEnnoblementsParams() ListEnnoblementsParams {
|
||||
return ListEnnoblementsParams{
|
||||
|
|
|
@ -373,8 +373,6 @@ const (
|
|||
PlayerSortServerKeyDESC
|
||||
)
|
||||
|
||||
const PlayerListMaxLimit = 200
|
||||
|
||||
type ListPlayersParams struct {
|
||||
ids []int
|
||||
idGT NullInt
|
||||
|
@ -385,7 +383,10 @@ type ListPlayersParams struct {
|
|||
offset int
|
||||
}
|
||||
|
||||
const listPlayersParamsModelName = "ListPlayersParams"
|
||||
const (
|
||||
PlayerListMaxLimit = 200
|
||||
listPlayersParamsModelName = "ListPlayersParams"
|
||||
)
|
||||
|
||||
func NewListPlayersParams() ListPlayersParams {
|
||||
return ListPlayersParams{
|
||||
|
|
|
@ -464,8 +464,6 @@ const (
|
|||
ServerSortOpenDESC
|
||||
)
|
||||
|
||||
const ServerListMaxLimit = 500
|
||||
|
||||
type ListServersParams struct {
|
||||
keys []string
|
||||
keyGT NullString
|
||||
|
@ -479,7 +477,10 @@ type ListServersParams struct {
|
|||
offset int
|
||||
}
|
||||
|
||||
const listServersParamsModelName = "ListServersParams"
|
||||
const (
|
||||
ServerListMaxLimit = 500
|
||||
listServersParamsModelName = "ListServersParams"
|
||||
)
|
||||
|
||||
func NewListServersParams() ListServersParams {
|
||||
return ListServersParams{
|
||||
|
|
|
@ -368,8 +368,6 @@ const (
|
|||
TribeSortServerKeyDESC
|
||||
)
|
||||
|
||||
const TribeListMaxLimit = 200
|
||||
|
||||
type ListTribesParams struct {
|
||||
ids []int
|
||||
idGT NullInt
|
||||
|
@ -380,7 +378,10 @@ type ListTribesParams struct {
|
|||
offset int
|
||||
}
|
||||
|
||||
const listTribesParamsModelName = "ListTribesParams"
|
||||
const (
|
||||
TribeListMaxLimit = 200
|
||||
listTribesParamsModelName = "ListTribesParams"
|
||||
)
|
||||
|
||||
func NewListTribesParams() ListTribesParams {
|
||||
return ListTribesParams{
|
||||
|
|
|
@ -213,8 +213,6 @@ const (
|
|||
TribeChangeSortServerKeyDESC
|
||||
)
|
||||
|
||||
const TribeChangeListMaxLimit = 200
|
||||
|
||||
type ListTribeChangesParams struct {
|
||||
serverKeys []string
|
||||
sort []TribeChangeSort
|
||||
|
@ -222,7 +220,10 @@ type ListTribeChangesParams struct {
|
|||
offset int
|
||||
}
|
||||
|
||||
const listTribeChangesParamsModelName = "ListTribeChangesParams"
|
||||
const (
|
||||
TribeChangeListMaxLimit = 200
|
||||
listTribeChangesParamsModelName = "ListTribeChangesParams"
|
||||
)
|
||||
|
||||
func NewListTribeChangesParams() ListTribeChangesParams {
|
||||
return ListTribeChangesParams{
|
||||
|
|
|
@ -224,8 +224,6 @@ const (
|
|||
TribeSnapshotSortServerKeyDESC
|
||||
)
|
||||
|
||||
const TribeSnapshotListMaxLimit = 200
|
||||
|
||||
type ListTribeSnapshotsParams struct {
|
||||
serverKeys []string
|
||||
sort []TribeSnapshotSort
|
||||
|
@ -233,7 +231,10 @@ type ListTribeSnapshotsParams struct {
|
|||
offset int
|
||||
}
|
||||
|
||||
const listTribeSnapshotsParamsModelName = "ListTribeSnapshotsParams"
|
||||
const (
|
||||
TribeSnapshotListMaxLimit = 200
|
||||
listTribeSnapshotsParamsModelName = "ListTribeSnapshotsParams"
|
||||
)
|
||||
|
||||
func NewListTribeSnapshotsParams() ListTribeSnapshotsParams {
|
||||
return ListTribeSnapshotsParams{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
|
@ -91,6 +92,10 @@ func (v Version) URL() *url.URL {
|
|||
}
|
||||
}
|
||||
|
||||
func (v Version) IsZero() bool {
|
||||
return v == Version{}
|
||||
}
|
||||
|
||||
type Versions []Version
|
||||
|
||||
type VersionSort uint8
|
||||
|
@ -100,14 +105,77 @@ const (
|
|||
VersionSortCodeDESC
|
||||
)
|
||||
|
||||
type ListVersionsParams struct {
|
||||
sort []VersionSort
|
||||
type VersionCursor struct {
|
||||
code NullString
|
||||
}
|
||||
|
||||
const listVersionsParamsModelName = "ListVersionsParams"
|
||||
func NewVersionCursor(code NullString) (VersionCursor, error) {
|
||||
if !code.Valid {
|
||||
return VersionCursor{}, nil
|
||||
}
|
||||
|
||||
if err := validateVersionCode(code.Value); err != nil {
|
||||
return VersionCursor{}, err
|
||||
}
|
||||
|
||||
return VersionCursor{
|
||||
code: code,
|
||||
}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
encodedVersionCursorMinLength = 1
|
||||
encodedVersionCursorMaxLength = 5000
|
||||
)
|
||||
|
||||
func decodeVersionCursor(encoded string) (VersionCursor, error) {
|
||||
if err := validateStringLen(encoded, encodedVersionCursorMinLength, encodedVersionCursorMaxLength); err != nil {
|
||||
return VersionCursor{}, err
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return VersionCursor{}, err
|
||||
}
|
||||
|
||||
return NewVersionCursor(NullString{
|
||||
Value: string(decoded),
|
||||
Valid: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (vc VersionCursor) Code() NullString {
|
||||
return vc.code
|
||||
}
|
||||
|
||||
func (vc VersionCursor) IsZero() bool {
|
||||
return !vc.code.Valid
|
||||
}
|
||||
|
||||
func (vc VersionCursor) Encode() string {
|
||||
if vc.IsZero() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString([]byte(vc.code.Value))
|
||||
}
|
||||
|
||||
type ListVersionsParams struct {
|
||||
sort []VersionSort
|
||||
cursor VersionCursor
|
||||
limit int
|
||||
}
|
||||
|
||||
const (
|
||||
VersionListMaxLimit = 200
|
||||
listVersionsParamsModelName = "ListVersionsParams"
|
||||
)
|
||||
|
||||
func NewListVersionsParams() ListVersionsParams {
|
||||
return ListVersionsParams{sort: []VersionSort{VersionSortCodeASC}}
|
||||
return ListVersionsParams{
|
||||
sort: []VersionSort{VersionSortCodeASC},
|
||||
limit: VersionListMaxLimit,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -132,3 +200,100 @@ func (params *ListVersionsParams) SetSort(sort []VersionSort) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListVersionsParams) Cursor() VersionCursor {
|
||||
return params.cursor
|
||||
}
|
||||
|
||||
func (params *ListVersionsParams) SetCursor(vc VersionCursor) error {
|
||||
params.cursor = vc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListVersionsParams) SetEncodedCursor(encoded string) error {
|
||||
decoded, err := decodeVersionCursor(encoded)
|
||||
if err != nil {
|
||||
return ValidationError{
|
||||
Model: listVersionsParamsModelName,
|
||||
Field: "cursor",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
params.cursor = decoded
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListVersionsParams) Limit() int {
|
||||
return params.limit
|
||||
}
|
||||
|
||||
func (params *ListVersionsParams) SetLimit(limit int) error {
|
||||
if err := validateIntInRange(limit, 1, VersionListMaxLimit); err != nil {
|
||||
return ValidationError{
|
||||
Model: listVersionsParamsModelName,
|
||||
Field: "limit",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
params.limit = limit
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ListVersionsResult struct {
|
||||
versions Versions
|
||||
self VersionCursor
|
||||
next VersionCursor
|
||||
}
|
||||
|
||||
const listVersionsResultModelName = "ListVersionsResult"
|
||||
|
||||
func NewListVersionsResult(versions Versions, next Version) (ListVersionsResult, error) {
|
||||
var err error
|
||||
res := ListVersionsResult{
|
||||
versions: versions,
|
||||
}
|
||||
|
||||
if len(versions) > 0 {
|
||||
res.self, err = NewVersionCursor(NullString{
|
||||
Value: versions[0].Code(),
|
||||
Valid: true,
|
||||
})
|
||||
if err != nil {
|
||||
return ListVersionsResult{}, ValidationError{
|
||||
Model: listVersionsResultModelName,
|
||||
Field: "self",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.next, err = NewVersionCursor(NullString{
|
||||
Value: next.Code(),
|
||||
Valid: !next.IsZero(),
|
||||
})
|
||||
if err != nil {
|
||||
return ListVersionsResult{}, ValidationError{
|
||||
Model: listVersionsResultModelName,
|
||||
Field: "next",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (res ListVersionsResult) Versions() Versions {
|
||||
return res.versions
|
||||
}
|
||||
|
||||
func (res ListVersionsResult) Self() VersionCursor {
|
||||
return res.self
|
||||
}
|
||||
|
||||
func (res ListVersionsResult) Next() VersionCursor {
|
||||
return res.next
|
||||
}
|
||||
|
|
|
@ -1,13 +1,83 @@
|
|||
package domain_test
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewVersionCursor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validVersionCursor := domaintest.NewVersionCursor(t)
|
||||
|
||||
type args struct {
|
||||
code domain.NullString
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
code: validVersionCursor.Code(),
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "OK: nil code",
|
||||
args: args{
|
||||
code: domain.NullString{},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, versionCodeTest := range newVersionCodeValidationTests() {
|
||||
tests = append(tests, test{
|
||||
name: versionCodeTest.name,
|
||||
args: args{
|
||||
code: domain.NullString{
|
||||
Value: versionCodeTest.code,
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
expectedErr: versionCodeTest.expectedErr,
|
||||
})
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
vc, err := domain.NewVersionCursor(tt.args.code)
|
||||
require.ErrorIs(t, err, tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.code, vc.Code())
|
||||
if tt.args.code.Valid {
|
||||
assert.NotEmpty(t, vc.Encode())
|
||||
} else {
|
||||
assert.Empty(t, vc.Encode())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListVersionsParams_SetSort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -80,6 +150,200 @@ func TestListVersionsParams_SetSort(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListVersionsParams_SetEncodedCursor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validCursor := domaintest.NewVersionCursor(t)
|
||||
|
||||
type args struct {
|
||||
cursor string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedCursor domain.VersionCursor
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
cursor: validCursor.Encode(),
|
||||
},
|
||||
expectedCursor: validCursor,
|
||||
},
|
||||
{
|
||||
name: "ERR: len(cursor) < 1",
|
||||
args: args{
|
||||
cursor: "",
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListVersionsParams",
|
||||
Field: "cursor",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 5000,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(cursor) > 5000",
|
||||
args: args{
|
||||
cursor: gofakeit.LetterN(5001),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListVersionsParams",
|
||||
Field: "cursor",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 5000,
|
||||
Current: 5001,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: malformed base64",
|
||||
args: args{
|
||||
cursor: "112345",
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListVersionsParams",
|
||||
Field: "cursor",
|
||||
Err: base64.CorruptInputError(4),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListVersionsParams()
|
||||
|
||||
require.ErrorIs(t, params.SetEncodedCursor(tt.args.cursor), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
fmt.Println(tt.expectedErr.Error())
|
||||
return
|
||||
}
|
||||
assert.True(t, params.Cursor().Code().Valid)
|
||||
assert.Equal(t, tt.expectedCursor.Code(), params.Cursor().Code())
|
||||
assert.Equal(t, tt.args.cursor, params.Cursor().Encode())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListVersionsParams_SetLimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
limit int
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
limit: domain.VersionListMaxLimit,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: limit < 1",
|
||||
args: args{
|
||||
limit: 0,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListVersionsParams",
|
||||
Field: "limit",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: fmt.Sprintf("ERR: limit > %d", domain.VersionListMaxLimit),
|
||||
args: args{
|
||||
limit: domain.VersionListMaxLimit + 1,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListVersionsParams",
|
||||
Field: "limit",
|
||||
Err: domain.MaxLessEqualError{
|
||||
Max: domain.VersionListMaxLimit,
|
||||
Current: domain.VersionListMaxLimit + 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListVersionsParams()
|
||||
|
||||
require.ErrorIs(t, params.SetLimit(tt.args.limit), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.limit, params.Limit())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewListVersionsResult(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
versions := domain.Versions{
|
||||
domaintest.NewVersion(t),
|
||||
domaintest.NewVersion(t),
|
||||
domaintest.NewVersion(t),
|
||||
}
|
||||
next := domaintest.NewVersion(t)
|
||||
|
||||
t.Run("OK: with next", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewListVersionsResult(versions, next)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, versions, res.Versions())
|
||||
assert.True(t, res.Self().Code().Valid)
|
||||
assert.Equal(t, versions[0].Code(), res.Self().Code().Value)
|
||||
assert.True(t, res.Next().Code().Valid)
|
||||
assert.Equal(t, next.Code(), res.Next().Code().Value)
|
||||
})
|
||||
|
||||
t.Run("OK: without next", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewListVersionsResult(versions, domain.Version{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, versions, res.Versions())
|
||||
assert.True(t, res.Self().Code().Valid)
|
||||
assert.Equal(t, versions[0].Code(), res.Self().Code().Value)
|
||||
assert.True(t, res.Next().IsZero())
|
||||
})
|
||||
|
||||
t.Run("OK: 0 versions", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewListVersionsResult(nil, domain.Version{})
|
||||
require.NoError(t, err)
|
||||
assert.Zero(t, res.Versions())
|
||||
assert.True(t, res.Self().IsZero())
|
||||
assert.True(t, res.Next().IsZero())
|
||||
})
|
||||
}
|
||||
|
||||
type versionCodeValidationTest struct {
|
||||
name string
|
||||
code string
|
||||
|
|
|
@ -224,8 +224,6 @@ const (
|
|||
VillageSortServerKeyDESC
|
||||
)
|
||||
|
||||
const VillageListMaxLimit = 500
|
||||
|
||||
type ListVillagesParams struct {
|
||||
ids []int
|
||||
idGT NullInt
|
||||
|
@ -235,7 +233,10 @@ type ListVillagesParams struct {
|
|||
offset int
|
||||
}
|
||||
|
||||
const listVillagesParamsModelName = "ListVillagesParams"
|
||||
const (
|
||||
VillageListMaxLimit = 500
|
||||
listVillagesParamsModelName = "ListVillagesParams"
|
||||
)
|
||||
|
||||
func NewListVillagesParams() ListVillagesParams {
|
||||
return ListVillagesParams{
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"net/http"
|
||||
"sync"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/app"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/swgui"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
|
@ -12,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
type apiHTTPHandler struct {
|
||||
apimodel.Unimplemented
|
||||
versionSvc *app.VersionService
|
||||
getOpenAPISchema func() (*openapi3.T, error)
|
||||
}
|
||||
|
||||
|
@ -22,10 +23,11 @@ func WithOpenAPIConfig(oapiCfg OpenAPIConfig) APIHTTPHandlerOption {
|
|||
}
|
||||
}
|
||||
|
||||
func NewAPIHTTPHandler(opts ...APIHTTPHandlerOption) http.Handler {
|
||||
func NewAPIHTTPHandler(versionSvc *app.VersionService, opts ...APIHTTPHandlerOption) http.Handler {
|
||||
cfg := newAPIHTTPHandlerConfig(opts...)
|
||||
|
||||
h := &apiHTTPHandler{
|
||||
versionSvc: versionSvc,
|
||||
getOpenAPISchema: sync.OnceValues(func() (*openapi3.T, error) {
|
||||
return getOpenAPISchema(cfg.openAPI)
|
||||
}),
|
||||
|
|
|
@ -35,8 +35,10 @@ func getOpenAPISchema(cfg OpenAPIConfig) (*openapi3.T, error) {
|
|||
schema.Servers = make(openapi3.Servers, 0, len(cfg.Servers))
|
||||
|
||||
for _, s := range cfg.Servers {
|
||||
u := *s.URL
|
||||
u.Path = cfg.BasePath
|
||||
schema.Servers = append(schema.Servers, &openapi3.Server{
|
||||
URL: s.URL.String(),
|
||||
URL: u.String(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -21,9 +21,11 @@ func TestOpenAPI(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
u1, err := url.Parse(gofakeit.URL())
|
||||
u1.Path = ""
|
||||
require.NoError(t, err)
|
||||
|
||||
u2, err := url.Parse(gofakeit.URL())
|
||||
u2.Path = ""
|
||||
require.NoError(t, err)
|
||||
|
||||
servers := []port.OpenAPIConfigServer{
|
||||
|
@ -35,10 +37,13 @@ func TestOpenAPI(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
basePath := "/api"
|
||||
|
||||
handler := newAPIHTTPHandler(t, func(cfg *apiHTTPHandlerConfig) {
|
||||
cfg.options = append(cfg.options, port.WithOpenAPIConfig(port.OpenAPIConfig{
|
||||
Enabled: true,
|
||||
Servers: servers,
|
||||
Enabled: true,
|
||||
BasePath: basePath,
|
||||
Servers: servers,
|
||||
}))
|
||||
})
|
||||
|
||||
|
@ -52,7 +57,7 @@ func TestOpenAPI(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
for i, expected := range servers {
|
||||
idx := slices.IndexFunc(body.Servers, func(s *openapi3.Server) bool {
|
||||
return s.URL == expected.URL.String()
|
||||
return s.URL == expected.URL.String()+basePath
|
||||
})
|
||||
assert.GreaterOrEqualf(t, idx, 0, "expected[%d] not found", i)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@ import (
|
|||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/app"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/bun/buntest"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port"
|
||||
)
|
||||
|
||||
|
@ -22,7 +25,12 @@ func newAPIHTTPHandler(tb testing.TB, opts ...func(cfg *apiHTTPHandlerConfig)) h
|
|||
opt(cfg)
|
||||
}
|
||||
|
||||
return port.NewAPIHTTPHandler(cfg.options...)
|
||||
versionRepo := adapter.NewVersionBunRepository(buntest.NewSQLiteDB(tb))
|
||||
|
||||
return port.NewAPIHTTPHandler(
|
||||
app.NewVersionService(versionRepo),
|
||||
cfg.options...,
|
||||
)
|
||||
}
|
||||
|
||||
func doRequest(h http.Handler, method, target string, body io.Reader) *http.Response {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package port
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
||||
)
|
||||
|
||||
func (h *apiHTTPHandler) ListVersions(w http.ResponseWriter, r *http.Request, params apimodel.ListVersionsParams) {
|
||||
domainParams := domain.NewListVersionsParams()
|
||||
|
||||
if params.Limit != nil {
|
||||
_ = domainParams.SetLimit(*params.Limit)
|
||||
}
|
||||
|
||||
if params.Cursor != nil {
|
||||
_ = domainParams.SetEncodedCursor(*params.Cursor)
|
||||
}
|
||||
|
||||
res, err := h.versionSvc.List(r.Context(), domainParams)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderJSON(w, r, http.StatusOK, apimodel.NewListVersionsResponse(res))
|
||||
}
|
|
@ -1,3 +1,30 @@
|
|||
package apimodel
|
||||
|
||||
import "gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
|
||||
//go:generate oapi-codegen --config=config.yml ../../../../api/openapi3.yml
|
||||
|
||||
func NewVersion(v domain.Version) Version {
|
||||
return Version{
|
||||
Code: v.Code(),
|
||||
Host: v.Host(),
|
||||
Name: v.Name(),
|
||||
Timezone: v.Timezone(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewListVersionsResponse(res domain.ListVersionsResult) ListVersionsResponse {
|
||||
versions := res.Versions()
|
||||
|
||||
resp := ListVersionsResponse{
|
||||
Items: make([]Version, 0, len(versions)),
|
||||
Self: res.Self().Encode(),
|
||||
Next: res.Next().Encode(),
|
||||
}
|
||||
|
||||
for _, v := range versions {
|
||||
resp.Items = append(resp.Items, NewVersion(v))
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue