Files
mal/api/playback/service_ranking_test.go
mkelvers 1504f6f473 test: add coverage for allanime client and playback service
- test decodeSourceURL, detectStreamType, detectEmbedType
- test buildStreamSource, buildSourceReferences
- test decryptTobeparsed, isLikelyM3U8, isLikelyMP4
- test rankSources, normalizeQuality, parseQualityValue
- test qualityMatches, sourceQualityPriority
2026-05-10 18:11:41 +02:00

491 lines
9.4 KiB
Go

package playback
import (
"testing"
)
func TestRankSources(t *testing.T) {
t.Parallel()
tests := []struct {
name string
sources []StreamSource
quality string
wantErr bool
}{
{
name: "empty sources returns error",
sources: nil,
quality: "best",
wantErr: true,
},
{
name: "filters empty URLs",
sources: []StreamSource{
{URL: "", Type: "mp4"},
{URL: "https://example.com/v.mp4", Type: "mp4"},
},
quality: "best",
wantErr: false,
},
{
name: "deduplicates URLs",
sources: []StreamSource{
{URL: "https://a.com/v.mp4", Type: "mp4"},
{URL: "https://b.com/v.mp4", Type: "m3u8"},
{URL: "https://a.com/v.mp4", Type: "mp4"},
},
quality: "best",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
_, err := rankSources(tt.sources, tt.quality)
if (err != nil) != tt.wantErr {
t.Errorf("rankSources() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestRankSourcesOrdering(t *testing.T) {
t.Parallel()
sources := []StreamSource{
{URL: "https://embed.com/v.mp4", Type: "embed", Provider: "streamwish"},
{URL: "https://mp4.com/v.mp4", Type: "mp4", Provider: "s-mp4"},
{URL: "https://m3u8.com/v.m3u8", Type: "m3u8", Provider: "default"},
{URL: "https://unknown.com/v.mp4", Type: "unknown", Provider: "other"},
}
ranked, err := rankSources(sources, "best")
if err != nil {
t.Fatalf("rankSources() error = %v", err)
}
if len(ranked) != 4 {
t.Fatalf("got %d sources, want 4", len(ranked))
}
if ranked[0].source.Type != "mp4" {
t.Errorf("ranked[0] = %q, want mp4 (type priority: mp4 > m3u8 > unknown > embed)", ranked[0].source.Type)
}
if ranked[1].source.Type != "m3u8" {
t.Errorf("ranked[1] = %q, want m3u8", ranked[1].source.Type)
}
}
func TestRankSourcesWithQuality(t *testing.T) {
t.Parallel()
sources := []StreamSource{
{URL: "https://a.com/v.mp4", Quality: "1080p", Type: "mp4"},
{URL: "https://b.com/v.mp4", Quality: "720p", Type: "mp4"},
{URL: "https://c.com/v.mp4", Quality: "480p", Type: "mp4"},
}
ranked, err := rankSources(sources, "1080p")
if err != nil {
t.Fatalf("rankSources() error = %v", err)
}
if ranked[0].source.Quality != "1080p" {
t.Errorf("ranked[0].Quality = %q, want 1080p", ranked[0].source.Quality)
}
}
func TestNormalizeQuality(t *testing.T) {
t.Parallel()
tests := []struct {
name string
quality string
wantNorm string
}{
{
name: "empty returns best",
quality: "",
wantNorm: "best",
},
{
name: "lowercase best",
quality: "BEST",
wantNorm: "best",
},
{
name: "with spaces",
quality: " 720p ",
wantNorm: "720p",
},
{
name: "worst",
quality: "worst",
wantNorm: "worst",
},
{
name: "specific quality",
quality: "1080p",
wantNorm: "1080p",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := normalizeQuality(tt.quality)
if got != tt.wantNorm {
t.Errorf("normalizeQuality(%q) = %q, want %q", tt.quality, got, tt.wantNorm)
}
})
}
}
func TestParseQualityValue(t *testing.T) {
t.Parallel()
tests := []struct {
name string
quality string
want int
}{
{
name: "auto returns 240",
quality: "auto",
want: 240,
},
{
name: "1080p extracts 1080",
quality: "1080p",
want: 1080,
},
{
name: "720 extracts 720",
quality: "720",
want: 720,
},
{
name: "fhd is treated as fhd",
quality: "fhd",
want: 0,
},
{
name: "empty returns 0",
quality: "",
want: 0,
},
{
name: "multiple digits stops at first non-digit",
quality: "1080p60fps",
want: 1080,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := parseQualityValue(tt.quality)
if got != tt.want {
t.Errorf("parseQualityValue(%q) = %d, want %d", tt.quality, got, tt.want)
}
})
}
}
func TestQualityMatches(t *testing.T) {
t.Parallel()
tests := []struct {
name string
source string
target string
want bool
}{
{
name: "exact match",
source: "1080p",
target: "1080p",
want: true,
},
{
name: "target in source",
source: "1920x1080",
target: "1080",
want: true,
},
{
name: "digit match",
source: "1080p",
target: "1080",
want: true,
},
{
name: "no match",
source: "720p",
target: "1080",
want: false,
},
{
name: "empty source returns false",
source: "",
target: "1080",
want: false,
},
{
name: "empty target returns true (empty always contained)",
source: "1080p",
target: "",
want: true,
},
{
name: "auto doesn't match specific",
source: "auto",
target: "1080",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := qualityMatches(tt.source, tt.target)
if got != tt.want {
t.Errorf("qualityMatches(%q, %q) = %v, want %v", tt.source, tt.target, got, tt.want)
}
})
}
}
func TestSourceQualityPriority(t *testing.T) {
t.Parallel()
tests := []struct {
name string
source string
target string
wantMin int
}{
{
name: "best mode favors higher quality",
source: "1080p",
target: "best",
wantMin: 1080,
},
{
name: "worst mode penalizes higher quality",
source: "1080p",
target: "worst",
wantMin: -2000,
},
{
name: "exact match gets bonus",
source: "1080p",
target: "1080p",
wantMin: 2000,
},
{
name: "close match gets penalty but positive score",
source: "1080p",
target: "720p",
wantMin: 500,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := sourceQualityPriority(tt.source, tt.target)
if tt.wantMin != 0 && got < tt.wantMin {
t.Errorf("sourceQualityPriority(%q, %q) = %d, want >= %d", tt.source, tt.target, got, tt.wantMin)
}
})
}
}
func TestSourceTypePriorityLookup(t *testing.T) {
t.Parallel()
tests := []struct {
name string
sourceType string
want int
}{
{
name: "mp4 priority",
sourceType: "mp4",
want: 500,
},
{
name: "m3u8 priority",
sourceType: "m3u8",
want: 450,
},
{
name: "unknown uses fallback",
sourceType: "unknown",
want: 300,
},
{
name: "embed fallback",
sourceType: "embed",
want: 100,
},
{
name: "unrecognized uses fallback",
sourceType: "video",
want: 200,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := lookupPriority(sourceTypePriority, tt.sourceType, 200)
if got != tt.want {
t.Errorf("lookupPriority(sourceTypePriority, %q, 200) = %d, want %d", tt.sourceType, got, tt.want)
}
})
}
}
func TestProviderPriorityLookup(t *testing.T) {
t.Parallel()
tests := []struct {
name string
provider string
want int
}{
{
name: "s-mp4",
provider: "s-mp4",
want: 120,
},
{
name: "default",
provider: "default",
want: 115,
},
{
name: "yt-mp4",
provider: "yt-mp4",
want: 100,
},
{
name: "unknown uses fallback",
provider: "unknown",
want: 60,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := lookupPriority(providerPriority, tt.provider, 60)
if got != tt.want {
t.Errorf("lookupPriority(providerPriority, %q, 60) = %d, want %d", tt.provider, got, tt.want)
}
})
}
}
func TestNormalizeSourceTypeFromProbe(t *testing.T) {
t.Parallel()
tests := []struct {
name string
source StreamSource
contentType string
wantType string
}{
{
name: "video/mp4 normalizes to mp4",
source: StreamSource{Type: "unknown"},
contentType: "video/mp4",
wantType: "mp4",
},
{
name: "application/octet-stream unchanged",
source: StreamSource{Type: "mp4"},
contentType: "application/octet-stream",
wantType: "mp4",
},
{
name: "mpegurl normalizes to m3u8",
source: StreamSource{Type: "unknown"},
contentType: "application/vnd.apple.mpegurl",
wantType: "m3u8",
},
{
name: "video/mpegurl",
source: StreamSource{Type: "unknown"},
contentType: "video/mpegurl",
wantType: "m3u8",
},
{
name: "case insensitive",
source: StreamSource{Type: "unknown"},
contentType: "VIDEO/MP4",
wantType: "mp4",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := normalizeSourceTypeFromProbe(tt.source, tt.contentType)
if got.Type != tt.wantType {
t.Errorf("normalizeSourceTypeFromProbe().Type = %q, want %q", got.Type, tt.wantType)
}
})
}
}
func TestExtractDigits(t *testing.T) {
t.Parallel()
tests := []struct {
name string
value string
want string
}{
{
name: "extracts digits",
value: "1080p",
want: "1080",
},
{
name: "empty if no digits",
value: "p",
want: "",
},
{
name: "stops at non-digit after digits",
value: "720p60",
want: "720",
},
{
name: "multiple non-digit does not break",
value: "abc123def",
want: "123",
},
{
name: "all digits",
value: "1080",
want: "1080",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := extractDigits(tt.value)
if got != tt.want {
t.Errorf("extractDigits(%q) = %q, want %q", tt.value, got, tt.want)
}
})
}
}