From aaad73da4749bf4aa7c971f20429cb9ae86cdcb1 Mon Sep 17 00:00:00 2001 From: Daniel Mendoza Date: Tue, 21 Jan 2020 22:47:59 -0500 Subject: [PATCH 1/2] feat: rest implementation and fix where condition get all --- .../repositories/sql_tasks_repository.go | 2 +- app/tasks/rest/rest.go | 83 +++++++++++++++++-- app/tasks/rest/structs.go | 24 ++++++ 3 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 app/tasks/rest/structs.go diff --git a/app/tasks/repositories/sql_tasks_repository.go b/app/tasks/repositories/sql_tasks_repository.go index 3dd0f8b..3715720 100644 --- a/app/tasks/repositories/sql_tasks_repository.go +++ b/app/tasks/repositories/sql_tasks_repository.go @@ -19,7 +19,7 @@ func (s *sqlTasksRepository) GetAll(ctx context.Context, page int, limit int) ([ var tasks []DbTask pagination.Paging(&pagination.Param{ - DB: s.Conn.Where("id > ?", 0), + DB: s.Conn.Where("id is not null"), Page: page, Limit: limit, OrderBy: []string{"id desc"}, diff --git a/app/tasks/rest/rest.go b/app/tasks/rest/rest.go index 20cfde2..bcefb38 100644 --- a/app/tasks/rest/rest.go +++ b/app/tasks/rest/rest.go @@ -4,29 +4,98 @@ import ( "fance/app/tasks/managers" "github.com/labstack/echo/v4" "net/http" + "strconv" ) type Rest struct { - Reader managers.Reader - Writer managers.Writer + reader managers.Reader + writer managers.Writer } func NewRest(reader managers.Reader, writer managers.Writer) *Rest { - return &Rest{Reader: reader, Writer: writer} + return &Rest{reader: reader, writer: writer} } func (r *Rest) GetAll(c echo.Context) error { - return c.String(http.StatusOK, "Get, all!") + page, limit := r.getPageAndLimit(c) + tasks, err := r.reader.RetrieveAll(c.Request().Context(), page, limit) + if err != nil { + return err + } + return c.JSON(http.StatusOK, r.mapTasksInfoToGetAll(tasks, page, limit)) +} + +func (r *Rest) mapTasksInfoToGetAll(taskInfo []managers.TaskInfo, page, limit int) TasksGetAll { + result := TasksGetAll{ + Page: page, + Limit: limit, + } + tasks := make([]TaskResponse, len(taskInfo)) + for i, t := range taskInfo { + tasks[i] = *r.mapTaskInfoToResponse(&t) + } + result.Tasks = tasks + return result +} + +func (r *Rest) getPageAndLimit(c echo.Context) (int, int) { + p := c.QueryParam("page") + l := c.QueryParam("limit") + if p == "" || l == "" { + return 1, 10 + } + page, _ := strconv.Atoi(p) + limit, _ := strconv.Atoi(l) + return page, limit } func (r *Rest) PostTask(c echo.Context) error { - return c.String(http.StatusOK, "post, task!") + t := new(TaskRequest) + if err := c.Bind(t); err != nil { + return err + } + task, err := r.writer.Create(c.Request().Context(), r.mapRequestToTaskInfo(t)) + if err != nil { + return c.JSON(http.StatusConflict, ErrorRest{Msg: err.Error()}) + } + return c.JSON(http.StatusCreated, r.mapTaskInfoToResponse(task)) } func (r *Rest) PutTask(c echo.Context) error { - return c.String(http.StatusOK, "put task!") + t := new(TaskRequest) + if err := c.Bind(t); err != nil { + return err + } + info := r.mapRequestToTaskInfo(t) + info.Id = c.Param("id") + task, err := r.writer.Update(c.Request().Context(), info) + if err != nil { + return c.JSON(http.StatusConflict, ErrorRest{Msg: err.Error()}) + } + return c.JSON(http.StatusOK, r.mapTaskInfoToResponse(task)) } func (r *Rest) DeleteTask(c echo.Context) error { - return c.String(http.StatusOK, "delete task!") + id := c.Param("id") + if err := r.writer.Delete(c.Request().Context(), id); err != nil { + return c.JSON(http.StatusConflict, ErrorRest{Msg: err.Error()}) + } + return c.JSON(http.StatusNoContent, "") +} + +func (r *Rest) mapRequestToTaskInfo(request *TaskRequest) *managers.TaskInfo { + return &managers.TaskInfo{ + Title: request.Title, + Description: request.Description, + Status: request.Status, + } +} + +func (r *Rest) mapTaskInfoToResponse(task *managers.TaskInfo) *TaskResponse { + return &TaskResponse{ + Id: task.Id, + Title: task.Title, + Description: task.Description, + Status: task.Status, + } } diff --git a/app/tasks/rest/structs.go b/app/tasks/rest/structs.go new file mode 100644 index 0000000..bccaf76 --- /dev/null +++ b/app/tasks/rest/structs.go @@ -0,0 +1,24 @@ +package rest + +type TaskRequest struct { + Title string `json:"title"` + Description string `json:"description"` + Status string `json:"status"` +} + +type TaskResponse struct { + Id string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Status string `json:"status"` +} + +type ErrorRest struct { + Msg string `json:"msg"` +} + +type TasksGetAll struct { + Tasks []TaskResponse `json:"tasks"` + Page int `json:"page"` + Limit int `json:"limit"` +} From acddfa09d877759893f6700344a9f5ae78803b23 Mon Sep 17 00:00:00 2001 From: Daniel Mendoza Date: Tue, 21 Jan 2020 23:31:04 -0500 Subject: [PATCH 2/2] tests: rest tests and fix sql repository test --- app/tasks/managers/mocks/Reader.go | 35 ++++++ app/tasks/managers/mocks/Writer.go | 72 +++++++++++ .../repositories/sql_tasks_repository_test.go | 2 +- app/tasks/rest/rest_test.go | 114 ++++++++++++++++++ 4 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 app/tasks/managers/mocks/Reader.go create mode 100644 app/tasks/managers/mocks/Writer.go create mode 100644 app/tasks/rest/rest_test.go diff --git a/app/tasks/managers/mocks/Reader.go b/app/tasks/managers/mocks/Reader.go new file mode 100644 index 0000000..f40f533 --- /dev/null +++ b/app/tasks/managers/mocks/Reader.go @@ -0,0 +1,35 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import context "context" +import managers "fance/app/tasks/managers" +import mock "github.com/stretchr/testify/mock" + +// Reader is an autogenerated mock type for the Reader type +type Reader struct { + mock.Mock +} + +// RetrieveAll provides a mock function with given fields: ctx, page, limit +func (_m *Reader) RetrieveAll(ctx context.Context, page int, limit int) ([]managers.TaskInfo, error) { + ret := _m.Called(ctx, page, limit) + + var r0 []managers.TaskInfo + if rf, ok := ret.Get(0).(func(context.Context, int, int) []managers.TaskInfo); ok { + r0 = rf(ctx, page, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]managers.TaskInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, int, int) error); ok { + r1 = rf(ctx, page, limit) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/app/tasks/managers/mocks/Writer.go b/app/tasks/managers/mocks/Writer.go new file mode 100644 index 0000000..fac908b --- /dev/null +++ b/app/tasks/managers/mocks/Writer.go @@ -0,0 +1,72 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import context "context" +import managers "fance/app/tasks/managers" +import mock "github.com/stretchr/testify/mock" + +// Writer is an autogenerated mock type for the Writer type +type Writer struct { + mock.Mock +} + +// Create provides a mock function with given fields: ctx, task +func (_m *Writer) Create(ctx context.Context, task *managers.TaskInfo) (*managers.TaskInfo, error) { + ret := _m.Called(ctx, task) + + var r0 *managers.TaskInfo + if rf, ok := ret.Get(0).(func(context.Context, *managers.TaskInfo) *managers.TaskInfo); ok { + r0 = rf(ctx, task) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*managers.TaskInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *managers.TaskInfo) error); ok { + r1 = rf(ctx, task) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Delete provides a mock function with given fields: ctx, taskID +func (_m *Writer) Delete(ctx context.Context, taskID string) error { + ret := _m.Called(ctx, taskID) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, taskID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Update provides a mock function with given fields: ctx, task +func (_m *Writer) Update(ctx context.Context, task *managers.TaskInfo) (*managers.TaskInfo, error) { + ret := _m.Called(ctx, task) + + var r0 *managers.TaskInfo + if rf, ok := ret.Get(0).(func(context.Context, *managers.TaskInfo) *managers.TaskInfo); ok { + r0 = rf(ctx, task) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*managers.TaskInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *managers.TaskInfo) error); ok { + r1 = rf(ctx, task) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/app/tasks/repositories/sql_tasks_repository_test.go b/app/tasks/repositories/sql_tasks_repository_test.go index aa9fcb3..e6dd155 100644 --- a/app/tasks/repositories/sql_tasks_repository_test.go +++ b/app/tasks/repositories/sql_tasks_repository_test.go @@ -46,7 +46,7 @@ var ( func (suite *sqlRepositorySuite) TestSqlTasksRepository_GetAll() { tasksReply := getTasksDbReply() mocket.Catcher.Reset().NewMock(). - WithQuery(`SELECT * FROM "db_tasks" WHERE (id > 0) ORDER BY id desc LIMIT 1 OFFSET 0`). + WithQuery(`SELECT * FROM "db_tasks" WHERE (id is not null) ORDER BY id desc LIMIT 1 OFFSET 0`). WithReply(tasksReply) tasks, err := suite.repository.GetAll(suite.ctx, 1, 1) suite.NoError(err) diff --git a/app/tasks/rest/rest_test.go b/app/tasks/rest/rest_test.go new file mode 100644 index 0000000..1a3a4ab --- /dev/null +++ b/app/tasks/rest/rest_test.go @@ -0,0 +1,114 @@ +package rest + +import ( + "context" + "fance/app/tasks/managers" + "fance/app/tasks/managers/mocks" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +type restSuite struct { + suite.Suite + ctx context.Context + reader *mocks.Reader + writer *mocks.Writer + rest *Rest +} + +func (suite *restSuite) SetupTest() { + suite.ctx = context.Background() + suite.reader = new(mocks.Reader) + suite.writer = new(mocks.Writer) + suite.rest = NewRest(suite.reader, suite.writer) +} + +func TestRestSuite(t *testing.T) { + suite.Run(t, &restSuite{}) +} + +var ( + taskInfo0 = managers.TaskInfo{ + Id: "d1b9e7b2-923d-43b2-a1b0-63a96a02663f", + Title: "My Title", + Description: "As a PM...", + Status: "DOING", + } + tasksInfo0 = []managers.TaskInfo{taskInfo0} + + getAllJSON = `{"tasks":[{"id":"d1b9e7b2-923d-43b2-a1b0-63a96a02663f","title":"My Title","description":"As a PM...","status":"DOING"}],"page":1,"limit":10} +` + taskInfo1 = managers.TaskInfo{ + Id: "d1b9e7b2-923d-43b2-a1b0-63a96a02663f", + Title: "Unit Tests", + Description: "As a Android developer...", + Status: "TODO", + } + taskJSON = `{ "title": "Unit Tests", "description": "As a Android developer...", "status": "TODO" }` + +) + +func (suite *restSuite) TestRest_GetAll() { + suite.reader.Mock.On("RetrieveAll", mock.Anything, mock.Anything, mock.Anything).Return(tasksInfo0, nil).Once() + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks", strings.NewReader("")) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + err := suite.rest.GetAll(c) + suite.NoError(err) + suite.Equal(http.StatusOK, rec.Code) + suite.Equal(getAllJSON, rec.Body.String()) +} + + +func (suite *restSuite) TestRest_PostTask() { + suite.writer.Mock.On("Create", mock.Anything, mock.Anything).Return(&taskInfo1, nil).Once() + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/api/v1/tasks", strings.NewReader(taskJSON)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + err := suite.rest.PostTask(c) + expected := `{"id":"d1b9e7b2-923d-43b2-a1b0-63a96a02663f","title":"Unit Tests","description":"As a Android developer...","status":"TODO"} +` + suite.NoError(err) + suite.Equal(http.StatusCreated, rec.Code) + suite.Equal(expected, rec.Body.String()) +} + +func (suite *restSuite) TestRest_PutTask() { + suite.writer.Mock.On("Update", mock.Anything, mock.Anything).Return(&taskInfo1, nil).Once() + + e := echo.New() + req := httptest.NewRequest(http.MethodPut, "/api/v1/tasks/" + taskInfo1.Id, strings.NewReader(taskJSON)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + err := suite.rest.PutTask(c) + expected := `{"id":"d1b9e7b2-923d-43b2-a1b0-63a96a02663f","title":"Unit Tests","description":"As a Android developer...","status":"TODO"} +` + suite.NoError(err) + suite.Equal(http.StatusOK, rec.Code) + suite.Equal(expected, rec.Body.String()) +} + +func (suite *restSuite) TestRest_DeleteTask() { + suite.writer.Mock.On("Delete", mock.Anything, mock.Anything).Return(nil).Once() + + e := echo.New() + req := httptest.NewRequest(http.MethodDelete, "/api/v1/tasks/" + taskInfo1.Id, strings.NewReader("")) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + err := suite.rest.DeleteTask(c) + suite.NoError(err) + suite.Equal(http.StatusNoContent, rec.Code) +}