feat: idnorm and playlist support
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProcessPlaylist_AppendsURLTvg(t *testing.T) {
|
||||
in := "#EXTM3U\n" +
|
||||
`#EXTINF:-1 tvg-id="Rai 1" tvg-name="Rai 1",Rai 1` + "\n" +
|
||||
"http://example.com/stream\n"
|
||||
|
||||
var out bytes.Buffer
|
||||
ids, orig, err := processPlaylist(strings.NewReader(in), &out, "https://relay/epg")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if orig != "" {
|
||||
t.Errorf("originalEPG = %q, want empty", orig)
|
||||
}
|
||||
if got, want := ids, []string{"Rai 1"}; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("tvgIDs = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
want := `#EXTM3U url-tvg="https://relay/epg"` + "\n" +
|
||||
`#EXTINF:-1 tvg-id="Rai 1" tvg-name="Rai 1",Rai 1` + "\n" +
|
||||
"http://example.com/stream\n"
|
||||
if out.String() != want {
|
||||
t.Errorf("output mismatch.\n got: %q\nwant: %q", out.String(), want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessPlaylist_ReplacesExistingURLTvg(t *testing.T) {
|
||||
in := `#EXTM3U url-tvg="http://upstream.example/epg.xml"` + "\n" +
|
||||
`#EXTINF:-1 tvg-id="Italia 1",Italia 1` + "\n" +
|
||||
"http://example.com/stream\n"
|
||||
|
||||
var out bytes.Buffer
|
||||
ids, orig, err := processPlaylist(strings.NewReader(in), &out, "https://relay/epg")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if orig != "http://upstream.example/epg.xml" {
|
||||
t.Errorf("originalEPG = %q, want upstream URL captured", orig)
|
||||
}
|
||||
if got, want := ids, []string{"Italia 1"}; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("tvgIDs = %v, want %v", got, want)
|
||||
}
|
||||
if !strings.Contains(out.String(), `url-tvg="https://relay/epg"`) {
|
||||
t.Errorf("expected rewritten url-tvg in output, got: %q", out.String())
|
||||
}
|
||||
if strings.Contains(out.String(), "upstream.example") {
|
||||
t.Errorf("upstream URL leaked into output: %q", out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessPlaylist_EmptyEPGURLLeavesHeaderAlone(t *testing.T) {
|
||||
in := `#EXTM3U url-tvg="http://upstream.example/epg.xml"` + "\n" +
|
||||
"http://example.com/stream\n"
|
||||
|
||||
var out bytes.Buffer
|
||||
_, orig, err := processPlaylist(strings.NewReader(in), &out, "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if orig != "http://upstream.example/epg.xml" {
|
||||
t.Errorf("originalEPG = %q, want captured", orig)
|
||||
}
|
||||
if out.String() != in {
|
||||
t.Errorf("output should match input verbatim when epgURL is empty.\n got: %q\nwant: %q", out.String(), in)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessPlaylist_PreservesCRLF(t *testing.T) {
|
||||
in := "#EXTM3U\r\n" +
|
||||
`#EXTINF:-1 tvg-id="Rai 1",Rai 1` + "\r\n" +
|
||||
"http://example.com/stream\r\n"
|
||||
|
||||
var out bytes.Buffer
|
||||
if _, _, err := processPlaylist(strings.NewReader(in), &out, "https://relay/epg"); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(out.String(), `#EXTM3U url-tvg="https://relay/epg"`+"\r\n") {
|
||||
t.Errorf("CRLF not preserved in header: %q", out.String())
|
||||
}
|
||||
if !strings.Contains(out.String(), "http://example.com/stream\r\n") {
|
||||
t.Errorf("CRLF not preserved in body: %q", out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessPlaylist_StreamLinesPassThrough(t *testing.T) {
|
||||
// All non-header lines must come through byte-for-byte; only the first
|
||||
// non-empty line (the EXTM3U header) is rewritten.
|
||||
in := "#EXTM3U\n" +
|
||||
`#EXTINF:-1 tvg-id="A",A` + "\n" +
|
||||
"http://example.com/a\n" +
|
||||
`#EXTINF:-1 tvg-id="",B` + "\n" +
|
||||
"http://example.com/b\n"
|
||||
|
||||
var out bytes.Buffer
|
||||
ids, _, err := processPlaylist(strings.NewReader(in), &out, "https://relay/epg")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got, want := ids, []string{"A"}; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("tvgIDs = %v, want %v (empty tvg-id must be skipped)", got, want)
|
||||
}
|
||||
for _, must := range []string{
|
||||
`#EXTINF:-1 tvg-id="A",A` + "\n",
|
||||
"http://example.com/a\n",
|
||||
`#EXTINF:-1 tvg-id="",B` + "\n",
|
||||
"http://example.com/b\n",
|
||||
} {
|
||||
if !strings.Contains(out.String(), must) {
|
||||
t.Errorf("output missing line %q", must)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessPlaylist_SkipsLeadingBlankLines(t *testing.T) {
|
||||
in := "\n\n#EXTM3U\n#EXTINF:-1 tvg-id=\"X\",X\nhttp://example.com/x\n"
|
||||
|
||||
var out bytes.Buffer
|
||||
ids, _, err := processPlaylist(strings.NewReader(in), &out, "https://relay/epg")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(ids, []string{"X"}) {
|
||||
t.Errorf("tvgIDs = %v", ids)
|
||||
}
|
||||
if !strings.HasPrefix(out.String(), "\n\n#EXTM3U "+`url-tvg="https://relay/epg"`+"\n") {
|
||||
t.Errorf("leading blanks should be preserved before rewritten header: %q", out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessPlaylist_RejectsMissingHeader(t *testing.T) {
|
||||
in := "garbage\n#EXTINF:-1 tvg-id=\"X\",X\n"
|
||||
var out bytes.Buffer
|
||||
_, _, err := processPlaylist(strings.NewReader(in), &out, "https://relay/epg")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing #EXTM3U header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessPlaylist_RejectsEmptyInput(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
_, _, err := processPlaylist(strings.NewReader(""), &out, "https://relay/epg")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty input")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user