forked from ebhomengo/niki
1
0
Fork 0

feat(validator): implement unit test for admin login validator (#109)

This commit is contained in:
Ruhollah 2024-08-03 18:16:14 +03:30
parent 705adda09b
commit fb4bd97c5c
7 changed files with 293 additions and 0 deletions

3
.mockery.yaml Normal file
View File

@ -0,0 +1,3 @@
inpackage: True
with-expecter: True
testonly: True

View File

@ -0,0 +1,17 @@
## Mocking interfaces in unit tests
1- add a //go:generate directive above the interface:
```go
//go:generate mockery --name Repository
type Repository interface {
AdminExistByPhoneNumber(ctx context.Context, phoneNumber string) (bool, error)
AdminExistByEmail(ctx context.Context, email string) (bool, error)
}
```
2- run go generate to create the mock files:
```bash
go generate ./...
```
3- use the generated mock types in your tests.
for more information visit:
https://vektra.github.io/mockery/latest/

4
go.mod
View File

@ -13,6 +13,7 @@ require (
github.com/labstack/echo/v4 v4.12.0 github.com/labstack/echo/v4 v4.12.0
github.com/redis/go-redis/v9 v9.4.0 github.com/redis/go-redis/v9 v9.4.0
github.com/rubenv/sql-migrate v1.6.0 github.com/rubenv/sql-migrate v1.6.0
github.com/stretchr/testify v1.9.0
github.com/swaggo/echo-swagger v1.4.1 github.com/swaggo/echo-swagger v1.4.1
github.com/swaggo/swag v1.16.3 github.com/swaggo/swag v1.16.3
golang.org/x/crypto v0.23.0 golang.org/x/crypto v0.23.0
@ -23,6 +24,7 @@ require (
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/structs v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
@ -42,6 +44,8 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect

2
go.sum
View File

@ -292,6 +292,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=

View File

@ -0,0 +1,115 @@
package adminvalidator
import (
"context"
"errors"
adminserviceparam "git.gocasts.ir/ebhomengo/niki/param/admin/admin"
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidateLoginWithPhoneNumberRequest(t *testing.T) {
mockRepo := NewMockRepository(t)
validator := New(mockRepo)
ctx := context.Background()
validPhoneNumber := "09123456789"
validPassword := "validpassword"
t.Run("Valid request", func(t *testing.T) {
req := adminserviceparam.LoginWithPhoneNumberRequest{
PhoneNumber: validPhoneNumber,
Password: validPassword,
}
mockRepo.EXPECT().AdminExistByPhoneNumber(ctx, validPhoneNumber).Return(true, nil).Once()
fieldErrors, err := validator.ValidateLoginWithPhoneNumberRequest(ctx, req)
assert.NoError(t, err)
assert.Nil(t, fieldErrors)
})
t.Run("Empty phone number", func(t *testing.T) {
req := adminserviceparam.LoginWithPhoneNumberRequest{
PhoneNumber: "",
Password: validPassword,
}
fieldErrors, err := validator.ValidateLoginWithPhoneNumberRequest(ctx, req)
assert.Error(t, err)
assert.NotNil(t, fieldErrors)
assert.Contains(t, fieldErrors, "phone_number")
})
t.Run("Empty password", func(t *testing.T) {
req := adminserviceparam.LoginWithPhoneNumberRequest{
PhoneNumber: validPhoneNumber,
Password: "",
}
mockRepo.EXPECT().AdminExistByPhoneNumber(ctx, validPhoneNumber).Return(true, nil).Once()
fieldErrors, err := validator.ValidateLoginWithPhoneNumberRequest(ctx, req)
assert.Error(t, err)
assert.NotNil(t, fieldErrors)
assert.Contains(t, fieldErrors, "password")
})
t.Run("Invalid phone number format", func(t *testing.T) {
req := adminserviceparam.LoginWithPhoneNumberRequest{
PhoneNumber: "12345",
Password: validPassword,
}
fieldErrors, err := validator.ValidateLoginWithPhoneNumberRequest(ctx, req)
assert.Error(t, err)
assert.NotNil(t, fieldErrors)
assert.Equal(t, errmsg.ErrorMsgPhoneNumberIsNotValid, fieldErrors["phone_number"])
})
t.Run("Invalid password length", func(t *testing.T) {
req := adminserviceparam.LoginWithPhoneNumberRequest{
PhoneNumber: validPhoneNumber,
Password: "short",
}
mockRepo.EXPECT().AdminExistByPhoneNumber(ctx, validPhoneNumber).Return(true, nil).Once()
fieldErrors, err := validator.ValidateLoginWithPhoneNumberRequest(ctx, req)
assert.Error(t, err)
assert.NotNil(t, fieldErrors)
assert.Contains(t, fieldErrors, "password")
})
t.Run("Phone number does not exist", func(t *testing.T) {
req := adminserviceparam.LoginWithPhoneNumberRequest{
PhoneNumber: validPhoneNumber,
Password: validPassword,
}
mockRepo.EXPECT().AdminExistByPhoneNumber(ctx, validPhoneNumber).Return(false, nil).Once()
fieldErrors, err := validator.ValidateLoginWithPhoneNumberRequest(ctx, req)
assert.Error(t, err)
assert.NotNil(t, fieldErrors)
assert.Equal(t, errmsg.ErrorMsgPhoneNumberOrPassIsIncorrect, fieldErrors["phone_number"])
})
t.Run("Repository error", func(t *testing.T) {
req := adminserviceparam.LoginWithPhoneNumberRequest{
PhoneNumber: validPhoneNumber,
Password: validPassword,
}
mockRepo.EXPECT().AdminExistByPhoneNumber(ctx, validPhoneNumber).Return(false, errors.New("repo error")).Once()
fieldErrors, err := validator.ValidateLoginWithPhoneNumberRequest(ctx, req)
assert.Error(t, err)
assert.NotNil(t, fieldErrors)
assert.Equal(t, errmsg.ErrorMsgSomethingWentWrong, fieldErrors["phone_number"])
})
}

View File

@ -0,0 +1,150 @@
// Code generated by mockery v2.41.0. DO NOT EDIT.
package adminvalidator
import (
context "context"
mock "github.com/stretchr/testify/mock"
)
// MockRepository is an autogenerated mock type for the Repository type
type MockRepository struct {
mock.Mock
}
type MockRepository_Expecter struct {
mock *mock.Mock
}
func (_m *MockRepository) EXPECT() *MockRepository_Expecter {
return &MockRepository_Expecter{mock: &_m.Mock}
}
// AdminExistByEmail provides a mock function with given fields: ctx, email
func (_m *MockRepository) AdminExistByEmail(ctx context.Context, email string) (bool, error) {
ret := _m.Called(ctx, email)
if len(ret) == 0 {
panic("no return value specified for AdminExistByEmail")
}
var r0 bool
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok {
return rf(ctx, email)
}
if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok {
r0 = rf(ctx, email)
} else {
r0 = ret.Get(0).(bool)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, email)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockRepository_AdminExistByEmail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AdminExistByEmail'
type MockRepository_AdminExistByEmail_Call struct {
*mock.Call
}
// AdminExistByEmail is a helper method to define mock.On call
// - ctx context.Context
// - email string
func (_e *MockRepository_Expecter) AdminExistByEmail(ctx interface{}, email interface{}) *MockRepository_AdminExistByEmail_Call {
return &MockRepository_AdminExistByEmail_Call{Call: _e.mock.On("AdminExistByEmail", ctx, email)}
}
func (_c *MockRepository_AdminExistByEmail_Call) Run(run func(ctx context.Context, email string)) *MockRepository_AdminExistByEmail_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *MockRepository_AdminExistByEmail_Call) Return(_a0 bool, _a1 error) *MockRepository_AdminExistByEmail_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockRepository_AdminExistByEmail_Call) RunAndReturn(run func(context.Context, string) (bool, error)) *MockRepository_AdminExistByEmail_Call {
_c.Call.Return(run)
return _c
}
// AdminExistByPhoneNumber provides a mock function with given fields: ctx, phoneNumber
func (_m *MockRepository) AdminExistByPhoneNumber(ctx context.Context, phoneNumber string) (bool, error) {
ret := _m.Called(ctx, phoneNumber)
if len(ret) == 0 {
panic("no return value specified for AdminExistByPhoneNumber")
}
var r0 bool
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok {
return rf(ctx, phoneNumber)
}
if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok {
r0 = rf(ctx, phoneNumber)
} else {
r0 = ret.Get(0).(bool)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, phoneNumber)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockRepository_AdminExistByPhoneNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AdminExistByPhoneNumber'
type MockRepository_AdminExistByPhoneNumber_Call struct {
*mock.Call
}
// AdminExistByPhoneNumber is a helper method to define mock.On call
// - ctx context.Context
// - phoneNumber string
func (_e *MockRepository_Expecter) AdminExistByPhoneNumber(ctx interface{}, phoneNumber interface{}) *MockRepository_AdminExistByPhoneNumber_Call {
return &MockRepository_AdminExistByPhoneNumber_Call{Call: _e.mock.On("AdminExistByPhoneNumber", ctx, phoneNumber)}
}
func (_c *MockRepository_AdminExistByPhoneNumber_Call) Run(run func(ctx context.Context, phoneNumber string)) *MockRepository_AdminExistByPhoneNumber_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *MockRepository_AdminExistByPhoneNumber_Call) Return(_a0 bool, _a1 error) *MockRepository_AdminExistByPhoneNumber_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockRepository_AdminExistByPhoneNumber_Call) RunAndReturn(run func(context.Context, string) (bool, error)) *MockRepository_AdminExistByPhoneNumber_Call {
_c.Call.Return(run)
return _c
}
// NewMockRepository creates a new instance of MockRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockRepository(t interface {
mock.TestingT
Cleanup(func())
}) *MockRepository {
mock := &MockRepository{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -19,10 +19,12 @@ const (
maxLengthPassword = 32 maxLengthPassword = 32
) )
//go:generate mockery --name Repository
type Repository interface { type Repository interface {
AdminExistByPhoneNumber(ctx context.Context, phoneNumber string) (bool, error) AdminExistByPhoneNumber(ctx context.Context, phoneNumber string) (bool, error)
AdminExistByEmail(ctx context.Context, email string) (bool, error) AdminExistByEmail(ctx context.Context, email string) (bool, error)
} }
type Validator struct { type Validator struct {
repo Repository repo Repository
} }