add service
This commit is contained in:
449
internal/service/tests/auth_suite_test.go
Normal file
449
internal/service/tests/auth_suite_test.go
Normal file
@@ -0,0 +1,449 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gojuno/minimock/v3"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"smart-search-back/internal/mocks"
|
||||
"smart-search-back/internal/model"
|
||||
"smart-search-back/internal/service"
|
||||
"smart-search-back/pkg/crypto"
|
||||
apperrors "smart-search-back/pkg/errors"
|
||||
"smart-search-back/pkg/jwt"
|
||||
)
|
||||
|
||||
type Suite struct {
|
||||
suite.Suite
|
||||
|
||||
ctx context.Context
|
||||
authService service.AuthService
|
||||
userRepo *mocks.UserRepositoryMock
|
||||
sessionRepo *mocks.SessionRepositoryMock
|
||||
}
|
||||
|
||||
func newSuite(ctx context.Context) *Suite {
|
||||
return &Suite{ctx: ctx}
|
||||
}
|
||||
|
||||
func TestAuthService(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
suite.Run(t, newSuite(ctx))
|
||||
}
|
||||
|
||||
func (s *Suite) SetupSuite() {
|
||||
s.ctx = context.Background()
|
||||
}
|
||||
|
||||
func (s *Suite) SetupTest() {
|
||||
ctrl := minimock.NewController(s.T())
|
||||
|
||||
s.userRepo = mocks.NewUserRepositoryMock(ctrl)
|
||||
s.sessionRepo = mocks.NewSessionRepositoryMock(ctrl)
|
||||
|
||||
s.authService = service.NewAuthService(s.userRepo, s.sessionRepo)
|
||||
}
|
||||
|
||||
func createTestUser(password string) *model.User {
|
||||
return &model.User{
|
||||
ID: 1,
|
||||
Email: "test@example.com",
|
||||
EmailHash: crypto.EmailHash("test@example.com"),
|
||||
PasswordHash: crypto.PasswordHash(password),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func createTestSession(userID int) *model.Session {
|
||||
return &model.Session{
|
||||
ID: 1,
|
||||
UserID: userID,
|
||||
AccessToken: "test-access-token",
|
||||
RefreshToken: "test-refresh-token",
|
||||
IP: "127.0.0.1",
|
||||
UserAgent: "test-agent",
|
||||
CreatedAt: time.Now(),
|
||||
ExpiresAt: time.Now().Add(30 * 24 * time.Hour),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_Success() {
|
||||
password := "testpassword"
|
||||
user := createTestUser(password)
|
||||
|
||||
s.userRepo.FindByEmailHashMock.Return(user, nil)
|
||||
s.sessionRepo.CreateMock.Return(nil)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
password,
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.NoError(err)
|
||||
s.NotEmpty(accessToken)
|
||||
s.NotEmpty(refreshToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_UserNotFound() {
|
||||
err := apperrors.NewBusinessError(apperrors.UserNotFound, "user not found")
|
||||
s.userRepo.FindByEmailHashMock.Return(nil, err)
|
||||
|
||||
accessToken, refreshToken, loginErr := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
"password",
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.Error(loginErr)
|
||||
s.Empty(accessToken)
|
||||
s.Empty(refreshToken)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(loginErr, &appErr))
|
||||
s.Equal(apperrors.UserNotFound, appErr.Code)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_DatabaseError_OnFindUser() {
|
||||
dbErr := apperrors.NewInternalError(apperrors.DatabaseError, "failed to find user", nil)
|
||||
s.userRepo.FindByEmailHashMock.Return(nil, dbErr)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
"password",
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.Error(err)
|
||||
s.Empty(accessToken)
|
||||
s.Empty(refreshToken)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.InternalErrorType, appErr.Type)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_InvalidPassword() {
|
||||
password := "correctpassword"
|
||||
user := createTestUser(password)
|
||||
s.userRepo.FindByEmailHashMock.Return(user, nil)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
"wrongpassword",
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.Error(err)
|
||||
s.Empty(accessToken)
|
||||
s.Empty(refreshToken)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.AuthInvalidCredentials, appErr.Code)
|
||||
s.Contains(appErr.Message, "Invalid email or password")
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_EmptyPassword() {
|
||||
user := createTestUser("")
|
||||
s.userRepo.FindByEmailHashMock.Return(user, nil)
|
||||
s.sessionRepo.CreateMock.Return(nil)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
"",
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.NoError(err)
|
||||
s.NotEmpty(accessToken)
|
||||
s.NotEmpty(refreshToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_EmailWithSpacesAndCase() {
|
||||
password := "testpassword"
|
||||
normalizedEmail := "test@example.com"
|
||||
user := createTestUser(password)
|
||||
user.EmailHash = crypto.EmailHash(normalizedEmail)
|
||||
s.userRepo.FindByEmailHashMock.Return(user, nil)
|
||||
s.sessionRepo.CreateMock.Return(nil)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
" TEST@EXAMPLE.COM ",
|
||||
password,
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.NoError(err)
|
||||
s.NotEmpty(accessToken)
|
||||
s.NotEmpty(refreshToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_EmptyEmail() {
|
||||
err := apperrors.NewBusinessError(apperrors.UserNotFound, "user not found")
|
||||
s.userRepo.FindByEmailHashMock.Return(nil, err)
|
||||
|
||||
accessToken, refreshToken, loginErr := s.authService.Login(
|
||||
s.ctx,
|
||||
"",
|
||||
"password",
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.Error(loginErr)
|
||||
s.Empty(accessToken)
|
||||
s.Empty(refreshToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_SessionCreateError() {
|
||||
password := "testpassword"
|
||||
user := createTestUser(password)
|
||||
s.userRepo.FindByEmailHashMock.Return(user, nil)
|
||||
|
||||
dbErr := apperrors.NewInternalError(apperrors.DatabaseError, "failed to create session", nil)
|
||||
s.sessionRepo.CreateMock.Return(dbErr)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
password,
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.Error(err)
|
||||
s.Empty(accessToken)
|
||||
s.Empty(refreshToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_EmptyIPAndUserAgent() {
|
||||
password := "testpassword"
|
||||
user := createTestUser(password)
|
||||
s.userRepo.FindByEmailHashMock.Return(user, nil)
|
||||
s.sessionRepo.CreateMock.Return(nil)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
password,
|
||||
"",
|
||||
"",
|
||||
)
|
||||
|
||||
s.NoError(err)
|
||||
s.NotEmpty(accessToken)
|
||||
s.NotEmpty(refreshToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Refresh_Success() {
|
||||
session := createTestSession(1)
|
||||
s.sessionRepo.FindByRefreshTokenMock.Return(session, nil)
|
||||
s.sessionRepo.UpdateAccessTokenMock.Return(nil)
|
||||
|
||||
accessToken, err := s.authService.Refresh(s.ctx, "test-refresh-token")
|
||||
|
||||
s.NoError(err)
|
||||
s.NotEmpty(accessToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Refresh_RefreshInvalid() {
|
||||
err := apperrors.NewBusinessError(apperrors.RefreshInvalid, "refresh token is invalid or expired")
|
||||
s.sessionRepo.FindByRefreshTokenMock.Return(nil, err)
|
||||
|
||||
accessToken, refreshErr := s.authService.Refresh(s.ctx, "invalid-token")
|
||||
|
||||
s.Error(refreshErr)
|
||||
s.Empty(accessToken)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(refreshErr, &appErr))
|
||||
s.Equal(apperrors.RefreshInvalid, appErr.Code)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Refresh_DatabaseError_OnFindSession() {
|
||||
dbErr := apperrors.NewInternalError(apperrors.DatabaseError, "failed to find session", nil)
|
||||
s.sessionRepo.FindByRefreshTokenMock.Return(nil, dbErr)
|
||||
|
||||
accessToken, err := s.authService.Refresh(s.ctx, "test-refresh-token")
|
||||
|
||||
s.Error(err)
|
||||
s.Empty(accessToken)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.InternalErrorType, appErr.Type)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Refresh_EmptyToken() {
|
||||
err := apperrors.NewBusinessError(apperrors.RefreshInvalid, "refresh token is invalid or expired")
|
||||
s.sessionRepo.FindByRefreshTokenMock.Return(nil, err)
|
||||
|
||||
accessToken, refreshErr := s.authService.Refresh(s.ctx, "")
|
||||
|
||||
s.Error(refreshErr)
|
||||
s.Empty(accessToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Refresh_UpdateAccessTokenError() {
|
||||
session := createTestSession(1)
|
||||
s.sessionRepo.FindByRefreshTokenMock.Return(session, nil)
|
||||
|
||||
dbErr := apperrors.NewInternalError(apperrors.DatabaseError, "failed to update access token", nil)
|
||||
s.sessionRepo.UpdateAccessTokenMock.Return(dbErr)
|
||||
|
||||
accessToken, err := s.authService.Refresh(s.ctx, "test-refresh-token")
|
||||
|
||||
s.Error(err)
|
||||
s.Empty(accessToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Refresh_UserIDZero() {
|
||||
session := createTestSession(0)
|
||||
s.sessionRepo.FindByRefreshTokenMock.Return(session, nil)
|
||||
s.sessionRepo.UpdateAccessTokenMock.Return(nil)
|
||||
|
||||
accessToken, err := s.authService.Refresh(s.ctx, "test-refresh-token")
|
||||
|
||||
s.NoError(err)
|
||||
s.NotEmpty(accessToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Validate_Success() {
|
||||
s.T().Parallel()
|
||||
|
||||
userID := 1
|
||||
accessToken, err := jwt.GenerateAccessToken(userID)
|
||||
s.NoError(err)
|
||||
|
||||
validatedUserID, validateErr := s.authService.Validate(s.ctx, accessToken)
|
||||
|
||||
s.NoError(validateErr)
|
||||
s.Equal(userID, validatedUserID)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Validate_EmptyToken() {
|
||||
s.T().Parallel()
|
||||
|
||||
userID, err := s.authService.Validate(s.ctx, "")
|
||||
|
||||
s.Error(err)
|
||||
s.Equal(0, userID)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.AuthInvalidToken, appErr.Code)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Validate_InvalidTokenFormat() {
|
||||
s.T().Parallel()
|
||||
|
||||
userID, err := s.authService.Validate(s.ctx, "invalid.token.format")
|
||||
|
||||
s.Error(err)
|
||||
s.Equal(0, userID)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.AuthInvalidToken, appErr.Code)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Validate_RefreshTokenInsteadOfAccess() {
|
||||
s.T().Parallel()
|
||||
|
||||
userID := 1
|
||||
refreshToken, err := jwt.GenerateRefreshToken(userID)
|
||||
s.NoError(err)
|
||||
|
||||
validatedUserID, validateErr := s.authService.Validate(s.ctx, refreshToken)
|
||||
|
||||
s.Error(validateErr)
|
||||
s.Equal(0, validatedUserID)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(validateErr, &appErr))
|
||||
s.Equal(apperrors.AuthInvalidToken, appErr.Code)
|
||||
s.Contains(appErr.Message, "not an access token")
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Validate_UserIDZero() {
|
||||
s.T().Parallel()
|
||||
|
||||
accessToken, err := jwt.GenerateAccessToken(0)
|
||||
s.NoError(err)
|
||||
|
||||
validatedUserID, validateErr := s.authService.Validate(s.ctx, accessToken)
|
||||
|
||||
s.NoError(validateErr)
|
||||
s.Equal(0, validatedUserID)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Validate_InvalidSignature() {
|
||||
s.T().Parallel()
|
||||
|
||||
invalidToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwidHlwZSI6ImFjY2VzcyIsImlhdCI6MTYwOTQ1NjgwMCwiZXhwIjoxNjA5NDY1ODAwfQ.invalid-signature"
|
||||
|
||||
userID, err := s.authService.Validate(s.ctx, invalidToken)
|
||||
|
||||
s.Error(err)
|
||||
s.Equal(0, userID)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.AuthInvalidToken, appErr.Code)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Logout_Success() {
|
||||
s.sessionRepo.RevokeMock.Return(nil)
|
||||
|
||||
err := s.authService.Logout(s.ctx, "test-refresh-token")
|
||||
|
||||
s.NoError(err)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Logout_DatabaseError() {
|
||||
dbErr := apperrors.NewInternalError(apperrors.DatabaseError, "failed to revoke session", nil)
|
||||
s.sessionRepo.RevokeMock.Return(dbErr)
|
||||
|
||||
err := s.authService.Logout(s.ctx, "test-refresh-token")
|
||||
|
||||
s.Error(err)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.InternalErrorType, appErr.Type)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Logout_EmptyToken() {
|
||||
s.sessionRepo.RevokeMock.Return(nil)
|
||||
|
||||
err := s.authService.Logout(s.ctx, "")
|
||||
|
||||
s.NoError(err)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Logout_NonExistentToken() {
|
||||
s.sessionRepo.RevokeMock.Return(nil)
|
||||
|
||||
err := s.authService.Logout(s.ctx, "non-existent-token")
|
||||
|
||||
s.NoError(err)
|
||||
}
|
||||
Reference in New Issue
Block a user