package allanime import ( "bytes" "context" "crypto/aes" "encoding/json" "mal/internal/domain" "testing" ) func isLikelyM3U8(data []byte) bool { return bytes.HasPrefix(bytes.TrimSpace(data), []byte("#EXTM3U")) } func isLikelyMP4(data []byte) bool { if len(data) < 8 { return false } return string(data[4:8]) == "ftyp" } type stringTransformTestCase struct { name string input string want string } type sourceReferencesTestCase struct { name string rawURLs []any wantRefs []sourceReference } func runStringTransformTests(t *testing.T, tests []stringTransformTestCase, fn func(string) string) { t.Helper() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := fn(tt.input) if got != tt.want { t.Errorf("got %q for input %q, want %q", got, tt.input, tt.want) } }) } } func runSourceReferenceTests(t *testing.T, tests []sourceReferencesTestCase) { t.Helper() 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 TestDecodeSourceURL(t *testing.T) { t.Parallel() tests := []stringTransformTestCase{ { name: "empty returns empty", input: "", want: "", }, { name: "with double prefix stripped", input: "--example.com/video.mp4", want: "example.com/video.mp4", }, { name: "hex substitution", input: "7aexample", want: "Bexample", }, { name: "mixed substitution", input: "79url7a01", want: "AurlB9", }, { name: "clock replacement", input: "/clock", want: "/clock.json", }, { name: "no clock replacement if already json", input: "/clock.json", want: "/clock.json", }, { name: "complex url", input: "--79stream7acom", want: "AstreamBcom", }, } runStringTransformTests(t, tests, decodeSourceURL) } func TestDetectStreamType(t *testing.T) { t.Parallel() tests := []stringTransformTestCase{ { name: "m3u8 extension", input: "https://example.com/video.m3u8", want: "m3u8", }, { name: "master m3u8", input: "https://example.com/master.m3u8", want: "m3u8", }, { name: "mp4 extension", input: "https://example.com/video.mp4", want: "mp4", }, { name: "unknown", input: "https://example.com/video.avi", want: "unknown", }, { name: "empty returns unknown", input: "", want: "unknown", }, { name: "case insensitive - M3U8", input: "https://example.com/MASTER.M3U8", want: "m3u8", }, } runStringTransformTests(t, tests, detectStreamType) } func TestDetectEmbedType(t *testing.T) { t.Parallel() tests := []stringTransformTestCase{ { name: "streamwish", input: "https://streamwish.com/e/abc123", want: "embed", }, { name: "streamsb", input: "https://streamsb.com/e/abc123", want: "embed", }, { name: "mp4upload", input: "https://mp4upload.com/e/abc123", want: "embed", }, { name: "ok.ru", input: "https://ok.ru/video/123", want: "embed", }, { name: "gogoplay", input: "https://gogoplay.io/embed/123", want: "embed", }, { name: "streamlare", input: "https://streamlare.com/e/abc", want: "embed", }, { name: "unknown host", input: "https://unknown.com/video", want: "unknown", }, } runStringTransformTests(t, tests, detectEmbedType) } 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 := []sourceReferencesTestCase{ { 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: ""}, }, }, } runSourceReferenceTests(t, tests) } 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("