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"
	"testing"
	"time"

	"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
	// 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 {
	rand *rand.Rand
}

// DataStoreFunc provides an instance of a data store
type DataStoreFunc func(*testing.T) models.Datastore

//NewBasicResourceProvider creates a dumb resource provider that generates resources that have valid, random names (and other unique attributes)
func NewBasicResourceProvider() ResourceProvider {
	return &BasicResourceProvider{
		rand: rand.New(rand.NewSource(time.Now().UnixNano())),
	}
}

func (brp *BasicResourceProvider) NextID() uint32 {
	return brp.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: fmt.Sprintf("/source_%09d", brp.NextID()),
	}

	return trigger
}

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) 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 }

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 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 err != nil {
				t.Fatalf("expecting no error, got: %s", err)
			}
			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("duplicate trigger source of same type on same app", func(t *testing.T) {
			h := NewHarness(t, ctx, ds)
			defer h.Cleanup()
			app := h.GivenAppInDb(rp.ValidApp())
			fn := h.GivenFnInDb(rp.ValidFn(app.ID))
			origT := h.GivenTriggerInDb(rp.ValidTrigger(app.ID, fn.ID))

			newT := rp.ValidTrigger(app.ID, fn.ID)

			newT.Source = origT.Source

			_, err := ds.InsertTrigger(ctx, newT)

			if err != models.ErrTriggerSourceExists {
				t.Errorf("Expecting to fail with duplicate source on same app, got %s", err)
			}
			//todo ensure this doesn't apply when type is not equal
		})
		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 RunTriggerBySourceTests(t *testing.T, dsf DataStoreFunc, rp ResourceProvider) {

	t.Run("http_trigger_access", func(t *testing.T) {
		ds := dsf(t)
		ctx := rp.DefaultCtx()
		t.Run("get_non_existant_trigger", func(t *testing.T) {
			_, err := ds.GetTriggerBySource(ctx, "none", "http", "/source")
			if err != models.ErrTriggerNotFound {
				t.Fatalf("Expecting trigger not found, got %s", err)
			}
		})

		t.Run("get_trigger_specific_http_route", 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))
			trigger, err := ds.GetTriggerBySource(ctx, testApp.ID, testTrigger.Type, testTrigger.Source)

			if err != nil {
				t.Fatalf("Expecting trigger, got error  %s", err)
			}

			if !trigger.Equals(testTrigger) {
				t.Errorf("Expecting trigger %#v got %#v", testTrigger, trigger)
			}
		})

	})

}

func RunAllTests(t *testing.T, dsf DataStoreFunc, rp ResourceProvider) {
	buf := setLogBuffer()
	defer func() {
		if t.Failed() {
			t.Log(buf.String())
		}
	}()

	RunAppsTest(t, dsf, rp)
	RunFnsTest(t, dsf, rp)
	RunTriggersTest(t, dsf, rp)
	RunTriggerBySourceTests(t, dsf, rp)

}
