281 行
8.5 KiB
Go
281 行
8.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestMediaHealthAndSessionLifecycle(t *testing.T) {
|
|
store, err := newSessionStore(t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("newSessionStore: %v", err)
|
|
}
|
|
|
|
server := newMediaServer(store)
|
|
|
|
healthReq := httptest.NewRequest(http.MethodGet, "/media/health", nil)
|
|
healthRes := httptest.NewRecorder()
|
|
server.routes().ServeHTTP(healthRes, healthReq)
|
|
if healthRes.Code != http.StatusOK {
|
|
t.Fatalf("expected health 200, got %d", healthRes.Code)
|
|
}
|
|
|
|
session, err := store.createSession(CreateSessionRequest{
|
|
UserID: "1",
|
|
Title: "Test Session",
|
|
Format: "webm",
|
|
MimeType: "video/webm",
|
|
QualityPreset: "balanced",
|
|
FacingMode: "environment",
|
|
DeviceKind: "desktop",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("createSession: %v", err)
|
|
}
|
|
|
|
if _, err := store.updateSession(session.ID, func(current *Session) error {
|
|
current.Segments = append(current.Segments, SegmentMeta{
|
|
Sequence: 0,
|
|
Filename: "000000.webm",
|
|
DurationMS: 60000,
|
|
SizeBytes: 7,
|
|
ContentType: "video/webm",
|
|
})
|
|
current.Markers = append(current.Markers, Marker{
|
|
ID: "marker-1",
|
|
Type: "manual",
|
|
Label: "关键片段",
|
|
Timestamp: 5000,
|
|
CreatedAt: "2026-03-14T00:00:00Z",
|
|
})
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatalf("updateSession: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(filepath.Join(store.segmentsDir(session.ID), "000000.webm"), []byte("segment"), 0o644); err != nil {
|
|
t.Fatalf("write segment: %v", err)
|
|
}
|
|
|
|
current, err := store.getSession(session.ID)
|
|
if err != nil {
|
|
t.Fatalf("getSession: %v", err)
|
|
}
|
|
if current.UploadedSegments != 1 {
|
|
t.Fatalf("expected uploaded segment count to be recomputed")
|
|
}
|
|
if current.UploadedBytes != 7 {
|
|
t.Fatalf("expected uploaded bytes to be recomputed, got %d", current.UploadedBytes)
|
|
}
|
|
}
|
|
|
|
func TestProcessSessionArchivesPlayback(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
store, err := newSessionStore(tempDir)
|
|
if err != nil {
|
|
t.Fatalf("newSessionStore: %v", err)
|
|
}
|
|
|
|
session, err := store.createSession(CreateSessionRequest{UserID: "1", Title: "Archive Session"})
|
|
if err != nil {
|
|
t.Fatalf("createSession: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(filepath.Join(store.segmentsDir(session.ID), "000000.webm"), []byte("segment"), 0o644); err != nil {
|
|
t.Fatalf("write segment: %v", err)
|
|
}
|
|
|
|
if _, err := store.updateSession(session.ID, func(current *Session) error {
|
|
current.Segments = append(current.Segments, SegmentMeta{
|
|
Sequence: 0,
|
|
Filename: "000000.webm",
|
|
DurationMS: 60000,
|
|
SizeBytes: 7,
|
|
ContentType: "video/webm",
|
|
})
|
|
current.ArchiveStatus = ArchiveQueued
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatalf("updateSession: %v", err)
|
|
}
|
|
|
|
fakeFFmpeg := filepath.Join(tempDir, "ffmpeg")
|
|
script := "#!/bin/sh\ninput=''\noutput=''\nprev=''\nfor arg in \"$@\"; do\n if [ \"$prev\" = '-i' ]; then input=\"$arg\"; fi\n prev=\"$arg\"\n output=\"$arg\"\ndone\nif [ -n \"$input\" ] && [ -f \"$input\" ]; then cp \"$input\" \"$output\"; else : > \"$output\"; fi\n"
|
|
if err := os.WriteFile(fakeFFmpeg, []byte(script), 0o755); err != nil {
|
|
t.Fatalf("write fake ffmpeg: %v", err)
|
|
}
|
|
|
|
originalPath := os.Getenv("PATH")
|
|
t.Setenv("PATH", tempDir+string(os.PathListSeparator)+originalPath)
|
|
|
|
if err := processSession(store, session.ID); err != nil {
|
|
t.Fatalf("processSession: %v", err)
|
|
}
|
|
|
|
archived, err := store.getSession(session.ID)
|
|
if err != nil {
|
|
t.Fatalf("getSession: %v", err)
|
|
}
|
|
|
|
if archived.ArchiveStatus != ArchiveCompleted {
|
|
t.Fatalf("expected archive completed, got %s", archived.ArchiveStatus)
|
|
}
|
|
if archived.Playback.WebMURL == "" || !strings.HasSuffix(archived.Playback.WebMURL, ".webm") {
|
|
t.Fatalf("expected webm playback url, got %#v", archived.Playback)
|
|
}
|
|
}
|
|
|
|
func TestRefreshFromDiskPicksUpSessionsCreatedAfterWorkerStartup(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
workerStore, err := newSessionStore(tempDir)
|
|
if err != nil {
|
|
t.Fatalf("newSessionStore(worker): %v", err)
|
|
}
|
|
if got := len(workerStore.listProcessableSessions()); got != 0 {
|
|
t.Fatalf("expected no processable sessions at startup, got %d", got)
|
|
}
|
|
|
|
appStore, err := newSessionStore(tempDir)
|
|
if err != nil {
|
|
t.Fatalf("newSessionStore(app): %v", err)
|
|
}
|
|
|
|
session, err := appStore.createSession(CreateSessionRequest{UserID: "1", Title: "Queued Session"})
|
|
if err != nil {
|
|
t.Fatalf("createSession: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(appStore.segmentsDir(session.ID), "000000.webm"), []byte("segment"), 0o644); err != nil {
|
|
t.Fatalf("write segment: %v", err)
|
|
}
|
|
if _, err := appStore.updateSession(session.ID, func(current *Session) error {
|
|
current.Segments = append(current.Segments, SegmentMeta{
|
|
Sequence: 0,
|
|
Filename: "000000.webm",
|
|
DurationMS: 60000,
|
|
SizeBytes: 7,
|
|
ContentType: "video/webm",
|
|
})
|
|
current.ArchiveStatus = ArchiveQueued
|
|
current.Status = StatusFinalizing
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatalf("updateSession: %v", err)
|
|
}
|
|
|
|
if err := workerStore.refreshFromDisk(); err != nil {
|
|
t.Fatalf("refreshFromDisk: %v", err)
|
|
}
|
|
|
|
processable := workerStore.listProcessableSessions()
|
|
if len(processable) != 1 {
|
|
t.Fatalf("expected worker to pick up queued session after refresh, got %d", len(processable))
|
|
}
|
|
if processable[0].ID != session.ID {
|
|
t.Fatalf("expected session %s, got %s", session.ID, processable[0].ID)
|
|
}
|
|
}
|
|
|
|
func TestHandleSessionGetRefreshesSessionStateFromDisk(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
serverStore, err := newSessionStore(tempDir)
|
|
if err != nil {
|
|
t.Fatalf("newSessionStore(server): %v", err)
|
|
}
|
|
server := newMediaServer(serverStore)
|
|
|
|
writerStore, err := newSessionStore(tempDir)
|
|
if err != nil {
|
|
t.Fatalf("newSessionStore(writer): %v", err)
|
|
}
|
|
|
|
session, err := writerStore.createSession(CreateSessionRequest{UserID: "1", Title: "Fresh Session"})
|
|
if err != nil {
|
|
t.Fatalf("createSession: %v", err)
|
|
}
|
|
if _, err := writerStore.updateSession(session.ID, func(current *Session) error {
|
|
current.Status = StatusFinalizing
|
|
current.ArchiveStatus = ArchiveQueued
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatalf("queue session: %v", err)
|
|
}
|
|
|
|
getReq := httptest.NewRequest(http.MethodGet, "/media/sessions/"+session.ID, nil)
|
|
getRes := httptest.NewRecorder()
|
|
server.routes().ServeHTTP(getRes, getReq)
|
|
if getRes.Code != http.StatusOK {
|
|
t.Fatalf("expected get session 200, got %d", getRes.Code)
|
|
}
|
|
|
|
var queuedResponse struct {
|
|
Session Session `json:"session"`
|
|
}
|
|
if err := json.NewDecoder(getRes.Body).Decode(&queuedResponse); err != nil {
|
|
t.Fatalf("decode queued response: %v", err)
|
|
}
|
|
if queuedResponse.Session.ArchiveStatus != ArchiveQueued {
|
|
t.Fatalf("expected queued archive status, got %s", queuedResponse.Session.ArchiveStatus)
|
|
}
|
|
|
|
if _, err := writerStore.updateSession(session.ID, func(current *Session) error {
|
|
current.Status = StatusArchived
|
|
current.ArchiveStatus = ArchiveCompleted
|
|
current.Playback = PlaybackInfo{
|
|
WebMURL: "/media/assets/sessions/" + session.ID + "/recording.webm",
|
|
Ready: true,
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatalf("complete session: %v", err)
|
|
}
|
|
|
|
refreshReq := httptest.NewRequest(http.MethodGet, "/media/sessions/"+session.ID, nil)
|
|
refreshRes := httptest.NewRecorder()
|
|
server.routes().ServeHTTP(refreshRes, refreshReq)
|
|
if refreshRes.Code != http.StatusOK {
|
|
t.Fatalf("expected refreshed get session 200, got %d", refreshRes.Code)
|
|
}
|
|
|
|
var completedResponse struct {
|
|
Session Session `json:"session"`
|
|
}
|
|
if err := json.NewDecoder(refreshRes.Body).Decode(&completedResponse); err != nil {
|
|
t.Fatalf("decode completed response: %v", err)
|
|
}
|
|
if completedResponse.Session.ArchiveStatus != ArchiveCompleted {
|
|
t.Fatalf("expected completed archive status, got %s", completedResponse.Session.ArchiveStatus)
|
|
}
|
|
if !completedResponse.Session.Playback.Ready {
|
|
t.Fatalf("expected playback ready after refresh")
|
|
}
|
|
}
|
|
|
|
func TestViewerSignalReturnsConflictBeforePublisherTrackReady(t *testing.T) {
|
|
store, err := newSessionStore(t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("newSessionStore: %v", err)
|
|
}
|
|
|
|
server := newMediaServer(store)
|
|
session, err := store.createSession(CreateSessionRequest{UserID: "1", Title: "Viewer Pending"})
|
|
if err != nil {
|
|
t.Fatalf("createSession: %v", err)
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/media/sessions/"+session.ID+"/viewer-signal", strings.NewReader(`{"type":"offer","sdp":"mock-offer"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
res := httptest.NewRecorder()
|
|
server.routes().ServeHTTP(res, req)
|
|
|
|
if res.Code != http.StatusConflict {
|
|
t.Fatalf("expected viewer-signal 409 before video track is ready, got %d", res.Code)
|
|
}
|
|
}
|