Refactoring #3

Merged
itten merged 87 commits from refactoring into main 2026-09-01 23:52:36 +03:00
2 changed files with 244 additions and 0 deletions
Showing only changes of commit e50ad8adb5 - Show all commits
+91
View File
@@ -0,0 +1,91 @@
package playback
import (
"errors"
"fmt"
"time"
)
var (
ErrPlaylistFeedUUIDRequired = errors.New("playlist feed UUID is required when its domain is configured")
ErrPlaylistEntryEmpty = errors.New("playlist entry must contain at least one feed")
ErrPlaylistSyncFeedsRequired = errors.New("synchronized playlist entry requires both video and audio feeds")
ErrPlaylistDurationNegative = errors.New("playlist entry duration cannot be negative")
)
type PlaylistFeed struct {
Domain string
UUID string
}
type PlaylistEntry struct {
Name string
Video PlaylistFeed
Audio PlaylistFeed
SyncRequested bool
Duration time.Duration
}
type Playlist struct {
Entries []PlaylistEntry
Loop bool
}
func (f PlaylistFeed) IsConfigured() bool {
return f.UUID != ""
}
func (f PlaylistFeed) Validate() error {
if f.UUID != "" && f.Domain == "" {
return ErrFeedDomainRequired
}
if f.Domain != "" && f.UUID == "" {
return ErrPlaylistFeedUUIDRequired
}
return nil
}
func (e PlaylistEntry) Validate() error {
if err := e.Video.Validate(); err != nil {
return fmt.Errorf("video: %w", err)
}
if err := e.Audio.Validate(); err != nil {
return fmt.Errorf("audio: %w", err)
}
if !e.Video.IsConfigured() && !e.Audio.IsConfigured() {
return ErrPlaylistEntryEmpty
}
if e.SyncRequested && (!e.Video.IsConfigured() || !e.Audio.IsConfigured()) {
return ErrPlaylistSyncFeedsRequired
}
if e.Duration < 0 {
return ErrPlaylistDurationNegative
}
return nil
}
func (p Playlist) Validate() error {
for index, entry := range p.Entries {
if err := entry.Validate(); err != nil {
return fmt.Errorf("playlist entry %d: %w", index, err)
}
}
return nil
}
func (e PlaylistEntry) SessionConfig(retry RetryPolicy) SessionConfig {
return SessionConfig{
Video: FeedConfig{
Domain: e.Video.Domain,
UUID: e.Video.UUID,
Active: e.Video.UUID != "",
},
Audio: FeedConfig{
Domain: e.Audio.Domain,
UUID: e.Audio.UUID,
Active: e.Audio.UUID != "",
},
SyncRequested: e.SyncRequested,
Retry: retry,
}
}
+153
View File
@@ -0,0 +1,153 @@
package playback
import (
"errors"
"testing"
"time"
)
func TestPlaylistEntryValidate(t *testing.T) {
tests := []struct {
name string
entry PlaylistEntry
wantErr error
}{
{name: "video only", entry: PlaylistEntry{Video: PlaylistFeed{Domain: "video-domain", UUID: "video"}}},
{name: "audio only", entry: PlaylistEntry{Audio: PlaylistFeed{Domain: "audio-domain", UUID: "audio"}}},
{
name: "independent feeds from different domains",
entry: PlaylistEntry{
Video: PlaylistFeed{Domain: "video-domain", UUID: "video"},
Audio: PlaylistFeed{Domain: "audio-domain", UUID: "audio"},
},
},
{
name: "synchronized pair",
entry: PlaylistEntry{
Video: PlaylistFeed{Domain: "domain", UUID: "video"},
Audio: PlaylistFeed{Domain: "domain", UUID: "audio"},
SyncRequested: true,
Duration: 10 * time.Second,
},
},
{name: "zero duration", entry: PlaylistEntry{Video: PlaylistFeed{Domain: "domain", UUID: "video"}}},
{name: "empty entry", entry: PlaylistEntry{}, wantErr: ErrPlaylistEntryEmpty},
{
name: "video UUID without domain",
entry: PlaylistEntry{Video: PlaylistFeed{UUID: "video"}},
wantErr: ErrFeedDomainRequired,
},
{
name: "audio domain without UUID",
entry: PlaylistEntry{Audio: PlaylistFeed{Domain: "audio-domain"}},
wantErr: ErrPlaylistFeedUUIDRequired,
},
{
name: "sync without audio",
entry: PlaylistEntry{
Video: PlaylistFeed{Domain: "domain", UUID: "video"},
SyncRequested: true,
},
wantErr: ErrPlaylistSyncFeedsRequired,
},
{
name: "sync without video",
entry: PlaylistEntry{
Audio: PlaylistFeed{Domain: "domain", UUID: "audio"},
SyncRequested: true,
},
wantErr: ErrPlaylistSyncFeedsRequired,
},
{
name: "negative duration",
entry: PlaylistEntry{
Video: PlaylistFeed{Domain: "domain", UUID: "video"},
Duration: -time.Second,
},
wantErr: ErrPlaylistDurationNegative,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.entry.Validate()
if !errors.Is(err, test.wantErr) {
t.Fatalf("Validate() error = %v, want %v", err, test.wantErr)
}
})
}
}
func TestPlaylistEntrySessionConfig(t *testing.T) {
retry := RetryPolicy{MaxAttempts: 3, InitialDelay: time.Second, MaxDelay: 5 * time.Second}
entry := PlaylistEntry{
Video: PlaylistFeed{Domain: "video-domain", UUID: "video"},
Audio: PlaylistFeed{Domain: "audio-domain", UUID: "audio"},
SyncRequested: true,
Duration: 10 * time.Second,
}
got := entry.SessionConfig(retry)
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 got != want {
t.Fatalf("SessionConfig() = %#v, want %#v", got, want)
}
}
func TestPlaylistEntrySessionConfigLeavesMissingFeedInactive(t *testing.T) {
entry := PlaylistEntry{Video: PlaylistFeed{Domain: "domain", UUID: "video"}}
got := entry.SessionConfig(RetryPolicy{})
if !got.Video.Active {
t.Fatal("SessionConfig() video is inactive, want active")
}
if got.Audio != (FeedConfig{}) {
t.Fatalf("SessionConfig() audio = %#v, want zero value", got.Audio)
}
}
func TestPlaylistValidate(t *testing.T) {
valid := PlaylistEntry{Video: PlaylistFeed{Domain: "domain", UUID: "video"}}
tests := []struct {
name string
playlist Playlist
wantErr error
}{
{name: "empty playlist", playlist: Playlist{}},
{name: "valid entries", playlist: Playlist{Entries: []PlaylistEntry{valid, valid}, Loop: true}},
{
name: "invalid entry",
playlist: Playlist{Entries: []PlaylistEntry{
valid,
{Audio: PlaylistFeed{UUID: "audio"}},
}},
wantErr: ErrFeedDomainRequired,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.playlist.Validate()
if !errors.Is(err, test.wantErr) {
t.Fatalf("Validate() error = %v, want %v", err, test.wantErr)
}
})
}
}
func TestPlaylistValidateReportsEntryIndex(t *testing.T) {
playlist := Playlist{Entries: []PlaylistEntry{
{Video: PlaylistFeed{Domain: "domain", UUID: "video"}},
{},
}}
err := playlist.Validate()
if err == nil || err.Error() != "playlist entry 1: playlist entry must contain at least one feed" {
t.Fatalf("Validate() error = %v, want indexed entry error", err)
}
}