veza/veza-backend-api/internal/handlers/chat_handler_test.go

196 lines
5.8 KiB
Go
Raw Normal View History

2025-12-03 19:29:37 +00:00
package handlers
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"gorm.io/gorm"
"veza-backend-api/internal/models"
"veza-backend-api/internal/services"
)
type MockUserRepository struct {
users map[uuid.UUID]*models.User
}
func NewMockUserRepository() *MockUserRepository {
return &MockUserRepository{
users: make(map[uuid.UUID]*models.User),
}
}
func (m *MockUserRepository) CreateUser(ctx context.Context, user *models.User) error {
m.users[user.ID] = user
return nil
}
func (m *MockUserRepository) GetUserByID(ctx context.Context, id uuid.UUID) (*models.User, error) {
user, ok := m.users[id]
if !ok {
return nil, gorm.ErrRecordNotFound
}
return user, nil
}
func (m *MockUserRepository) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
panic("not implemented")
}
func (m *MockUserRepository) GetUserByUsername(ctx context.Context, username string) (*models.User, error) {
for _, user := range m.users {
if user.Username == username {
return user, nil
}
}
return nil, gorm.ErrRecordNotFound
}
func (m *MockUserRepository) UpdateUser(ctx context.Context, user *models.User) error {
m.users[user.ID] = user
return nil
}
func (m *MockUserRepository) DeleteUser(ctx context.Context, id uuid.UUID) error {
panic("not implemented")
}
func (m *MockUserRepository) UpdateLastLoginAt(ctx context.Context, userID uuid.UUID) error {
panic("not implemented")
}
func (m *MockUserRepository) IncrementTokenVersion(ctx context.Context, userID uuid.UUID) error {
panic("not implemented")
}
// Compatibility methods for services.UserRepository interface
func (m *MockUserRepository) GetByID(id string) (*models.User, error) {
idUUID, err := uuid.Parse(id)
if err != nil {
return nil, err
}
return m.GetUserByID(context.Background(), idUUID)
}
func (m *MockUserRepository) GetByEmail(email string) (*models.User, error) {
return m.GetUserByEmail(context.Background(), email)
}
func (m *MockUserRepository) GetByUsername(username string) (*models.User, error) {
return m.GetUserByUsername(context.Background(), username)
}
func (m *MockUserRepository) Create(user *models.User) error {
return m.CreateUser(context.Background(), user)
}
func (m *MockUserRepository) Update(user *models.User) error {
return m.UpdateUser(context.Background(), user)
}
func (m *MockUserRepository) Delete(id string) error {
idUUID, _ := uuid.Parse(id)
return m.DeleteUser(context.Background(), idUUID)
}
func setupTestChatHandler(t *testing.T) (*ChatHandler, *gin.Engine, func(), uuid.UUID) {
gin.SetMode(gin.TestMode)
logger := zap.NewNop()
jwtSecret := "supersecretchatkey"
chatService := services.NewChatService(jwtSecret, logger)
// Mock UserService
mockUserRepo := NewMockUserRepository()
userID := uuid.New()
mockUser := &models.User{
ID: userID,
Username: "testuser",
Email: "test@example.com",
// ... other fields as needed
}
mockUserRepo.CreateUser(context.Background(), mockUser)
userService := services.NewUserService(mockUserRepo)
handler := NewChatHandler(chatService, userService, logger)
r := gin.New()
// Simulate auth middleware setting user_id
r.Use(func(c *gin.Context) {
c.Set("user_id", userID) // Pass UUID object as middleware does
c.Set("username", "testuser")
c.Next()
})
r.POST("/chat/token", handler.GetToken)
cleanup := func() {
// No specific cleanup needed for these tests
}
return handler, r, cleanup, userID
}
func TestChatHandler_GetToken_Success(t *testing.T) {
_, r, cleanup, userID := setupTestChatHandler(t)
defer cleanup()
req := httptest.NewRequest(http.MethodPost, "/chat/token", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response APIResponse
2025-12-03 19:29:37 +00:00
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response.Success)
assert.Nil(t, response.Error)
// Data should be map/struct. Since it is interface{}, we need to marshal/unmarshal or type assert carefully.
// API sends ChatTokenResponse struct.
// Let's re-marshal Data to get ChatTokenResponse
dataBytes, _ := json.Marshal(response.Data)
var tokenResponse services.ChatTokenResponse
err = json.Unmarshal(dataBytes, &tokenResponse)
assert.NoError(t, err)
assert.NotEmpty(t, tokenResponse.Token)
assert.Greater(t, tokenResponse.ExpiresIn, int64(0))
assert.Equal(t, "/ws", tokenResponse.WSUrl)
2025-12-03 19:29:37 +00:00
// Optionally, verify token content
parsedToken, err := jwt.Parse(tokenResponse.Token, func(token *jwt.Token) (interface{}, error) {
2025-12-03 19:29:37 +00:00
assert.Equal(t, jwt.SigningMethodHS256, token.Method)
return []byte("supersecretchatkey"), nil
})
assert.NoError(t, err)
claims, ok := parsedToken.Claims.(jwt.MapClaims)
assert.True(t, ok)
assert.Equal(t, userID.String(), claims["sub"])
assert.Equal(t, "testuser", claims["name"])
}
func TestChatHandler_GetToken_Unauthorized(t *testing.T) {
logger := zap.NewNop()
jwtSecret := "supersecretchatkey"
chatService := services.NewChatService(jwtSecret, logger)
mockUserRepo := NewMockUserRepository()
userService := services.NewUserService(mockUserRepo)
handler := NewChatHandler(chatService, userService, logger)
r := gin.New()
r.POST("/chat/token", handler.GetToken) // No auth middleware
req := httptest.NewRequest(http.MethodPost, "/chat/token", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
var response map[string]interface{}
2025-12-03 19:29:37 +00:00
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// API might return standard error JSON or APIResponse depending on middleware
// The handler uses c.JSON(Unauthorized, gin.H{"error":...}) directly in manual checks
// See lines 41, 46 in handler.
2025-12-03 19:29:37 +00:00
assert.Equal(t, "unauthorized", response["error"])
}