zee-solution/internal/transport/http/handler/auth_register_test.go
2025-06-05 21:21:28 +07:00

222 lines
8.0 KiB
Go

package handler
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"starter-kit/internal/adapter/persistence"
"starter-kit/internal/domain/role"
"starter-kit/internal/domain/user"
"starter-kit/internal/service"
"starter-kit/internal/transport/http/dto"
"github.com/DATA-DOG/go-sqlmock"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// mock user repository với khả năng hook
type mockUserRepo struct {
user.Repository // nhúng interface để implement tự động
CreateFunc func(ctx context.Context, u *user.User) error
GetByIDFunc func(ctx context.Context, id string) (*user.User, error)
AddRoleFunc func(ctx context.Context, userID string, roleID int) error
}
func (m *mockUserRepo) Create(ctx context.Context, u *user.User) error {
if m.CreateFunc != nil {
return m.CreateFunc(ctx, u)
}
return nil
}
func (m *mockUserRepo) GetByID(ctx context.Context, id string) (*user.User, error) {
if m.GetByIDFunc != nil {
return m.GetByIDFunc(ctx, id)
}
return nil, nil
}
func (m *mockUserRepo) AddRole(ctx context.Context, userID string, roleID int) error {
if m.AddRoleFunc != nil {
return m.AddRoleFunc(ctx, userID, roleID)
}
return nil
}
func TestRegisterHandler(t *testing.T) {
// Thiết lập
gin.SetMode(gin.TestMode)
// UUID cố định cho bài test
testUserID := "123e4567-e89b-12d3-a456-426614174000"
// Tạo mock database
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp))
if err != nil {
t.Fatalf("Không thể tạo mock database: %v", err)
}
defer func() { _ = db.Close() }()
// Kết nối GORM
gormDB, err := gorm.Open(mysql.New(mysql.Config{
Conn: db,
SkipInitializeWithVersion: true,
}), &gorm.Config{})
if err != nil {
t.Fatalf("Không thể kết nối GORM: %v", err)
}
// Tạo repositories thật sẽ kết nối với mock DB
realUserRepo := persistence.NewUserRepository(gormDB)
roleRepo := persistence.NewRoleRepository(gormDB)
// Tạo mock repository với đầy đủ các phương thức cần thiết
mockedUserRepo := &mockUserRepo{
Repository: realUserRepo, // delegate các phương thức còn lại
CreateFunc: func(ctx context.Context, u *user.User) error {
// Chú ý: Trong thực tế, ID sẽ được tạo bởi DB (uuid_generate_v4())
// Nhưng vì đây là test, chúng ta cần giả lập việc DB thiết lập ID sau khi INSERT
// Gọi repository thật để thực thi SQL
err := realUserRepo.Create(ctx, u)
// Gán ID cố định sau khi tạo, giả lập việc DB tạo và trả về ID
u.ID = testUserID
return err
},
GetByIDFunc: func(ctx context.Context, id string) (*user.User, error) {
// Tạo user đủ thông tin với role đã preload
userRole := &role.Role{ID: 1, Name: "user", Description: "Basic user role"}
u := &user.User{
ID: testUserID,
Username: "testuser",
Email: "test@example.com",
FullName: "Test User",
AvatarURL: "",
IsActive: true,
Roles: []*role.Role{userRole}, // Gán role đã preload
}
return u, nil
},
AddRoleFunc: func(ctx context.Context, userID string, roleID int) error {
// Kiểm tra đảm bảo ID phù hợp
if userID != testUserID {
return fmt.Errorf("expected user ID %s but got %s", testUserID, userID)
}
// Khi chúng ta gọi AddRole của repo thật, nó sẽ thực thi câu lệnh SQL
return realUserRepo.AddRole(ctx, userID, roleID)
},
}
// Tạo service với mock userRepo
jwtSecret := "test-secret-key"
authSvc := service.NewAuthService(mockedUserRepo, roleRepo, jwtSecret, time.Duration(15)*time.Minute)
// Tạo handler
authHandler := NewAuthHandler(authSvc)
// Tạo router
r := gin.Default()
r.POST("/api/v1/auth/register", authHandler.Register)
// Dữ liệu đăng ký
registerData := dto.RegisterRequest{
Username: "testuser",
Email: "test@example.com",
Password: "password123",
FullName: "Test User",
}
// Chuyển đổi dữ liệu thành JSON
jsonData, err := json.Marshal(registerData)
if err != nil {
t.Fatalf("Lỗi chuyển đổi JSON: %v", err)
}
t.Run("Đăng ký tài khoản mới thành công", func(t *testing.T) {
// Setup các mong đợi SQL match chính xác với GORM theo logs và UserRepository implementation
// 1. Kiểm tra xem username đã tồn tại chưa (userRepo.GetByUsername)
mock.ExpectQuery("SELECT \\* FROM `users` WHERE username = \\? ORDER BY `users`\\.`id` LIMIT \\?").
WithArgs("testuser", 1).
WillReturnError(gorm.ErrRecordNotFound) // Username 'testuser' chưa tồn tại
// 2. Kiểm tra xem email đã tồn tại chưa (userRepo.GetByEmail)
mock.ExpectQuery("SELECT \\* FROM `users` WHERE email = \\? ORDER BY `users`\\.`id` LIMIT \\?").
WithArgs("test@example.com", 1).
WillReturnError(gorm.ErrRecordNotFound) // Email 'test@example.com' chưa tồn tại
// --- Sequence of operations after successful username/email checks and password hashing ---
// 3. Transaction for userRepo.Create (Implicit transaction by GORM)
mock.ExpectBegin()
// 4. Tạo user mới (userRepo.Create)
// Khi không đặt trước ID, GORM không đưa ID vào SQL, để DB tạo UUID tự động
mock.ExpectExec("^INSERT INTO `users` \\(`username`,`email`,`password_hash`,`full_name`,`avatar_url`,`is_active`,`last_login_at`,`created_at`,`updated_at`,`deleted_at`\\) VALUES \\(\\?,\\?,\\?,\\?,\\?,\\?,\\?,\\?,\\?,\\?\\)").
WithArgs(
"testuser", // username
"test@example.com", // email
sqlmock.AnyArg(), // password_hash
"Test User", // full_name
"", // avatar_url
true, // is_active
sqlmock.AnyArg(), // last_login_at
sqlmock.AnyArg(), // created_at
sqlmock.AnyArg(), // updated_at
sqlmock.AnyArg(), // deleted_at
).
WillReturnResult(sqlmock.NewResult(0, 1)) // UUID không có sequence ID, chỉ cần 1 row affected
mock.ExpectCommit()
// 5. Lấy role mặc định 'user' (roleRepo.GetByName)
mock.ExpectQuery("SELECT \\* FROM `roles` WHERE name = \\? ORDER BY `roles`\\.`id` LIMIT \\?").
WithArgs("user", 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "description", "created_at", "updated_at", "deleted_at"}).
AddRow(1, "user", "Basic user role", time.Now(), time.Now(), nil))
// 6. Thêm role cho user (userRepo.AddRole -> user_roles table)
// GORM's Create for user_roles có thể dùng 'INSERT ... ON CONFLICT'
mock.ExpectExec("INSERT INTO `user_roles` \\(`user_id`, `role_id`\\) VALUES \\(\\?\\, \\?\\)").
WithArgs(testUserID, 1). // user_id (UUID string), role_id (int)
WillReturnResult(sqlmock.NewResult(0, 1)) // Thêm thành công 1 row
// Chú ý: Vì chúng ta đã override mockUserRepo.GetByID và mockUserRepo.AddRole
// nên không cần mock SQL cho các query lấy thông tin user sau khi tạo
// mockUserRepo.GetByID sẽ trả về user đã có role được preload
// Tạo request
req, _ := http.NewRequest("POST", "/api/v1/auth/register", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
// Thực thi request
r.ServeHTTP(w, req)
// Kiểm tra kết quả
assert.Equal(t, http.StatusCreated, w.Code, "Status code phải là 201")
// Parse JSON response
var response dto.UserResponse
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err, "Parse JSON không có lỗi")
// Kiểm tra thông tin phản hồi
assert.Equal(t, registerData.Username, response.Username, "Username phải khớp")
assert.Equal(t, registerData.Email, response.Email, "Email phải khớp")
assert.Equal(t, registerData.FullName, response.FullName, "FullName phải khớp")
assert.NotEmpty(t, response.ID, "ID không được rỗng")
// Kiểm tra nếu có SQL expectations nào chưa được đáp ứng
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("Các expectations chưa được đáp ứng: %s", err)
}
})
}