ci / module boundaries / boundaries (push) Successful in 7s
ci / db / db (push) Successful in 2m33s
ci / api / api (push) Successful in 1m10s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 3m45s
ci / ts / ts (packages + web) (push) Successful in 6m29s
131 lines
5.3 KiB
Go
131 lines
5.3 KiB
Go
package meta
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const docTemplate = "{albumartist}/{year} - {album}/{disc:1-}{track:02} - {title}.{ext}"
|
|
|
|
func TestRenderWithoutMissingYear(t *testing.T) {
|
|
for _, pattern := range []string{
|
|
"{artist}/{album} ({year})/{track:02} - {title}.{ext}",
|
|
"{artist}/{year} - {album}/{track:02} - {title}.{ext}",
|
|
"{artist}/{album} - {year}/{track:02} - {title}.{ext}",
|
|
"{artist}/{year}/{album}/{track:02} - {title}.{ext}",
|
|
"{artist}/[{year} - ]{album}/{track:02} - {title}.{ext}",
|
|
} {
|
|
t.Run(pattern, func(t *testing.T) {
|
|
tpl, err := ParseTemplate(pattern)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
input := in(Fields{Artist: "A", Album: "B", Title: "C", Track: "1"}, 1, "mp3")
|
|
got, err := tpl.RenderWithoutMissingYear(input)
|
|
if err != nil || got != "A/B/01 - C.mp3" {
|
|
t.Fatalf("got %q, %v", got, err)
|
|
}
|
|
input.Fields.Title = ""
|
|
if _, err := tpl.RenderWithoutMissingYear(input); err == nil {
|
|
t.Fatal("missing title must still fail")
|
|
}
|
|
input.Fields.Title, input.Fields.Year = "C", "2026"
|
|
want, err := tpl.Render(input)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err = tpl.RenderWithoutMissingYear(input)
|
|
if err != nil || got != want {
|
|
t.Fatalf("known year: got %q, %v; want %q", got, err, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func in(f Fields, discs int, ext string) TemplateInput {
|
|
return TemplateInput{Fields: f, DiscCount: discs, Ext: ext}
|
|
}
|
|
|
|
func TestRenderTemplate(t *testing.T) {
|
|
tpl, err := ParseTemplate(docTemplate)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f := Fields{Artist: "Кино", Album: "Группа крови", Year: "1988-01-05", Track: "3/11", Disc: "1", Title: "Война"}
|
|
cases := []struct {
|
|
name string
|
|
input TemplateInput
|
|
want string
|
|
}{
|
|
{"single disc", in(f, 1, "FLAC"), "Кино/1988 - Группа крови/03 - Война.flac"},
|
|
{"multi disc", in(f, 2, "mp3"), "Кино/1988 - Группа крови/1-03 - Война.mp3"},
|
|
{"forbidden chars and trailing dots", in(Fields{Artist: "AC/DC", Album: "What?...", Year: "1980", Track: "1", Title: ".hidden: x"}, 1, "mp3"),
|
|
"AC_DC/1980 - What_/01 - .hidden_ x.mp3"},
|
|
}
|
|
for _, c := range cases {
|
|
got, err := tpl.Render(c.input)
|
|
if err != nil || got != c.want {
|
|
t.Errorf("%s: got %q, %v; want %q", c.name, got, err, c.want)
|
|
}
|
|
}
|
|
|
|
if _, err := tpl.Render(in(Fields{Artist: "A", Album: "B", Track: "1", Title: "C"}, 1, "mp3")); err == nil {
|
|
t.Error("missing year outside [ ] must fail")
|
|
}
|
|
opt, _ := ParseTemplate("{albumartist}/[{year} - ]{album}/{track:02} - {title}.{ext}")
|
|
if got, _ := opt.Render(in(Fields{Artist: "A", Album: "B", Track: "1", Title: "C"}, 1, "mp3")); got != "A/B/01 - C.mp3" {
|
|
t.Errorf("optional part: %q", got)
|
|
}
|
|
long, _ := opt.Render(in(Fields{Artist: "A", Album: "B", Track: "1", Title: strings.Repeat("я", 300)}, 1, "flac"))
|
|
if seg := long[strings.LastIndex(long, "/")+1:]; len(seg) > MaxSegmentBytes || !strings.HasSuffix(seg, ".flac") {
|
|
t.Errorf("long title segment: %d bytes %q", len(seg), seg[len(seg)-10:])
|
|
}
|
|
}
|
|
|
|
func TestParseTemplateErrors(t *testing.T) {
|
|
for _, bad := range []string{"", "/{artist}/{title}.{ext}", "{artist}/{title}.mp3", "{artist}/{nope}/{title}.{ext}",
|
|
"{artist}/../{title}.{ext}", "[{artist}/{title}.{ext}", "{artist}/{year:02}/{title}.{ext}", "{artist}//{title}.{ext}"} {
|
|
if _, err := ParseTemplate(bad); err == nil {
|
|
t.Errorf("%q must be rejected", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Шаблоны из docs/library-and-storage.md превращаются в шаблоны записи и проходят проверку обратимости.
|
|
func TestSuggestAndCheck(t *testing.T) {
|
|
exprs := []string{
|
|
`^(?P<skip>_incoming)/`,
|
|
`^(?P<artist>[^/]+)/(?P<year>\d{4}) - (?P<album>[^/]+)/(?:(?P<disc>\d)-)?(?P<track>\d{1,3}) - (?P<title>[^/]+)\.\w+$`,
|
|
`^(?P<genre>[^/]+)/(?P<artist>[^/]+)/(?P<album>.+?) \((?P<year>\d{4})\)/(?P<track>\d+)\. (?P<title>[^/]+)\.\w+$`,
|
|
}
|
|
paths := []string{"Кино/1988 - Группа крови/01 - Группа крови.flac", "Кино/1988 - Группа крови/02 - Закрой за мной дверь.flac",
|
|
"Rock/Queen/Jazz (1978)/3. Jealousy.mp3"}
|
|
got, err := SuggestTemplate(exprs, paths)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != "{artist}/{year} - {album}/{disc:1-}{track:02} - {title}.{ext}" {
|
|
t.Fatalf("suggested %q", got)
|
|
}
|
|
|
|
tpl, _ := ParseTemplate(got)
|
|
patterns, _ := CompilePatterns(exprs)
|
|
samples := []TemplateSample{
|
|
{RelPath: paths[0], Input: in(Fields{Artist: "Кино", Year: "1988", Album: "Группа крови", Track: "1", Title: "Группа крови"}, 1, "flac")},
|
|
{RelPath: paths[1], Input: in(Fields{Artist: "Кино", Year: "1988", Album: "Группа крови", Track: "2", Title: "Закрой за мной дверь"}, 1, "flac")},
|
|
{RelPath: "Pink Floyd/1979 - The Wall/2-01 - Hey You.flac",
|
|
Input: in(Fields{Artist: "Pink Floyd", Year: "1979", Album: "The Wall", Disc: "2", Track: "1", Title: "Hey You"}, 2, "flac")},
|
|
}
|
|
res := CheckTemplate(tpl, patterns, samples)
|
|
if !res.OK || res.RoundTrip != 3 || res.SamePath != 3 {
|
|
t.Fatalf("check: %+v", res)
|
|
}
|
|
|
|
// Шаблон, который регэкспы источника не разберут обратно.
|
|
bad, _ := ParseTemplate("{artist} - {album}/{title}.{ext}")
|
|
res = CheckTemplate(bad, patterns, samples)
|
|
if res.OK || res.RoundTrip != 0 || len(res.Mismatches) != 3 || res.Mismatches[0].Fields[0] != "pattern" {
|
|
t.Fatalf("bad template check: %+v", res)
|
|
}
|
|
}
|