Skip to content

Commit

Permalink
feat: wrap db instance in handler struct, add integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Dara Hayes committed Feb 22, 2018
1 parent 21cef30 commit a2d1003
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 22 deletions.
10 changes: 6 additions & 4 deletions cmd/metrics-api/metrics-api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@ func main() {

config := config.GetConfig()

db, err := dao.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)
dbHandler := dao.DatabaseHandler{}

err := dbHandler.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)

if err != nil {
panic("failed to connect to sql database : " + err.Error())
}
defer db.Close()
defer dbHandler.DB.Close()

if err := dao.DoInitialSetup(); err != nil {
if err := dbHandler.DoInitialSetup(); err != nil {
panic("failed to perform database setup : " + err.Error())
}

metricsDao := dao.NewMetricsDAO(db)
metricsDao := dao.NewMetricsDAO(dbHandler.DB)
router := web.NewRouter()

//metrics route
Expand Down
5 changes: 2 additions & 3 deletions deployments/docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
postgres:
image: timescale/timescaledb
image: postgres:9.5
ports:
- 5432:5432
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
PGDATA : /var/lib/postgresql/data/pgdata
PGDATABASE: aerogear_mobile_metrics
POSTGRES_DB: aerogear_mobile_metrics
2 changes: 1 addition & 1 deletion pkg/dao/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type MetricsDAO struct {

// Create a metrics record
func (m *MetricsDAO) Create(clientId string, metricsData []byte) error {
_, err := db.Exec("INSERT INTO mobileappmetrics(clientId, data) VALUES($1, $2)", clientId, metricsData)
_, err := m.db.Exec("INSERT INTO mobileappmetrics(clientId, data) VALUES($1, $2)", clientId, metricsData)
return err
}

Expand Down
130 changes: 130 additions & 0 deletions pkg/dao/dao_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// +build integration

package dao

import (
"testing"

"github.com/aerogear/aerogear-metrics-api/pkg/config"
"github.com/stretchr/testify/mock"
)

type MockDB struct {
mock.Mock
}

func TestIshealthy(t *testing.T) {
config := config.GetConfig()
dbHandler := DatabaseHandler{}

err := dbHandler.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)

if err != nil {
t.Errorf("Connect() returned an error: %s", err.Error())
}

dao := NewMetricsDAO(dbHandler.DB)

isHealthy, err := dao.IsHealthy()

if err != nil {
t.Errorf("isHealthy returned an error %s", err.Error())
}

if !isHealthy {
t.Errorf("isHealthy returned false")
}
}

func TestIshealthyWhenDisconnected(t *testing.T) {
config := config.GetConfig()
dbHandler := DatabaseHandler{}

err := dbHandler.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)

if err != nil {
t.Errorf("Connect() returned an error: %s", err.Error())
}

dao := NewMetricsDAO(dbHandler.DB)

dbHandler.Disconnect()

isHealthy, err := dao.IsHealthy()

if err == nil {
t.Errorf("isHealthy returned no error when disconnected")
}

if isHealthy {
t.Errorf("isHealthy returned true when disconnected")
}
}

func TestCreate(t *testing.T) {
config := config.GetConfig()
dbHandler := DatabaseHandler{}

err := dbHandler.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)
dbHandler.DoInitialSetup()

if err != nil {
t.Errorf("Connect() returned an error: %s", err.Error())
}

dao := NewMetricsDAO(dbHandler.DB)

clientId := "org.aerogear.metrics.testing"
metricsData := []byte("{\"app\":{\"id\":\"com.example.someApp\",\"sdkVersion\":\"2.4.6\",\"appVersion\":\"256\"},\"device\":{\"platform\":\"android\",\"platformVersion\":\"27\"}}")

err = dao.Create(clientId, metricsData)

if err != nil {
t.Errorf("Create() returned an error %s", err.Error())
}
}

func TestCreateBadJSON(t *testing.T) {
config := config.GetConfig()
dbHandler := DatabaseHandler{}

err := dbHandler.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)

if err != nil {
t.Errorf("Connect() returned an error: %s", err.Error())
}

dao := NewMetricsDAO(dbHandler.DB)

clientId := "org.aerogear.metrics.testing"
metricsData := []byte("InvalidJSON")

err = dao.Create(clientId, metricsData)

if err == nil {
t.Errorf("Create() with invalid JSON did not return an error")
}
}

func TestCreateEmptyClientID(t *testing.T) {
config := config.GetConfig()
dbHandler := DatabaseHandler{}

err := dbHandler.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)
dbHandler.DoInitialSetup()

if err != nil {
t.Errorf("Connect() returned an error: %s", err.Error())
}

dao := NewMetricsDAO(dbHandler.DB)

clientId := ""
metricsData := []byte("{\"app\":{\"id\":\"com.example.someApp\",\"sdkVersion\":\"2.4.6\",\"appVersion\":\"256\"},\"device\":{\"platform\":\"android\",\"platformVersion\":\"27\"}}")

err = dao.Create(clientId, metricsData)

if err == nil {
t.Errorf("Create() with empty clientId did not return an error")
}
}
32 changes: 18 additions & 14 deletions pkg/dao/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import (
_ "github.com/lib/pq"
)

var db *sql.DB
// var db *sql.DB

func Connect(dbHost, dbUser, dbPassword, dbName, sslMode string) (*sql.DB, error) {
if db != nil {
return db, nil
type DatabaseHandler struct {
DB *sql.DB
}

func (handler *DatabaseHandler) Connect(dbHost, dbUser, dbPassword, dbName, sslMode string) error {
if handler.DB != nil {
return nil
}
//connection logic
connStr := fmt.Sprintf("host=%v user=%v password=%v dbname=%v sslmode=%v", dbHost, dbUser, dbPassword, dbName, sslMode)
Expand All @@ -22,31 +26,31 @@ func Connect(dbHost, dbUser, dbPassword, dbName, sslMode string) (*sql.DB, error

// an error can happen here if the connection string is invalid
if err != nil {
return nil, err
return err
}

// an error happens here if we cannot connect
if err = dbInstance.Ping(); err != nil {
return nil, err
return err
}

// assign db variable declared above
db = dbInstance
return dbInstance, nil
handler.DB = dbInstance
return nil
}

func Disconnect() error {
if db != nil {
return db.Close()
func (handler *DatabaseHandler) Disconnect() error {
if handler.DB != nil {
return handler.DB.Close()
}
return nil
}

func DoInitialSetup() error {
if db == nil {
func (handler *DatabaseHandler) DoInitialSetup() error {
if handler.DB == nil {
return errors.New("cannot setup database, must call Connect() first")
}
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS mobileappmetrics(clientId varchar(30) NOT NULL, event_time timestamptz NOT NULL DEFAULT now() Not NULL, data jsonb)"); err != nil {
if _, err := handler.DB.Exec("CREATE TABLE IF NOT EXISTS mobileappmetrics(clientId varchar(30) NOT NULL CHECK (clientId <> ''), event_time timestamptz NOT NULL DEFAULT now() Not NULL, data jsonb)"); err != nil {
return err
}
return nil
Expand Down
115 changes: 115 additions & 0 deletions pkg/dao/db_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// +build integration

package dao

import (
"testing"

"github.com/aerogear/aerogear-metrics-api/pkg/config"
)

func TestConnect(t *testing.T) {
config := config.GetConfig()
dbHandler := DatabaseHandler{}

err := dbHandler.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)

if err != nil {
t.Errorf("Connect() returned an error: %s", err.Error())
}

err = dbHandler.DB.Ping()

if err != nil {
t.Errorf("Failed to Ping the database after Connect(): %s", err.Error())
}
}

func TestConnectAlreadyConnected(t *testing.T) {
config := config.GetConfig()
dbHandler := DatabaseHandler{}

err := dbHandler.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)

if err != nil {
t.Errorf("Connect() returned an error: %s", err.Error())
}

err = dbHandler.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)

if err != nil {
t.Errorf("Connect() returned an error: %s", err.Error())
}
}

func TestDisconnect(t *testing.T) {
config := config.GetConfig()
dbHandler := DatabaseHandler{}

err := dbHandler.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)

if err != nil {
t.Errorf("Connect() returned an error: %s", err.Error())
}

err = dbHandler.Disconnect()

if err != nil {
t.Errorf("Disconnect() returned an error: %s", err.Error())
}

err = dbHandler.DB.Ping()

if err == nil {
t.Errorf("Ping did not return an error after calling Disconnect()")
}
}

func TestDisconnectNotConnected(t *testing.T) {
dbHandler := DatabaseHandler{}

err := dbHandler.Disconnect()

if err != nil {
t.Errorf("Disconnect() returned an error: %s", err.Error())
}
}

func TestDoInitialSetup(t *testing.T) {
config := config.GetConfig()
dbHandler := DatabaseHandler{}

err := dbHandler.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)

if err != nil {
t.Errorf("Connect() returned an error: %s", err.Error())
}

err = dbHandler.DoInitialSetup()

if err != nil {
t.Errorf("DoInitialSetup() returned an error: %s", err.Error())
}

var exists bool

err = dbHandler.DB.QueryRow("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'mobileappmetrics');").Scan(&exists)

if err != nil {
t.Errorf("Database returned an error while checking if table exists: %s", err.Error())
}

if !exists {
t.Errorf("Expected table mobileappmetrics does not exist")
}
}

func TestDoInitialSetupNotConnected(t *testing.T) {
dbHandler := DatabaseHandler{}

err := dbHandler.DoInitialSetup()

if err == nil {
t.Errorf("DoInitialSetup did not return an error")
}
}

0 comments on commit a2d1003

Please sign in to comment.