|
@@ -0,0 +1,231 @@
|
|
|
|
|
+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) Delete(ctx context.Context, tokenHash string) error {
|
|
|
|
|
+ return 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)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|