diff --git a/routes/actions.go b/routes/actions.go index c5f07e8..fb24749 100644 --- a/routes/actions.go +++ b/routes/actions.go @@ -14,6 +14,7 @@ func newActionRouter(m *models.Model) http.Handler { router.Get("/", getActionsFunc(m)) router.Post("/", postActionFunc(m)) router.Get("/{actionid}", getActionByIDFunc(m)) + router.Put("/{actionid}", putActionFunc(m)) return router } @@ -125,14 +126,23 @@ func postActionFunc(m *models.Model) http.HandlerFunc { } } +type updateActionResponse struct { + UpdatedAction *models.Action `json:"updated_action"` + ID int64 `json:"id"` +} + func putActionFunc(m *models.Model) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - + id, err := strconv.Atoi(chi.URLParam(r, "actionid")) + if err != nil { + notFoundHandler(w, r) + return + } r.Body = http.MaxBytesReader(w, r.Body, 1024) dec := json.NewDecoder(r.Body) dec.DisallowUnknownFields() var a models.Action - err := dec.Decode(&a) + err = dec.Decode(&a) if err != nil { badRequestError(w, err) return @@ -149,8 +159,9 @@ func putActionFunc(m *models.Model) http.HandlerFunc { CompletedChunks: a.CompletedChunks, CompletedOn: a.CompletedOn, PlanID: a.PlanID, + ActionID: int64(id), } - id, err := m.AddAction(action) + err = m.SaveAction(action) if err != nil { serverError(w, err) return @@ -161,11 +172,11 @@ func putActionFunc(m *models.Model) http.HandlerFunc { return } - response := &createActionResponse{ - CreatedAction: action, + response := &updateActionResponse{ + UpdatedAction: action, ID: int64(id), } - w.WriteHeader(http.StatusCreated) + w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(response); err != nil { serverError(w, err) diff --git a/routes/put_action_test.go b/routes/put_action_test.go new file mode 100644 index 0000000..5dc03b4 --- /dev/null +++ b/routes/put_action_test.go @@ -0,0 +1,236 @@ +package routes_test + +import ( + "bytes" + "encoding/json" + "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 TestPureJSONPutAction(t *testing.T) { + // set up + assert := assert.New(t) + compOn, _ := time.Parse("2006-01-02", "2021-01-01") + a := &models.Action{ + PlanID: 5, + CompletedOn: &compOn, + EstimatedChunks: 3, + CompletedChunks: 2, + ActionDescription: "here's an action", + } + m := getModel([]*models.Plan{}, []*models.Action{a}) + router := routes.NewRouter(m) + data := []byte(`{ + "action_description": "here's an action", + "estimated_chunks": 3, + "completed_chunks": 2, + "completed_on": "2021-01-01T00:00:00Z", + "plan_id": 5 + }`) + req, _ := http.NewRequest("PUT", "/actions/0", 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.StatusOK, status) + // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. + expected := `{ + "updated_action": { + "action_description": "here's an action", + "estimated_chunks": 3, + "completed_chunks": 2, + "completed_on": "2021-01-01T00:00:00Z", + "plan_id": 5, + "action_id": 0 + }, + "id": 0 + }` + assert.JSONEq(expected, rr.Body.String()) + contentType := rr.Header().Get("Content-Type") + assert.Equal("application/json", contentType) +} + +func TestExtraFieldActionPutJSON(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(`{ + "completed_on": "2021-01-01T00:00:00Z", + "plan_id": 5, + "sabotage": "omg" + }`) + req, _ := http.NewRequest("PUT", "/actions/1", 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 TestEmptyBodyActionPut(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("PUT", "/actions/1", 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 TestTwoBodyActionPut(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_id": 5 + }, { + "plan_id": 6 + }`) + req, _ := http.NewRequest("PUT", "/actions/1", 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 TestBadActionIDPut(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_id": 5 + }, { + "plan_id": 6 + }`) + req, _ := http.NewRequest("PUT", "/actions/text", 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.StatusNotFound, status) + // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. + expected := `Not Found` + assert.Equal(expected, strings.TrimSpace(rr.Body.String())) +} + +func TestErrorUpdateAction(t *testing.T) { + // set up + assert := assert.New(t) + + m := getErrorModel("Model always errors") + + router := routes.NewRouter(m) + a := &models.Action{PlanID: 6} + data, _ := json.Marshal(a) + req, _ := http.NewRequest("PUT", "/actions/1", 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.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 TestErrorOnRetrieveUpdateAction(t *testing.T) { + // set up + assert := assert.New(t) + + m := getErrorOnGetModel("Model always errors") + + router := routes.NewRouter(m) + a := &models.Action{PlanID: 6} + data, _ := json.Marshal(a) + req, _ := http.NewRequest("PUT", "/actions/1", 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.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 TestErrorWriterUpdateAction(t *testing.T) { + // set up + assert := assert.New(t) + + a := &models.Action{PlanID: 6} + m := getModel([]*models.Plan{}, []*models.Action{a}) + + router := routes.NewRouter(m) + data, _ := json.Marshal(a) + req, _ := http.NewRequest("PUT", "/actions/1", bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") + + rr := NewBadWriter() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusInternalServerError, status) + +}