diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e6958c2bd..51b5e7b6d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,9 +39,7 @@ test_job: integration_tests: stage: test script: - - go build -o functions-alpine - - docker build -t funcy/functions:latest . - - make docker-test-run-with-sqlite3 + - DOCKER_LOCATION=container_ip make docker-test-run-with-sqlite3 deploy_job: only: diff --git a/Makefile b/Makefile index 15bda2df6..7ec441768 100644 --- a/Makefile +++ b/Makefile @@ -41,13 +41,13 @@ docker-run: docker-build docker run --rm --privileged -it -e NO_PROXY -e HTTP_PROXY -e LOG_LEVEL=debug -e "DB_URL=sqlite3:///app/data/fn.db" -v ${CURDIR}/data:/app/data -p 8080:8080 funcy/functions docker-test-run-with-sqlite3: - ./api_test.sh sqlite3 + ./api_test.sh sqlite3 4 docker-test-run-with-mysql: - ./api_test.sh mysql + ./api_test.sh mysql 4 docker-test-run-with-postgres: - ./api_test.sh postgres + ./api_test.sh postgres 4 docker-test: docker run -ti --privileged --rm -e LOG_LEVEL=debug \ diff --git a/api_test.sh b/api_test.sh index c306fa3ed..574cab8af 100755 --- a/api_test.sh +++ b/api_test.sh @@ -1,61 +1,52 @@ set -ex +function host { + case ${DOCKER_LOCATION:-localhost} in + localhost) + echo "localhost" + ;; + docker_ip) + if [[ ! -z ${DOCKER_HOST} ]] + then + DOCKER_IP=`echo ${DOCKER_HOST} | awk -F/ '{print $3}'| awk -F: '{print $1}'` + fi + + echo ${DOCKER_IP} + ;; + container_ip) + echo "$(docker inspect -f '{{.NetworkSettings.IPAddress}}' ${1})" + ;; + esac +} + case "$1" in "sqlite3" ) - # 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 10 - # docker logs func-server - # docker inspect -f '{{.NetworkSettings.IPAddress}}' func-server + rm -fr /tmp/fn_integration_tests.db + touch /tmp/fn_integration_tests.db + DB_URL="sqlite3:///tmp/fn_integration_tests.db" ;; "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 3306:3306 -e MYSQL_DATABASE=funcs -e MYSQL_ROOT_PASSWORD=root -d mysql - sleep 30 - docker logs func-mysql-test - 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 - docker logs func-server - docker inspect -f '{{.NetworkSettings.IPAddress}}' func-mysql-test - docker inspect -f '{{.NetworkSettings.IPAddress}}' func-server + DB_CONTAINER="func-mysql-test" + docker rm -fv ${DB_CONTAINER} || echo No prev mysql test db container + docker run --name ${DB_CONTAINER} -p 3306:3306 -e MYSQL_DATABASE=funcs -e MYSQL_ROOT_PASSWORD=root -d mysql + sleep 15 + MYSQL_HOST=`host ${DB_CONTAINER}` + MYSQL_PORT=3306 + DB_URL="mysql://root:root@tcp(${MYSQL_HOST}:${MYSQL_PORT})/funcs" ;; "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 -e "POSTGRES_DB=funcs" -e "POSTGRES_PASSWORD=root" -p 5432:5432 -d postgres - sleep 30 - docker logs func-postgres-test - 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:root@${POSTGRES_HOST}:${POSTGRES_PORT}/funcs?sslmode=disable" -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock funcy/functions - docker logs func-server - docker inspect -f '{{.NetworkSettings.IPAddress}}' func-postgres-test - docker inspect -f '{{.NetworkSettings.IPAddress}}' func-server + DB_CONTAINER="func-postgres-test" + docker rm -fv ${DB_CONTAINER} || echo No prev test db container + docker run --name ${DB_CONTAINER} -e "POSTGRES_DB=funcs" -e "POSTGRES_PASSWORD=root" -p 5432:5432 -d postgres + sleep 15 + POSTGRES_HOST=`host ${DB_CONTAINER}` + POSTGRES_PORT=5432 + DB_URL="postgres://postgres:root@${POSTGRES_HOST}:${POSTGRES_PORT}/funcs?sslmode=disable" ;; esac -case ${DOCKER_LOCATION:-localhost} in -localhost) - cd test/fn-api-tests && API_URL="http://localhost:8080" go test -v ./...; cd ../../ - ;; -docker_ip) - if [[ ! -z ${DOCKER_HOST} ]] - then - DOCKER_IP=`echo ${DOCKER_HOST} | awk -F/ '{print $3}'| awk -F: '{print $1}'` - fi - - cd test/fn-api-tests && API_URL="http://${DOCKER_IP:-localhost}:8080" go test -v ./...; cd ../../ - ;; -container_ip) - cd test/fn-api-tests && API_URL="http://"$(docker inspect -f '{{.NetworkSettings.IPAddress}}' func-server)":8080" go test -v ./...; cd ../../ - ;; -esac +cd test/fn-api-tests && API_URL="http://localhost:8080" DB_URL=${DB_URL} go test -v -parallel ${2:-1} ./...; cd ../../ diff --git a/test/fn-api-tests/apps_api.go b/test/fn-api-tests/apps_api.go new file mode 100644 index 000000000..54f43f7f6 --- /dev/null +++ b/test/fn-api-tests/apps_api.go @@ -0,0 +1,100 @@ +package tests + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/funcy/functions_go/client" + "github.com/funcy/functions_go/client/apps" + "github.com/funcy/functions_go/models" +) + +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.Errorf("Unexpected error occurred: %v. Status code: %v", msg, code) + case *apps.PostAppsDefault: + msg := err.(*apps.PostAppsDefault).Payload.Error.Message + code := err.(*apps.PostAppsDefault).Code() + t.Errorf("Unexpected error occurred: %v. Status code: %v", msg, code) + case *apps.GetAppsAppNotFound: + msg := err.(*apps.GetAppsAppNotFound).Payload.Error.Message + if !strings.Contains("App not found", msg) { + t.Errorf("Unexpected error occurred: %v", msg) + } + case *apps.GetAppsAppDefault: + msg := err.(*apps.GetAppsAppDefault).Payload.Error.Message + code := err.(*apps.GetAppsAppDefault).Code() + t.Errorf("Unexpected error occurred: %v. Status code: %v", msg, code) + case *apps.PatchAppsAppDefault: + msg := err.(*apps.PatchAppsAppDefault).Payload.Error.Message + code := err.(*apps.PatchAppsAppDefault).Code() + t.Errorf("Unexpected error occurred: %v. Status code: %v", msg, code) + case *apps.PatchAppsAppNotFound: + msg := err.(*apps.PatchAppsAppNotFound).Payload.Error.Message + t.Errorf("Unexpected error occurred: %v.", msg) + case *apps.PatchAppsAppBadRequest: + msg := err.(*apps.PatchAppsAppBadRequest).Payload.Error.Message + t.Errorf("Unexpected error occurred: %v.", msg) + default: + t.Errorf("Unable to determine type of error: %s", err) + } + } + +} + +func CreateAppNoAssert(ctx context.Context, fnclient *client.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 *client.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.Errorf("App name mismatch.\nExpected: %v\nActual: %v", + appName, appPayload.Payload.App.Name) + } +} + +func UpdateApp(t *testing.T, ctx context.Context, fnclient *client.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 *client.Functions, appName string) { + cfg := &apps.DeleteAppsAppParams{ + App: appName, + Context: ctx, + } + cfg.WithTimeout(time.Second * 60) + _, err := fnclient.Apps.DeleteAppsApp(cfg) + CheckAppResponseError(t, err) +} diff --git a/test/fn-api-tests/apps_test.go b/test/fn-api-tests/apps_test.go index 9bdc4b2f7..62e52cfb5 100644 --- a/test/fn-api-tests/apps_test.go +++ b/test/fn-api-tests/apps_test.go @@ -10,24 +10,10 @@ import ( ) 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) { + t.Parallel() + s := SetupDefaultSuite() cfg := &apps.GetAppsAppParams{ App: "missing-app", Context: s.Context, @@ -35,25 +21,26 @@ func TestApps(t *testing.T) { 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) { + t.Run("create-app-and-delete-no-config-test", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() 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) { + t.Parallel() + s := SetupDefaultSuite() CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{"A": "a"}) - t.Logf("Test `%v` passed.", t.Name()) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("inspect-app-with-config-test", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{"A": "a"}) cfg := &apps.GetAppsAppParams{ Context: s.Context, App: s.AppName, @@ -63,54 +50,56 @@ func TestApps(t *testing.T) { 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`.") + t.Error("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) + t.Errorf("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) { + t.Parallel() + s := SetupDefaultSuite() 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`.") + t.Error("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) + t.Errorf("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) { + t.Parallel() + s := SetupDefaultSuite() 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`.") + t.Error("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) + t.Errorf("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) { + t.Parallel() + s := SetupDefaultSuite() 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/test/fn-api-tests/calls_test.go b/test/fn-api-tests/calls_test.go index fe3fe58a6..043524d37 100644 --- a/test/fn-api-tests/calls_test.go +++ b/test/fn-api-tests/calls_test.go @@ -11,9 +11,10 @@ import ( ) func TestCalls(t *testing.T) { - s := SetupDefaultSuite() t.Run("list-calls-for-missing-app", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() cfg := &call.GetAppsAppCallsRouteParams{ App: s.AppName, Route: s.RoutePath, @@ -21,19 +22,15 @@ func TestCalls(t *testing.T) { } _, err := s.Client.Call.GetAppsAppCallsRoute(cfg) if err == nil { - t.Fatalf("Must fail with missing app error, but got %s", err) + t.Errorf("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: Host(), - } - u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) - t.Run("list-calls-for-missing-route", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + cfg := &call.GetAppsAppCallsRouteParams{ App: s.AppName, Route: s.RoutePath, @@ -41,14 +38,19 @@ func TestCalls(t *testing.T) { } _, err := s.Client.Call.GetAppsAppCallsRoute(cfg) if err == nil { - t.Fatalf("Must fail with missing route error, but got %s", err) + t.Errorf("Must fail with missing route error, but got %s", err) } + + DeleteApp(t, s.Context, s.Client, s.AppName) }) - 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) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, + s.RouteConfig, s.RouteHeaders) + cfg := &call.GetCallsCallParams{ Call: "dummy", Context: s.Context, @@ -56,12 +58,25 @@ func TestCalls(t *testing.T) { 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.Error("Must fail because `dummy` call does not exist.") } - t.Logf("Test `%v` passed.", t.Name()) + + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("get-real-call", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, + s.RouteConfig, s.RouteHeaders) + + u := url.URL{ + Scheme: "http", + Host: Host(), + } + u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) callID := CallAsync(t, u, &bytes.Buffer{}) time.Sleep(time.Second * 5) @@ -75,13 +90,29 @@ func TestCalls(t *testing.T) { switch err.(type) { case *call.GetCallsCallNotFound: msg := err.(*call.GetCallsCallNotFound).Payload.Error.Message - t.Fatalf("Unexpected error occurred: %v.", msg) + t.Errorf("Unexpected error occurred: %v.", msg) } } - t.Logf("Test `%v` passed.", t.Name()) + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("list-calls", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, + s.RouteConfig, s.RouteHeaders) + + u := url.URL{ + Scheme: "http", + Host: Host(), + } + u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) + + CallAsync(t, u, &bytes.Buffer{}) + time.Sleep(time.Second * 8) + cfg := &call.GetAppsAppCallsRouteParams{ App: s.AppName, Route: s.RoutePath, @@ -89,18 +120,18 @@ func TestCalls(t *testing.T) { } calls, err := s.Client.Call.GetAppsAppCallsRoute(cfg) if err != nil { - t.Fatalf("Unexpected error: %s", err) + t.Errorf("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) + t.Errorf("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) + t.Errorf("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) }) - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) - DeleteApp(t, s.Context, s.Client, s.AppName) } diff --git a/test/fn-api-tests/exec_test.go b/test/fn-api-tests/exec_test.go index 6cc8206a6..5bfa1365e 100644 --- a/test/fn-api-tests/exec_test.go +++ b/test/fn-api-tests/exec_test.go @@ -27,12 +27,12 @@ func CallAsync(t *testing.T, u url.URL, content io.Reader) string { output := &bytes.Buffer{} err := CallFN(u.String(), content, output, "POST", []string{}) if err != nil { - t.Fatalf("Got unexpected error: %v", err) + t.Errorf("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()) + t.Errorf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String()) } type CallID struct { @@ -43,42 +43,55 @@ func CallAsync(t *testing.T, u url.URL, content io.Reader) string { json.NewDecoder(output).Decode(callID) if callID.CallID == "" { - t.Fatalf("`call_id` not suppose to be empty string") + t.Errorf("`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: Host(), - } - u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) - t.Run("run-sync-funcy/hello-no-input", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + 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: Host(), + } + u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) + content := &bytes.Buffer{} output := &bytes.Buffer{} err := CallFN(u.String(), content, output, "POST", []string{}) if err != nil { - t.Fatalf("Got unexpected error: %v", err) + t.Errorf("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.Errorf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String()) } - t.Logf("Test `%v` passed.", t.Name()) + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("run-sync-funcy/hello-with-input", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + 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: Host(), + } + u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) + content := &bytes.Buffer{} json.NewEncoder(content).Encode(struct { Name string @@ -86,29 +99,64 @@ func TestRouteExecutions(t *testing.T) { output := &bytes.Buffer{} err := CallFN(u.String(), content, output, "POST", []string{}) if err != nil { - t.Fatalf("Got unexpected error: %v", err) + t.Errorf("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.Errorf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String()) } - t.Logf("Test `%v` passed.", t.Name()) + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) + }) - _, 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) { + t.Parallel() + s := SetupDefaultSuite() + 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: Host(), + } + u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) + + _, 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) + CallAsync(t, u, &bytes.Buffer{}) - t.Logf("Test `%v` passed.", t.Name()) + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("run-async-funcy/hello-with-status-check", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + 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: Host(), + } + u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) + + _, 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) + callID := CallAsync(t, u, &bytes.Buffer{}) time.Sleep(time.Second * 10) cfg := &call.GetCallsCallParams{ @@ -121,35 +169,39 @@ func TestRouteExecutions(t *testing.T) { switch err.(type) { case *call.GetCallsCallNotFound: msg := err.(*call.GetCallsCallNotFound).Payload.Error.Message - t.Fatalf("Unexpected error occurred: %v.", msg) + t.Errorf("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) + t.Errorf("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) + t.Errorf("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) + t.Errorf("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) + t.Errorf("Call object status mismatch.\n\tExpected: %v\n\tActual:%v", "success", callObject.Status) } + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) + }) - 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) { + t.Parallel() + s := SetupDefaultSuite() + routePath := "/" + RandStringBytes(10) + image := "funcy/timeout:0.0.1" + routeType := "sync" + + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, routePath, image, routeType, + s.RouteConfig, s.RouteHeaders) u := url.URL{ Scheme: "http", @@ -166,7 +218,7 @@ func TestRouteExecutions(t *testing.T) { 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()) + t.Errorf("Must fail because of timeout, but got error message: %v", output.String()) } tB := &TimeoutBody{} @@ -179,24 +231,28 @@ func TestRouteExecutions(t *testing.T) { cfg.WithTimeout(time.Second * 60) callObj, err := s.Client.Call.GetCallsCall(cfg) if err != nil { - t.Fatalf("Unexpected error: %s", err) + t.Errorf("Unexpected error: %s", err) } if !strings.Contains("timeout", callObj.Payload.Call.Status) { - t.Fatalf("Call status mismatch.\n\tExpected: %v\n\tActual: %v", + t.Errorf("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) + DeleteApp(t, s.Context, s.Client, s.AppName) }) - 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) { + t.Parallel() + s := SetupDefaultSuite() + routePath := "/multi-log" + image := "funcy/multi-log:0.0.1" + routeType := "async" + + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, routePath, image, routeType, + s.RouteConfig, s.RouteHeaders) + u := url.URL{ Scheme: "http", Host: Host(), @@ -213,30 +269,34 @@ func TestRouteExecutions(t *testing.T) { logObj, err := s.Client.Operations.GetCallsCallLog(cfg) if err != nil { - t.Fatalf("Unexpected error: %s", err) + t.Errorf("Unexpected error: %s", err) } if logObj.Payload.Log.Log == "" { - t.Fatalf("Log entry must not be empty!") + t.Errorf("Log entry must not be empty!") } if !strings.Contains(logObj.Payload.Log.Log, "First line") { - t.Fatalf("Log entry must contain `First line` "+ + t.Errorf("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` "+ + t.Errorf("Log entry must contain `Second line` "+ "string, but got: %v", logObj.Payload.Log.Log) } + + DeleteRoute(t, s.Context, s.Client, s.AppName, routePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) - DeleteRoute(t, s.Context, s.Client, s.AppName, routePath) - - routePath = "/os.environ" - image = "denismakogon/os.environ" - routeType = "sync" - CreateRoute(t, s.Context, s.Client, s.AppName, routePath, image, routeType, - s.RouteConfig, s.RouteHeaders) - t.Run("verify-headers-separator", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + routePath := "/os.environ" + image := "denismakogon/os.environ" + routeType := "sync" + CreateRoute(t, s.Context, s.Client, s.AppName, routePath, image, routeType, + s.RouteConfig, s.RouteHeaders) + u := url.URL{ Scheme: "http", Host: Host(), @@ -254,17 +314,21 @@ func TestRouteExecutions(t *testing.T) { t.Errorf("HEADER_ACCEPT='application/xml, application/json; q=0.2' "+ "should be in output, have:\n%", res) } + DeleteRoute(t, s.Context, s.Client, s.AppName, routePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) - 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) { + t.Parallel() + s := SetupDefaultSuite() + routePath := "/log" + image := "funcy/log:0.0.1" + routeType := "async" + + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, routePath, image, routeType, + s.RouteConfig, s.RouteHeaders) + u := url.URL{ Scheme: "http", Host: Host(), @@ -286,13 +350,26 @@ func TestRouteExecutions(t *testing.T) { _, err := s.Client.Operations.GetCallsCallLog(cfg) if err != nil { - t.Fatalf("Unexpected error: %s", err) + t.Errorf("Unexpected error: %s", err) } + DeleteRoute(t, s.Context, s.Client, s.AppName, routePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("exec-oversized-log-test", func(t *testing.T) { + t.Parallel() t.Skip("Skipped until fix for https://gitlab-odx.oracle.com/odx/functions/issues/86.") + + s := SetupDefaultSuite() + routePath := "/log" + image := "funcy/log:0.0.1" + routeType := "async" + + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, routePath, image, routeType, + s.RouteConfig, s.RouteHeaders) + size := 1 * 1024 * 1024 * 1024 u := url.URL{ Scheme: "http", @@ -314,15 +391,14 @@ func TestRouteExecutions(t *testing.T) { logObj, err := s.Client.Operations.GetCallsCallLog(cfg) if err != nil { - t.Fatalf("Unexpected error: %s", err) + t.Errorf("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", + t.Errorf("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) }) - DeleteRoute(t, s.Context, s.Client, s.AppName, routePath) - - DeleteApp(t, s.Context, s.Client, s.AppName) } diff --git a/test/fn-api-tests/routes_api.go b/test/fn-api-tests/routes_api.go new file mode 100644 index 000000000..3b7029e53 --- /dev/null +++ b/test/fn-api-tests/routes_api.go @@ -0,0 +1,220 @@ +package tests + +import ( + "context" + "testing" + "time" + + "github.com/funcy/functions_go/client" + "github.com/funcy/functions_go/client/routes" + "github.com/funcy/functions_go/models" +) + +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.Errorf("Unexpected error occurred: %v. Status code: %v", msg, code) + case *routes.PostAppsAppRoutesBadRequest: + msg := err.(*routes.PostAppsAppRoutesBadRequest).Payload.Error.Message + t.Errorf("Unexpected error occurred: %v.", msg) + case *routes.PostAppsAppRoutesConflict: + msg := err.(*routes.PostAppsAppRoutesConflict).Payload.Error.Message + t.Errorf("Unexpected error occurred: %v.", msg) + case *routes.GetAppsAppRoutesRouteNotFound: + msg := err.(*routes.GetAppsAppRoutesRouteNotFound).Payload.Error.Message + t.Errorf("Unexpected error occurred: %v.", msg) + case *routes.GetAppsAppRoutesRouteDefault: + msg := err.(*routes.GetAppsAppRoutesRouteDefault).Payload.Error.Message + code := err.(*routes.GetAppsAppRoutesRouteDefault).Code() + t.Errorf("Unexpected error occurred: %v. Status code: %v", msg, code) + case *routes.DeleteAppsAppRoutesRouteNotFound: + msg := err.(*routes.DeleteAppsAppRoutesRouteNotFound).Payload.Error.Message + t.Errorf("Unexpected error occurred: %v.", msg) + case *routes.DeleteAppsAppRoutesRouteDefault: + msg := err.(*routes.DeleteAppsAppRoutesRouteDefault).Payload.Error.Message + code := err.(*routes.DeleteAppsAppRoutesRouteDefault).Code() + t.Errorf("Unexpected error occurred: %v. Status code: %v", msg, code) + case *routes.GetAppsAppRoutesNotFound: + msg := err.(*routes.GetAppsAppRoutesNotFound).Payload.Error.Message + t.Errorf("Unexpected error occurred: %v.", msg) + case *routes.GetAppsAppRoutesDefault: + msg := err.(*routes.GetAppsAppRoutesDefault).Payload.Error.Message + code := err.(*routes.GetAppsAppRoutesDefault).Code() + t.Errorf("Unexpected error occurred: %v. Status code: %v", msg, code) + case *routes.PatchAppsAppRoutesRouteBadRequest: + msg := err.(*routes.PatchAppsAppRoutesRouteBadRequest).Payload.Error.Message + t.Errorf("Unexpected error occurred: %v.", msg) + case *routes.PatchAppsAppRoutesRouteNotFound: + msg := err.(*routes.PatchAppsAppRoutesRouteNotFound).Payload.Error.Message + t.Errorf("Unexpected error occurred: %v.", msg) + case *routes.PatchAppsAppRoutesRouteDefault: + msg := err.(*routes.PatchAppsAppRoutesRouteDefault).Payload.Error.Message + code := err.(*routes.PatchAppsAppRoutesRouteDefault).Code() + t.Errorf("Unexpected error occurred: %v. Status code: %v", msg, code) + default: + t.Errorf("Unable to determine type of error: %s", err) + } + } +} + +func assertRouteFields(t *testing.T, routeObject *models.Route, path, image, routeType string) { + + rPath := routeObject.Path + rImage := routeObject.Image + rType := routeObject.Type + rTimeout := *routeObject.Timeout + rIdleTimeout := *routeObject.IDLETimeout + if rPath != path { + t.Errorf("Route path mismatch. Expected: %v. Actual: %v", path, rPath) + } + if rImage != image { + t.Errorf("Route image mismatch. Expected: %v. Actual: %v", image, rImage) + } + if rType != routeType { + t.Errorf("Route type mismatch. Expected: %v. Actual: %v", routeType, rType) + } + if rTimeout == 0 { + t.Error("Route timeout should have default value of 30 seconds, but got 0 seconds") + } + if rIdleTimeout == 0 { + t.Error("Route idle timeout should have default value of 30 seconds, but got 0 seconds") + } + +} + +func createRoute(ctx context.Context, fnclient *client.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 *client.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 *client.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 *client.Functions, appName, routePath string) { + _, err := deleteRoute(ctx, fnclient, appName, routePath) + CheckRouteResponseError(t, err) +} + +func ListRoutes(t *testing.T, ctx context.Context, fnclient *client.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 *client.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 *client.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{} + } + + 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) + + 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 +} diff --git a/test/fn-api-tests/routes_test.go b/test/fn-api-tests/routes_test.go index adb539de9..01d7a5bee 100644 --- a/test/fn-api-tests/routes_test.go +++ b/test/fn-api-tests/routes_test.go @@ -1,41 +1,63 @@ package tests +// import ( "testing" "github.com/funcy/functions_go/models" + "gitlab-odx.oracle.com/odx/functions/api/id" ) func TestRoutes(t *testing.T) { - s := SetupDefaultSuite() - newRouteType := "sync" - newRoutePath := "/new-hello" - - CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + newRoutePath := id.New().String() t.Run("create-route", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) 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()) + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("list-and-find-route", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, + s.RouteConfig, s.RouteHeaders) 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.Errorf("Unable to find corresponding route `%v` in list", s.RoutePath) } - t.Logf("Test `%v` passed.", t.Name()) + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("can-get-corresponding-route", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, + s.RouteConfig, s.RouteHeaders) + 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.Errorf("Unable to find corresponding route `%v` in list", s.RoutePath) } - t.Logf("Test `%v` passed.", t.Name()) + + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("can-update-route-info", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, + s.RouteConfig, s.RouteHeaders) + routeResp, err := UpdateRoute( t, s.Context, s.Client, s.AppName, s.RoutePath, @@ -45,39 +67,66 @@ func TestRoutes(t *testing.T) { CheckRouteResponseError(t, err) assertRouteFields(t, routeResp.Payload.Route, s.RoutePath, s.Image, newRouteType) - t.Logf("Test `%v` passed.", t.Name()) + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("fail-to-update-route-path", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, + s.RouteConfig, s.RouteHeaders) + _, 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.Errorf("Route path suppose to be immutable, but it's not.") } - t.Logf("Test `%v` passed.", t.Name()) + + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("create-route-duplicate", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, + s.RouteConfig, s.RouteHeaders) + _, 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.Errorf("Route duplicate error should appear, but it didn't") } + + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("can-delete-route", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, + s.RouteConfig, s.RouteHeaders) + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) - t.Logf("Test `%v` passed.", t.Name()) + DeleteApp(t, s.Context, s.Client, s.AppName) }) t.Run("fail-to-delete-missing-route", func(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + _, err := deleteRoute(s.Context, s.Client, s.AppName, "dummy-route") if err == nil { - t.Fatal("Delete from missing route must fail.") + t.Error("Delete from missing route must fail.") } + DeleteApp(t, s.Context, s.Client, s.AppName) }) - - DeleteApp(t, s.Context, s.Client, s.AppName) } diff --git a/test/fn-api-tests/utils.go b/test/fn-api-tests/utils.go index a0f04bb6d..4e00c9545 100644 --- a/test/fn-api-tests/utils.go +++ b/test/fn-api-tests/utils.go @@ -4,7 +4,6 @@ import ( "context" "strings" "sync" - "testing" "time" "gitlab-odx.oracle.com/odx/functions/api/server" @@ -12,19 +11,19 @@ import ( "fmt" "io" "log" + "math/rand" "net/http" "net/url" "os" "github.com/funcy/functions_go/client" - "github.com/funcy/functions_go/client/apps" - "github.com/funcy/functions_go/client/routes" - "github.com/funcy/functions_go/models" httptransport "github.com/go-openapi/runtime/client" "github.com/go-openapi/strfmt" "github.com/spf13/viper" ) +const lBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + func Host() string { apiURL := os.Getenv("API_URL") if apiURL == "" { @@ -45,18 +44,16 @@ func APIClient() *client.Functions { } // create the API client, with the transport - client := client.New(transport, strfmt.Default) - - return client + return client.New(transport, strfmt.Default) } var ( getServer sync.Once + cancel2 context.CancelFunc + s *server.Server ) func getServerWithCancel() (*server.Server, context.CancelFunc) { - var cancel2 context.CancelFunc - var s *server.Server getServer.Do(func() { ctx, cancel := context.WithCancel(context.Background()) @@ -64,11 +61,15 @@ func getServerWithCancel() (*server.Server, context.CancelFunc) { viper.Set(server.EnvAPIURL, "http://localhost:8080") viper.Set(server.EnvLogLevel, "fatal") timeString := time.Now().Format("2006_01_02_15_04_05") + db_url := os.Getenv("DB_URL") tmpDir := os.TempDir() tmpMq := fmt.Sprintf("%s/fn_integration_test_%s_worker_mq.db", tmpDir, timeString) - tmpDB := fmt.Sprintf("%s/fn_integration_test_%s_fn.db", tmpDir, timeString) + tmpDb := fmt.Sprintf("%s/fn_integration_test_%s_fn.db", tmpDir, timeString) viper.Set(server.EnvMQURL, fmt.Sprintf("bolt://%s", tmpMq)) - viper.Set(server.EnvDBURL, fmt.Sprintf("sqlite3://%s", tmpDB)) + if db_url == "" { + db_url = fmt.Sprintf("sqlite3://%s", tmpDb) + } + viper.Set(server.EnvDBURL, db_url) s = server.NewFromEnv(ctx) @@ -79,6 +80,7 @@ func getServerWithCancel() (*server.Server, context.CancelFunc) { panic("Failed to start server.") } }) + log.Println(server.EnvAPIURL) _, err := http.Get(viper.GetString(server.EnvAPIURL) + "/version") for err != nil { _, err = http.Get(viper.GetString(server.EnvAPIURL) + "/version") @@ -87,7 +89,7 @@ func getServerWithCancel() (*server.Server, context.CancelFunc) { cancel2 = context.CancelFunc(func() { cancel() os.Remove(tmpMq) - os.Remove(tmpDB) + os.Remove(tmpDb) }) }) return s, cancel2 @@ -107,12 +109,20 @@ type SuiteSetup struct { Cancel context.CancelFunc } +func RandStringBytes(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = lBytes[rand.Intn(len(lBytes))] + } + return strings.ToLower(string(b)) +} + func SetupDefaultSuite() *SuiteSetup { ss := &SuiteSetup{ Context: context.Background(), Client: APIClient(), - AppName: "test-app", - RoutePath: "/hello", + AppName: RandStringBytes(10), + RoutePath: "/" + RandStringBytes(10), Image: "funcy/hello", Format: "default", RouteType: "async", @@ -137,353 +147,10 @@ func SetupDefaultSuite() *SuiteSetup { } } } + return ss } -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.PostAppsDefault).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 *client.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 *client.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 *client.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 *client.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 *client.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 *client.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 *client.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 *client.Functions, appName, routePath string) { - _, err := deleteRoute(ctx, fnclient, appName, routePath) - CheckRouteResponseError(t, err) -} - -func ListRoutes(t *testing.T, ctx context.Context, fnclient *client.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 *client.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 *client.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 -} - func EnvAsHeader(req *http.Request, selectedEnv []string) { detectedEnv := os.Environ() if len(selectedEnv) > 0 {