mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Fix input async tasks + tests (#137)
This commit is contained in:
@@ -16,6 +16,7 @@ var (
|
||||
ErrRoutesList = errors.New("Could not list routes from datastore")
|
||||
ErrRoutesNotFound = errors.New("Route not found")
|
||||
ErrRoutesMissingNew = errors.New("Missing new route")
|
||||
ErrInvalidPayload = errors.New("Invalid payload")
|
||||
)
|
||||
|
||||
type Routes []*Route
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
)
|
||||
|
||||
func TestRunnerHello(t *testing.T) {
|
||||
t.Skip()
|
||||
runner, err := New(NewMetricLogger())
|
||||
if err != nil {
|
||||
t.Fatalf("Test error during New() - %s", err)
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iron-io/functions/api/models"
|
||||
"github.com/iron-io/functions/api/runner"
|
||||
"github.com/iron-io/runner/common"
|
||||
)
|
||||
|
||||
type appResponse struct {
|
||||
Message string `json:"message"`
|
||||
App *models.App `json:"app"`
|
||||
}
|
||||
|
||||
type appsResponse struct {
|
||||
Message string `json:"message"`
|
||||
Apps models.Apps `json:"apps"`
|
||||
}
|
||||
|
||||
type routeResponse struct {
|
||||
Message string `json:"message"`
|
||||
Route *models.Route `json:"route"`
|
||||
}
|
||||
|
||||
type routesResponse struct {
|
||||
Message string `json:"message"`
|
||||
Routes models.Routes `json:"routes"`
|
||||
}
|
||||
|
||||
type tasksResponse struct {
|
||||
Message string `json:"message"`
|
||||
Task models.Task `json:"tasksResponse"`
|
||||
}
|
||||
|
||||
func testRouter() *gin.Engine {
|
||||
r := gin.Default()
|
||||
ctx := context.Background()
|
||||
r.Use(func(c *gin.Context) {
|
||||
ctx, _ := common.LoggerWithFields(ctx, extractFields(c))
|
||||
c.Set("ctx", ctx)
|
||||
c.Next()
|
||||
})
|
||||
bindHandlers(r,
|
||||
func(ctx *gin.Context) {
|
||||
handleRequest(ctx, nil)
|
||||
},
|
||||
func(ctx *gin.Context) {})
|
||||
return r
|
||||
}
|
||||
|
||||
func testRunner(t *testing.T) *runner.Runner {
|
||||
r, err := runner.New(runner.NewMetricLogger())
|
||||
if err != nil {
|
||||
t.Fatal("Test: failed to create new runner")
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func routerRequest(t *testing.T, router *gin.Engine, method, path string, body io.Reader) (*http.Request, *httptest.ResponseRecorder) {
|
||||
req, err := http.NewRequest(method, "http://localhost:8080"+path, body)
|
||||
if err != nil {
|
||||
t.Fatalf("Test: Could not create %s request to %s: %v", method, path, err)
|
||||
}
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
return req, rec
|
||||
}
|
||||
|
||||
func getErrorResponse(t *testing.T, rec *httptest.ResponseRecorder) models.Error {
|
||||
respBody, err := ioutil.ReadAll(rec.Body)
|
||||
if err != nil {
|
||||
t.Error("Test: Expected not empty response body")
|
||||
}
|
||||
|
||||
var errResp models.Error
|
||||
err = json.Unmarshal(respBody, &errResp)
|
||||
if err != nil {
|
||||
t.Error("Test: Expected response body to be a valid models.Error object")
|
||||
}
|
||||
|
||||
return errResp
|
||||
}
|
||||
@@ -30,6 +30,11 @@ func handleSpecial(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func toEnvName(envtype, name string) string {
|
||||
name = strings.ToUpper(strings.Replace(name, "-", "_", -1))
|
||||
return fmt.Sprintf("%s_%s", envtype, name)
|
||||
}
|
||||
|
||||
func handleRequest(c *gin.Context, enqueue models.Enqueue) {
|
||||
if strings.HasPrefix(c.Request.URL.Path, "/v1") {
|
||||
c.Status(http.StatusNotFound)
|
||||
@@ -56,9 +61,7 @@ func handleRequest(c *gin.Context, enqueue models.Enqueue) {
|
||||
}()
|
||||
} else if c.Request.Method == "GET" {
|
||||
reqPayload := c.Request.URL.Query().Get("payload")
|
||||
if len(reqPayload) > 0 {
|
||||
payload = strings.NewReader(reqPayload)
|
||||
}
|
||||
payload = strings.NewReader(reqPayload)
|
||||
}
|
||||
|
||||
appName := c.Param("app")
|
||||
@@ -114,22 +117,22 @@ func handleRequest(c *gin.Context, enqueue models.Enqueue) {
|
||||
|
||||
// app config
|
||||
for k, v := range app.Config {
|
||||
envVars["CONFIG_"+strings.ToUpper(k)] = v
|
||||
envVars[toEnvName("CONFIG", k)] = v
|
||||
}
|
||||
|
||||
// route config
|
||||
for k, v := range el.Config {
|
||||
envVars["CONFIG_"+strings.ToUpper(k)] = v
|
||||
envVars[toEnvName("CONFIG", k)] = v
|
||||
}
|
||||
|
||||
// params
|
||||
for _, param := range params {
|
||||
envVars["PARAM_"+strings.ToUpper(param.Key)] = param.Value
|
||||
envVars[toEnvName("PARAM", param.Key)] = param.Value
|
||||
}
|
||||
|
||||
// headers
|
||||
for header, value := range c.Request.Header {
|
||||
envVars["HEADER_"+strings.ToUpper(header)] = strings.Join(value, " ")
|
||||
envVars[toEnvName("HEADER", header)] = strings.Join(value, " ")
|
||||
}
|
||||
|
||||
cfg := &runner.Config{
|
||||
@@ -148,6 +151,14 @@ func handleRequest(c *gin.Context, enqueue models.Enqueue) {
|
||||
var result drivers.RunResult
|
||||
switch el.Type {
|
||||
case "async":
|
||||
// Read payload
|
||||
pl, err := ioutil.ReadAll(cfg.Stdin)
|
||||
if err != nil {
|
||||
log.WithError(err).Error(models.ErrInvalidPayload)
|
||||
c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidPayload))
|
||||
return
|
||||
}
|
||||
|
||||
// Create Task
|
||||
priority := int32(0)
|
||||
task := &models.Task{}
|
||||
@@ -155,6 +166,8 @@ func handleRequest(c *gin.Context, enqueue models.Enqueue) {
|
||||
task.ID = cfg.ID
|
||||
task.RouteName = cfg.AppName
|
||||
task.Priority = &priority
|
||||
task.EnvVars = cfg.Env
|
||||
task.Payload = string(pl)
|
||||
// Push to queue
|
||||
enqueue(task)
|
||||
log.Info("Added new task to queue")
|
||||
@@ -175,10 +188,6 @@ func handleRequest(c *gin.Context, enqueue models.Enqueue) {
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.WithError(err).Error(models.ErrRunnerRunRoute)
|
||||
c.JSON(http.StatusInternalServerError, simpleError(models.ErrRunnerRunRoute))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
105
api/server/runner_async_test.go
Normal file
105
api/server/runner_async_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iron-io/functions/api/datastore"
|
||||
"github.com/iron-io/functions/api/models"
|
||||
"github.com/iron-io/functions/api/mqs"
|
||||
"github.com/iron-io/runner/common"
|
||||
)
|
||||
|
||||
func testRouterAsync(enqueueFunc models.Enqueue) *gin.Engine {
|
||||
r := gin.New()
|
||||
r.Use(gin.Logger())
|
||||
ctx := context.Background()
|
||||
r.Use(func(c *gin.Context) {
|
||||
ctx, _ := common.LoggerWithFields(ctx, extractFields(c))
|
||||
c.Set("ctx", ctx)
|
||||
c.Next()
|
||||
})
|
||||
bindHandlers(r,
|
||||
func(ctx *gin.Context) {
|
||||
handleRequest(ctx, enqueueFunc)
|
||||
},
|
||||
func(ctx *gin.Context) {})
|
||||
return r
|
||||
}
|
||||
|
||||
func TestRouteRunnerAsyncExecution(t *testing.T) {
|
||||
New(&datastore.Mock{
|
||||
FakeApps: []*models.App{
|
||||
{Name: "myapp", Config: map[string]string{"app": "true"}},
|
||||
},
|
||||
FakeRoutes: []*models.Route{
|
||||
{Type: "async", Path: "/myroute", AppName: "myapp", Image: "iron/hello", Config: map[string]string{"test": "true"}},
|
||||
{Type: "async", Path: "/myerror", AppName: "myapp", Image: "iron/error", Config: map[string]string{"test": "true"}},
|
||||
{Type: "async", Path: "/myroute/:param", AppName: "myapp", Image: "iron/hello", Config: map[string]string{"test": "true"}},
|
||||
},
|
||||
}, &mqs.Mock{}, testRunner(t))
|
||||
|
||||
for i, test := range []struct {
|
||||
path string
|
||||
body string
|
||||
headers map[string][]string
|
||||
expectedCode int
|
||||
expectedEnv map[string]string
|
||||
}{
|
||||
{"/r/myapp/myroute", ``, map[string][]string{}, http.StatusOK, map[string]string{"CONFIG_TEST": "true", "CONFIG_APP": "true"}},
|
||||
{
|
||||
"/r/myapp/myroute/1",
|
||||
``,
|
||||
map[string][]string{"X-Function": []string{"test"}},
|
||||
http.StatusOK,
|
||||
map[string]string{
|
||||
"CONFIG_TEST": "true",
|
||||
"CONFIG_APP": "true",
|
||||
"PARAM_PARAM": "1",
|
||||
"HEADER_X_FUNCTION": "test",
|
||||
},
|
||||
},
|
||||
{"/r/myapp/myerror", ``, map[string][]string{}, http.StatusOK, map[string]string{"CONFIG_TEST": "true", "CONFIG_APP": "true"}},
|
||||
{"/r/myapp/myroute", `{ "name": "test" }`, map[string][]string{}, http.StatusOK, map[string]string{"CONFIG_TEST": "true", "CONFIG_APP": "true"}},
|
||||
} {
|
||||
body := bytes.NewBuffer([]byte(test.body))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
router := testRouterAsync(func(task *models.Task) (*models.Task, error) {
|
||||
if test.body != task.Payload {
|
||||
t.Errorf("Test %d: Expected task Payload to be the same as the test body", i)
|
||||
}
|
||||
|
||||
if test.expectedEnv != nil {
|
||||
for name, value := range test.expectedEnv {
|
||||
if value != task.EnvVars[name] {
|
||||
t.Errorf("Test %d: Expected header `%s` to be `%s` but was `%s`",
|
||||
i, name, value, task.EnvVars[name])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
return task, nil
|
||||
})
|
||||
|
||||
req, rec := newRouterRequest(t, "POST", test.path, body)
|
||||
for name, value := range test.headers {
|
||||
req.Header.Set(name, value[0])
|
||||
}
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != test.expectedCode {
|
||||
t.Errorf("Test %d: Expected status code to be %d but was %d",
|
||||
i, test.expectedCode, rec.Code)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,17 @@ import (
|
||||
"github.com/iron-io/functions/api/datastore"
|
||||
"github.com/iron-io/functions/api/models"
|
||||
"github.com/iron-io/functions/api/mqs"
|
||||
"github.com/iron-io/functions/api/runner"
|
||||
)
|
||||
|
||||
func testRunner(t *testing.T) *runner.Runner {
|
||||
r, err := runner.New(runner.NewMetricLogger())
|
||||
if err != nil {
|
||||
t.Fatal("Test: failed to create new runner")
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func TestRouteRunnerGet(t *testing.T) {
|
||||
New(&datastore.Mock{
|
||||
FakeApps: []*models.App{
|
||||
@@ -88,7 +97,6 @@ func TestRouteRunnerPost(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRouteRunnerExecution(t *testing.T) {
|
||||
t.Skip()
|
||||
New(&datastore.Mock{
|
||||
FakeApps: []*models.App{
|
||||
{Name: "myapp", Config: models.Config{}},
|
||||
|
||||
@@ -30,7 +30,7 @@ type Server struct {
|
||||
|
||||
func New(ds models.Datastore, mq models.MessageQueue, r *runner.Runner) *Server {
|
||||
Api = &Server{
|
||||
Router: gin.Default(),
|
||||
Router: gin.New(),
|
||||
Datastore: ds,
|
||||
MQ: mq,
|
||||
Runner: r,
|
||||
@@ -150,6 +150,8 @@ func (s *Server) Run(ctx context.Context) {
|
||||
}
|
||||
|
||||
func bindHandlers(engine *gin.Engine, reqHandler func(ginC *gin.Context), taskHandler func(ginC *gin.Context)) {
|
||||
engine.Use(gin.Logger())
|
||||
|
||||
engine.GET("/", handlePing)
|
||||
engine.GET("/version", handleVersion)
|
||||
|
||||
@@ -185,3 +187,28 @@ func bindHandlers(engine *gin.Engine, reqHandler func(ginC *gin.Context), taskHa
|
||||
func simpleError(err error) *models.Error {
|
||||
return &models.Error{&models.ErrorBody{Message: err.Error()}}
|
||||
}
|
||||
|
||||
type appResponse struct {
|
||||
Message string `json:"message"`
|
||||
App *models.App `json:"app"`
|
||||
}
|
||||
|
||||
type appsResponse struct {
|
||||
Message string `json:"message"`
|
||||
Apps models.Apps `json:"apps"`
|
||||
}
|
||||
|
||||
type routeResponse struct {
|
||||
Message string `json:"message"`
|
||||
Route *models.Route `json:"route"`
|
||||
}
|
||||
|
||||
type routesResponse struct {
|
||||
Message string `json:"message"`
|
||||
Routes models.Routes `json:"routes"`
|
||||
}
|
||||
|
||||
type tasksResponse struct {
|
||||
Message string `json:"message"`
|
||||
Task models.Task `json:"tasksResponse"`
|
||||
}
|
||||
|
||||
@@ -2,17 +2,79 @@ package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iron-io/functions/api/datastore"
|
||||
"github.com/iron-io/functions/api/models"
|
||||
"github.com/iron-io/functions/api/mqs"
|
||||
"github.com/iron-io/runner/common"
|
||||
)
|
||||
|
||||
var tmpBolt = "/tmp/func_test_bolt.db"
|
||||
|
||||
func testRouter() *gin.Engine {
|
||||
r := gin.New()
|
||||
r.Use(gin.Logger())
|
||||
ctx := context.Background()
|
||||
r.Use(func(c *gin.Context) {
|
||||
ctx, _ := common.LoggerWithFields(ctx, extractFields(c))
|
||||
c.Set("ctx", ctx)
|
||||
c.Next()
|
||||
})
|
||||
bindHandlers(r,
|
||||
func(ctx *gin.Context) {
|
||||
handleRequest(ctx, nil)
|
||||
},
|
||||
func(ctx *gin.Context) {})
|
||||
return r
|
||||
}
|
||||
|
||||
func routerRequest(t *testing.T, router *gin.Engine, method, path string, body io.Reader) (*http.Request, *httptest.ResponseRecorder) {
|
||||
req, err := http.NewRequest(method, "http://localhost:8080"+path, body)
|
||||
if err != nil {
|
||||
t.Fatalf("Test: Could not create %s request to %s: %v", method, path, err)
|
||||
}
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
return req, rec
|
||||
}
|
||||
|
||||
func newRouterRequest(t *testing.T, method, path string, body io.Reader) (*http.Request, *httptest.ResponseRecorder) {
|
||||
req, err := http.NewRequest(method, "http://localhost:8080"+path, body)
|
||||
if err != nil {
|
||||
t.Fatalf("Test: Could not create %s request to %s: %v", method, path, err)
|
||||
}
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
return req, rec
|
||||
}
|
||||
|
||||
func getErrorResponse(t *testing.T, rec *httptest.ResponseRecorder) models.Error {
|
||||
respBody, err := ioutil.ReadAll(rec.Body)
|
||||
if err != nil {
|
||||
t.Error("Test: Expected not empty response body")
|
||||
}
|
||||
|
||||
var errResp models.Error
|
||||
err = json.Unmarshal(respBody, &errResp)
|
||||
if err != nil {
|
||||
t.Error("Test: Expected response body to be a valid models.Error object")
|
||||
}
|
||||
|
||||
return errResp
|
||||
}
|
||||
|
||||
func prepareBolt(t *testing.T) (models.Datastore, func()) {
|
||||
ds, err := datastore.New("bolt://" + tmpBolt)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user