refactor: [shitty claude AI first try] restructure server and user services, add new test cases, and improve error handling

This commit is contained in:
2025-08-10 21:40:15 +03:00
parent 588576b82f
commit f503e45be1
23 changed files with 2568 additions and 134 deletions

View File

@ -0,0 +1,53 @@
package domain
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUser_Structure(t *testing.T) {
user := User{
Uuid: "123e4567-e89b-12d3-a456-426614174000",
PhoneNumber: "+1234567890",
}
assert.Equal(t, "123e4567-e89b-12d3-a456-426614174000", user.Uuid)
assert.Equal(t, "+1234567890", user.PhoneNumber)
}
func TestErrUserNotFound_Error(t *testing.T) {
tests := []struct {
name string
phoneNumber string
want string
}{
{
name: "returns formatted error message",
phoneNumber: "+1234567890",
want: "User not found with phone number: +1234567890",
},
{
name: "handles empty phone number",
phoneNumber: "",
want: "User not found with phone number: ",
},
{
name: "handles international phone number",
phoneNumber: "+44123456789",
want: "User not found with phone number: +44123456789",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ErrUserNotFound{PhoneNumber: tt.phoneNumber}
assert.Equal(t, tt.want, err.Error())
})
}
}
func TestErrUserNotFound_IsError(t *testing.T) {
err := ErrUserNotFound{PhoneNumber: "+1234567890"}
assert.Implements(t, (*error)(nil), err)
}

View File

@ -25,10 +25,9 @@ func (h UserHandler) GetUserById(ctx context.Context, request GetUserByIdRequest
}, nil
}
var responseUser User
err = copier.Copy(responseUser, user)
err = copier.Copy(&responseUser, user)
if err != nil {
return GetUserById404JSONResponse{Message: err.Error()}, nil
}
return GetUserById200JSONResponse{User: responseUser}, nil
}
@ -45,9 +44,3 @@ func (h UserHandler) RegisterRoutes(router fiber.Router) {
server := NewStrictHandler(h, nil)
RegisterHandlers(router, server)
}
//func RegisterUserHandler(router fiber.Router) {
// server := NewStrictHandler(NewUserHandler(), nil)
// RegisterHandlers(router, server)
//
//}

View File

@ -0,0 +1,198 @@
package handler
import (
"context"
"testing"
"git.logidex.ru/fakz9/logidex-id/internal/api/user/domain"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// MockUserService implements service.UserService
type MockUserService struct {
mock.Mock
}
func (m *MockUserService) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*domain.User, error) {
args := m.Called(ctx, phoneNumber)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.User), args.Error(1)
}
func (m *MockUserService) GetUserByUuid(ctx context.Context, uuid string) (*domain.User, error) {
args := m.Called(ctx, uuid)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.User), args.Error(1)
}
func (m *MockUserService) CreateUser(ctx context.Context, phoneNumber string) (*domain.User, error) {
args := m.Called(ctx, phoneNumber)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.User), args.Error(1)
}
func (m *MockUserService) VerifyUser(ctx context.Context, uuid string) (*domain.User, error) {
args := m.Called(ctx, uuid)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.User), args.Error(1)
}
func TestNewUserHandler(t *testing.T) {
mockService := &MockUserService{}
handler := NewUserHandler(mockService)
assert.NotNil(t, handler)
assert.Equal(t, mockService, handler.service)
assert.Implements(t, (*StrictServerInterface)(nil), handler)
}
func TestUserHandler_GetUserById(t *testing.T) {
tests := []struct {
name string
userId string
setupMock func(*MockUserService)
expectedStatus int
expectError bool
expectedMsg string
}{
{
name: "successful user retrieval",
userId: "123e4567-e89b-12d3-a456-426614174000",
setupMock: func(m *MockUserService) {
user := &domain.User{
Uuid: "123e4567-e89b-12d3-a456-426614174000",
PhoneNumber: "+79161234567",
}
m.On("GetUserByUuid", mock.Anything, "123e4567-e89b-12d3-a456-426614174000").
Return(user, nil).Once()
},
expectedStatus: 200, // Fixed copier bug
expectError: false,
},
{
name: "user not found",
userId: "nonexistent-uuid",
setupMock: func(m *MockUserService) {
m.On("GetUserByUuid", mock.Anything, "nonexistent-uuid").
Return(nil, domain.ErrUserNotFound{PhoneNumber: "nonexistent-uuid"}).Once()
},
expectedStatus: 404,
expectError: true,
expectedMsg: "User not found with phone number: nonexistent-uuid",
},
{
name: "service error",
userId: "error-uuid",
setupMock: func(m *MockUserService) {
m.On("GetUserByUuid", mock.Anything, "error-uuid").
Return(nil, assert.AnError).Once()
},
expectedStatus: 404,
expectError: true,
expectedMsg: assert.AnError.Error(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockService := &MockUserService{}
handler := &UserHandler{service: mockService}
ctx := context.Background()
tt.setupMock(mockService)
request := GetUserByIdRequestObject{
UserId: tt.userId,
}
result, err := handler.GetUserById(ctx, request)
assert.NoError(t, err) // Handler should not return errors, only response objects
if tt.expectedStatus == 200 {
response, ok := result.(GetUserById200JSONResponse)
assert.True(t, ok, "Expected 200 response type")
_ = response // Just to avoid unused variable error
} else {
response, ok := result.(GetUserById404JSONResponse)
assert.True(t, ok, "Expected 404 response type")
assert.Equal(t, tt.expectedMsg, response.Message)
_ = response // Use the variable to avoid unused error
}
mockService.AssertExpectations(t)
})
}
}
func TestUserHandler_CreateUser(t *testing.T) {
mockService := &MockUserService{}
handler := &UserHandler{service: mockService}
ctx := context.Background()
t.Run("create user not implemented", func(t *testing.T) {
request := CreateUserRequestObject{}
// This should panic as per the current implementation
assert.Panics(t, func() {
_, _ = handler.CreateUser(ctx, request)
}, "CreateUser should panic as it's not implemented")
})
}
func TestUserHandler_EdgeCases(t *testing.T) {
t.Run("empty user ID should be handled gracefully", func(t *testing.T) {
mockService := &MockUserService{}
handler := &UserHandler{service: mockService}
ctx := context.Background()
mockService.On("GetUserByUuid", mock.Anything, "").
Return(nil, domain.ErrUserNotFound{PhoneNumber: ""}).Once()
request := GetUserByIdRequestObject{
UserId: "",
}
result, err := handler.GetUserById(ctx, request)
assert.NoError(t, err)
response, ok := result.(GetUserById404JSONResponse)
assert.True(t, ok)
assert.Contains(t, response.Message, "User not found")
mockService.AssertExpectations(t)
})
t.Run("special characters in user ID", func(t *testing.T) {
mockService := &MockUserService{}
handler := &UserHandler{service: mockService}
ctx := context.Background()
specialUserId := "user@#$%^&*()"
mockService.On("GetUserByUuid", mock.Anything, specialUserId).
Return(nil, domain.ErrUserNotFound{PhoneNumber: specialUserId}).Once()
request := GetUserByIdRequestObject{
UserId: specialUserId,
}
result, err := handler.GetUserById(ctx, request)
assert.NoError(t, err)
response, ok := result.(GetUserById404JSONResponse)
assert.True(t, ok)
_ = response // Use the variable to avoid unused error
mockService.AssertExpectations(t)
})
}

View File

@ -40,13 +40,6 @@ func (u userRepo) GetUserByUuid(ctx context.Context, requestUuid string) (*db.Us
return &dbUser, nil
}
func userFromDbToDomain(dbUser db.User) *domain.User {
return &domain.User{
PhoneNumber: dbUser.PhoneNumber,
Uuid: dbUser.Uuid.String(),
}
}
func (u userRepo) CreateUser(ctx context.Context, phoneNumber string) (*db.User, error) {
queries := db.New(u.db)
user, err := queries.CreateUser(ctx, phoneNumber)

View File

@ -8,7 +8,9 @@ import (
type UserService interface {
GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*domain.User, error)
GetUserByUuid(ctx context.Context, phoneNumber string) (*domain.User, error)
GetUserByUuid(ctx context.Context, uuid string) (*domain.User, error)
CreateUser(ctx context.Context, phoneNumber string) (*domain.User, error)
VerifyUser(ctx context.Context, uuid string) (*domain.User, error)
}
type userService struct {
repo domain.UserRepository
@ -42,6 +44,28 @@ func (u userService) GetUserByPhoneNumber(ctx context.Context, phoneNumber strin
}, nil
}
func (u userService) CreateUser(ctx context.Context, phoneNumber string) (*domain.User, error) {
dbUser, err := u.repo.CreateUser(ctx, phoneNumber)
if err != nil {
return nil, err
}
return &domain.User{
Uuid: dbUser.Uuid.String(),
PhoneNumber: dbUser.PhoneNumber,
}, nil
}
func (u userService) VerifyUser(ctx context.Context, uuid string) (*domain.User, error) {
dbUser, err := u.repo.VerifyUser(ctx, uuid)
if err != nil {
return nil, err
}
return &domain.User{
Uuid: dbUser.Uuid.String(),
PhoneNumber: dbUser.PhoneNumber,
}, nil
}
func NewUserService(repo domain.UserRepository) UserService {
return &userService{repo: repo}
}

View File

@ -0,0 +1,337 @@
package service
import (
"context"
"testing"
"git.logidex.ru/fakz9/logidex-id/internal/api/user/domain"
db "git.logidex.ru/fakz9/logidex-id/internal/db/generated"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// MockUserRepository implements domain.UserRepository
type MockUserRepository struct {
mock.Mock
}
func (m *MockUserRepository) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*db.User, error) {
args := m.Called(ctx, phoneNumber)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*db.User), args.Error(1)
}
func (m *MockUserRepository) GetUserByUuid(ctx context.Context, uuid string) (*db.User, error) {
args := m.Called(ctx, uuid)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*db.User), args.Error(1)
}
func (m *MockUserRepository) CreateUser(ctx context.Context, phoneNumber string) (*db.User, error) {
args := m.Called(ctx, phoneNumber)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*db.User), args.Error(1)
}
func (m *MockUserRepository) VerifyUser(ctx context.Context, uuid string) (*db.User, error) {
args := m.Called(ctx, uuid)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*db.User), args.Error(1)
}
func TestNewUserService(t *testing.T) {
mockRepo := &MockUserRepository{}
service := NewUserService(mockRepo)
assert.NotNil(t, service)
assert.Implements(t, (*UserService)(nil), service)
}
func TestUserService_GetUserByPhoneNumber(t *testing.T) {
mockRepo := &MockUserRepository{}
service := &userService{repo: mockRepo}
ctx := context.Background()
phoneNumber := "+79161234567"
testUuid := uuid.New()
tests := []struct {
name string
setupMock func()
expectedUser *domain.User
wantErr bool
errType interface{}
}{
{
name: "user found",
setupMock: func() {
dbUser := &db.User{
Uuid: testUuid,
PhoneNumber: phoneNumber,
}
mockRepo.On("GetUserByPhoneNumber", ctx, phoneNumber).Return(dbUser, nil).Once()
},
expectedUser: &domain.User{
Uuid: testUuid.String(),
PhoneNumber: phoneNumber,
},
wantErr: false,
},
{
name: "user not found",
setupMock: func() {
mockRepo.On("GetUserByPhoneNumber", ctx, phoneNumber).Return(nil, nil).Once()
},
wantErr: true,
errType: domain.ErrUserNotFound{},
},
{
name: "repository error",
setupMock: func() {
mockRepo.On("GetUserByPhoneNumber", ctx, phoneNumber).Return(nil, assert.AnError).Once()
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset mock
mockRepo.ExpectedCalls = nil
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)
}
mockRepo.AssertExpectations(t)
})
}
}
func TestUserService_GetUserByUuid(t *testing.T) {
mockRepo := &MockUserRepository{}
service := &userService{repo: mockRepo}
ctx := context.Background()
testUuid := uuid.New()
uuidString := testUuid.String()
phoneNumber := "+79161234567"
tests := []struct {
name string
setupMock func()
expectedUser *domain.User
wantErr bool
errType interface{}
}{
{
name: "user found",
setupMock: func() {
dbUser := &db.User{
Uuid: testUuid,
PhoneNumber: phoneNumber,
}
mockRepo.On("GetUserByUuid", ctx, uuidString).Return(dbUser, nil).Once()
},
expectedUser: &domain.User{
Uuid: uuidString,
PhoneNumber: phoneNumber,
},
wantErr: false,
},
{
name: "user not found",
setupMock: func() {
mockRepo.On("GetUserByUuid", ctx, uuidString).Return(nil, nil).Once()
},
wantErr: true,
errType: domain.ErrUserNotFound{},
},
{
name: "repository error",
setupMock: func() {
mockRepo.On("GetUserByUuid", ctx, uuidString).Return(nil, assert.AnError).Once()
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset mock
mockRepo.ExpectedCalls = nil
tt.setupMock()
result, err := service.GetUserByUuid(ctx, uuidString)
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)
}
mockRepo.AssertExpectations(t)
})
}
}
func TestUserService_CreateUser(t *testing.T) {
mockRepo := &MockUserRepository{}
service := &userService{repo: mockRepo}
ctx := context.Background()
phoneNumber := "+79161234567"
testUuid := uuid.New()
tests := []struct {
name string
setupMock func()
expectedUser *domain.User
wantErr bool
}{
{
name: "user created successfully",
setupMock: func() {
dbUser := &db.User{
Uuid: testUuid,
PhoneNumber: phoneNumber,
}
mockRepo.On("CreateUser", ctx, phoneNumber).Return(dbUser, nil).Once()
},
expectedUser: &domain.User{
Uuid: testUuid.String(),
PhoneNumber: phoneNumber,
},
wantErr: false,
},
{
name: "repository error",
setupMock: func() {
mockRepo.On("CreateUser", ctx, phoneNumber).Return(nil, assert.AnError).Once()
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset mock
mockRepo.ExpectedCalls = nil
tt.setupMock()
result, err := service.CreateUser(ctx, phoneNumber)
if tt.wantErr {
assert.Error(t, err)
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedUser, result)
}
mockRepo.AssertExpectations(t)
})
}
}
func TestUserService_VerifyUser(t *testing.T) {
mockRepo := &MockUserRepository{}
service := &userService{repo: mockRepo}
ctx := context.Background()
testUuid := uuid.New()
uuidString := testUuid.String()
phoneNumber := "+79161234567"
tests := []struct {
name string
setupMock func()
expectedUser *domain.User
wantErr bool
}{
{
name: "user verified successfully",
setupMock: func() {
dbUser := &db.User{
Uuid: testUuid,
PhoneNumber: phoneNumber,
}
mockRepo.On("VerifyUser", ctx, uuidString).Return(dbUser, nil).Once()
},
expectedUser: &domain.User{
Uuid: uuidString,
PhoneNumber: phoneNumber,
},
wantErr: false,
},
{
name: "repository error",
setupMock: func() {
mockRepo.On("VerifyUser", ctx, uuidString).Return(nil, assert.AnError).Once()
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset mock
mockRepo.ExpectedCalls = nil
tt.setupMock()
result, err := service.VerifyUser(ctx, uuidString)
if tt.wantErr {
assert.Error(t, err)
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedUser, result)
}
mockRepo.AssertExpectations(t)
})
}
}
// Test edge cases and error scenarios
func TestUserService_ErrorMessages(t *testing.T) {
mockRepo := &MockUserRepository{}
service := &userService{repo: mockRepo}
ctx := context.Background()
phoneNumber := "+79161234567"
t.Run("GetUserByPhoneNumber returns proper error message", func(t *testing.T) {
mockRepo.On("GetUserByPhoneNumber", ctx, phoneNumber).Return(nil, nil).Once()
_, err := service.GetUserByPhoneNumber(ctx, phoneNumber)
assert.Error(t, err)
userErr, ok := err.(domain.ErrUserNotFound)
assert.True(t, ok)
assert.Equal(t, phoneNumber, userErr.PhoneNumber)
assert.Contains(t, err.Error(), phoneNumber)
mockRepo.AssertExpectations(t)
})
}