Full stack tests

This commit is contained in:
Denis Makogon
2017-07-05 12:38:09 -07:00
committed by Reed Allman
parent c85571f51d
commit adf61c77be
28 changed files with 1264 additions and 60 deletions

View File

@@ -51,6 +51,16 @@ test_job:
script: script:
- DOCKER_LOCATION=container_ip ./test.sh - DOCKER_LOCATION=container_ip ./test.sh
#integration_tests:
# stage: test
# script:
# - go build -o functions-alpine
# - docker build -t funcy/functions:latest .
# - make docker-test-run-with-bolt
# - make docker-test-run-with-mysql
# - make docker-test-run-with-postgres
# - make docker-test-run-with-redis
deploy_job: deploy_job:
only: only:
- tags - tags

View File

@@ -37,6 +37,18 @@ docker-build:
docker-run: docker-build docker-run: docker-build
docker run --rm --privileged -it -e NO_PROXY -e HTTP_PROXY -e LOG_LEVEL=debug -e "DB_URL=bolt:///app/data/bolt.db" -v ${CURDIR}/data:/app/data -p 8080:8080 funcy/functions docker run --rm --privileged -it -e NO_PROXY -e HTTP_PROXY -e LOG_LEVEL=debug -e "DB_URL=bolt:///app/data/bolt.db" -v ${CURDIR}/data:/app/data -p 8080:8080 funcy/functions
docker-test-run-with-bolt:
./api_test.sh bolt
docker-test-run-with-mysql:
./api_test.sh mysql
docker-test-run-with-postgres:
./api_test.sh postgres
docker-test-run-with-redis:
./api_test.sh redis
docker-test: docker-test:
docker run -ti --privileged --rm -e LOG_LEVEL=debug \ docker run -ti --privileged --rm -e LOG_LEVEL=debug \
-v /var/run/docker.sock:/var/run/docker.sock \ -v /var/run/docker.sock:/var/run/docker.sock \

52
api_test.sh Executable file
View File

@@ -0,0 +1,52 @@
set -ex
case "$1" in
"bolt" )
docker rm -fv func-server || echo No prev func-server container
docker run --name func-server --privileged -v /var/run/docker.sock:/var/run/docker.sock -d -e NO_PROXY -e HTTP_PROXY -e DOCKER_HOST=${DOCKER_HOST} -e LOG_LEVEL=debug -p 8080:8080 funcy/functions
sleep 1
;;
"mysql" )
docker rm -fv func-mysql-test || echo No prev mysql test db container
docker rm -fv func-server || echo No prev func-server container
docker run --name func-mysql-test -p 3307:3306 -e MYSQL_DATABASE=funcs -e MYSQL_ROOT_PASSWORD=root -d mysql
sleep 8
export MYSQL_HOST="$(docker inspect -f '{{.NetworkSettings.IPAddress}}' func-mysql-test)"
export MYSQL_PORT=3306
docker run --name func-server --privileged -d -e NO_PROXY -e HTTP_PROXY -e DOCKER_HOST=${DOCKER_HOST} -e LOG_LEVEL=debug -e "DB_URL=mysql://root:root@tcp(${MYSQL_HOST}:${MYSQL_PORT})/funcs" -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock funcy/functions
;;
"postgres" )
docker rm -fv func-postgres-test || echo No prev test db container
docker rm -fv func-server || echo No prev func-server container
docker run --name func-postgres-test -p -e "POSTGRES_DB=funcs" 5432:5432 -d postgres
sleep 8
export POSTGRES_HOST="$(docker inspect -f '{{.NetworkSettings.IPAddress}}' func-postgres-test)"
export POSTGRES_PORT=5432
docker run --name func-server --privileged -d -e NO_PROXY -e HTTP_PROXY -e DOCKER_HOST=${DOCKER_HOST} -e LOG_LEVEL=debug -e "DB_URL=postgres://postgres@${POSTGRES_HOST}:${POSTGRES_PORT}/funcs?sslmode=disable" -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock funcy/functions
;;
"redis" )
docker rm -fv func-redis-test|| echo No prev redis test db container
docker rm -fv func-server || echo No prev func-server container
docker run --name func-redis-test -p 6379:6379 -d redis
sleep 8
export REDIS_HOST="$(docker inspect -f '{{.NetworkSettings.IPAddress}}' func-redis-test)"
export REDIS_PORT=6379
docker run --name func-server --privileged -d -e NO_PROXY -e HTTP_PROXY -e DOCKER_HOST=${DOCKER_HOST} -e LOG_LEVEL=debug -e "DB_URL=redis://${REDIS_HOST}:${REDIS_PORT}/" -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock funcy/functions
;;
esac
cd fn/tests && API_URL="http://$(docker inspect -f '{{.NetworkSettings.IPAddress}}' func-server):8080" go test -v ./...; cd ../../

View File

@@ -13,7 +13,10 @@ dep:
dep ensure dep ensure
test: test:
go test $(go list ./... | grep -v /vendor/) go test $(go list ./... | grep -v /vendor/ | grep -v /tests)
test-integration:
cd tests/ && go test -v ./...; cd ..
release: release:
GOOS=linux go build -o fn_linux GOOS=linux go build -o fn_linux

View File

@@ -8,6 +8,7 @@ import (
"context" "context"
fnclient "github.com/funcy/functions_go/client" fnclient "github.com/funcy/functions_go/client"
client "gitlab-odx.oracle.com/odx/functions/fn/client"
apiapps "github.com/funcy/functions_go/client/apps" apiapps "github.com/funcy/functions_go/client/apps"
"github.com/funcy/functions_go/models" "github.com/funcy/functions_go/models"
"github.com/jmoiron/jsonq" "github.com/jmoiron/jsonq"
@@ -20,7 +21,7 @@ type appsCmd struct {
} }
func apps() cli.Command { func apps() cli.Command {
a := appsCmd{client: apiClient()} a := appsCmd{client: client.APIClient()}
return cli.Command{ return cli.Command{
Name: "apps", Name: "apps",

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
fnclient "github.com/funcy/functions_go/client" fnclient "github.com/funcy/functions_go/client"
client "gitlab-odx.oracle.com/odx/functions/fn/client"
apicall "github.com/funcy/functions_go/client/call" apicall "github.com/funcy/functions_go/client/call"
"github.com/funcy/functions_go/models" "github.com/funcy/functions_go/models"
"github.com/urfave/cli" "github.com/urfave/cli"
@@ -15,7 +16,7 @@ type callsCmd struct {
} }
func calls() cli.Command { func calls() cli.Command {
c := callsCmd{client: apiClient()} c := callsCmd{client: client.APIClient()}
return cli.Command{ return cli.Command{
Name: "calls", Name: "calls",

View File

@@ -1,4 +1,4 @@
package main package client
import ( import (
"os" "os"
@@ -11,7 +11,7 @@ import (
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
) )
func host() string { func Host() string {
apiURL := os.Getenv("API_URL") apiURL := os.Getenv("API_URL")
if apiURL == "" { if apiURL == "" {
apiURL = "http://localhost:8080" apiURL = "http://localhost:8080"
@@ -21,12 +21,12 @@ func host() string {
if err != nil { if err != nil {
log.Fatalln("Couldn't parse API URL:", err) log.Fatalln("Couldn't parse API URL:", err)
} }
log.Println("trace: Host:", u.Host)
return u.Host return u.Host
} }
func apiClient() *fnclient.Functions { func APIClient() *fnclient.Functions {
transport := httptransport.New(host(), "/v1", []string{"http"}) transport := httptransport.New(Host(), "/v1", []string{"http"})
if os.Getenv("FN_TOKEN") != "" { if os.Getenv("FN_TOKEN") != "" {
transport.DefaultAuthentication = httptransport.BearerToken(os.Getenv("FN_TOKEN")) transport.DefaultAuthentication = httptransport.BearerToken(os.Getenv("FN_TOKEN"))
} }

53
fn/client/call_fn.go Normal file
View File

@@ -0,0 +1,53 @@
package client
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func EnvAsHeader(req *http.Request, selectedEnv []string) {
detectedEnv := os.Environ()
if len(selectedEnv) > 0 {
detectedEnv = selectedEnv
}
for _, e := range detectedEnv {
kv := strings.Split(e, "=")
name := kv[0]
req.Header.Set(name, os.Getenv(name))
}
}
func CallFN(u string, content io.Reader, output io.Writer, method string, env []string) error {
if method == "" {
if content == nil {
method = "GET"
} else {
method = "POST"
}
}
req, err := http.NewRequest(method, u, content)
if err != nil {
return fmt.Errorf("error running route: %s", err)
}
req.Header.Set("Content-Type", "application/json")
if len(env) > 0 {
EnvAsHeader(req, env)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("error running route: %s", err)
}
io.Copy(output, resp.Body)
return nil
}

View File

@@ -11,6 +11,7 @@ import (
"time" "time"
functions "github.com/funcy/functions_go" functions "github.com/funcy/functions_go"
client "gitlab-odx.oracle.com/odx/functions/fn/client"
"github.com/funcy/functions_go/models" "github.com/funcy/functions_go/models"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -150,7 +151,7 @@ func (p *deploycmd) route(c *cli.Context, ff *funcfile) error {
return fmt.Errorf("error setting endpoint: %v", err) return fmt.Errorf("error setting endpoint: %v", err)
} }
routesCmd := routesCmd{client: apiClient()} routesCmd := routesCmd{client: client.APIClient()}
rt := &models.Route{} rt := &models.Route{}
if err := routeWithFuncFile(c, ff, rt); err != nil { if err := routeWithFuncFile(c, ff, rt); err != nil {
return fmt.Errorf("error getting route with funcfile: %s", err) return fmt.Errorf("error getting route with funcfile: %s", err)

View File

@@ -5,8 +5,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"net/http"
"net/url" "net/url"
"os" "os"
"path" "path"
@@ -14,6 +12,8 @@ import (
"text/tabwriter" "text/tabwriter"
fnclient "github.com/funcy/functions_go/client" fnclient "github.com/funcy/functions_go/client"
client "gitlab-odx.oracle.com/odx/functions/fn/client"
utils "gitlab-odx.oracle.com/odx/functions/fn/utils"
apiroutes "github.com/funcy/functions_go/client/routes" apiroutes "github.com/funcy/functions_go/client/routes"
fnmodels "github.com/funcy/functions_go/models" fnmodels "github.com/funcy/functions_go/models"
"github.com/jmoiron/jsonq" "github.com/jmoiron/jsonq"
@@ -57,7 +57,7 @@ var routeFlags = []cli.Flag{
func routes() cli.Command { func routes() cli.Command {
r := routesCmd{client: apiClient()} r := routesCmd{client: client.APIClient()}
return cli.Command{ return cli.Command{
Name: "routes", Name: "routes",
@@ -132,7 +132,7 @@ func routes() cli.Command {
} }
func call() cli.Command { func call() cli.Command {
r := routesCmd{client: apiClient()} r := routesCmd{client: client.APIClient()}
return cli.Command{ return cli.Command{
Name: "call", Name: "call",
@@ -191,55 +191,12 @@ func (a *routesCmd) call(c *cli.Context) error {
u := url.URL{ u := url.URL{
Scheme: "http", Scheme: "http",
Host: host(), Host: client.Host(),
} }
u.Path = path.Join(u.Path, "r", appName, route) u.Path = path.Join(u.Path, "r", appName, route)
content := stdin() content := stdin()
return callfn(u.String(), content, os.Stdout, c.String("method"), c.StringSlice("e")) return client.CallFN(u.String(), content, os.Stdout, c.String("method"), c.StringSlice("e"))
}
func callfn(u string, content io.Reader, output io.Writer, method string, env []string) error {
if method == "" {
if content == nil {
method = "GET"
} else {
method = "POST"
}
}
req, err := http.NewRequest(method, u, content)
if err != nil {
return fmt.Errorf("error running route: %s", err)
}
req.Header.Set("Content-Type", "application/json")
if len(env) > 0 {
envAsHeader(req, env)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("error running route: %s", err)
}
io.Copy(output, resp.Body)
return nil
}
func envAsHeader(req *http.Request, selectedEnv []string) {
detectedEnv := os.Environ()
if len(selectedEnv) > 0 {
detectedEnv = selectedEnv
}
for _, e := range detectedEnv {
kv := strings.Split(e, "=")
name := kv[0]
req.Header.Set(name, os.Getenv(name))
}
} }
func routeWithFlags(c *cli.Context, rt *fnmodels.Route) { func routeWithFlags(c *cli.Context, rt *fnmodels.Route) {

View File

@@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"os" "os"
"testing" "testing"
"gitlab-odx.oracle.com/odx/functions/fn/utils"
) )
func TestEnvAsHeader(t *testing.T) { func TestEnvAsHeader(t *testing.T) {
@@ -17,7 +18,7 @@ func TestEnvAsHeader(t *testing.T) {
} }
for _, selectedEnv := range cases { for _, selectedEnv := range cases {
req, _ := http.NewRequest("GET", "http://www.example.com", nil) req, _ := http.NewRequest("GET", "http://www.example.com", nil)
envAsHeader(req, selectedEnv) utils.EnvAsHeader(req, selectedEnv)
if found := req.Header.Get("k"); found != expectedValue { if found := req.Header.Get("k"); found != expectedValue {
t.Errorf("not found expected header: %v", found) t.Errorf("not found expected header: %v", found)
} }

View File

@@ -11,6 +11,7 @@ import (
"strings" "strings"
"time" "time"
"gitlab-odx.oracle.com/odx/functions/fn/client"
functions "github.com/funcy/functions_go" functions "github.com/funcy/functions_go"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -173,7 +174,7 @@ func runremotetest(target string, in, expectedOut, expectedErr *string, env map[
os.Setenv(k, v) os.Setenv(k, v)
restrictedEnv = append(restrictedEnv, k) restrictedEnv = append(restrictedEnv, k)
} }
if err := callfn(target, stdin, &stdout, "", restrictedEnv); err != nil { if err := client.CallFN(target, stdin, &stdout, "", restrictedEnv); err != nil {
return fmt.Errorf("%v\nstdout:%s\n", err, stdout.String()) return fmt.Errorf("%v\nstdout:%s\n", err, stdout.String())
} }

19
fn/tests/README.md Normal file
View File

@@ -0,0 +1,19 @@
Oracle Functions integration API tests
======================================
Test dependencies
-----------------
```bash
DOCKER_HOST - for building images
API_URL - Oracle Functions API endpoint
```
How to run tests?
-----------------
```bash
export API_URL=http://localhost:8080
go test -v ./...
```

117
fn/tests/apps_test.go Normal file
View File

@@ -0,0 +1,117 @@
package tests
import (
"testing"
"time"
"strings"
"reflect"
"github.com/funcy/functions_go/client/apps"
)
func TestApps(t *testing.T) {
s := SetupDefaultSuite()
t.Run("no-apps-found-test", func(t *testing.T) {
cfg := &apps.GetAppsParams{
Context: s.Context,
}
cfg.WithTimeout(time.Second * 60)
appsPayload, err := s.Client.Apps.GetApps(cfg)
CheckAppResponseError(t, err)
// on this step we should not have any apps so far
actualApps := appsPayload.Payload.Apps
if len(actualApps) != 0 {
t.Fatalf("Expected to see no apps, but found %v apps.", len(actualApps))
}
t.Logf("Test `%v` passed", t.Name())
})
t.Run("app-not-found-test", func(t *testing.T) {
cfg := &apps.GetAppsAppParams{
App: "missing-app",
Context: s.Context,
}
cfg.WithTimeout(time.Second * 60)
_, err := s.Client.Apps.GetAppsApp(cfg)
CheckAppResponseError(t, err)
t.Logf("Test `%v` passed", t.Name())
})
t.Run("create-app-no-config-test", func(t *testing.T) {
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("delete-app-no-config", func(t *testing.T) {
DeleteApp(t, s.Context, s.Client, s.AppName)
t.Logf("Test `%v` passed", t.Name())
})
t.Run("create-app-with-config-test", func(t *testing.T) {
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{"A": "a"})
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("inspect-app-with-config-test", func(t *testing.T) {
cfg := &apps.GetAppsAppParams{
Context: s.Context,
App: s.AppName,
}
appPayload, err := s.Client.Apps.GetAppsApp(cfg)
CheckAppResponseError(t, err)
appBody := appPayload.Payload.App
val, ok := appBody.Config["A"]
if !ok {
t.Fatal("Error during app config inspect: config map misses required entity `A` with value `a`.")
}
if !strings.Contains("a", val) {
t.Fatalf("App config value is different. Expected: `a`. Actual %v", val)
}
DeleteApp(t, s.Context, s.Client, s.AppName)
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("patch-override-app-config", func(t *testing.T){
config := map[string]string{
"A": "b",
}
appPayload := UpdateApp(t, s.Context, s.Client, s.AppName, config)
val, ok := appPayload.Payload.App.Config["A"]
if !ok {
t.Fatal("Error during app config inspect: config map misses required entity `A` with value `a`.")
}
if !strings.Contains("b", val) {
t.Fatalf("App config value is different. Expected: `b`. Actual %v", val)
}
DeleteApp(t, s.Context, s.Client, s.AppName)
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("patch-add-app-config", func(t *testing.T) {
config := map[string]string{
"B": "b",
}
appPayload := UpdateApp(t, s.Context, s.Client, s.AppName, config)
val, ok := appPayload.Payload.App.Config["B"]
if !ok {
t.Fatal("Error during app config inspect: config map misses required entity `B` with value `b`.")
}
if !strings.Contains("b", val) {
t.Fatalf("App config value is different. Expected: `b`. Actual %v", val)
}
DeleteApp(t, s.Context, s.Client, s.AppName)
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("crete-app-duplicate", func(t *testing.T) {
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
_, err := CreateAppNoAssert(s.Context, s.Client, s.AppName, map[string]string{})
if reflect.TypeOf(err) != reflect.TypeOf(apps.NewPostAppsConflict()) {
CheckAppResponseError(t, err)
}
DeleteApp(t, s.Context, s.Client, s.AppName)
t.Logf("Test `%v` passed.", t.Name())
})
}

107
fn/tests/calls_test.go Normal file
View File

@@ -0,0 +1,107 @@
package tests
import (
"bytes"
"testing"
"time"
"net/url"
"path"
"gitlab-odx.oracle.com/odx/functions/fn/client"
"github.com/funcy/functions_go/client/call"
)
func TestCalls(t *testing.T) {
s := SetupDefaultSuite()
t.Run("list-calls-for-missing-app", func(t *testing.T) {
cfg := &call.GetAppsAppCallsRouteParams{
App: s.AppName,
Route: s.RoutePath,
Context: s.Context,
}
_, err := s.Client.Call.GetAppsAppCallsRoute(cfg)
if err == nil {
t.Fatalf("Must fail with missing app error, but got %s", err)
}
})
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
u := url.URL{
Scheme: "http",
Host: client.Host(),
}
u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath)
t.Run("list-calls-for-missing-route", func(t *testing.T) {
cfg := &call.GetAppsAppCallsRouteParams{
App: s.AppName,
Route: s.RoutePath,
Context: s.Context,
}
_, err := s.Client.Call.GetAppsAppCallsRoute(cfg)
if err == nil {
t.Fatalf("Must fail with missing route error, but got %s", err)
}
})
CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType,
s.RouteConfig, s.RouteHeaders)
t.Run("get-dummy-call", func(t *testing.T) {
cfg := &call.GetCallsCallParams{
Call: "dummy",
Context: s.Context,
}
cfg.WithTimeout(time.Second * 60)
_, err := s.Client.Call.GetCallsCall(cfg)
if err == nil {
t.Fatal("Must fail because `dummy` call does not exist.")
}
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("get-real-call", func(t *testing.T) {
callID := CallAsync(t, u, &bytes.Buffer{})
time.Sleep(time.Second * 2)
cfg := &call.GetCallsCallParams{
Call: callID,
Context: s.Context,
}
cfg.WithTimeout(time.Second * 60)
_, err := s.Client.Call.GetCallsCall(cfg)
if err != nil {
switch err.(type) {
case *call.GetCallsCallNotFound:
msg := err.(*call.GetCallsCallNotFound).Payload.Error.Message
t.Fatalf("Unexpected error occurred: %v.", msg)
}
}
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("list-calls", func(t *testing.T) {
cfg := &call.GetAppsAppCallsRouteParams{
App: s.AppName,
Route: s.RoutePath,
Context: s.Context,
}
calls, err := s.Client.Call.GetAppsAppCallsRoute(cfg)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if len(calls.Payload.Calls) == 0 {
t.Fatalf("Must fail. There should be at least one call to `%v` route.", s.RoutePath)
}
for _, c := range calls.Payload.Calls {
if c.Path != s.RoutePath {
t.Fatalf("Call path mismatch.\n\tExpected: %v\n\tActual: %v", c.Path, s.RoutePath)
}
}
})
DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath)
DeleteApp(t, s.Context, s.Client, s.AppName)
}

303
fn/tests/exec_test.go Normal file
View File

@@ -0,0 +1,303 @@
package tests
import (
"io"
"encoding/json"
"bytes"
"testing"
"time"
"net/url"
"path"
"strings"
"gitlab-odx.oracle.com/odx/functions/fn/client"
"github.com/funcy/functions_go/client/call"
"github.com/funcy/functions_go/client/operations"
)
type ErrMsg struct {
Message string `json:"message"`
}
type TimeoutBody struct{
Error ErrMsg `json:"error"`
CallID string `json:"request_id"`
}
func CallAsync(t *testing.T, u url.URL, content io.Reader) string {
output := &bytes.Buffer{}
err := client.CallFN(u.String(), content, output, "POST", []string{})
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
expectedOutput := "call_id"
if !strings.Contains(output.String(), expectedOutput) {
t.Fatalf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String())
}
type CallID struct {
CallID string `json:"call_id"`
}
callID := &CallID{}
json.NewDecoder(output).Decode(callID)
if callID.CallID == "" {
t.Fatalf("`call_id` not suppose to be empty string")
}
t.Logf("Async execution call ID: %v", callID.CallID)
return callID.CallID
}
func TestRouteExecutions(t *testing.T) {
s := SetupDefaultSuite()
newRouteType := "async"
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, "sync",
s.RouteConfig, s.RouteHeaders)
u := url.URL{
Scheme: "http",
Host: client.Host(),
}
u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath)
t.Run("run-sync-funcy/hello-no-input", func(t *testing.T) {
content := &bytes.Buffer{}
output := &bytes.Buffer{}
err := client.CallFN(u.String(), content, output, "POST", []string{})
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
expectedOutput := "Hello World!\n"
if !strings.Contains(expectedOutput, output.String()) {
t.Fatalf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String())
}
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("run-sync-funcy/hello-with-input", func(t *testing.T) {
content := &bytes.Buffer{}
json.NewEncoder(content).Encode(struct {
Name string
}{Name: "John"})
output := &bytes.Buffer{}
err := client.CallFN(u.String(), content, output, "POST", []string{})
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
expectedOutput := "Hello John!\n"
if !strings.Contains(expectedOutput, output.String()) {
t.Fatalf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String())
}
t.Logf("Test `%v` passed.", t.Name())
})
_, err := UpdateRoute(
t, s.Context, s.Client,
s.AppName, s.RoutePath,
s.Image, newRouteType, s.Format,
s.Memory, s.RouteConfig, s.RouteHeaders, "")
CheckRouteResponseError(t, err)
t.Run("run-async-funcy/hello", func(t *testing.T) {
CallAsync(t, u, &bytes.Buffer{})
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("run-async-funcy/hello-with-status-check", func(t *testing.T) {
callID := CallAsync(t, u, &bytes.Buffer{})
time.Sleep(time.Second * 2)
cfg := &call.GetCallsCallParams{
Call: callID,
Context: s.Context,
}
cfg.WithTimeout(time.Second * 60)
callResponse, err := s.Client.Call.GetCallsCall(cfg)
if err != nil {
switch err.(type) {
case *call.GetCallsCallNotFound:
msg := err.(*call.GetCallsCallNotFound).Payload.Error.Message
t.Fatalf("Unexpected error occurred: %v.", msg)
}
}
callObject := callResponse.Payload.Call
if callObject.AppName != s.AppName {
t.Fatalf("Call object app name mismatch.\n\tExpected: %v\n\tActual:%v", s.AppName, callObject.AppName)
}
if callObject.ID != callID {
t.Fatalf("Call object ID mismatch.\n\tExpected: %v\n\tActual:%v", callID, callObject.ID)
}
if callObject.Path != s.RoutePath {
t.Fatalf("Call object route path mismatch.\n\tExpected: %v\n\tActual:%v", s.RoutePath, callObject.Path)
}
if callObject.Status != "success" {
t.Fatalf("Call object status mismatch.\n\tExpected: %v\n\tActual:%v", "success", callObject.Status)
}
})
DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath)
routePath := "/timeout"
image := "funcy/timeout:0.0.1"
routeType := "sync"
CreateRoute(t, s.Context, s.Client, s.AppName, routePath, image, routeType,
s.RouteConfig, s.RouteHeaders)
t.Run("exec-timeout-test", func(t *testing.T) {
u := url.URL{
Scheme: "http",
Host: client.Host(),
}
u.Path = path.Join(u.Path, "r", s.AppName, routePath)
content := &bytes.Buffer{}
json.NewEncoder(content).Encode(struct {
Seconds int64 `json:"seconds"`
}{Seconds: 31})
output := &bytes.Buffer{}
client.CallFN(u.String(), content, output, "POST", []string{})
if !strings.Contains(output.String(), "Timed out") {
t.Fatalf("Must fail because of timeout, but got error message: %v", output.String())
}
tB := &TimeoutBody{}
json.NewDecoder(output).Decode(tB)
cfg := &call.GetCallsCallParams{
Call: tB.CallID,
Context: s.Context,
}
cfg.WithTimeout(time.Second * 60)
callObj, err := s.Client.Call.GetCallsCall(cfg)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if !strings.Contains("timeout", callObj.Payload.Call.Status) {
t.Fatalf("Call status mismatch.\n\tExpected: %v\n\tActual: %v",
"output", "callObj.Payload.Call.Status")
}
t.Logf("Test `%v` passed.", t.Name())
})
DeleteRoute(t, s.Context, s.Client, s.AppName, routePath)
routePath = "/multi-log"
image = "funcy/multi-log:0.0.1"
routeType = "async"
CreateRoute(t, s.Context, s.Client, s.AppName, routePath, image, routeType,
s.RouteConfig, s.RouteHeaders)
t.Run("exec-multi-log-test", func(t *testing.T) {
u := url.URL{
Scheme: "http",
Host: client.Host(),
}
u.Path = path.Join(u.Path, "r", s.AppName, routePath)
callID := CallAsync(t, u, &bytes.Buffer{})
time.Sleep(5 * time.Second)
cfg := &operations.GetCallsCallLogParams{
Call: callID,
Context: s.Context,
}
logObj, err := s.Client.Operations.GetCallsCallLog(cfg)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if logObj.Payload.Log.Log == "" {
t.Fatalf("Log entry must not be empty!")
}
if !strings.Contains(logObj.Payload.Log.Log, "First line") {
t.Fatalf("Log entry must contain `First line` " +
"string, but got: %v", logObj.Payload.Log.Log)
}
if !strings.Contains(logObj.Payload.Log.Log, "Second line") {
t.Fatalf("Log entry must contain `Second line` " +
"string, but got: %v", logObj.Payload.Log.Log)
}
})
DeleteRoute(t, s.Context, s.Client, s.AppName, routePath)
routePath = "/log"
image = "funcy/log:0.0.1"
routeType = "async"
CreateRoute(t, s.Context, s.Client, s.AppName, routePath, image, routeType,
s.RouteConfig, s.RouteHeaders)
t.Run("exec-log-test", func(t *testing.T) {
u := url.URL{
Scheme: "http",
Host: client.Host(),
}
u.Path = path.Join(u.Path, "r", s.AppName, routePath)
content := &bytes.Buffer{}
json.NewEncoder(content).Encode(struct {
Size int
}{Size: 20})
callID := CallAsync(t, u, content)
time.Sleep(5 * time.Second)
cfg := &operations.GetCallsCallLogParams{
Call: callID,
Context: s.Context,
}
_, err := s.Client.Operations.GetCallsCallLog(cfg)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
})
t.Run("exec-oversized-log-test", func(t *testing.T) {
t.Skip("Skipped until fix for https://gitlab-odx.oracle.com/odx/functions/issues/86.")
size := 1 * 1024 * 1024 * 1024
u := url.URL{
Scheme: "http",
Host: client.Host(),
}
u.Path = path.Join(u.Path, "r", s.AppName, routePath)
content := &bytes.Buffer{}
json.NewEncoder(content).Encode(struct {
Size int
}{Size: size}) //exceeding log by 1 symbol
callID := CallAsync(t, u, content)
time.Sleep(5 * time.Second)
cfg := &operations.GetCallsCallLogParams{
Call: callID,
Context: s.Context,
}
logObj, err := s.Client.Operations.GetCallsCallLog(cfg)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if len(logObj.Payload.Log.Log) >= size {
t.Fatalf("Log entry suppose to be truncated up to expected size %v, got %v",
size / 1024, len(logObj.Payload.Log.Log))
}
})
DeleteRoute(t, s.Context, s.Client, s.AppName, routePath)
DeleteApp(t, s.Context, s.Client, s.AppName)
}

View File

@@ -0,0 +1,8 @@
FROM funcy/go:dev as build-stage
WORKDIR /function
ADD . /src
RUN cd /src && go build -o func
FROM funcy/go
WORKDIR /function
COPY --from=build-stage /src/func /function/
ENTRYPOINT ["./func"]

View File

@@ -0,0 +1,5 @@
name: funcy/log
version: 0.0.1
runtime: go
entrypoint: ./func
path: /log

29
fn/tests/fn/log/main.go Normal file
View File

@@ -0,0 +1,29 @@
package main
import (
"fmt"
"encoding/json"
"os"
"math/rand"
)
const lBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
type OutputSize struct {
Size int `json:"size"`
}
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = lBytes[rand.Intn(len(lBytes))]
}
return string(b)
}
func main() {
out := &OutputSize{}
json.NewDecoder(os.Stdin).Decode(out)
fmt.Fprintln(os.Stderr, RandStringBytes(out.Size))
}

View File

@@ -0,0 +1,3 @@
{
"size": 1048576
}

View File

@@ -0,0 +1,8 @@
FROM funcy/go:dev as build-stage
WORKDIR /function
ADD . /src
RUN cd /src && go build -o func
FROM funcy/go
WORKDIR /function
COPY --from=build-stage /src/func /function/
ENTRYPOINT ["./func"]

View File

@@ -0,0 +1,5 @@
name: funcy/multi-log
version: 0.0.1
runtime: go
entrypoint: ./func
path: /multi-log

View File

@@ -0,0 +1,15 @@
package main
import (
"fmt"
"os"
"time"
)
func main() {
fmt.Fprintln(os.Stderr, "First line")
fmt.Fprintln(os.Stdout, "Ok")
time.Sleep(3 * time.Second)
fmt.Fprintln(os.Stderr, "Second line")
}

View File

@@ -0,0 +1,8 @@
FROM funcy/go:dev as build-stage
WORKDIR /function
ADD . /src
RUN cd /src && go build -o func
FROM funcy/go
WORKDIR /function
COPY --from=build-stage /src/func /function/
ENTRYPOINT ["./func"]

View File

@@ -0,0 +1,5 @@
name: funcy/timeout
version: 0.0.1
runtime: go
entrypoint: ./func
path: /timeouter

View File

@@ -0,0 +1,9 @@
package main
import (
"time"
)
func main() {
time.Sleep(32 * time.Second)
}

83
fn/tests/routes_test.go Normal file
View File

@@ -0,0 +1,83 @@
package tests
import (
"testing"
"github.com/funcy/functions_go/models"
)
func TestRoutes(t *testing.T) {
s := SetupDefaultSuite()
newRouteType := "sync"
newRoutePath := "/new-hello"
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
t.Run("create-route", func(t *testing.T) {
CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType,
s.RouteConfig, s.RouteHeaders)
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("list-and-find-route", func(t *testing.T) {
if !assertContainsRoute(ListRoutes(t, s.Context, s.Client, s.AppName), s.RoutePath) {
t.Fatalf("Unable to find corresponding route `%v` in list", s.RoutePath)
}
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("can-get-corresponding-route", func(t *testing.T) {
rObjects := []*models.Route{GetRoute(t, s.Context, s.Client, s.AppName, s.RoutePath), }
if !assertContainsRoute(rObjects, s.RoutePath) {
t.Fatalf("Unable to find corresponding route `%v` in list", s.RoutePath)
}
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("can-update-route-info", func(t *testing.T) {
routeResp, err := UpdateRoute(
t, s.Context, s.Client,
s.AppName, s.RoutePath,
s.Image, newRouteType, s.Format,
s.Memory, s.RouteConfig, s.RouteHeaders, "")
CheckRouteResponseError(t, err)
assertRouteFields(t, routeResp.Payload.Route, s.RoutePath, s.Image, newRouteType)
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("fail-to-update-route-path", func(t *testing.T) {
_, err := UpdateRoute(
t, s.Context, s.Client,
s.AppName, s.RoutePath,
s.Image, s.RouteType, s.Format,
s.Memory, s.RouteConfig, s.RouteHeaders, newRoutePath)
if err == nil {
t.Fatalf("Route path suppose to be immutable, but it's not.")
}
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("create-route-duplicate", func(t *testing.T) {
_, err := createRoute(s.Context, s.Client, s.AppName, s.Image, s.RoutePath, newRouteType, s.RouteConfig, s.RouteHeaders)
if err == nil {
t.Fatalf("Route duplicate error should appear, but it didn't")
}
})
t.Run("can-delete-route", func(t *testing.T) {
DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath)
t.Logf("Test `%v` passed.", t.Name())
})
t.Run("fail-to-delete-missing-route", func(t *testing.T) {
_, err := deleteRoute(s.Context, s.Client, s.AppName, "dummy-route")
if err == nil {
t.Fatal("Delete from missing route must fail.")
}
})
DeleteApp(t, s.Context, s.Client, s.AppName)
}

388
fn/tests/utils.go Normal file
View File

@@ -0,0 +1,388 @@
package tests
import (
"context"
"strings"
"time"
"testing"
fn "github.com/funcy/functions_go/client"
"github.com/funcy/functions_go/models"
"github.com/funcy/functions_go/client/apps"
"gitlab-odx.oracle.com/odx/functions/fn/client"
"github.com/funcy/functions_go/client/routes"
)
type SuiteSetup struct {
Context context.Context
Client *fn.Functions
AppName string
RoutePath string
Image string
RouteType string
Format string
Memory int64
RouteConfig map[string]string
RouteHeaders map[string][]string
}
func SetupDefaultSuite() *SuiteSetup {
return &SuiteSetup{
Context: context.Background(),
Client: client.APIClient(),
AppName: "test-app",
RoutePath: "/hello",
Image: "funcy/hello",
Format: "default",
RouteType: "async",
RouteConfig: map[string]string{},
RouteHeaders: map[string][]string{},
}
}
func CheckAppResponseError(t *testing.T, err error) {
if err != nil {
switch err.(type) {
case *apps.DeleteAppsAppDefault:
msg := err.(*apps.DeleteAppsAppDefault).Payload.Error.Message
code := err.(*apps.DeleteAppsAppDefault).Code()
t.Fatalf("Unexpected error occurred: %v. Status code: %v", msg, code)
return
case *apps.PostAppsDefault:
msg := err.(*apps.PostAppsDefault).Payload.Error.Message
code := err.(*apps.DeleteAppsAppDefault).Code()
t.Fatalf("Unexpected error occurred: %v. Status code: %v", msg, code)
return
case *apps.GetAppsAppNotFound:
msg := err.(*apps.GetAppsAppNotFound).Payload.Error.Message
if !strings.Contains("App not found", msg) {
t.Fatalf("Unexpected error occurred: %v", msg)
return
}
return
case *apps.GetAppsAppDefault:
msg := err.(*apps.GetAppsAppDefault).Payload.Error.Message
code := err.(*apps.GetAppsAppDefault).Code()
t.Fatalf("Unexpected error occurred: %v. Status code: %v", msg, code)
return
case *apps.PatchAppsAppDefault:
msg := err.(*apps.PatchAppsAppDefault).Payload.Error.Message
code := err.(*apps.PatchAppsAppDefault).Code()
t.Fatalf("Unexpected error occurred: %v. Status code: %v", msg, code)
return
case *apps.PatchAppsAppNotFound:
msg := err.(*apps.PatchAppsAppNotFound).Payload.Error.Message
t.Fatalf("Unexpected error occurred: %v.", msg)
return
case *apps.PatchAppsAppBadRequest:
msg := err.(*apps.PatchAppsAppBadRequest).Payload.Error.Message
t.Fatalf("Unexpected error occurred: %v.", msg)
return
}
t.Fatalf("Unable to determine type of error: %s", err)
}
}
func CreateAppNoAssert(ctx context.Context, fnclient *fn.Functions, appName string, config map[string]string) (*apps.PostAppsOK, error){
cfg := &apps.PostAppsParams{
Body: &models.AppWrapper{
App: &models.App{
Config: config,
Name: appName,
},
},
Context: ctx,
}
cfg.WithTimeout(time.Second * 60)
return fnclient.Apps.PostApps(cfg)
}
func CreateApp(t *testing.T, ctx context.Context, fnclient *fn.Functions, appName string, config map[string]string) {
appPayload, err := CreateAppNoAssert(ctx, fnclient, appName, config)
CheckAppResponseError(t, err)
if !strings.Contains(appName, appPayload.Payload.App.Name) {
t.Fatalf("App name mismatch.\nExpected: %v\nActual: %v",
appName, appPayload.Payload.App.Name)
}
}
func UpdateApp(t *testing.T, ctx context.Context, fnclient *fn.Functions ,appName string, config map[string]string) *apps.PatchAppsAppOK {
CreateApp(t, ctx, fnclient, appName, map[string]string{"A": "a"})
cfg := &apps.PatchAppsAppParams{
App: appName,
Body: &models.AppWrapper{
App: &models.App{
Config: config,
Name: "",
},
},
Context: ctx,
}
appPayload, err := fnclient.Apps.PatchAppsApp(cfg)
CheckAppResponseError(t, err)
return appPayload
}
func DeleteApp(t *testing.T, ctx context.Context, fnclient *fn.Functions, appName string) {
cfg := &apps.DeleteAppsAppParams{
App: appName,
Context: ctx,
}
cfg.WithTimeout(time.Second * 60)
_, err := fnclient.Apps.DeleteAppsApp(cfg)
CheckAppResponseError(t, err)
}
func CheckRouteResponseError(t *testing.T, err error) {
if err != nil {
switch err.(type) {
case *routes.PostAppsAppRoutesDefault:
msg := err.(*routes.PostAppsAppRoutesDefault).Payload.Error.Message
code := err.(*routes.PostAppsAppRoutesDefault).Code()
t.Fatalf("Unexpected error occurred: %v. Status code: %v", msg, code)
return
case *routes.PostAppsAppRoutesBadRequest:
msg := err.(*routes.PostAppsAppRoutesBadRequest).Payload.Error.Message
t.Fatalf("Unexpected error occurred: %v.", msg)
return
case *routes.PostAppsAppRoutesConflict:
msg := err.(*routes.PostAppsAppRoutesConflict).Payload.Error.Message
t.Fatalf("Unexpected error occurred: %v.", msg)
return
case *routes.GetAppsAppRoutesRouteNotFound:
msg := err.(*routes.GetAppsAppRoutesRouteNotFound).Payload.Error.Message
t.Fatalf("Unexpected error occurred: %v.", msg)
return
case *routes.GetAppsAppRoutesRouteDefault:
msg := err.(*routes.GetAppsAppRoutesRouteDefault).Payload.Error.Message
code := err.(*routes.GetAppsAppRoutesRouteDefault).Code()
t.Fatalf("Unexpected error occurred: %v. Status code: %v", msg, code)
return
case *routes.DeleteAppsAppRoutesRouteNotFound:
msg := err.(*routes.DeleteAppsAppRoutesRouteNotFound).Payload.Error.Message
t.Fatalf("Unexpected error occurred: %v.", msg)
return
case *routes.DeleteAppsAppRoutesRouteDefault:
msg := err.(*routes.DeleteAppsAppRoutesRouteDefault).Payload.Error.Message
code := err.(*routes.DeleteAppsAppRoutesRouteDefault).Code()
t.Fatalf("Unexpected error occurred: %v. Status code: %v", msg, code)
return
case *routes.GetAppsAppRoutesNotFound:
msg := err.(*routes.GetAppsAppRoutesNotFound).Payload.Error.Message
t.Fatalf("Unexpected error occurred: %v.", msg)
return
case *routes.GetAppsAppRoutesDefault:
msg := err.(*routes.GetAppsAppRoutesDefault).Payload.Error.Message
code := err.(*routes.GetAppsAppRoutesDefault).Code()
t.Fatalf("Unexpected error occurred: %v. Status code: %v", msg, code)
return
case *routes.PatchAppsAppRoutesRouteBadRequest:
msg := err.(*routes.PatchAppsAppRoutesRouteBadRequest).Payload.Error.Message
t.Fatalf("Unexpected error occurred: %v.", msg)
return
case *routes.PatchAppsAppRoutesRouteNotFound:
msg := err.(*routes.PatchAppsAppRoutesRouteNotFound).Payload.Error.Message
t.Fatalf("Unexpected error occurred: %v.", msg)
return
case *routes.PatchAppsAppRoutesRouteDefault:
msg := err.(*routes.PatchAppsAppRoutesRouteDefault).Payload.Error.Message
code := err.(*routes.PatchAppsAppRoutesRouteDefault).Code()
t.Fatalf("Unexpected error occurred: %v. Status code: %v", msg, code)
return
}
t.Fatalf("Unable to determine type of error: %s", err)
}
}
func logRoute(t *testing.T, routeObject *models.Route) {
t.Logf("Route path: %v", routeObject.Path)
t.Logf("Route image: %v", routeObject.Image)
t.Logf("Route type: %v", routeObject.Type)
t.Logf("Route timeout: %vs", *routeObject.Timeout)
t.Logf("Route idle timeout: %vs", *routeObject.IDLETimeout)
}
func assertRouteFields(t *testing.T, routeObject *models.Route, path, image, routeType string) {
logRoute(t, routeObject)
rPath := routeObject.Path
rImage := routeObject.Image
rType := routeObject.Type
rTimeout := *routeObject.Timeout
rIdleTimeout := *routeObject.IDLETimeout
if rPath != path {
t.Fatalf("Route path mismatch. Expected: %v. Actual: %v", path, rPath)
}
if rImage != image {
t.Fatalf("Route image mismatch. Expected: %v. Actual: %v", image, rImage)
}
if rType != routeType {
t.Fatalf("Route type mismatch. Expected: %v. Actual: %v", routeType, rType)
}
if rTimeout == 0 {
t.Fatal("Route timeout should have default value of 30 seconds, but got 0 seconds")
}
if rIdleTimeout == 0 {
t.Fatal("Route idle timeout should have default value of 30 seconds, but got 0 seconds")
}
}
func createRoute(ctx context.Context, fnclient *fn.Functions, appName, image, routePath, routeType string, routeConfig map[string]string, headers map[string][]string) (*routes.PostAppsAppRoutesOK, error) {
cfg := &routes.PostAppsAppRoutesParams{
App: appName,
Body: &models.RouteWrapper{
Route: &models.Route{
Config: routeConfig,
Headers: headers,
Image: image,
Path: routePath,
Type: routeType,
},
},
Context: ctx,
}
cfg.WithTimeout(time.Second * 60)
return fnclient.Routes.PostAppsAppRoutes(cfg)
}
func CreateRoute(t *testing.T, ctx context.Context, fnclient *fn.Functions, appName, routePath, image, routeType string, routeConfig map[string]string, headers map[string][]string) {
routeResponse, err := createRoute(ctx, fnclient, appName, image, routePath, routeType, routeConfig, headers)
CheckRouteResponseError(t, err)
assertRouteFields(t, routeResponse.Payload.Route, routePath, image, routeType)
}
func deleteRoute(ctx context.Context, fnclient *fn.Functions, appName, routePath string) (*routes.DeleteAppsAppRoutesRouteOK, error){
cfg := &routes.DeleteAppsAppRoutesRouteParams{
App: appName,
Route: routePath,
Context: ctx,
}
cfg.WithTimeout(time.Second * 60)
return fnclient.Routes.DeleteAppsAppRoutesRoute(cfg)
}
func DeleteRoute(t *testing.T, ctx context.Context, fnclient *fn.Functions, appName, routePath string) {
_, err := deleteRoute(ctx, fnclient, appName, routePath)
CheckRouteResponseError(t, err)
}
func ListRoutes(t *testing.T, ctx context.Context, fnclient *fn.Functions, appName string) []*models.Route {
cfg := &routes.GetAppsAppRoutesParams{
App: appName,
Context: ctx,
}
cfg.WithTimeout(time.Second * 60)
routesResponse, err := fnclient.Routes.GetAppsAppRoutes(cfg)
CheckRouteResponseError(t, err)
return routesResponse.Payload.Routes
}
func GetRoute(t *testing.T, ctx context.Context, fnclient *fn.Functions, appName, routePath string) *models.Route{
cfg := &routes.GetAppsAppRoutesRouteParams{
App: appName,
Route: routePath,
Context: ctx,
}
cfg.WithTimeout(time.Second * 60)
routeResponse, err := fnclient.Routes.GetAppsAppRoutesRoute(cfg)
CheckRouteResponseError(t, err)
return routeResponse.Payload.Route
}
func UpdateRoute(t *testing.T, ctx context.Context, fnclient *fn.Functions, appName, routePath, image, routeType, format string, memory int64, routeConfig map[string]string, headers map[string][]string, newRoutePath string) (*routes.PatchAppsAppRoutesRouteOK, error){
routeObject := GetRoute(t, ctx, fnclient, appName, routePath)
if routeObject.Config == nil {
routeObject.Config = map[string]string{}
}
if routeObject.Headers == nil {
routeObject.Headers = map[string][]string{}
}
logRoute(t, routeObject)
routeObject.Path = ""
if newRoutePath != "" {
routeObject.Path = newRoutePath
}
if routeConfig != nil {
for k, v := range routeConfig {
if string(k[0]) == "-" {
delete(routeObject.Config, string(k[1:]))
continue
}
routeObject.Config[k] = v
}
}
if headers != nil {
for k, v := range headers {
if string(k[0]) == "-" {
delete(routeObject.Headers, k)
continue
}
routeObject.Headers[k] = v
}
}
if image != "" {
routeObject.Image = image
}
if format != "" {
routeObject.Format = format
}
if routeType != "" {
routeObject.Type = routeType
}
if memory > 0 {
routeObject.Memory = memory
}
cfg := &routes.PatchAppsAppRoutesRouteParams{
App: appName,
Context: ctx,
Body: &models.RouteWrapper{
Route: routeObject,
},
Route: routePath,
}
cfg.WithTimeout(time.Second * 60)
t.Log("Calling update")
return fnclient.Routes.PatchAppsAppRoutesRoute(cfg)
}
func assertContainsRoute(routeModels []*models.Route, expectedRoute string) bool {
for _, r := range routeModels {
if r.Path == expectedRoute {
return true
}
}
return false
}