package library import ( "context" "os" "path/filepath" "strings" "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/require" "pipu/api/internal/auth" ) func TestNormalizeExcludedDirs(t *testing.T) { dirs, err := normalizeExcludedDirs([]string{" Downloads_PumpEars/ ", `Artist\Samples`, "Downloads_PumpEars", "Artist/./Samples", "100%_loops"}) require.NoError(t, err) require.Equal(t, []string{"Downloads_PumpEars", "Artist/Samples", "100%_loops"}, dirs) for _, invalid := range []string{"", ".", "./", "/etc", "../outside", "album/../samples", `C:\Downloads`, `\\server\share`, "folder\nother", strings.Repeat("a", 1001)} { t.Run(invalid, func(t *testing.T) { _, err := normalizeExcludedDirs([]string{invalid}) require.Error(t, err) }) } _, err = normalizeExcludedDirs(make([]string, 101)) require.Error(t, err) dirs, err = normalizeExcludedDirs(nil) require.NoError(t, err) require.Empty(t, dirs) } func TestWalkPrunesExcludedSubtrees(t *testing.T) { root := t.TempDir() for _, rel := range []string{"Downloads_PumpEars/sample.wav", "Downloads_PumpEars/nested/sample.wav", "Downloads_PumpEars-copy/song.flac", "Artist/Downloads_PumpEars/song.flac", "Artist/Samples/loop.wav", "Artist/Album/song.flac", "100%_loops/loop.wav", "100xxloops/song.flac"} { p := filepath.Join(root, filepath.FromSlash(rel)) require.NoError(t, os.MkdirAll(filepath.Dir(p), 0o755)) require.NoError(t, os.WriteFile(p, []byte("audio"), 0o644)) } dirs := []string{"Downloads_PumpEars", "Artist/Samples", "100%_loops"} var visited []string found, err := walk(root, dirs, func(string) bool { return false }, func(_, _ int, current string) { visited = append(visited, current) }) require.NoError(t, err) require.ElementsMatch(t, []string{"Downloads_PumpEars-copy/song.flac", "Artist/Downloads_PumpEars/song.flac", "Artist/Album/song.flac", "100xxloops/song.flac"}, rels(found)) for _, dir := range visited { require.False(t, pathExcluded(dir, dirs), "excluded directories must not be visited") } // A point scan must reject an excluded path before touching storage or the filesystem. _, err = (&Scanner{}).ScanFile(context.Background(), Source{ExcludedDirs: dirs}, root, "Downloads_PumpEars/sample.wav") require.ErrorContains(t, err, "excluded scan folder") } type exclusionsSourceStore struct { SourceStore source Source } func (s *exclusionsSourceStore) GetSource(context.Context, uuid.UUID) (Source, error) { return s.source, nil } func (s *exclusionsSourceStore) SaveSource(_ context.Context, src Source) (Source, error) { s.source = src return src, nil } func TestExcludedDirsPatchPreservesOmittedAndAllowsClearing(t *testing.T) { id := uuid.New() store := &exclusionsSourceStore{source: Source{ID: id, Name: "Base", Type: SourceLocal, Status: "mounted", ExcludedDirs: []string{"Downloads"}}} board := NewProgressBoard() svc := &SourceService{store: store, board: board} ctx := auth.WithPrincipal(context.Background(), auth.Principal{Role: auth.RoleAdmin}) _, err := svc.UpdateSource(ctx, id, SourceInput{}) require.NoError(t, err) require.Equal(t, []string{"Downloads"}, store.source.ExcludedDirs) empty := []string{} _, err = svc.UpdateSource(ctx, id, SourceInput{ExcludedDirs: &empty}) require.NoError(t, err) require.Empty(t, store.source.ExcludedDirs) board.start(id, false, time.Now()) dirs := []string{"Downloads_PumpEars"} _, err = svc.UpdateSource(ctx, id, SourceInput{ExcludedDirs: &dirs}) require.Error(t, err) require.Empty(t, store.source.ExcludedDirs, "a running scan must not race with exclusions changes") }