diff --git a/routes/errors.go b/routes/errors.go index 81160ab..a43793a 100644 --- a/routes/errors.go +++ b/routes/errors.go @@ -11,6 +11,12 @@ func serverError(w http.ResponseWriter, err error) { http.Error(w, http.StatusText(code), code) } +func badRequestError(w http.ResponseWriter, err error) { + code := http.StatusBadRequest + 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) diff --git a/routes/plans.go b/routes/plans.go index c09999e..6d2921c 100644 --- a/routes/plans.go +++ b/routes/plans.go @@ -4,9 +4,9 @@ import ( "encoding/json" "gitea.deepak.science/deepak/gogmagog/models" "github.com/go-chi/chi" + "io" "net/http" "strconv" - "time" ) func newPlanRouter(m *models.Model) http.Handler { @@ -63,9 +63,22 @@ type createPlanResponse struct { func postPlanFunc(m *models.Model) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - time := time.Now() - plan := &models.Plan{PlanDate: &time} + r.Body = http.MaxBytesReader(w, r.Body, 1024) + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + var p models.Plan + err := dec.Decode(&p) + if err != nil { + badRequestError(w, err) + return + } + err = dec.Decode(&struct{}{}) + if err != io.EOF { + badRequestError(w, err) + return + } + plan := &models.Plan{PlanDate: p.PlanDate} id, err := m.AddPlan(plan) if err != nil { serverError(w, err) diff --git a/routes/post_plan_test.go b/routes/post_plan_test.go index 311a1d8..b52abda 100644 --- a/routes/post_plan_test.go +++ b/routes/post_plan_test.go @@ -1,6 +1,8 @@ package routes_test import ( + "bytes" + "encoding/json" "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" "github.com/stretchr/testify/assert" @@ -18,10 +20,11 @@ func TestCreatePlanRoute(t *testing.T) { p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) router := routes.NewRouter(m) - req, _ := http.NewRequest("POST", "/plans", nil) + data, _ := json.Marshal(p) + req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() - // function under test router.ServeHTTP(rr, req) @@ -41,6 +44,119 @@ func TestCreatePlanRoute(t *testing.T) { assert.Equal("application/json", contentType) } +func TestPureJSON(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) + data := []byte(`{ + "plan_date": "2021-01-01T00:00:00Z", + "plan_id": 5 + }`) + req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusCreated, status) + // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. + expected := `{ + "created_plan": { + "plan_id": 6, + "plan_date": "2021-01-01T00:00:00Z" + }, + "id": 0 + }` + assert.JSONEq(expected, rr.Body.String()) + contentType := rr.Header().Get("Content-Type") + assert.Equal("application/json", contentType) +} + +func TestExtraFieldJSON(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) + data := []byte(`{ + "plan_date": "2021-01-01T00:00:00Z", + "plan_id": 5, + "plan_sabotage": "omg" + }`) + req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusBadRequest, status) + // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. + expected := `Bad Request` + assert.Equal(expected, strings.TrimSpace(rr.Body.String())) +} +func TestEmptyBody(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) + data := []byte(``) + req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusBadRequest, status) + // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. + expected := `Bad Request` + assert.Equal(expected, strings.TrimSpace(rr.Body.String())) +} + +func TestTwoBody(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) + data := []byte(`{ + "plan_date": "2021-01-01T00:00:00Z", + "plan_id": 5 + }, { + "plan_date": "2021-01-01T00:00:00Z", + "plan_id": 6 + }`) + req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusBadRequest, status) + // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. + expected := `Bad Request` + assert.Equal(expected, strings.TrimSpace(rr.Body.String())) +} + + func TestErrorCreatePlan(t *testing.T) { // set up assert := assert.New(t) @@ -48,7 +164,11 @@ func TestErrorCreatePlan(t *testing.T) { m := getErrorModel("Model always errors") router := routes.NewRouter(m) - req, _ := http.NewRequest("POST", "/plans", nil) + planDate, _ := time.Parse("2006-01-02", "2021-01-01") + p := &models.Plan{PlanID: 6, PlanDate: &planDate} + data, _ := json.Marshal(p) + req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -70,8 +190,11 @@ func TestErrorOnRetrieveCreatePlan(t *testing.T) { m := getErrorOnGetModel("Model always errors") router := routes.NewRouter(m) - req, _ := http.NewRequest("POST", "/plans", nil) - + planDate, _ := time.Parse("2006-01-02", "2021-01-01") + p := &models.Plan{PlanID: 6, PlanDate: &planDate} + data, _ := json.Marshal(p) + req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() // function under test @@ -94,7 +217,9 @@ func TestErrorWriterCreatePlan(t *testing.T) { m := getModel([]*models.Plan{p}, []*models.Action{}) router := routes.NewRouter(m) - req, _ := http.NewRequest("POST", "/plans", nil) + data, _ := json.Marshal(p) + req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") rr := NewBadWriter()