package handlers import ( "context" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) // MockHLSServiceForHLSHandler mocks HLSService type MockHLSServiceForHLSHandler struct { mock.Mock } func (m *MockHLSServiceForHLSHandler) GetMasterPlaylist(ctx context.Context, trackID uuid.UUID) (string, error) { args := m.Called(ctx, trackID) return args.String(0), args.Error(1) } func (m *MockHLSServiceForHLSHandler) GetQualityPlaylist(ctx context.Context, trackID uuid.UUID, bitrate string) (string, error) { args := m.Called(ctx, trackID, bitrate) return args.String(0), args.Error(1) } func (m *MockHLSServiceForHLSHandler) GetSegmentPath(ctx context.Context, trackID uuid.UUID, bitrate string, segment string) (string, error) { args := m.Called(ctx, trackID, bitrate, segment) return args.String(0), args.Error(1) } func (m *MockHLSServiceForHLSHandler) GetStreamInfo(ctx context.Context, trackID uuid.UUID) (map[string]interface{}, error) { args := m.Called(ctx, trackID) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).(map[string]interface{}), args.Error(1) } func (m *MockHLSServiceForHLSHandler) GetStreamStatus(ctx context.Context, trackID uuid.UUID) (map[string]interface{}, error) { args := m.Called(ctx, trackID) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).(map[string]interface{}), args.Error(1) } func (m *MockHLSServiceForHLSHandler) TriggerTranscodeQueue(ctx context.Context, trackID uuid.UUID, userID uuid.UUID) (uuid.UUID, error) { args := m.Called(ctx, trackID, userID) return args.Get(0).(uuid.UUID), args.Error(1) } func setupTestHLSRouter(mockService *MockHLSServiceForHLSHandler) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() handler := NewHLSHandlerWithInterface(mockService) api := router.Group("/api/v1/hls") api.Use(func(c *gin.Context) { userIDStr := c.GetHeader("X-User-ID") if userIDStr != "" { uid, err := uuid.Parse(userIDStr) if err == nil { c.Set("user_id", uid) } } c.Next() }) { api.GET("/tracks/:id/master.m3u8", handler.ServeMasterPlaylist) api.GET("/tracks/:id/:bitrate/playlist.m3u8", handler.ServeQualityPlaylist) api.GET("/tracks/:id/:bitrate/:segment", handler.ServeSegment) api.GET("/tracks/:id/info", handler.GetStreamInfo) api.GET("/tracks/:id/status", handler.GetStreamStatus) api.POST("/tracks/:id/transcode", handler.TriggerTranscode) } return router } func TestHLSHandler_ServeMasterPlaylist_Success(t *testing.T) { // Setup mockService := new(MockHLSServiceForHLSHandler) router := setupTestHLSRouter(mockService) trackID := uuid.New() expectedPlaylist := "#EXTM3U\n#EXT-X-VERSION:3\n" mockService.On("GetMasterPlaylist", mock.Anything, trackID).Return(expectedPlaylist, nil) // Execute req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/master.m3u8", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "application/vnd.apple.mpegurl", w.Header().Get("Content-Type")) assert.Equal(t, expectedPlaylist, w.Body.String()) mockService.AssertExpectations(t) } func TestHLSHandler_ServeMasterPlaylist_InvalidTrackID(t *testing.T) { // Setup mockService := new(MockHLSServiceForHLSHandler) router := setupTestHLSRouter(mockService) // Execute - Invalid UUID req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/invalid-id/master.m3u8", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusBadRequest, w.Code) mockService.AssertNotCalled(t, "GetMasterPlaylist") } func TestHLSHandler_ServeMasterPlaylist_NotFound(t *testing.T) { // Setup mockService := new(MockHLSServiceForHLSHandler) router := setupTestHLSRouter(mockService) trackID := uuid.New() mockService.On("GetMasterPlaylist", mock.Anything, trackID).Return("", assert.AnError) // Execute req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/master.m3u8", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusNotFound, w.Code) mockService.AssertExpectations(t) } func TestHLSHandler_ServeQualityPlaylist_Success(t *testing.T) { // Setup mockService := new(MockHLSServiceForHLSHandler) router := setupTestHLSRouter(mockService) trackID := uuid.New() bitrate := "128000" expectedPlaylist := "#EXTM3U\n#EXT-X-VERSION:3\n" mockService.On("GetQualityPlaylist", mock.Anything, trackID, bitrate).Return(expectedPlaylist, nil) // Execute req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/"+bitrate+"/playlist.m3u8", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "application/vnd.apple.mpegurl", w.Header().Get("Content-Type")) mockService.AssertExpectations(t) } func TestHLSHandler_ServeSegment_Success(t *testing.T) { // Setup mockService := new(MockHLSServiceForHLSHandler) router := setupTestHLSRouter(mockService) trackID := uuid.New() bitrate := "128000" segment := "segment001.ts" expectedPath := "/path/to/segment.ts" mockService.On("GetSegmentPath", mock.Anything, trackID, bitrate, segment).Return(expectedPath, nil) // Execute req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/"+bitrate+"/"+segment, nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "video/mp2t", w.Header().Get("Content-Type")) mockService.AssertExpectations(t) } func TestHLSHandler_GetStreamInfo_Success(t *testing.T) { // Setup mockService := new(MockHLSServiceForHLSHandler) router := setupTestHLSRouter(mockService) trackID := uuid.New() expectedInfo := map[string]interface{}{ "bitrates": []string{"128000", "256000"}, "status": "ready", } mockService.On("GetStreamInfo", mock.Anything, trackID).Return(expectedInfo, nil) // Execute req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/info", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) mockService.AssertExpectations(t) } func TestHLSHandler_GetStreamStatus_Success(t *testing.T) { // Setup mockService := new(MockHLSServiceForHLSHandler) router := setupTestHLSRouter(mockService) trackID := uuid.New() expectedStatus := map[string]interface{}{ "transcoding": false, "progress": 0.5, } mockService.On("GetStreamStatus", mock.Anything, trackID).Return(expectedStatus, nil) // Execute req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/status", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) mockService.AssertExpectations(t) } func TestHLSHandler_TriggerTranscode_Success(t *testing.T) { // Setup mockService := new(MockHLSServiceForHLSHandler) router := setupTestHLSRouter(mockService) userID := uuid.New() trackID := uuid.New() jobID := uuid.New() mockService.On("TriggerTranscodeQueue", mock.Anything, trackID, userID).Return(jobID, nil) // Execute req, _ := http.NewRequest("POST", "/api/v1/hls/tracks/"+trackID.String()+"/transcode", nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) mockService.AssertExpectations(t) } func TestHLSHandler_TriggerTranscode_Unauthorized(t *testing.T) { // Setup mockService := new(MockHLSServiceForHLSHandler) router := setupTestHLSRouter(mockService) trackID := uuid.New() // Execute - No X-User-ID header req, _ := http.NewRequest("POST", "/api/v1/hls/tracks/"+trackID.String()+"/transcode", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden) mockService.AssertNotCalled(t, "TriggerTranscodeQueue") }