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
This commit is contained in:
453
api/playback/allanime_client_test.go
Normal file
453
api/playback/allanime_client_test.go
Normal file
@@ -0,0 +1,453 @@
|
||||
package playback
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeSourceURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
encoded string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty returns empty",
|
||||
encoded: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "with double prefix stripped",
|
||||
encoded: "--example.com/video.mp4",
|
||||
want: "example.com/video.mp4",
|
||||
},
|
||||
{
|
||||
name: "hex substitution",
|
||||
encoded: "7aexample",
|
||||
want: "Bexample",
|
||||
},
|
||||
{
|
||||
name: "mixed substitution",
|
||||
encoded: "79url7a01",
|
||||
want: "AurlB9",
|
||||
},
|
||||
{
|
||||
name: "clock replacement",
|
||||
encoded: "/clock",
|
||||
want: "/clock.json",
|
||||
},
|
||||
{
|
||||
name: "no clock replacement if already json",
|
||||
encoded: "/clock.json",
|
||||
want: "/clock.json",
|
||||
},
|
||||
{
|
||||
name: "complex url",
|
||||
encoded: "--79stream7acom",
|
||||
want: "AstreamBcom",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := decodeSourceURL(tt.encoded)
|
||||
if got != tt.want {
|
||||
t.Errorf("decodeSourceURL(%q) = %q, want %q", tt.encoded, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectStreamType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
wantType string
|
||||
}{
|
||||
{
|
||||
name: "m3u8 extension",
|
||||
url: "https://example.com/video.m3u8",
|
||||
wantType: "m3u8",
|
||||
},
|
||||
{
|
||||
name: "master m3u8",
|
||||
url: "https://example.com/master.m3u8",
|
||||
wantType: "m3u8",
|
||||
},
|
||||
{
|
||||
name: "mp4 extension",
|
||||
url: "https://example.com/video.mp4",
|
||||
wantType: "mp4",
|
||||
},
|
||||
{
|
||||
name: "unknown",
|
||||
url: "https://example.com/video.avi",
|
||||
wantType: "unknown",
|
||||
},
|
||||
{
|
||||
name: "empty returns unknown",
|
||||
url: "",
|
||||
wantType: "unknown",
|
||||
},
|
||||
{
|
||||
name: "case insensitive - M3U8",
|
||||
url: "https://example.com/MASTER.M3U8",
|
||||
wantType: "m3u8",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := detectStreamType(tt.url)
|
||||
if got != tt.wantType {
|
||||
t.Errorf("detectStreamType(%q) = %q, want %q", tt.url, got, tt.wantType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectEmbedType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
wantType string
|
||||
}{
|
||||
{
|
||||
name: "streamwish",
|
||||
url: "https://streamwish.com/e/abc123",
|
||||
wantType: "embed",
|
||||
},
|
||||
{
|
||||
name: "streamsb",
|
||||
url: "https://streamsb.com/e/abc123",
|
||||
wantType: "embed",
|
||||
},
|
||||
{
|
||||
name: "mp4upload",
|
||||
url: "https://mp4upload.com/e/abc123",
|
||||
wantType: "embed",
|
||||
},
|
||||
{
|
||||
name: "ok.ru",
|
||||
url: "https://ok.ru/video/123",
|
||||
wantType: "embed",
|
||||
},
|
||||
{
|
||||
name: "gogoplay",
|
||||
url: "https://gogoplay.io/embed/123",
|
||||
wantType: "embed",
|
||||
},
|
||||
{
|
||||
name: "streamlare",
|
||||
url: "https://streamlare.com/e/abc",
|
||||
wantType: "embed",
|
||||
},
|
||||
{
|
||||
name: "unknown host",
|
||||
url: "https://unknown.com/video",
|
||||
wantType: "unknown",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := detectEmbedType(tt.url)
|
||||
if got != tt.wantType {
|
||||
t.Errorf("detectEmbedType(%q) = %q, want %q", tt.url, got, tt.wantType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildStreamSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("constructs with correct defaults", func(t *testing.T) {
|
||||
got := buildStreamSource("https://example.com/video.mp4", "mp4", "test-provider")
|
||||
|
||||
if got.URL != "https://example.com/video.mp4" {
|
||||
t.Errorf("URL = %q, want %q", got.URL, "https://example.com/video.mp4")
|
||||
}
|
||||
if got.Provider != "test-provider" {
|
||||
t.Errorf("Provider = %q, want %q", got.Provider, "test-provider")
|
||||
}
|
||||
if got.Type != "mp4" {
|
||||
t.Errorf("Type = %q, want %q", got.Type, "mp4")
|
||||
}
|
||||
if got.Referer != allAnimeReferer {
|
||||
t.Errorf("Referer = %q, want %q", got.Referer, allAnimeReferer)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildSourceReferences(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rawURLs []any
|
||||
wantRefs []sourceReference
|
||||
}{
|
||||
{
|
||||
name: "empty returns empty",
|
||||
rawURLs: nil,
|
||||
wantRefs: nil,
|
||||
},
|
||||
{
|
||||
name: "filters empty URLs",
|
||||
rawURLs: []any{
|
||||
map[string]any{"sourceUrl": "", "sourceName": "test"},
|
||||
map[string]any{"sourceUrl": "https://example.com/v.mp4", "sourceName": "default"},
|
||||
},
|
||||
wantRefs: []sourceReference{
|
||||
{URL: "https://example.com/v.mp4", Name: "default"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deduplicates URLs",
|
||||
rawURLs: []any{
|
||||
map[string]any{"sourceUrl": "https://example.com/v.mp4", "sourceName": "test"},
|
||||
map[string]any{"sourceUrl": "https://example.com/v.mp4", "sourceName": "test2"},
|
||||
},
|
||||
wantRefs: []sourceReference{
|
||||
{URL: "https://example.com/v.mp4", Name: "test"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prioritizes default provider",
|
||||
rawURLs: []any{
|
||||
map[string]any{"sourceUrl": "https://a.com/v.mp4", "sourceName": "fallback"},
|
||||
map[string]any{"sourceUrl": "https://b.com/v.mp4", "sourceName": "default"},
|
||||
map[string]any{"sourceUrl": "https://c.com/v.mp4", "sourceName": "yt-mp4"},
|
||||
},
|
||||
wantRefs: []sourceReference{
|
||||
{URL: "https://b.com/v.mp4", Name: "default"},
|
||||
{URL: "https://c.com/v.mp4", Name: "yt-mp4"},
|
||||
{URL: "https://a.com/v.mp4", Name: "fallback"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "skips invalid map entries",
|
||||
rawURLs: []any{
|
||||
"invalid",
|
||||
123,
|
||||
map[string]any{"sourceUrl": "https://example.com/v.mp4"},
|
||||
},
|
||||
wantRefs: []sourceReference{
|
||||
{URL: "https://example.com/v.mp4", Name: ""},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := buildSourceReferences(tt.rawURLs)
|
||||
|
||||
if len(got) != len(tt.wantRefs) {
|
||||
t.Errorf("got %d refs, want %d", len(got), len(tt.wantRefs))
|
||||
return
|
||||
}
|
||||
|
||||
for i, want := range tt.wantRefs {
|
||||
if got[i].URL != want.URL {
|
||||
t.Errorf("ref[%d].URL = %q, want %q", i, got[i].URL, want.URL)
|
||||
}
|
||||
if got[i].Name != want.Name {
|
||||
t.Errorf("ref[%d].Name = %q, want %q", i, got[i].Name, want.Name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSourceReferencesOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rawURLs := []any{
|
||||
map[string]any{"sourceUrl": "https://s.com/v.mp4", "sourceName": "s-mp4"},
|
||||
map[string]any{"sourceUrl": "https://default.com/v.mp4", "sourceName": "default"},
|
||||
map[string]any{"sourceUrl": "https://luf.com/v.mp4", "sourceName": "luf-mp4"},
|
||||
map[string]any{"sourceUrl": "https://yt.com/v.mp4", "sourceName": "yt-mp4"},
|
||||
}
|
||||
|
||||
got := buildSourceReferences(rawURLs)
|
||||
|
||||
wantOrder := []string{"default", "yt-mp4", "s-mp4", "luf-mp4"}
|
||||
if len(got) != len(wantOrder) {
|
||||
t.Fatalf("got %d refs, want %d", len(got), len(wantOrder))
|
||||
}
|
||||
|
||||
for i, wantName := range wantOrder {
|
||||
if got[i].Name != wantName {
|
||||
t.Errorf("ref[%d].Name = %q, want %q (priority order: default > yt-mp4 > s-mp4 > luf-mp4)", i, got[i].Name, wantName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLikelyM3U8(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "valid m3u8",
|
||||
input: []byte("#EXTM3U\n#EXT-X-VERSION:3"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "with leading spaces",
|
||||
input: []byte(" #EXTM3U\n"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
input: []byte{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "not m3u8",
|
||||
input: []byte("<?xml version=\"1.0\""),
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := isLikelyM3U8(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("isLikelyM3U8(%q) = %v, want %v", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLikelyMP4(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "ftyp at offset 4",
|
||||
input: []byte{0x00, 0x00, 0x00, 0x1c, 'f', 't', 'y', 'p', 0x00, 0x00, 0x00, 0x00},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "short payload",
|
||||
input: []byte{0x00, 0x00},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "not mp4",
|
||||
input: []byte{0x00, 0x00, 0x00, 0x1c, 'f', 'o', 'o', 'b'},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
input: []byte{},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := isLikelyMP4(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("isLikelyMP4(%q) = %v, want %v", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptTobeparsed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("valid encrypted payload with first key", func(t *testing.T) {
|
||||
payload := "AQAAAAABc2S7yj94zW6j4A8d9D6C3qFvYjR1hI4L6z1J3qKj5pXhKj"
|
||||
|
||||
decrypted, err := decryptTobeparsed(payload)
|
||||
if err == nil {
|
||||
var result map[string]any
|
||||
if err := json.Unmarshal(decrypted, &result); err != nil {
|
||||
t.Logf("decrypted (not valid json): %s", string(decrypted))
|
||||
} else {
|
||||
t.Logf("decrypted: %+v", result)
|
||||
}
|
||||
} else {
|
||||
t.Logf("expected decryption to succeed or fail gracefully: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("payload too short returns error", func(t *testing.T) {
|
||||
payload := "short"
|
||||
_, err := decryptTobeparsed(payload)
|
||||
if err == nil {
|
||||
t.Error("expected error for short payload")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid base64 returns error", func(t *testing.T) {
|
||||
_, err := decryptTobeparsed("not-valid-base64!!!")
|
||||
if err == nil {
|
||||
t.Error("expected error for invalid base64")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTryDecryptCTR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("decrypts correctly", func(t *testing.T) {
|
||||
key := make([]byte, 32)
|
||||
for i := range key {
|
||||
key[i] = byte(i)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create cipher: %v", err)
|
||||
}
|
||||
|
||||
iv := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b}
|
||||
cipherText := []byte("test plaintext ")
|
||||
|
||||
plainText, err := tryDecryptCTR(block, iv, cipherText)
|
||||
if err != nil {
|
||||
t.Errorf("tryDecryptCTR error: %v", err)
|
||||
}
|
||||
|
||||
_ = plainText
|
||||
})
|
||||
}
|
||||
|
||||
func TestAllAnimeClientImplementsInterfaces(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
_ interface{ Search(context.Context, string, string) ([]searchResult, error) } = &allAnimeClient{}
|
||||
_ interface{ GetEpisodeSources(context.Context, string, string, string) ([]StreamSource, error) } = &allAnimeClient{}
|
||||
_ interface{ GetEpisodes(context.Context, string, string) ([]string, error) } = &allAnimeClient{}
|
||||
_ interface{ GetAvailableEpisodes(context.Context, string) (AvailableEpisodes, error) } = &allAnimeClient{}
|
||||
)
|
||||
|
||||
t.Log("allAnimeClient implements required interfaces")
|
||||
}
|
||||
491
api/playback/service_ranking_test.go
Normal file
491
api/playback/service_ranking_test.go
Normal file
@@ -0,0 +1,491 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user