Skip to content

Commit

Permalink
feat: adding update
Browse files Browse the repository at this point in the history
  • Loading branch information
wellingtonlope committed Aug 1, 2024
1 parent 2c80820 commit 6c4a00a
Show file tree
Hide file tree
Showing 6 changed files with 526 additions and 0 deletions.
10 changes: 10 additions & 0 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func main() {
fx.As(new(todo.GetAllStore)),
fx.As(new(todo.GetByIDStore)),
fx.As(new(todo.DeleteByIDStore)),
fx.As(new(todo.UpdateStore)),
),
fx.Annotate(
todo.NewCreate,
Expand All @@ -79,6 +80,10 @@ func main() {
todo.NewDeleteByID,
fx.As(new(todo.DeleteByID)),
),
fx.Annotate(
todo.NewUpdate,
fx.As(new(todo.Update)),
),
fx.Annotate(
handler.NewTodoCreate,
fx.As(new(handler.Handler)),
Expand All @@ -99,6 +104,11 @@ func main() {
fx.As(new(handler.Handler)),
fx.ResultTags(`group:"handlers"`),
),
fx.Annotate(
handler.NewTodoUpdate,
fx.As(new(handler.Handler)),
fx.ResultTags(`group:"handlers"`),
),
),
fx.Invoke(
fx.Annotate(
Expand Down
84 changes: 84 additions & 0 deletions internal/app/usecase/todo/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package todo

import (
"context"
"errors"
"fmt"
"time"

"github.com/wellingtonlope/todo-api/internal/app/usecase"
"github.com/wellingtonlope/todo-api/internal/domain"
"github.com/wellingtonlope/todo-api/pkg/clock"
)

var ErrUpdateStoreNotFound = errors.New("todo not found by ID")

type (
UpdateInput struct {
ID string
Title string
Description string
}
UpdateOutput struct {
ID string
Title string
Description string
CreatedAt time.Time
UpdatedAt time.Time
}
UpdateStore interface {
GetByID(ctx context.Context, id string) (domain.Todo, error)
Update(ctx context.Context, todo domain.Todo) error
}
Update interface {
Handle(context.Context, UpdateInput) (UpdateOutput, error)
}
update struct {
store UpdateStore
clock clock.Client
}
)

func NewUpdate(store UpdateStore, clock clock.Client) *update {
return &update{
store: store,
clock: clock,
}
}

func (uc *update) Handle(ctx context.Context, input UpdateInput) (UpdateOutput, error) {
todo, err := uc.store.GetByID(ctx, input.ID)
if err != nil {
if errors.Is(err, ErrGetByIDStoreNotFound) {
return UpdateOutput{}, usecase.NewError(fmt.Sprintf("todo not found with id %s", input.ID),
err, usecase.ErrorTypeNotFound)
}
return UpdateOutput{}, usecase.NewError("fail to get a todo by id",
err, usecase.ErrorTypeInternalError)

}
todo, err = todo.Update(input.Title, input.Description, uc.clock.Now())
if err != nil {
return UpdateOutput{}, usecase.NewError(err.Error(), err, usecase.ErrorTypeBadRequest)
}
err = uc.store.Update(ctx, todo)
if err != nil {
if errors.Is(err, ErrUpdateStoreNotFound) {
return UpdateOutput{}, usecase.NewError(fmt.Sprintf("todo not found with id %s", input.ID),
err, usecase.ErrorTypeNotFound)
}
return UpdateOutput{}, usecase.NewError("fail to update a todo in the store", err,
usecase.ErrorTypeInternalError)
}
return uc.domainTodoToOutput(todo), nil
}

func (uc *update) domainTodoToOutput(todo domain.Todo) UpdateOutput {
return UpdateOutput{
ID: todo.ID,
Title: todo.Title,
Description: todo.Description,
CreatedAt: todo.CreatedAt,
UpdatedAt: todo.UpdatedAt,
}
}
234 changes: 234 additions & 0 deletions internal/app/usecase/todo/update_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package todo_test

import (
"context"
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/wellingtonlope/todo-api/internal/app/usecase"
"github.com/wellingtonlope/todo-api/internal/app/usecase/todo"
"github.com/wellingtonlope/todo-api/internal/domain"
"github.com/wellingtonlope/todo-api/pkg/clock"
)

func TestUpdate_Handle(t *testing.T) {
exampleDate, _ := time.Parse(time.DateOnly, "2024-01-01")
exampleDateUpdated, _ := time.Parse(time.DateOnly, "2024-01-02")
testCases := []struct {
name string
updateStore *updateStoreMock
clock *clock.ClientMock
ctx context.Context
input todo.UpdateInput
result todo.UpdateOutput
err error
}{
{
name: "should fail when todo not found",
updateStore: func() *updateStoreMock {
m := new(updateStoreMock)
m.On("GetByID", context.TODO(), "123").
Return(domain.Todo{}, todo.ErrGetByIDStoreNotFound).Once()
return m
}(),
clock: clock.NewClientMock(),
ctx: context.TODO(),
input: todo.UpdateInput{
ID: "123",
Title: "example title updated",
Description: "example description updated",
},
result: todo.UpdateOutput{},
err: usecase.NewError("todo not found with id 123",
todo.ErrGetByIDStoreNotFound, usecase.ErrorTypeNotFound),
},
{
name: "should fail when get by id fails",
updateStore: func() *updateStoreMock {
m := new(updateStoreMock)
m.On("GetByID", context.TODO(), "123").
Return(domain.Todo{}, assert.AnError).Once()
return m
}(),
clock: clock.NewClientMock(),
ctx: context.TODO(),
input: todo.UpdateInput{
ID: "123",
Title: "example title updated",
Description: "example description updated",
},
result: todo.UpdateOutput{},
err: usecase.NewError("fail to get a todo by id",
assert.AnError, usecase.ErrorTypeInternalError),
},
{
name: "should fail when input is invalid",
updateStore: func() *updateStoreMock {
m := new(updateStoreMock)
m.On("GetByID", context.TODO(), "123").
Return(domain.Todo{
ID: "123",
Title: "example title",
Description: "example description",
CreatedAt: exampleDate,
UpdatedAt: exampleDate,
}, nil).Once()
return m
}(),
clock: func() *clock.ClientMock {
m := clock.NewClientMock()
m.On("Now").Return(exampleDateUpdated).Once()
return m
}(),
ctx: context.TODO(),
input: todo.UpdateInput{
ID: "123",
Title: "",
Description: "example description updated",
},
result: todo.UpdateOutput{},
err: usecase.NewError(fmt.Errorf("%w: title", domain.ErrTodoInvalidInput).Error(),
fmt.Errorf("%w: title", domain.ErrTodoInvalidInput), usecase.ErrorTypeBadRequest),
},
{
name: "should fail when update todo not found",
updateStore: func() *updateStoreMock {
m := new(updateStoreMock)
m.On("GetByID", context.TODO(), "123").
Return(domain.Todo{
ID: "123",
Title: "example title",
Description: "example description",
CreatedAt: exampleDate,
UpdatedAt: exampleDate,
}, nil).Once()
m.On("Update", context.TODO(), domain.Todo{
ID: "123",
Title: "example title updated",
Description: "example description updated",
CreatedAt: exampleDate,
UpdatedAt: exampleDateUpdated,
}).Return(todo.ErrUpdateStoreNotFound).Once()
return m
}(),
clock: func() *clock.ClientMock {
m := clock.NewClientMock()
m.On("Now").Return(exampleDateUpdated).Once()
return m
}(),
ctx: context.TODO(),
input: todo.UpdateInput{
ID: "123",
Title: "example title updated",
Description: "example description updated",
},
result: todo.UpdateOutput{},
err: usecase.NewError("todo not found with id 123",
todo.ErrUpdateStoreNotFound, usecase.ErrorTypeNotFound),
},
{
name: "should fail when update store fails",
updateStore: func() *updateStoreMock {
m := new(updateStoreMock)
m.On("GetByID", context.TODO(), "123").
Return(domain.Todo{
ID: "123",
Title: "example title",
Description: "example description",
CreatedAt: exampleDate,
UpdatedAt: exampleDate,
}, nil).Once()
m.On("Update", context.TODO(), domain.Todo{
ID: "123",
Title: "example title updated",
Description: "example description updated",
CreatedAt: exampleDate,
UpdatedAt: exampleDateUpdated,
}).Return(assert.AnError).Once()
return m
}(),
clock: func() *clock.ClientMock {
m := clock.NewClientMock()
m.On("Now").Return(exampleDateUpdated).Once()
return m
}(),
ctx: context.TODO(),
input: todo.UpdateInput{
ID: "123",
Title: "example title updated",
Description: "example description updated",
},
result: todo.UpdateOutput{},
err: usecase.NewError("fail to update a todo in the store", assert.AnError,
usecase.ErrorTypeInternalError),
},
{
name: "should update todo",
updateStore: func() *updateStoreMock {
m := new(updateStoreMock)
m.On("GetByID", context.TODO(), "123").
Return(domain.Todo{
ID: "123",
Title: "example title",
Description: "example description",
CreatedAt: exampleDate,
UpdatedAt: exampleDate,
}, nil).Once()
m.On("Update", context.TODO(), domain.Todo{
ID: "123",
Title: "example title updated",
Description: "example description updated",
CreatedAt: exampleDate,
UpdatedAt: exampleDateUpdated,
}).Return(nil).Once()
return m
}(),
clock: func() *clock.ClientMock {
m := clock.NewClientMock()
m.On("Now").Return(exampleDateUpdated).Once()
return m
}(),
ctx: context.TODO(),
input: todo.UpdateInput{
ID: "123",
Title: "example title updated",
Description: "example description updated",
},
result: todo.UpdateOutput{
ID: "123",
Title: "example title updated",
Description: "example description updated",
CreatedAt: exampleDate,
UpdatedAt: exampleDateUpdated,
},
err: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
uc := todo.NewUpdate(tc.updateStore, tc.clock)
result, err := uc.Handle(tc.ctx, tc.input)
assert.Equal(t, tc.result, result)
assert.Equal(t, tc.err, err)
tc.updateStore.AssertExpectations(t)
tc.clock.AssertExpectations(t)
})
}
}

type updateStoreMock struct {
mock.Mock
}

func (m *updateStoreMock) GetByID(ctx context.Context, id string) (domain.Todo, error) {
args := m.Called(ctx, id)
return args.Get(0).(domain.Todo), args.Error(1)
}

func (m *updateStoreMock) Update(ctx context.Context, todo domain.Todo) error {
args := m.Called(ctx, todo)
return args.Error(0)
}
Loading

0 comments on commit 6c4a00a

Please sign in to comment.