package uploads import ( "context" "path/filepath" "testing" "github.com/google/uuid" "github.com/stretchr/testify/require" "pipu/api/internal/library" ) type previewCatalog struct{ source library.Source } func (c *previewCatalog) GetSource(context.Context, uuid.UUID) (library.Source, error) { return c.source, nil } func (c *previewCatalog) ListSources(context.Context) ([]library.Source, error) { return []library.Source{c.source}, nil } func (c *previewCatalog) Patterns(context.Context, uuid.UUID) ([]library.PatternRow, error) { return nil, nil } type previewStore struct{} func (previewStore) Settings(context.Context) (Settings, error) { return Settings{}, nil } func (previewStore) MatchCatalog(context.Context, string, string) (*uuid.UUID, *uuid.UUID, int, error) { return nil, nil, 0, nil } func (previewStore) BatchMaxDisc(context.Context, uuid.UUID, string) (int, error) { return 1, nil } type previewRoot string func TestPreviewMissingYearStillHasImportPath(t *testing.T) { root := t.TempDir() catalog := &previewCatalog{source: library.Source{ID: uuid.New(), Name: "Base", Type: library.SourceLocal, AcceptsUploads: true, WriteTemplate: nullable("{albumartist}/{albumartist} - {album} ({year})/{title}.{ext}")}} planner := &PlacementPlanner{store: previewStore{}, lib: catalog, roots: previewRoot(root)} f := File{Status: StatusPendingReview, OriginalName: "song.mp3", Meta: &Meta{ Artist: "Artist feat. Guest", AlbumArtist: "Artist", Album: "Album", Title: "Song"}} planner.previewPlacement(context.Background(), &f) require.NotNil(t, f.TargetRelPath) require.Equal(t, "Artist/Artist - Album/Song.mp3", *f.TargetRelPath) require.NotNil(t, f.TargetStoragePath) require.Equal(t, CodeMissingYear, *f.StatusReason) pl := planner.plan(context.Background(), f, *f.Meta, f.TargetSourceID) require.Equal(t, f.TargetRelPath, pl.RelPath) require.Nil(t, f.Meta.Year, "path rendering must not invent metadata") year := 2026 f.Meta.Year = &year planner.previewPlacement(context.Background(), &f) require.Equal(t, "Artist/Artist - Album (2026)/Song.mp3", *f.TargetRelPath) require.Nil(t, f.StatusReason) } func (r previewRoot) Root(library.Source) (string, error) { return string(r), nil } func TestPreviewUsesCurrentImportTemplate(t *testing.T) { ctx := context.Background() root := t.TempDir() catalog := &previewCatalog{source: library.Source{ID: uuid.New(), Name: "Base", Type: library.SourceLocal, AcceptsUploads: true, WriteTemplate: nullable("{albumartist}/{album}/{title}.{ext}")}} planner := &PlacementPlanner{store: previewStore{}, lib: catalog, roots: previewRoot(root)} track := 3 year := 2026 f := File{Status: StatusPendingReview, OriginalName: "song.flac", Meta: &Meta{ Artist: "Artist", AlbumArtist: "Artist", Album: "Album", Title: "Song", Track: &track, Year: &year}, TargetSourceID: &catalog.source.ID, TargetRelPath: nullable("stale/path.flac"), StatusReason: nullable(CodeNoTarget)} planner.previewPlacement(ctx, &f) require.Equal(t, "Artist/Album/Song.flac", *f.TargetRelPath) require.Equal(t, filepath.Join(root, "Artist", "Album", "Song.flac"), *f.TargetStoragePath) require.Nil(t, f.StatusReason) catalog.source.WriteTemplate = nullable("{albumartist}/{album}/{track:02} - {title}.{ext}") planner.previewPlacement(ctx, &f) planned := planner.plan(ctx, f, *f.Meta, f.TargetSourceID) require.Equal(t, "Artist/Album/03 - Song.flac", *f.TargetRelPath) require.Equal(t, planned.RelPath, f.TargetRelPath, "preview and import must use the same path") catalog.source.Type = library.SourceNFS catalog.source.NFSServer, catalog.source.NFSExport = nullable("nas"), nullable("/music/") planner.previewPlacement(ctx, &f) require.Equal(t, "nas:/music/Artist/Album/03 - Song.flac", *f.TargetStoragePath) f.Status = StatusImported catalog.source.WriteTemplate = nullable("changed/{title}.{ext}") planner.previewPlacement(ctx, &f) require.Equal(t, "Artist/Album/03 - Song.flac", *f.TargetRelPath, "imported files retain their actual destination") f.Status = StatusPendingReview catalog.source.WriteTemplate = nil planner.previewPlacement(ctx, &f) require.Nil(t, f.TargetRelPath, "invalid configuration must not show the stale path") require.Nil(t, f.TargetStoragePath) require.Equal(t, CodeTemplate, *f.StatusReason) // Reading a preview does not write to the store or change the review status. require.Equal(t, StatusPendingReview, f.Status) }