Compare commits
3 Commits
2bda056ca7
...
cd7f919eb2
| Author | SHA1 | Date | |
|---|---|---|---|
|
cd7f919eb2
|
|||
|
e77d3c4d5e
|
|||
|
0e16bb5361
|
13
main.go
13
main.go
@@ -14,7 +14,7 @@ func main() {
|
|||||||
// Config
|
// Config
|
||||||
conf, err := config.GetConf("config")
|
conf, err := config.GetConf("config")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not get config", err)
|
log.Println("Could not get config", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,23 +25,24 @@ func main() {
|
|||||||
// DB
|
// DB
|
||||||
store, err := store.GetStore(&conf.Db)
|
store, err := store.GetStore(&conf.Db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not get store", err)
|
log.Println("Could not get store", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if store != nil {
|
if store != nil {
|
||||||
log.Print("Got DB connection")
|
log.Println("Got DB connection")
|
||||||
}
|
}
|
||||||
m := models.New(store)
|
m := models.New(store)
|
||||||
if m != nil {
|
if m != nil {
|
||||||
log.Print("created model")
|
log.Println("created model")
|
||||||
}
|
}
|
||||||
|
|
||||||
router := routes.NewRouter(m)
|
router := routes.NewRouter(m)
|
||||||
|
|
||||||
log.Print("Running server on " + port)
|
log.Println("Running server on " + port)
|
||||||
|
|
||||||
http.ListenAndServe(":"+port, router)
|
http.ListenAndServe(":"+port, router)
|
||||||
|
|
||||||
log.Print("App environment is " + env)
|
log.Println("App environment is " + env)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
// Store represents the backing store.
|
// Store represents the backing store.
|
||||||
type Store interface {
|
type Store interface {
|
||||||
|
ConnectionLive() error
|
||||||
SelectActions() ([]*Action, error)
|
SelectActions() ([]*Action, error)
|
||||||
SelectActionByID(id int) (*Action, error)
|
SelectActionByID(id int) (*Action, error)
|
||||||
SelectPlans() ([]*Plan, error)
|
SelectPlans() ([]*Plan, error)
|
||||||
@@ -19,3 +24,12 @@ type Model struct {
|
|||||||
func New(store Store) *Model {
|
func New(store Store) *Model {
|
||||||
return &Model{Store: store}
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action
|
|||||||
return ms.actions, nil
|
return ms.actions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ms *multiStore) ConnectionLive() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestModelActions(t *testing.T) {
|
func TestModelActions(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
a1 := &models.Action{ActionID: 3}
|
a1 := &models.Action{ActionID: 3}
|
||||||
@@ -83,3 +87,25 @@ func TestModelPlanMethods(t *testing.T) {
|
|||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
assert.EqualValues(6, planId)
|
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
58
routes/health.go
Normal 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
87
routes/health_test.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@ func TestErrorPlan(t *testing.T) {
|
|||||||
// set up
|
// set up
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
m := getErrorModel()
|
m := getErrorModel("Model always errors")
|
||||||
|
|
||||||
router := routes.NewRouter(m)
|
router := routes.NewRouter(m)
|
||||||
req, _ := http.NewRequest("GET", "/plans", nil)
|
req, _ := http.NewRequest("GET", "/plans", nil)
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action
|
|||||||
return ms.actions, nil
|
return ms.actions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ms *multiStore) ConnectionLive() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func getEmptyModel() *models.Model {
|
func getEmptyModel() *models.Model {
|
||||||
ss := &multiStore{
|
ss := &multiStore{
|
||||||
[]*models.Action{},
|
[]*models.Action{},
|
||||||
@@ -75,11 +79,15 @@ func (e *errorStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action,
|
|||||||
return nil, e.error
|
return nil, e.error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *errorStore) ConnectionLive() error {
|
||||||
|
return e.error
|
||||||
|
}
|
||||||
|
|
||||||
type errorStore struct {
|
type errorStore struct {
|
||||||
error error
|
error error
|
||||||
}
|
}
|
||||||
|
|
||||||
func getErrorModel() *models.Model {
|
func getErrorModel(errorMsg string) *models.Model {
|
||||||
e := &errorStore{error: fmt.Errorf("Model always errors")}
|
e := &errorStore{error: fmt.Errorf(errorMsg)}
|
||||||
return models.New(e)
|
return models.New(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ func NewRouter(m *models.Model) http.Handler {
|
|||||||
router.MethodNotAllowed(methodNotAllowedHandler)
|
router.MethodNotAllowed(methodNotAllowedHandler)
|
||||||
router.NotFound(notFoundHandler)
|
router.NotFound(notFoundHandler)
|
||||||
router.Mount("/plans", newPlanRouter(m))
|
router.Mount("/plans", newPlanRouter(m))
|
||||||
|
router.Mount("/health", newHealthRouter(m))
|
||||||
router.Get("/ping", ping)
|
router.Get("/ping", ping)
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,3 +78,7 @@ func (store *postgresStore) InsertPlan(plan *models.Plan) (int, error) {
|
|||||||
}
|
}
|
||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *postgresStore) ConnectionLive() error {
|
||||||
|
return store.db.Ping()
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func getDbMock(t *testing.T) (models.Store, sqlmock.Sqlmock) {
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("got an error creating a stub db. How?: %s", err)
|
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)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,30 +40,30 @@ func createPostgresDB(dbConf *config.DBConfig) (*sqlx.DB, error) {
|
|||||||
|
|
||||||
tmp, err := sql.Open("pgx", connStr)
|
tmp, err := sql.Open("pgx", connStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not connect to database: \n", err)
|
log.Print("Could not connect to database: \n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
db := sqlx.NewDb(tmp, "pgx")
|
db := sqlx.NewDb(tmp, "pgx")
|
||||||
if err := db.Ping(); err != nil {
|
if err := db.Ping(); err != nil {
|
||||||
log.Fatal("database ping failed\n", err)
|
log.Print("database ping failed\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
driver, err := postgres.WithInstance(db.DB, &postgres.Config{})
|
driver, err := postgres.WithInstance(db.DB, &postgres.Config{})
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
m, err := migrate.NewWithDatabaseInstance("file://store/migrations", "postgres", driver)
|
m, err := migrate.NewWithDatabaseInstance("file://store/migrations", "postgres", driver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not perform migration", err)
|
log.Print("Could not perform migration", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := m.Up(); err != nil {
|
if err := m.Up(); err != nil {
|
||||||
if err == migrate.ErrNoChange {
|
if err == migrate.ErrNoChange {
|
||||||
log.Print("No migration needed.")
|
log.Print("No migration needed.")
|
||||||
} else {
|
} 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
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user