diff --git a/.gitignore b/.gitignore index 1be3b2dc0..916e3ec50 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,6 @@ fnlb/fnlb /fn .DS_Store /fnserver -.idea/ *iml target/ +fn-api-tests.test diff --git a/Makefile b/Makefile index e6a303d78..e8cd44124 100644 --- a/Makefile +++ b/Makefile @@ -31,11 +31,14 @@ test-basic: checkfmt pull-images fn-test-utils test: checkfmt pull-images test-basic test-middleware test-extensions test-api: test-basic - ./api_test.sh mysql 4 - ./api_test.sh postgres 4 ./api_test.sh sqlite3 4 + ./api_test.sh mysql 4 0 + ./api_test.sh postgres 4 0 -full-test: test test-api +build-static: + go install + +full-test: build-static test test-api img-sleeper: docker pull fnproject/sleeper diff --git a/api_test.sh b/api_test.sh index c7ac3a263..cafd63cd6 100755 --- a/api_test.sh +++ b/api_test.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -exuo pipefail +set -exo pipefail function host { case ${DOCKER_LOCATION:-localhost} in @@ -24,17 +24,16 @@ case "$1" in "sqlite3" ) rm -fr /tmp/fn_integration_tests.db touch /tmp/fn_integration_tests.db - FN_DB_URL="sqlite3:///tmp/fn_integration_tests.db" + export FN_DB_URL="sqlite3:///tmp/fn_integration_tests.db" ;; "mysql" ) 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 - FN_DB_URL="mysql://root:root@tcp(${MYSQL_HOST}:${MYSQL_PORT})/funcs" + export FN_DB_URL="mysql://root:root@tcp(${MYSQL_HOST}:${MYSQL_PORT})/funcs" ;; @@ -42,12 +41,29 @@ case "$1" in 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 - FN_DB_URL="postgres://postgres:root@${POSTGRES_HOST}:${POSTGRES_PORT}/funcs?sslmode=disable" + export FN_DB_URL="postgres://postgres:root@${POSTGRES_HOST}:${POSTGRES_PORT}/funcs?sslmode=disable" ;; esac +#test test/fn-api-tests/fn-api-tests.test +#status=`echo $?` +#rebuild="${3:-1}" +#circleci=`echo ${CIRCLECI}` +#cd test/fn-api-tests +#if [[ $status -ne 0 ]] || [[ $rebuild -ne 0 ]] ; then +# if [[ $circleci == "true" ]]; then +# # dirty things to make CI pass +# ls -lah /usr/local/go/pkg/linux_amd64/runtime +# sudo chown -R `whoami`:root /usr/local/go +# sudo chmod -R 777 /usr/local/go/pkg/linux_amd64 +# ls -lah /usr/local/go/pkg/linux_amd64/runtime +# fi +# pwd +# go test -i -a -o fn-api-tests.test +#fi +#pwd +#./fn-api-tests.test -test.v -test.parallel ${2:-1} ./...; cd ../../ cd test/fn-api-tests && FN_API_URL="http://localhost:8080" FN_DB_URL=${FN_DB_URL} go test -v -parallel ${2:-1} ./...; cd ../../ diff --git a/test/fn-api-tests/apps_test.go b/test/fn-api-tests/apps_test.go index fd0192fa1..7dfcdb88c 100644 --- a/test/fn-api-tests/apps_test.go +++ b/test/fn-api-tests/apps_test.go @@ -9,121 +9,118 @@ import ( "github.com/fnproject/fn_go/client/apps" ) -func TestApps(t *testing.T) { - - t.Run("delete-app-not-found-test", func(t *testing.T) { - t.Parallel() - s := SetupDefaultSuite() - cfg := &apps.DeleteAppsAppParams{ - App: "missing-app", - Context: s.Context, - } - cfg.WithTimeout(time.Second * 60) - _, err := s.Client.Apps.DeleteAppsApp(cfg) - if err == nil { - t.Errorf("Error during app delete: we should get HTTP 404, but got: %s", err.Error()) - } - }) - - t.Run("app-not-found-test", func(t *testing.T) { - t.Parallel() - s := SetupDefaultSuite() - cfg := &apps.GetAppsAppParams{ - App: "missing-app", - Context: s.Context, - } - cfg.WithTimeout(time.Second * 60) - _, err := s.Client.Apps.GetAppsApp(cfg) - CheckAppResponseError(t, err) - }) - - 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{}) - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - - 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"}) - 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"}) - app := GetApp(t, s.Context, s.Client, s.AppName) - val, ok := app.Config["A"] - if !ok { - t.Error("Error during app config inspect: config map misses required entity `A` with value `a`.") - } - if !strings.Contains("a", val) { - t.Errorf("App config value is different. Expected: `a`. Actual %v", val) - } - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - - t.Run("patch-app-with-exact-same-config-data", func(t *testing.T) { - t.Parallel() - s := SetupDefaultSuite() - config := map[string]string{ - "A": "a", - } - - appUpdatePayload := CreateUpdateApp(t, s.Context, s.Client, s.AppName, config) - _, ok := appUpdatePayload.Payload.App.Config["A"] - if !ok { - t.Error("Error during app update: config map misses required entity `A` with value `a`.") - } - - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - - t.Run("patch-override-app-config", func(t *testing.T) { - t.Parallel() - s := SetupDefaultSuite() - config := map[string]string{ - "A": "b", - } - appPayload := CreateUpdateApp(t, s.Context, s.Client, s.AppName, config) - val, ok := appPayload.Payload.App.Config["A"] - if !ok { - t.Error("Error during app config inspect: config map misses required entity `A` with value `a`.") - } - if !strings.Contains("b", val) { - t.Errorf("App config value is different. Expected: `b`. Actual %v", val) - } - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - - t.Run("patch-add-app-config", func(t *testing.T) { - t.Parallel() - s := SetupDefaultSuite() - config := map[string]string{ - "B": "b", - } - appPayload := CreateUpdateApp(t, s.Context, s.Client, s.AppName, config) - val, ok := appPayload.Payload.App.Config["B"] - if !ok { - t.Error("Error during app config inspect: config map misses required entity `B` with value `b`.") - } - if !strings.Contains("b", val) { - t.Errorf("App config value is different. Expected: `b`. Actual %v", val) - } - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - - 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) - }) +func TestAppDeleteNotFound(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + cfg := &apps.DeleteAppsAppParams{ + App: "missing-app", + Context: s.Context, + } + cfg.WithTimeout(time.Second * 60) + _, err := s.Client.Apps.DeleteAppsApp(cfg) + if err == nil { + t.Errorf("Error during app delete: we should get HTTP 404, but got: %s", err.Error()) + } +} + +func TestAppGetNotFound(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + cfg := &apps.GetAppsAppParams{ + App: "missing-app", + Context: s.Context, + } + cfg.WithTimeout(time.Second * 60) + _, err := s.Client.Apps.GetAppsApp(cfg) + CheckAppResponseError(t, err) +} + +func TestAppCreateNoConfigSuccess(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestAppCreateWithConfigSuccess(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{"A": "a"}) + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestAppInsect(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{"A": "a"}) + app := GetApp(t, s.Context, s.Client, s.AppName) + val, ok := app.Config["A"] + if !ok { + t.Error("Error during app config inspect: config map misses required entity `A` with value `a`.") + } + if !strings.Contains("a", val) { + t.Errorf("App config value is different. Expected: `a`. Actual %v", val) + } + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestAppPatchSameConfig(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + config := map[string]string{ + "A": "a", + } + + appUpdatePayload := CreateUpdateApp(t, s.Context, s.Client, s.AppName, config) + _, ok := appUpdatePayload.Payload.App.Config["A"] + if !ok { + t.Error("Error during app update: config map misses required entity `A` with value `a`.") + } + + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestAppPatchOverwriteConfig(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + config := map[string]string{ + "A": "b", + } + appPayload := CreateUpdateApp(t, s.Context, s.Client, s.AppName, config) + val, ok := appPayload.Payload.App.Config["A"] + if !ok { + t.Error("Error during app config inspect: config map misses required entity `A` with value `a`.") + } + if !strings.Contains("b", val) { + t.Errorf("App config value is different. Expected: `b`. Actual %v", val) + } + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestAppsPatchConfigAddValue(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + config := map[string]string{ + "B": "b", + } + appPayload := CreateUpdateApp(t, s.Context, s.Client, s.AppName, config) + val, ok := appPayload.Payload.App.Config["B"] + if !ok { + t.Error("Error during app config inspect: config map misses required entity `B` with value `b`.") + } + if !strings.Contains("b", val) { + t.Errorf("App config value is different. Expected: `b`. Actual %v", val) + } + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestAppDuplicate(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) } diff --git a/test/fn-api-tests/calls_test.go b/test/fn-api-tests/calls_test.go index e92a9a4b4..43b9bf0ab 100644 --- a/test/fn-api-tests/calls_test.go +++ b/test/fn-api-tests/calls_test.go @@ -10,106 +10,71 @@ import ( "github.com/fnproject/fn_go/client/call" ) -func TestCalls(t *testing.T) { - - t.Run("list-calls-for-missing-app", func(t *testing.T) { - t.Parallel() - s := SetupDefaultSuite() - cfg := &call.GetAppsAppCallsParams{ - App: s.AppName, - Path: &s.RoutePath, - Context: s.Context, - } - _, err := s.Client.Call.GetAppsAppCalls(cfg) - if err == nil { - t.Errorf("Must fail with missing app error, but got %s", err) - } - }) - - 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.Format, s.RouteConfig, s.RouteHeaders) - - cfg := &call.GetAppsAppCallsCallParams{ - Call: "dummy", - App: s.AppName, - Context: s.Context, - } - cfg.WithTimeout(time.Second * 60) - _, err := s.Client.Call.GetAppsAppCallsCall(cfg) - if err == nil { - t.Error("Must fail because `dummy` call does not exist.") - } - - 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.Format, s.RouteConfig, s.RouteHeaders) - - u := url.URL{ - Scheme: "http", - Host: Host(), - } - u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) - - time.Sleep(time.Second * 5) - _, err := s.Client.Call.GetAppsAppCalls(&call.GetAppsAppCallsParams{ - App: s.AppName, - Path: &s.RoutePath, - }) - if err != nil { - switch err.(type) { - case *call.GetAppsAppCallsCallNotFound: - msg := err.(*call.GetAppsAppCallsCallNotFound).Payload.Error.Message - t.Errorf("Unexpected error occurred: %v.", msg) - } - } - 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.Format, 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.GetAppsAppCallsParams{ - App: s.AppName, - Path: &s.RoutePath, - Context: s.Context, - } - calls, err := s.Client.Call.GetAppsAppCalls(cfg) - if err != nil { - t.Errorf("Unexpected error: %s", err) - } - if calls == nil || calls.Payload == nil || calls.Payload.Calls == nil || len(calls.Payload.Calls) == 0 { - t.Errorf("Must fail. There should be at least one call to `%v` route.", s.RoutePath) - return - } - for _, c := range calls.Payload.Calls { - if c.Path != s.RoutePath { - t.Errorf("Call path mismatch.\n\tExpected: %v\n\tActual: %v", c.Path, s.RoutePath) - } - } - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - +func TestCallsMissingApp(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + cfg := &call.GetAppsAppCallsParams{ + App: s.AppName, + Path: &s.RoutePath, + Context: s.Context, + } + _, err := s.Client.Call.GetAppsAppCalls(cfg) + if err == nil { + t.Errorf("Must fail with missing app error, but got %s", err) + } +} + +func TestCallsDummy(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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + + cfg := &call.GetAppsAppCallsCallParams{ + Call: "dummy", + App: s.AppName, + Context: s.Context, + } + cfg.WithTimeout(time.Second * 60) + _, err := s.Client.Call.GetAppsAppCallsCall(cfg) + if err == nil { + t.Error("Must fail because `dummy` call does not exist.") + } + + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestGetExactCall(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.Format, s.Timeout, s.IdleTimeout, 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{}) + + cfg := &call.GetAppsAppCallsCallParams{ + Call: callID, + App: s.AppName, + Context: s.Context, + } + cfg.WithTimeout(time.Second * 60) + + retryErr := APICallWithRetry(t, 10, time.Second*2, func() (err error) { + _, err = s.Client.Call.GetAppsAppCallsCall(cfg) + return err + }) + + if retryErr != nil { + t.Error(retryErr.Error()) + } + + 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 371c818b5..db729ad55 100644 --- a/test/fn-api-tests/exec_test.go +++ b/test/fn-api-tests/exec_test.go @@ -14,15 +14,6 @@ import ( "github.com/fnproject/fn_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 := CallFN(u.String(), content, output, "POST", []string{}) @@ -49,119 +40,125 @@ func CallAsync(t *testing.T, u url.URL, content io.Reader) string { return callID.CallID } -func TestRouteExecutions(t *testing.T) { +func TestCanCallfunction(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.Format, s.Timeout, s.IdleTimeout, 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.Errorf("Got unexpected error: %v", err) + } + expectedOutput := "Hello World!\n" + if !strings.Contains(expectedOutput, output.String()) { + t.Errorf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String()) + } + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestCallOutputMatch(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.Format, s.Timeout, s.IdleTimeout, 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 + }{Name: "John"}) + output := &bytes.Buffer{} + _, err := CallFN(u.String(), content, output, "POST", []string{}) + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + expectedOutput := "Hello John!\n" + if !strings.Contains(expectedOutput, output.String()) { + t.Errorf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String()) + } + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestCanCallAsync(t *testing.T) { newRouteType := "async" + 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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) - t.Run("run-sync-fnproject/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.Format, s.RouteConfig, s.RouteHeaders) + u := url.URL{ + Scheme: "http", + Host: Host(), + } + u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) - 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, "") - content := &bytes.Buffer{} - output := &bytes.Buffer{} - _, err := CallFN(u.String(), content, output, "POST", []string{}) - if err != nil { - t.Errorf("Got unexpected error: %v", err) - } - expectedOutput := "Hello World!\n" - if !strings.Contains(expectedOutput, output.String()) { - t.Errorf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String()) - } - DeleteApp(t, s.Context, s.Client, s.AppName) + CheckRouteResponseError(t, err) + + CallAsync(t, u, &bytes.Buffer{}) + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestCanGetAsyncState(t *testing.T) { + newRouteType := "async" + 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.Format, s.Timeout, s.IdleTimeout, 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{}) + cfg := &call.GetAppsAppCallsCallParams{ + Call: callID, + App: s.AppName, + Context: s.Context, + } + cfg.WithTimeout(time.Second * 60) + + retryErr := APICallWithRetry(t, 10, time.Second*2, func() (err error) { + _, err = s.Client.Call.GetAppsAppCallsCall(cfg) + return err }) - t.Run("run-sync-fnproject/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.Format, 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 - }{Name: "John"}) - output := &bytes.Buffer{} - _, err := CallFN(u.String(), content, output, "POST", []string{}) - if err != nil { - t.Errorf("Got unexpected error: %v", err) - } - expectedOutput := "Hello John!\n" - if !strings.Contains(expectedOutput, output.String()) { - t.Errorf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String()) - } - DeleteApp(t, s.Context, s.Client, s.AppName) - - }) - - t.Run("run-async-fnproject/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.Format, 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{}) - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - - t.Run("run-async-fnproject/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.Format, 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.GetAppsAppCallsCallParams{ - Call: callID, - App: s.AppName, - Context: s.Context, - } - cfg.WithTimeout(time.Second * 60) + if retryErr != nil { + t.Error(retryErr.Error()) + } else { callResponse, err := s.Client.Call.GetAppsAppCallsCall(cfg) if err != nil { switch err.(type) { @@ -184,46 +181,54 @@ func TestRouteExecutions(t *testing.T) { if callObject.Status != "success" { t.Errorf("Call object status mismatch.\n\tExpected: %v\n\tActual:%v", "success", callObject.Status) } + } - DeleteApp(t, s.Context, s.Client, s.AppName) + DeleteApp(t, s.Context, s.Client, s.AppName) +} +func TestCanCauseTimeout(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.Format, int32(10), s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + + u := url.URL{ + Scheme: "http", + Host: Host(), + } + u.Path = path.Join(u.Path, "r", s.AppName, routePath) + + content := &bytes.Buffer{} + json.NewEncoder(content).Encode(struct { + Seconds int64 `json:"seconds"` + }{Seconds: 11}) + output := &bytes.Buffer{} + + headers, _ := CallFN(u.String(), content, output, "POST", []string{}) + + if !strings.Contains(output.String(), "Timed out") { + t.Errorf("Must fail because of timeout, but got error message: %v", output.String()) + } + cfg := &call.GetAppsAppCallsCallParams{ + Call: headers.Get("FN_CALL_ID"), + App: s.AppName, + Context: s.Context, + } + cfg.WithTimeout(time.Second * 60) + + retryErr := APICallWithRetry(t, 10, time.Second*2, func() (err error) { + _, err = s.Client.Call.GetAppsAppCallsCall(cfg) + return err }) - 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.Format, s.RouteConfig, s.RouteHeaders) - - u := url.URL{ - Scheme: "http", - Host: 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{} - - headers, _ := CallFN(u.String(), content, output, "POST", []string{}) - - if !strings.Contains(output.String(), "Timed out") { - t.Errorf("Must fail because of timeout, but got error message: %v", output.String()) - } - - cfg := &call.GetAppsAppCallsCallParams{ - Call: headers.Get("FN_CALL_ID"), - App: s.AppName, - Context: s.Context, - } - cfg.WithTimeout(time.Second * 60) + if retryErr != nil { + t.Error(retryErr.Error()) + } else { callObj, err := s.Client.Call.GetAppsAppCallsCall(cfg) if err != nil { t.Errorf("Unexpected error: %s", err) @@ -232,36 +237,43 @@ func TestRouteExecutions(t *testing.T) { t.Errorf("Call status mismatch.\n\tExpected: %v\n\tActual: %v", "output", "callObj.Payload.Call.Status") } + } + DeleteApp(t, s.Context, s.Client, s.AppName) +} - DeleteApp(t, s.Context, s.Client, s.AppName) +func TestMultiLog(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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + + u := url.URL{ + Scheme: "http", + Host: Host(), + } + u.Path = path.Join(u.Path, "r", s.AppName, routePath) + + callID := CallAsync(t, u, &bytes.Buffer{}) + + cfg := &operations.GetAppsAppCallsCallLogParams{ + Call: callID, + App: s.AppName, + Context: s.Context, + } + + retryErr := APICallWithRetry(t, 10, time.Second*2, func() (err error) { + _, err = s.Client.Operations.GetAppsAppCallsCallLog(cfg) + return err }) - 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.Format, s.RouteConfig, s.RouteHeaders) - - u := url.URL{ - Scheme: "http", - Host: Host(), - } - u.Path = path.Join(u.Path, "r", s.AppName, routePath) - - callID := CallAsync(t, u, &bytes.Buffer{}) - time.Sleep(15 * time.Second) - - cfg := &operations.GetAppsAppCallsCallLogParams{ - Call: callID, - App: s.AppName, - Context: s.Context, - } - + if retryErr != nil { + t.Error(retryErr.Error()) + } else { logObj, err := s.Client.Operations.GetAppsAppCallsCallLog(cfg) if err != nil { t.Errorf("Unexpected error: %s", err) @@ -277,115 +289,120 @@ func TestRouteExecutions(t *testing.T) { t.Errorf("Log entry must contain `Second line` "+ "string, but got: %v", logObj.Payload.Log.Log) } + } - DeleteApp(t, s.Context, s.Client, s.AppName) + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestCallResponseHeadersMatch(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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + + u := url.URL{ + Scheme: "http", + Host: Host(), + } + u.Path = path.Join(u.Path, "r", s.AppName, routePath) + content := &bytes.Buffer{} + output := &bytes.Buffer{} + CallFN(u.String(), content, output, "POST", + []string{ + "ACCEPT: application/xml", + "ACCEPT: application/json; q=0.2", + }) + res := output.String() + if !strings.Contains("application/xml, application/json; q=0.2", res) { + t.Errorf("HEADER_ACCEPT='application/xml, application/json; q=0.2' "+ + "should be in output, have:%s\n", res) + } + DeleteRoute(t, s.Context, s.Client, s.AppName, routePath) + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestCanWriteLogs(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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + + u := url.URL{ + Scheme: "http", + Host: 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) + + cfg := &operations.GetAppsAppCallsCallLogParams{ + Call: callID, + App: s.AppName, + Context: s.Context, + } + + retryErr := APICallWithRetry(t, 10, time.Second*2, func() (err error) { + _, err = s.Client.Operations.GetAppsAppCallsCallLog(cfg) + return err }) - 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.Format, s.RouteConfig, s.RouteHeaders) + if retryErr != nil { + t.Error(retryErr.Error()) + } - u := url.URL{ - Scheme: "http", - Host: Host(), - } - u.Path = path.Join(u.Path, "r", s.AppName, routePath) - content := &bytes.Buffer{} - output := &bytes.Buffer{} - CallFN(u.String(), content, output, "POST", - []string{ - "ACCEPT: application/xml", - "ACCEPT: application/json; q=0.2", - }) - res := output.String() - if !strings.Contains("application/xml, application/json; q=0.2", res) { - t.Errorf("HEADER_ACCEPT='application/xml, application/json; q=0.2' "+ - "should be in output, have:%s\n", res) - } - DeleteRoute(t, s.Context, s.Client, s.AppName, routePath) - DeleteApp(t, s.Context, s.Client, s.AppName) + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestOversizedLog(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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + + size := 1 * 1024 * 1024 * 1024 + u := url.URL{ + Scheme: "http", + Host: 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) + + cfg := &operations.GetAppsAppCallsCallLogParams{ + Call: callID, + App: s.AppName, + Context: s.Context, + } + + retryErr := APICallWithRetry(t, 10, time.Second*2, func() (err error) { + _, err = s.Client.Operations.GetAppsAppCallsCallLog(cfg) + return err }) - - t.Run("exec-log-test", func(t *testing.T) { - //XXX: Fix this test. - t.Skip("Flaky test needs to be rewritten. https://github.com/fnproject/fn/issues/253") - 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.Format, s.RouteConfig, s.RouteHeaders) - - u := url.URL{ - Scheme: "http", - Host: 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(10 * time.Second) - - cfg := &operations.GetAppsAppCallsCallLogParams{ - Call: callID, - App: s.AppName, - Context: s.Context, - } - - _, err := s.Client.Operations.GetAppsAppCallsCallLog(cfg) - - if err != nil { - t.Errorf("Unexpected error: %s", err) - } - - 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.Format, s.RouteConfig, s.RouteHeaders) - - size := 1 * 1024 * 1024 * 1024 - u := url.URL{ - Scheme: "http", - Host: 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.GetAppsAppCallsCallLogParams{ - Call: callID, - App: s.AppName, - Context: s.Context, - } - + if retryErr != nil { + t.Error(retryErr.Error()) + } else { logObj, err := s.Client.Operations.GetAppsAppCallsCallLog(cfg) if err != nil { t.Errorf("Unexpected error: %s", err) @@ -394,7 +411,7 @@ func TestRouteExecutions(t *testing.T) { t.Errorf("Log entry suppose to be truncated up to expected size %v, got %v", size/1024, len(logObj.Payload.Log.Log)) } - DeleteApp(t, s.Context, s.Client, s.AppName) - }) + } + DeleteApp(t, s.Context, s.Client, s.AppName) } diff --git a/test/fn-api-tests/formats_test.go b/test/fn-api-tests/formats_test.go index 59cc51290..ad7019b15 100644 --- a/test/fn-api-tests/formats_test.go +++ b/test/fn-api-tests/formats_test.go @@ -14,59 +14,54 @@ type JSONResponse struct { Message string `json:"message"` } -func TestFnFormats(t *testing.T) { +func TestFnJSONFormats(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() - t.Run("test-json-format", func(t *testing.T) { - t.Parallel() - s := SetupDefaultSuite() + // TODO(treeder): put image in fnproject @ dockerhub + image := "denismakogon/test-hot-json-go:0.0.1" + format := "json" + route := "/test-hot-json-go" - // TODO(treeder): put image in fnproject @ dockerhub - image := "denismakogon/test-hot-json-go:0.0.1" - format := "json" - route := "/test-hot-json-go" + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + CreateRoute(t, s.Context, s.Client, s.AppName, route, image, "sync", + format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) - CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) - CreateRoute(t, s.Context, s.Client, s.AppName, route, image, "sync", - format, s.RouteConfig, s.RouteHeaders) - - u := url.URL{ - Scheme: "http", - Host: Host(), - } - u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) - - b, _ := json.Marshal(&struct { - Name string `json:"name"` - }{ - Name: "Jimmy", - }) - content := bytes.NewBuffer(b) - output := &bytes.Buffer{} - headers, err := CallFN(u.String(), content, output, "POST", []string{}) - if err != nil { - t.Errorf("Got unexpected error: %v", err) - } - - msg := &JSONResponse{} - json.Unmarshal(output.Bytes(), msg) - expectedOutput := "Hello Jimmy" - if !strings.Contains(expectedOutput, msg.Message) { - t.Errorf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String()) - } - - expectedHeaderNames := []string{"Content-Type", "Content-Length"} - expectedHeaderValues := []string{"application/json; charset=utf-8", strconv.Itoa(output.Len())} - for i, name := range expectedHeaderNames { - actual := headers.Get(name) - expected := expectedHeaderValues[i] - if !strings.Contains(expected, actual) { - t.Errorf("HTTP header assertion error for %v."+ - "\n\tExpected: %v\n\tActual: %v", name, expected, actual) - } - } - - DeleteApp(t, s.Context, s.Client, s.AppName) + u := url.URL{ + Scheme: "http", + Host: Host(), + } + u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) + b, _ := json.Marshal(&struct { + Name string `json:"name"` + }{ + Name: "Jimmy", }) + content := bytes.NewBuffer(b) + output := &bytes.Buffer{} + headers, err := CallFN(u.String(), content, output, "POST", []string{}) + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + msg := &JSONResponse{} + json.Unmarshal(output.Bytes(), msg) + expectedOutput := "Hello Jimmy" + if !strings.Contains(expectedOutput, msg.Message) { + t.Errorf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String()) + } + + expectedHeaderNames := []string{"Content-Type", "Content-Length"} + expectedHeaderValues := []string{"application/json; charset=utf-8", strconv.Itoa(output.Len())} + for i, name := range expectedHeaderNames { + actual := headers.Get(name) + expected := expectedHeaderValues[i] + if !strings.Contains(expected, actual) { + t.Errorf("HTTP header assertion error for %v."+ + "\n\tExpected: %v\n\tActual: %v", name, expected, actual) + } + } + + 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 index e5b2a6a7b..fb1517710 100644 --- a/test/fn-api-tests/routes_api.go +++ b/test/fn-api-tests/routes_api.go @@ -89,17 +89,19 @@ func assertRouteFields(t *testing.T, routeObject *models.Route, path, image, rou } -func createRoute(ctx context.Context, fnclient *client.Fn, appName, image, routePath, routeType, routeFormat string, routeConfig map[string]string, headers map[string][]string) (*routes.PostAppsAppRoutesOK, error) { +func createRoute(ctx context.Context, fnclient *client.Fn, appName, image, routePath, routeType, routeFormat string, timeout, idleTimeout int32, 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, - Format: routeFormat, + Config: routeConfig, + Headers: headers, + Image: image, + Path: routePath, + Type: routeType, + Format: routeFormat, + Timeout: &timeout, + IDLETimeout: &idleTimeout, }, }, Context: ctx, @@ -119,8 +121,8 @@ func createRoute(ctx context.Context, fnclient *client.Fn, appName, image, route } -func CreateRoute(t *testing.T, ctx context.Context, fnclient *client.Fn, appName, routePath, image, routeType, routeFormat string, routeConfig map[string]string, headers map[string][]string) { - routeResponse, err := createRoute(ctx, fnclient, appName, image, routePath, routeType, routeFormat, routeConfig, headers) +func CreateRoute(t *testing.T, ctx context.Context, fnclient *client.Fn, appName, routePath, image, routeType, routeFormat string, timeout, idleTimeout int32, routeConfig map[string]string, headers map[string][]string) { + routeResponse, err := createRoute(ctx, fnclient, appName, image, routePath, routeType, routeFormat, timeout, idleTimeout, routeConfig, headers) CheckRouteResponseError(t, err) assertRouteFields(t, routeResponse.Payload.Route, routePath, image, routeType, routeFormat) diff --git a/test/fn-api-tests/routes_test.go b/test/fn-api-tests/routes_test.go index 1dece0808..a02316a0e 100644 --- a/test/fn-api-tests/routes_test.go +++ b/test/fn-api-tests/routes_test.go @@ -4,208 +4,207 @@ import ( "testing" "github.com/fnproject/fn/api/id" - api_models "github.com/fnproject/fn/api/models" "github.com/fnproject/fn_go/models" + "reflect" ) -func TestRoutes(t *testing.T) { - newRouteType := "sync" - newRoutePath := id.New().String() - t.Run("create-route-with-empty-type", func(t *testing.T) { - t.Parallel() - s := SetupDefaultSuite() - CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) - _, err := createRoute(s.Context, s.Client, s.AppName, s.RoutePath, s.Image, "", s.Format, - s.RouteConfig, s.RouteHeaders) - if err == nil { - t.Errorf("Should fail with Invalid route Type.") - } - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - - 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.Format, s.RouteConfig, s.RouteHeaders) - 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.Format, s.RouteConfig, s.RouteHeaders) - if !assertContainsRoute(ListRoutes(t, s.Context, s.Client, s.AppName), s.RoutePath) { - t.Errorf("Unable to find corresponding route `%v` in list", 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.Format, s.RouteConfig, s.RouteHeaders) - - rObjects := []*models.Route{GetRoute(t, s.Context, s.Client, s.AppName, s.RoutePath)} - if !assertContainsRoute(rObjects, s.RoutePath) { - t.Errorf("Unable to find corresponding route `%v` in list", 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.Format, s.RouteConfig, s.RouteHeaders) - - 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, s.Format) - - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - - t.Run("patch-route-with-config", 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.Format, s.RouteConfig, s.RouteHeaders) - - newRouteConf := map[string]string{ - "A": "a", - } - - routeResp, err := UpdateRoute( - t, s.Context, s.Client, - s.AppName, s.RoutePath, - s.Image, s.RouteType, s.Format, - s.Memory, newRouteConf, s.RouteHeaders, "") - - CheckRouteResponseError(t, err) - assertRouteFields(t, routeResp.Payload.Route, s.RoutePath, s.Image, s.RouteType, s.Format) - - 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.Format, 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.Errorf("Route path suppose to be immutable, but it's not.") - } - - 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.Format, s.RouteConfig, s.RouteHeaders) - - _, err := createRoute(s.Context, s.Client, s.AppName, s.Image, s.RoutePath, - newRouteType, s.Format, s.RouteConfig, s.RouteHeaders) - if err == nil { - t.Errorf("Route duplicate error should appear, but it didn't") - } - - 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.Format, s.RouteConfig, s.RouteHeaders) - - DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) - 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.Error("Delete from missing route must fail.") - } - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - - t.Run("deploy-route-without-existing-app", func(T *testing.T) { - t.Parallel() - s := SetupDefaultSuite() - DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, s.RouteHeaders) - GetApp(t, s.Context, s.Client, s.AppName) - GetRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - - t.Run("deploy-route-with-existing-app", func(T *testing.T) { - s := SetupDefaultSuite() - CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) - DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, s.RouteHeaders) - GetApp(t, s.Context, s.Client, s.AppName) - GetRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - - t.Run("deploy-update-with-existing-route-and-app", func(T *testing.T) { - newRouteType := "sync" - 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.Format, s.RouteConfig, s.RouteHeaders) - - updatedRoute := DeployRoute( - t, s.Context, s.Client, - s.AppName, s.RoutePath, - s.Image, newRouteType, - s.Format, s.RouteConfig, s.RouteHeaders) - assertRouteFields(t, updatedRoute, s.RoutePath, s.Image, newRouteType, s.Format) - - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - - t.Run("multiple-deploy-route-with-headers", func(T *testing.T) { - s := SetupDefaultSuite() - CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) - routeHeaders := map[string][]string{} - routeHeaders["A"] = []string{"a"} - routeHeaders["B"] = []string{"b"} - DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, routeHeaders) - sameRoute := DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, routeHeaders) - if !api_models.Headers(sameRoute.Headers).Equals(api_models.Headers(routeHeaders)) { - t.Error("Route headers should remain the same after multiple deploys with exact the same parameters") - } - DeleteApp(t, s.Context, s.Client, s.AppName) - }) - +func TestCreateRouteEmptyType(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + _, err := createRoute(s.Context, s.Client, s.AppName, s.RoutePath, s.Image, "", + s.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + if err == nil { + t.Errorf("Should fail with Invalid route Type.") + } + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestCanCreateRoute(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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestListRoutes(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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + if !assertContainsRoute(ListRoutes(t, s.Context, s.Client, s.AppName), s.RoutePath) { + t.Errorf("Unable to find corresponding route `%v` in list", s.RoutePath) + } + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestInspectRoute(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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + + rObjects := []*models.Route{GetRoute(t, s.Context, s.Client, s.AppName, s.RoutePath)} + if !assertContainsRoute(rObjects, s.RoutePath) { + t.Errorf("Unable to find corresponding route `%v` in list", s.RoutePath) + } + + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestCanUpdateRouteType(t *testing.T) { + newRouteType := "sync" + 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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + + 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, s.Format) + + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestCanUpdateRouteConfig(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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + + newRouteConf := map[string]string{ + "A": "a", + } + + routeResp, err := UpdateRoute( + t, s.Context, s.Client, + s.AppName, s.RoutePath, + s.Image, s.RouteType, s.Format, + s.Memory, newRouteConf, s.RouteHeaders, "") + + CheckRouteResponseError(t, err) + assertRouteFields(t, routeResp.Payload.Route, s.RoutePath, s.Image, s.RouteType, s.Format) + + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestCantUpdateRoutePath(t *testing.T) { + + t.Parallel() + newRoutePath := id.New().String() + 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.Format, s.Timeout, s.IdleTimeout, 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.Errorf("Route path suppose to be immutable, but it's not.") + } + + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestRouteDuplicate(t *testing.T) { + t.Parallel() + newRouteType := "async" + 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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + + _, err := createRoute(s.Context, s.Client, s.AppName, s.Image, s.RoutePath, + newRouteType, s.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + if err == nil { + t.Errorf("Route duplicate error should appear, but it didn't") + } + + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestCanDeleteRoute(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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + + DeleteRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestCantDeleteRoute(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.Error("Delete from missing route must fail.") + } + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestDeployNewApp(t *testing.T) { + t.Parallel() + s := SetupDefaultSuite() + DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, s.RouteHeaders) + GetApp(t, s.Context, s.Client, s.AppName) + GetRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestDeployExistingApp(t *testing.T) { + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, s.RouteHeaders) + GetApp(t, s.Context, s.Client, s.AppName) + GetRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestDeployUpdate(t *testing.T) { + newRouteType := "sync" + 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.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders) + + updatedRoute := DeployRoute( + t, s.Context, s.Client, + s.AppName, s.RoutePath, + s.Image, newRouteType, + s.Format, s.RouteConfig, s.RouteHeaders) + assertRouteFields(t, updatedRoute, s.RoutePath, s.Image, newRouteType, s.Format) + + DeleteApp(t, s.Context, s.Client, s.AppName) +} + +func TestMulpileDeployExistingApp(t *testing.T) { + s := SetupDefaultSuite() + CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) + routeHeaders := map[string][]string{} + routeHeaders["A"] = []string{"a"} + routeHeaders["B"] = []string{"b"} + DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, routeHeaders) + sameRoute := DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, routeHeaders) + if ok := reflect.DeepEqual(sameRoute.Headers, routeHeaders); !ok { + t.Error("Route headers should remain the same after multiple deploys with exact the same parameters") + } + 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 2647facf4..4efafa1b7 100644 --- a/test/fn-api-tests/utils.go +++ b/test/fn-api-tests/utils.go @@ -12,6 +12,7 @@ import ( "runtime" "strings" "sync" + "testing" "time" "github.com/fnproject/fn/api/common" @@ -104,6 +105,8 @@ type SuiteSetup struct { RouteType string Format string Memory uint64 + Timeout int32 + IdleTimeout int32 RouteConfig map[string]string RouteHeaders map[string][]string Cancel context.CancelFunc @@ -131,6 +134,8 @@ func SetupDefaultSuite() *SuiteSetup { RouteHeaders: map[string][]string{}, Cancel: cancel, Memory: uint64(256), + Timeout: int32(30), + IdleTimeout: int32(30), } if Host() != "localhost:8080" { @@ -224,3 +229,16 @@ func MyCaller() string { f, l := fun.FileLine(fpcs[0] - 1) return fmt.Sprintf("%s:%d", f, l) } + +func APICallWithRetry(t *testing.T, attempts int, sleep time.Duration, callback func() error) (err error) { + for i := 0; i < attempts; i++ { + err = callback() + if err == nil { + t.Log("Exiting retry loop, API call was successful") + return nil + } + time.Sleep(sleep) + t.Logf("[%v] - Retryting API call after unsuccessful attemt with error: %v", i, err.Error()) + } + return err +}