feat: init api
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
"github.com/iliadenisov/galaxy/internal/router"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommand(t *testing.T) {
|
||||
r := router.SetupRouter()
|
||||
|
||||
payload := rest.Command{
|
||||
Race: "SomeRace",
|
||||
Vote: &rest.CommandVote{
|
||||
Recipient: "AnotherRace",
|
||||
},
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("PUT", "/api/v1/command", asBody(payload))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code, w.Body)
|
||||
|
||||
// error: notblank validator
|
||||
payload.Race = ""
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PUT", "/api/v1/command", asBody(payload))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
|
||||
payload.Race = " "
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PUT", "/api/v1/command", asBody(payload))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
|
||||
// error: no commands
|
||||
payload = rest.Command{
|
||||
Race: "SomeRace",
|
||||
}
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PUT", "/api/v1/command", asBody(payload))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
|
||||
// error: more than one command
|
||||
payload = rest.Command{
|
||||
Race: "SomeRace",
|
||||
Vote: &rest.CommandVote{
|
||||
Recipient: "AnotherRace",
|
||||
},
|
||||
DeclarePeace: &rest.CommandDeclarePeace{
|
||||
Opponent: "OpponentRace",
|
||||
},
|
||||
}
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PUT", "/api/v1/command", asBody(payload))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
package router
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
)
|
||||
|
||||
type CommandExecutor func(controller.Config, rest.Command) error
|
||||
|
||||
var (
|
||||
ErrCommandNotProcessed = errors.New("command was not processed by executor")
|
||||
)
|
||||
|
||||
func CommandHandler(c *gin.Context, executor Executor) {
|
||||
func CommandHandler(c *gin.Context, config controller.Config, executor CommandExecutor) {
|
||||
var cmd rest.Command
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
err := executor(cmd)
|
||||
err := executor(config, cmd)
|
||||
switch {
|
||||
case err == nil:
|
||||
c.Status(http.StatusOK)
|
||||
@@ -32,13 +35,12 @@ func CommandHandler(c *gin.Context, executor Executor) {
|
||||
}
|
||||
}
|
||||
|
||||
func Execute(cmd rest.Command) error {
|
||||
p := param()
|
||||
func ExecuteCommand(config controller.Config, cmd rest.Command) error {
|
||||
switch {
|
||||
case cmd.DeclareWar != nil:
|
||||
return game.DeclareWar(p, cmd.Race, cmd.DeclareWar.Opponent)
|
||||
return game.DeclareWar(config, cmd.Race, cmd.DeclareWar.Opponent)
|
||||
case cmd.DeclarePeace != nil:
|
||||
return game.DeclarePeace(p, cmd.Race, cmd.DeclareWar.Opponent)
|
||||
return game.DeclarePeace(config, cmd.Race, cmd.DeclareWar.Opponent)
|
||||
default:
|
||||
return ErrCommandNotProcessed
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -6,19 +6,11 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
)
|
||||
|
||||
func StatusHandler(c *gin.Context) {
|
||||
g, err := game.LoadState(param())
|
||||
|
||||
func transformError(c *gin.Context, err error) bool {
|
||||
if err == nil {
|
||||
c.JSON(http.StatusOK, rest.Status{
|
||||
Turn: g.Age,
|
||||
Players: len(g.Race),
|
||||
})
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
var ge = new(e.GenericError)
|
||||
@@ -30,8 +22,10 @@ func StatusHandler(c *gin.Context) {
|
||||
default:
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"generic_error": ge.Error(), "code": ge.Code})
|
||||
}
|
||||
return
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
|
||||
}
|
||||
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
)
|
||||
|
||||
func InitHandler(c *gin.Context, config controller.Config) {
|
||||
var init rest.Init
|
||||
if err := c.ShouldBindJSON(&init); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
races := make([]string, len(init.Races))
|
||||
for i := range init.Races {
|
||||
races[i] = init.Races[i].Name
|
||||
}
|
||||
|
||||
uuid, err := game.GenerateGame(config, races)
|
||||
if transformError(c, err) {
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, rest.InitResponse{
|
||||
UUID: uuid,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
)
|
||||
|
||||
func StatusHandler(c *gin.Context, config controller.Config) {
|
||||
g, err := game.LoadState(config)
|
||||
|
||||
if transformError(c, err) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, rest.Status{
|
||||
Turn: g.Age,
|
||||
Players: len(g.Race),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
"github.com/iliadenisov/galaxy/internal/router"
|
||||
"github.com/iliadenisov/galaxy/internal/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
root, cleanup := util.CreateWorkDir(t)
|
||||
defer cleanup()
|
||||
|
||||
r := router.SetupRouterConfig(func(p *controller.Param) { p.StoragePath = root })
|
||||
|
||||
payload := generateInitRequest(10)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/api/v1/init", asBody(payload))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusCreated, w.Code, w.Body)
|
||||
var initResponse rest.InitResponse
|
||||
assert.NoError(t, json.Unmarshal(w.Body.Bytes(), &initResponse))
|
||||
assert.NoError(t, uuid.Validate(initResponse.UUID.String()))
|
||||
}
|
||||
|
||||
func TestInitValidators(t *testing.T) {
|
||||
r := router.SetupRouter()
|
||||
payload := generateInitRequest(9)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/api/v1/init", asBody(payload))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
}
|
||||
|
||||
func generateInitRequest(races int) rest.Init {
|
||||
request := rest.Init{
|
||||
Races: make([]rest.Race, races),
|
||||
}
|
||||
for i := range request.Races {
|
||||
request.Races[i] = rest.Race{Name: fmt.Sprintf("Race_%02d", i)}
|
||||
}
|
||||
return request
|
||||
}
|
||||
+14
-20
@@ -7,29 +7,19 @@ import (
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
"github.com/iliadenisov/galaxy/internal/router/handler"
|
||||
)
|
||||
|
||||
var (
|
||||
StoragePath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
StoragePath = os.Getenv("STORAGE_PATH")
|
||||
}
|
||||
|
||||
func param() func(*controller.Param) {
|
||||
func initConfig() func(*controller.Param) {
|
||||
return func(p *controller.Param) {
|
||||
// TODO: initialize base controller settings
|
||||
p.StoragePath = StoragePath
|
||||
p.StoragePath = os.Getenv("STORAGE_PATH")
|
||||
}
|
||||
}
|
||||
|
||||
type Executor func(rest.Command) error
|
||||
|
||||
type Router struct {
|
||||
r *gin.Engine
|
||||
executor Executor
|
||||
executor handler.CommandExecutor
|
||||
}
|
||||
|
||||
func (r Router) Run() error {
|
||||
@@ -37,14 +27,14 @@ func (r Router) Run() error {
|
||||
}
|
||||
|
||||
func NewRouter() Router {
|
||||
return NewRouterExecutor(Execute)
|
||||
return NewRouterExecutor(handler.ExecuteCommand)
|
||||
}
|
||||
|
||||
func NewRouterExecutor(executor Executor) Router {
|
||||
return Router{r: setupRouter(executor)}
|
||||
func NewRouterExecutor(executor handler.CommandExecutor) Router {
|
||||
return Router{r: setupRouter(initConfig(), executor)}
|
||||
}
|
||||
|
||||
func setupRouter(executor Executor) *gin.Engine {
|
||||
func setupRouter(config controller.Config, executor handler.CommandExecutor) *gin.Engine {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
r := gin.New()
|
||||
r.Use(gin.Recovery())
|
||||
@@ -53,7 +43,11 @@ func setupRouter(executor Executor) *gin.Engine {
|
||||
v.RegisterValidation("notblank", notBlankStringValidator)
|
||||
}
|
||||
|
||||
r.GET("/api/v1/status", StatusHandler)
|
||||
r.PUT("/api/v1/command", LimitMiddleware(1), func(ctx *gin.Context) { CommandHandler(ctx, executor) })
|
||||
groupV1 := r.Group("/api/v1")
|
||||
|
||||
groupV1.GET("/status", func(ctx *gin.Context) { handler.StatusHandler(ctx, config) })
|
||||
groupV1.POST("/init", func(ctx *gin.Context) { handler.InitHandler(ctx, config) })
|
||||
groupV1.PUT("/command", LimitMiddleware(1), func(ctx *gin.Context) { handler.CommandHandler(ctx, config, executor) })
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -2,9 +2,14 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
)
|
||||
|
||||
func SetupRouter() *gin.Engine {
|
||||
return setupRouter(func(c rest.Command) error { return nil })
|
||||
return SetupRouterConfig(nil)
|
||||
}
|
||||
|
||||
func SetupRouterConfig(config controller.Config) *gin.Engine {
|
||||
return setupRouter(config, func(controller.Config, rest.Command) error { return nil })
|
||||
}
|
||||
|
||||
@@ -10,71 +10,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
"github.com/iliadenisov/galaxy/internal/router"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRouter(t *testing.T) {
|
||||
r := router.SetupRouter()
|
||||
|
||||
exampleCommand := rest.Command{
|
||||
Race: "SomeRace",
|
||||
Vote: &rest.CommandVote{
|
||||
Recipient: "AnotherRace",
|
||||
},
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("PUT", "/api/v1/command", cmdBody(exampleCommand))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code, w.Body)
|
||||
|
||||
// error: notblank validator
|
||||
exampleCommand.Race = ""
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PUT", "/api/v1/command", cmdBody(exampleCommand))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
|
||||
exampleCommand.Race = " "
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PUT", "/api/v1/command", cmdBody(exampleCommand))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
|
||||
// error: no commands
|
||||
exampleCommand = rest.Command{
|
||||
Race: "SomeRace",
|
||||
}
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PUT", "/api/v1/command", cmdBody(exampleCommand))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
|
||||
// error: more than one command
|
||||
exampleCommand = rest.Command{
|
||||
Race: "SomeRace",
|
||||
Vote: &rest.CommandVote{
|
||||
Recipient: "AnotherRace",
|
||||
},
|
||||
DeclarePeace: &rest.CommandDeclarePeace{
|
||||
Opponent: "OpponentRace",
|
||||
},
|
||||
}
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PUT", "/api/v1/command", cmdBody(exampleCommand))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
}
|
||||
|
||||
func TestLimitConnections(t *testing.T) {
|
||||
r := limitTestingRouter()
|
||||
|
||||
@@ -94,8 +33,8 @@ func TestLimitConnections(t *testing.T) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func cmdBody(cmd rest.Command) *strings.Reader {
|
||||
commandJson, _ := json.Marshal(cmd)
|
||||
func asBody(body any) *strings.Reader {
|
||||
commandJson, _ := json.Marshal(body)
|
||||
return strings.NewReader(string(commandJson))
|
||||
}
|
||||
|
||||
|
||||
@@ -17,5 +17,4 @@ func TestGetStatus(t *testing.T) {
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotImplemented, w.Code, w.Body)
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user