430 lines
10 KiB
Go
430 lines
10 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"git.logidex.ru/fakz9/logidex-id/internal/api/auth/domain"
|
|
userDomain "git.logidex.ru/fakz9/logidex-id/internal/api/user/domain"
|
|
hydraApi "github.com/ory/hydra-client-go"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
)
|
|
|
|
// MockAuthRepository implements domain.AuthRepository
|
|
type MockAuthRepository struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockAuthRepository) SaveOtpRequest(ctx context.Context, uuid string, code string) error {
|
|
args := m.Called(ctx, uuid, code)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockAuthRepository) GetOtpRequest(ctx context.Context, uuid string) (*string, error) {
|
|
args := m.Called(ctx, uuid)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*string), args.Error(1)
|
|
}
|
|
|
|
// MockUserService implements userService.UserService
|
|
type MockUserService struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockUserService) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*userDomain.User, error) {
|
|
args := m.Called(ctx, phoneNumber)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*userDomain.User), args.Error(1)
|
|
}
|
|
|
|
func (m *MockUserService) GetUserByUuid(ctx context.Context, uuid string) (*userDomain.User, error) {
|
|
args := m.Called(ctx, uuid)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*userDomain.User), args.Error(1)
|
|
}
|
|
|
|
func (m *MockUserService) CreateUser(ctx context.Context, phoneNumber string) (*userDomain.User, error) {
|
|
args := m.Called(ctx, phoneNumber)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*userDomain.User), args.Error(1)
|
|
}
|
|
|
|
func (m *MockUserService) VerifyUser(ctx context.Context, uuid string) (*userDomain.User, error) {
|
|
args := m.Called(ctx, uuid)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*userDomain.User), args.Error(1)
|
|
}
|
|
|
|
// MockHydraResponse implements hydra response interface
|
|
type MockHydraResponse struct {
|
|
redirectTo *string
|
|
}
|
|
|
|
func (m *MockHydraResponse) GetRedirectToOk() (*string, bool) {
|
|
if m.redirectTo == nil {
|
|
return nil, false
|
|
}
|
|
return m.redirectTo, true
|
|
}
|
|
|
|
func TestNewAuthService(t *testing.T) {
|
|
mockRepo := &MockAuthRepository{}
|
|
mockUserService := &MockUserService{}
|
|
mockHydraClient := &hydraApi.APIClient{}
|
|
|
|
service := NewAuthService(mockRepo, mockUserService, mockHydraClient)
|
|
|
|
assert.NotNil(t, service)
|
|
assert.Implements(t, (*AuthService)(nil), service)
|
|
}
|
|
|
|
func TestAuthService_getUserByPhoneNumber(t *testing.T) {
|
|
mockUserService := &MockUserService{}
|
|
service := &authService{userService: mockUserService}
|
|
ctx := context.Background()
|
|
phoneNumber := "+79161234567"
|
|
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expectedUser *userDomain.User
|
|
wantErr bool
|
|
errType interface{}
|
|
}{
|
|
{
|
|
name: "user found",
|
|
setupMock: func() {
|
|
user := &userDomain.User{
|
|
Uuid: "test-uuid",
|
|
PhoneNumber: phoneNumber,
|
|
}
|
|
mockUserService.On("GetUserByPhoneNumber", ctx, phoneNumber).Return(user, nil).Once()
|
|
},
|
|
expectedUser: &userDomain.User{
|
|
Uuid: "test-uuid",
|
|
PhoneNumber: phoneNumber,
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "user not found",
|
|
setupMock: func() {
|
|
mockUserService.On("GetUserByPhoneNumber", ctx, phoneNumber).Return(nil, nil).Once()
|
|
},
|
|
wantErr: true,
|
|
errType: domain.ErrUserNotFound{},
|
|
},
|
|
{
|
|
name: "service error",
|
|
setupMock: func() {
|
|
mockUserService.On("GetUserByPhoneNumber", ctx, phoneNumber).Return(nil, assert.AnError).Once()
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
|
|
result, err := service.getUserByPhoneNumber(ctx, phoneNumber)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
assert.Nil(t, result)
|
|
if tt.errType != nil {
|
|
assert.IsType(t, tt.errType, err)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.expectedUser, result)
|
|
}
|
|
|
|
mockUserService.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAuthService_getOrCreateUser(t *testing.T) {
|
|
mockUserService := &MockUserService{}
|
|
service := &authService{userService: mockUserService}
|
|
ctx := context.Background()
|
|
phoneNumber := "+79161234567"
|
|
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expectedUser *userDomain.User
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "existing user found",
|
|
setupMock: func() {
|
|
user := &userDomain.User{
|
|
Uuid: "test-uuid",
|
|
PhoneNumber: phoneNumber,
|
|
}
|
|
mockUserService.On("GetUserByPhoneNumber", ctx, phoneNumber).Return(user, nil).Once()
|
|
},
|
|
expectedUser: &userDomain.User{
|
|
Uuid: "test-uuid",
|
|
PhoneNumber: phoneNumber,
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "user not found, create new",
|
|
setupMock: func() {
|
|
newUser := &userDomain.User{
|
|
Uuid: "new-uuid",
|
|
PhoneNumber: phoneNumber,
|
|
}
|
|
mockUserService.On("GetUserByPhoneNumber", ctx, phoneNumber).Return(nil, nil).Once()
|
|
mockUserService.On("CreateUser", ctx, phoneNumber).Return(newUser, nil).Once()
|
|
},
|
|
expectedUser: &userDomain.User{
|
|
Uuid: "new-uuid",
|
|
PhoneNumber: phoneNumber,
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "get user service error",
|
|
setupMock: func() {
|
|
mockUserService.On("GetUserByPhoneNumber", ctx, phoneNumber).Return(nil, assert.AnError).Once()
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "create user service error",
|
|
setupMock: func() {
|
|
mockUserService.On("GetUserByPhoneNumber", ctx, phoneNumber).Return(nil, nil).Once()
|
|
mockUserService.On("CreateUser", ctx, phoneNumber).Return(nil, assert.AnError).Once()
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
|
|
result, err := service.getOrCreateUser(ctx, phoneNumber)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
assert.Nil(t, result)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.expectedUser, result)
|
|
}
|
|
|
|
mockUserService.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAuthService_validateHydraResponse(t *testing.T) {
|
|
service := &authService{}
|
|
userUuid := "test-uuid"
|
|
|
|
tests := []struct {
|
|
name string
|
|
statusCode int
|
|
wantErr bool
|
|
errType interface{}
|
|
}{
|
|
{
|
|
name: "success status",
|
|
statusCode: 200,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "bad request status",
|
|
statusCode: 400,
|
|
wantErr: true,
|
|
errType: domain.ErrInvalidHydraAccept{},
|
|
},
|
|
{
|
|
name: "internal server error status",
|
|
statusCode: 500,
|
|
wantErr: true,
|
|
errType: domain.ErrInvalidHydraAccept{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
resp := &http.Response{StatusCode: tt.statusCode}
|
|
|
|
err := service.validateHydraResponse(resp, userUuid)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
if tt.errType != nil {
|
|
assert.IsType(t, tt.errType, err)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAuthService_extractRedirectUrl(t *testing.T) {
|
|
service := &authService{}
|
|
userUuid := "test-uuid"
|
|
|
|
tests := []struct {
|
|
name string
|
|
response *MockHydraResponse
|
|
expectedUrl string
|
|
wantErr bool
|
|
errType interface{}
|
|
}{
|
|
{
|
|
name: "valid redirect url",
|
|
response: &MockHydraResponse{
|
|
redirectTo: stringPtr("https://example.com/callback"),
|
|
},
|
|
expectedUrl: "https://example.com/callback",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "nil redirect url",
|
|
response: &MockHydraResponse{
|
|
redirectTo: nil,
|
|
},
|
|
wantErr: true,
|
|
errType: domain.ErrInvalidHydraAccept{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := service.extractRedirectUrl(tt.response, userUuid)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
assert.Empty(t, result)
|
|
if tt.errType != nil {
|
|
assert.IsType(t, tt.errType, err)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.expectedUrl, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAuthService_OtpRequest(t *testing.T) {
|
|
mockRepo := &MockAuthRepository{}
|
|
mockUserService := &MockUserService{}
|
|
service := &authService{
|
|
repo: mockRepo,
|
|
userService: mockUserService,
|
|
}
|
|
ctx := context.Background()
|
|
phoneNumber := "89161234567" // will be formatted to +79161234567
|
|
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
wantErr bool
|
|
errType interface{}
|
|
}{
|
|
{
|
|
name: "success - existing user",
|
|
setupMock: func() {
|
|
user := &userDomain.User{
|
|
Uuid: "test-uuid",
|
|
PhoneNumber: "+79161234567",
|
|
}
|
|
mockUserService.On("GetUserByPhoneNumber", ctx, "+79161234567").Return(user, nil).Once()
|
|
mockRepo.On("SaveOtpRequest", ctx, "test-uuid", "123456").Return(nil).Once()
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "success - create new user",
|
|
setupMock: func() {
|
|
newUser := &userDomain.User{
|
|
Uuid: "new-uuid",
|
|
PhoneNumber: "+79161234567",
|
|
}
|
|
mockUserService.On("GetUserByPhoneNumber", ctx, "+79161234567").Return(nil, nil).Once()
|
|
mockUserService.On("CreateUser", ctx, "+79161234567").Return(newUser, nil).Once()
|
|
mockRepo.On("SaveOtpRequest", ctx, "new-uuid", "123456").Return(nil).Once()
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "invalid phone number",
|
|
setupMock: func() {
|
|
// No mock setup needed for invalid phone
|
|
},
|
|
wantErr: true,
|
|
errType: domain.ErrInvalidPhoneNumber{},
|
|
},
|
|
{
|
|
name: "repo save error",
|
|
setupMock: func() {
|
|
user := &userDomain.User{
|
|
Uuid: "test-uuid",
|
|
PhoneNumber: "+79161234567",
|
|
}
|
|
mockUserService.On("GetUserByPhoneNumber", ctx, "+79161234567").Return(user, nil).Once()
|
|
mockRepo.On("SaveOtpRequest", ctx, "test-uuid", "123456").Return(assert.AnError).Once()
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Reset mocks
|
|
mockRepo.ExpectedCalls = nil
|
|
mockUserService.ExpectedCalls = nil
|
|
|
|
tt.setupMock()
|
|
|
|
testPhone := phoneNumber
|
|
if tt.name == "invalid phone number" {
|
|
testPhone = "invalid"
|
|
}
|
|
|
|
err := service.OtpRequest(ctx, testPhone)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
if tt.errType != nil {
|
|
assert.IsType(t, tt.errType, err)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
mockRepo.AssertExpectations(t)
|
|
mockUserService.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Helper function to create string pointer
|
|
func stringPtr(s string) *string {
|
|
return &s
|
|
}
|