playlist phantom track matching added, updated search queries for efficiency
This commit is contained in:
@@ -201,25 +201,24 @@ func TestWriteM3U8EmptyDir(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseM3U8InvalidFile(t *testing.T) {
|
||||
func TestParseM3U8EmptyFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
badFile := filepath.Join(dir, "bad.m3u8")
|
||||
emptyFile := filepath.Join(dir, "empty.m3u8")
|
||||
|
||||
// Write a file without the M3U header.
|
||||
err := os.WriteFile(
|
||||
badFile,
|
||||
[]byte("just some text\n"),
|
||||
emptyFile,
|
||||
[]byte(""),
|
||||
0o644,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("could not write test file: %v", err)
|
||||
}
|
||||
|
||||
_, err = parseM3U8(badFile)
|
||||
_, err = parseM3U8(emptyFile)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid M3U file")
|
||||
t.Fatal("expected error for empty M3U file")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,3 +591,272 @@ func TestParseExtInf(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindPlaylistFileOverlappingIDs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
|
||||
// Create playlists with IDs 1 and 10 — the glob
|
||||
// pattern "1-*.m3u8" must not match "10-longer.m3u8".
|
||||
if err := writeM3U8(dir, 1, "Short", nil); err != nil {
|
||||
t.Fatalf("writeM3U8(1) error = %v", err)
|
||||
}
|
||||
|
||||
if err := writeM3U8(dir, 10, "Longer", nil); err != nil {
|
||||
t.Fatalf("writeM3U8(10) error = %v", err)
|
||||
}
|
||||
|
||||
found, err := findPlaylistFile(dir, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("findPlaylistFile(1) error = %v", err)
|
||||
}
|
||||
|
||||
if got := extractPlaylistID(found); got != 1 {
|
||||
t.Errorf(
|
||||
"findPlaylistFile(1) returned ID %d, want 1",
|
||||
got,
|
||||
)
|
||||
}
|
||||
|
||||
found, err = findPlaylistFile(dir, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("findPlaylistFile(10) error = %v", err)
|
||||
}
|
||||
|
||||
if got := extractPlaylistID(found); got != 10 {
|
||||
t.Errorf(
|
||||
"findPlaylistFile(10) returned ID %d, want 10",
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseM3U8SimpleFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
simpleFile := filepath.Join(dir, "simple.m3u")
|
||||
|
||||
// Write a simple M3U with no #EXTM3U header — just paths.
|
||||
content := "Artist/Album/01 - Song.flac\nOther/Track.mp3\n"
|
||||
|
||||
if err := os.WriteFile(
|
||||
simpleFile, []byte(content), 0o644,
|
||||
); err != nil {
|
||||
t.Fatalf("could not write test file: %v", err)
|
||||
}
|
||||
|
||||
parsed, err := parseM3U8(simpleFile)
|
||||
if err != nil {
|
||||
t.Fatalf("parseM3U8() error = %v", err)
|
||||
}
|
||||
|
||||
if len(parsed.Entries) != 2 {
|
||||
t.Fatalf(
|
||||
"parsed %d entries, want 2",
|
||||
len(parsed.Entries),
|
||||
)
|
||||
}
|
||||
|
||||
if parsed.Entries[0].RelativePath !=
|
||||
"Artist/Album/01 - Song.flac" {
|
||||
t.Errorf(
|
||||
"entry[0].RelativePath = %q, want %q",
|
||||
parsed.Entries[0].RelativePath,
|
||||
"Artist/Album/01 - Song.flac",
|
||||
)
|
||||
}
|
||||
|
||||
if parsed.Entries[1].RelativePath !=
|
||||
"Other/Track.mp3" {
|
||||
t.Errorf(
|
||||
"entry[1].RelativePath = %q, want %q",
|
||||
parsed.Entries[1].RelativePath,
|
||||
"Other/Track.mp3",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseM3U8SimpleFormatWithComments(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
simpleFile := filepath.Join(dir, "commented.m3u")
|
||||
|
||||
// Simple M3U with comment lines (no #EXTM3U header).
|
||||
content := "# Generated by SomeApp\n" +
|
||||
"Artist/Song.flac\n" +
|
||||
"# Another comment\n" +
|
||||
"Other/Track.mp3\n"
|
||||
|
||||
if err := os.WriteFile(
|
||||
simpleFile, []byte(content), 0o644,
|
||||
); err != nil {
|
||||
t.Fatalf("could not write test file: %v", err)
|
||||
}
|
||||
|
||||
parsed, err := parseM3U8(simpleFile)
|
||||
if err != nil {
|
||||
t.Fatalf("parseM3U8() error = %v", err)
|
||||
}
|
||||
|
||||
if len(parsed.Entries) != 2 {
|
||||
t.Fatalf(
|
||||
"parsed %d entries, want 2",
|
||||
len(parsed.Entries),
|
||||
)
|
||||
}
|
||||
|
||||
if parsed.Entries[0].RelativePath !=
|
||||
"Artist/Song.flac" {
|
||||
t.Errorf(
|
||||
"entry[0].RelativePath = %q, want %q",
|
||||
parsed.Entries[0].RelativePath,
|
||||
"Artist/Song.flac",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveM3UEntries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
entries := []m3uEntry{
|
||||
{RelativePath: "Artist/Song1.flac"},
|
||||
{RelativePath: "Artist/Song2.flac"},
|
||||
{RelativePath: "Artist/Song3.flac"},
|
||||
}
|
||||
|
||||
targets := map[string]struct{}{
|
||||
"/music/Artist/Song2.flac": {},
|
||||
}
|
||||
|
||||
result := removeM3UEntries(entries, targets, "/music")
|
||||
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("expected 2 entries, got %d", len(result))
|
||||
}
|
||||
|
||||
if result[0].RelativePath != "Artist/Song1.flac" {
|
||||
t.Errorf(
|
||||
"entry[0] = %q, want %q",
|
||||
result[0].RelativePath,
|
||||
"Artist/Song1.flac",
|
||||
)
|
||||
}
|
||||
|
||||
if result[1].RelativePath != "Artist/Song3.flac" {
|
||||
t.Errorf(
|
||||
"entry[1] = %q, want %q",
|
||||
result[1].RelativePath,
|
||||
"Artist/Song3.flac",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveM3UEntriesAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
entries := []m3uEntry{
|
||||
{RelativePath: "Song.flac"},
|
||||
}
|
||||
|
||||
targets := map[string]struct{}{
|
||||
"/music/Song.flac": {},
|
||||
}
|
||||
|
||||
result := removeM3UEntries(entries, targets, "/music")
|
||||
|
||||
if len(result) != 0 {
|
||||
t.Errorf("expected 0 entries, got %d", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceM3UEntryPaths(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
entries := []m3uEntry{
|
||||
{
|
||||
RelativePath: "old/path/song.flac",
|
||||
DurationSec: 180,
|
||||
DisplayTitle: "Song",
|
||||
},
|
||||
{
|
||||
RelativePath: "other/track.mp3",
|
||||
DurationSec: 240,
|
||||
DisplayTitle: "Track",
|
||||
},
|
||||
}
|
||||
|
||||
replacements := map[string]string{
|
||||
"/music/old/path/song.flac": "new/path/song.flac",
|
||||
}
|
||||
|
||||
result := replaceM3UEntryPaths(
|
||||
entries, replacements, "/music",
|
||||
)
|
||||
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("expected 2 entries, got %d", len(result))
|
||||
}
|
||||
|
||||
if result[0].RelativePath != "new/path/song.flac" {
|
||||
t.Errorf(
|
||||
"entry[0].RelativePath = %q, want %q",
|
||||
result[0].RelativePath,
|
||||
"new/path/song.flac",
|
||||
)
|
||||
}
|
||||
|
||||
// Duration and title should be preserved.
|
||||
if result[0].DurationSec != 180 {
|
||||
t.Errorf(
|
||||
"entry[0].DurationSec = %d, want 180",
|
||||
result[0].DurationSec,
|
||||
)
|
||||
}
|
||||
|
||||
// Unchanged entry should remain the same.
|
||||
if result[1].RelativePath != "other/track.mp3" {
|
||||
t.Errorf(
|
||||
"entry[1].RelativePath = %q, want %q",
|
||||
result[1].RelativePath,
|
||||
"other/track.mp3",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindM3UEntry(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
entries := []m3uEntry{
|
||||
{RelativePath: "Artist/Song1.flac"},
|
||||
{RelativePath: "Artist/Song2.flac"},
|
||||
{RelativePath: "Artist/Song3.flac"},
|
||||
}
|
||||
|
||||
entry, idx := findM3UEntry(
|
||||
entries, "/music/Artist/Song2.flac", "/music",
|
||||
)
|
||||
|
||||
if idx != 1 {
|
||||
t.Errorf("expected index 1, got %d", idx)
|
||||
}
|
||||
|
||||
if entry.RelativePath != "Artist/Song2.flac" {
|
||||
t.Errorf(
|
||||
"entry.RelativePath = %q, want %q",
|
||||
entry.RelativePath,
|
||||
"Artist/Song2.flac",
|
||||
)
|
||||
}
|
||||
|
||||
// Not found.
|
||||
_, idx = findM3UEntry(
|
||||
entries, "/music/Artist/Missing.flac", "/music",
|
||||
)
|
||||
|
||||
if idx != -1 {
|
||||
t.Errorf("expected index -1, got %d", idx)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user