Files
fn-serverless/api/datastore/datastoretest/test.go
Tom Coupland d7139358ce List Cursor management moved into datastore layer. (#1102)
* Don't try to delete an app that wasn't successfully created in the case of failure

* Allow datastore implementations to inject additional annotations on objects

* Allow for datastores transparently adding annotations on apps, fns and triggers. Change NameIn filter to Name for apps.

* Move *List types including JSON annotations for App, Fn and Trigger into models

* Change return types for GetApps, GetFns and GetTriggers on datastore to
be models.*List and ove cursor generation into datastore

* Trigger cursor handling fixed into db layer

Also changes the name generation so that it is not in the same order
as the id (well is random), this means we are now testing our name ordering.

* GetFns now respects cursors

* Apps now feeds cursor back

* Mock fixes

* Fixing up api level cursor decoding

* Tidy up treatment of cursors in the db layer

* Adding conditions for non nil items lists

* fix mock test
2018-06-29 19:14:13 +01:00

1546 lines
46 KiB
Go

package datastoretest
// Data store correctness tests -
// These tests run validation tests on an underlying data store implementation and can be re-used for new data stores.
// TODO: Generalize some tests around metadata (updated_created,ids)
// TODO: Generalize tests around pagination and filtering
import (
"bytes"
"context"
"fmt"
"log"
"math/rand"
"sort"
"sync/atomic"
"testing"
"time"
"github.com/fnproject/fn/api/id"
"github.com/fnproject/fn/api/models"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func setLogBuffer() *bytes.Buffer {
var buf bytes.Buffer
buf.WriteByte('\n')
logrus.SetOutput(&buf)
gin.DefaultErrorWriter = &buf
gin.DefaultWriter = &buf
log.SetOutput(&buf)
return &buf
}
//ResourceProvider provides an abstraction for supplying data store tests with
// appropriate initial testing objects for running tests
// Use the resource calls to supply objects with (e.g.) middleware enforced annotations set on them
// Use DefaultCtx to override custom middleware-supplied context variables
type ResourceProvider interface {
// ValidApp returns a valid app to use for inserts
ValidApp() *models.App
// ValidFn returns a valid fn to use for inserts
ValidFn(appId string) *models.Fn
// ValidFn returns a valid fn to use for inserts
ValidRoute(appId string) *models.Route
// ValidTrigger returns a valid trigger to use for inserts
ValidTrigger(appId string, fnId string) *models.Trigger
// DefaultCtx returns a context object (which may have custom attributes set)
// this may be used (e.g.) to pass on tenancy and user details that would originate from a middleware to your data store
DefaultCtx() context.Context
}
// BasicResourceProvider supplies simple objects and can be used as a base for custom resource providers
type BasicResourceProvider struct {
idCount uint32
}
// DataStoreFunc provides an instance of a data store
type DataStoreFunc func(*testing.T) models.Datastore
func NewBasicResourceProvider() ResourceProvider {
return &BasicResourceProvider{}
}
func (brp *BasicResourceProvider) NextID() uint32 {
return atomic.AddUint32(&brp.idCount, rand.Uint32())
}
func (brp *BasicResourceProvider) DefaultCtx() context.Context {
return context.Background()
}
// Creates a valid app which always has a sequential named
func (brp *BasicResourceProvider) ValidApp() *models.App {
app := &models.App{
Name: fmt.Sprintf("app_%09d", brp.NextID()),
}
return app
}
func (brp *BasicResourceProvider) ValidTrigger(appId, funcId string) *models.Trigger {
trigger := &models.Trigger{
Name: fmt.Sprintf("trigger_%09d", brp.NextID()),
AppID: appId,
FnID: funcId,
Type: "http",
Source: "ASource",
}
return trigger
}
// Creates a valid route which always has a sequential named
func (brp *BasicResourceProvider) ValidRoute(appId string) *models.Route {
testRoute := &models.Route{
AppID: appId,
Path: fmt.Sprintf("/test_%09d", brp.NextID()),
Image: "fnproject/fn-test-utils",
Type: "sync",
Format: "http",
Timeout: models.DefaultTimeout,
IdleTimeout: models.DefaultIdleTimeout,
Memory: models.DefaultMemory,
}
return testRoute
}
func (brp *BasicResourceProvider) ValidFn(appId string) *models.Fn {
return &models.Fn{
AppID: appId,
Name: fmt.Sprintf("test_%09d", brp.NextID()),
Image: "fnproject/fn-test-utils",
Format: "http",
ResourceConfig: models.ResourceConfig{
Timeout: models.DefaultTimeout,
IdleTimeout: models.DefaultIdleTimeout,
Memory: models.DefaultMemory,
},
}
}
type Harness struct {
ctx context.Context
t *testing.T
ds models.Datastore
appIds []string
}
func (h *Harness) GivenAppInDb(app *models.App) *models.App {
a, err := h.ds.InsertApp(h.ctx, app)
if err != nil {
h.t.Fatal("failed to create app", err)
return nil
}
h.AppForDeletion(a)
return a
}
func (h *Harness) GivenRouteInDb(rt *models.Route) *models.Route {
r, err := h.ds.InsertRoute(h.ctx, rt)
if err != nil {
h.t.Fatal("failed to create rt", err)
return nil
}
return r
}
func (h *Harness) Cleanup() {
for _, appId := range h.appIds {
err := h.ds.RemoveApp(h.ctx, appId)
if err != nil && err != models.ErrAppsNotFound {
h.t.Fatalf("Failed to cleanup app %s %s", appId, err)
}
}
}
func (h *Harness) GivenFnInDb(validFunc *models.Fn) *models.Fn {
fn, err := h.ds.InsertFn(h.ctx, validFunc)
if err != nil {
h.t.Fatalf("Failed to insert function %s", err)
return nil
}
return fn
}
func (h *Harness) GivenTriggerInDb(validTrigger *models.Trigger) *models.Trigger {
trigger, err := h.ds.InsertTrigger(h.ctx, validTrigger)
if err != nil {
h.t.Fatalf("Failed to insert trigger %s", err)
return nil
}
return trigger
}
func (h *Harness) AppForDeletion(app *models.App) {
h.appIds = append(h.appIds, app.ID)
}
func NewHarness(t *testing.T, ctx context.Context, ds models.Datastore) *Harness {
return &Harness{
ctx: ctx,
t: t,
ds: ds,
}
}
type AppByName []*models.App
func (a AppByName) Len() int { return len(a) }
func (a AppByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a AppByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
type FnByName []*models.Fn
func (f FnByName) Len() int { return len(f) }
func (f FnByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f FnByName) Less(i, j int) bool { return f[i].Name < f[j].Name }
type TriggerByName []*models.Trigger
func (f TriggerByName) Len() int { return len(f) }
func (f TriggerByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f TriggerByName) Less(i, j int) bool { return f[i].Name < f[j].Name }
type RouteByPath []*models.Route
func (f RouteByPath) Len() int { return len(f) }
func (f RouteByPath) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f RouteByPath) Less(i, j int) bool { return f[i].Path < f[j].Path }
func RunAppsTest(t *testing.T, dsf DataStoreFunc, rp ResourceProvider) {
ds := dsf(t)
ctx := rp.DefaultCtx()
t.Run("apps", func(t *testing.T) {
// Testing insert app
t.Run("insert missing app name fails", func(t *testing.T) {
_, err := ds.InsertApp(ctx, &models.App{})
if err != models.ErrMissingName {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrMissingName, err)
}
})
t.Run("insert sets created time and updated time ", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
start := time.Now()
returnedApp, err := ds.InsertApp(ctx, rp.ValidApp())
if err != nil {
t.Fatalf("Expected succcess, got %s", err)
}
h.AppForDeletion(returnedApp)
if !time.Time(returnedApp.CreatedAt).After(start) {
t.Fatalf("expected created to be set %s", returnedApp.CreatedAt)
}
if !time.Time(returnedApp.UpdatedAt).After(start) {
t.Fatalf("expected updated to be set %s", returnedApp.UpdatedAt)
}
})
t.Run("update sets update time ", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
// Set a config var
testApp := h.GivenAppInDb(rp.ValidApp())
time.Sleep(10 * time.Millisecond)
testApp.Config = map[string]string{"TEST": "1"}
updated, err := ds.UpdateApp(ctx, testApp)
if err != nil {
t.Fatalf("didn't update app %s", err)
}
if !time.Time(updated.UpdatedAt).After(time.Time(testApp.UpdatedAt)) {
t.Errorf("Expected updated time to be after original %s !> %s", updated.UpdatedAt, testApp.UpdatedAt)
}
})
t.Run("update no-op", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
// Set a config var
testApp := h.GivenAppInDb(rp.ValidApp())
time.Sleep(1 * time.Millisecond)
updated, err := ds.UpdateApp(ctx, testApp)
if err != nil {
t.Fatalf("Expected succes got %s", err)
}
if updated == testApp {
t.Fatalf("Update should return a new value")
}
if updated.UpdatedAt.String() != testApp.UpdatedAt.String() {
t.Fatalf("Expected app not to be updated but update times weren't equal %s != %s", updated.UpdatedAt, testApp.UpdatedAt)
}
})
t.Run("update with new config var", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
// Set a config var
testApp := h.GivenAppInDb(rp.ValidApp())
testApp.Config = map[string]string{"TEST": "1"}
updated, err := ds.UpdateApp(ctx, testApp)
if err != nil {
t.Fatalf("error when updating app: %v", err)
}
expected := &models.App{ID: testApp.ID, Name: testApp.Name, Config: map[string]string{"TEST": "1"}}
if !expected.EqualsWithAnnotationSubset(updated) {
t.Fatalf("expected updated `%v` but got `%v`", expected, updated)
}
})
t.Run("set multiple config vars", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testApp.Config = map[string]string{"TEST": "1"}
updated, err := ds.UpdateApp(ctx, testApp)
if err != nil {
t.Fatalf("error when updating app: %v", err)
}
// Set a different var (without clearing the existing)
another := testApp.Clone()
another.Config = map[string]string{"OTHER": "TEST"}
updated, err = ds.UpdateApp(ctx, another)
if err != nil {
t.Fatalf("error when updating app: %v", err)
}
expected := &models.App{Name: testApp.Name, ID: testApp.ID, Config: map[string]string{"TEST": "1", "OTHER": "TEST"}}
if !expected.EqualsWithAnnotationSubset(updated) {
t.Fatalf("expected updated `%v` but got `%v`", expected, updated)
}
})
t.Run("Insert duplicate named app", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testApp2 := rp.ValidApp()
testApp2.Name = testApp.Name
_, err := ds.InsertApp(ctx, testApp2)
if err != models.ErrAppsAlreadyExists {
t.Fatalf("Expecting duplicate error got %s", err)
}
})
t.Run("Update name is immutable", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testApp.Name = "other"
_, err := ds.UpdateApp(ctx, testApp)
if err != models.ErrAppsNameImmutable {
t.Fatalf("Expecting name immutable %s", err)
}
})
t.Run("remove config var", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
app := rp.ValidApp()
app.Config = map[string]string{"OTHER": "TEST", "TEST": "1"}
// Delete a var
testApp := h.GivenAppInDb(app)
testApp.Config = map[string]string{"TEST": ""}
updated, err := ds.UpdateApp(ctx, testApp)
if err != nil {
t.Fatalf("error when updating app: %v", err)
}
expected := &models.App{Name: testApp.Name, ID: testApp.ID, Config: map[string]string{"OTHER": "TEST"}}
if !expected.EqualsWithAnnotationSubset(updated) {
t.Fatalf("expected updated `%#v` but got `%#v`", expected, updated)
}
})
// Testing get app
t.Run("Get with empty App ID", func(t *testing.T) {
_, err := ds.GetAppByID(ctx, "")
if err != models.ErrAppsMissingID {
t.Fatalf("expected error to be %v, but it was %s", models.ErrAppsMissingID, err)
}
})
t.Run("Get app by ID ", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
app, err := ds.GetAppByID(ctx, testApp.ID)
if err != nil {
t.Fatalf("error: %s", err)
}
if app.Name != testApp.Name {
t.Fatalf("expected `app.Name` to be `%s` but it was `%s`", app.Name, testApp.Name)
}
})
t.Run("List apps", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
apps, err := ds.GetApps(ctx, &models.AppFilter{PerPage: 100})
if err != nil {
t.Fatalf("not expecting err %s", err)
}
if len(apps.Items) != 0 {
t.Fatalf("expecting 0 results, got %d", len(apps.Items))
}
if apps.Items == nil {
t.Fatalf("response items must not be nil")
}
a1 := h.GivenAppInDb(rp.ValidApp())
h.GivenAppInDb(rp.ValidApp())
// Testing list apps
apps, err = ds.GetApps(ctx, &models.AppFilter{PerPage: 100})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if len(apps.Items) != 2 {
t.Fatalf("expected result count to be 2, got %d", len(apps.Items))
}
apps, err = ds.GetApps(ctx, &models.AppFilter{PerPage: 100, Name: a1.Name})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if len(apps.Items) != 1 {
t.Fatalf("expected result count to be 1, got %d", len(apps.Items))
}
for _, app := range apps.Items {
if app.Name == a1.Name {
return
}
}
t.Fatalf("expected app list to contain app %s, got %#v", a1.Name, apps)
})
t.Run("Simple Pagination", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
// test pagination stuff (ordering / limits / cursoring)
a1 := h.GivenAppInDb(rp.ValidApp())
a2 := h.GivenAppInDb(rp.ValidApp())
a3 := h.GivenAppInDb(rp.ValidApp())
gendApps := []*models.App{a1, a2, a3}
sort.Sort(AppByName(gendApps))
apps, err := ds.GetApps(ctx, &models.AppFilter{PerPage: 1})
if err != nil {
t.Fatalf(" error: %s", err)
}
if len(apps.Items) != 1 {
t.Fatalf(" expected result count to be 1 but got %d", len(apps.Items))
} else if apps.Items[0].Name != gendApps[0].Name {
t.Fatalf(" expected `app.Name` to be `%s` but it was `%s`", gendApps[0].Name, apps.Items[0].Name)
}
apps, err = ds.GetApps(ctx, &models.AppFilter{PerPage: 100, Cursor: apps.NextCursor})
if err != nil {
t.Fatalf(" error: %s", err)
}
if len(apps.Items) != 2 {
t.Fatalf(" expected result count to be 2 but got %d", len(apps.Items))
} else if apps.Items[0].Name != gendApps[1].Name {
t.Fatalf(" expected `app.Name` to be `%s` but it was `%s`", gendApps[1].Name, apps.Items[0].Name)
} else if apps.Items[1].Name != gendApps[2].Name {
t.Fatalf(" expected `app.Name` to be `%s` but it was `%s`", gendApps[2].Name, apps.Items[1].Name)
}
a4 := h.GivenAppInDb(rp.ValidApp())
gendApps = append(gendApps, a4)
sort.Sort(AppByName(gendApps))
apps, err = ds.GetApps(ctx, &models.AppFilter{PerPage: 100})
if err != nil {
t.Fatalf(" error: %s", err)
}
if len(apps.Items) != 4 {
t.Fatalf(" expected result count to be 4 but got %d", len(apps.Items))
} else if apps.Items[3].Name != gendApps[3].Name {
t.Fatalf(" expected `app.Name` to be `%s` but it was `%s`", gendApps[4].Name, apps.Items[0].Name)
}
})
t.Run("delete app with empty Id", func(t *testing.T) {
// Testing app delete
err := ds.RemoveApp(ctx, "")
if err != models.ErrAppsMissingID {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrAppsMissingID, err)
}
})
t.Run("delete app results in app not existing", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
err := ds.RemoveApp(ctx, testApp.ID)
if err != nil {
t.Fatalf("error: %s", err)
}
app, err := ds.GetAppByID(ctx, testApp.ID)
if err != models.ErrAppsNotFound {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrAppsNotFound, err)
}
if app != nil {
t.Log(err.Error())
t.Fatal("failed to remove the app, app should be gone already")
}
})
t.Run("cannot update non-existant app ", func(t *testing.T) {
missingApp := &models.App{
ID: "nonexistant",
Name: "nonexistant",
}
_, err := ds.UpdateApp(ctx, missingApp)
if err != models.ErrAppsNotFound {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrAppsNotFound, err)
}
})
})
}
func RunRoutesTest(t *testing.T, dsf DataStoreFunc, rp ResourceProvider) {
ds := dsf(t)
ctx := rp.DefaultCtx()
t.Run("routes", func(t *testing.T) {
t.Run("empty val", func(t *testing.T) {
_, err := ds.InsertRoute(ctx, nil)
if err != models.ErrDatastoreEmptyRoute {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyRoute, err)
}
})
t.Run("Insert with non-existant app ", func(t *testing.T) {
newTestRoute := rp.ValidRoute("notreal")
_, err := ds.InsertRoute(ctx, newTestRoute)
if err != models.ErrAppsNotFound {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrAppsNotFound, err)
}
})
t.Run("Insert duplicate route fails", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testRoute := rp.ValidRoute(testApp.ID)
h.GivenRouteInDb(testRoute)
_, err := ds.InsertRoute(ctx, testRoute)
if err != models.ErrRoutesAlreadyExists {
t.Fatalf("expected error to be `%v`, but it was `%v`", models.ErrRoutesAlreadyExists, err)
}
})
// Testing get
t.Run("get route with empty path", func(t *testing.T) {
_, err := ds.GetRoute(ctx, id.New().String(), "")
if err != models.ErrRoutesMissingPath {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrRoutesMissingPath, err)
}
})
t.Run("get route with empty app id", func(t *testing.T) {
_, err := ds.GetRoute(ctx, "", "a")
if err != models.ErrRoutesMissingAppID {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrRoutesMissingAppID, err)
}
})
t.Run("get valid route", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testRoute := h.GivenRouteInDb(rp.ValidRoute(testApp.ID))
route, err := ds.GetRoute(ctx, testApp.ID, testRoute.Path)
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if !route.Equals(testRoute) {
t.Fatalf("expected to insert:\n%v\nbut got:\n%v", testRoute, *route)
}
})
// Testing update
t.Run("update route set headers and config", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testRoute := h.GivenRouteInDb(rp.ValidRoute(testApp.ID))
// Update some fields, and add 3 configs and 3 headers.
updated, err := ds.UpdateRoute(ctx, &models.Route{
AppID: testApp.ID,
Path: testRoute.Path,
Timeout: 2,
Config: map[string]string{
"FIRST": "1",
"SECOND": "2",
"THIRD": "3",
},
Headers: models.Headers{
"First": []string{"test"},
"Second": []string{"test", "test"},
"Third": []string{"test", "test2"},
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := &models.Route{
// unchanged
AppID: testApp.ID,
Path: testRoute.Path,
Image: "fnproject/fn-test-utils",
Type: "sync",
Format: "http",
IdleTimeout: testRoute.IdleTimeout,
Memory: testRoute.Memory,
CPUs: testRoute.CPUs,
// updated
Timeout: 2,
Config: map[string]string{
"FIRST": "1",
"SECOND": "2",
"THIRD": "3",
},
Headers: models.Headers{
"First": []string{"test"},
"Second": []string{"test", "test"},
"Third": []string{"test", "test2"},
},
}
if !updated.Equals(expected) {
t.Fatalf("expected updated `%v` but got `%v`", expected, updated)
}
})
t.Run("update route modify headers and config", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testRoute := rp.ValidRoute(testApp.ID)
testRoute.Config = map[string]string{
"FIRST": "1",
"SECOND": "2",
"THIRD": "3",
}
testRoute.Headers = models.Headers{
"First": []string{"test"},
"Second": []string{"test", "test"},
"Third": []string{"test", "test2"},
}
testRoute.Timeout = 2
h.GivenRouteInDb(testRoute)
// Update a config var, remove another. Add one Header, remove another.
updated, err := ds.UpdateRoute(ctx, &models.Route{
AppID: testRoute.AppID,
Path: testRoute.Path,
Config: map[string]string{
"FIRST": "first",
"SECOND": "",
"THIRD": "3",
},
Headers: models.Headers{
"First": []string{"test2"},
"Second": nil,
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := &models.Route{
// unchanged
AppID: testRoute.AppID,
Path: testRoute.Path,
Image: "fnproject/fn-test-utils",
Type: "sync",
Format: "http",
Timeout: 2,
Memory: testRoute.Memory,
CPUs: testRoute.CPUs,
IdleTimeout: testRoute.IdleTimeout,
// updated
Config: map[string]string{
"FIRST": "first",
"THIRD": "3",
},
Headers: models.Headers{
"First": []string{"test2"},
"Third": []string{"test", "test2"},
},
}
if !updated.Equals(expected) {
t.Fatalf("expected updated:\n`%#v`\nbut got:\n`%#v`", expected, updated)
}
})
t.Run("simple pagination", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testRoute := h.GivenRouteInDb(rp.ValidRoute(testApp.ID))
// Testing list fns
routes, err := ds.GetRoutesByApp(ctx, testApp.ID, &models.RouteFilter{PerPage: 1})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if len(routes) == 0 {
t.Fatal("expected result count to be greater than 0")
}
if routes[0] == nil {
t.Fatalf("expected non-nil route")
} else if routes[0].Path != testRoute.Path {
t.Fatalf("expected `app.Name` to be `%s` but it was `%s`", testRoute.Path, routes[0].Path)
}
routes, err = ds.GetRoutesByApp(ctx, testApp.ID, &models.RouteFilter{Image: testRoute.Image, PerPage: 1})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if len(routes) == 0 {
t.Fatal("expected result count to be greater than 0")
}
if routes[0] == nil {
t.Fatalf("expected non-nil route")
} else if routes[0].Path != testRoute.Path {
t.Fatalf("expected `route.Path` to be `%s` but it was `%s`", testRoute.Path, routes[0].Path)
}
})
t.Run("pagination on empty app is invalid", func(t *testing.T) {
_, err := ds.GetRoutesByApp(ctx, "", &models.RouteFilter{PerPage: 1})
if err != models.ErrRoutesMissingAppID {
t.Fatalf("Expecting app ID error, got %s", err)
}
})
t.Run("pagination on non-existant app returns no routes", func(t *testing.T) {
routes, err := ds.GetRoutesByApp(ctx, id.New().String(), &models.RouteFilter{PerPage: 1})
if err != nil {
t.Fatalf("error: %s", err)
}
if len(routes) != 0 {
t.Fatalf("expected result count to be 0 but got %d", len(routes))
}
})
t.Run("pagination on routes return routes in order ", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
r1 := h.GivenRouteInDb(rp.ValidRoute(testApp.ID))
r2 := h.GivenRouteInDb(rp.ValidRoute(testApp.ID))
r3 := h.GivenRouteInDb(rp.ValidRoute(testApp.ID))
gendRoutes := []*models.Route{r1, r2, r3}
sort.Sort(RouteByPath(gendRoutes))
routes, err := ds.GetRoutesByApp(ctx, testApp.ID, &models.RouteFilter{PerPage: 1})
if err != nil {
t.Fatalf("error: %s", err)
}
if len(routes) != 1 {
t.Fatalf("expected result count to be 1 but got %d", len(routes))
} else if routes[0].Path != gendRoutes[0].Path {
t.Fatalf("expected `route.Path` to be `%s` but it was `%s`", gendRoutes[0].Path, routes[0].Path)
}
routes, err = ds.GetRoutesByApp(ctx, testApp.ID, &models.RouteFilter{PerPage: 2, Cursor: routes[0].Path})
if err != nil {
t.Fatalf("error: %s", err)
}
if len(routes) != 2 {
t.Fatalf("expected result count to be 2 but got %d", len(routes))
} else if routes[0].Path != gendRoutes[1].Path {
t.Fatalf("expected `route.Path` to be `%s` but it was `%s`", gendRoutes[1].Path, routes[0].Path)
} else if routes[1].Path != gendRoutes[2].Path {
t.Fatalf("expected `route.Path` to be `%s` but it was `%s`", gendRoutes[2].Path, routes[1].Path)
}
r4 := rp.ValidRoute(testApp.ID)
r4.Path = "/abcdefg" // < /test lexicographically, but not in length
h.GivenRouteInDb(r4)
routes, err = ds.GetRoutesByApp(ctx, testApp.ID, &models.RouteFilter{PerPage: 100})
if err != nil {
t.Fatalf("error: %s", err)
}
if len(routes) != 4 {
t.Fatalf("expected result count to be 4 but got %d", len(routes))
} else if routes[0].Path != r4.Path {
t.Fatalf("expected `route.Path` to be `%s` but it was `%s`", r4.Path, routes[0].Path)
}
})
t.Run("remove route with empty app ID", func(t *testing.T) {
// Testing route delete
err := ds.RemoveRoute(ctx, "", "")
if err != models.ErrRoutesMissingAppID {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrRoutesMissingAppID, err)
}
})
t.Run("remove route with empty app path", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
err := ds.RemoveRoute(ctx, testApp.ID, "")
if err != models.ErrRoutesMissingPath {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrRoutesMissingPath, err)
}
})
t.Run("remove valid route removes route ", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testRoute := h.GivenRouteInDb(rp.ValidRoute(testApp.ID))
err := ds.RemoveRoute(ctx, testApp.ID, testRoute.Path)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
route, err := ds.GetRoute(ctx, testApp.ID, testRoute.Path)
if err != nil && err != models.ErrRoutesNotFound {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrRoutesNotFound, err)
}
if route != nil {
t.Fatalf("failed to remove the route: %v", route)
}
_, err = ds.UpdateRoute(ctx, &models.Route{
AppID: testApp.ID,
Path: testRoute.Path,
Image: "test",
})
if err != models.ErrRoutesNotFound {
t.Fatalf("expected error to be `%v`, but it was `%v`", models.ErrRoutesNotFound, err)
}
})
})
}
func RunFnsTest(t *testing.T, dsf DataStoreFunc, rp ResourceProvider) {
ds := dsf(t)
ctx := rp.DefaultCtx()
t.Run("Fns", func(t *testing.T) {
// Testing insert fn
t.Run("empty function", func(t *testing.T) {
_, err := ds.InsertFn(ctx, nil)
if err != models.ErrDatastoreEmptyFn {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyFn, err)
}
})
t.Run("invalid app ID", func(t *testing.T) {
testFn := rp.ValidFn("notreal")
_, err := ds.InsertFn(ctx, testFn)
if err != models.ErrAppsNotFound {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrAppsNotFound, err)
}
})
t.Run("non-empty function ID", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := rp.ValidFn(testApp.ID)
testFn.ID = "abc"
_, err := ds.InsertFn(ctx, testFn)
if err != models.ErrFnsIDProvided {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrFnsIDProvided, err)
}
})
t.Run("insert valid func", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := rp.ValidFn(testApp.ID)
testFn, err := ds.InsertFn(ctx, testFn)
if err != nil {
t.Fatalf("error when storing perfectly good fn: %s", err)
}
})
// Testing get
t.Run("Get with empty function ID", func(t *testing.T) {
_, err := ds.GetFnByID(ctx, "")
if err != models.ErrDatastoreEmptyFnID {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyFnID, err)
}
})
t.Run("Get with valid function", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
fn, err := ds.GetFnByID(ctx, testFn.ID)
if err != nil {
t.Fatalf("unexpected error %v : %s", err, testFn.ID)
}
if !testFn.EqualsWithAnnotationSubset(fn) {
t.Fatalf("expected to get the right func:\n%v\nbut got:\n%v", testFn, fn)
}
})
// Testing update
t.Run("Update function add values", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
// Update some fields, and add 3 configs
updated, err := ds.UpdateFn(ctx, &models.Fn{
ID: testFn.ID,
Name: testFn.Name,
AppID: testFn.AppID,
Config: map[string]string{
"FIRST": "1",
"SECOND": "2",
"THIRD": "3",
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := &models.Fn{
// unchanged
ID: testFn.ID,
Name: testFn.Name,
AppID: testApp.ID,
Image: "fnproject/fn-test-utils",
Format: "http",
ResourceConfig: models.ResourceConfig{
Timeout: testFn.Timeout,
IdleTimeout: testFn.IdleTimeout,
Memory: testFn.Memory,
},
// updated
Config: map[string]string{
"FIRST": "1",
"SECOND": "2",
"THIRD": "3",
},
}
if !expected.EqualsWithAnnotationSubset(updated) {
t.Fatalf("expected updated `%#v` but got `%#v`", expected, updated)
}
})
t.Run("Update function modify and remove values", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
fn := rp.ValidFn(testApp.ID)
fn.Config = map[string]string{
"FIRST": "1",
"SECOND": "2",
"THIRD": "3",
}
testFn := h.GivenFnInDb(fn)
// Update a config var, remove another. Add one Header, remove another.
updated, err := ds.UpdateFn(ctx, &models.Fn{
ID: testFn.ID,
Name: testFn.Name,
AppID: testFn.AppID,
Config: map[string]string{
"FIRST": "first",
"SECOND": "",
"THIRD": "3",
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := &models.Fn{
// unchanged
ID: testFn.ID,
Name: testFn.Name,
AppID: testApp.ID,
Image: "fnproject/fn-test-utils",
Format: "http",
ResourceConfig: models.ResourceConfig{
Timeout: testFn.Timeout,
IdleTimeout: testFn.IdleTimeout,
Memory: testFn.Memory,
},
// updated
Config: map[string]string{
"FIRST": "first",
"THIRD": "3",
},
}
if !expected.EqualsWithAnnotationSubset(updated) {
t.Fatalf("expected updated:\n`%v`\nbut got:\n`%v`", expected, updated)
}
})
t.Run("basic pagination no functions", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
// Testing list fns
fns, err := ds.GetFns(ctx, &models.FnFilter{AppID: testApp.ID, PerPage: 1})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if len(fns.Items) != 0 {
t.Fatal("expected result count to be 0")
}
if fns.Items == nil {
t.Fatal("response items must not be nil")
}
})
t.Run("basic pagination with funcs", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
f1 := h.GivenFnInDb(rp.ValidFn(testApp.ID))
f2 := h.GivenFnInDb(rp.ValidFn(testApp.ID))
f3 := h.GivenFnInDb(rp.ValidFn(testApp.ID))
gendFns := []*models.Fn{f1, f2, f3}
sort.Sort(FnByName(gendFns))
// Testing list fns
fns, err := ds.GetFns(ctx, &models.FnFilter{AppID: testApp.ID})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if len(fns.Items) != 3 {
t.Fatalf("expected result count to be 3, but was %d", len(fns.Items))
}
fns, err = ds.GetFns(ctx, &models.FnFilter{AppID: testApp.ID, PerPage: 1})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if len(fns.Items) != 1 {
t.Fatalf("expected result count to be 1, but was %d", len(fns.Items))
}
if !gendFns[0].EqualsWithAnnotationSubset(fns.Items[0]) {
t.Fatalf("Expecting function to be %#v but was %#v", gendFns[0], fns.Items[0])
}
fns, err = ds.GetFns(ctx, &models.FnFilter{AppID: testApp.ID, PerPage: 2, Cursor: fns.NextCursor})
if err != nil {
t.Fatalf("error: %s", err)
}
if len(fns.Items) != 2 {
t.Fatalf("expected result count to be 2 but got %d", len(fns.Items))
} else if !gendFns[1].EqualsWithAnnotationSubset(fns.Items[0]) {
t.Fatalf("expected `func.Name` to be `%#v` but it was `%#v`", gendFns[1].Name, fns.Items[0].Name)
} else if !gendFns[2].EqualsWithAnnotationSubset(fns.Items[1]) {
t.Fatalf("expected `func.Name` to be `%#v` but it was `%#v`", gendFns[2], fns.Items[1])
}
})
t.Run("delete with empty fn name", func(t *testing.T) {
// Testing func delete
err := ds.RemoveFn(ctx, "")
if err != models.ErrDatastoreEmptyFnID {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyFnID, err)
}
})
t.Run("delete valid fn", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
err := ds.RemoveFn(ctx, testFn.ID)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
fn, err := ds.GetFnByID(ctx, testFn.ID)
if err != nil && err != models.ErrFnsNotFound {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrFnsNotFound, err)
}
if fn != nil {
t.Fatalf("failed to remove the func: %v", fn)
}
})
})
}
func RunTriggersTest(t *testing.T, dsf DataStoreFunc, rp ResourceProvider) {
t.Run("triggers", func(t *testing.T) {
ds := dsf(t)
ctx := rp.DefaultCtx()
// Testing insert trigger
t.Run("insert invalid app ID", func(t *testing.T) {
newTestTrigger := rp.ValidTrigger("notreal", "fnId")
_, err := ds.InsertTrigger(ctx, newTestTrigger)
if err != models.ErrAppsNotFound {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrAppsNotFound, err)
}
})
t.Run("invalid func ID", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
_, err := ds.InsertTrigger(ctx, rp.ValidTrigger(testApp.ID, "notReal"))
if err != models.ErrFnsNotFound {
t.Fatalf("expected error `%v`, but it was `%v`", models.ErrFnsNotFound, err)
}
})
t.Run("duplicate name", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
newTrigger := rp.ValidTrigger(testApp.ID, testFn.ID)
insertedTrigger, err := ds.InsertTrigger(ctx, newTrigger)
if err != nil {
t.Fatalf("error when storing new trigger: %s", err)
}
newTrigger.ID = insertedTrigger.ID
if !newTrigger.EqualsWithAnnotationSubset(insertedTrigger) {
t.Errorf("Expecting returned trigger %#v to equal %#v", insertedTrigger, newTrigger)
}
repeatTrigger := rp.ValidTrigger(testApp.ID, testFn.ID)
repeatTrigger.Name = newTrigger.Name
_, err = ds.InsertTrigger(ctx, repeatTrigger)
if err != models.ErrTriggerExists {
t.Errorf("Expected ErrTriggerExists, not %s", err)
}
})
t.Run("valid trigger", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
newTrigger := rp.ValidTrigger(testApp.ID, testFn.ID)
insertedTrigger, err := ds.InsertTrigger(ctx, newTrigger)
if err != nil {
t.Fatalf("error when storing new trigger: %s", err)
}
if insertedTrigger.ID == "" {
t.Fatalf("No ID ")
}
newTrigger.ID = insertedTrigger.ID
if !newTrigger.EqualsWithAnnotationSubset(insertedTrigger) {
t.Errorf("Expecting returned trigger %#v to equal %#v", insertedTrigger, newTrigger)
}
})
t.Run("get trigger invalid ID", func(t *testing.T) {
_, err := ds.GetTriggerByID(ctx, "notreal")
if err != models.ErrTriggerNotFound {
t.Fatalf("was expecting models.ErrTriggerNotFound : %s", err)
}
})
t.Run("get existing trigger", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
newTrigger := rp.ValidTrigger(testApp.ID, testFn.ID)
insertedTrigger := h.GivenTriggerInDb(newTrigger)
gotTrigger, err := ds.GetTriggerByID(ctx, insertedTrigger.ID)
if err != nil {
t.Fatalf("expecting no error, got: %s", err)
}
newTrigger.ID = insertedTrigger.ID
if !newTrigger.EqualsWithAnnotationSubset(gotTrigger) {
t.Errorf("Expecting returned trigger %#v to equal %#v", gotTrigger, newTrigger)
}
})
t.Run("missing app Id", func(t *testing.T) {
emptyFilter := &models.TriggerFilter{}
_, err := ds.GetTriggers(ctx, emptyFilter)
if err != models.ErrTriggerMissingAppID {
t.Fatalf("expected models.ErrTriggerMissingAppID, but got %s", err)
}
})
t.Run("non-existant app", func(t *testing.T) {
nonMatchingFilter := &models.TriggerFilter{AppID: "notexist"}
triggers, err := ds.GetTriggers(ctx, nonMatchingFilter)
if len(triggers.Items) != 0 && err == nil {
t.Fatalf("expected empty trigger list and no error, but got list [%v] and err %s", triggers.Items, err)
}
})
t.Run("app id not same as fn id ", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp1 := h.GivenAppInDb(rp.ValidApp())
testApp2 := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp1.ID))
tr := rp.ValidTrigger(testApp2.ID, testFn.ID)
_, err := ds.InsertTrigger(ctx, tr)
if err != models.ErrTriggerFnIDNotSameApp {
t.Errorf("expected error when Fn ID did not match Trigger App ID, got %s", err)
}
})
t.Run("page triggers", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
var storedTriggers []*models.Trigger
for i := 0; i < 10; i++ {
trigger := rp.ValidTrigger(testApp.ID, testFn.ID)
trigger.Source = fmt.Sprintf("src_%v", i)
storedTriggers = append(storedTriggers, h.GivenTriggerInDb(trigger))
}
sort.Sort(TriggerByName(storedTriggers))
appIDFilter := &models.TriggerFilter{AppID: testApp.ID}
triggers, err := ds.GetTriggers(ctx, appIDFilter)
if err != nil {
t.Fatalf("Test GetTriggers(page triggers), not expecting err %s", err)
}
if len(triggers.Items) != 10 {
t.Fatalf("Test GetTriggers(page triggers), expecting 10 results, got %d", len(triggers.Items))
}
for i := 1; i < 10; i++ {
if triggers.Items[i-1].Name > triggers.Items[i].Name {
t.Fatalf("Test GetTriggers(page triggers), names out of order, %s, %s", triggers.Items[i-1], triggers.Items[i])
}
}
fiveFilter := &models.TriggerFilter{AppID: testApp.ID, PerPage: 5}
triggers, err = ds.GetTriggers(ctx, fiveFilter)
if err != nil {
t.Fatalf("Test GetTriggers(page triggers), not expecting err %s", err)
}
if len(triggers.Items) != 5 {
t.Fatalf("Test GetTriggers(page triggers), expecting 5 results, got %d", len(triggers.Items))
}
for i := 0; i < 5; i++ {
if !triggers.Items[i].EqualsWithAnnotationSubset(storedTriggers[i]) {
t.Fatalf("Test GetTriggers(first five page triggers), expect equal, %s, %s", triggers.Items[i], storedTriggers[i])
}
}
if triggers.NextCursor == "" {
t.Fatalf("Test GetTriggers(first five page triggers), expected Cursor but got nothing")
}
secondFiveFilter := &models.TriggerFilter{AppID: testApp.ID, PerPage: 5, Cursor: triggers.NextCursor}
triggers, err = ds.GetTriggers(ctx, secondFiveFilter)
if err != nil {
t.Fatalf("Test GetTriggers(second five page triggers), not expecting err %s", err)
}
if len(triggers.Items) != 5 {
t.Fatalf("Test GetTriggers(second five page triggers), expecting 5 results, got %d", len(triggers.Items))
}
for i := 0; i < 5; i++ {
if !triggers.Items[i].EqualsWithAnnotationSubset(storedTriggers[i+5]) {
t.Fatalf("Test GetTriggers(second five page triggers), expect equal, %s, %s", triggers.Items[i], storedTriggers[i+5])
}
}
zeroFilter := &models.TriggerFilter{AppID: testApp.ID, PerPage: 0}
triggers, err = ds.GetTriggers(ctx, zeroFilter)
if err != nil {
t.Fatalf("Test GetTriggers(zero page triggers), not expecting err %s", err)
}
if len(triggers.Items) != 10 {
t.Fatalf("Test GetTriggers(zero page triggers), expecting 10 results, got %d", len(triggers.Items))
}
if triggers.NextCursor != "" {
t.Fatalf("Test GetTriggers(zero page triggers), expected no NextCursor, got %s", triggers.NextCursor)
}
negativeFilter := &models.TriggerFilter{AppID: testApp.ID, PerPage: -10}
triggers, err = ds.GetTriggers(ctx, negativeFilter)
if err != nil {
t.Fatalf("Test GetTriggers(negative page triggers), not expecting err %s", err)
}
if len(triggers.Items) != 10 {
t.Fatalf("Test GetTriggers(negative page triggers), expecting 10 results, got %d", len(triggers.Items))
}
if triggers.NextCursor != "" {
t.Fatalf("Test GetTriggers(negative page triggers), expected no NextCursor, got %s", triggers.NextCursor)
}
emptyListFilter := &models.TriggerFilter{AppID: "notexist"}
triggers, err = ds.GetTriggers(ctx, emptyListFilter)
if err != nil {
t.Fatalf("Test GetTriggers(notexist page triggers), not expecting err %s", err)
}
if len(triggers.Items) != 0 {
t.Fatalf("Test GetTriggers(notexist page triggers), expecting 0 results, got %d", len(triggers.Items))
}
if triggers.Items == nil {
t.Fatalf("Test GetTriggers(notexist page triggers), response items must not be nil")
}
})
t.Run("filter triggers", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
testFn2 := h.GivenFnInDb(rp.ValidFn(testApp.ID))
var storedTriggers []*models.Trigger
for i := 0; i < 10; i++ {
trigger := rp.ValidTrigger(testApp.ID, testFn.ID)
trigger.Source = fmt.Sprintf("src_%v", i)
storedTriggers = append(storedTriggers, h.GivenTriggerInDb(trigger))
}
trigger := rp.ValidTrigger(testApp.ID, testFn2.ID)
trigger.Source = fmt.Sprintf("src_%v", 11)
trigger = h.GivenTriggerInDb(trigger)
storedTriggers = append(storedTriggers, trigger)
sort.Sort(TriggerByName(storedTriggers))
appIDFilter := &models.TriggerFilter{AppID: testApp.ID}
triggers, err := ds.GetTriggers(ctx, appIDFilter)
if err != nil {
t.Fatalf("Test GetTriggers(get all triggers for app), not expecting err %s", err)
}
if len(triggers.Items) != 11 {
t.Fatalf("Test GetTriggers(get all triggers for app), expecting 10 results, got %d", len(triggers.Items))
}
for i := 0; i < 11; i++ {
if !storedTriggers[i].EqualsWithAnnotationSubset(triggers.Items[i]) {
t.Fatalf("Test GetTriggers(get all triggers for app), expecting ordered by names, but aren't: %+v, %+v", storedTriggers[i], triggers.Items[i])
}
}
NameFilter := &models.TriggerFilter{AppID: testApp.ID, Name: storedTriggers[0].Name}
triggers, err = ds.GetTriggers(ctx, NameFilter)
if err != nil {
t.Fatalf("Test GetTriggers(filter by name), not expecting err %s", err)
}
if len(triggers.Items) != 1 {
t.Fatalf("Test GetTriggers(filter by name), expecting 1 results, got %d", len(triggers.Items))
}
if !storedTriggers[0].EqualsWithAnnotationSubset(triggers.Items[0]) {
t.Fatalf("expect single result to equal first stored result : %#v != %#v", triggers.Items[4], storedTriggers[4])
}
// components are AND'd
findNothingFilter := &models.TriggerFilter{AppID: testApp.ID, FnID: testFn.ID}
triggers, err = ds.GetTriggers(ctx, findNothingFilter)
if err != nil {
t.Fatalf("Test GetTriggers(AND filtering), not expecting err %s", err)
}
if len(triggers.Items) != 10 {
t.Fatalf("Test GetTriggers(AND filtering), expecting 10 results, got %d", len(triggers.Items))
}
})
t.Run("update triggers", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
testTrigger := h.GivenTriggerInDb(rp.ValidTrigger(testApp.ID, testFn.ID))
testTrigger.Name = "newName"
testTrigger.Source = "newSource"
time.Sleep(10 * time.Millisecond)
gotTrigger, err := ds.UpdateTrigger(ctx, testTrigger)
if err != nil {
t.Fatalf("error when updating trigger: %s", err)
}
if !testTrigger.EqualsWithAnnotationSubset(gotTrigger) {
t.Fatalf("expecting returned triggers equal, got : %#v : %#v", testTrigger, gotTrigger)
}
gotTrigger, err = ds.GetTriggerByID(ctx, testTrigger.ID)
if err != nil {
t.Fatalf("wasn't expecting an error : %s", err)
}
if !testTrigger.EqualsWithAnnotationSubset(gotTrigger) {
t.Fatalf("expecting fetch trigger to be updated got : %v : %v", testTrigger, gotTrigger)
}
if testTrigger.CreatedAt.String() != gotTrigger.CreatedAt.String() {
t.Fatalf("create timestamps should match : %v : %v", testTrigger.CreatedAt, gotTrigger.CreatedAt)
}
if testTrigger.UpdatedAt.String() == gotTrigger.UpdatedAt.String() {
t.Fatalf("update timestamps shouldn't match : %v : %v", testTrigger, gotTrigger)
}
})
t.Run("remove non-existant", func(t *testing.T) {
err := ds.RemoveTrigger(ctx, "nonexistant")
if err != models.ErrTriggerNotFound {
t.Fatalf("Expecting trigger not found , got %v ", err)
}
})
t.Run("Remove existing", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
testTrigger := h.GivenTriggerInDb(rp.ValidTrigger(testApp.ID, testFn.ID))
err := ds.RemoveTrigger(ctx, testTrigger.ID)
if err != nil {
t.Fatalf("expecting no error, got %s", err)
}
_, err = ds.GetTriggerByID(ctx, testTrigger.ID)
if err != models.ErrTriggerNotFound {
t.Fatalf("was expecting ErrTriggerNotFound : %s", err)
}
})
t.Run("Remove function should remove triggers", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
testTrigger := h.GivenTriggerInDb(rp.ValidTrigger(testApp.ID, testFn.ID))
err := ds.RemoveFn(ctx, testFn.ID)
if err != nil {
t.Fatalf("expecting no error, got %s", err)
}
tr, err := ds.GetTriggerByID(ctx, testTrigger.ID)
if err != models.ErrTriggerNotFound {
t.Fatalf("was expecting ErrTriggerNotFound got %s %#v", err, tr)
}
})
t.Run("Remove app should remove triggers", func(t *testing.T) {
h := NewHarness(t, ctx, ds)
defer h.Cleanup()
testApp := h.GivenAppInDb(rp.ValidApp())
testFn := h.GivenFnInDb(rp.ValidFn(testApp.ID))
testTrigger := h.GivenTriggerInDb(rp.ValidTrigger(testApp.ID, testFn.ID))
err := ds.RemoveApp(ctx, testFn.AppID)
if err != nil {
t.Fatalf("expecting no error, got %s", err)
}
tr, err := ds.GetTriggerByID(ctx, testTrigger.ID)
if err != models.ErrTriggerNotFound {
t.Fatalf("was expecting ErrTriggerNotFound got %s %#v", err, tr)
}
})
})
}
func RunAllTests(t *testing.T, dsf DataStoreFunc, rp ResourceProvider) {
buf := setLogBuffer()
defer func() {
if t.Failed() {
t.Log(buf.String())
}
}()
RunAppsTest(t, dsf, rp)
RunRoutesTest(t, dsf, rp)
RunFnsTest(t, dsf, rp)
RunTriggersTest(t, dsf, rp)
}