Adds route to retrieve all plans and tests thereof, and json tags for model
All checks were successful
gitea-deepak/gogmagog/pipeline/head This commit looks good
All checks were successful
gitea-deepak/gogmagog/pipeline/head This commit looks good
This commit is contained in:
parent
6d104dc72a
commit
2bda056ca7
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.15
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/golang-migrate/migrate/v4 v4.14.1
|
||||
github.com/jackc/pgx/v4 v4.10.1
|
||||
github.com/jmoiron/sqlx v1.2.0
|
||||
|
2
go.sum
2
go.sum
@ -108,6 +108,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
|
20
main.go
20
main.go
@ -3,8 +3,10 @@ package main
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/config"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"gitea.deepak.science/deepak/gogmagog/store"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
@ -20,9 +22,6 @@ func main() {
|
||||
port := appConf.Port
|
||||
env := appConf.Environment
|
||||
|
||||
log.Print("Running server on " + port)
|
||||
log.Print("App environment is " + env)
|
||||
|
||||
// DB
|
||||
store, err := store.GetStore(&conf.Db)
|
||||
if err != nil {
|
||||
@ -38,14 +37,11 @@ func main() {
|
||||
log.Print("created model")
|
||||
}
|
||||
|
||||
acts, acterr := m.Actions()
|
||||
if acterr != nil {
|
||||
log.Fatal("whoopsies", acterr)
|
||||
} else {
|
||||
log.Printf("Got %d actions", len(acts))
|
||||
for i, act := range acts {
|
||||
log.Printf("%d: %v", i, act)
|
||||
}
|
||||
}
|
||||
router := routes.NewRouter(m)
|
||||
|
||||
log.Print("Running server on " + port)
|
||||
|
||||
http.ListenAndServe(":"+port, router)
|
||||
|
||||
log.Print("App environment is " + env)
|
||||
}
|
||||
|
@ -6,14 +6,14 @@ import (
|
||||
|
||||
// Action represents a single action item.
|
||||
type Action struct {
|
||||
ActionID int64
|
||||
ActionDescription string
|
||||
EstimatedChunks int
|
||||
CompletedChunks int
|
||||
CompletedOn time.Time
|
||||
PlanID int
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
ActionID int64 `json:"action_id"`
|
||||
ActionDescription string `json:"action_description"`
|
||||
EstimatedChunks int `json:"estimated_chunks"`
|
||||
CompletedChunks int `json:"completed_chunks"`
|
||||
CompletedOn time.Time `json:"completed_on"`
|
||||
PlanID int `json:"plan_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Actions returns all actions from the model.
|
||||
|
@ -6,8 +6,8 @@ import (
|
||||
|
||||
// Plan represents a single day's agenda of actions.
|
||||
type Plan struct {
|
||||
PlanID int64
|
||||
PlanDate time.Time
|
||||
PlanID int64 `json:"plan_id"`
|
||||
PlanDate time.Time `json:"plan_date"`
|
||||
}
|
||||
|
||||
// Plans returns all plans in the model.
|
||||
|
12
routes/errors.go
Normal file
12
routes/errors.go
Normal file
@ -0,0 +1,12 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func serverError(w http.ResponseWriter, err error) {
|
||||
code := http.StatusInternalServerError
|
||||
log.Printf("received error: {%v}", err)
|
||||
http.Error(w, http.StatusText(code), code)
|
||||
}
|
29
routes/http_util_test.go
Normal file
29
routes/http_util_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type BadResponseWriter struct {
|
||||
Code int
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func NewBadWriter() *BadResponseWriter {
|
||||
return &BadResponseWriter{
|
||||
header: http.Header{},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *BadResponseWriter) Header() http.Header {
|
||||
return w.header
|
||||
}
|
||||
|
||||
func (w *BadResponseWriter) Write(b []byte) (int, error) {
|
||||
return 0, fmt.Errorf("always an error")
|
||||
}
|
||||
|
||||
func (w *BadResponseWriter) WriteHeader(statusCode int) {
|
||||
w.Code = statusCode
|
||||
}
|
28
routes/plans.go
Normal file
28
routes/plans.go
Normal file
@ -0,0 +1,28 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"github.com/go-chi/chi"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func newPlanRouter(m *models.Model) http.Handler {
|
||||
router := chi.NewRouter()
|
||||
router.Get("/", getAllPlansFunc(m))
|
||||
return router
|
||||
}
|
||||
|
||||
func getAllPlansFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
plans, err := m.Plans()
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(plans); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
}
|
||||
}
|
105
routes/plans_test.go
Normal file
105
routes/plans_test.go
Normal file
@ -0,0 +1,105 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEmptyPlans(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewRouter(m)
|
||||
req, _ := http.NewRequest("GET", "/plans", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
expected := `[]`
|
||||
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestOnePlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
p := &models.Plan{PlanID: 6, PlanDate: planDate}
|
||||
m := getModel([]*models.Plan{p}, []*models.Action{})
|
||||
router := routes.NewRouter(m)
|
||||
req, _ := http.NewRequest("GET", "/plans", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `[
|
||||
{
|
||||
"plan_id": 6,
|
||||
"plan_date": "2021-01-01T00:00:00Z"
|
||||
}
|
||||
]`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestErrorPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorModel()
|
||||
|
||||
router := routes.NewRouter(m)
|
||||
req, _ := http.NewRequest("GET", "/plans", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestEmptyPlanErrorWriter(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getEmptyModel()
|
||||
|
||||
router := routes.NewRouter(m)
|
||||
req, _ := http.NewRequest("GET", "/plans", nil)
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
85
routes/route_model_test.go
Normal file
85
routes/route_model_test.go
Normal file
@ -0,0 +1,85 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
)
|
||||
|
||||
type multiStore struct {
|
||||
actions []*models.Action
|
||||
plans []*models.Plan
|
||||
}
|
||||
|
||||
func (ms *multiStore) SelectActions() ([]*models.Action, error) {
|
||||
return ms.actions, nil
|
||||
}
|
||||
|
||||
func (ms *multiStore) SelectActionByID(id int) (*models.Action, error) {
|
||||
return ms.actions[0], nil
|
||||
}
|
||||
|
||||
func (ms *multiStore) SelectPlans() ([]*models.Plan, error) {
|
||||
return ms.plans, nil
|
||||
}
|
||||
|
||||
func (ms *multiStore) SelectPlanByID(id int) (*models.Plan, error) {
|
||||
return ms.plans[0], nil
|
||||
}
|
||||
|
||||
func (ms *multiStore) InsertPlan(plan *models.Plan) (int, error) {
|
||||
return int(plan.PlanID), nil
|
||||
}
|
||||
|
||||
func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, error) {
|
||||
return ms.actions, nil
|
||||
}
|
||||
|
||||
func getEmptyModel() *models.Model {
|
||||
ss := &multiStore{
|
||||
[]*models.Action{},
|
||||
[]*models.Plan{},
|
||||
}
|
||||
m := models.New(ss)
|
||||
return m
|
||||
}
|
||||
func getModel(plns []*models.Plan, acts []*models.Action) *models.Model {
|
||||
ss := &multiStore{
|
||||
actions: acts,
|
||||
plans: plns,
|
||||
}
|
||||
m := models.New(ss)
|
||||
return m
|
||||
}
|
||||
|
||||
func (e *errorStore) SelectActions() ([]*models.Action, error) {
|
||||
return nil, e.error
|
||||
}
|
||||
|
||||
func (e *errorStore) SelectActionByID(id int) (*models.Action, error) {
|
||||
return nil, e.error
|
||||
}
|
||||
|
||||
func (e *errorStore) SelectPlans() ([]*models.Plan, error) {
|
||||
return nil, e.error
|
||||
}
|
||||
|
||||
func (e *errorStore) SelectPlanByID(id int) (*models.Plan, error) {
|
||||
return nil, e.error
|
||||
}
|
||||
|
||||
func (e *errorStore) InsertPlan(plan *models.Plan) (int, error) {
|
||||
return 0, e.error
|
||||
}
|
||||
|
||||
func (e *errorStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, error) {
|
||||
return nil, e.error
|
||||
}
|
||||
|
||||
type errorStore struct {
|
||||
error error
|
||||
}
|
||||
|
||||
func getErrorModel() *models.Model {
|
||||
e := &errorStore{error: fmt.Errorf("Model always errors")}
|
||||
return models.New(e)
|
||||
}
|
35
routes/routes.go
Normal file
35
routes/routes.go
Normal file
@ -0,0 +1,35 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"github.com/go-chi/chi"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewRouter returns a router powered by the provided model.
|
||||
func NewRouter(m *models.Model) http.Handler {
|
||||
router := chi.NewRouter()
|
||||
router.MethodNotAllowed(methodNotAllowedHandler)
|
||||
router.NotFound(notFoundHandler)
|
||||
router.Mount("/plans", newPlanRouter(m))
|
||||
router.Get("/ping", ping)
|
||||
return router
|
||||
}
|
||||
func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) {
|
||||
code := http.StatusMethodNotAllowed
|
||||
http.Error(w, http.StatusText(code), code)
|
||||
}
|
||||
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
code := http.StatusNotFound
|
||||
http.Error(w, http.StatusText(code), code)
|
||||
}
|
||||
|
||||
func ping(w http.ResponseWriter, r *http.Request) {
|
||||
// A very simple health check.
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(map[string]string{"ping": "pong"}); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
}
|
86
routes/routes_test.go
Normal file
86
routes/routes_test.go
Normal file
@ -0,0 +1,86 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPingHandler(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewRouter(m)
|
||||
req, _ := http.NewRequest("GET", "/ping", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
expected := `{"ping": "pong"}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestPingPostHandler(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewRouter(m)
|
||||
req, _ := http.NewRequest("POST", "/ping", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusMethodNotAllowed, status)
|
||||
expected := http.StatusText(http.StatusMethodNotAllowed)
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestNotFoundHandler(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewRouter(m)
|
||||
req, _ := http.NewRequest("POST", "/null", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusNotFound, status)
|
||||
expected := http.StatusText(http.StatusNotFound)
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestPingError(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewRouter(m)
|
||||
req, _ := http.NewRequest("GET", "/ping", nil)
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user