ci / api / api (push) Successful in 26s
ci / mobile / mobile (Metro-бандл Android) (push) Failing after 22s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 37s
ci / ts / ts (packages + web) (push) Successful in 51s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 53s
- schema for sources, path patterns, media files, artists, albums, tracks, genres (migration 00003) - path patterns (RE2 with named groups, skip), merge with tags by source priority, fallbacks from the file name and folder, name normalization and multi-artist split - scanner: walk, skip unchanged files by size and mtime, ffprobe in 8 workers with a single catalog writer, renames by content hash keep the track, duplicates fold into one track with the best file as primary, missing files are kept 30 days - storage-agent mounts NFS with validated server, export and an option whitelist, no shell; test mount counts audio files - admin API: sources CRUD, connection test, patterns with preview, scan, library status, file errors, host folder picker; scheduled scans and health checks; mounts are restored on start - catalog API: artist, artist albums, album with tracks, track - api image is alpine with ffmpeg for ffprobe Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
3.5 KiB
Go
93 lines
3.5 KiB
Go
package meta
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestKey(t *testing.T) {
|
|
assert.Equal(t, "the beatles", Key(" The_Beatles! "))
|
|
assert.Equal(t, Key("Ёлка"), Key("елка"))
|
|
assert.Equal(t, "ac dc", Key("AC/DC"))
|
|
assert.Equal(t, "", Key(" -- "))
|
|
}
|
|
|
|
func TestClean(t *testing.T) {
|
|
assert.Equal(t, "Hello World", Clean(" Hello__World "))
|
|
// «й» из двух кодовых точек (и + кратка) становится одной.
|
|
assert.Equal(t, "й", Clean("й"))
|
|
}
|
|
|
|
func TestSplitArtists(t *testing.T) {
|
|
known := func(k string) bool { return k == "simon garfunkel" }
|
|
names := func(cs []ArtistCredit) []string {
|
|
var out []string
|
|
for _, c := range cs {
|
|
out = append(out, c.Role+":"+c.Name)
|
|
}
|
|
return out
|
|
}
|
|
assert.Equal(t, []string{"main:Simon & Garfunkel"}, names(SplitArtists("Simon & Garfunkel", known)))
|
|
assert.Equal(t, []string{"main:Tom", "main:Jerry"}, names(SplitArtists("Tom & Jerry", known)))
|
|
assert.Equal(t, []string{"main:A", "main:B"}, names(SplitArtists("A; B", nil)))
|
|
assert.Equal(t, []string{"main:A", "feat:B", "feat:C"}, names(SplitArtists("A feat. B & C", nil)))
|
|
assert.Equal(t, []string{"main:A"}, names(SplitArtists("A / a", nil)), "duplicates by key are dropped")
|
|
assert.Empty(t, SplitArtists(" ", nil))
|
|
}
|
|
|
|
func TestPatterns(t *testing.T) {
|
|
patterns, err := CompilePatterns([]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+$`,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
m := MatchPath(patterns, "Кино/1988 - Группа крови/1-01 - Группа крови.flac")
|
|
assert.Equal(t, 1, m.Index)
|
|
assert.Equal(t, map[string]string{
|
|
"artist": "Кино", "year": "1988", "album": "Группа крови", "disc": "1", "track": "01", "title": "Группа крови",
|
|
}, m.Fields)
|
|
|
|
m = MatchPath(patterns, "Rock/Queen/Jazz (1978)/03. Jealousy.mp3")
|
|
assert.Equal(t, 2, m.Index)
|
|
assert.Equal(t, "Rock", m.Fields["genre"])
|
|
|
|
assert.True(t, MatchPath(patterns, "_incoming/x.mp3").Skip)
|
|
assert.Equal(t, -1, MatchPath(patterns, "loose.mp3").Index)
|
|
}
|
|
|
|
func TestCompilePatternErrors(t *testing.T) {
|
|
for _, bad := range []string{`(?P<artist>[`, `^(?P<band>.+)$`, `^.+$`} {
|
|
_, err := CompilePatterns([]string{`^(?P<title>.+)$`, bad})
|
|
var pe *PatternError
|
|
require.ErrorAs(t, err, &pe, bad)
|
|
assert.Equal(t, 1, pe.Index)
|
|
}
|
|
}
|
|
|
|
func TestMerge(t *testing.T) {
|
|
tags := Fields{Title: "Tag title", Artist: "", Album: "Tag album", Year: "2019-05-01", Track: "3/12"}
|
|
path := Fields{Title: "Path title", Artist: "Path artist", Album: "Path album", Year: "2018", Track: "04"}
|
|
|
|
m := Merge(tags, path, true, "x/y.flac")
|
|
assert.Equal(t, "Tag title", m.Title)
|
|
assert.Equal(t, "Path artist", m.Artist, "empty tag falls back to the path")
|
|
assert.Equal(t, "Path artist", m.AlbumArtist, "album artist defaults to the artist")
|
|
assert.Equal(t, 2019, m.Year)
|
|
assert.Equal(t, 3, m.Track)
|
|
|
|
m = Merge(tags, path, false, "x/y.flac")
|
|
assert.Equal(t, "Path title", m.Title)
|
|
assert.Equal(t, 2018, m.Year)
|
|
assert.Equal(t, 4, m.Track)
|
|
|
|
m = Merge(Fields{Genre: "Rock; Pop/rock"}, Fields{}, true, "Some Folder/07 - My_Song.mp3")
|
|
assert.Equal(t, "My Song", m.Title)
|
|
assert.Equal(t, UnknownArtist, m.Artist)
|
|
assert.Equal(t, "Some Folder", m.Album)
|
|
assert.Equal(t, []string{"Rock", "Pop", "rock"}[:2], m.Genres[:2])
|
|
}
|