Files
IDP-Backend/internal/api/auth/service/auth_service_test.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
}