Files

246 lines
9.0 KiB
Go
Raw Permalink Normal View History

package playback
import (
"context"
"encoding/json"
"errors"
"testing"
"github.com/Silo-Server/silo-server/internal/models"
)
type stubSubtitleInventoryResolver struct {
file *models.MediaFile
additional []SubtitleInventoryEntryV3
err error
additionalErr error
calls int
}
func (s *stubSubtitleInventoryResolver) MediaFile(context.Context, int) (*models.MediaFile, error) {
s.calls++
if s.err != nil {
return nil, s.err
}
return s.file, nil
}
func (s *stubSubtitleInventoryResolver) AdditionalSubtitles(context.Context, *models.MediaFile) ([]SubtitleInventoryEntryV3, error) {
return s.additional, s.additionalErr
}
// A generated track's realtime event carries the ordinal the next plan will
// publish, so the client selects it by identity instead of counting the tracks
// it can currently see.
func TestSubtitleReadyNotifierPublishesTheGeneratedTrackOrdinal(t *testing.T) {
sessions := NewSessionManager(0, 0)
session, _ := sessions.StartSession(1, "profile-a", 100, PlayDirect, false)
_ = sessions.SetRealtimeConnection(session.ID, true)
hub := NewRealtimeHub()
conn := &dispatchTestConn{}
reg := hub.Register(session.ID, conn)
defer hub.Unregister(reg)
file := &models.MediaFile{
ID: 100,
ExternalSubtitles: []models.ExternalSubtitle{
{Path: "/media/movie.en.srt", Language: "en", Format: "srt"},
},
SubtitleTracks: []models.SubtitleTrack{
{Index: 0, Language: "ja", Codec: "hdmv_pgs_subtitle"},
// A burn-in-only track the client never sees in subtitle_urls. The
// generated track still lands after it.
{Index: 1, Language: "de", Codec: "dvd_subtitle"},
},
}
resolver := &stubSubtitleInventoryResolver{
file: file,
additional: []SubtitleInventoryEntryV3{{CombinedIndex: 3, Codec: "srt", Source: SubtitleSourceDownloadedV3, Language: "es", Label: "Spanish (AI)", DownloadedSubtitleID: 77}},
}
notifier := NewSubtitleReadyNotifier(sessions, hub, resolver)
notifier.SubtitleReady(context.Background(), 100, 77, "es", "Spanish (AI)")
if len(conn.messages) != 1 {
t.Fatalf("messages = %d, want 1 subtitle ready event", len(conn.messages))
}
event, ok := conn.messages[0].(EventEnvelope)
if !ok {
t.Fatalf("message type = %T, want EventEnvelope", conn.messages[0])
}
var payload SubtitleReadyPayload
if err := json.Unmarshal(event.Payload, &payload); err != nil {
t.Fatalf("json.Unmarshal(payload): %v", err)
}
if payload.Track == nil {
t.Fatal("payload.Track is nil; the event must name the new track's ordinal")
}
if payload.Track.CombinedIndex != 3 {
t.Errorf("track combined_index = %d, want 3 (1 external + 2 embedded)", payload.Track.CombinedIndex)
}
if want := TrackIDV3(file.ID, "subtitle", 3); payload.Track.TrackID != want {
t.Errorf("track_id = %q, want %q", payload.Track.TrackID, want)
}
if payload.Track.Delivery != SubtitleDeliverySidecarV3 {
t.Errorf("track delivery = %q, want %q", payload.Track.Delivery, SubtitleDeliverySidecarV3)
}
if payload.Track.URL == "" {
t.Error("a sidecar track must carry a session-scoped URL")
}
}
func TestSubtitleReadyNotifierOmitsTrackWhenTheFileCannotBeResolved(t *testing.T) {
sessions := NewSessionManager(0, 0)
session, _ := sessions.StartSession(1, "profile-a", 100, PlayDirect, false)
_ = sessions.SetRealtimeConnection(session.ID, true)
hub := NewRealtimeHub()
conn := &dispatchTestConn{}
reg := hub.Register(session.ID, conn)
defer hub.Unregister(reg)
resolver := &stubSubtitleInventoryResolver{err: errors.New("file gone")}
notifier := NewSubtitleReadyNotifier(sessions, hub, resolver)
notifier.SubtitleReady(context.Background(), 100, 77, "es", "Spanish (AI)")
if len(conn.messages) != 1 {
t.Fatalf("messages = %d, want the event to be delivered without a track block", len(conn.messages))
}
var payload SubtitleReadyPayload
if err := json.Unmarshal(conn.messages[0].(EventEnvelope).Payload, &payload); err != nil {
t.Fatalf("json.Unmarshal(payload): %v", err)
}
if payload.Track != nil {
t.Errorf("payload.Track = %+v, want nil when the file cannot be resolved", payload.Track)
}
if payload.SubtitleID != 77 || payload.Language != "es" {
t.Errorf("payload lost its identifiers: %+v", payload)
}
}
func TestSubtitleReadyNotifierOmitsTrackWhenDownloadedInventoryCannotBeResolved(t *testing.T) {
sessions := NewSessionManager(0, 0)
session, _ := sessions.StartSession(1, "profile-a", 100, PlayDirect, false)
_ = sessions.SetRealtimeConnection(session.ID, true)
hub := NewRealtimeHub()
conn := &dispatchTestConn{}
reg := hub.Register(session.ID, conn)
defer hub.Unregister(reg)
resolver := &stubSubtitleInventoryResolver{
file: &models.MediaFile{ID: 100},
additionalErr: errors.New("subtitle repository unavailable"),
}
NewSubtitleReadyNotifier(sessions, hub, resolver).SubtitleReady(context.Background(), 100, 77, "es", "Spanish (AI)")
if len(conn.messages) != 1 {
t.Fatalf("messages = %d, want one metadata-only event", len(conn.messages))
}
var payload SubtitleReadyPayload
if err := json.Unmarshal(conn.messages[0].(EventEnvelope).Payload, &payload); err != nil {
t.Fatal(err)
}
if payload.Track != nil {
t.Fatalf("track = %#v, want nil when the ordinal inventory is unavailable", payload.Track)
}
}
func TestSubtitleReadyNotifierWorksWithoutAnInventoryResolver(t *testing.T) {
sessions := NewSessionManager(0, 0)
session, _ := sessions.StartSession(1, "profile-a", 100, PlayDirect, false)
_ = sessions.SetRealtimeConnection(session.ID, true)
hub := NewRealtimeHub()
conn := &dispatchTestConn{}
reg := hub.Register(session.ID, conn)
defer hub.Unregister(reg)
notifier := NewSubtitleReadyNotifier(sessions, hub, nil)
if notifier == nil {
t.Fatal("a nil inventory resolver must not disable the notifier")
}
notifier.SubtitleReady(context.Background(), 100, 77, "es", "Spanish (AI)")
if len(conn.messages) != 1 {
t.Fatalf("messages = %d, want 1", len(conn.messages))
}
}
func TestSubtitleReadyNotifierTranslationCompletedCarriesTheTrack(t *testing.T) {
sessions := NewSessionManager(0, 0)
session, _ := sessions.StartSession(1, "profile-a", 100, PlayDirect, false)
_ = sessions.SetRealtimeConnection(session.ID, true)
hub := NewRealtimeHub()
conn := &dispatchTestConn{}
reg := hub.Register(session.ID, conn)
defer hub.Unregister(reg)
resolver := &stubSubtitleInventoryResolver{
file: &models.MediaFile{ID: 100, SubtitleTracks: []models.SubtitleTrack{{Index: 0, Language: "en", Codec: "subrip"}}},
additional: []SubtitleInventoryEntryV3{{CombinedIndex: 1, Codec: "srt", Source: SubtitleSourceDownloadedV3, Language: "fr", Label: "French (AI)", DownloadedSubtitleID: 55}},
}
notifier := NewSubtitleReadyNotifier(sessions, hub, resolver)
notifier.TranslationCompleted(context.Background(), session.ID, 100, 9, "track-key", 55, "fr", "French (AI)")
if len(conn.messages) != 1 {
t.Fatalf("messages = %d, want 1 translation completed event", len(conn.messages))
}
var payload SubtitleTranslationCompletedPayload
if err := json.Unmarshal(conn.messages[0].(EventEnvelope).Payload, &payload); err != nil {
t.Fatalf("json.Unmarshal(payload): %v", err)
}
if payload.Track == nil || payload.Track.CombinedIndex != 1 {
t.Fatalf("payload.Track = %+v, want the ordinal-1 generated track", payload.Track)
}
if payload.JobID != 9 || payload.TrackKey != "track-key" || payload.SubtitleID != 55 {
t.Errorf("payload lost its identifiers: %+v", payload)
}
}
func TestSubtitleReadyNotifierMatchesDownloadedRowIdentity(t *testing.T) {
sessions := NewSessionManager(0, 0)
session, _ := sessions.StartSession(1, "profile-a", 100, PlayDirect, false)
_ = sessions.SetRealtimeConnection(session.ID, true)
hub := NewRealtimeHub()
conn := &dispatchTestConn{}
reg := hub.Register(session.ID, conn)
defer hub.Unregister(reg)
resolver := &stubSubtitleInventoryResolver{
file: &models.MediaFile{ID: 100},
additional: []SubtitleInventoryEntryV3{
{Codec: "srt", Source: SubtitleSourceDownloadedV3, Language: "es", Label: "Spanish", DownloadedSubtitleID: 77},
{Codec: "srt", Source: SubtitleSourceDownloadedV3, Language: "es", Label: "Spanish", DownloadedSubtitleID: 88},
},
}
NewSubtitleReadyNotifier(sessions, hub, resolver).SubtitleReady(context.Background(), 100, 77, "es", "Spanish")
var payload SubtitleReadyPayload
if err := json.Unmarshal(conn.messages[0].(EventEnvelope).Payload, &payload); err != nil {
t.Fatal(err)
}
if payload.Track == nil || payload.Track.CombinedIndex != 0 {
t.Fatalf("track = %#v, want exact row 77 at ordinal 0", payload.Track)
}
}
// Sessions without a realtime connection must not cost a repository lookup.
func TestSubtitleReadyNotifierSkipsSessionsWithoutRealtime(t *testing.T) {
sessions := NewSessionManager(0, 0)
session, _ := sessions.StartSession(1, "profile-a", 100, PlayDirect, false)
_ = session
hub := NewRealtimeHub()
resolver := &stubSubtitleInventoryResolver{file: &models.MediaFile{ID: 100}}
notifier := NewSubtitleReadyNotifier(sessions, hub, resolver)
notifier.SubtitleReady(context.Background(), 100, 77, "es", "Spanish (AI)")
if resolver.calls != 0 {
t.Errorf("resolver called %d times for a session with no realtime connection, want 0", resolver.calls)
}
}