playlist-to-session bridge
This commit is contained in:
@@ -23,6 +23,7 @@ type PlaylistState struct {
|
||||
var (
|
||||
ErrPlaylistEmpty = errors.New("playlist is empty")
|
||||
ErrPlaylistIndexOutOfRange = errors.New("playlist index is out of range")
|
||||
ErrPlaylistNoSelection = errors.New("playlist has no selected entry")
|
||||
ErrUnknownPlaylistCommand = errors.New("unknown playlist command")
|
||||
)
|
||||
|
||||
@@ -91,3 +92,39 @@ func (s PlaylistState) Entry(playlist Playlist) (PlaylistEntry, bool) {
|
||||
}
|
||||
return playlist.Entries[s.CurrentIndex], true
|
||||
}
|
||||
|
||||
func ApplyPlaylistSelection(
|
||||
playlist Playlist,
|
||||
current PlaylistState,
|
||||
command PlaylistCommand,
|
||||
retry RetryPolicy,
|
||||
) (
|
||||
next PlaylistState,
|
||||
sessionCommand SessionCommand,
|
||||
apply bool,
|
||||
err error,
|
||||
) {
|
||||
next, err = ApplyPlaylistCommand(playlist, current, command)
|
||||
if err != nil {
|
||||
return current, SessionCommand{}, false, err
|
||||
}
|
||||
|
||||
apply = command.Kind == PlaylistSelect || next != current
|
||||
if !apply {
|
||||
return next, SessionCommand{}, false, nil
|
||||
}
|
||||
|
||||
entry, ok := next.Entry(playlist)
|
||||
if !ok {
|
||||
return current, SessionCommand{}, false, ErrPlaylistNoSelection
|
||||
}
|
||||
session := entry.SessionConfig(retry)
|
||||
if err := session.Validate(); err != nil {
|
||||
return current, SessionCommand{}, false, err
|
||||
}
|
||||
|
||||
return next, SessionCommand{
|
||||
Kind: CommandSetSession,
|
||||
Session: session,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
package playback
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestApplyPlaylistSelectionFirstNext(t *testing.T) {
|
||||
retry := validPlaylistRetryPolicy()
|
||||
|
||||
next, command, apply, err := ApplyPlaylistSelection(
|
||||
navigationPlaylist(false),
|
||||
PlaylistState{},
|
||||
PlaylistCommand{Kind: PlaylistNext},
|
||||
retry,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyPlaylistSelection() error = %v", err)
|
||||
}
|
||||
if !apply {
|
||||
t.Fatal("ApplyPlaylistSelection() apply = false, want true")
|
||||
}
|
||||
wantState := PlaylistState{CurrentIndex: 0, HasSelection: true}
|
||||
if next != wantState {
|
||||
t.Fatalf("ApplyPlaylistSelection() state = %#v, want %#v", next, wantState)
|
||||
}
|
||||
wantCommand := SessionCommand{
|
||||
Kind: CommandSetSession,
|
||||
Session: SessionConfig{
|
||||
Video: FeedConfig{Domain: "domain", UUID: "video-1", Active: true},
|
||||
Retry: retry,
|
||||
},
|
||||
}
|
||||
if command != wantCommand {
|
||||
t.Fatalf("ApplyPlaylistSelection() command = %#v, want %#v", command, wantCommand)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyPlaylistSelectionUsesCompleteEntrySession(t *testing.T) {
|
||||
retry := validPlaylistRetryPolicy()
|
||||
playlist := Playlist{Entries: []PlaylistEntry{
|
||||
{
|
||||
Video: PlaylistFeed{Domain: "video-domain", UUID: "video"},
|
||||
Audio: PlaylistFeed{Domain: "audio-domain", UUID: "audio"},
|
||||
SyncRequested: true,
|
||||
},
|
||||
{Video: PlaylistFeed{Domain: "next-domain", UUID: "next-video"}},
|
||||
}}
|
||||
|
||||
_, command, apply, err := ApplyPlaylistSelection(
|
||||
playlist,
|
||||
PlaylistState{CurrentIndex: 1, HasSelection: true},
|
||||
PlaylistCommand{Kind: PlaylistSelect, Index: 0},
|
||||
retry,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyPlaylistSelection() error = %v", err)
|
||||
}
|
||||
if !apply {
|
||||
t.Fatal("ApplyPlaylistSelection() apply = false, want true")
|
||||
}
|
||||
want := SessionConfig{
|
||||
Video: FeedConfig{Domain: "video-domain", UUID: "video", Active: true},
|
||||
Audio: FeedConfig{Domain: "audio-domain", UUID: "audio", Active: true},
|
||||
SyncRequested: true,
|
||||
Retry: retry,
|
||||
}
|
||||
if command.Kind != CommandSetSession || command.Session != want {
|
||||
t.Fatalf("ApplyPlaylistSelection() command = %#v, want session %#v", command, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyPlaylistSelectionVideoOnlyClearsAudio(t *testing.T) {
|
||||
_, command, apply, err := ApplyPlaylistSelection(
|
||||
navigationPlaylist(false),
|
||||
PlaylistState{CurrentIndex: 1, HasSelection: true},
|
||||
PlaylistCommand{Kind: PlaylistSelect, Index: 0},
|
||||
validPlaylistRetryPolicy(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyPlaylistSelection() error = %v", err)
|
||||
}
|
||||
if !apply {
|
||||
t.Fatal("ApplyPlaylistSelection() apply = false, want true")
|
||||
}
|
||||
if command.Session.Audio != (FeedConfig{}) {
|
||||
t.Fatalf("ApplyPlaylistSelection() audio = %#v, want zero value", command.Session.Audio)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyPlaylistSelectionApplicationDecision(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
loop bool
|
||||
current int
|
||||
command PlaylistCommand
|
||||
want int
|
||||
apply bool
|
||||
}{
|
||||
{name: "reselect current", current: 1, command: PlaylistCommand{Kind: PlaylistSelect, Index: 1}, want: 1, apply: true},
|
||||
{name: "move next", current: 1, command: PlaylistCommand{Kind: PlaylistNext}, want: 2, apply: true},
|
||||
{name: "next stops at end", current: 2, command: PlaylistCommand{Kind: PlaylistNext}, want: 2},
|
||||
{name: "previous stops at beginning", current: 0, command: PlaylistCommand{Kind: PlaylistPrevious}, want: 0},
|
||||
{name: "next wraps", loop: true, current: 2, command: PlaylistCommand{Kind: PlaylistNext}, want: 0, apply: true},
|
||||
{name: "previous wraps", loop: true, current: 0, command: PlaylistCommand{Kind: PlaylistPrevious}, want: 2, apply: true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
next, command, apply, err := ApplyPlaylistSelection(
|
||||
navigationPlaylist(test.loop),
|
||||
PlaylistState{CurrentIndex: test.current, HasSelection: true},
|
||||
test.command,
|
||||
validPlaylistRetryPolicy(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyPlaylistSelection() error = %v", err)
|
||||
}
|
||||
wantState := PlaylistState{CurrentIndex: test.want, HasSelection: true}
|
||||
if next != wantState {
|
||||
t.Fatalf("ApplyPlaylistSelection() state = %#v, want %#v", next, wantState)
|
||||
}
|
||||
if apply != test.apply {
|
||||
t.Fatalf("ApplyPlaylistSelection() apply = %v, want %v", apply, test.apply)
|
||||
}
|
||||
if apply && command.Kind != CommandSetSession {
|
||||
t.Fatalf("ApplyPlaylistSelection() command kind = %v, want %v", command.Kind, CommandSetSession)
|
||||
}
|
||||
if !apply && command != (SessionCommand{}) {
|
||||
t.Fatalf("ApplyPlaylistSelection() command = %#v, want zero value", command)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyPlaylistSelectionErrorsDoNotApply(t *testing.T) {
|
||||
current := PlaylistState{CurrentIndex: 1, HasSelection: true}
|
||||
tests := []struct {
|
||||
name string
|
||||
command PlaylistCommand
|
||||
retry RetryPolicy
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "invalid command",
|
||||
command: PlaylistCommand{},
|
||||
retry: validPlaylistRetryPolicy(),
|
||||
wantErr: ErrUnknownPlaylistCommand,
|
||||
},
|
||||
{
|
||||
name: "invalid retry",
|
||||
command: PlaylistCommand{Kind: PlaylistNext},
|
||||
retry: RetryPolicy{},
|
||||
wantErr: ErrInvalidRetryDelay,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
next, command, apply, err := ApplyPlaylistSelection(
|
||||
navigationPlaylist(false),
|
||||
current,
|
||||
test.command,
|
||||
test.retry,
|
||||
)
|
||||
if !errors.Is(err, test.wantErr) {
|
||||
t.Fatalf("ApplyPlaylistSelection() error = %v, want %v", err, test.wantErr)
|
||||
}
|
||||
if next != current {
|
||||
t.Fatalf("ApplyPlaylistSelection() state = %#v, want unchanged %#v", next, current)
|
||||
}
|
||||
if apply {
|
||||
t.Fatal("ApplyPlaylistSelection() apply = true, want false")
|
||||
}
|
||||
if command != (SessionCommand{}) {
|
||||
t.Fatalf("ApplyPlaylistSelection() command = %#v, want zero value", command)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func validPlaylistRetryPolicy() RetryPolicy {
|
||||
return RetryPolicy{
|
||||
MaxAttempts: 3,
|
||||
InitialDelay: time.Millisecond,
|
||||
MaxDelay: time.Second,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user