veza/veza-backend-api/internal/handlers/hls_handler_test.go
senke 286be8ba1d
Some checks failed
Backend API CI / test-unit (push) Failing after 0s
Backend API CI / test-integration (push) Failing after 0s
Frontend CI / test (push) Failing after 0s
Storybook Audit / Build & audit Storybook (push) Failing after 0s
chore(v0.102): consolidate remaining changes — docs, frontend, backend
- docs: SCOPE_CONTROL, CONTRIBUTING, README, .github templates
- frontend: DeveloperDashboardView, Player components, MSW handlers, auth, reactQuerySync
- backend: playback_analytics, playlist_service, testutils, integration README

Excluded (artifacts): .auth, playwright-report, test-results, storybook_audit_detailed.json
2026-02-20 13:02:12 +01:00

274 lines
8.2 KiB
Go

package handlers
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"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) {
// Create a temp file for the segment (c.File requires file to exist)
tmpDir := t.TempDir()
segmentPath := filepath.Join(tmpDir, "segment001.ts")
err := os.WriteFile(segmentPath, []byte("fake ts content"), 0644)
assert.NoError(t, err)
mockService := new(MockHLSServiceForHLSHandler)
router := setupTestHLSRouter(mockService)
trackID := uuid.New()
bitrate := "128000"
segment := "segment001.ts"
mockService.On("GetSegmentPath", mock.Anything, trackID, bitrate, segment).Return(segmentPath, nil)
req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/"+bitrate+"/"+segment, nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "video/mp2t", w.Header().Get("Content-Type"))
assert.Equal(t, "fake ts content", w.Body.String())
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 - 202 Accepted for async job submission
assert.Equal(t, http.StatusAccepted, 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")
}