Files
pipu-music/services/api/internal/library/meta/meta_test.go
T
mrleo1nidandClaude Opus 5.5 c583991d14
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
feat(api): library sources, scanner and catalog (M2)
- 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>
2026-10-20 06:48:37 +03:00

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