diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9a819da40..2f4f7c894 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -51,6 +51,16 @@ test_job: script: - 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: only: - tags diff --git a/Makefile b/Makefile index de78b237b..649555797 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,18 @@ 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-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 run -ti --privileged --rm -e LOG_LEVEL=debug \ -v /var/run/docker.sock:/var/run/docker.sock \ diff --git a/api_test.sh b/api_test.sh new file mode 100755 index 000000000..c832994a1 --- /dev/null +++ b/api_test.sh @@ -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 ../../ diff --git a/fn/Makefile b/fn/Makefile index ad8b5e512..c16f8681d 100644 --- a/fn/Makefile +++ b/fn/Makefile @@ -13,7 +13,10 @@ dep: dep ensure 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: GOOS=linux go build -o fn_linux diff --git a/fn/apps.go b/fn/apps.go index 813932f73..28c468580 100644 --- a/fn/apps.go +++ b/fn/apps.go @@ -8,6 +8,7 @@ import ( "context" fnclient "github.com/funcy/functions_go/client" + client "gitlab-odx.oracle.com/odx/functions/fn/client" apiapps "github.com/funcy/functions_go/client/apps" "github.com/funcy/functions_go/models" "github.com/jmoiron/jsonq" @@ -20,7 +21,7 @@ type appsCmd struct { } func apps() cli.Command { - a := appsCmd{client: apiClient()} + a := appsCmd{client: client.APIClient()} return cli.Command{ Name: "apps", diff --git a/fn/calls.go b/fn/calls.go index 312623f53..3c5ecf181 100644 --- a/fn/calls.go +++ b/fn/calls.go @@ -5,6 +5,7 @@ import ( "fmt" fnclient "github.com/funcy/functions_go/client" + client "gitlab-odx.oracle.com/odx/functions/fn/client" apicall "github.com/funcy/functions_go/client/call" "github.com/funcy/functions_go/models" "github.com/urfave/cli" @@ -15,7 +16,7 @@ type callsCmd struct { } func calls() cli.Command { - c := callsCmd{client: apiClient()} + c := callsCmd{client: client.APIClient()} return cli.Command{ Name: "calls", diff --git a/fn/api.go b/fn/client/api.go similarity index 77% rename from fn/api.go rename to fn/client/api.go index 773a65636..8f23c9ecb 100644 --- a/fn/api.go +++ b/fn/client/api.go @@ -1,4 +1,4 @@ -package main +package client import ( "os" @@ -11,7 +11,7 @@ import ( "github.com/go-openapi/strfmt" ) -func host() string { +func Host() string { apiURL := os.Getenv("API_URL") if apiURL == "" { apiURL = "http://localhost:8080" @@ -21,12 +21,12 @@ func host() string { if err != nil { log.Fatalln("Couldn't parse API URL:", err) } - + log.Println("trace: Host:", u.Host) return u.Host } -func apiClient() *fnclient.Functions { - transport := httptransport.New(host(), "/v1", []string{"http"}) +func APIClient() *fnclient.Functions { + transport := httptransport.New(Host(), "/v1", []string{"http"}) if os.Getenv("FN_TOKEN") != "" { transport.DefaultAuthentication = httptransport.BearerToken(os.Getenv("FN_TOKEN")) } diff --git a/fn/client/call_fn.go b/fn/client/call_fn.go new file mode 100644 index 000000000..847842e9d --- /dev/null +++ b/fn/client/call_fn.go @@ -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 +} diff --git a/fn/deploy.go b/fn/deploy.go index df67cbd22..ed3764fef 100644 --- a/fn/deploy.go +++ b/fn/deploy.go @@ -11,6 +11,7 @@ import ( "time" functions "github.com/funcy/functions_go" + client "gitlab-odx.oracle.com/odx/functions/fn/client" "github.com/funcy/functions_go/models" "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) } - routesCmd := routesCmd{client: apiClient()} + routesCmd := routesCmd{client: client.APIClient()} rt := &models.Route{} if err := routeWithFuncFile(c, ff, rt); err != nil { return fmt.Errorf("error getting route with funcfile: %s", err) diff --git a/fn/routes.go b/fn/routes.go index 8dfaa4489..358a68a64 100644 --- a/fn/routes.go +++ b/fn/routes.go @@ -5,8 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "io" - "net/http" "net/url" "os" "path" @@ -14,6 +12,8 @@ import ( "text/tabwriter" 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" fnmodels "github.com/funcy/functions_go/models" "github.com/jmoiron/jsonq" @@ -57,7 +57,7 @@ var routeFlags = []cli.Flag{ func routes() cli.Command { - r := routesCmd{client: apiClient()} + r := routesCmd{client: client.APIClient()} return cli.Command{ Name: "routes", @@ -132,7 +132,7 @@ func routes() cli.Command { } func call() cli.Command { - r := routesCmd{client: apiClient()} + r := routesCmd{client: client.APIClient()} return cli.Command{ Name: "call", @@ -191,55 +191,12 @@ func (a *routesCmd) call(c *cli.Context) error { u := url.URL{ Scheme: "http", - Host: host(), + Host: client.Host(), } u.Path = path.Join(u.Path, "r", appName, route) content := stdin() - return 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)) - } + return client.CallFN(u.String(), content, os.Stdout, c.String("method"), c.StringSlice("e")) } func routeWithFlags(c *cli.Context, rt *fnmodels.Route) { diff --git a/fn/routes_test.go b/fn/routes_test.go index f519ccbac..fba875775 100644 --- a/fn/routes_test.go +++ b/fn/routes_test.go @@ -4,6 +4,7 @@ import ( "net/http" "os" "testing" + "gitlab-odx.oracle.com/odx/functions/fn/utils" ) func TestEnvAsHeader(t *testing.T) { @@ -17,7 +18,7 @@ func TestEnvAsHeader(t *testing.T) { } for _, selectedEnv := range cases { req, _ := http.NewRequest("GET", "http://www.example.com", nil) - envAsHeader(req, selectedEnv) + utils.EnvAsHeader(req, selectedEnv) if found := req.Header.Get("k"); found != expectedValue { t.Errorf("not found expected header: %v", found) } diff --git a/fn/testfn.go b/fn/testfn.go index d64ded0d7..899302517 100644 --- a/fn/testfn.go +++ b/fn/testfn.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "gitlab-odx.oracle.com/odx/functions/fn/client" functions "github.com/funcy/functions_go" "github.com/urfave/cli" ) @@ -173,7 +174,7 @@ func runremotetest(target string, in, expectedOut, expectedErr *string, env map[ os.Setenv(k, v) 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()) } diff --git a/fn/tests/README.md b/fn/tests/README.md new file mode 100644 index 000000000..3b8dbd072 --- /dev/null +++ b/fn/tests/README.md @@ -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 ./... +``` diff --git a/fn/tests/apps_test.go b/fn/tests/apps_test.go new file mode 100644 index 000000000..ad8af4da5 --- /dev/null +++ b/fn/tests/apps_test.go @@ -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()) + }) +} diff --git a/fn/tests/calls_test.go b/fn/tests/calls_test.go new file mode 100644 index 000000000..a4b854474 --- /dev/null +++ b/fn/tests/calls_test.go @@ -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) +} diff --git a/fn/tests/exec_test.go b/fn/tests/exec_test.go new file mode 100644 index 000000000..ae4ac2a14 --- /dev/null +++ b/fn/tests/exec_test.go @@ -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) +} diff --git a/fn/tests/fn/log/Dockerfile b/fn/tests/fn/log/Dockerfile new file mode 100644 index 000000000..162432515 --- /dev/null +++ b/fn/tests/fn/log/Dockerfile @@ -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"] diff --git a/fn/tests/fn/log/func.yaml b/fn/tests/fn/log/func.yaml new file mode 100644 index 000000000..2149508f9 --- /dev/null +++ b/fn/tests/fn/log/func.yaml @@ -0,0 +1,5 @@ +name: funcy/log +version: 0.0.1 +runtime: go +entrypoint: ./func +path: /log diff --git a/fn/tests/fn/log/main.go b/fn/tests/fn/log/main.go new file mode 100644 index 000000000..0a36b9b77 --- /dev/null +++ b/fn/tests/fn/log/main.go @@ -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)) +} diff --git a/fn/tests/fn/log/sample.payload.json b/fn/tests/fn/log/sample.payload.json new file mode 100644 index 000000000..8f2e36ac3 --- /dev/null +++ b/fn/tests/fn/log/sample.payload.json @@ -0,0 +1,3 @@ +{ + "size": 1048576 +} diff --git a/fn/tests/fn/multi-log/Dockerfile b/fn/tests/fn/multi-log/Dockerfile new file mode 100644 index 000000000..162432515 --- /dev/null +++ b/fn/tests/fn/multi-log/Dockerfile @@ -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"] diff --git a/fn/tests/fn/multi-log/func.yaml b/fn/tests/fn/multi-log/func.yaml new file mode 100644 index 000000000..7dbac8d96 --- /dev/null +++ b/fn/tests/fn/multi-log/func.yaml @@ -0,0 +1,5 @@ +name: funcy/multi-log +version: 0.0.1 +runtime: go +entrypoint: ./func +path: /multi-log diff --git a/fn/tests/fn/multi-log/main.go b/fn/tests/fn/multi-log/main.go new file mode 100644 index 000000000..bf7004639 --- /dev/null +++ b/fn/tests/fn/multi-log/main.go @@ -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") +} diff --git a/fn/tests/fn/timeout/Dockerfile b/fn/tests/fn/timeout/Dockerfile new file mode 100644 index 000000000..162432515 --- /dev/null +++ b/fn/tests/fn/timeout/Dockerfile @@ -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"] diff --git a/fn/tests/fn/timeout/func.yaml b/fn/tests/fn/timeout/func.yaml new file mode 100644 index 000000000..a2c43df85 --- /dev/null +++ b/fn/tests/fn/timeout/func.yaml @@ -0,0 +1,5 @@ +name: funcy/timeout +version: 0.0.1 +runtime: go +entrypoint: ./func +path: /timeouter diff --git a/fn/tests/fn/timeout/main.go b/fn/tests/fn/timeout/main.go new file mode 100644 index 000000000..747006b5f --- /dev/null +++ b/fn/tests/fn/timeout/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "time" +) + +func main() { + time.Sleep(32 * time.Second) +} diff --git a/fn/tests/routes_test.go b/fn/tests/routes_test.go new file mode 100644 index 000000000..daad8a8db --- /dev/null +++ b/fn/tests/routes_test.go @@ -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) +} diff --git a/fn/tests/utils.go b/fn/tests/utils.go new file mode 100644 index 000000000..36a5e21cc --- /dev/null +++ b/fn/tests/utils.go @@ -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 +}