diff --git a/README.md b/README.md index e3d5502..fb1b4aa 100644 --- a/README.md +++ b/README.md @@ -77,17 +77,16 @@ The DIP promotes high-level modules (e.g., use cases) to depend on abstractions - [ ] Request ID in logger (Header `X-Request-Id: xxx`) - RESTful - [x] Create Resource (`POST` verb) - - [ ] Update Resource (`PUT` verb) - - [ ] Partially Update Resource (`PATCH` verb) + - [x] Update Resource (`PUT` verb) + - [x] Partially Update Resource (`PATCH` verb) - [ ] Find Resource (`GET` verb) - [ ] Offset Pagination (Query param `?limit=10&page=1`) - [ ] Sorting (Query param `?sort=fullname DESC,id DESC`) - - [x] Delete resource (`DELETE` verb, idempotent) - Testing - - [ ] Table Driven Test - - [ ] Mocking + - [x] Table Driven Test + - [x] Mocking - Others - - [ ] Database migration and seed tool + - [x] Database migration and seed tool - [ ] Releaser ## How To Run diff --git a/go.mod b/go.mod index 0ed2944..0ac213a 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,19 @@ module github.com/DoWithLogic/golang-clean-architecture go 1.20 require ( + github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/go-sql-driver/mysql v1.7.1 github.com/invopop/validation v0.3.0 github.com/jmoiron/sqlx v1.3.5 github.com/labstack/echo/v4 v4.11.1 github.com/rs/zerolog v1.30.0 github.com/spf13/viper v1.16.0 + github.com/stretchr/testify v1.8.4 + go.uber.org/mock v0.3.0 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/labstack/gommon v0.4.0 // indirect @@ -22,6 +26,7 @@ require ( github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/go.sum b/go.sum index 759f52e..dcaa458 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -196,8 +198,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -215,6 +218,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= +go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/internal/users/delivery/http/handler/handler.go b/internal/users/delivery/http/handler/handler.go index f13ca39..0567749 100644 --- a/internal/users/delivery/http/handler/handler.go +++ b/internal/users/delivery/http/handler/handler.go @@ -69,7 +69,12 @@ func (h *handlers) CreateUser(c echo.Context) error { ) } - createdID, err := h.uc.CreateUser(ctx, &entities.Users{Fullname: payload.Fullname, PhoneNumber: payload.PhoneNumber}) + argsCreateUser := entities.CreateUser{ + FUllName: payload.FullName, + PhoneNumber: payload.PhoneNumber, + } + + createdID, err := h.uc.CreateUser(ctx, argsCreateUser) if err != nil { return c.JSON(http.StatusInternalServerError, dtos.NewResponseError( http.StatusInternalServerError, @@ -125,14 +130,14 @@ func (h *handlers) UpdateUser(c echo.Context) error { ) } - updateArgs := &entities.UpdateUsers{ + argsUpdateUser := entities.UpdateUsers{ UserID: userID, Fullname: payload.Fullname, PhoneNumber: payload.Fullname, UserType: payload.UserType, } - err = h.uc.UpdateUser(ctx, updateArgs) + err = h.uc.UpdateUser(ctx, argsUpdateUser) if err != nil { return c.JSON(http.StatusBadRequest, dtos.NewResponseError( http.StatusInternalServerError, @@ -178,7 +183,12 @@ func (h *handlers) UpdateUserStatus(c echo.Context) error { ) } - err = h.uc.UpdateUserStatus(ctx, &entities.UpdateUsers{UserID: userID, IsActive: payload.IsActive}) + argsUpdateUserStatus := entities.UpdateUserStatus{ + UserID: userID, + IsActive: payload.IsActive, + } + + err = h.uc.UpdateUserStatus(ctx, argsUpdateUserStatus) if err != nil { return c.JSON(http.StatusBadRequest, dtos.NewResponseError( http.StatusInternalServerError, diff --git a/internal/users/dtos/create_users.go b/internal/users/dtos/create_users.go index 89ee065..5671822 100644 --- a/internal/users/dtos/create_users.go +++ b/internal/users/dtos/create_users.go @@ -3,13 +3,13 @@ package dtos import "github.com/invopop/validation" type CreateUserPayload struct { - Fullname string `json:"fullname"` + FullName string `json:"fullname"` PhoneNumber string `json:"phone_number"` } func (cup CreateUserPayload) Validate() error { return validation.ValidateStruct(&cup, - validation.Field(&cup.Fullname, validation.Required, validation.Length(0, 50)), + validation.Field(&cup.FullName, validation.Required, validation.Length(0, 50)), validation.Field(&cup.PhoneNumber, validation.Required, validation.Length(0, 13)), ) } diff --git a/internal/users/entities/create_users.go b/internal/users/entities/create_users.go new file mode 100644 index 0000000..55633fb --- /dev/null +++ b/internal/users/entities/create_users.go @@ -0,0 +1,23 @@ +package entities + +import "time" + +type CreateUser struct { + FUllName string + PhoneNumber string + UserType string + IsActive bool + CreatedAt time.Time + CreatedBy string +} + +func NewCreateUser(data CreateUser) CreateUser { + return CreateUser{ + FUllName: data.FUllName, + PhoneNumber: data.PhoneNumber, + UserType: UserTypeRegular, + IsActive: true, + CreatedAt: time.Now(), + CreatedBy: "martin", + } +} diff --git a/internal/users/entities/update_user_status.go b/internal/users/entities/update_user_status.go new file mode 100644 index 0000000..1f6ec6d --- /dev/null +++ b/internal/users/entities/update_user_status.go @@ -0,0 +1,19 @@ +package entities + +import "time" + +type UpdateUserStatus struct { + UserID int64 + IsActive bool + UpdatedAt time.Time + UpdatedBy string +} + +func NewUpdateUserStatus(payload UpdateUserStatus) UpdateUserStatus { + return UpdateUserStatus{ + UserID: payload.UserID, + IsActive: payload.IsActive, + UpdatedAt: time.Now(), + UpdatedBy: "martin", + } +} diff --git a/internal/users/entities/update_users.go b/internal/users/entities/update_users.go index 9b2ee6c..cf317fe 100644 --- a/internal/users/entities/update_users.go +++ b/internal/users/entities/update_users.go @@ -7,18 +7,17 @@ type UpdateUsers struct { Fullname string PhoneNumber string UserType string - IsActive bool - UpdatedAt string + UpdatedAt time.Time UpdatedBy string } -func NewUpdateUsers(data UpdateUsers) *UpdateUsers { - return &UpdateUsers{ +func NewUpdateUsers(data UpdateUsers) UpdateUsers { + return UpdateUsers{ UserID: data.UserID, Fullname: data.Fullname, PhoneNumber: data.PhoneNumber, UserType: UserTypeRegular, - UpdatedAt: time.Now().Format("2006-01-02 15:04:05"), + UpdatedAt: time.Now(), UpdatedBy: "martin", } } diff --git a/internal/users/entities/users.go b/internal/users/entities/users.go index a2b73aa..ec65580 100644 --- a/internal/users/entities/users.go +++ b/internal/users/entities/users.go @@ -2,7 +2,6 @@ package entities import ( "errors" - "time" ) type ( @@ -40,14 +39,3 @@ func (locking *LockingOpt) Validate() error { return nil } - -func NewUser(data Users) *Users { - return &Users{ - Fullname: data.Fullname, - PhoneNumber: data.PhoneNumber, - UserType: UserTypeRegular, - IsActive: true, - CreatedAt: time.Now().Format("2006-01-02 15:04:05"), - CreatedBy: "martin", - } -} diff --git a/internal/users/mock/repository_mock.go b/internal/users/mock/repository_mock.go new file mode 100644 index 0000000..d764ad3 --- /dev/null +++ b/internal/users/mock/repository_mock.go @@ -0,0 +1,94 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: internal/users/repository/repository.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + entities "github.com/DoWithLogic/golang-clean-architecture/internal/users/entities" + gomock "go.uber.org/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// GetUserByID mocks base method. +func (m *MockRepository) GetUserByID(arg0 context.Context, arg1 int64, arg2 entities.LockingOpt) (entities.Users, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserByID", arg0, arg1, arg2) + ret0, _ := ret[0].(entities.Users) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserByID indicates an expected call of GetUserByID. +func (mr *MockRepositoryMockRecorder) GetUserByID(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByID", reflect.TypeOf((*MockRepository)(nil).GetUserByID), arg0, arg1, arg2) +} + +// SaveNewUser mocks base method. +func (m *MockRepository) SaveNewUser(arg0 context.Context, arg1 entities.Users) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SaveNewUser", arg0, arg1) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SaveNewUser indicates an expected call of SaveNewUser. +func (mr *MockRepositoryMockRecorder) SaveNewUser(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveNewUser", reflect.TypeOf((*MockRepository)(nil).SaveNewUser), arg0, arg1) +} + +// UpdateUserByID mocks base method. +func (m *MockRepository) UpdateUserByID(arg0 context.Context, arg1 entities.UpdateUsers) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateUserByID", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateUserByID indicates an expected call of UpdateUserByID. +func (mr *MockRepositoryMockRecorder) UpdateUserByID(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserByID", reflect.TypeOf((*MockRepository)(nil).UpdateUserByID), arg0, arg1) +} + +// UpdateUserStatusByID mocks base method. +func (m *MockRepository) UpdateUserStatusByID(arg0 context.Context, arg1 entities.UpdateUserStatus) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateUserStatusByID", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateUserStatusByID indicates an expected call of UpdateUserStatusByID. +func (mr *MockRepositoryMockRecorder) UpdateUserStatusByID(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserStatusByID", reflect.TypeOf((*MockRepository)(nil).UpdateUserStatusByID), arg0, arg1) +} diff --git a/internal/users/mock/usecase_mock.go b/internal/users/mock/usecase_mock.go new file mode 100644 index 0000000..e44fd3a --- /dev/null +++ b/internal/users/mock/usecase_mock.go @@ -0,0 +1,79 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: internal/users/usecase/usecase.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + entities "github.com/DoWithLogic/golang-clean-architecture/internal/users/entities" + gomock "go.uber.org/mock/gomock" +) + +// MockUsecase is a mock of Usecase interface. +type MockUsecase struct { + ctrl *gomock.Controller + recorder *MockUsecaseMockRecorder +} + +// MockUsecaseMockRecorder is the mock recorder for MockUsecase. +type MockUsecaseMockRecorder struct { + mock *MockUsecase +} + +// NewMockUsecase creates a new mock instance. +func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { + mock := &MockUsecase{ctrl: ctrl} + mock.recorder = &MockUsecaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { + return m.recorder +} + +// CreateUser mocks base method. +func (m *MockUsecase) CreateUser(ctx context.Context, user entities.Users) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateUser", ctx, user) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateUser indicates an expected call of CreateUser. +func (mr *MockUsecaseMockRecorder) CreateUser(ctx, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockUsecase)(nil).CreateUser), ctx, user) +} + +// UpdateUser mocks base method. +func (m *MockUsecase) UpdateUser(ctx context.Context, updateData entities.UpdateUsers) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateUser", ctx, updateData) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateUser indicates an expected call of UpdateUser. +func (mr *MockUsecaseMockRecorder) UpdateUser(ctx, updateData interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockUsecase)(nil).UpdateUser), ctx, updateData) +} + +// UpdateUserStatus mocks base method. +func (m *MockUsecase) UpdateUserStatus(ctx context.Context, req entities.UpdateUserStatus) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateUserStatus", ctx, req) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateUserStatus indicates an expected call of UpdateUserStatus. +func (mr *MockUsecaseMockRecorder) UpdateUserStatus(ctx, req interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserStatus", reflect.TypeOf((*MockUsecase)(nil).UpdateUserStatus), ctx, req) +} diff --git a/internal/users/repository/repository.go b/internal/users/repository/repository.go index a433abe..50b9b2c 100644 --- a/internal/users/repository/repository.go +++ b/internal/users/repository/repository.go @@ -13,10 +13,10 @@ import ( type ( Repository interface { - SaveNewUser(context.Context, *entities.Users) (int64, error) - UpdateUserByID(context.Context, *entities.UpdateUsers) error + SaveNewUser(context.Context, entities.CreateUser) (int64, error) + UpdateUserByID(context.Context, entities.UpdateUsers) error GetUserByID(context.Context, int64, entities.LockingOpt) (entities.Users, error) - UpdateUserStatusByID(context.Context, *entities.Users) error + UpdateUserStatusByID(context.Context, entities.UpdateUserStatus) error } repository struct { @@ -33,9 +33,9 @@ func NewRepository(conn database.SQLTxConn, log *zerolog.Logger) Repository { return &repository{conn, log} } -func (repo *repository) SaveNewUser(ctx context.Context, user *entities.Users) (int64, error) { +func (repo *repository) SaveNewUser(ctx context.Context, user entities.CreateUser) (int64, error) { args := custom.Array{ - user.Fullname, + user.FUllName, user.PhoneNumber, user.IsActive, user.UserType, @@ -54,7 +54,7 @@ func (repo *repository) SaveNewUser(ctx context.Context, user *entities.Users) ( return userID, err } -func (repo *repository) UpdateUserByID(ctx context.Context, user *entities.UpdateUsers) error { +func (repo *repository) UpdateUserByID(ctx context.Context, user entities.UpdateUsers) error { args := custom.Array{ user.Fullname, user.Fullname, user.PhoneNumber, user.PhoneNumber, @@ -114,7 +114,7 @@ func (repo *repository) GetUserByID(ctx context.Context, userID int64, lockOpt e return userData, err } -func (repo *repository) UpdateUserStatusByID(ctx context.Context, req *entities.Users) error { +func (repo *repository) UpdateUserStatusByID(ctx context.Context, req entities.UpdateUserStatus) error { args := custom.Array{ req.IsActive, req.UpdatedAt, @@ -122,12 +122,17 @@ func (repo *repository) UpdateUserStatusByID(ctx context.Context, req *entities. req.UserID, } - err := new(database.SQL).Exec(repo.conn.ExecContext(ctx, repository_query.UpdateUserStatusByID, args...)).Scan(nil, nil) + var updatedID int64 + err := new(database.SQL).Exec(repo.conn.ExecContext(ctx, repository_query.UpdateUserStatusByID, args...)).Scan(nil, &updatedID) if err != nil { repo.log.Z().Err(err).Msg("[repository]UpdateUserStatusByID.ExecContext") return err } + if updatedID != req.UserID { + return errors.New("user not found") + } + return nil } diff --git a/internal/users/repository/repository_test.go b/internal/users/repository/repository_test.go new file mode 100644 index 0000000..abae007 --- /dev/null +++ b/internal/users/repository/repository_test.go @@ -0,0 +1,82 @@ +package repository_test + +import ( + "context" + "os" + "testing" + "time" + + sqlmock "github.com/DATA-DOG/go-sqlmock" + "github.com/DoWithLogic/golang-clean-architecture/internal/users/entities" + "github.com/DoWithLogic/golang-clean-architecture/internal/users/repository" + "github.com/DoWithLogic/golang-clean-architecture/internal/users/repository/repository_query" + "github.com/DoWithLogic/golang-clean-architecture/pkg/otel/zerolog" + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func Test_repository_UpdateUserByID(t *testing.T) { + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + defer db.Close() + + var ( + conn = sqlx.NewDb(db, "sqlmock") + repo = repository.NewRepository(conn, zerolog.NewZeroLog(context.Background(), os.Stdout)) + ) + + currentTime := time.Now() + userID := int64(1) + + t.Run("UpdateUserByID_positive_case", func(t *testing.T) { + user := entities.CreateUser{ + FUllName: "martin yonatan pasaribu", + PhoneNumber: "08121213131414", + UserType: entities.UserTypePremium, + IsActive: true, + CreatedAt: currentTime, + CreatedBy: "admin", + } + + mock. + ExpectExec(repository_query.InsertUsers). + WithArgs( + user.FUllName, + user.PhoneNumber, + user.IsActive, + user.UserType, + currentTime, + user.CreatedBy, + ).WillReturnResult(sqlmock.NewResult(userID, 1)) + + userID, err := repo.SaveNewUser(context.Background(), user) + assert.NoError(t, err) + assert.NotEmpty(t, userID) + + argsUpdateUser := entities.UpdateUsers{ + UserID: userID, + Fullname: "edited name", + PhoneNumber: "081122334455", + UpdatedAt: currentTime, + UpdatedBy: "admin", + } + + mock.ExpectExec(repository_query.UpdateUsers). + WithArgs( + argsUpdateUser.Fullname, argsUpdateUser.Fullname, + argsUpdateUser.PhoneNumber, argsUpdateUser.PhoneNumber, + argsUpdateUser.UserType, argsUpdateUser.UserType, + argsUpdateUser.UpdatedAt, + argsUpdateUser.UpdatedBy, + argsUpdateUser.UserID, + ).WillReturnResult(sqlmock.NewResult(userID, 1)) + + err = repo.UpdateUserByID(context.Background(), argsUpdateUser) + require.NoError(t, err) + }) + +} diff --git a/internal/users/usecase/usecase.go b/internal/users/usecase/usecase.go index de57be2..0a470a0 100644 --- a/internal/users/usecase/usecase.go +++ b/internal/users/usecase/usecase.go @@ -2,8 +2,6 @@ package usecase import ( "context" - "fmt" - "time" "github.com/DoWithLogic/golang-clean-architecture/internal/users/entities" "github.com/DoWithLogic/golang-clean-architecture/internal/users/repository" @@ -14,9 +12,9 @@ import ( type ( Usecase interface { - CreateUser(context.Context, *entities.Users) (int64, error) - UpdateUser(context.Context, *entities.UpdateUsers) error - UpdateUserStatus(context.Context, *entities.UpdateUsers) error + CreateUser(ctx context.Context, user entities.CreateUser) (int64, error) + UpdateUser(ctx context.Context, updateData entities.UpdateUsers) error + UpdateUserStatus(ctx context.Context, req entities.UpdateUserStatus) error } usecase struct { @@ -30,8 +28,8 @@ func NewUseCase(repo repository.Repository, log *zerolog.Logger, txConn *sqlx.DB return &usecase{repo, log, txConn} } -func (uc *usecase) CreateUser(ctx context.Context, user *entities.Users) (int64, error) { - userID, err := uc.repo.SaveNewUser(ctx, entities.NewUser(*user)) +func (uc *usecase) CreateUser(ctx context.Context, payload entities.CreateUser) (int64, error) { + userID, err := uc.repo.SaveNewUser(ctx, entities.NewCreateUser(payload)) if err != nil { uc.log.Z().Err(err).Msg("[usecase]CreateUser.SaveNewUser") @@ -41,7 +39,7 @@ func (uc *usecase) CreateUser(ctx context.Context, user *entities.Users) (int64, return userID, nil } -func (uc *usecase) UpdateUser(ctx context.Context, updateData *entities.UpdateUsers) error { +func (uc *usecase) UpdateUser(ctx context.Context, updateData entities.UpdateUsers) error { return func(dbTx *sqlx.DB) error { txConn, err := uc.dbTx.BeginTx(ctx, nil) if err != nil { @@ -61,7 +59,7 @@ func (uc *usecase) UpdateUser(ctx context.Context, updateData *entities.UpdateUs return err } - if err = repoTx.UpdateUserByID(ctx, entities.NewUpdateUsers(*updateData)); err != nil { + if err = repoTx.UpdateUserByID(ctx, entities.NewUpdateUsers(updateData)); err != nil { uc.log.Z().Err(err).Msg("[usecase]UpdateUser.UpdateUserByID") return err } @@ -70,24 +68,18 @@ func (uc *usecase) UpdateUser(ctx context.Context, updateData *entities.UpdateUs }(uc.dbTx) } -func (uc *usecase) UpdateUserStatus(ctx context.Context, req *entities.UpdateUsers) error { - userDetail, err := uc.repo.GetUserByID(ctx, req.UserID, entities.LockingOpt{}) +func (uc *usecase) UpdateUserStatus(ctx context.Context, req entities.UpdateUserStatus) error { + _, err := uc.repo.GetUserByID(ctx, req.UserID, entities.LockingOpt{}) if err != nil { uc.log.Z().Err(err).Msg("[usecase]UpdateUserStatus.GetUserByID") - return err - } - - fmt.Println("user_id", userDetail.UserID) - updateStatusArgs := &entities.Users{ - UserID: userDetail.UserID, - IsActive: req.IsActive, - UpdatedAt: time.Now().Format("2006-01-02 15:04:05"), - UpdatedBy: "martin", + return err } - if err := uc.repo.UpdateUserStatusByID(ctx, updateStatusArgs); err != nil { + if err := uc.repo.UpdateUserStatusByID(ctx, entities.NewUpdateUserStatus(req)); err != nil { uc.log.Z().Err(err).Msg("[usecase]UpdateUserStatus.UpdateUserStatusByID") + + return err } return nil diff --git a/makefile b/makefile index ebec3f1..6421469 100644 --- a/makefile +++ b/makefile @@ -43,3 +43,9 @@ run: database-up migration-up local down : migration-down database-down +mock-repository: + mockgen -source internal/users/repository/repository.go -destination internal/users/mock/repository_mock.go -package=mocks + +mock-usecase: + mockgen -source internal/users/usecase/usecase.go -destination internal/users/mock/usecase_mock.go -package=mocks +