package services import ( "context" "testing" "time" "gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/models" "golang.org/x/crypto/bcrypt" ) type mockUserRepo struct { users []*models.User } func (m *mockUserRepo) Create(ctx context.Context, user *models.User) error { m.users = append(m.users, user) return nil } func (m *mockUserRepo) GetByID(ctx context.Context, id string) (*models.User, error) { for _, u := range m.users { if u.ID == id { return u, nil } } return nil, nil } func (m *mockUserRepo) GetByEmail(ctx context.Context, email string) (*models.User, error) { for _, u := range m.users { if u.Email == email { return u, nil } } return nil, nil } func (m *mockUserRepo) Update(ctx context.Context, user *models.User) error { return nil } func (m *mockUserRepo) UpdateRole(ctx context.Context, id, role string) error { return nil } func (m *mockUserRepo) UpdateStatus(ctx context.Context, id, status string) error { return nil } func (m *mockUserRepo) List(ctx context.Context, filter models.UserFilter) ([]*models.User, error) { return m.users, nil } type mockRefreshTokenRepo struct { tokens []*models.RefreshToken } func (m *mockRefreshTokenRepo) Create(ctx context.Context, userID, plainToken string, expiresAt time.Time) error { return nil } func (m *mockRefreshTokenRepo) GetValid(ctx context.Context, plainToken string) (*models.RefreshToken, error) { return nil, nil } func (m *mockRefreshTokenRepo) Revoke(ctx context.Context, tokenHash string) error { return nil } func (m *mockRefreshTokenRepo) RevokeAllForUser(ctx context.Context, userID string) error { return nil } func (m *mockRefreshTokenRepo) GetRevoked(ctx context.Context, plainToken string) (*models.RefreshToken, error) { return nil, nil } func (m *mockRefreshTokenRepo) Delete(ctx context.Context, tokenHash string) error { return nil } func (m *mockRefreshTokenRepo) ReplaceIfExists(ctx context.Context, tokenHash string) (bool, error) { return true, nil } func (m *mockRefreshTokenRepo) CleanupExpired(ctx context.Context) error { return nil } func TestAuthService_Register_Success(t *testing.T) { userRepo := &mockUserRepo{} refreshRepo := &mockRefreshTokenRepo{} svc := NewAuthService(userRepo, refreshRepo, "test-secret", "test-refresh-secret") input := RegisterInput{ Email: "test@example.com", Password: "password123", Role: "customer", } result, refreshToken, err := svc.Register(context.Background(), input) if err != nil { t.Fatalf("unexpected error: %v", err) } if result.User.Email != "test@example.com" { t.Errorf("expected email test@example.com, got %s", result.User.Email) } if result.User.Role != "customer" { t.Errorf("expected role customer, got %s", result.User.Role) } if refreshToken == "" { t.Error("expected non-empty refresh token") } if result.AccessToken == "" { t.Error("expected non-empty access token") } } func TestAuthService_Register_DuplicateEmail(t *testing.T) { userRepo := &mockUserRepo{} refreshRepo := &mockRefreshTokenRepo{} svc := NewAuthService(userRepo, refreshRepo, "test-secret", "test-refresh-secret") hash, _ := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost) userRepo.users = append(userRepo.users, &models.User{ Email: "existing@example.com", PasswordHash: string(hash), Role: "customer", }) input := RegisterInput{ Email: "existing@example.com", Password: "password123", Role: "customer", } _, _, err := svc.Register(context.Background(), input) if err != ErrEmailExists { t.Errorf("expected ErrEmailExists, got %v", err) } } func TestAuthService_Login_InvalidPassword(t *testing.T) { userRepo := &mockUserRepo{} refreshRepo := &mockRefreshTokenRepo{} svc := NewAuthService(userRepo, refreshRepo, "test-secret", "test-refresh-secret") hash, _ := bcrypt.GenerateFromPassword([]byte("correct-password"), bcrypt.DefaultCost) userRepo.users = append(userRepo.users, &models.User{ ID: "user-1", Email: "test@example.com", PasswordHash: string(hash), Role: "customer", Status: "active", }) input := LoginInput{ Email: "test@example.com", Password: "wrong-password", } _, _, err := svc.Login(context.Background(), input) if err != ErrInvalidCreds { t.Errorf("expected ErrInvalidCreds, got %v", err) } } func TestAuthService_Login_BannedUser(t *testing.T) { userRepo := &mockUserRepo{} refreshRepo := &mockRefreshTokenRepo{} svc := NewAuthService(userRepo, refreshRepo, "test-secret", "test-refresh-secret") hash, _ := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost) userRepo.users = append(userRepo.users, &models.User{ ID: "user-1", Email: "banned@example.com", PasswordHash: string(hash), Role: "customer", Status: "banned", }) input := LoginInput{ Email: "banned@example.com", Password: "password", } _, _, err := svc.Login(context.Background(), input) if err != ErrUserBanned { t.Errorf("expected ErrUserBanned, got %v", err) } } func TestAuthService_ValidateAccessToken(t *testing.T) { userRepo := &mockUserRepo{} refreshRepo := &mockRefreshTokenRepo{} svc := NewAuthService(userRepo, refreshRepo, "test-secret", "test-refresh-secret") hash, _ := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost) userRepo.users = append(userRepo.users, &models.User{ ID: "user-1", Email: "test@example.com", PasswordHash: string(hash), Role: "moderator", Status: "active", }) input := RegisterInput{ Email: "test@example.com", Password: "password", Role: "moderator", } result, _, err := svc.Register(context.Background(), input) if err != nil { t.Fatalf("register failed: %v", err) } claims, err := svc.ValidateAccessToken(result.AccessToken) if err != nil { t.Fatalf("token validation failed: %v", err) } if claims.Role != "moderator" { t.Errorf("expected role moderator, got %s", claims.Role) } } func TestAuthService_ValidateAccessToken_Invalid(t *testing.T) { userRepo := &mockUserRepo{} refreshRepo := &mockRefreshTokenRepo{} svc := NewAuthService(userRepo, refreshRepo, "test-secret", "test-refresh-secret") _, err := svc.ValidateAccessToken("invalid-token") if err != ErrInvalidToken { t.Errorf("expected ErrInvalidToken, got %v", err) } }