From 70ddd91d6bc4cd12c6833e924bf3b67281720dc8 Mon Sep 17 00:00:00 2001 From: Deepak Date: Fri, 1 Jan 2021 11:49:22 -0600 Subject: [PATCH] Adds actions, changes time to pointers to make nillable. --- models/action.go | 16 +-- models/plan.go | 4 +- routes/actions.go | 54 ++++++++++ routes/actions_test.go | 228 +++++++++++++++++++++++++++++++++++++++++ routes/plans_test.go | 6 +- routes/routes.go | 1 + store/postgres_test.go | 28 ++--- 7 files changed, 310 insertions(+), 27 deletions(-) create mode 100644 routes/actions.go create mode 100644 routes/actions_test.go diff --git a/models/action.go b/models/action.go index f981532..287a7e7 100644 --- a/models/action.go +++ b/models/action.go @@ -6,14 +6,14 @@ import ( // Action represents a single action item. type Action struct { - 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"` + 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,omitempty"` + PlanID int `json:"plan_id"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` } // Actions returns all actions from the model. diff --git a/models/plan.go b/models/plan.go index 9244130..02e0212 100644 --- a/models/plan.go +++ b/models/plan.go @@ -6,8 +6,8 @@ import ( // Plan represents a single day's agenda of actions. type Plan struct { - PlanID int64 `json:"plan_id"` - PlanDate time.Time `json:"plan_date"` + PlanID int64 `json:"plan_id"` + PlanDate *time.Time `json:"plan_date"` } // Plans returns all plans in the model. diff --git a/routes/actions.go b/routes/actions.go new file mode 100644 index 0000000..4850d8e --- /dev/null +++ b/routes/actions.go @@ -0,0 +1,54 @@ +package routes + +import ( + "encoding/json" + "gitea.deepak.science/deepak/gogmagog/models" + "github.com/go-chi/chi" + "net/http" + "strconv" +) + +func newActionRouter(m *models.Model) http.Handler { + router := chi.NewRouter() + router.Get("/", getAllActionsFunc(m)) + router.Get("/{actionid}", getActionByIDFunc(m)) + return router +} + +func getAllActionsFunc(m *models.Model) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + actions, err := m.Actions() + if err != nil { + serverError(w, err) + return + } + w.Header().Add("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(actions); err != nil { + serverError(w, err) + } + } +} + +func getActionByIDFunc(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 + } + action, err := m.Action(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(action); err != nil { + serverError(w, err) + } + } +} diff --git a/routes/actions_test.go b/routes/actions_test.go new file mode 100644 index 0000000..aad5b85 --- /dev/null +++ b/routes/actions_test.go @@ -0,0 +1,228 @@ +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 TestEmptyActions(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + router := routes.NewRouter(m) + req, _ := http.NewRequest("GET", "/actions", 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 TestOneAction(t *testing.T) { + // set up + assert := assert.New(t) + createdDate, _ := time.Parse("2006-01-02", "2021-01-01") + updatedDate, _ := time.Parse("2006-01-02", "2021-01-02") + completedDate, _ := time.Parse("2006-01-02", "2021-01-03") + a1 := &models.Action{ActionID: 3, ActionDescription: "testing", CompletedChunks: 1, CompletedOn: &completedDate, CreatedAt: &createdDate, UpdatedAt: &updatedDate, EstimatedChunks: 3, PlanID: 0} + m := getModel([]*models.Plan{}, []*models.Action{a1}) + router := routes.NewRouter(m) + req, _ := http.NewRequest("GET", "/actions", 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 := `[ + { + "action_id": 3, + "action_description": "testing", + "estimated_chunks": 3, + "completed_chunks": 1, + "completed_on": "2021-01-03T00:00:00Z", + "updated_at": "2021-01-02T00:00:00Z", + "created_at": "2021-01-01T00:00:00Z", + "plan_id": 0 + } + ]` + assert.JSONEq(expected, rr.Body.String()) + contentType := rr.Header().Get("Content-Type") + assert.Equal("application/json", contentType) +} + +func TestErrorAction(t *testing.T) { + // set up + assert := assert.New(t) + + m := getErrorModel("Model always errors") + + router := routes.NewRouter(m) + req, _ := http.NewRequest("GET", "/actions", 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 TestEmptyActionErrorWriter(t *testing.T) { + // set up + assert := assert.New(t) + + m := getEmptyModel() + + router := routes.NewRouter(m) + req, _ := http.NewRequest("GET", "/actions", nil) + + rr := NewBadWriter() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusInternalServerError, status) + +} + +func TestOneActionByID(t *testing.T) { + // set up + assert := assert.New(t) + createdDate, _ := time.Parse("2006-01-02", "2021-01-01") + updatedDate, _ := time.Parse("2006-01-02", "2021-01-02") + a := &models.Action{ActionID: 6, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 3} + m := getModel([]*models.Plan{}, []*models.Action{a}) + router := routes.NewRouter(m) + req, _ := http.NewRequest("GET", "/actions/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 := `{ + "action_id": 6, + "action_description": "howdy", + "estimated_chunks": 54, + "completed_chunks": 0, + "updated_at": "2021-01-02T00:00:00Z", + "created_at": "2021-01-01T00:00:00Z", + "plan_id": 3 + }` + assert.JSONEq(expected, rr.Body.String()) + contentType := rr.Header().Get("Content-Type") + assert.Equal("application/json", contentType) +} + +func TestErrorActionByID(t *testing.T) { + // set up + assert := assert.New(t) + + m := getErrorModel("Model always errors") + + router := routes.NewRouter(m) + req, _ := http.NewRequest("GET", "/actions/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 TestEmptyActionErrorWriterByID(t *testing.T) { + // set up + assert := assert.New(t) + + a := &models.Action{ActionID: 6} + m := getModel([]*models.Plan{}, []*models.Action{a}) + + router := routes.NewRouter(m) + req, _ := http.NewRequest("GET", "/actions/6", nil) + + rr := NewBadWriter() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusInternalServerError, status) + +} + +func TestNotFoundActionByIDText(t *testing.T) { + // set up + assert := assert.New(t) + + m := getEmptyModel() + + router := routes.NewRouter(m) + req, _ := http.NewRequest("GET", "/actions/wo", nil) + + rr := httptest.NewRecorder() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusNotFound, status) + +} +func TestNotFoundActionByIDEmpty(t *testing.T) { + // set up + assert := assert.New(t) + + m := getEmptyModel() + + router := routes.NewRouter(m) + req, _ := http.NewRequest("GET", "/actions/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/plans_test.go b/routes/plans_test.go index 1405bf0..fd58b1c 100644 --- a/routes/plans_test.go +++ b/routes/plans_test.go @@ -37,7 +37,7 @@ 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} + p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) router := routes.NewRouter(m) req, _ := http.NewRequest("GET", "/plans", nil) @@ -108,7 +108,7 @@ 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} + 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) @@ -158,7 +158,7 @@ func TestEmptyPlanErrorWriterByID(t *testing.T) { assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 6, PlanDate: planDate} + p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) router := routes.NewRouter(m) diff --git a/routes/routes.go b/routes/routes.go index b970149..57e189e 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -13,6 +13,7 @@ func NewRouter(m *models.Model) http.Handler { router.MethodNotAllowed(methodNotAllowedHandler) router.NotFound(notFoundHandler) router.Mount("/plans", newPlanRouter(m)) + router.Mount("/actions", newActionRouter(m)) router.Mount("/health", newHealthRouter(m)) router.Get("/ping", ping) return router diff --git a/store/postgres_test.go b/store/postgres_test.go index 1718d03..4ea8b0e 100644 --- a/store/postgres_test.go +++ b/store/postgres_test.go @@ -40,7 +40,7 @@ func TestSelectPlans(t *testing.T) { assert.Equal(1, len(plans)) plan := plans[0] assert.EqualValues(idToUse, plan.PlanID) - assert.Equal(currentTime, plan.PlanDate) + assert.Equal(currentTime, *plan.PlanDate) if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("unfulfilled expectations: %s", err) @@ -61,7 +61,7 @@ func TestSelectPlanByID(t *testing.T) { plan, err := str.SelectPlanByID(idToUse) assert.Nil(err) assert.EqualValues(idToUse, plan.PlanID) - assert.Equal(currentTime, plan.PlanDate) + assert.Equal(currentTime, *plan.PlanDate) if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("unfulfilled expectations: %s", err) @@ -74,7 +74,7 @@ func TestInsertPlan(t *testing.T) { str, mock := getDbMock(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - plan := &models.Plan{PlanDate: planDate} + plan := &models.Plan{PlanDate: &planDate} idToUse := 8 @@ -103,7 +103,7 @@ func TestInsertPlanErr(t *testing.T) { str, mock := getDbMock(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - plan := &models.Plan{PlanDate: planDate} + plan := &models.Plan{PlanDate: &planDate} mock.ExpectBegin() mock.ExpectQuery("^INSERT INTO plans \\(plan_date\\) VALUES \\(\\$1\\) RETURNING plan_id$"). @@ -127,7 +127,7 @@ func TestInsertPlanCommitErr(t *testing.T) { str, mock := getDbMock(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - plan := &models.Plan{PlanDate: planDate} + plan := &models.Plan{PlanDate: &planDate} idToUse := 8 @@ -205,9 +205,9 @@ func TestSelectActions(t *testing.T) { assert.Equal(desc, action.ActionDescription) assert.Equal(estChunks, action.EstimatedChunks) assert.Equal(compChunks, action.CompletedChunks) - assert.Equal(completeTime, action.CompletedOn) - assert.Equal(createTime, action.CreatedAt) - assert.Equal(updateTime, action.UpdatedAt) + assert.Equal(completeTime, *action.CompletedOn) + assert.Equal(createTime, *action.CreatedAt) + assert.Equal(updateTime, *action.UpdatedAt) assert.Equal(idToUse, action.PlanID) if err := mock.ExpectationsWereMet(); err != nil { @@ -255,9 +255,9 @@ func TestSelectActionsByPlanID(t *testing.T) { assert.Equal(desc, action.ActionDescription) assert.Equal(estChunks, action.EstimatedChunks) assert.Equal(compChunks, action.CompletedChunks) - assert.Equal(completeTime, action.CompletedOn) - assert.Equal(createTime, action.CreatedAt) - assert.Equal(updateTime, action.UpdatedAt) + assert.Equal(completeTime, *action.CompletedOn) + assert.Equal(createTime, *action.CreatedAt) + assert.Equal(updateTime, *action.UpdatedAt) assert.Equal(idToUse, action.PlanID) if err := mock.ExpectationsWereMet(); err != nil { @@ -328,9 +328,9 @@ func TestSelectActionById(t *testing.T) { assert.Equal(desc, action.ActionDescription) assert.Equal(estChunks, action.EstimatedChunks) assert.Equal(compChunks, action.CompletedChunks) - assert.Equal(completeTime, action.CompletedOn) - assert.Equal(createTime, action.CreatedAt) - assert.Equal(updateTime, action.UpdatedAt) + assert.Equal(completeTime, *action.CompletedOn) + assert.Equal(createTime, *action.CreatedAt) + assert.Equal(updateTime, *action.UpdatedAt) assert.Equal(idToUse, action.PlanID) if err := mock.ExpectationsWereMet(); err != nil {