package service_test import ( "context" "testing" "github.com/user-management-system/internal/auth" "github.com/user-management-system/internal/domain" "github.com/user-management-system/internal/repository" "github.com/user-management-system/internal/service" ) // ============================================================================= // UserService CRUD Tests - Phase 1 (Simplified) // ============================================================================= func TestUserService_List(t *testing.T) { env := setupAuthTestEnv(t) if env == nil { return } ctx := context.Background() t.Run("List users", func(t *testing.T) { // Create multiple users for i := 0; i < 3; i++ { user := &domain.User{ Username: "listuser_" + string(rune('a'+i)), Password: "$2a$10$dummyhash", Status: domain.UserStatusActive, } env.userSvc.Create(ctx, user) } // List users, total, err := env.userSvc.List(ctx, 0, 10) if err != nil { t.Fatalf("List failed: %v", err) } if len(users) == 0 { t.Error("Expected users to be returned") } if total < 3 { t.Errorf("Expected total >= 3, got %d", total) } }) t.Run("List users with pagination", func(t *testing.T) { users, total, err := env.userSvc.List(ctx, 0, 2) if err != nil { t.Fatalf("List with pagination failed: %v", err) } if len(users) > 2 { t.Errorf("Expected max 2 users, got %d", len(users)) } if total == 0 { t.Error("Expected total > 0") } }) t.Run("List users with invalid pagination", func(t *testing.T) { // Test with negative offset _, _, err := env.userSvc.List(ctx, -1, 10) if err != nil { t.Errorf("List with negative offset should handle it gracefully: %v", err) } // Test with zero limit _, _, err = env.userSvc.List(ctx, 0, 0) if err != nil { t.Errorf("List with zero limit should handle it gracefully: %v", err) } }) } func TestUserService_GetByID(t *testing.T) { env := setupAuthTestEnv(t) if env == nil { return } ctx := context.Background() t.Run("Get user by ID success", func(t *testing.T) { // Create user user := &domain.User{ Username: "getbyid", Password: "$2a$10$dummyhash", Status: domain.UserStatusActive, } err := env.userSvc.Create(ctx, user) if err != nil { t.Fatalf("Create failed: %v", err) } // Get by ID found, err := env.userSvc.GetByID(ctx, user.ID) if err != nil { t.Fatalf("GetByID failed: %v", err) } if found.Username != "getbyid" { t.Errorf("Expected username 'getbyid', got %s", found.Username) } }) t.Run("Get user by ID not found", func(t *testing.T) { _, err := env.userSvc.GetByID(ctx, 99999) if err == nil { t.Error("Expected error for non-existent user") } }) t.Run("Get user by ID with zero ID", func(t *testing.T) { _, err := env.userSvc.GetByID(ctx, 0) if err == nil { t.Error("Expected error for zero ID") } }) } func TestUserService_Update(t *testing.T) { env := setupAuthTestEnv(t) if env == nil { return } ctx := context.Background() t.Run("Update user success", func(t *testing.T) { // Create user user := &domain.User{ Username: "updateuser", Password: "$2a$10$dummyhash", Status: domain.UserStatusActive, Nickname: "Old Nickname", } err := env.userSvc.Create(ctx, user) if err != nil { t.Fatalf("Create failed: %v", err) } // Update user.Nickname = "New Nickname" err = env.userSvc.Update(ctx, user) if err != nil { t.Fatalf("Update failed: %v", err) } // Verify updated, _ := env.userSvc.GetByID(ctx, user.ID) if updated.Nickname != "New Nickname" { t.Errorf("Expected nickname 'New Nickname', got %s", updated.Nickname) } }) t.Run("Update non-existent user", func(t *testing.T) { user := &domain.User{ ID: 99999, Username: "nonexistent", Password: "$2a$10$dummyhash", Status: domain.UserStatusActive, } err := env.userSvc.Update(ctx, user) // 实际实现可能静默处理 _ = err t.Logf("Update non-existent user returned: %v", err) }) } func TestUserService_UpdateStatus(t *testing.T) { env := setupAuthTestEnv(t) if env == nil { return } ctx := context.Background() t.Run("Update status success", func(t *testing.T) { // Create user user := &domain.User{ Username: "statususer", Password: "$2a$10$dummyhash", Status: domain.UserStatusActive, } err := env.userSvc.Create(ctx, user) if err != nil { t.Fatalf("Create failed: %v", err) } // Update status to locked err = env.userSvc.UpdateStatus(ctx, user.ID, domain.UserStatusLocked) if err != nil { t.Fatalf("UpdateStatus failed: %v", err) } // Verify updated, _ := env.userSvc.GetByID(ctx, user.ID) if updated.Status != domain.UserStatusLocked { t.Errorf("Expected status %d, got %d", domain.UserStatusLocked, updated.Status) } }) t.Run("Update status for non-existent user", func(t *testing.T) { err := env.userSvc.UpdateStatus(ctx, 99999, domain.UserStatusLocked) // 实际实现可能静默处理 _ = err t.Logf("UpdateStatus non-existent user returned: %v", err) }) } func TestUserService_Delete(t *testing.T) { env := setupAuthTestEnv(t) if env == nil { return } ctx := context.Background() t.Run("Delete user success", func(t *testing.T) { // Create user user := &domain.User{ Username: "deleteuser", Password: "$2a$10$dummyhash", Status: domain.UserStatusActive, } err := env.userSvc.Create(ctx, user) if err != nil { t.Fatalf("Create failed: %v", err) } // Delete err = env.userSvc.Delete(ctx, user.ID) if err != nil { t.Fatalf("Delete failed: %v", err) } // Verify deletion _, err = env.userSvc.GetByID(ctx, user.ID) if err == nil { t.Error("Expected error for deleted user") } }) t.Run("Delete non-existent user", func(t *testing.T) { err := env.userSvc.Delete(ctx, 99999) // 实际实现可能静默处理 _ = err t.Logf("Delete non-existent user returned: %v", err) }) } func TestUserService_ChangePassword(t *testing.T) { env := setupAuthTestEnv(t) if env == nil { return } ctx := context.Background() t.Run("Change password success", func(t *testing.T) { // Create user with known password hashedPassword, _ := auth.HashPassword("OldPassword123!") user := &domain.User{ Username: "changepwd", Password: hashedPassword, Status: domain.UserStatusActive, } env.userSvc.Create(ctx, user) // Change password err := env.userSvc.ChangePassword(ctx, user.ID, "OldPassword123!", "NewPassword456!") if err != nil { t.Fatalf("ChangePassword failed: %v", err) } // Verify new password works updated, _ := env.userSvc.GetByID(ctx, user.ID) if !auth.VerifyPassword(updated.Password, "NewPassword456!") { t.Error("New password verification failed") } }) t.Run("Change password with wrong old password", func(t *testing.T) { hashedPassword, _ := auth.HashPassword("CorrectPassword123!") user := &domain.User{ Username: "wrongpwd", Password: hashedPassword, Status: domain.UserStatusActive, } env.userSvc.Create(ctx, user) err := env.userSvc.ChangePassword(ctx, user.ID, "WrongPassword!", "NewPassword456!") if err == nil { t.Error("Expected error for wrong old password") } }) t.Run("Change password for non-existent user", func(t *testing.T) { err := env.userSvc.ChangePassword(ctx, 99999, "old", "NewPassword123!") if err == nil { t.Error("Expected error for non-existent user") } }) t.Run("Change password with empty old password", func(t *testing.T) { user := &domain.User{ Username: "emptypwd", Password: "$2a$10$dummyhash", Status: domain.UserStatusActive, } env.userSvc.Create(ctx, user) err := env.userSvc.ChangePassword(ctx, user.ID, "", "NewPassword123!") if err == nil { t.Error("Expected error for empty old password") } }) t.Run("Change password with empty new password", func(t *testing.T) { hashedPassword, _ := auth.HashPassword("OldPassword123!") user := &domain.User{ Username: "emptynewpwd", Password: hashedPassword, Status: domain.UserStatusActive, } env.userSvc.Create(ctx, user) err := env.userSvc.ChangePassword(ctx, user.ID, "OldPassword123!", "") if err == nil { t.Error("Expected error for empty new password") } }) t.Run("Change password with weak new password", func(t *testing.T) { hashedPassword, _ := auth.HashPassword("OldPassword123!") user := &domain.User{ Username: "weakpwd", Password: hashedPassword, Status: domain.UserStatusActive, } env.userSvc.Create(ctx, user) err := env.userSvc.ChangePassword(ctx, user.ID, "OldPassword123!", "123") if err == nil { t.Error("Expected error for weak new password") } }) t.Run("Change password persists history synchronously", func(t *testing.T) { hashedPassword, _ := auth.HashPassword("HistoryOld123!") user := &domain.User{ Username: "historysync", Password: hashedPassword, Status: domain.UserStatusActive, } env.userSvc.Create(ctx, user) if err := env.userSvc.ChangePassword(ctx, user.ID, "HistoryOld123!", "HistoryNew456!"); err != nil { t.Fatalf("ChangePassword failed: %v", err) } historyRepo := repository.NewPasswordHistoryRepository(env.db) history, err := historyRepo.GetByUserID(ctx, user.ID, 10) if err != nil { t.Fatalf("GetByUserID failed: %v", err) } if len(history) == 0 { t.Fatal("expected password history to be written synchronously") } if !auth.VerifyPassword(history[0].PasswordHash, "HistoryNew456!") { t.Fatal("latest password history hash does not match new password") } }) } func TestUserService_BatchUpdateStatus(t *testing.T) { env := setupAuthTestEnv(t) if env == nil { return } ctx := context.Background() // Create test users user1 := &domain.User{Username: "batch1", Password: "$2a$10$hash", Status: domain.UserStatusActive} user2 := &domain.User{Username: "batch2", Password: "$2a$10$hash", Status: domain.UserStatusActive} env.userSvc.Create(ctx, user1) env.userSvc.Create(ctx, user2) t.Run("Batch update status", func(t *testing.T) { _, err := env.userSvc.BatchUpdateStatus(ctx, &service.BatchUpdateStatusRequest{ IDs: []int64{user1.ID, user2.ID}, Status: domain.UserStatusLocked, }) if err != nil { t.Fatalf("BatchUpdateStatus failed: %v", err) } updated1, _ := env.userSvc.GetByID(ctx, user1.ID) if updated1.Status != domain.UserStatusLocked { t.Error("Expected user1 status to be locked") } }) } func TestUserService_Create(t *testing.T) { env := setupAuthTestEnv(t) if env == nil { return } ctx := context.Background() t.Run("Create user success", func(t *testing.T) { email := "create@test.com" user := &domain.User{ Username: "createuser", Password: "$2a$10$hash", Email: &email, Status: domain.UserStatusActive, } err := env.userSvc.Create(ctx, user) if err != nil { t.Fatalf("Create failed: %v", err) } if user.ID == 0 { t.Error("Expected user ID to be set") } }) t.Run("Create user with duplicate username", func(t *testing.T) { user1 := &domain.User{Username: "dupcreate", Password: "$2a$10$hash", Status: domain.UserStatusActive} env.userSvc.Create(ctx, user1) user2 := &domain.User{Username: "dupcreate", Password: "$2a$10$hash", Status: domain.UserStatusActive} err := env.userSvc.Create(ctx, user2) if err == nil { t.Error("Expected error for duplicate username") } }) } func TestUserService_GetByEmail(t *testing.T) { env := setupAuthTestEnv(t) if env == nil { return } ctx := context.Background() t.Run("Get user by email", func(t *testing.T) { email := "getbyemail@test.com" user := &domain.User{ Username: "emailuser", Password: "$2a$10$hash", Email: &email, Status: domain.UserStatusActive, } env.userSvc.Create(ctx, user) found, err := env.userSvc.GetByEmail(ctx, "getbyemail@test.com") if err != nil { t.Fatalf("GetByEmail failed: %v", err) } if found.Username != "emailuser" { t.Errorf("Expected username 'emailuser', got %s", found.Username) } }) t.Run("Get user by non-existent email", func(t *testing.T) { _, err := env.userSvc.GetByEmail(ctx, "nonexistent@test.com") if err == nil { t.Error("Expected error for non-existent email") } }) }