Speed up API tests (#624)

* Adjust API tests internal API

* Refactor API tests to take less time

 - sqlite: tests 15s, overall time: 1m
 - mysql: tests 15s, overall time: 59s

* Use retry func to survive in faulty places

* Use retry func while trying to ping SQL datastore

 - implements retry func specifically for SQL datastore ping
 - fmt fixes
 - using sqlx.Db.PingContext instead of sqlx.Db.Ping
 - propogate context to SQL datastore

* Simplify TestCanCauseTimeout retry loop

* Call retry with sane timeout

* Fix TestOversizedLog, use retry func

* Increase number of attempts

 2 test cases are really faulty in CI, so they need a lot more time to finish.

* Increase TestCanCauseTimeout timeout

* Use retry at TestMultiLog to speed it up

* Use retry at TestCanWriteLogs to speed it up

* Use retry at TestGetCallsSuccess to speed it up

* Use retry at TestCanGetAsyncState to speed it up

* Use retry at TestListCallsSuccess to speed it up

* Remove sleep calls

* Remove dup test case

* Cleaup Calls API test

* Build API tests binary once

 This patch lets CI to build API tests binary once and reuse that whenever it needs it

* Swap API tests checks

* Build API test binary by default

 dirty fix for CircleCI

* Use retry func to determine if datastore is alive in tests

* go install should also reduce build time

* Fix rebase issues
This commit is contained in:
Denis Makogon
2018-01-02 21:29:49 +02:00
committed by Reed Allman
parent cafc277325
commit 9d6f0b2a05
10 changed files with 785 additions and 773 deletions

2
.gitignore vendored
View File

@@ -30,6 +30,6 @@ fnlb/fnlb
/fn /fn
.DS_Store .DS_Store
/fnserver /fnserver
.idea/
*iml *iml
target/ target/
fn-api-tests.test

View File

@@ -31,11 +31,14 @@ test-basic: checkfmt pull-images fn-test-utils
test: checkfmt pull-images test-basic test-middleware test-extensions test: checkfmt pull-images test-basic test-middleware test-extensions
test-api: test-basic test-api: test-basic
./api_test.sh mysql 4
./api_test.sh postgres 4
./api_test.sh sqlite3 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: img-sleeper:
docker pull fnproject/sleeper docker pull fnproject/sleeper

View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
set -exuo pipefail set -exo pipefail
function host { function host {
case ${DOCKER_LOCATION:-localhost} in case ${DOCKER_LOCATION:-localhost} in
@@ -24,17 +24,16 @@ case "$1" in
"sqlite3" ) "sqlite3" )
rm -fr /tmp/fn_integration_tests.db rm -fr /tmp/fn_integration_tests.db
touch /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" ) "mysql" )
DB_CONTAINER="func-mysql-test" DB_CONTAINER="func-mysql-test"
docker rm -fv ${DB_CONTAINER} || echo No prev mysql test db container 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 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_HOST=`host ${DB_CONTAINER}`
MYSQL_PORT=3306 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" DB_CONTAINER="func-postgres-test"
docker rm -fv ${DB_CONTAINER} || echo No prev test db container 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 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_HOST=`host ${DB_CONTAINER}`
POSTGRES_PORT=5432 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 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 ../../ cd test/fn-api-tests && FN_API_URL="http://localhost:8080" FN_DB_URL=${FN_DB_URL} go test -v -parallel ${2:-1} ./...; cd ../../

View File

@@ -9,121 +9,118 @@ import (
"github.com/fnproject/fn_go/client/apps" "github.com/fnproject/fn_go/client/apps"
) )
func TestApps(t *testing.T) { func TestAppDeleteNotFound(t *testing.T) {
t.Parallel()
t.Run("delete-app-not-found-test", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() cfg := &apps.DeleteAppsAppParams{
s := SetupDefaultSuite() App: "missing-app",
cfg := &apps.DeleteAppsAppParams{ Context: s.Context,
App: "missing-app", }
Context: s.Context, cfg.WithTimeout(time.Second * 60)
} _, err := s.Client.Apps.DeleteAppsApp(cfg)
cfg.WithTimeout(time.Second * 60) if err == nil {
_, err := s.Client.Apps.DeleteAppsApp(cfg) t.Errorf("Error during app delete: we should get HTTP 404, but got: %s", err.Error())
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()
t.Run("app-not-found-test", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() cfg := &apps.GetAppsAppParams{
s := SetupDefaultSuite() App: "missing-app",
cfg := &apps.GetAppsAppParams{ Context: s.Context,
App: "missing-app", }
Context: s.Context, cfg.WithTimeout(time.Second * 60)
} _, err := s.Client.Apps.GetAppsApp(cfg)
cfg.WithTimeout(time.Second * 60) CheckAppResponseError(t, err)
_, err := s.Client.Apps.GetAppsApp(cfg) }
CheckAppResponseError(t, err)
}) func TestAppCreateNoConfigSuccess(t *testing.T) {
t.Parallel()
t.Run("create-app-and-delete-no-config-test", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
s := SetupDefaultSuite() DeleteApp(t, s.Context, s.Client, s.AppName)
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()
t.Run("create-app-with-config-test", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{"A": "a"})
s := SetupDefaultSuite() DeleteApp(t, s.Context, s.Client, s.AppName)
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()
t.Run("inspect-app-with-config-test", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{"A": "a"})
s := SetupDefaultSuite() app := GetApp(t, s.Context, s.Client, s.AppName)
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{"A": "a"}) val, ok := app.Config["A"]
app := GetApp(t, s.Context, s.Client, s.AppName) if !ok {
val, ok := app.Config["A"] t.Error("Error during app config inspect: config map misses required entity `A` with value `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)
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)
} }
DeleteApp(t, s.Context, s.Client, s.AppName)
}) func TestAppPatchSameConfig(t *testing.T) {
t.Parallel()
t.Run("patch-app-with-exact-same-config-data", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() config := map[string]string{
s := SetupDefaultSuite() "A": "a",
config := map[string]string{ }
"A": "a",
} appUpdatePayload := CreateUpdateApp(t, s.Context, s.Client, s.AppName, config)
_, ok := appUpdatePayload.Payload.App.Config["A"]
appUpdatePayload := CreateUpdateApp(t, s.Context, s.Client, s.AppName, config) if !ok {
_, ok := appUpdatePayload.Payload.App.Config["A"] t.Error("Error during app update: config map misses required entity `A` with value `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)
}
DeleteApp(t, s.Context, s.Client, s.AppName)
}) func TestAppPatchOverwriteConfig(t *testing.T) {
t.Parallel()
t.Run("patch-override-app-config", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() config := map[string]string{
s := SetupDefaultSuite() "A": "b",
config := map[string]string{ }
"A": "b", appPayload := CreateUpdateApp(t, s.Context, s.Client, s.AppName, config)
} val, ok := appPayload.Payload.App.Config["A"]
appPayload := CreateUpdateApp(t, s.Context, s.Client, s.AppName, config) if !ok {
val, ok := appPayload.Payload.App.Config["A"] t.Error("Error during app config inspect: config map misses required entity `A` with value `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)
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)
} }
DeleteApp(t, s.Context, s.Client, s.AppName)
}) func TestAppsPatchConfigAddValue(t *testing.T) {
t.Parallel()
t.Run("patch-add-app-config", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() config := map[string]string{
s := SetupDefaultSuite() "B": "b",
config := map[string]string{ }
"B": "b", appPayload := CreateUpdateApp(t, s.Context, s.Client, s.AppName, config)
} val, ok := appPayload.Payload.App.Config["B"]
appPayload := CreateUpdateApp(t, s.Context, s.Client, s.AppName, config) if !ok {
val, ok := appPayload.Payload.App.Config["B"] t.Error("Error during app config inspect: config map misses required entity `B` with value `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)
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)
} }
DeleteApp(t, s.Context, s.Client, s.AppName)
}) func TestAppDuplicate(t *testing.T) {
t.Parallel()
t.Run("crete-app-duplicate", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
s := SetupDefaultSuite() _, err := CreateAppNoAssert(s.Context, s.Client, s.AppName, map[string]string{})
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) if reflect.TypeOf(err) != reflect.TypeOf(apps.NewPostAppsConflict()) {
_, err := CreateAppNoAssert(s.Context, s.Client, s.AppName, map[string]string{}) CheckAppResponseError(t, err)
if reflect.TypeOf(err) != reflect.TypeOf(apps.NewPostAppsConflict()) { }
CheckAppResponseError(t, err) DeleteApp(t, s.Context, s.Client, s.AppName)
}
DeleteApp(t, s.Context, s.Client, s.AppName)
})
} }

View File

@@ -10,106 +10,71 @@ import (
"github.com/fnproject/fn_go/client/call" "github.com/fnproject/fn_go/client/call"
) )
func TestCalls(t *testing.T) { func TestCallsMissingApp(t *testing.T) {
t.Parallel()
t.Run("list-calls-for-missing-app", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() cfg := &call.GetAppsAppCallsParams{
s := SetupDefaultSuite() App: s.AppName,
cfg := &call.GetAppsAppCallsParams{ Path: &s.RoutePath,
App: s.AppName, Context: s.Context,
Path: &s.RoutePath, }
Context: s.Context, _, err := s.Client.Call.GetAppsAppCalls(cfg)
} if err == nil {
_, err := s.Client.Call.GetAppsAppCalls(cfg) t.Errorf("Must fail with missing app error, but got %s", err)
if err == nil { }
t.Errorf("Must fail with missing app error, but got %s", err) }
}
}) func TestCallsDummy(t *testing.T) {
t.Parallel()
t.Run("get-dummy-call", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
s := SetupDefaultSuite() CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType,
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) s.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders)
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",
cfg := &call.GetAppsAppCallsCallParams{ App: s.AppName,
Call: "dummy", Context: s.Context,
App: s.AppName, }
Context: s.Context, cfg.WithTimeout(time.Second * 60)
} _, err := s.Client.Call.GetAppsAppCallsCall(cfg)
cfg.WithTimeout(time.Second * 60) if err == nil {
_, err := s.Client.Call.GetAppsAppCallsCall(cfg) t.Error("Must fail because `dummy` call does not exist.")
if err == nil { }
t.Error("Must fail because `dummy` call does not exist.")
} DeleteApp(t, s.Context, s.Client, s.AppName)
}
DeleteApp(t, s.Context, s.Client, s.AppName)
}) func TestGetExactCall(t *testing.T) {
t.Parallel()
t.Run("get-real-call", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
s := SetupDefaultSuite() CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType,
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) s.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders)
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",
u := url.URL{ Host: Host(),
Scheme: "http", }
Host: Host(), u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath)
}
u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) callID := CallAsync(t, u, &bytes.Buffer{})
time.Sleep(time.Second * 5) cfg := &call.GetAppsAppCallsCallParams{
_, err := s.Client.Call.GetAppsAppCalls(&call.GetAppsAppCallsParams{ Call: callID,
App: s.AppName, App: s.AppName,
Path: &s.RoutePath, Context: s.Context,
}) }
if err != nil { cfg.WithTimeout(time.Second * 60)
switch err.(type) {
case *call.GetAppsAppCallsCallNotFound: retryErr := APICallWithRetry(t, 10, time.Second*2, func() (err error) {
msg := err.(*call.GetAppsAppCallsCallNotFound).Payload.Error.Message _, err = s.Client.Call.GetAppsAppCallsCall(cfg)
t.Errorf("Unexpected error occurred: %v.", msg) return err
} })
}
DeleteApp(t, s.Context, s.Client, s.AppName) if retryErr != nil {
}) t.Error(retryErr.Error())
}
t.Run("list-calls", func(t *testing.T) {
t.Parallel() DeleteApp(t, s.Context, s.Client, s.AppName)
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)
})
} }

View File

@@ -14,15 +14,6 @@ import (
"github.com/fnproject/fn_go/client/operations" "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 { func CallAsync(t *testing.T, u url.URL, content io.Reader) string {
output := &bytes.Buffer{} output := &bytes.Buffer{}
_, err := CallFN(u.String(), content, output, "POST", []string{}) _, 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 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" 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) { u := url.URL{
t.Parallel() Scheme: "http",
s := SetupDefaultSuite() Host: Host(),
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) }
CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, "sync", u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath)
s.Format, s.RouteConfig, s.RouteHeaders)
u := url.URL{ _, err := UpdateRoute(
Scheme: "http", t, s.Context, s.Client,
Host: Host(), s.AppName, s.RoutePath,
} s.Image, newRouteType, s.Format,
u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath) s.Memory, s.RouteConfig, s.RouteHeaders, "")
content := &bytes.Buffer{} CheckRouteResponseError(t, err)
output := &bytes.Buffer{}
_, err := CallFN(u.String(), content, output, "POST", []string{}) CallAsync(t, u, &bytes.Buffer{})
if err != nil { DeleteApp(t, s.Context, s.Client, s.AppName)
t.Errorf("Got unexpected error: %v", err) }
}
expectedOutput := "Hello World!\n" func TestCanGetAsyncState(t *testing.T) {
if !strings.Contains(expectedOutput, output.String()) { newRouteType := "async"
t.Errorf("Assertion error.\n\tExpected: %v\n\tActual: %v", expectedOutput, output.String()) t.Parallel()
} s := SetupDefaultSuite()
DeleteApp(t, s.Context, s.Client, s.AppName) 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) { if retryErr != nil {
t.Parallel() t.Error(retryErr.Error())
s := SetupDefaultSuite() } else {
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)
callResponse, err := s.Client.Call.GetAppsAppCallsCall(cfg) callResponse, err := s.Client.Call.GetAppsAppCallsCall(cfg)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
@@ -184,46 +181,54 @@ func TestRouteExecutions(t *testing.T) {
if callObject.Status != "success" { if callObject.Status != "success" {
t.Errorf("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)
} }
}
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) { if retryErr != nil {
t.Parallel() t.Error(retryErr.Error())
s := SetupDefaultSuite() } else {
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)
callObj, err := s.Client.Call.GetAppsAppCallsCall(cfg) callObj, err := s.Client.Call.GetAppsAppCallsCall(cfg)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %s", err) 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", t.Errorf("Call status mismatch.\n\tExpected: %v\n\tActual: %v",
"output", "callObj.Payload.Call.Status") "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) { if retryErr != nil {
t.Parallel() t.Error(retryErr.Error())
s := SetupDefaultSuite() } else {
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,
}
logObj, err := s.Client.Operations.GetAppsAppCallsCallLog(cfg) logObj, err := s.Client.Operations.GetAppsAppCallsCallLog(cfg)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %s", err) t.Errorf("Unexpected error: %s", err)
@@ -277,115 +289,120 @@ func TestRouteExecutions(t *testing.T) {
t.Errorf("Log entry must contain `Second line` "+ t.Errorf("Log entry must contain `Second line` "+
"string, but got: %v", logObj.Payload.Log.Log) "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) { if retryErr != nil {
t.Parallel() t.Error(retryErr.Error())
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)
u := url.URL{ DeleteApp(t, s.Context, s.Client, s.AppName)
Scheme: "http", }
Host: Host(),
} func TestOversizedLog(t *testing.T) {
u.Path = path.Join(u.Path, "r", s.AppName, routePath) t.Parallel()
content := &bytes.Buffer{} s := SetupDefaultSuite()
output := &bytes.Buffer{} routePath := "/log"
CallFN(u.String(), content, output, "POST", image := "funcy/log:0.0.1"
[]string{ routeType := "async"
"ACCEPT: application/xml",
"ACCEPT: application/json; q=0.2", CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
}) CreateRoute(t, s.Context, s.Client, s.AppName, routePath, image, routeType,
res := output.String() s.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders)
if !strings.Contains("application/xml, application/json; q=0.2", res) {
t.Errorf("HEADER_ACCEPT='application/xml, application/json; q=0.2' "+ size := 1 * 1024 * 1024 * 1024
"should be in output, have:%s\n", res) u := url.URL{
} Scheme: "http",
DeleteRoute(t, s.Context, s.Client, s.AppName, routePath) Host: Host(),
DeleteApp(t, s.Context, s.Client, s.AppName) }
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
}) })
if retryErr != nil {
t.Run("exec-log-test", func(t *testing.T) { t.Error(retryErr.Error())
//XXX: Fix this test. } else {
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,
}
logObj, err := s.Client.Operations.GetAppsAppCallsCallLog(cfg) logObj, err := s.Client.Operations.GetAppsAppCallsCallLog(cfg)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %s", err) 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", t.Errorf("Log entry suppose to be truncated up to expected size %v, got %v",
size/1024, len(logObj.Payload.Log.Log)) size/1024, len(logObj.Payload.Log.Log))
} }
DeleteApp(t, s.Context, s.Client, s.AppName)
})
}
DeleteApp(t, s.Context, s.Client, s.AppName)
} }

View File

@@ -14,59 +14,54 @@ type JSONResponse struct {
Message string `json:"message"` 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) { // TODO(treeder): put image in fnproject @ dockerhub
t.Parallel() image := "denismakogon/test-hot-json-go:0.0.1"
s := SetupDefaultSuite() format := "json"
route := "/test-hot-json-go"
// TODO(treeder): put image in fnproject @ dockerhub CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
image := "denismakogon/test-hot-json-go:0.0.1" CreateRoute(t, s.Context, s.Client, s.AppName, route, image, "sync",
format := "json" format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders)
route := "/test-hot-json-go"
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) u := url.URL{
CreateRoute(t, s.Context, s.Client, s.AppName, route, image, "sync", Scheme: "http",
format, s.RouteConfig, s.RouteHeaders) Host: Host(),
}
u := url.URL{ u.Path = path.Join(u.Path, "r", s.AppName, s.RoutePath)
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)
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)
} }

View File

@@ -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{ cfg := &routes.PostAppsAppRoutesParams{
App: appName, App: appName,
Body: &models.RouteWrapper{ Body: &models.RouteWrapper{
Route: &models.Route{ Route: &models.Route{
Config: routeConfig, Config: routeConfig,
Headers: headers, Headers: headers,
Image: image, Image: image,
Path: routePath, Path: routePath,
Type: routeType, Type: routeType,
Format: routeFormat, Format: routeFormat,
Timeout: &timeout,
IDLETimeout: &idleTimeout,
}, },
}, },
Context: ctx, 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) { 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, routeConfig, headers) routeResponse, err := createRoute(ctx, fnclient, appName, image, routePath, routeType, routeFormat, timeout, idleTimeout, routeConfig, headers)
CheckRouteResponseError(t, err) CheckRouteResponseError(t, err)
assertRouteFields(t, routeResponse.Payload.Route, routePath, image, routeType, routeFormat) assertRouteFields(t, routeResponse.Payload.Route, routePath, image, routeType, routeFormat)

View File

@@ -4,208 +4,207 @@ import (
"testing" "testing"
"github.com/fnproject/fn/api/id" "github.com/fnproject/fn/api/id"
api_models "github.com/fnproject/fn/api/models"
"github.com/fnproject/fn_go/models" "github.com/fnproject/fn_go/models"
"reflect"
) )
func TestRoutes(t *testing.T) { func TestCreateRouteEmptyType(t *testing.T) {
newRouteType := "sync" t.Parallel()
newRoutePath := id.New().String() s := SetupDefaultSuite()
t.Run("create-route-with-empty-type", func(t *testing.T) { CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
t.Parallel() _, err := createRoute(s.Context, s.Client, s.AppName, s.RoutePath, s.Image, "",
s := SetupDefaultSuite() s.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders)
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) if err == nil {
_, err := createRoute(s.Context, s.Client, s.AppName, s.RoutePath, s.Image, "", s.Format, t.Errorf("Should fail with Invalid route Type.")
s.RouteConfig, s.RouteHeaders) }
if err == nil { DeleteApp(t, s.Context, s.Client, s.AppName)
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()
t.Run("create-route", func(t *testing.T) { CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
t.Parallel() CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType,
s := SetupDefaultSuite() s.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders)
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) DeleteApp(t, s.Context, s.Client, s.AppName)
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) func TestListRoutes(t *testing.T) {
}) t.Parallel()
s := SetupDefaultSuite()
t.Run("list-and-find-route", func(t *testing.T) { CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
t.Parallel() CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType,
s := SetupDefaultSuite() s.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders)
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) if !assertContainsRoute(ListRoutes(t, s.Context, s.Client, s.AppName), s.RoutePath) {
CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, t.Errorf("Unable to find corresponding route `%v` in list", s.RoutePath)
s.Format, s.RouteConfig, s.RouteHeaders) }
if !assertContainsRoute(ListRoutes(t, s.Context, s.Client, s.AppName), s.RoutePath) { DeleteApp(t, s.Context, s.Client, s.AppName)
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()
t.Run("can-get-corresponding-route", func(t *testing.T) { CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
t.Parallel() CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType,
s := SetupDefaultSuite() s.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, s.RoutePath, s.Image, s.RouteType, rObjects := []*models.Route{GetRoute(t, s.Context, s.Client, s.AppName, s.RoutePath)}
s.Format, s.RouteConfig, s.RouteHeaders) if !assertContainsRoute(rObjects, s.RoutePath) {
t.Errorf("Unable to find corresponding route `%v` in list", s.RoutePath)
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)
} }
DeleteApp(t, s.Context, s.Client, s.AppName) func TestCanUpdateRouteType(t *testing.T) {
}) newRouteType := "sync"
t.Parallel()
t.Run("can-update-route-info", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
s := SetupDefaultSuite() CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType,
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) s.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders)
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,
routeResp, err := UpdateRoute( s.AppName, s.RoutePath,
t, s.Context, s.Client, s.Image, newRouteType, s.Format,
s.AppName, s.RoutePath, s.Memory, s.RouteConfig, s.RouteHeaders, "")
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)
CheckRouteResponseError(t, err)
assertRouteFields(t, routeResp.Payload.Route, s.RoutePath, s.Image, newRouteType, s.Format) DeleteApp(t, s.Context, s.Client, s.AppName)
}
DeleteApp(t, s.Context, s.Client, s.AppName)
}) func TestCanUpdateRouteConfig(t *testing.T) {
t.Parallel()
t.Run("patch-route-with-config", func(t *testing.T) { s := SetupDefaultSuite()
t.Parallel() CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
s := SetupDefaultSuite() CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType,
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) s.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders)
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",
newRouteConf := map[string]string{ }
"A": "a",
} routeResp, err := UpdateRoute(
t, s.Context, s.Client,
routeResp, err := UpdateRoute( s.AppName, s.RoutePath,
t, s.Context, s.Client, s.Image, s.RouteType, s.Format,
s.AppName, s.RoutePath, s.Memory, newRouteConf, s.RouteHeaders, "")
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)
CheckRouteResponseError(t, err)
assertRouteFields(t, routeResp.Payload.Route, s.RoutePath, s.Image, s.RouteType, s.Format) DeleteApp(t, s.Context, s.Client, s.AppName)
}
DeleteApp(t, s.Context, s.Client, s.AppName)
}) func TestCantUpdateRoutePath(t *testing.T) {
t.Run("fail-to-update-route-path", func(t *testing.T) { t.Parallel()
t.Parallel() newRoutePath := id.New().String()
s := SetupDefaultSuite() s := SetupDefaultSuite()
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) 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, CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType,
s.Format, s.RouteConfig, s.RouteHeaders) s.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders)
_, err := UpdateRoute( _, err := UpdateRoute(
t, s.Context, s.Client, t, s.Context, s.Client,
s.AppName, s.RoutePath, s.AppName, s.RoutePath,
s.Image, s.RouteType, s.Format, s.Image, s.RouteType, s.Format,
s.Memory, s.RouteConfig, s.RouteHeaders, newRoutePath) s.Memory, s.RouteConfig, s.RouteHeaders, newRoutePath)
if err == nil { if err == nil {
t.Errorf("Route path suppose to be immutable, but it's not.") t.Errorf("Route path suppose to be immutable, but it's not.")
} }
DeleteApp(t, s.Context, s.Client, s.AppName) DeleteApp(t, s.Context, s.Client, s.AppName)
}) }
t.Run("create-route-duplicate", func(t *testing.T) { func TestRouteDuplicate(t *testing.T) {
t.Parallel() t.Parallel()
s := SetupDefaultSuite() newRouteType := "async"
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) s := SetupDefaultSuite()
CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
s.Format, s.RouteConfig, s.RouteHeaders) 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.RouteConfig, s.RouteHeaders) _, err := createRoute(s.Context, s.Client, s.AppName, s.Image, s.RoutePath,
if err == nil { newRouteType, s.Format, s.Timeout, s.IdleTimeout, s.RouteConfig, s.RouteHeaders)
t.Errorf("Route duplicate error should appear, but it didn't") if err == nil {
} t.Errorf("Route duplicate error should appear, but it didn't")
}
DeleteApp(t, s.Context, s.Client, s.AppName)
}) DeleteApp(t, s.Context, s.Client, s.AppName)
}
t.Run("can-delete-route", func(t *testing.T) {
t.Parallel() func TestCanDeleteRoute(t *testing.T) {
s := SetupDefaultSuite() t.Parallel()
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) s := SetupDefaultSuite()
CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
s.Format, s.RouteConfig, s.RouteHeaders) 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) 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() func TestCantDeleteRoute(t *testing.T) {
s := SetupDefaultSuite() t.Parallel()
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) 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 { _, err := deleteRoute(s.Context, s.Client, s.AppName, "dummy-route")
t.Error("Delete from missing route must fail.") if err == nil {
} t.Error("Delete from missing route must fail.")
DeleteApp(t, s.Context, s.Client, s.AppName) }
}) DeleteApp(t, s.Context, s.Client, s.AppName)
}
t.Run("deploy-route-without-existing-app", func(T *testing.T) {
t.Parallel() func TestDeployNewApp(t *testing.T) {
s := SetupDefaultSuite() t.Parallel()
DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, s.RouteHeaders) s := SetupDefaultSuite()
GetApp(t, s.Context, s.Client, s.AppName) DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, s.RouteHeaders)
GetRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) GetApp(t, s.Context, s.Client, s.AppName)
DeleteApp(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() func TestDeployExistingApp(t *testing.T) {
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) s := SetupDefaultSuite()
DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, s.RouteHeaders) CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
GetApp(t, s.Context, s.Client, s.AppName) DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, s.RouteHeaders)
GetRoute(t, s.Context, s.Client, s.AppName, s.RoutePath) GetApp(t, s.Context, s.Client, s.AppName)
DeleteApp(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" func TestDeployUpdate(t *testing.T) {
s := SetupDefaultSuite() newRouteType := "sync"
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) s := SetupDefaultSuite()
CreateRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
s.Format, s.RouteConfig, s.RouteHeaders) 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, updatedRoute := DeployRoute(
s.AppName, s.RoutePath, t, s.Context, s.Client,
s.Image, newRouteType, s.AppName, s.RoutePath,
s.Format, s.RouteConfig, s.RouteHeaders) s.Image, newRouteType,
assertRouteFields(t, updatedRoute, s.RoutePath, s.Image, newRouteType, s.Format) s.Format, s.RouteConfig, s.RouteHeaders)
assertRouteFields(t, updatedRoute, s.RoutePath, s.Image, newRouteType, s.Format)
DeleteApp(t, s.Context, s.Client, s.AppName)
}) DeleteApp(t, s.Context, s.Client, s.AppName)
}
t.Run("multiple-deploy-route-with-headers", func(T *testing.T) {
s := SetupDefaultSuite() func TestMulpileDeployExistingApp(t *testing.T) {
CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) s := SetupDefaultSuite()
routeHeaders := map[string][]string{} CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{})
routeHeaders["A"] = []string{"a"} routeHeaders := map[string][]string{}
routeHeaders["B"] = []string{"b"} routeHeaders["A"] = []string{"a"}
DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, routeHeaders) routeHeaders["B"] = []string{"b"}
sameRoute := DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, routeHeaders) 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)) { sameRoute := DeployRoute(t, s.Context, s.Client, s.AppName, s.RoutePath, s.Image, s.RouteType, s.Format, s.RouteConfig, routeHeaders)
t.Error("Route headers should remain the same after multiple deploys with exact the same parameters") 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) }
}) DeleteApp(t, s.Context, s.Client, s.AppName)
} }

View File

@@ -12,6 +12,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"testing"
"time" "time"
"github.com/fnproject/fn/api/common" "github.com/fnproject/fn/api/common"
@@ -104,6 +105,8 @@ type SuiteSetup struct {
RouteType string RouteType string
Format string Format string
Memory uint64 Memory uint64
Timeout int32
IdleTimeout int32
RouteConfig map[string]string RouteConfig map[string]string
RouteHeaders map[string][]string RouteHeaders map[string][]string
Cancel context.CancelFunc Cancel context.CancelFunc
@@ -131,6 +134,8 @@ func SetupDefaultSuite() *SuiteSetup {
RouteHeaders: map[string][]string{}, RouteHeaders: map[string][]string{},
Cancel: cancel, Cancel: cancel,
Memory: uint64(256), Memory: uint64(256),
Timeout: int32(30),
IdleTimeout: int32(30),
} }
if Host() != "localhost:8080" { if Host() != "localhost:8080" {
@@ -224,3 +229,16 @@ func MyCaller() string {
f, l := fun.FileLine(fpcs[0] - 1) f, l := fun.FileLine(fpcs[0] - 1)
return fmt.Sprintf("%s:%d", f, l) 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
}