diff --git a/do.sh b/do.sh index 9e7aabe..4aeb25a 100644 --- a/do.sh +++ b/do.sh @@ -12,7 +12,7 @@ build() { test() { echo "I am ${FUNCNAME[0]}ing" - _lint && _vet && _test + fmt && _lint && _vet && _test } run() { diff --git a/routes/errors.go b/routes/errors.go index e7e560c..81160ab 100644 --- a/routes/errors.go +++ b/routes/errors.go @@ -10,3 +10,12 @@ func serverError(w http.ResponseWriter, err error) { log.Printf("received error: {%v}", err) http.Error(w, http.StatusText(code), code) } + +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) +} diff --git a/routes/plans.go b/routes/plans.go index 38ecdee..7ee439b 100644 --- a/routes/plans.go +++ b/routes/plans.go @@ -5,11 +5,13 @@ import ( "gitea.deepak.science/deepak/gogmagog/models" "github.com/go-chi/chi" "net/http" + "strconv" ) func newPlanRouter(m *models.Model) http.Handler { router := chi.NewRouter() router.Get("/", getAllPlansFunc(m)) + router.Get("/{planid}", getPlanByIDFunc(m)) return router } @@ -26,3 +28,27 @@ func getAllPlansFunc(m *models.Model) http.HandlerFunc { } } } + +func getPlanByIDFunc(m *models.Model) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, err := strconv.Atoi(chi.URLParam(r, "planid")) + if err != nil { + notFoundHandler(w, r) + return + } + plan, err := m.Plan(id) + if err != nil { + if models.IsNotFoundError(err) { + notFoundHandler(w, r) + return + } + serverError(w, err) + return + + } + w.Header().Add("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(plan); err != nil { + serverError(w, err) + } + } +} diff --git a/routes/plans_test.go b/routes/plans_test.go index 5f0cc15..1405bf0 100644 --- a/routes/plans_test.go +++ b/routes/plans_test.go @@ -103,3 +103,113 @@ func TestEmptyPlanErrorWriter(t *testing.T) { assert.Equal(http.StatusInternalServerError, status) } + +func TestOnePlanByID(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/6", 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 TestErrorPlanByID(t *testing.T) { + // set up + assert := assert.New(t) + + m := getErrorModel("Model always errors") + + router := routes.NewRouter(m) + req, _ := http.NewRequest("GET", "/plans/5", 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 TestEmptyPlanErrorWriterByID(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/6", nil) + + rr := NewBadWriter() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusInternalServerError, status) + +} + +func TestNotFoundPlanByIDText(t *testing.T) { + // set up + assert := assert.New(t) + + m := getEmptyModel() + + router := routes.NewRouter(m) + req, _ := http.NewRequest("GET", "/plans/wo", nil) + + rr := httptest.NewRecorder() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusNotFound, status) + +} +func TestNotFoundPlanByIDEmpty(t *testing.T) { + // set up + assert := assert.New(t) + + m := getEmptyModel() + + router := routes.NewRouter(m) + req, _ := http.NewRequest("GET", "/plans/1", nil) + + rr := httptest.NewRecorder() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusNotFound, status) + +} diff --git a/routes/route_model_test.go b/routes/route_model_test.go index 6146e2c..b79dac0 100644 --- a/routes/route_model_test.go +++ b/routes/route_model_test.go @@ -10,11 +10,23 @@ type multiStore struct { plans []*models.Plan } +type testNotFoundError struct { + error +} + +func (t *testNotFoundError) NotFound() bool { + return true +} + func (ms *multiStore) SelectActions() ([]*models.Action, error) { return ms.actions, nil } func (ms *multiStore) SelectActionByID(id int) (*models.Action, error) { + if len(ms.actions) < 1 { + err := &testNotFoundError{fmt.Errorf("too small")} + return nil, err + } return ms.actions[0], nil } @@ -23,6 +35,10 @@ func (ms *multiStore) SelectPlans() ([]*models.Plan, error) { } func (ms *multiStore) SelectPlanByID(id int) (*models.Plan, error) { + if len(ms.plans) < 1 { + err := &testNotFoundError{fmt.Errorf("too small")} + return nil, err + } return ms.plans[0], nil } diff --git a/routes/routes.go b/routes/routes.go index 15ee2d4..b970149 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -17,14 +17,6 @@ func NewRouter(m *models.Model) http.Handler { 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.