package playback import ( "errors" "testing" "time" ) func validCommandSession() SessionConfig { return SessionConfig{ Video: FeedConfig{Domain: "/video", UUID: "video", Active: true}, Audio: FeedConfig{Domain: "/audio", UUID: "audio", Active: true}, SyncRequested: true, Retry: RetryPolicy{ MaxAttempts: 3, InitialDelay: time.Millisecond, MaxDelay: time.Second, }, } } func TestApplySessionCommand(t *testing.T) { base := validCommandSession() newVideo := FeedConfig{Domain: "/new-video", UUID: "new-video", Active: false} newAudio := FeedConfig{Domain: "/new-audio", UUID: "new-audio", Active: true} tests := []struct { name string current SessionConfig command SessionCommand want SessionConfig }{ { name: "set video replaces complete config", current: base, command: SessionCommand{Kind: CommandSetVideo, Config: newVideo}, want: func() SessionConfig { c := base; c.Video = newVideo; return c }(), }, { name: "set audio replaces complete config", current: base, command: SessionCommand{Kind: CommandSetAudio, Config: newAudio}, want: func() SessionConfig { c := base; c.Audio = newAudio; return c }(), }, { name: "stop video disables sync", current: base, command: SessionCommand{Kind: CommandStopVideo}, want: func() SessionConfig { c := base; c.Video.Active = false; c.SyncRequested = false; return c }(), }, { name: "stop audio disables sync", current: base, command: SessionCommand{Kind: CommandStopAudio}, want: func() SessionConfig { c := base; c.Audio.Active = false; c.SyncRequested = false; return c }(), }, { name: "stop all preserves sync request", current: base, command: SessionCommand{Kind: CommandStopAll}, want: func() SessionConfig { c := base; c.Video.Active = false; c.Audio.Active = false; return c }(), }, { name: "resume video", current: func() SessionConfig { c := base; c.Video.Active = false; return c }(), command: SessionCommand{Kind: CommandResumeVideo}, want: base, }, { name: "resume audio", current: func() SessionConfig { c := base; c.Audio.Active = false; return c }(), command: SessionCommand{Kind: CommandResumeAudio}, want: base, }, { name: "resume all activates only configured feeds", current: func() SessionConfig { c := base; c.Video.Active = false; c.Audio = FeedConfig{}; return c }(), command: SessionCommand{Kind: CommandResumeAll}, want: func() SessionConfig { c := base; c.Audio = FeedConfig{}; return c }(), }, { name: "remove video clears config and disables sync", current: base, command: SessionCommand{Kind: CommandRemoveVideo}, want: func() SessionConfig { c := base; c.Video = FeedConfig{}; c.SyncRequested = false; return c }(), }, { name: "remove audio clears config and disables sync", current: base, command: SessionCommand{Kind: CommandRemoveAudio}, want: func() SessionConfig { c := base; c.Audio = FeedConfig{}; c.SyncRequested = false; return c }(), }, { name: "enable sync without feeds records request", current: func() SessionConfig { c := base c.Video = FeedConfig{} c.Audio = FeedConfig{} c.SyncRequested = false return c }(), command: SessionCommand{Kind: CommandEnableSync}, want: func() SessionConfig { c := base c.Video = FeedConfig{} c.Audio = FeedConfig{} c.SyncRequested = true return c }(), }, { name: "disable sync", current: base, command: SessionCommand{Kind: CommandDisableSync}, want: func() SessionConfig { c := base; c.SyncRequested = false; return c }(), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ApplySessionCommand(tt.current, tt.command) if err != nil { t.Fatalf("ApplySessionCommand() error = %v", err) } if got != tt.want { t.Fatalf("ApplySessionCommand() = %#v, want %#v", got, tt.want) } if got.Retry != tt.current.Retry { t.Fatalf("retry changed from %#v to %#v", tt.current.Retry, got.Retry) } }) } } func TestApplySessionCommandFailurePreservesState(t *testing.T) { base := validCommandSession() tests := []struct { name string current SessionConfig command SessionCommand wantErr error }{ {"set empty video", base, SessionCommand{Kind: CommandSetVideo}, ErrVideoNotConfigured}, {"set empty audio", base, SessionCommand{Kind: CommandSetAudio}, ErrAudioNotConfigured}, {"invalid video", base, SessionCommand{Kind: CommandSetVideo, Config: FeedConfig{UUID: "video"}}, ErrFeedDomainRequired}, {"invalid audio", base, SessionCommand{Kind: CommandSetAudio, Config: FeedConfig{UUID: "audio"}}, ErrFeedDomainRequired}, { "resume missing video", func() SessionConfig { c := base; c.Video = FeedConfig{}; return c }(), SessionCommand{Kind: CommandResumeVideo}, ErrVideoNotConfigured, }, { "resume missing audio", func() SessionConfig { c := base; c.Audio = FeedConfig{}; return c }(), SessionCommand{Kind: CommandResumeAudio}, ErrAudioNotConfigured, }, {"unknown command", base, SessionCommand{Kind: 255}, ErrUnknownSessionCommand}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ApplySessionCommand(tt.current, tt.command) if !errors.Is(err, tt.wantErr) { t.Fatalf("ApplySessionCommand() error = %v, want %v", err, tt.wantErr) } if got != tt.current { t.Fatalf("failed command changed state from %#v to %#v", tt.current, got) } }) } } func TestApplySessionCommandRejectsInvalidResult(t *testing.T) { current := validCommandSession() current.Retry = RetryPolicy{} got, err := ApplySessionCommand(current, SessionCommand{Kind: CommandDisableSync}) if !errors.Is(err, ErrInvalidRetryDelay) { t.Fatalf("ApplySessionCommand() error = %v, want %v", err, ErrInvalidRetryDelay) } if got != current { t.Fatalf("failed command changed state from %#v to %#v", current, got) } }