summaryrefslogtreecommitdiff
path: root/internal/tag/tag_test.go
blob: f722cb6ede3c0e89dc4cb043511696871951f2e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package tag

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
)

// A minimal but genuinely valid MP3: one silent MPEG-1 Layer III frame.
// id3v2 needs a real file to append a tag to, and a zero-byte file is not one.
func writeMP3(t *testing.T, dir, name string) string {
	t.Helper()
	// 0xFF 0xFB = MPEG-1 Layer III, no CRC; 0x90 = 128kbps 44.1kHz; then padding.
	frame := []byte{0xFF, 0xFB, 0x90, 0x00}
	body := append(frame, make([]byte, 413)...)
	p := filepath.Join(dir, name)
	if err := os.WriteFile(p, body, 0o644); err != nil {
		t.Fatalf("writing %s: %v", name, err)
	}
	return p
}

func TestWriteDir_TagsEachFile(t *testing.T) {
	dir := t.TempDir()
	writeMP3(t, dir, "01 - The Law of Gravitation.mp3")
	writeMP3(t, dir, "02 - The Relation of Mathematics to Physics.mp3")

	results, err := WriteDir(dir, Metadata{
		Album:  "Character of Physical Law",
		Artist: "Richard Feynman",
	})
	if err != nil {
		t.Fatalf("WriteDir: %v", err)
	}
	if len(results) != 2 {
		t.Fatalf("got %d results, want 2", len(results))
	}
	for _, r := range results {
		if r.Err != nil {
			t.Errorf("%s: %v", r.File, r.Err)
		}
	}

	title, track, ok := Read(filepath.Join(dir, "01 - The Law of Gravitation.mp3"))
	if !ok {
		t.Fatal("tags were not readable after writing")
	}
	if title != "The Law of Gravitation" {
		t.Errorf("title = %q", title)
	}
	if track != 1 {
		t.Errorf("track = %d, want 1", track)
	}
}

// Read is handed to the feed generator, which relies on ok=false to fall back
// to the filename. An untagged file is the normal state of a fresh download,
// not an error.
func TestRead_UntaggedFileReportsNotOK(t *testing.T) {
	dir := t.TempDir()
	p := writeMP3(t, dir, "01 - Untagged.mp3")
	if _, _, ok := Read(p); ok {
		t.Error("an untagged file should report ok=false")
	}
}

func TestRead_MissingFileReportsNotOK(t *testing.T) {
	if _, _, ok := Read(filepath.Join(t.TempDir(), "nope.mp3")); ok {
		t.Error("a missing file should report ok=false, not panic")
	}
}

// Track frames are commonly "3/7". Taking the whole string would make the
// number unparseable and lose the ordering.
func TestRead_ParsesTrackOfTotal(t *testing.T) {
	dir := t.TempDir()
	writeMP3(t, dir, "03 - Third.mp3")
	writeMP3(t, dir, "01 - First.mp3")
	writeMP3(t, dir, "02 - Second.mp3")

	if _, err := WriteDir(dir, Metadata{Album: "A"}); err != nil {
		t.Fatalf("WriteDir: %v", err)
	}
	_, track, ok := Read(filepath.Join(dir, "03 - Third.mp3"))
	if !ok {
		t.Fatal("tags not readable")
	}
	if track != 3 {
		t.Errorf("track = %d, want 3 (from a \"3/3\" frame)", track)
	}
}

// ID3v2 is an MP3 convention; writing it into other containers produces
// something no player reads, so other formats are skipped rather than corrupted.
func TestWriteDir_SkipsNonMP3(t *testing.T) {
	dir := t.TempDir()
	writeMP3(t, dir, "01 - One.mp3")
	if err := os.WriteFile(filepath.Join(dir, "02 - Two.opus"), []byte("not an mp3"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(dir, "cover.jpg"), []byte("jpeg"), 0o644); err != nil {
		t.Fatal(err)
	}

	results, err := WriteDir(dir, Metadata{Album: "A"})
	if err != nil {
		t.Fatalf("WriteDir: %v", err)
	}
	if len(results) != 1 || !strings.HasSuffix(results[0].File, ".mp3") {
		t.Errorf("got %+v, want only the mp3", results)
	}
}

func TestWriteDir_MissingDirectoryIsAnError(t *testing.T) {
	if _, err := WriteDir(filepath.Join(t.TempDir(), "nope"), Metadata{}); err == nil {
		t.Error("expected an error for a missing directory")
	}
}

// A file with no track prefix should still get its metadata; only the track
// number is absent.
func TestWriteDir_HandlesUnnumberedFiles(t *testing.T) {
	dir := t.TempDir()
	writeMP3(t, dir, "Introduction.mp3")

	results, err := WriteDir(dir, Metadata{Album: "Course", Artist: "Someone"})
	if err != nil {
		t.Fatalf("WriteDir: %v", err)
	}
	if results[0].Err != nil {
		t.Fatalf("tagging failed: %v", results[0].Err)
	}
	title, track, ok := Read(filepath.Join(dir, "Introduction.mp3"))
	if !ok || title != "Introduction" {
		t.Errorf("title = %q ok = %v, want the filename stem", title, ok)
	}
	if track != 0 {
		t.Errorf("track = %d, want 0 for an unnumbered file", track)
	}
}

// Re-running the tagger must be safe; the pipeline is meant to be resumable.
func TestWriteDir_IsIdempotent(t *testing.T) {
	dir := t.TempDir()
	writeMP3(t, dir, "01 - One.mp3")
	md := Metadata{Album: "A", Artist: "B"}

	for i := 0; i < 3; i++ {
		if _, err := WriteDir(dir, md); err != nil {
			t.Fatalf("pass %d: %v", i, err)
		}
	}
	title, track, ok := Read(filepath.Join(dir, "01 - One.mp3"))
	if !ok || title != "One" || track != 1 {
		t.Errorf("after 3 passes: title=%q track=%d ok=%v", title, track, ok)
	}
}