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 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) // Optionally, verify token content parsedToken, err := jwt.Parse(tokenResponse.Token, func(token *jwt.Token) (interface{}, error) { 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{} 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. assert.Equal(t, "unauthorized", response["error"]) }