package sheet
import (
"strings"
"testing"
)
func sample() Sheet {
return Sheet{
Album: "The Perfect Crime",
AlbumArtist: "Straylight Run",
ArtistDir: "straylight-run",
AlbumDir: "the-perfect-crime",
Year: "2004",
Cover: "",
Tracks: []Track{
{ID: 1, Title: "The Perfect Crime #2", Artist: "", Disc: 1, Number: 1, Source: "01 one.flac", Derived: "1-01_the-perfect-crime-2.flac"},
{ID: 2, Title: "Handcuffs", Artist: "Someone Else", Disc: 1, Number: 2, Source: "02 two.flac", Derived: "1-02_handcuffs.flac"},
{ID: 3, Title: "A/B", Artist: "", Disc: 2, Number: 1, Source: "03 three.flac", Derived: "2-01_a_b.flac"},
},
}
}
func TestRenderParseRoundTrip(t *testing.T) {
before := sample()
got, err := Parse(Render(before), before)
if err != nil {
t.Fatal(err)
}
if got.Album != before.Album || got.AlbumArtist != before.AlbumArtist {
t.Errorf("album fields changed: %+v", got)
}
if got.ArtistDir != before.ArtistDir || got.AlbumDir != before.AlbumDir || got.Year != before.Year {
t.Errorf("folder fields changed: %+v", got)
}
if len(got.Tracks) != len(before.Tracks) {
t.Fatalf("got %d tracks, want %d", len(got.Tracks), len(before.Tracks))
}
for i := range before.Tracks {
if got.Tracks[i] != before.Tracks[i] {
t.Errorf("track %d: got %+v, want %+v", i, got.Tracks[i], before.Tracks[i])
}
}
if c := got.ChangedFrom(before); c.Any() {
t.Errorf("a round trip reported changes: %+v", c)
}
}
// A '#' is ordinary text in a title, and a ':' is ordinary text in a title. The
// old editor stripped an inline ' #' comment, which quietly truncated any title
// that contained one.
func TestHashesAndColonsSurviveInValues(t *testing.T) {
before := sample()
text := Render(before)
text = strings.Replace(text, "title: The Perfect Crime #2", "title: Track #4: Reprise #2", 1)
got, err := Parse(text, before)
if err != nil {
t.Fatal(err)
}
if got.Tracks[0].Title != "Track #4: Reprise #2" {
t.Errorf("title = %q", got.Tracks[0].Title)
}
}
// Deleting a block is how a file is left out of the album. The old editor told
// people a deleted line skipped the file and then filed it anyway, under a name
// it had already chosen for it.
func TestDeletingABlockDropsThatTrack(t *testing.T) {
before := sample()
text := Render(before)
lines := strings.Split(text, "\n")
var kept []string
skip := false
for _, l := range lines {
if strings.HasPrefix(l, "## 2") {
skip = true
continue
}
if skip && strings.HasPrefix(l, "## 3") {
skip = false
}
if !skip {
kept = append(kept, l)
}
}
got, err := Parse(strings.Join(kept, "\n"), before)
if err != nil {
t.Fatal(err)
}
if len(got.Tracks) != 2 {
t.Fatalf("got %d tracks, want 2", len(got.Tracks))
}
if got.Tracks[0].ID != 1 || got.Tracks[1].ID != 3 {
t.Errorf("the wrong tracks survived: %+v", got.Tracks)
}
}
func TestParsingRefusesDamage(t *testing.T) {
before := sample()
cases := []struct {
name string
edit func(string) string
want string
}{
{"empty", func(string) string { return "" }, "no tracks"},
{"unknown id", func(s string) string { return strings.Replace(s, "## 3", "## 9", 1) }, "no track 9"},
{"duplicate id", func(s string) string { return strings.Replace(s, "## 3", "## 1", 1) }, "appears twice"},
{"unknown album field", func(s string) string { return "genre: metal\n" + s }, "unknown field"},
{"unknown track field", func(s string) string { return strings.Replace(s, "## 1\n", "## 1\nlabel: x\n", 1) }, "unknown track field"},
{"track field at the top", func(s string) string { return "title: x\n" + s }, "belongs inside a track block"},
{"album field in a block", func(s string) string { return strings.Replace(s, "## 1\n", "## 1\nalbum: x\n", 1) }, "belongs at the top"},
{"junk line", func(s string) string { return "nonsense\n" + s }, "not a 'field: value' line"},
{"bad disc", func(s string) string { return strings.Replace(s, "disc: 1", "disc: 0", 1) }, "disc"},
{"bad track", func(s string) string { return strings.Replace(s, "track: 1", "track: x", 1) }, "track"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_, err := Parse(tc.edit(Render(before)), before)
if err == nil {
t.Fatal("parsing should have failed")
}
if !strings.Contains(err.Error(), tc.want) {
t.Errorf("error %q does not mention %q", err, tc.want)
}
})
}
}
func TestChangesAreReported(t *testing.T) {
before := sample()
after := before
after.Year = "2005"
after.Cover = "none"
got := after.ChangedFrom(before)
if !got.Year || !got.Cover || got.Album || got.ArtistDir || got.AlbumArtist || got.AlbumDir {
t.Errorf("changes = %+v", got)
}
if !got.Any() {
t.Error("Any should be true")
}
same := before
if same.ChangedFrom(before).Any() {
t.Error("nothing changed, but Any is true")
}
}
// The rendered file has to say what intake will name each file, because the name
// is not obvious from the title once normalisation has had it.
func TestRenderShowsTheDerivedName(t *testing.T) {
text := Render(sample())
if !strings.Contains(text, "1-01_the-perfect-crime-2.flac") {
t.Errorf("the derived name is missing:\n%s", text)
}
if !strings.Contains(text, "# from: 01 one.flac") {
t.Errorf("the source file is missing:\n%s", text)
}
}