package handler import ( "testing" "uptodownBot/internal/domain" ) func TestBuildFormatList(t *testing.T) { formats := []domain.Format{ {ID: "1", HasVideo: true, HasAudio: false, Resolution: "1920x1080"}, {ID: "2", HasVideo: false, HasAudio: true, Bitrate: "128k"}, {ID: "3", HasVideo: true, HasAudio: false, Resolution: "1280x720"}, {ID: "4", HasVideo: true, HasAudio: true, Resolution: "640x480"}, } ids, err := buildFormatList(formats) if err != nil { t.Fatalf("buildFormatList() error: %v", err) } if len(ids) == 0 { t.Fatal("buildFormatList() returned empty list") } if ids[0] != "best" { t.Errorf("first ID = %q, want best", ids[0]) } // Best, then all video formats, then all audio-only wantVideo := map[string]bool{"1": true, "3": true, "4": true} wantAudio := map[string]bool{"2": true} seenAudio := false for i := 1; i < len(ids); i++ { if wantVideo[ids[i]] { if seenAudio { t.Errorf("video format %q found after audio-only formats", ids[i]) } } if wantAudio[ids[i]] { seenAudio = true } } } func TestFindFormatByID(t *testing.T) { formats := []domain.Format{ {ID: "1", Resolution: "1920x1080"}, {ID: "2", Bitrate: "128k"}, } f := findFormatByID(formats, "1") if f == nil { t.Fatal("findFormatByID(1) = nil, want non-nil") } if f.ID != "1" { t.Errorf("findFormatByID(1).ID = %q, want 1", f.ID) } f = findFormatByID(formats, "nonexistent") if f != nil { t.Errorf("findFormatByID(nonexistent) = %+v, want nil", f) } } func TestStepStateDetermineMediaType(t *testing.T) { tests := []struct { name string ss *stepState want domain.MediaType }{ { name: "nil main format", ss: &stepState{}, want: domain.MediaTypeVideoAudio, }, { name: "audio only no secondary", ss: &stepState{MainFormat: &domain.Format{IsAudioOnly: true}}, want: domain.MediaTypeAudio, }, { name: "audio only with secondary", ss: &stepState{MainFormat: &domain.Format{IsAudioOnly: true}, SecondaryFmt: &domain.Format{HasVideo: true}}, want: domain.MediaTypeVideoAudio, }, { name: "has video, has audio", ss: &stepState{MainFormat: &domain.Format{HasVideo: true, HasAudio: true}}, want: domain.MediaTypeVideoAudio, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := tt.ss.determineMediaType() if got != tt.want { t.Errorf("determineMediaType() = %v, want %v", got, tt.want) } }) } } func TestStepStateBuildFormatString(t *testing.T) { tests := []struct { name string ss *stepState want string }{ { name: "best format", ss: &stepState{MainFormatID: "best"}, want: "", }, { name: "single format", ss: &stepState{MainFormatID: "137"}, want: "137", }, { name: "combined format", ss: &stepState{MainFormatID: "137", SecondaryID: "140", SecondaryFmt: &domain.Format{ID: "140"}}, want: "137+140", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := tt.ss.buildFormatString() if got != tt.want { t.Errorf("buildFormatString() = %q, want %q", got, tt.want) } }) } } func TestFormatBytes(t *testing.T) { tests := []struct { input int64 want string }{ {0, "0B"}, {1, "1B"}, {1023, "1023B"}, {1024, "1.0KB"}, {1536, "1.5KB"}, {1048576, "1.0MB"}, {1073741824, "1.0GB"}, {1099511627776, "1.0TB"}, } for _, tt := range tests { got := formatBytes(tt.input) if got != tt.want { t.Errorf("formatBytes(%d) = %q, want %q", tt.input, got, tt.want) } } } func TestFormatDuration(t *testing.T) { tests := []struct { input float64 want string }{ {0, "0:00"}, {5, "0:05"}, {60, "1:00"}, {65, "1:05"}, {3600, "1:00:00"}, {3661, "1:01:01"}, {86400, "24:00:00"}, } for _, tt := range tests { got := formatDuration(tt.input) if got != tt.want { t.Errorf("formatDuration(%f) = %q, want %q", tt.input, got, tt.want) } } } func TestBuildFilteredFormatList(t *testing.T) { formats := []domain.Format{ {ID: "1", HasVideo: true, HasAudio: false, Resolution: "1920x1080"}, {ID: "2", HasVideo: false, HasAudio: true, IsAudioOnly: true, Bitrate: "128k"}, {ID: "3", HasVideo: true, HasAudio: false, Resolution: "1280x720"}, {ID: "4", HasVideo: true, HasAudio: true, Resolution: "640x480"}, {ID: "5", HasVideo: false, HasAudio: true, IsAudioOnly: true, Bitrate: "320k"}, } t.Run("ask type returns all formats", func(t *testing.T) { ids, err := buildFilteredFormatList(formats, "ask") if err != nil { t.Fatalf("buildFilteredFormatList(ask) error: %v", err) } if ids[0] != "best" { t.Errorf("first ID = %q, want best", ids[0]) } // Should include all non-best IDs expectedIDs := map[string]bool{"1": true, "2": true, "3": true, "4": true, "5": true} for _, id := range ids[1:] { if !expectedIDs[id] { t.Errorf("unexpected ID %q in ask list", id) } delete(expectedIDs, id) } if len(expectedIDs) > 0 { t.Errorf("missing IDs in ask list: %v", expectedIDs) } }) t.Run("video type only returns video formats", func(t *testing.T) { ids, err := buildFilteredFormatList(formats, "video") if err != nil { t.Fatalf("buildFilteredFormatList(video) error: %v", err) } if ids[0] != "best" { t.Errorf("first ID = %q, want best", ids[0]) } for _, id := range ids[1:] { if id == "2" || id == "5" { t.Errorf("video list contains audio-only ID %q", id) } } }) t.Run("audio type only returns audio-only formats", func(t *testing.T) { ids, err := buildFilteredFormatList(formats, "audio") if err != nil { t.Fatalf("buildFilteredFormatList(audio) error: %v", err) } if ids[0] != "best" { t.Errorf("first ID = %q, want best", ids[0]) } for _, id := range ids[1:] { if id == "1" || id == "3" || id == "4" { t.Errorf("audio list contains video ID %q", id) } } }) t.Run("default (empty) returns all formats", func(t *testing.T) { ids, err := buildFilteredFormatList(formats, "") if err != nil { t.Fatalf("buildFilteredFormatList('') error: %v", err) } if len(ids) != 6 { // best + 5 formats t.Errorf("got %d IDs, want 6", len(ids)) } }) } func TestAutoSelectVideoFormat(t *testing.T) { formats := []domain.Format{ {ID: "137", HasVideo: true, HasAudio: false, Height: 1080, Width: 1920, Resolution: "1920x1080"}, {ID: "136", HasVideo: true, HasAudio: false, Height: 720, Width: 1280, Resolution: "1280x720"}, {ID: "135", HasVideo: true, HasAudio: false, Height: 480, Width: 854, Resolution: "854x480"}, {ID: "247", HasVideo: true, HasAudio: true, Height: 1080, Width: 1920, Resolution: "1920x1080"}, {ID: "140", HasVideo: false, HasAudio: true, IsAudioOnly: true, Bitrate: "128k"}, } t.Run("best returns best, no secondary", func(t *testing.T) { mainID, secondaryID := autoSelectVideoFormat(formats, "best") if mainID != "best" { t.Errorf("mainID = %q, want best", mainID) } if secondaryID != "" { t.Errorf("secondaryID = %q, want empty", secondaryID) } }) t.Run("empty returns best", func(t *testing.T) { mainID, _ := autoSelectVideoFormat(formats, "") if mainID != "best" { t.Errorf("mainID = %q, want best", mainID) } }) t.Run("exact match 1080p", func(t *testing.T) { mainID, _ := autoSelectVideoFormat(formats, "1080p") if mainID != "137" && mainID != "247" { t.Errorf("mainID = %q, want 137 or 247", mainID) } }) t.Run("prefer above 360p", func(t *testing.T) { mainID, _ := autoSelectVideoFormat(formats, "360p") // 480p (135) is the closest above 360p; below is none if mainID != "135" { t.Errorf("mainID = %q, want 135 (480p is closest above 360p)", mainID) } }) t.Run("exact match 720p", func(t *testing.T) { mainID, _ := autoSelectVideoFormat(formats, "720p") // 720p (136) is an exact match if mainID != "136" { t.Errorf("mainID = %q, want 136 (exact match for 720p)", mainID) } }) t.Run("above 1080p takes 1080p as closest below", func(t *testing.T) { mainID, _ := autoSelectVideoFormat(formats, "2160p") // No 4K format, should fall back to 1080p (closest below) if mainID != "137" && mainID != "247" { t.Errorf("mainID = %q, want 137 or 247 (1080p is closest below 2160p)", mainID) } }) t.Run("video-only picks best audio secondary", func(t *testing.T) { formats := []domain.Format{ {ID: "137", HasVideo: true, HasAudio: false, Height: 1080}, {ID: "140", HasVideo: false, HasAudio: true, IsAudioOnly: true, Bitrate: "128k"}, } _, secondaryID := autoSelectVideoFormat(formats, "1080p") if secondaryID != "140" { t.Errorf("secondaryID = %q, want 140", secondaryID) } }) t.Run("no match returns best", func(t *testing.T) { formats := []domain.Format{ {ID: "140", HasVideo: false, HasAudio: true, IsAudioOnly: true}, } mainID, _ := autoSelectVideoFormat(formats, "1080p") if mainID != "best" { t.Errorf("mainID = %q, want best", mainID) } }) } func TestAutoSelectAudioFormat(t *testing.T) { formats := []domain.Format{ {ID: "140", HasVideo: false, HasAudio: true, IsAudioOnly: true, Bitrate: "128k"}, {ID: "256", HasVideo: false, HasAudio: true, IsAudioOnly: true, Bitrate: "256k"}, {ID: "320", HasVideo: false, HasAudio: true, IsAudioOnly: true, Bitrate: "320k"}, } t.Run("best returns best", func(t *testing.T) { id := autoSelectAudioFormat(formats, "best") if id != "best" { t.Errorf("got %q, want best", id) } }) t.Run("empty returns best", func(t *testing.T) { id := autoSelectAudioFormat(formats, "") if id != "best" { t.Errorf("got %q, want best", id) } }) t.Run("exact match 256k", func(t *testing.T) { id := autoSelectAudioFormat(formats, "256k") if id != "256" { t.Errorf("got %q, want 256", id) } }) t.Run("prefer above 192k", func(t *testing.T) { id := autoSelectAudioFormat(formats, "192k") if id != "256" { t.Errorf("got %q, want 256 (closest above 192k)", id) } }) t.Run("above highest takes highest below", func(t *testing.T) { id := autoSelectAudioFormat(formats, "384k") if id != "320" { t.Errorf("got %q, want 320 (closest below 384k)", id) } }) t.Run("no match returns best", func(t *testing.T) { formats := []domain.Format{ {ID: "1", HasVideo: true, HasAudio: true}, } id := autoSelectAudioFormat(formats, "128k") if id != "best" { t.Errorf("got %q, want best", id) } }) } func TestFormatLabelForDisplay(t *testing.T) { tests := []struct { f domain.Format want string }{ { f: domain.Format{Resolution: "1920x1080", Filesize: 1048576}, want: "1920x1080 (1.0MB)", }, { f: domain.Format{Resolution: "1920x1080"}, want: "1920x1080", }, { f: domain.Format{Bitrate: "128k", Filesize: 1048576}, want: "128k (1.0MB)", }, { f: domain.Format{Bitrate: "128k"}, want: "128k", }, { f: domain.Format{ID: "137"}, want: "137", }, { f: domain.Format{IsAudioOnly: true, Bitrate: "128k", Filesize: 1048576, Resolution: "audio only"}, want: "128k (1.0MB)", }, { f: domain.Format{IsAudioOnly: true, Bitrate: "128k", Resolution: "audio only"}, want: "128k", }, { f: domain.Format{IsAudioOnly: true, ID: "140", Extension: "m4a", Filesize: 1048576, Resolution: "audio only"}, want: "140 (m4a) (1.0MB)", }, } for _, tt := range tests { got := formatLabelForDisplay(tt.f) if got != tt.want { t.Errorf("formatLabelForDisplay(%+v) = %q, want %q", tt.f, got, tt.want) } } }