diff --git a/config/test.yml b/config/test.yml new file mode 100644 index 0000000..ad5df7c --- /dev/null +++ b/config/test.yml @@ -0,0 +1,7 @@ +debug: false +multi_word_var: "I'm complex in config.yml" +db: + host: "localhost" + username: "ali" + password: "passwd" + multi_word_nested_var: "WHAT??" \ No newline at end of file diff --git a/delivery/http_server/benefactor/kind_box_req/add.go b/delivery/http_server/benefactor/kind_box_req/add.go index 849b96c..a25fbb7 100644 --- a/delivery/http_server/benefactor/kind_box_req/add.go +++ b/delivery/http_server/benefactor/kind_box_req/add.go @@ -1,28 +1,36 @@ package benefactorkindboxreqhandler import ( + "fmt" "net/http" param "git.gocasts.ir/ebhomengo/niki/param/benefactor/kind_box_req" "git.gocasts.ir/ebhomengo/niki/pkg/claim" + errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg" httpmsg "git.gocasts.ir/ebhomengo/niki/pkg/http_msg" echo "github.com/labstack/echo/v4" ) func (h Handler) Add(c echo.Context) error { req := param.KindBoxReqAddRequest{} - if bErr := c.Bind(&req); bErr != nil { - return echo.NewHTTPError(http.StatusBadRequest) + if err := c.Bind(&req); err != nil { + fmt.Println("err", err, req) + + return c.JSON(http.StatusBadRequest, echo.Map{ + "message": errmsg.ErrBadRequest, + }) + // TODO: return echo.NewHTTPError(http.StatusBadRequest, errmsg.ErrBadRequest) ؟؟؟ } claims := claim.GetClaimsFromEchoContext(c) req.BenefactorID = claims.UserID - if fieldErrors, err := h.benefactorKindBoxReqVld.ValidateAddRequest(req); err != nil { - msg, code := httpmsg.Error(err) + result := h.benefactorKindBoxReqVld.ValidateAddRequest(req) + if result != nil { + msg, code := httpmsg.Error(result.Err) return c.JSON(code, echo.Map{ "message": msg, - "errors": fieldErrors, + "errors": result.Fields, }) } resp, sErr := h.benefactorKindBoxReqSvc.Add(c.Request().Context(), req) diff --git a/delivery/http_server/benefactor/kind_box_req/add_test.go b/delivery/http_server/benefactor/kind_box_req/add_test.go new file mode 100644 index 0000000..95732fa --- /dev/null +++ b/delivery/http_server/benefactor/kind_box_req/add_test.go @@ -0,0 +1,153 @@ +package benefactorkindboxreqhandler_test + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "git.gocasts.ir/ebhomengo/niki/delivery/http_server/middleware" + "git.gocasts.ir/ebhomengo/niki/entity" + "git.gocasts.ir/ebhomengo/niki/param/benefactor/address" + benefactoreparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/benefactore" + benefactorkindboxreqparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/kind_box_req" + testutils "git.gocasts.ir/ebhomengo/niki/test" + "git.gocasts.ir/ebhomengo/niki/test/seed" + "github.com/brianvoe/gofakeit/v6" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" +) + +func TestAdd(t *testing.T) { + testutils.SetupEnd2EndTest(t) + respSendOTP := testutils.SendOTP(t) + + loginOrRegisterRequest := benefactoreparam.LoginOrRegisterRequest{ + PhoneNumber: respSendOTP.PhoneNumber, + VerificationCode: respSendOTP.Code, + } + benefactor, cleanupBenefactor := testutils.CreateBenefactorWithSvc(t, loginOrRegisterRequest) + defer cleanupBenefactor() + benefactorAddAddressRequest := addressparam.BenefactorAddAddressRequest{ + PostalCode: gofakeit.Address().Zip, + Address: gofakeit.Address().Address, + Lat: float32(gofakeit.Address().Latitude), + Lon: float32(gofakeit.Address().Longitude), + CityID: gofakeit.UintRange(1, 100), + ProvinceID: gofakeit.UintRange(1, 31), + BenefactorID: benefactor.BenefactorInfo.ID, + } + address, cleanupAddress := testutils.CreateAddressWithSvc(t, benefactorAddAddressRequest) + defer cleanupAddress() + kindboxreqResponse, _ := json.Marshal(entity.KindBoxReq{ + ID: 1, + KindBoxType: entity.KindBoxCylindrical, + CountRequested: gofakeit.UintRange(1, 100), + CountAccepted: 0, + BenefactorID: benefactor.BenefactorInfo.ID, + Status: entity.KindBoxReqPendingStatus, + Description: "", + ReferDate: time.Time{}, + AddressID: address.Address.ID, + }) + + type testCase struct { + name string + requestBody interface{} + expectedStatus int + expectedBody string + err bool + token string + } + + testCases := []testCase{ + { + name: "invalid payload", + requestBody: `invalid payload`, + expectedStatus: http.StatusBadRequest, + expectedBody: `{"message": "Bad request"}`, + err: true, + token: "Bearer " + benefactor.Tokens.AccessToken, + }, + { + name: "invalid or expired jwt", + requestBody: benefactorkindboxreqparam.KindBoxReqAddRequest{ + TypeID: 1, + AddressID: address.Address.ID, + ReferDate: time.Now(), + CountRequested: 1, + }, + token: "Bearer 12" + benefactor.Tokens.AccessToken, + expectedStatus: http.StatusUnauthorized, + err: true, + expectedBody: `{"message":"invalid or expired jwt"}`, + }, + { + name: "Validation Failed", + requestBody: benefactorkindboxreqparam.KindBoxReqAddRequest{ + AddressID: address.Address.ID, + ReferDate: time.Now(), + CountRequested: 2, + }, + err: true, + token: "Bearer " + benefactor.Tokens.AccessToken, + expectedStatus: http.StatusUnprocessableEntity, + expectedBody: `{ + "errors":{ + "type_id":"cannot be blank" + }, + "message":"invalid input" + }`, + }, + { + name: "Added successfully", + requestBody: benefactorkindboxreqparam.KindBoxReqAddRequest{ + TypeID: 2, + AddressID: address.Address.ID, + ReferDate: time.Now(), + CountRequested: 2, + }, + token: "Bearer " + benefactor.Tokens.AccessToken, + expectedStatus: http.StatusCreated, + expectedBody: string(kindboxreqResponse), + }, + } + + e := echo.New() + r := e.Group("/benefactor/kindboxreqs") + + r.POST("/", testutils.BenefactorkindBoxReqHandler.Add, middleware.Auth(testutils.AuthSvc, testutils.AuthConfig), + middleware.BenefactorAuthorization(entity.UserBenefactorRole)) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + requestBody, _ := json.Marshal(tc.requestBody) + req := httptest.NewRequest(http.MethodPost, "/benefactor/kindboxreqs/", bytes.NewBuffer(requestBody)) + + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + req.Header.Set(echo.HeaderAuthorization, tc.token) + + rec := httptest.NewRecorder() + + e.ServeHTTP(rec, req) + + // Assertion + assert.Equal(t, tc.expectedStatus, rec.Code) + + if tc.err { + assert.JSONEq(t, tc.expectedBody, rec.Body.String()) + return + } + response := &benefactorkindboxreqparam.KindBoxReqAddResponse{} + err := json.Unmarshal( + rec.Body.Bytes(), + response, + ) + assert.Nil(t, err, "error in deserializing the request") + seed.DeleteBenefactor(t, testutils.MysqlRepo, response.KindBoxReq.ID) + assert.NotEmpty(t, response.KindBoxReq) + }) + } +} diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml new file mode 100644 index 0000000..d649945 --- /dev/null +++ b/docker-compose.dev.yaml @@ -0,0 +1,40 @@ +version: '3.9' + +services: + mysqltest: + image: mysql:8.0 + ports: + - "3305:3306" + container_name: niki-database-test + volumes: + - dbdatatest:/var/lib/mysql + restart: always + command: [ 'mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci' ] + environment: + MYSQL_ROOT_PASSWORD: 123456 + MYSQL_DATABASE: test_db + MYSQL_USER: testuser + MYSQL_PASSWORD: test1234 + + niki-redis-test: + image: bitnami/redis:6.2 + container_name: niki-redis-test + restart: always + ports: + - '6381:6379' + # TODO - remove `--save "" --appendonly no` from command to persist data + command: redis-server --loglevel warning --protected-mode no --save "" --appendonly no + environment: + - ALLOW_EMPTY_PASSWORD=yes + volumes: + - niki-redis-data-test:/data + + + +volumes: + dbdatatest: + niki-redis-data-test: + + + +# docker-compose -f docker-compose.dev.yaml up -d \ No newline at end of file diff --git a/docker-compose.yaml.back b/docker-compose.yaml.back deleted file mode 100644 index e71d74a..0000000 --- a/docker-compose.yaml.back +++ /dev/null @@ -1,37 +0,0 @@ -version: '3.9' - -services: - mysql: - platform: linux/amd64 - image: mysql:8.0 - ports: - - 3305:3305 - volumes: - - ~/apps/mysql:/var/lib/mysql - restart: always - hostname: mysql - container_name: niki_mysql - environment: - - MYSQL_ROOT_PASSWORD=root - - MYSQL_USER=niki_user - - MYSQL_PASSWORD=NIKI_user@123 - - MYSQL_DATABASE=niki_db - - niki-redis: - image: bitnami/redis:6.2 - container_name: niki-redis - restart: always - ports: - - '6380:6379' - # TODO - remove `--save "" --appendonly no` from command to persist data - command: redis-server --loglevel warning --protected-mode no --save "" --appendonly no - environment: - - ALLOW_EMPTY_PASSWORD=yes - volumes: - - niki-redis-data:/data - - - -volumes: - dbdata: - niki-redis-data: diff --git a/go.mod b/go.mod index ff1d54e..0c66320 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module git.gocasts.ir/ebhomengo/niki go 1.21.3 require ( + github.com/brianvoe/gofakeit/v6 v6.28.0 github.com/go-ozzo/ozzo-validation v3.6.0+incompatible github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/go-sql-driver/mysql v1.6.0 @@ -14,6 +15,7 @@ require ( github.com/oklog/ulid/v2 v2.1.0 github.com/redis/go-redis/v9 v9.4.0 github.com/rubenv/sql-migrate v1.6.0 + github.com/stretchr/testify v1.8.4 golang.org/x/crypto v0.17.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) @@ -21,6 +23,7 @@ require ( require ( github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fatih/structs v1.1.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -33,6 +36,7 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/net v0.19.0 // indirect diff --git a/go.sum b/go.sum index 1b26768..56fb3a7 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= diff --git a/internal/initial/validator.go b/internal/initial/validator.go index 8cd60c3..b11a9e3 100644 --- a/internal/initial/validator.go +++ b/internal/initial/validator.go @@ -4,7 +4,6 @@ import ( "git.gocasts.ir/ebhomengo/niki/adapter/redis" "git.gocasts.ir/ebhomengo/niki/config" "git.gocasts.ir/ebhomengo/niki/repository/mysql" - mysqlkindboxreq "git.gocasts.ir/ebhomengo/niki/repository/mysql/kind_box_req" adminvalidator "git.gocasts.ir/ebhomengo/niki/validator/admin/admin" adminkindboxreqvalidator "git.gocasts.ir/ebhomengo/niki/validator/admin/kind_box_req" benefactoraddressvalidator "git.gocasts.ir/ebhomengo/niki/validator/benefactor/address" @@ -34,7 +33,6 @@ func InitBenefactorValidator() benefactorvalidator.Validator { func InitBenefactorKindBoxReqValidator(cfg config.Config, redisAdapter redis.Adapter, db *mysql.DB) benefactorkindboxreqvalidator.Validator { return benefactorkindboxreqvalidator.New( - mysqlkindboxreq.New(db), InitBenefactorService(cfg, redisAdapter, db), InitBenefactorAddressService(db), ) diff --git a/param/benefactor/kind_box_req/add.go b/param/benefactor/kind_box_req/add.go index 0bea866..dcea5f1 100644 --- a/param/benefactor/kind_box_req/add.go +++ b/param/benefactor/kind_box_req/add.go @@ -1,6 +1,8 @@ package benefactorkindboxreqparam import ( + "time" + entity "git.gocasts.ir/ebhomengo/niki/entity" ) @@ -8,7 +10,7 @@ type KindBoxReqAddRequest struct { BenefactorID uint `json:"benefactor_id"` TypeID entity.KindBoxType `json:"type_id"` AddressID uint `json:"address_id"` - ReferDate string `json:"refer_date"` + ReferDate time.Time `json:"refer_date"` CountRequested uint `json:"count_requested"` } diff --git a/pkg/err_msg/message.go b/pkg/err_msg/message.go index 4c7c36c..348c97f 100644 --- a/pkg/err_msg/message.go +++ b/pkg/err_msg/message.go @@ -14,6 +14,7 @@ const ( ErrorMsgOtpCodeIsNotValid = "verification code is not valid" ErrorMsgCantScanQueryResult = "can't scan query result" ErrorMsgPhoneNumberOrPassIsIncorrect = "phone number or password is incorrect" + ErrBadRequest = "Bad request" ErrorMsgAcceptKindBoxReqStatus = "only pending requests will have the ability to be confirmed" ErrorMsgRejectKindBoxReqStatus = "only pending requests will have the ability to be rejected" ) diff --git a/repository/mysql/kind_box_req/kind_box_req_test.go b/repository/mysql/kind_box_req/kind_box_req_test.go new file mode 100644 index 0000000..08937eb --- /dev/null +++ b/repository/mysql/kind_box_req/kind_box_req_test.go @@ -0,0 +1,72 @@ +package mysqlkindboxreq_test + +import ( + "context" + "testing" + + "git.gocasts.ir/ebhomengo/niki/entity" + errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" + mysqlkindboxreq "git.gocasts.ir/ebhomengo/niki/repository/mysql/kind_box_req" + testutils "git.gocasts.ir/ebhomengo/niki/test" + "git.gocasts.ir/ebhomengo/niki/test/seed" + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/assert" +) + +func TestAddKindBoxReq(t *testing.T) { + mysqlRepo := testutils.Setup(t) + mysqlKindboxReq := mysqlkindboxreq.New(mysqlRepo) + + benefactor, cleanupBenefactor := seed.CreateBenefactor(t, mysqlRepo) + defer cleanupBenefactor() + address, cleanupAddress := seed.CreateAddress(t, mysqlRepo, benefactor.ID) + defer cleanupAddress() + + testCases := []struct { + name string + repoErr bool + expectedErr error + kindBoxReq entity.KindBoxReq + }{ + { + name: "repo fails", + repoErr: true, + expectedErr: richerror.New("mysqlkindboxreq.AddKindBoxReq").WithMessage(errmsg.ErrorMsgNotFound).WithKind(richerror.KindUnexpected), + kindBoxReq: entity.KindBoxReq{ + KindBoxType: entity.KindBoxStandUp, + AddressID: address.ID, + CountRequested: gofakeit.UintRange(1, 100), + ReferDate: gofakeit.Date(), + Status: entity.KindBoxReqPendingStatus, + }, + }, + { + name: "ordinary", + kindBoxReq: entity.KindBoxReq{ + BenefactorID: benefactor.ID, + KindBoxType: entity.KindBoxStandUp, + AddressID: address.ID, + CountRequested: gofakeit.UintRange(1, 100), + ReferDate: gofakeit.Date(), + Status: entity.KindBoxReqPendingStatus, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + kindBoxReq, err := mysqlKindboxReq.AddKindBoxReq(ctx, tc.kindBoxReq) + + if tc.expectedErr != nil { + assert.Equal(t, tc.expectedErr.Error(), err.Error()) + assert.Empty(t, kindBoxReq) + return + } + assert.NoError(t, err) + assert.NotEmpty(t, kindBoxReq) + seed.DeleteBenefactor(t, mysqlRepo, kindBoxReq.ID) + }) + } +} diff --git a/rtx.log b/rtx.log deleted file mode 100644 index e69de29..0000000 diff --git a/service/benefactor/kind_box_req/add.go b/service/benefactor/kind_box_req/add.go index ff225ed..b2f4db6 100644 --- a/service/benefactor/kind_box_req/add.go +++ b/service/benefactor/kind_box_req/add.go @@ -2,7 +2,6 @@ package benefactorkindboxreqservice import ( "context" - "time" entity "git.gocasts.ir/ebhomengo/niki/entity" param "git.gocasts.ir/ebhomengo/niki/param/benefactor/kind_box_req" @@ -11,15 +10,11 @@ import ( func (s Service) Add(ctx context.Context, req param.KindBoxReqAddRequest) (param.KindBoxReqAddResponse, error) { const op = "userkindboxreqservice.Add" - t, tErr := time.Parse(time.DateTime, req.ReferDate) - if tErr != nil { - return param.KindBoxReqAddResponse{}, richerror.New(op).WithErr(tErr).WithKind(richerror.KindInvalid) - } kindBoxReq, err := s.repo.AddKindBoxReq(ctx, entity.KindBoxReq{ BenefactorID: req.BenefactorID, KindBoxType: req.TypeID, AddressID: req.AddressID, - ReferDate: t, + ReferDate: req.ReferDate, CountRequested: req.CountRequested, Status: entity.KindBoxReqPendingStatus, }) diff --git a/service/benefactor/kind_box_req/add_test.go b/service/benefactor/kind_box_req/add_test.go new file mode 100644 index 0000000..03a16e8 --- /dev/null +++ b/service/benefactor/kind_box_req/add_test.go @@ -0,0 +1,64 @@ +package benefactorkindboxreqservice_test + +import ( + "context" + "fmt" + "testing" + "time" + + benefactorkindboxreqparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/kind_box_req" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" + benefactorkindboxreqservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/kind_box_req" + "git.gocasts.ir/ebhomengo/niki/test/mock" + "github.com/stretchr/testify/assert" +) + +func TestAdd(t *testing.T) { + testCases := []struct { + name string + repoErr bool + expectedErr error + req benefactorkindboxreqparam.KindBoxReqAddRequest + }{ + { + name: "repo fails", + repoErr: true, + expectedErr: richerror.New("userkindboxreqservice.Add").WithErr(fmt.Errorf(benefactorkindboxreqmock.RepoErr)).WithKind(richerror.KindUnexpected), + req: benefactorkindboxreqparam.KindBoxReqAddRequest{ + BenefactorID: 1, + AddressID: 1, + ReferDate: time.Now(), + CountRequested: 1, + TypeID: 1, + }, + }, + { + name: "ordinary", + req: benefactorkindboxreqparam.KindBoxReqAddRequest{ + BenefactorID: 1, + AddressID: 1, + ReferDate: time.Now(), + CountRequested: 1, + TypeID: 1, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + repo := benefactorkindboxreqmock.NewMockRepository(tc.repoErr) + svc := benefactorkindboxreqservice.New(repo) + ctx := context.Background() + + kindBoxreq, err := svc.Add(ctx, tc.req) + + if tc.expectedErr != nil { + assert.Equal(t, tc.expectedErr.Error(), err.Error()) + assert.Empty(t, kindBoxreq) + return + } + + assert.NoError(t, err) + assert.NotEmpty(t, kindBoxreq) + }) + } +} diff --git a/test/db.go b/test/db.go new file mode 100644 index 0000000..6729436 --- /dev/null +++ b/test/db.go @@ -0,0 +1,39 @@ +package testutils + +import ( + "sync" + "testing" + + "git.gocasts.ir/ebhomengo/niki/repository/migrator" + "git.gocasts.ir/ebhomengo/niki/repository/mysql" + + //nolint + _ "github.com/go-sql-driver/mysql" +) + +var once = sync.Once{} + +const port = 3305 + +func MySQLTestConfig() mysql.Config { + return mysql.Config{ + Username: "testuser", + Password: "test1234", + Port: port, + Host: "localhost", + DBName: "test_db", + } +} + +func Setup(t *testing.T) *mysql.DB { + t.Helper() + // connect to mysql database + config := MySQLTestConfig() + once.Do(func() { + mgr := migrator.New(config) + mgr.Up() + }) + mysqlRepo := mysql.New(config) + + return mysqlRepo +} diff --git a/test/end2end.go b/test/end2end.go new file mode 100644 index 0000000..3edfcab --- /dev/null +++ b/test/end2end.go @@ -0,0 +1,105 @@ +package testutils + +import ( + "context" + "testing" + + smsprovider "git.gocasts.ir/ebhomengo/niki/adapter/sms_provider/kavenegar" + kavenegarotp "git.gocasts.ir/ebhomengo/niki/adapter/sms_provider/kavenegar/otp" + "git.gocasts.ir/ebhomengo/niki/config" + benefactorkindboxreqhandler "git.gocasts.ir/ebhomengo/niki/delivery/http_server/benefactor/kind_box_req" + "git.gocasts.ir/ebhomengo/niki/internal/initial" + addressparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/address" + benefactoreparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/benefactore" + "git.gocasts.ir/ebhomengo/niki/repository/mysql" + mysqladdress "git.gocasts.ir/ebhomengo/niki/repository/mysql/address" + mysqlbenefactor "git.gocasts.ir/ebhomengo/niki/repository/mysql/benefactor" + mysqlkindboxreq "git.gocasts.ir/ebhomengo/niki/repository/mysql/kind_box_req" + redisotp "git.gocasts.ir/ebhomengo/niki/repository/redis/redis_otp" + authservice "git.gocasts.ir/ebhomengo/niki/service/auth" + benefactoraddressservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/address" + benefactorservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/benefactor" + benefactorkindboxreqservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/kind_box_req" + benefactorkindboxreqvalidator "git.gocasts.ir/ebhomengo/niki/validator/benefactor/kind_box_req" + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/assert" +) + +var ( + benefactorSvc benefactorservice.Service + benefactorAddressSvc benefactoraddressservice.Service + BenefactorkindBoxReqHandler benefactorkindboxreqhandler.Handler + AuthSvc authservice.Service + MysqlRepo *mysql.DB + AuthConfig authservice.Config +) + +func SetupEnd2EndTest(t *testing.T) { + t.Helper() + cfg := config.C() + + MysqlRepo = Setup(t) + redisAdapter := SetupRedis(t) + + AuthSvc = initial.InitBenefactorAuthService(cfg) + + RedisOtp := redisotp.New(redisAdapter) + benefactorMysql := mysqlbenefactor.New(MysqlRepo) + kavenegarSmsProvider := smsprovider.New(cfg.KavenegarSmsProvider) + otpSmsProvider := kavenegarotp.New(kavenegarSmsProvider) + benefactorSvc = benefactorservice.New(cfg.BenefactorSvc, RedisOtp, otpSmsProvider, AuthSvc, benefactorMysql) + benefactorAddressMysql := mysqladdress.New(MysqlRepo) + benefactorAddressSvc = benefactoraddressservice.New(benefactorAddressMysql) + benefactorKindBoxReqMysql := mysqlkindboxreq.New(MysqlRepo) + benefactorKindBoxReqSvc := benefactorkindboxreqservice.New(benefactorKindBoxReqMysql) + benefactorKindBoxReqVld := benefactorkindboxreqvalidator.New(benefactorSvc, benefactorAddressSvc) + + BenefactorkindBoxReqHandler = benefactorkindboxreqhandler.New(cfg.Auth, AuthSvc, benefactorKindBoxReqSvc, benefactorKindBoxReqVld) + + //nolint + return +} + +func SendOTP(t *testing.T) benefactoreparam.SendOtpResponse { + t.Helper() + req := benefactoreparam.SendOtpRequest{PhoneNumber: gofakeit.Phone()} + ctx := context.Background() + resp, err := benefactorSvc.SendOtp(ctx, req) + if err != nil { + t.Logf(err.Error()) + } + + return resp +} + +//nolint +func CreateBenefactorWithSvc(t *testing.T, req benefactoreparam.LoginOrRegisterRequest) (benefactoreparam.LoginOrRegisterResponse, func()) { + t.Helper() + ctx := context.Background() + resp, err := benefactorSvc.LoginOrRegister(ctx, req) + if err != nil { + t.Logf(err.Error()) + } + + return resp, func() { + _, err := MysqlRepo.Conn().ExecContext(ctx, `delete from benefactors where id=?`, + resp.BenefactorInfo.ID) + assert.Nil(t, err) + } +} + +//nolint +func CreateAddressWithSvc(t *testing.T, req addressparam.BenefactorAddAddressRequest) (addressparam.BenefactorAddAddressResponse, func()) { + t.Helper() + ctx := context.Background() + resp, err := benefactorAddressSvc.Add(ctx, req) + if err != nil { + t.Logf(err.Error()) + } + + return resp, func() { + _, err := MysqlRepo.Conn().ExecContext(ctx, `delete from addresses where id=?`, + resp.Address.ID) + assert.Nil(t, err) + } +} diff --git a/test/mock/benefactor_kind_box_req_mock.go b/test/mock/benefactor_kind_box_req_mock.go new file mode 100644 index 0000000..0d6ce1a --- /dev/null +++ b/test/mock/benefactor_kind_box_req_mock.go @@ -0,0 +1,65 @@ +package benefactorkindboxreqmock + +import ( + "context" + "fmt" + "time" + + "git.gocasts.ir/ebhomengo/niki/entity" +) + +const RepoErr = "repository error" + +type DefaultKindBoxReqTest struct { + BenefactorID uint + TypeID entity.KindBoxType + AddressID uint + ReferDate time.Time + CountRequested uint +} + +func DefaultKindBoxReq() DefaultKindBoxReqTest { + return DefaultKindBoxReqTest{ + BenefactorID: 1, + TypeID: 1, + AddressID: 1, + ReferDate: time.Now(), + CountRequested: 1, + } +} + +type MockRepository struct { + kindBoxReqs []entity.KindBoxReq + hasErr bool +} + +func NewMockRepository(hasErr bool) *MockRepository { + var kindBoxReqs []entity.KindBoxReq + DefaultKindBoxReq := DefaultKindBoxReq() + + kindBoxReqs = append(kindBoxReqs, entity.KindBoxReq{ + BenefactorID: DefaultKindBoxReq.BenefactorID, + AddressID: DefaultKindBoxReq.AddressID, + KindBoxType: DefaultKindBoxReq.TypeID, + ReferDate: DefaultKindBoxReq.ReferDate, + CountRequested: DefaultKindBoxReq.CountRequested, + Status: entity.KindBoxReqPendingStatus, + }) + + return &MockRepository{ + kindBoxReqs: kindBoxReqs, + hasErr: hasErr, + } +} + +//nolint +func (m *MockRepository) AddKindBoxReq(ctx context.Context, kindBoxReq entity.KindBoxReq) (entity.KindBoxReq, error) { + if m.hasErr { + return entity.KindBoxReq{}, fmt.Errorf(RepoErr) + } + + kindBoxReq.ID = 1 + m.kindBoxReqs = append(m.kindBoxReqs, kindBoxReq) + + return kindBoxReq, nil +} diff --git a/test/redisadapter.go b/test/redisadapter.go new file mode 100644 index 0000000..62035b7 --- /dev/null +++ b/test/redisadapter.go @@ -0,0 +1,26 @@ +package testutils + +import ( + "testing" + + "git.gocasts.ir/ebhomengo/niki/adapter/redis" +) + +const portRedis = 6381 + +func RedisTestConfig() redis.Config { + return redis.Config{ + Host: "localhost", + Port: portRedis, + Password: "", + DB: 0, + } +} + +func SetupRedis(t *testing.T) redis.Adapter { + t.Helper() + config := RedisTestConfig() + redisAdapter := redis.New(config) + + return redisAdapter +} diff --git a/test/seed/kind_box_req.go b/test/seed/kind_box_req.go new file mode 100644 index 0000000..c0921c4 --- /dev/null +++ b/test/seed/kind_box_req.go @@ -0,0 +1,79 @@ +package seed + +import ( + "context" + "testing" + "time" + + "git.gocasts.ir/ebhomengo/niki/entity" + "git.gocasts.ir/ebhomengo/niki/repository/mysql" + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/assert" +) + +//nolint +func CreateBenefactor(t *testing.T, db *mysql.DB) (*entity.Benefactor, func()) { + t.Helper() + benefactor := &entity.Benefactor{ + FirstName: gofakeit.FirstName(), + LastName: gofakeit.LastName(), + PhoneNumber: gofakeit.Phone(), + Address: gofakeit.Address().Address, + Description: "", + Email: gofakeit.Email(), + City: gofakeit.City(), + Gender: 0, + Status: entity.BenefactorActiveStatus, + Birthdate: time.Time{}, + Role: entity.UserBenefactorRole, + } + ctx := context.Background() + res, err := db.Conn().ExecContext(ctx, `insert into benefactors(phone_number, status, role) values(?, ?, ?)`, + benefactor.PhoneNumber, benefactor.Status.String(), benefactor.Role.String()) + assert.Nil(t, err) + //nolint + id, _ := res.LastInsertId() + benefactor.ID = uint(id) + + return benefactor, func() { + _, err := db.Conn().ExecContext(ctx, `delete from benefactors where id=?`, + id) + assert.Nil(t, err) + } +} + +//nolint +func CreateAddress(t *testing.T, db *mysql.DB, benfactorID uint) (*entity.Address, func()) { + t.Helper() + address := &entity.Address{ + PostalCode: gofakeit.Address().Zip, + Address: gofakeit.Address().Address, + Lat: float32(gofakeit.Address().Latitude), + Lon: float32(gofakeit.Address().Longitude), + //nolint + CityID: 1, + //nolint + ProvinceID: 15, + BenefactorID: benfactorID, + } + ctx := context.Background() + res, err := db.Conn().ExecContext(ctx, `insert into addresses(postal_code, address, lat, lon,province_id,city_id,benefactor_id) values(?, ?, ?,?,?,?,?)`, + address.PostalCode, address.Address, address.Lat, address.Lon, address.ProvinceID, address.CityID, address.BenefactorID) + assert.Nil(t, err) + //nolint + // error is always nil + id, _ := res.LastInsertId() + address.ID = uint(id) + + return address, func() { + _, err := db.Conn().ExecContext(ctx, `delete from addresses where id=?`, + id) + assert.Nil(t, err) + } +} + +func DeleteBenefactor(t *testing.T, db *mysql.DB, kindBoxReqID uint) { + t.Helper() + _, mErr := db.Conn().Exec(`delete from kind_box_reqs where id=?`, kindBoxReqID) + assert.Nil(t, mErr) +} diff --git a/validator/benefactor/kind_box_req/add.go b/validator/benefactor/kind_box_req/add.go index 6ea00b3..58f58ee 100644 --- a/validator/benefactor/kind_box_req/add.go +++ b/validator/benefactor/kind_box_req/add.go @@ -2,7 +2,6 @@ package benefactorkindboxreqvalidator import ( "errors" - "time" param "git.gocasts.ir/ebhomengo/niki/param/benefactor/kind_box_req" errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg" @@ -10,7 +9,7 @@ import ( validation "github.com/go-ozzo/ozzo-validation/v4" ) -func (v Validator) ValidateAddRequest(req param.KindBoxReqAddRequest) (map[string]string, error) { +func (v Validator) ValidateAddRequest(req param.KindBoxReqAddRequest) *ValidatorError { const op = "userkindboxreqvalidator.ValidateAddRequest" if err := validation.ValidateStruct(&req, @@ -31,7 +30,6 @@ func (v Validator) ValidateAddRequest(req param.KindBoxReqAddRequest) (map[strin validation.Field(&req.ReferDate, validation.Required, - validation.Date(time.DateTime), ), ); err != nil { @@ -46,12 +44,16 @@ func (v Validator) ValidateAddRequest(req param.KindBoxReqAddRequest) (map[strin } } - return fieldErrors, richerror.New(op). - WithMessage(errmsg.ErrorMsgInvalidInput). - WithKind(richerror.KindInvalid). - WithMeta(map[string]interface{}{"req": req}). - WithErr(err) + return &ValidatorError{ + Fields: fieldErrors, + Err: richerror.New(op). + WithMessage(errmsg.ErrorMsgInvalidInput). + WithKind(richerror.KindInvalid). + WithMeta(map[string]interface{}{"req": req}). + WithErr(err), + } + } - return map[string]string{}, nil + return nil } diff --git a/validator/benefactor/kind_box_req/add_test.go b/validator/benefactor/kind_box_req/add_test.go new file mode 100644 index 0000000..0e9ab99 --- /dev/null +++ b/validator/benefactor/kind_box_req/add_test.go @@ -0,0 +1,214 @@ +package benefactorkindboxreqvalidator_test + +import ( + "context" + "fmt" + "testing" + "time" + + "git.gocasts.ir/ebhomengo/niki/entity" + addressparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/address" + param "git.gocasts.ir/ebhomengo/niki/param/benefactor/benefactore" + benefactorkindboxreqparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/kind_box_req" + errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg" + benefactorkindboxreqvalidator "git.gocasts.ir/ebhomengo/niki/validator/benefactor/kind_box_req" + "github.com/stretchr/testify/assert" +) + +const RepoErr = "repository error" + +type StubService struct { + addresses []entity.Address + benefactors []entity.Benefactor + haveError bool +} + +func NewMock(haveError bool) *StubService { + var addresses []entity.Address + var benefactors []entity.Benefactor + + addresses = append(addresses, entity.Address{ + ID: 1, + PostalCode: "123456789", + Address: "tehran", + Lat: 25.25, + Lon: 25.26, + CityID: 1, + ProvinceID: 1, + BenefactorID: 1, + }) + benefactors = append(benefactors, entity.Benefactor{ + ID: 1, + FirstName: "mehdi", + LastName: "rez", + PhoneNumber: "09191234556", + Address: "tehran", + Description: "", + Email: "example@gmail.com", + City: "teran", + Gender: 0, + Status: 0, + Birthdate: time.Time{}, + Role: 1, + }) + + return &StubService{ + addresses: addresses, + benefactors: benefactors, + haveError: haveError, + } +} + +func (s StubService) BenefactorExistByID(ctx context.Context, request param.BenefactorExistByIDRequest) (param.BenefactorExistByIDResponse, error) { + if s.haveError { + // error response + return param.BenefactorExistByIDResponse{Existed: false}, fmt.Errorf(RepoErr) + } + + for _, benefactor := range s.benefactors { + if benefactor.ID == request.ID { + return param.BenefactorExistByIDResponse{Existed: true}, nil + } + } + return param.BenefactorExistByIDResponse{Existed: false}, nil +} + +func (s StubService) AddressExistByID(ctx context.Context, request addressparam.GetAddressByIDRequest) (addressparam.GetAddressByIDResponse, error) { + if s.haveError { + // error response + return addressparam.GetAddressByIDResponse{Address: nil}, fmt.Errorf(RepoErr) + } + for _, address := range s.addresses { + if address.ID == request.ID { + return addressparam.GetAddressByIDResponse{Address: &address}, nil + } + } + + return addressparam.GetAddressByIDResponse{Address: nil}, nil +} + +func TestValidateAddRequest(t *testing.T) { + testCases := []struct { + name string + params benefactorkindboxreqparam.KindBoxReqAddRequest + repoErr bool + error error + }{ + { + name: "ordinary add", + params: benefactorkindboxreqparam.KindBoxReqAddRequest{ + AddressID: 1, + BenefactorID: 1, + TypeID: 1, + CountRequested: 2, + ReferDate: time.Now(), + }, + }, + { + name: "repo error", + repoErr: true, + error: fmt.Errorf("benefactor_id: record not found\naddress_id: something went wrong\n"), + params: benefactorkindboxreqparam.KindBoxReqAddRequest{ + AddressID: 1, + BenefactorID: 1, + TypeID: 1, + CountRequested: 1, + ReferDate: time.Now(), + }, + }, + { + name: "Count Requested cannot be empty", + error: fmt.Errorf(fmt.Sprintf("count_requested: cannot be blank\n")), + params: benefactorkindboxreqparam.KindBoxReqAddRequest{ + AddressID: 1, + BenefactorID: 1, + TypeID: 1, + CountRequested: 0, + ReferDate: time.Now(), + }, + }, + { + name: "TypeID cannot be empty", + error: fmt.Errorf(fmt.Sprintf("type_id: cannot be blank\n")), + params: benefactorkindboxreqparam.KindBoxReqAddRequest{ + AddressID: 1, + BenefactorID: 1, + TypeID: 0, + CountRequested: 1, + ReferDate: time.Now(), + }, + }, + { + name: "type with ID does exists", + error: fmt.Errorf(fmt.Sprintf("type_id: %s\n", errmsg.ErrorMsgNotFound)), + params: benefactorkindboxreqparam.KindBoxReqAddRequest{ + AddressID: 1, + BenefactorID: 1, + TypeID: 5, + CountRequested: 1, + ReferDate: time.Now(), + }, + }, + { + name: "AddressID cannot be empty", + error: fmt.Errorf(fmt.Sprintf("address_id: cannot be blank\n")), + params: benefactorkindboxreqparam.KindBoxReqAddRequest{ + AddressID: 0, + TypeID: 1, + BenefactorID: 1, + CountRequested: 1, + ReferDate: time.Now(), + }, + }, + { + name: "address with ID does exists", + error: fmt.Errorf(fmt.Sprintf("address_id: %s\n", errmsg.ErrorMsgNotFound)), + params: benefactorkindboxreqparam.KindBoxReqAddRequest{ + AddressID: 5000, + TypeID: 1, + CountRequested: 1, + BenefactorID: 1, + ReferDate: time.Now(), + }, + }, + { + name: "ReferDate should not be empty", + error: fmt.Errorf(fmt.Sprintf("refer_date: cannot be blank\n")), + params: benefactorkindboxreqparam.KindBoxReqAddRequest{ + AddressID: 1, + TypeID: 1, + BenefactorID: 1, + CountRequested: 1, + }, + }, + { + name: "This address does not belong to this benefactor", + error: fmt.Errorf(fmt.Sprintf("address_id: %s\n", errmsg.ErrorMsgNotFound)), + params: benefactorkindboxreqparam.KindBoxReqAddRequest{ + AddressID: 1, + BenefactorID: 100, + TypeID: 1, + CountRequested: 1, + ReferDate: time.Now(), + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // 1. setup + repo := NewMock(tc.repoErr) + vld := benefactorkindboxreqvalidator.New(repo, repo) + + // 2. execution + res := vld.ValidateAddRequest(tc.params) + + // 3. assertion + if tc.error == nil { + assert.Nil(t, res) + return + } + assert.Equal(t, tc.error.Error(), res.Error()) + }) + } +} diff --git a/validator/benefactor/kind_box_req/validator.go b/validator/benefactor/kind_box_req/validator.go index 9b17b0d..d07cbf7 100644 --- a/validator/benefactor/kind_box_req/validator.go +++ b/validator/benefactor/kind_box_req/validator.go @@ -16,12 +16,6 @@ const ( MaxKindBoxReq uint = 100 ) -type Repository interface { - // KindBoxReqExist(id uint) (bool, error) - // KindBoxBelongToBenefactor(bID uint, kbID uint) (bool, error) - // PendingStatus(id uint) (bool, error) -} - type BenefactorSvc interface { BenefactorExistByID(ctx context.Context, request param.BenefactorExistByIDRequest) (param.BenefactorExistByIDResponse, error) } @@ -31,13 +25,27 @@ type AddressSvc interface { } type Validator struct { - repo Repository benefactorSvc BenefactorSvc addressSvc AddressSvc } -func New(repo Repository, benefactorSvc BenefactorSvc, addressSvc AddressSvc) Validator { - return Validator{repo: repo, benefactorSvc: benefactorSvc, addressSvc: addressSvc} +type ValidatorError struct { + Fields map[string]string `json:"error"` + Err error `json:"message"` +} + +func (v ValidatorError) Error() string { + var err string + + for key, value := range v.Fields { + err += fmt.Sprintf("%s: %s\n", key, value) + } + + return err +} + +func New(benefactorSvc BenefactorSvc, addressSvc AddressSvc) Validator { + return Validator{benefactorSvc: benefactorSvc, addressSvc: addressSvc} } func (v Validator) doesBenefactorExist(value interface{}) error {