222 lines
8.0 KiB
Go
222 lines
8.0 KiB
Go
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"zee/internal/adapter/postgres"
|
|
"zee/internal/resource/role"
|
|
"zee/internal/resource/user"
|
|
"zee/internal/service"
|
|
"zee/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 := postgres.NewUserRepository(gormDB)
|
|
roleRepo := postgres.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)
|
|
}
|
|
})
|
|
}
|