package library import ( "os" "path/filepath" "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestMatchRenames(t *testing.T) { a := FileRow{ID: uuid.New(), RelPath: "old/a.flac", Size: 10, ContentHash: []byte{1}} b := FileRow{ID: uuid.New(), RelPath: "old/b.flac", Size: 10, ContentHash: []byte{2}} noHash := FileRow{ID: uuid.New(), RelPath: "old/c.flac", Size: 10} newFiles := []walked{ {rel: "new/a.flac", size: 10, hash: []byte{1}}, {rel: "new/a-copy.flac", size: 10, hash: []byte{1}}, // второй файл с тем же содержимым — новый {rel: "new/x.flac", size: 11, hash: []byte{2}}, // другой размер — не переименование {rel: "new/y.flac", size: 10}, } moves, rest, gone := matchRenames(newFiles, []FileRow{a, b, noHash}) require.Len(t, moves, 1) assert.Equal(t, a.ID, moves[0].from.ID) assert.Equal(t, "new/a.flac", moves[0].to.rel) assert.Equal(t, []string{"new/a-copy.flac", "new/x.flac", "new/y.flac"}, rels(rest)) assert.Equal(t, []uuid.UUID{b.ID, noHash.ID}, []uuid.UUID{gone[0].ID, gone[1].ID}) } func rels(ws []walked) []string { var out []string for _, w := range ws { out = append(out, w.rel) } return out } func TestWalkAndHash(t *testing.T) { root := t.TempDir() write := func(rel string, size int) { p := filepath.Join(root, filepath.FromSlash(rel)) require.NoError(t, os.MkdirAll(filepath.Dir(p), 0o755)) data := make([]byte, size) for i := range data { data[i] = byte(i) } require.NoError(t, os.WriteFile(p, data, 0o644)) } write("Artist/Album/01 - Song.FLAC", 100) write("Artist/Album/cover.jpg", 10) write("Artist/.hidden/02.mp3", 10) write("_incoming/new.mp3", 10) write("big.mp3", 3<<20) found, err := walk(root, nil, func(rel string) bool { return rel == "_incoming/new.mp3" }, nil) require.NoError(t, err) assert.ElementsMatch(t, []string{"Artist/Album/01 - Song.FLAC", "big.mp3"}, rels(found)) for _, w := range found { assert.Equal(t, w.mtime, w.mtime.Truncate(time.Microsecond)) } h1, err := contentHash(filepath.Join(root, "big.mp3"), 3<<20) require.NoError(t, err) require.NoError(t, os.Rename(filepath.Join(root, "big.mp3"), filepath.Join(root, "moved.mp3"))) h2, err := contentHash(filepath.Join(root, "moved.mp3"), 3<<20) require.NoError(t, err) assert.Equal(t, h1, h2, "hash survives a rename") _, err = walk(filepath.Join(root, "nope"), nil, func(string) bool { return false }, nil) assert.Error(t, err) } func TestWalkReportsIncrementalProgress(t *testing.T) { root := t.TempDir() for _, dir := range []string{"a-empty", "b-album", ".hidden"} { require.NoError(t, os.MkdirAll(filepath.Join(root, dir), 0o755)) } for _, rel := range []string{"b-album/1.mp3", "b-album/2.flac", "b-album/skip.mp3", ".hidden/3.mp3"} { require.NoError(t, os.WriteFile(filepath.Join(root, filepath.FromSlash(rel)), []byte("audio"), 0o644)) } var counts, folders []int var paths []string found, err := walk(root, nil, func(rel string) bool { return rel == "b-album/skip.mp3" }, func(n, dirs int, path string) { counts, folders, paths = append(counts, n), append(folders, dirs), append(paths, path) }) require.NoError(t, err) require.Len(t, found, 2) assert.Equal(t, []int{0, 0, 0, 1, 2}, counts, "reports activity before any file is found and after each accepted audio file") assert.Equal(t, []int{1, 2, 3, 3, 3}, folders) assert.Equal(t, []string{".", "a-empty", "b-album", "b-album", "b-album"}, paths) } func TestProgressDTOIncludesWalkActivity(t *testing.T) { p := Progress{Phase: "walking", Running: true, Found: 7, WalkedDirs: 4, CurrentDir: "Artist/Album"} dto := progressDTO(p) require.NotNil(t, dto.Phase) assert.Equal(t, "walking", string(*dto.Phase)) assert.Equal(t, p.Found, dto.Found) require.NotNil(t, dto.WalkedDirs) assert.Equal(t, p.WalkedDirs, *dto.WalkedDirs) require.NotNil(t, dto.CurrentDir) assert.Equal(t, p.CurrentDir, *dto.CurrentDir) } func TestParseFFprobe(t *testing.T) { raw := []byte(`{ "streams": [ {"codec_type": "video", "codec_name": "mjpeg"}, {"codec_type": "audio", "codec_name": "opus", "sample_rate": "48000", "channels": 2, "tags": {"TITLE": "Song", "ARTIST": "Band feat. Guest", "ALBUM": "Record", "DATE": "2020", "track": "3/10"}} ], "format": {"duration": "215.004", "bit_rate": "128000", "tags": {"album_artist": "Band"}} }`) p, err := parseFFprobe(raw, "/x/Song.OPUS") require.NoError(t, err) assert.Equal(t, "opus", p.Format) assert.Equal(t, "opus", p.Codec) assert.Equal(t, 215004, p.DurationMs) assert.Equal(t, 128000, p.Bitrate) assert.Equal(t, 48000, p.SampleRate) assert.Equal(t, "Song", p.Tags.Title) assert.Equal(t, "Band", p.Tags.AlbumArtist) assert.Equal(t, "3/10", p.Tags.Track) assert.False(t, p.Lossless()) _, err = parseFFprobe([]byte(`{"streams": [], "format": {}}`), "x.mp3") assert.Error(t, err) }