Compare commits

...

3 Commits

Author SHA1 Message Date
cd7f919eb2 Removes calls to log.Fatal, adds check for null store
All checks were successful
gitea-deepak/gogmagog/pipeline/head This commit looks good
2020-12-31 18:56:21 -06:00
e77d3c4d5e Adds health check and test 2020-12-31 18:47:27 -06:00
0e16bb5361 Adds healthy check to model 2020-12-31 18:05:06 -06:00
11 changed files with 227 additions and 15 deletions

13
main.go
View File

@@ -14,7 +14,7 @@ func main() {
// Config
conf, err := config.GetConf("config")
if err != nil {
log.Fatal("Could not get config", err)
log.Println("Could not get config", err)
os.Exit(1)
}
@@ -25,23 +25,24 @@ func main() {
// DB
store, err := store.GetStore(&conf.Db)
if err != nil {
log.Fatal("Could not get store", err)
log.Println("Could not get store", err)
os.Exit(1)
}
if store != nil {
log.Print("Got DB connection")
log.Println("Got DB connection")
}
m := models.New(store)
if m != nil {
log.Print("created model")
log.Println("created model")
}
router := routes.NewRouter(m)
log.Print("Running server on " + port)
log.Println("Running server on " + port)
http.ListenAndServe(":"+port, router)
log.Print("App environment is " + env)
log.Println("App environment is " + env)
}

View File

@@ -1,7 +1,12 @@
package models
import (
"fmt"
)
// Store represents the backing store.
type Store interface {
ConnectionLive() error
SelectActions() ([]*Action, error)
SelectActionByID(id int) (*Action, error)
SelectPlans() ([]*Plan, error)
@@ -19,3 +24,12 @@ type Model struct {
func New(store Store) *Model {
return &Model{Store: store}
}
// Healthy returns an error if the connection is healthy.
// Wrapper over db.Ping()
func (m *Model) Healthy() error {
if m.Store == nil {
return fmt.Errorf("No store available")
}
return m.ConnectionLive()
}

View File

@@ -35,6 +35,10 @@ func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action
return ms.actions, nil
}
func (ms *multiStore) ConnectionLive() error {
return nil
}
func TestModelActions(t *testing.T) {
assert := assert.New(t)
a1 := &models.Action{ActionID: 3}
@@ -83,3 +87,25 @@ func TestModelPlanMethods(t *testing.T) {
assert.Nil(err)
assert.EqualValues(6, planId)
}
func TestModelHealthy(t *testing.T) {
assert := assert.New(t)
ss := &multiStore{
[]*models.Action{},
[]*models.Plan{},
}
m := models.New(ss)
err := m.Healthy()
assert.Nil(err)
}
func TestNilModelUnhealthy(t *testing.T) {
assert := assert.New(t)
m := models.New(nil)
err := m.Healthy()
assert.NotNil(err)
}

58
routes/health.go Normal file
View File

@@ -0,0 +1,58 @@
package routes
import (
"encoding/json"
"gitea.deepak.science/deepak/gogmagog/models"
"github.com/go-chi/chi"
"net/http"
)
func newHealthRouter(m *models.Model) http.Handler {
router := chi.NewRouter()
router.Get("/", getHealthFunc(m))
return router
}
type healthCheck struct {
Name string `json:"name"`
Healthy bool `json:"healthy"`
Message string `json:"message"`
}
func getHealthFunc(m *models.Model) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var healths []*healthCheck
healths = append(healths, dbHealth(m))
code := http.StatusOK
for _, h := range healths {
if !h.Healthy {
code = http.StatusInternalServerError
break
}
}
w.WriteHeader(code)
w.Header().Add("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(healths); err != nil {
serverError(w, err)
}
}
}
func dbHealth(m *models.Model) *healthCheck {
errMessage := ""
health := true
name := "Store"
err := m.Healthy()
if err != nil {
errMessage = err.Error()
health = false
}
return &healthCheck{
Name: name,
Healthy: health,
Message: errMessage,
}
}

87
routes/health_test.go Normal file
View File

@@ -0,0 +1,87 @@
package routes_test
import (
"fmt"
"gitea.deepak.science/deepak/gogmagog/routes"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
)
func TestEmptyHeatlhErrorWriter(t *testing.T) {
// set up
assert := assert.New(t)
m := getEmptyModel()
router := routes.NewRouter(m)
req, _ := http.NewRequest("GET", "/health", nil)
rr := NewBadWriter()
// function under test
router.ServeHTTP(rr, req)
// check results
status := rr.Code
assert.Equal(http.StatusInternalServerError, status)
}
func TestEmptyHealth(t *testing.T) {
// set up
assert := assert.New(t)
m := getEmptyModel()
router := routes.NewRouter(m)
req, _ := http.NewRequest("GET", "/health", nil)
rr := httptest.NewRecorder()
// function under test
router.ServeHTTP(rr, req)
// check results
status := rr.Code
assert.Equal(http.StatusOK, status)
expected := `[
{
"name": "Store",
"healthy": true,
"message": ""
}
]`
assert.JSONEq(expected, rr.Body.String())
contentType := rr.Header().Get("Content-Type")
assert.Equal("application/json", contentType)
}
func TestUnhealthyDB(t *testing.T) {
// set up
assert := assert.New(t)
errorMsg := "error"
m := getErrorModel(errorMsg)
router := routes.NewRouter(m)
req, _ := http.NewRequest("GET", "/health", nil)
rr := httptest.NewRecorder()
// function under test
router.ServeHTTP(rr, req)
// check results
status := rr.Code
assert.Equal(http.StatusInternalServerError, status)
expected := fmt.Sprintf(`[
{
"name": "Store",
"healthy": false,
"message": "%s"
}
]`, errorMsg)
assert.JSONEq(expected, rr.Body.String())
contentType := rr.Header().Get("Content-Type")
assert.Equal("application/json", contentType)
}

View File

@@ -66,7 +66,7 @@ func TestErrorPlan(t *testing.T) {
// set up
assert := assert.New(t)
m := getErrorModel()
m := getErrorModel("Model always errors")
router := routes.NewRouter(m)
req, _ := http.NewRequest("GET", "/plans", nil)

View File

@@ -34,6 +34,10 @@ func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action
return ms.actions, nil
}
func (ms *multiStore) ConnectionLive() error {
return nil
}
func getEmptyModel() *models.Model {
ss := &multiStore{
[]*models.Action{},
@@ -75,11 +79,15 @@ func (e *errorStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action,
return nil, e.error
}
func (e *errorStore) ConnectionLive() error {
return e.error
}
type errorStore struct {
error error
}
func getErrorModel() *models.Model {
e := &errorStore{error: fmt.Errorf("Model always errors")}
func getErrorModel(errorMsg string) *models.Model {
e := &errorStore{error: fmt.Errorf(errorMsg)}
return models.New(e)
}

View File

@@ -13,6 +13,7 @@ func NewRouter(m *models.Model) http.Handler {
router.MethodNotAllowed(methodNotAllowedHandler)
router.NotFound(notFoundHandler)
router.Mount("/plans", newPlanRouter(m))
router.Mount("/health", newHealthRouter(m))
router.Get("/ping", ping)
return router
}

View File

@@ -78,3 +78,7 @@ func (store *postgresStore) InsertPlan(plan *models.Plan) (int, error) {
}
return id, nil
}
func (store *postgresStore) ConnectionLive() error {
return store.db.Ping()
}

View File

@@ -12,7 +12,7 @@ import (
)
func getDbMock(t *testing.T) (models.Store, sqlmock.Sqlmock) {
db, mock, err := sqlmock.New()
db, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
if err != nil {
t.Fatalf("got an error creating a stub db. How?: %s", err)
}
@@ -385,3 +385,16 @@ func TestErrActionByID(t *testing.T) {
t.Errorf("unfulfilled expectations: %s", err)
}
}
func TestConnectionLive(t *testing.T) {
// setup
assert := assert.New(t)
str, mock := getDbMock(t)
mock.ExpectPing()
// perform func under tests
err := str.ConnectionLive()
// results
assert.Nil(err)
}

View File

@@ -40,30 +40,30 @@ func createPostgresDB(dbConf *config.DBConfig) (*sqlx.DB, error) {
tmp, err := sql.Open("pgx", connStr)
if err != nil {
log.Fatal("Could not connect to database: \n", err)
log.Print("Could not connect to database: \n", err)
return nil, err
}
db := sqlx.NewDb(tmp, "pgx")
if err := db.Ping(); err != nil {
log.Fatal("database ping failed\n", err)
log.Print("database ping failed\n", err)
return nil, err
}
driver, err := postgres.WithInstance(db.DB, &postgres.Config{})
if err != nil {
log.Fatal("Could not create driver for db migration", err)
log.Print("Could not create driver for db migration", err)
return nil, err
}
m, err := migrate.NewWithDatabaseInstance("file://store/migrations", "postgres", driver)
if err != nil {
log.Fatal("Could not perform migration", err)
log.Print("Could not perform migration", err)
return nil, err
}
if err := m.Up(); err != nil {
if err == migrate.ErrNoChange {
log.Print("No migration needed.")
} else {
log.Fatalf("An error occurred while syncing the database.. %v", err)
log.Printf("An error occurred while syncing the database.. %v", err)
return nil, err
}
}