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) } }