SpotiFLAC-Mobile/go_backend/library_scan_supplement_test.go
zarzet 1bd54c530b fix(saf): use extension-agnostic .partial staged filename
Staged SAF outputs and library-scan partials now share a single naming pattern: '<name>.partial' regardless of the audio extension. The previous '<name>.partial.<ext>' form caused SAF / media-scanner to surface half-written files as valid audio.

- SafDownloadHandler: force 'application/octet-stream' MIME for staged docs and collapse buildStagedSafFileName to '<name>.partial'. Keep the legacy form behind buildLegacyStagedSafFileName and sweep both via deleteStaleStagedFiles so upgrades clean old residue.

- library_scan: add isLibraryStagingFile that skips both the new and legacy partial patterns during collectLibraryAudioFiles so residual staging files never show up in the library.

- library_scan_supplement_test: seed both legacy and new partial files and assert they are ignored by the scanner.
2026-05-08 20:35:41 +07:00

163 lines
5.2 KiB
Go

package gobackend
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
func TestLibraryScanFullIncrementalAndMetadataFallbacks(t *testing.T) {
dir := t.TempDir()
albumDir := filepath.Join(dir, "Album")
if err := os.MkdirAll(albumDir, 0755); err != nil {
t.Fatal(err)
}
mp3Path := filepath.Join(albumDir, "Artist - Song.mp3")
if err := os.WriteFile(mp3Path, []byte("not really mp3"), 0600); err != nil {
t.Fatal(err)
}
numberedPath := filepath.Join(albumDir, "01 - Intro.ogg")
if err := os.WriteFile(numberedPath, []byte("not really ogg"), 0600); err != nil {
t.Fatal(err)
}
apePath := filepath.Join(albumDir, "tagged.ape")
if err := os.WriteFile(apePath, []byte("audio"), 0600); err != nil {
t.Fatal(err)
}
if err := WriteAPETags(apePath, &APETag{Items: AudioMetadataToAPEItems(&AudioMetadata{
Title: "Tagged",
Artist: "APE Artist",
Album: "APE Album",
TrackNumber: 2,
TotalTracks: 3,
Date: "2026",
Genre: "Pop",
Composer: "Composer",
})}); err != nil {
t.Fatalf("write ape tags: %v", err)
}
cuePath, _ := writeExportCueFixture(t, albumDir)
if err := os.WriteFile(filepath.Join(albumDir, "ignored.txt"), []byte("ignore"), 0600); err != nil {
t.Fatal(err)
}
legacyPartialPath := filepath.Join(albumDir, "Artist - Song.partial.flac")
if err := os.WriteFile(legacyPartialPath, []byte("partial flac"), 0600); err != nil {
t.Fatal(err)
}
newPartialPath := filepath.Join(albumDir, "Artist - Song.flac.partial")
if err := os.WriteFile(newPartialPath, []byte("partial flac"), 0600); err != nil {
t.Fatal(err)
}
files, err := collectLibraryAudioFiles(dir, make(chan struct{}))
if err != nil {
t.Fatalf("collectLibraryAudioFiles: %v", err)
}
if len(files) < 4 {
t.Fatalf("files = %#v", files)
}
for _, file := range files {
if file.path == legacyPartialPath || file.path == newPartialPath {
t.Fatalf("staging file should be ignored: %#v", files)
}
}
cancelCh := make(chan struct{})
close(cancelCh)
if _, err := collectLibraryAudioFiles(dir, cancelCh); err == nil {
t.Fatal("expected cancelled collect")
}
jsonText, err := ScanLibraryFolder(dir)
if err != nil {
t.Fatalf("ScanLibraryFolder: %v", err)
}
var results []LibraryScanResult
if err := json.Unmarshal([]byte(jsonText), &results); err != nil {
t.Fatalf("decode scan results: %v", err)
}
if len(results) < 4 {
t.Fatalf("scan results = %#v", results)
}
foundTagged := false
for _, result := range results {
if result.FilePath == apePath {
foundTagged = result.TrackName == "Tagged" && result.ArtistName == "APE Artist"
}
}
if !foundTagged {
t.Fatalf("tagged APE not found in %#v", results)
}
if progress := GetLibraryScanProgress(); !strings.Contains(progress, `"IsComplete":true`) && !strings.Contains(progress, `"is_complete":true`) {
t.Fatalf("progress = %s", progress)
}
metaJSON, err := ReadAudioMetadataWithDisplayName(mp3Path, "Display Artist - Display Song.mp3")
if err != nil {
t.Fatalf("ReadAudioMetadataWithDisplayName: %v", err)
}
if !strings.Contains(metaJSON, "Display Song") {
t.Fatalf("metadata json = %s", metaJSON)
}
noExtPath := filepath.Join(albumDir, "noext")
if err := os.WriteFile(noExtPath, []byte("audio"), 0600); err != nil {
t.Fatal(err)
}
noExtJSON, err := ReadAudioMetadataWithDisplayNameAndCoverCacheKey(noExtPath, "Artist - No Ext.mp3", "cache-key")
if err != nil {
t.Fatalf("ReadAudioMetadataWithDisplayNameAndCoverCacheKey: %v", err)
}
if !strings.Contains(noExtJSON, "No Ext") {
t.Fatalf("no ext metadata = %s", noExtJSON)
}
existing := map[string]int64{}
for _, file := range files {
existing[file.path] = file.modTime
}
if info, err := os.Stat(cuePath); err == nil {
existing[cuePath+"#track01"] = info.ModTime().UnixMilli()
}
incJSON, err := scanLibraryFolderIncrementalWithExistingFiles(dir, existing)
if err != nil {
t.Fatalf("incremental existing: %v", err)
}
var inc IncrementalScanResult
if err := json.Unmarshal([]byte(incJSON), &inc); err != nil {
t.Fatalf("decode incremental: %v", err)
}
if inc.SkippedCount == 0 {
t.Fatalf("incremental = %#v", inc)
}
if _, err := ScanLibraryFolderIncremental("", "{}"); err == nil {
t.Fatal("expected empty incremental folder error")
}
if incJSON, err := ScanLibraryFolderIncremental(dir, `not-json`); err != nil || incJSON == "" {
t.Fatalf("incremental invalid existing JSON = %q/%v", incJSON, err)
}
snapshot := filepath.Join(dir, "snapshot.txt")
if err := os.WriteFile(snapshot, []byte("bad\n123\t"+mp3Path+"\nnotint\tpath\n999\t"+filepath.Join(dir, "deleted.mp3")+"\n"), 0600); err != nil {
t.Fatal(err)
}
fromSnapshot, err := ScanLibraryFolderIncrementalFromSnapshot(dir, snapshot)
if err != nil {
t.Fatalf("snapshot incremental: %v", err)
}
if !strings.Contains(fromSnapshot, "deleted.mp3") {
t.Fatalf("snapshot result = %s", fromSnapshot)
}
if _, err := ScanLibraryFolder(""); err == nil {
t.Fatal("expected empty folder scan error")
}
fileInsteadOfFolder := filepath.Join(dir, "file.flac")
if err := os.WriteFile(fileInsteadOfFolder, []byte("audio"), 0600); err != nil {
t.Fatal(err)
}
if _, err := ScanLibraryFolder(fileInsteadOfFolder); err == nil {
t.Fatal("expected not folder error")
}
CancelLibraryScan()
SetLibraryCoverCacheDir("")
}