Datastore refactor and added postgres tests (#259)

* fix apps & routes creation/update

* refactor datastore and added postgres tests

* added test-datastore and fixed circleci test
This commit is contained in:
Pedro Nasser
2016-11-14 15:03:10 -02:00
committed by GitHub
parent ff8c5538dd
commit 7aa1981fba
19 changed files with 776 additions and 110 deletions

View File

@@ -14,15 +14,18 @@ build-docker:
docker build -t iron/functions:latest .
test:
go test -v $(shell glide nv | grep -v examples | grep -v tool | grep -v fnctl)
go test -v $(shell go list ./... | grep -v vendor | grep -v examples | grep -v tool | grep -v fnctl)
cd fnctl && $(MAKE) test
test-datastore:
cd api/datastore && go test -v
test-docker:
docker run -ti --privileged --rm -e LOG_LEVEL=debug \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(DIR):/go/src/github.com/iron-io/functions \
-w /go/src/github.com/iron-io/functions iron/go:dev go test \
-v $(shell glide nv | grep -v examples | grep -v tool | grep -v fnctl)
-v $(shell go list ./... | grep -v vendor | grep -v examples | grep -v tool | grep -v fnctl | grep -v datastore)
run:
./functions

View File

@@ -72,18 +72,31 @@ func New(url *url.URL) (models.Datastore, error) {
return ds, nil
}
func (ds *BoltDatastore) StoreApp(app *models.App) (*models.App, error) {
func (ds *BoltDatastore) InsertApp(app *models.App) (*models.App, error) {
if app == nil {
return nil, models.ErrDatastoreEmptyApp
}
if app.Name == "" {
return nil, models.ErrDatastoreEmptyAppName
}
appname := []byte(app.Name)
err := ds.db.Update(func(tx *bolt.Tx) error {
bIm := tx.Bucket(ds.appsBucket)
v := bIm.Get(appname)
if v != nil {
return models.ErrAppsAlreadyExists
}
buf, err := json.Marshal(app)
if err != nil {
return err
}
err = bIm.Put([]byte(app.Name), buf)
err = bIm.Put(appname, buf)
if err != nil {
return err
}
@@ -94,6 +107,62 @@ func (ds *BoltDatastore) StoreApp(app *models.App) (*models.App, error) {
}
return nil
})
return app, err
}
func (ds *BoltDatastore) UpdateApp(newapp *models.App) (*models.App, error) {
if newapp == nil {
return nil, models.ErrDatastoreEmptyApp
}
if newapp.Name == "" {
return nil, models.ErrDatastoreEmptyAppName
}
var app *models.App
appname := []byte(newapp.Name)
err := ds.db.Update(func(tx *bolt.Tx) error {
bIm := tx.Bucket(ds.appsBucket)
v := bIm.Get(appname)
if v == nil {
return models.ErrAppsNotFound
}
err := json.Unmarshal(v, &app)
if err != nil {
return err
}
// Update app fields
if newapp.Config != nil {
if app.Config == nil {
app.Config = map[string]string{}
}
for k, v := range newapp.Config {
app.Config[k] = v
}
}
buf, err := json.Marshal(app)
if err != nil {
return err
}
err = bIm.Put(appname, buf)
if err != nil {
return err
}
bjParent := tx.Bucket(ds.routesBucket)
_, err = bjParent.CreateBucketIfNotExists([]byte(app.Name))
if err != nil {
return err
}
return nil
})
return app, err
}
@@ -181,23 +250,114 @@ func (ds *BoltDatastore) getRouteBucketForApp(tx *bolt.Tx, appName string) (*bol
return b, nil
}
func (ds *BoltDatastore) StoreRoute(route *models.Route) (*models.Route, error) {
func (ds *BoltDatastore) InsertRoute(route *models.Route) (*models.Route, error) {
if route == nil {
return nil, models.ErrDatastoreEmptyApp
}
if route.AppName == "" {
return nil, models.ErrDatastoreEmptyAppName
}
if route.Path == "" {
return nil, models.ErrDatastoreEmptyRoutePath
}
routePath := []byte(route.Path)
err := ds.db.Update(func(tx *bolt.Tx) error {
b, err := ds.getRouteBucketForApp(tx, route.AppName)
if err != nil {
return err
}
v := b.Get(routePath)
if v != nil {
return models.ErrRoutesAlreadyExists
}
buf, err := json.Marshal(route)
if err != nil {
return err
}
err = b.Put([]byte(route.Path), buf)
err = b.Put(routePath, buf)
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
return route, nil
}
func (ds *BoltDatastore) UpdateRoute(newroute *models.Route) (*models.Route, error) {
if newroute == nil {
return nil, models.ErrDatastoreEmptyRoute
}
if newroute.AppName == "" {
return nil, models.ErrDatastoreEmptyAppName
}
if newroute.Path == "" {
return nil, models.ErrDatastoreEmptyRoutePath
}
routePath := []byte(newroute.Path)
var route *models.Route
err := ds.db.Update(func(tx *bolt.Tx) error {
b, err := ds.getRouteBucketForApp(tx, newroute.AppName)
if err != nil {
return err
}
v := b.Get(routePath)
if v == nil {
return models.ErrRoutesNotFound
}
err = json.Unmarshal(v, &route)
if err != nil {
return err
}
// Update route fields
if newroute.Image != "" {
route.Image = newroute.Image
}
if route.Memory != 0 {
route.Memory = newroute.Memory
}
if route.Type != "" {
route.Type = newroute.Type
}
if newroute.Headers != nil {
if route.Config == nil {
route.Config = map[string]string{}
}
for k, v := range newroute.Headers {
route.Headers[k] = v
}
}
if newroute.Config != nil {
if route.Config == nil {
route.Config = map[string]string{}
}
for k, v := range newroute.Config {
route.Config[k] = v
}
}
buf, err := json.Marshal(route)
if err != nil {
return err
}
err = b.Put(routePath, buf)
if err != nil {
return err
}

View File

@@ -21,15 +21,16 @@ func setLogBuffer() *bytes.Buffer {
return &buf
}
const tmpBolt = "/tmp/func_test_bolt.db"
func TestBolt(t *testing.T) {
buf := setLogBuffer()
const tmpBolt = "/tmp/func_test_bolt.db"
os.Remove(tmpBolt)
ds, err := New("bolt://" + tmpBolt)
if err != nil {
t.Fatal("Error when creating datastore: %s", err)
t.Fatalf("Error when creating datastore: %v", err)
}
defer os.Remove(tmpBolt)
testApp := &models.App{
Name: "Test",
@@ -41,24 +42,47 @@ func TestBolt(t *testing.T) {
Image: "iron/hello",
}
// Testing store app
_, err = ds.StoreApp(nil)
if err == nil {
// Testing insert app
_, err = ds.InsertApp(nil)
if err != models.ErrDatastoreEmptyApp {
t.Log(buf.String())
t.Fatalf("Test StoreApp: expected error when using nil app", err)
t.Fatalf("Test InsertApp(nil): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyApp, err)
}
_, err = ds.StoreApp(testApp)
_, err = ds.InsertApp(&models.App{})
if err != models.ErrDatastoreEmptyAppName {
t.Log(buf.String())
t.Fatalf("Test InsertApp(nil): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyAppName, err)
}
_, err = ds.InsertApp(testApp)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test StoreApp: error when Bolt was storing new app: %s", err)
t.Fatalf("Test InsertApp: error when Bolt was storing new app: %s", err)
}
_, err = ds.InsertApp(testApp)
if err != models.ErrAppsAlreadyExists {
t.Log(buf.String())
t.Fatalf("Test InsertApp duplicated: expected error `%v`, but it was `%v`", models.ErrAppsAlreadyExists, err)
}
_, err = ds.UpdateApp(&models.App{
Name: testApp.Name,
Config: map[string]string{
"TEST": "1",
},
})
if err != nil {
t.Log(buf.String())
t.Fatalf("Test UpdateApp: error when Bolt was updating app: %v", err)
}
// Testing get app
_, err = ds.GetApp("")
if err == nil {
if err != models.ErrDatastoreEmptyAppName {
t.Log(buf.String())
t.Fatalf("Test GetApp: expected error when using empty app name", err)
t.Fatalf("Test GetApp: expected error to be %v, but it was %s", models.ErrDatastoreEmptyAppName, err)
}
app, err := ds.GetApp(testApp.Name)
@@ -75,7 +99,7 @@ func TestBolt(t *testing.T) {
apps, err := ds.GetApps(&models.AppFilter{})
if err != nil {
t.Log(buf.String())
t.Fatalf("Test GetApps: error: %s", err)
t.Fatalf("Test GetApps: unexpected error %v", err)
}
if len(apps) == 0 {
t.Fatal("Test GetApps: expected result count to be greater than 0")
@@ -87,9 +111,9 @@ func TestBolt(t *testing.T) {
// Testing app delete
err = ds.RemoveApp("")
if err == nil {
if err != models.ErrDatastoreEmptyAppName {
t.Log(buf.String())
t.Fatalf("Test RemoveApp: expected error when using empty app name", err)
t.Fatalf("Test RemoveApp: expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyAppName, err)
}
err = ds.RemoveApp(testApp.Name)
@@ -107,39 +131,63 @@ func TestBolt(t *testing.T) {
t.Fatalf("Test RemoveApp: failed to remove the app")
}
// Store app again to test routes
ds.StoreApp(testApp)
// Testing store route
_, err = ds.StoreRoute(nil)
if err == nil {
// Test update inexistent app
_, err = ds.UpdateApp(&models.App{
Name: testApp.Name,
Config: map[string]string{
"TEST": "1",
},
})
if err != models.ErrAppsNotFound {
t.Log(buf.String())
t.Fatalf("Test StoreRoute: expected error when using nil route", err)
t.Fatalf("Test UpdateApp inexistent: expected error to be %v, but it was %v", models.ErrAppsNotFound, err)
}
_, err = ds.StoreRoute(testRoute)
// Insert app again to test routes
ds.InsertApp(testApp)
// Testing insert route
_, err = ds.InsertRoute(nil)
if err == models.ErrDatastoreEmptyRoute {
t.Log(buf.String())
t.Fatalf("Test InsertRoute(nil): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyRoute, err)
}
_, err = ds.InsertRoute(testRoute)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test StoreReoute: error when Bolt was storing new route: %s", err)
t.Fatalf("Test InsertRoute: error when Bolt was storing new route: %s", err)
}
_, err = ds.InsertRoute(testRoute)
if err != models.ErrRoutesAlreadyExists {
t.Log(buf.String())
t.Fatalf("Test InsertRoute duplicated: expected error to be `%v`, but it was `%v`", models.ErrRoutesAlreadyExists, err)
}
_, err = ds.UpdateRoute(testRoute)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test UpdateRoute: unexpected error: %v", err)
}
// Testing get
_, err = ds.GetRoute("a", "")
if err == nil {
if err != models.ErrDatastoreEmptyRoutePath {
t.Log(buf.String())
t.Fatalf("Test GetRoute: expected error when using empty route name", err)
t.Fatalf("Test GetRoute(empty route path): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyRoutePath, err)
}
_, err = ds.GetRoute("", "a")
if err == nil {
if err != models.ErrDatastoreEmptyAppName {
t.Log(buf.String())
t.Fatalf("Test GetRoute: expected error when using empty app name", err)
t.Fatalf("Test GetRoute(empty app name): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyAppName, err)
}
route, err := ds.GetRoute(testApp.Name, testRoute.Path)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test GetRoute: error: %s", err)
t.Fatalf("Test GetRoute: unexpected error %v", err)
}
if route.Path != testRoute.Path {
t.Log(buf.String())
@@ -150,7 +198,7 @@ func TestBolt(t *testing.T) {
routes, err := ds.GetRoutesByApp(testApp.Name, &models.RouteFilter{})
if err != nil {
t.Log(buf.String())
t.Fatalf("Test GetRoutes: error: %s", err)
t.Fatalf("Test GetRoutes: unexpected error %v", err)
}
if len(routes) == 0 {
t.Fatal("Test GetRoutes: expected result count to be greater than 0")
@@ -176,21 +224,31 @@ func TestBolt(t *testing.T) {
// Testing app delete
err = ds.RemoveRoute("", "")
if err == nil {
if err != models.ErrDatastoreEmptyAppName {
t.Log(buf.String())
t.Fatalf("Test RemoveRoute: expected error when using empty app name", err)
t.Fatalf("Test RemoveRoute(empty app name): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyAppName, err)
}
err = ds.RemoveRoute("a", "")
if err == nil {
if err != models.ErrDatastoreEmptyRoutePath {
t.Log(buf.String())
t.Fatalf("Test RemoveRoute: expected error when using empty route name", err)
t.Fatalf("Test RemoveRoute(empty route path): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyRoutePath, err)
}
err = ds.RemoveRoute(testRoute.AppName, testRoute.Path)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test RemoveApp: error: %s", err)
t.Fatalf("Test RemoveApp: unexpected error: %v", err)
}
_, err = ds.UpdateRoute(&models.Route{
AppName: testRoute.AppName,
Path: testRoute.Path,
Image: "test",
})
if err != models.ErrRoutesNotFound {
t.Log(buf.String())
t.Fatalf("Test UpdateRoute inexistent: expected error to be `%v`, but it was `%v`", models.ErrRoutesNotFound, err)
}
route, err = ds.GetRoute(testRoute.AppName, testRoute.Path)

View File

@@ -27,7 +27,12 @@ func (m *Mock) GetApps(appFilter *models.AppFilter) ([]*models.App, error) {
return m.FakeApps, nil
}
func (m *Mock) StoreApp(app *models.App) (*models.App, error) {
func (m *Mock) InsertApp(app *models.App) (*models.App, error) {
// TODO: improve this mock method
return m.FakeApp, nil
}
func (m *Mock) UpdateApp(app *models.App) (*models.App, error) {
// TODO: improve this mock method
return m.FakeApp, nil
}
@@ -69,7 +74,12 @@ func (m *Mock) GetRoutesByApp(appName string, routeFilter *models.RouteFilter) (
return routes, nil
}
func (m *Mock) StoreRoute(route *models.Route) (*models.Route, error) {
func (m *Mock) InsertRoute(route *models.Route) (*models.Route, error) {
// TODO: improve this mock method
return m.FakeRoute, nil
}
func (m *Mock) UpdateRoute(route *models.Route) (*models.Route, error) {
// TODO: improve this mock method
return m.FakeRoute, nil
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/iron-io/functions/api/models"
"github.com/lib/pq"
_ "github.com/lib/pq"
)
@@ -75,17 +76,56 @@ func New(url *url.URL) (models.Datastore, error) {
return pg, nil
}
func (ds *PostgresDatastore) StoreApp(app *models.App) (*models.App, error) {
func (ds *PostgresDatastore) InsertApp(app *models.App) (*models.App, error) {
var cbyte []byte
var err error
if app == nil {
return nil, models.ErrDatastoreEmptyApp
}
if app.Name == "" {
return nil, models.ErrDatastoreEmptyAppName
}
if app.Config != nil {
cbyte, err = json.Marshal(app.Config)
if err != nil {
return nil, err
}
}
_, err = ds.db.Exec(`INSERT INTO apps (name, config) VALUES ($1, $2);`,
app.Name,
string(cbyte),
)
if err != nil {
pqErr := err.(*pq.Error)
if pqErr.Code == "23505" {
return nil, models.ErrAppsAlreadyExists
}
return nil, err
}
return app, nil
}
func (ds *PostgresDatastore) UpdateApp(app *models.App) (*models.App, error) {
if app == nil {
return nil, models.ErrAppsNotFound
}
cbyte, err := json.Marshal(app.Config)
if err != nil {
return nil, err
}
_, err = ds.db.Exec(`
INSERT INTO apps (name, config)
VALUES ($1, $2)
ON CONFLICT (name) DO UPDATE SET
config = $2;
res, err := ds.db.Exec(`
UPDATE apps SET
config = $2
WHERE name = $1
RETURNING *;
`,
app.Name,
string(cbyte),
@@ -95,10 +135,23 @@ func (ds *PostgresDatastore) StoreApp(app *models.App) (*models.App, error) {
return nil, err
}
n, err := res.RowsAffected()
if err != nil {
return nil, err
}
if n == 0 {
return nil, models.ErrAppsNotFound
}
return app, nil
}
func (ds *PostgresDatastore) RemoveApp(appName string) error {
if appName == "" {
return models.ErrDatastoreEmptyAppName
}
_, err := ds.db.Exec(`
DELETE FROM apps
WHERE name = $1
@@ -112,6 +165,10 @@ func (ds *PostgresDatastore) RemoveApp(appName string) error {
}
func (ds *PostgresDatastore) GetApp(name string) (*models.App, error) {
if name == "" {
return nil, models.ErrDatastoreEmptyAppName
}
row := ds.db.QueryRow("SELECT name, config FROM apps WHERE name=$1", name)
var resName string
@@ -125,6 +182,9 @@ func (ds *PostgresDatastore) GetApp(name string) (*models.App, error) {
json.Unmarshal([]byte(config), &res.Config)
if err != nil {
if err == sql.ErrNoRows {
return nil, models.ErrAppsNotFound
}
return nil, err
}
@@ -132,10 +192,19 @@ func (ds *PostgresDatastore) GetApp(name string) (*models.App, error) {
}
func scanApp(scanner rowScanner, app *models.App) error {
var configStr string
err := scanner.Scan(
&app.Name,
&configStr,
)
if configStr == "" {
return models.ErrAppsNotFound
}
json.Unmarshal([]byte(configStr), &app.Config)
return err
}
@@ -156,18 +225,25 @@ func (ds *PostgresDatastore) GetApps(filter *models.AppFilter) ([]*models.App, e
err := scanApp(rows, &app)
if err != nil {
return nil, err
if err == sql.ErrNoRows {
return res, nil
}
return res, err
}
res = append(res, &app)
}
if err := rows.Err(); err != nil {
return nil, err
return res, err
}
return res, nil
}
func (ds *PostgresDatastore) StoreRoute(route *models.Route) (*models.Route, error) {
func (ds *PostgresDatastore) InsertRoute(route *models.Route) (*models.Route, error) {
if route == nil {
return nil, models.ErrDatastoreEmptyRoute
}
hbyte, err := json.Marshal(route.Headers)
if err != nil {
return nil, err
@@ -187,14 +263,47 @@ func (ds *PostgresDatastore) StoreRoute(route *models.Route) (*models.Route, err
headers,
config
)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (app_name, path) DO UPDATE SET
path = $2,
VALUES ($1, $2, $3, $4, $5, $6);`,
route.AppName,
route.Path,
route.Image,
route.Memory,
string(hbyte),
string(cbyte),
)
if err != nil {
pqErr := err.(*pq.Error)
if pqErr.Code == "23505" {
return nil, models.ErrRoutesAlreadyExists
}
return nil, err
}
return route, nil
}
func (ds *PostgresDatastore) UpdateRoute(route *models.Route) (*models.Route, error) {
if route == nil {
return nil, models.ErrDatastoreEmptyRoute
}
hbyte, err := json.Marshal(route.Headers)
if err != nil {
return nil, err
}
cbyte, err := json.Marshal(route.Config)
if err != nil {
return nil, err
}
res, err := ds.db.Exec(`
UPDATE routes SET
image = $3,
memory = $4,
headers = $5,
config = $6;
`,
config = $6
WHERE app_name = $1 AND path = $2;`,
route.AppName,
route.Path,
route.Image,
@@ -206,11 +315,29 @@ func (ds *PostgresDatastore) StoreRoute(route *models.Route) (*models.Route, err
if err != nil {
return nil, err
}
n, err := res.RowsAffected()
if err != nil {
return nil, err
}
if n == 0 {
return nil, models.ErrRoutesNotFound
}
return route, nil
}
func (ds *PostgresDatastore) RemoveRoute(appName, routePath string) error {
_, err := ds.db.Exec(`
if appName == "" {
return models.ErrDatastoreEmptyAppName
}
if routePath == "" {
return models.ErrDatastoreEmptyRoutePath
}
res, err := ds.db.Exec(`
DELETE FROM routes
WHERE path = $1 AND app_name = $2
`, routePath, appName)
@@ -218,6 +345,16 @@ func (ds *PostgresDatastore) RemoveRoute(appName, routePath string) error {
if err != nil {
return err
}
n, err := res.RowsAffected()
if err != nil {
return err
}
if n == 0 {
return models.ErrRoutesRemoving
}
return nil
}
@@ -244,10 +381,18 @@ func scanRoute(scanner rowScanner, route *models.Route) error {
return err
}
func getRoute(qr rowQuerier, routePath string) (*models.Route, error) {
func (ds *PostgresDatastore) GetRoute(appName, routePath string) (*models.Route, error) {
if appName == "" {
return nil, models.ErrDatastoreEmptyAppName
}
if routePath == "" {
return nil, models.ErrDatastoreEmptyRoutePath
}
var route models.Route
row := qr.QueryRow(fmt.Sprintf("%s WHERE path=$1", routeSelector), routePath)
row := ds.db.QueryRow(fmt.Sprintf("%s WHERE app_name=$1 AND path=$2", routeSelector), appName, routePath)
err := scanRoute(row, &route)
if err == sql.ErrNoRows {
@@ -258,10 +403,6 @@ func getRoute(qr rowQuerier, routePath string) (*models.Route, error) {
return &route, nil
}
func (ds *PostgresDatastore) GetRoute(appName, routePath string) (*models.Route, error) {
return getRoute(ds.db, routePath)
}
func (ds *PostgresDatastore) GetRoutes(filter *models.RouteFilter) ([]*models.Route, error) {
res := []*models.Route{}
filterQuery := buildFilterQuery(filter)

View File

@@ -0,0 +1,277 @@
package datastore
import (
"database/sql"
"fmt"
"os/exec"
"testing"
"time"
"github.com/iron-io/functions/api/models"
)
const tmpPostgres = "postgres://postgres@127.0.0.1:15432/funcs?sslmode=disable"
func preparePostgresTest() func() {
fmt.Println("initializing postgres for test")
exec.Command("docker", "rm", "-f", "iron-postgres-test").Run()
exec.Command("docker", "run", "--name", "iron-postgres-test", "-p", "15432:5432", "-d", "postgres").Run()
for {
db, err := sql.Open("postgres", "postgres://postgres@127.0.0.1:15432?sslmode=disable")
if err != nil {
time.Sleep(1 * time.Second)
continue
}
db.Exec(`CREATE DATABASE funcs;`)
_, err = db.Exec(`GRANT ALL PRIVILEGES ON DATABASE funcs TO postgres;`)
if err == nil {
break
}
time.Sleep(1 * time.Second)
}
fmt.Println("postgres for test ready")
return func() {
exec.Command("docker", "rm", "-f", "iron-postgres-test").Run()
}
}
func TestPostgres(t *testing.T) {
close := preparePostgresTest()
defer close()
buf := setLogBuffer()
ds, err := New(tmpPostgres)
if err != nil {
t.Fatalf("Error when creating datastore: %v", err)
}
testApp := &models.App{
Name: "Test",
}
testRoute := &models.Route{
AppName: testApp.Name,
Path: "/test",
Image: "iron/hello",
}
// Testing insert app
_, err = ds.InsertApp(nil)
if err != models.ErrDatastoreEmptyApp {
t.Log(buf.String())
t.Fatalf("Test InsertApp(nil): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyApp, err)
}
_, err = ds.InsertApp(&models.App{})
if err != models.ErrDatastoreEmptyAppName {
t.Log(buf.String())
t.Fatalf("Test InsertApp(nil): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyAppName, err)
}
_, err = ds.InsertApp(testApp)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test InsertApp: error when storing new app: %s", err)
}
_, err = ds.InsertApp(testApp)
if err != models.ErrAppsAlreadyExists {
t.Log(buf.String())
t.Fatalf("Test InsertApp duplicated: expected error `%v`, but it was `%v`", models.ErrAppsAlreadyExists, err)
}
_, err = ds.UpdateApp(&models.App{
Name: testApp.Name,
Config: map[string]string{
"TEST": "1",
},
})
if err != nil {
t.Log(buf.String())
t.Fatalf("Test UpdateApp: error when updating app: %v", err)
}
// Testing get app
_, err = ds.GetApp("")
if err != models.ErrDatastoreEmptyAppName {
t.Log(buf.String())
t.Fatalf("Test GetApp: expected error to be %v, but it was %s", models.ErrDatastoreEmptyAppName, err)
}
app, err := ds.GetApp(testApp.Name)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test GetApp: error: %s", err)
}
if app.Name != testApp.Name {
t.Log(buf.String())
t.Fatalf("Test GetApp: expected `app.Name` to be `%s` but it was `%s`", app.Name, testApp.Name)
}
// Testing list apps
apps, err := ds.GetApps(&models.AppFilter{})
if err != nil {
t.Log(buf.String())
t.Fatalf("Test GetApps: unexpected error %v", err)
}
if len(apps) == 0 {
t.Fatal("Test GetApps: expected result count to be greater than 0")
}
if apps[0].Name != testApp.Name {
t.Log(buf.String())
t.Fatalf("Test GetApps: expected `app.Name` to be `%s` but it was `%s`", app.Name, testApp.Name)
}
// Testing app delete
err = ds.RemoveApp("")
if err != models.ErrDatastoreEmptyAppName {
t.Log(buf.String())
t.Fatalf("Test RemoveApp: expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyAppName, err)
}
err = ds.RemoveApp(testApp.Name)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test RemoveApp: error: %s", err)
}
app, err = ds.GetApp(testApp.Name)
if err != models.ErrAppsNotFound {
t.Log(buf.String())
t.Fatalf("Test GetApp(removed): expected error `%v`, but it was `%v`", models.ErrAppsNotFound, err)
}
if app != nil {
t.Log(buf.String())
t.Fatalf("Test RemoveApp: failed to remove the app")
}
// Test update inexistent app
_, err = ds.UpdateApp(&models.App{
Name: testApp.Name,
Config: map[string]string{
"TEST": "1",
},
})
if err != models.ErrAppsNotFound {
t.Log(buf.String())
t.Fatalf("Test UpdateApp(inexistent): expected error `%v`, but it was `%v`", models.ErrAppsNotFound, err)
}
// Insert app again to test routes
ds.InsertApp(testApp)
// Testing insert route
_, err = ds.InsertRoute(nil)
if err != models.ErrDatastoreEmptyRoute {
t.Log(buf.String())
t.Fatalf("Test InsertRoute(nil): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyRoute, err)
}
_, err = ds.InsertRoute(testRoute)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test InsertRoute: error when storing new route: %s", err)
}
_, err = ds.InsertRoute(testRoute)
if err != models.ErrRoutesAlreadyExists {
t.Log(buf.String())
t.Fatalf("Test InsertRoute duplicated: expected error to be `%v`, but it was `%v`", models.ErrRoutesAlreadyExists, err)
}
_, err = ds.UpdateRoute(testRoute)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test UpdateRoute: unexpected error: %v", err)
}
// Testing get
_, err = ds.GetRoute("a", "")
if err != models.ErrDatastoreEmptyRoutePath {
t.Log(buf.String())
t.Fatalf("Test GetRoute(empty route path): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyRoutePath, err)
}
_, err = ds.GetRoute("", "a")
if err != models.ErrDatastoreEmptyAppName {
t.Log(buf.String())
t.Fatalf("Test GetRoute(empty app name): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyAppName, err)
}
route, err := ds.GetRoute(testApp.Name, testRoute.Path)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test GetRoute: unexpected error %v", err)
}
if route.Path != testRoute.Path {
t.Log(buf.String())
t.Fatalf("Test GetRoute: expected `route.Path` to be `%s` but it was `%s`", route.Path, testRoute.Path)
}
// Testing list routes
routes, err := ds.GetRoutesByApp(testApp.Name, &models.RouteFilter{})
if err != nil {
t.Log(buf.String())
t.Fatalf("Test GetRoutes: unexpected error %v", err)
}
if len(routes) == 0 {
t.Fatal("Test GetRoutes: expected result count to be greater than 0")
}
if routes[0].Path != testRoute.Path {
t.Log(buf.String())
t.Fatalf("Test GetRoutes: expected `app.Name` to be `%s` but it was `%s`", testRoute.Path, routes[0].Path)
}
// Testing list routes
routes, err = ds.GetRoutes(&models.RouteFilter{Image: testRoute.Image})
if err != nil {
t.Log(buf.String())
t.Fatalf("Test GetRoutes: error: %s", err)
}
if len(routes) == 0 {
t.Fatal("Test GetRoutes: expected result count to be greater than 0")
}
if routes[0].Path != testRoute.Path {
t.Log(buf.String())
t.Fatalf("Test GetRoutes: expected `app.Name` to be `%s` but it was `%s`", testRoute.Path, routes[0].Path)
}
// Testing app delete
err = ds.RemoveRoute("", "")
if err != models.ErrDatastoreEmptyAppName {
t.Log(buf.String())
t.Fatalf("Test RemoveRoute(empty app name): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyAppName, err)
}
err = ds.RemoveRoute("a", "")
if err != models.ErrDatastoreEmptyRoutePath {
t.Log(buf.String())
t.Fatalf("Test RemoveRoute(empty route path): expected error `%v`, but it was `%v`", models.ErrDatastoreEmptyRoutePath, err)
}
err = ds.RemoveRoute(testRoute.AppName, testRoute.Path)
if err != nil {
t.Log(buf.String())
t.Fatalf("Test RemoveApp: unexpected error: %v", err)
}
_, err = ds.UpdateRoute(&models.Route{
AppName: testRoute.AppName,
Path: testRoute.Path,
Image: "test",
})
if err != models.ErrRoutesNotFound {
t.Log(buf.String())
t.Fatalf("Test UpdateRoute inexistent: expected error to be `%v`, but it was `%v`", models.ErrRoutesNotFound, err)
}
route, err = ds.GetRoute(testRoute.AppName, testRoute.Path)
if err != models.ErrRoutesNotFound {
t.Log(buf.String())
t.Fatalf("Test GetRoute: expected error `%v`, but it was `%v`", models.ErrRoutesNotFound, err)
}
if route != nil {
t.Log(buf.String())
t.Fatalf("Test RemoveApp: failed to remove the route")
}
}

View File

@@ -13,6 +13,7 @@ var (
ErrAppsRemoving = errors.New("Could not remove app from datastore")
ErrAppsGet = errors.New("Could not get app from datastore")
ErrAppsList = errors.New("Could not list apps from datastore")
ErrAppsAlreadyExists = errors.New("App already exists")
ErrAppsNotFound = errors.New("App not found")
ErrAppsNothingToUpdate = errors.New("Nothing to update")
ErrAppsMissingNew = errors.New("Missing new application")

View File

@@ -5,13 +5,15 @@ import "errors"
type Datastore interface {
GetApp(appName string) (*App, error)
GetApps(*AppFilter) ([]*App, error)
StoreApp(*App) (*App, error)
InsertApp(app *App) (*App, error)
UpdateApp(app *App) (*App, error)
RemoveApp(appName string) error
GetRoute(appName, routePath string) (*Route, error)
GetRoutes(*RouteFilter) (routes []*Route, err error)
GetRoutesByApp(string, *RouteFilter) (routes []*Route, err error)
StoreRoute(*Route) (*Route, error)
InsertRoute(route *Route) (*Route, error)
UpdateRoute(route *Route) (*Route, error)
RemoveRoute(appName, routePath string) error
// The following provide a generic key value store for arbitrary data, can be used by extensions to store extra data

View File

@@ -16,6 +16,7 @@ var (
ErrRoutesRemoving = errors.New("Could not remove route from datastore")
ErrRoutesGet = errors.New("Could not get route from datastore")
ErrRoutesList = errors.New("Could not list routes from datastore")
ErrRoutesAlreadyExists = errors.New("Route already exists")
ErrRoutesNotFound = errors.New("Route not found")
ErrRoutesMissingNew = errors.New("Missing new route")
ErrInvalidPayload = errors.New("Invalid payload")

View File

@@ -41,7 +41,7 @@ func handleAppCreate(c *gin.Context) {
return
}
_, err = Api.Datastore.StoreApp(wapp.App)
_, err = Api.Datastore.InsertApp(wapp.App)
if err != nil {
log.WithError(err).Errorln(models.ErrAppsCreate)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsCreate))

View File

@@ -26,28 +26,29 @@ func setLogBuffer() *bytes.Buffer {
func TestAppCreate(t *testing.T) {
buf := setLogBuffer()
New(&datastore.Mock{}, &mqs.Mock{}, testRunner(t))
s := New(&datastore.Mock{}, &mqs.Mock{}, testRunner(t))
router := testRouter(s)
for i, test := range []struct {
mock *datastore.Mock
path string
body string
expectedCode int
expectedError error
}{
// errors
{"/v1/apps", ``, http.StatusBadRequest, models.ErrInvalidJSON},
{"/v1/apps", `{}`, http.StatusBadRequest, models.ErrAppsMissingNew},
{"/v1/apps", `{ "name": "Test" }`, http.StatusBadRequest, models.ErrAppsMissingNew},
{"/v1/apps", `{ "app": { "name": "" } }`, http.StatusInternalServerError, models.ErrAppsValidationMissingName},
{"/v1/apps", `{ "app": { "name": "1234567890123456789012345678901" } }`, http.StatusInternalServerError, models.ErrAppsValidationTooLongName},
{"/v1/apps", `{ "app": { "name": "&&%@!#$#@$" } }`, http.StatusInternalServerError, models.ErrAppsValidationInvalidName},
{"/v1/apps", `{ "app": { "name": "&&%@!#$#@$" } }`, http.StatusInternalServerError, models.ErrAppsValidationInvalidName},
{&datastore.Mock{}, "/v1/apps", ``, http.StatusBadRequest, models.ErrInvalidJSON},
{&datastore.Mock{}, "/v1/apps", `{}`, http.StatusBadRequest, models.ErrAppsMissingNew},
{&datastore.Mock{}, "/v1/apps", `{ "name": "Test" }`, http.StatusBadRequest, models.ErrAppsMissingNew},
{&datastore.Mock{}, "/v1/apps", `{ "app": { "name": "" } }`, http.StatusInternalServerError, models.ErrAppsValidationMissingName},
{&datastore.Mock{}, "/v1/apps", `{ "app": { "name": "1234567890123456789012345678901" } }`, http.StatusInternalServerError, models.ErrAppsValidationTooLongName},
{&datastore.Mock{}, "/v1/apps", `{ "app": { "name": "&&%@!#$#@$" } }`, http.StatusInternalServerError, models.ErrAppsValidationInvalidName},
{&datastore.Mock{}, "/v1/apps", `{ "app": { "name": "&&%@!#$#@$" } }`, http.StatusInternalServerError, models.ErrAppsValidationInvalidName},
// success
{"/v1/apps", `{ "app": { "name": "teste" } }`, http.StatusCreated, nil},
{&datastore.Mock{}, "/v1/apps", `{ "app": { "name": "teste" } }`, http.StatusCreated, nil},
} {
s := New(test.mock, &mqs.Mock{}, testRunner(t))
router := testRouter(s)
body := bytes.NewBuffer([]byte(test.body))
_, rec := routerRequest(t, router, "POST", test.path, body)
@@ -171,21 +172,27 @@ func TestAppGet(t *testing.T) {
func TestAppUpdate(t *testing.T) {
buf := setLogBuffer()
s := New(&datastore.Mock{}, &mqs.Mock{}, testRunner(t))
router := testRouter(s)
for i, test := range []struct {
mock *datastore.Mock
path string
body string
expectedCode int
expectedError error
}{
// errors
{"/v1/apps/myapp", ``, http.StatusBadRequest, models.ErrInvalidJSON},
{&datastore.Mock{}, "/v1/apps/myapp", ``, http.StatusBadRequest, models.ErrInvalidJSON},
// success
{"/v1/apps/myapp", `{ "app": { "config": { "test": "1" } } }`, http.StatusOK, nil},
{&datastore.Mock{
FakeApp: &models.App{
Name: "myapp",
},
}, "/v1/apps/myapp", `{ "app": { "config": { "test": "1" } } }`, http.StatusOK, nil},
} {
s := New(test.mock, &mqs.Mock{}, testRunner(t))
router := testRouter(s)
body := bytes.NewBuffer([]byte(test.body))
_, rec := routerRequest(t, router, "PUT", test.path, body)

View File

@@ -28,10 +28,10 @@ func handleAppUpdate(c *gin.Context) {
return
}
app, err := Api.Datastore.StoreApp(wapp.App)
app, err := Api.Datastore.UpdateApp(wapp.App)
if err != nil {
log.WithError(err).Debug(models.ErrAppsCreate)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsCreate))
log.WithError(err).Debug(models.ErrAppsUpdate)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsUpdate))
return
}

View File

@@ -53,6 +53,7 @@ func handleRouteCreate(c *gin.Context) {
}
if app == nil {
// Create a new application and add the route to that new application
newapp := &models.App{Name: wroute.Route.AppName}
if err := newapp.Validate(); err != nil {
log.Error(err)
@@ -60,7 +61,7 @@ func handleRouteCreate(c *gin.Context) {
return
}
app, err = Api.Datastore.StoreApp(newapp)
app, err = Api.Datastore.InsertApp(newapp)
if err != nil {
log.WithError(err).Error(models.ErrAppsCreate)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsCreate))
@@ -68,7 +69,7 @@ func handleRouteCreate(c *gin.Context) {
}
}
_, err = Api.Datastore.StoreRoute(wroute.Route)
_, err = Api.Datastore.InsertRoute(wroute.Route)
if err != nil {
log.WithError(err).Error(models.ErrRoutesCreate)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrRoutesCreate))

View File

@@ -13,27 +13,29 @@ import (
func TestRouteCreate(t *testing.T) {
buf := setLogBuffer()
s := New(&datastore.Mock{}, &mqs.Mock{}, testRunner(t))
router := testRouter(s)
for i, test := range []struct {
mock *datastore.Mock
path string
body string
expectedCode int
expectedError error
}{
// errors
{"/v1/apps/a/routes", ``, http.StatusBadRequest, models.ErrInvalidJSON},
{"/v1/apps/a/routes", `{ }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
{"/v1/apps/a/routes", `{ "path": "/myroute" }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
{"/v1/apps/a/routes", `{ "route": { } }`, http.StatusInternalServerError, models.ErrRoutesValidationMissingImage},
{"/v1/apps/a/routes", `{ "route": { "image": "iron/hello" } }`, http.StatusInternalServerError, models.ErrRoutesValidationMissingPath},
{"/v1/apps/a/routes", `{ "route": { "image": "iron/hello", "path": "myroute" } }`, http.StatusInternalServerError, models.ErrRoutesValidationInvalidPath},
{"/v1/apps/$/routes", `{ "route": { "image": "iron/hello", "path": "/myroute" } }`, http.StatusInternalServerError, models.ErrAppsValidationInvalidName},
{&datastore.Mock{}, "/v1/apps/a/routes", ``, http.StatusBadRequest, models.ErrInvalidJSON},
{&datastore.Mock{}, "/v1/apps/a/routes", `{ }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
{&datastore.Mock{}, "/v1/apps/a/routes", `{ "path": "/myroute" }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
{&datastore.Mock{}, "/v1/apps/a/routes", `{ "route": { } }`, http.StatusInternalServerError, models.ErrRoutesValidationMissingImage},
{&datastore.Mock{}, "/v1/apps/a/routes", `{ "route": { "image": "iron/hello" } }`, http.StatusInternalServerError, models.ErrRoutesValidationMissingPath},
{&datastore.Mock{}, "/v1/apps/a/routes", `{ "route": { "image": "iron/hello", "path": "myroute" } }`, http.StatusInternalServerError, models.ErrRoutesValidationInvalidPath},
{&datastore.Mock{}, "/v1/apps/$/routes", `{ "route": { "image": "iron/hello", "path": "/myroute" } }`, http.StatusInternalServerError, models.ErrAppsValidationInvalidName},
// success
{"/v1/apps/a/routes", `{ "route": { "name": "myroute", "image": "iron/hello", "path": "/myroute" } }`, http.StatusCreated, nil},
{&datastore.Mock{}, "/v1/apps/a/routes", `{ "route": { "image": "iron/hello", "path": "/myroute" } }`, http.StatusCreated, nil},
} {
s := New(test.mock, &mqs.Mock{}, testRunner(t))
router := testRouter(s)
body := bytes.NewBuffer([]byte(test.body))
_, rec := routerRequest(t, router, "POST", test.path, body)

View File

@@ -37,10 +37,10 @@ func handleRouteUpdate(c *gin.Context) {
return
}
_, err = Api.Datastore.StoreRoute(wroute.Route)
_, err = Api.Datastore.UpdateRoute(wroute.Route)
if err != nil {
log.WithError(err).Debug(models.ErrAppsCreate)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsCreate))
log.WithError(err).Debug(models.ErrRoutesUpdate)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrRoutesUpdate))
return
}

View File

@@ -2,7 +2,6 @@ package server
import (
"bytes"
"fmt"
"net/http"
"strings"
"testing"
@@ -92,7 +91,6 @@ func TestRouteRunnerPost(t *testing.T) {
resp := getErrorResponse(t, rec)
respMsg := resp.Error.Message
expMsg := test.expectedError.Error()
fmt.Println(respMsg == expMsg)
if respMsg != expMsg && !strings.Contains(respMsg, expMsg) {
t.Log(buf.String())
t.Errorf("Test %d: Expected error message to have `%s`",
@@ -165,7 +163,6 @@ func TestMatchRoute(t *testing.T) {
for j, param := range test.expectedParams {
if params[j].Key != param.Key || params[j].Value != param.Value {
t.Log(buf.String())
fmt.Println(params[j])
t.Errorf("Test %d: expected param %d, key = %s, value = %s", i, j, param.Key, param.Value)
}
}

View File

@@ -3,6 +3,7 @@ package server
import (
"context"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"path"
@@ -185,6 +186,8 @@ func (s *Server) bindHandlers() {
engine.NoRoute(handleSpecial)
}
var ErrInternalServerError = errors.New("Something unexpected happened on the server")
func simpleError(err error) *models.Error {
return &models.Error{&models.ErrorBody{Message: err.Error()}}
}

View File

@@ -72,6 +72,7 @@ func getErrorResponse(t *testing.T, rec *httptest.ResponseRecorder) models.Error
}
func prepareBolt(t *testing.T) (models.Datastore, func()) {
os.Remove(tmpBolt)
ds, err := datastore.New("bolt://" + tmpBolt)
if err != nil {
t.Fatal("Error when creating datastore: %s", err)

View File

@@ -31,6 +31,8 @@ test:
override:
- make test-docker:
pwd: $GO_PROJECT
- make test-datastore:
pwd: $GO_PROJECT
deployment:
release: