package handlers import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "go.uber.org/zap/zaptest" "veza-backend-api/internal/models" "veza-backend-api/internal/services" ) // MockPlaybackAnalyticsService is a mock implementation of PlaybackAnalyticsService type MockPlaybackAnalyticsService struct { mock.Mock } func (m *MockPlaybackAnalyticsService) RecordPlayback(ctx context.Context, analytics *models.PlaybackAnalytics) error { args := m.Called(ctx, analytics) return args.Error(0) } func (m *MockPlaybackAnalyticsService) GetTrackStats(ctx context.Context, trackID uuid.UUID) (*services.PlaybackStats, error) { args := m.Called(ctx, trackID) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).(*services.PlaybackStats), args.Error(1) } func TestNewPlaybackWebSocketHandler(t *testing.T) { // Setup logger := zaptest.NewLogger(t) mockService := &MockPlaybackAnalyticsService{} // Execute handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger) // Assert assert.NotNil(t, handler) assert.NotNil(t, handler.analyticsService) assert.NotNil(t, handler.logger) assert.NotNil(t, handler.clients) assert.NotNil(t, handler.broadcast) assert.Equal(t, 0, handler.GetTotalConnectedClientsCount()) } func TestNewPlaybackWebSocketHandler_NilLogger(t *testing.T) { // Setup mockService := &MockPlaybackAnalyticsService{} // Execute handler := NewPlaybackWebSocketHandlerWithInterface(mockService, nil) // Assert assert.NotNil(t, handler) assert.NotNil(t, handler.logger) } func TestPlaybackWebSocketHandler_GetConnectedClientsCount(t *testing.T) { // Setup logger := zaptest.NewLogger(t) mockService := &MockPlaybackAnalyticsService{} handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger) // Execute - No clients connected count := handler.GetConnectedClientsCount(123) // Assert assert.Equal(t, 0, count) } func TestPlaybackWebSocketHandler_GetTotalConnectedClientsCount(t *testing.T) { // Setup logger := zaptest.NewLogger(t) mockService := &MockPlaybackAnalyticsService{} handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger) // Execute total := handler.GetTotalConnectedClientsCount() // Assert assert.Equal(t, 0, total) } func TestPlaybackWebSocketHandler_BroadcastAnalyticsUpdate(t *testing.T) { // Setup logger := zaptest.NewLogger(t) mockService := &MockPlaybackAnalyticsService{} handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger) trackID := int64(123) trackUUID := uuid.New() analytics := &models.PlaybackAnalytics{ TrackID: trackUUID, UserID: uuid.New(), PlayTime: 45, PauseCount: 2, SeekCount: 1, CompletionRate: 50.0, StartedAt: time.Now(), } // Execute - Should not panic even with no clients handler.BroadcastAnalyticsUpdate(trackID, analytics) // Assert - No error should occur assert.True(t, true) } func TestPlaybackWebSocketHandler_BroadcastAnalyticsUpdate_NilAnalytics(t *testing.T) { // Setup logger := zaptest.NewLogger(t) mockService := &MockPlaybackAnalyticsService{} handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger) // Execute - Should not panic with nil analytics handler.BroadcastAnalyticsUpdate(123, nil) // Assert - No error should occur assert.True(t, true) } func TestPlaybackWebSocketHandler_BroadcastStatsUpdate(t *testing.T) { // Setup logger := zaptest.NewLogger(t) mockService := &MockPlaybackAnalyticsService{} handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger) stats := &services.PlaybackStats{ TotalSessions: 100, TotalPlayTime: 3000, AveragePlayTime: 30.0, TotalPauses: 50, AveragePauses: 0.5, TotalSeeks: 25, AverageSeeks: 0.25, AverageCompletion: 75.0, CompletionRate: 60.0, } // Execute - Should not panic even with no clients handler.BroadcastStatsUpdate(123, stats) // Assert - No error should occur assert.True(t, true) } func TestPlaybackWebSocketHandler_BroadcastStatsUpdate_NilStats(t *testing.T) { // Setup logger := zaptest.NewLogger(t) mockService := &MockPlaybackAnalyticsService{} handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger) // Execute - Should not panic with nil stats handler.BroadcastStatsUpdate(123, nil) // Assert - No error should occur assert.True(t, true) } func TestPlaybackWebSocketHandler_WebSocketHandler_Unauthorized(t *testing.T) { // Setup gin.SetMode(gin.TestMode) router := gin.New() logger := zaptest.NewLogger(t) mockService := &MockPlaybackAnalyticsService{} handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger) router.GET("/ws", handler.WebSocketHandler) // Execute - Request without user ID req, _ := http.NewRequest("GET", "/ws", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusUnauthorized, w.Code) } func TestBroadcastMessage_MarshalJSON(t *testing.T) { // Setup msg := &BroadcastMessage{ TrackID: 123, Type: "analytics_update", Data: gin.H{"test": "data"}, Timestamp: time.Now(), } // Execute data, err := json.Marshal(msg) // Assert assert.NoError(t, err) assert.NotEmpty(t, data) var unmarshaled BroadcastMessage err = json.Unmarshal(data, &unmarshaled) assert.NoError(t, err) assert.Equal(t, msg.TrackID, unmarshaled.TrackID) assert.Equal(t, msg.Type, unmarshaled.Type) } func TestIncomingWebSocketMessage_MarshalJSON(t *testing.T) { // Setup msg := IncomingWebSocketMessage{ Type: "subscribe", TrackID: "123", Data: json.RawMessage(`{"test": "data"}`), } // Execute data, err := json.Marshal(msg) // Assert assert.NoError(t, err) assert.NotEmpty(t, data) var unmarshaled IncomingWebSocketMessage err = json.Unmarshal(data, &unmarshaled) assert.NoError(t, err) assert.Equal(t, msg.Type, unmarshaled.Type) assert.Equal(t, msg.TrackID, unmarshaled.TrackID) } func TestPlaybackWebSocketHandler_BroadcastChannelFull(t *testing.T) { // Setup logger := zaptest.NewLogger(t) mockService := &MockPlaybackAnalyticsService{} handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger) // Fill the broadcast channel trackID := int64(123) for i := 0; i < 257; i++ { msg := &BroadcastMessage{ TrackID: trackID, Type: "test", Data: gin.H{"index": i}, Timestamp: time.Now(), } select { case handler.broadcast <- msg: default: // Channel is full, this is expected } } trackUUID := uuid.New() analytics := &models.PlaybackAnalytics{ TrackID: trackUUID, UserID: uuid.New(), PlayTime: 45, PauseCount: 2, SeekCount: 1, CompletionRate: 50.0, StartedAt: time.Now(), } // Execute - Should not panic even when channel is full handler.BroadcastAnalyticsUpdate(123, analytics) // Assert - No error should occur assert.True(t, true) }