Files
radiantwave-legacy/internal/download/download_test.go
T
NeilClack 91711eef93 Replace AudioFile with AudioTrack, fix download+orphan+MD5 sync, always play original audio fallback
AudioTrack table replaces AudioFile (Filename, Name, AssignedFrequency,
MD5, Selected, Order) — fixes relative-path bug in scrollerPage.go that
caused "file not found" for single-track selection.

Download goroutine now:
  - Accepts *gorm.DB and updates AudioTrack.MD5 after each download.
  - Treats checksums.md5 as source of truth: removes local files and DB
    records not in the manifest (orphans).
  - Strips "./" prefix from manifest filenames (GNU md5sum format).

AudioOptions.go seeds 14 AudioTrack records matching server checksums.md5.
List background quad width matches the widest item (+ padding), centered.

scrollerPage.go switchAudioForFrequency falls back to original/selected
audio when a frequency has no matching track or file.  playOriginalAudio
is a no-op on repeat calls to avoid 5-second audio restart loop.

Fixes progress display: passes Max=0 during downloads so Home page shows
"x / X Files Downloaded" text instead of bogus "100.0%" percentage.

Downstream audio switching (DynamicAudio) verifies an AudioTrack exists
for the frequency before assigning it to a stream — frequencies without
a track (e.g. 528 Hz) gracefully use original audio instead.
2026-07-29 16:19:15 -05:00

181 lines
4.5 KiB
Go

package download_test
import (
"crypto/md5"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"rhemawavetech.com/rhemawave/internal/db"
"rhemawavetech.com/rhemawave/internal/download"
)
func md5Hex(data []byte) string {
return fmt.Sprintf("%x", md5.Sum(data))
}
// TestDownloadFileHashChanged verifies that when the server manifest reports a
// new MD5 for an existing local file, the old file is removed, the new version
// is downloaded, and the database record is updated with the new hash.
func TestDownloadFileHashChanged(t *testing.T) {
audioDir := t.TempDir()
dbDir := t.TempDir()
gdb, err := gorm.Open(sqlite.Open(filepath.Join(dbDir, "test.db")), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
sqlDB, err := gdb.DB()
if err != nil {
t.Fatal(err)
}
sqlDB.SetMaxOpenConns(1)
sqlDB.SetMaxIdleConns(1)
if err := gdb.AutoMigrate(&db.AudioTrack{}); err != nil {
t.Fatal(err)
}
oldContent := []byte("this is the OLD version of the audio file")
oldMD5 := md5Hex(oldContent)
newContent := []byte("this is the NEW version of the audio file")
newMD5 := md5Hex(newContent)
track := db.AudioTrack{
Filename: "174Hz.wav",
Name: "174 Hz",
AssignedFrequency: 174,
MD5: oldMD5,
Selected: true,
Order: 2,
}
if err := gdb.Create(&track).Error; err != nil {
t.Fatal(err)
}
oldPath := filepath.Join(audioDir, "174Hz.wav")
if err := os.WriteFile(oldPath, oldContent, 0644); err != nil {
t.Fatal(err)
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/checksums.md5":
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "%s 174Hz.wav\n", newMD5)
case "/174Hz.wav":
w.WriteHeader(http.StatusOK)
w.Write(newContent)
default:
http.NotFound(w, r)
}
}))
defer server.Close()
progress := make(chan download.DownloadStatus, 100)
done := make(chan struct{})
go func() {
download.DownloadAudioFiles(progress, gdb, audioDir, server.URL+"/")
close(done)
}()
for range progress {
}
<-done
newPath := filepath.Join(audioDir, "174Hz.wav")
gotContent, err := os.ReadFile(newPath)
if err != nil {
t.Fatalf("reading new file: %v", err)
}
if string(gotContent) != string(newContent) {
t.Errorf("new file content = %q, want %q", string(gotContent), string(newContent))
}
var updated db.AudioTrack
if err := gdb.First(&updated, track.ID).Error; err != nil {
t.Fatalf("fetching updated track: %v", err)
}
if updated.MD5 != newMD5 {
t.Errorf("DB MD5 = %q, want %q", updated.MD5, newMD5)
}
}
// TestDownloadOrphanRemoval verifies that files and DB records not in the
// manifest are removed.
func TestDownloadOrphanRemoval(t *testing.T) {
audioDir := t.TempDir()
dbDir := t.TempDir()
gdb, err := gorm.Open(sqlite.Open(filepath.Join(dbDir, "test.db")), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
sqlDB, err := gdb.DB()
if err != nil {
t.Fatal(err)
}
sqlDB.SetMaxOpenConns(1)
sqlDB.SetMaxIdleConns(1)
if err := gdb.AutoMigrate(&db.AudioTrack{}); err != nil {
t.Fatal(err)
}
orphanPath := filepath.Join(audioDir, "orphan.wav")
if err := os.WriteFile(orphanPath, []byte("orphan"), 0644); err != nil {
t.Fatal(err)
}
gdb.Create(&db.AudioTrack{Filename: "ghost.wav", Name: "Ghost Track", Order: 99})
keptContent := []byte("kept file")
keptMD5 := md5Hex(keptContent)
keptPath := filepath.Join(audioDir, "kept.wav")
if err := os.WriteFile(keptPath, keptContent, 0644); err != nil {
t.Fatal(err)
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/checksums.md5":
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "%s kept.wav\n", keptMD5)
case "/kept.wav":
w.WriteHeader(http.StatusOK)
w.Write(keptContent)
default:
http.NotFound(w, r)
}
}))
defer server.Close()
progress := make(chan download.DownloadStatus, 100)
done := make(chan struct{})
go func() {
download.DownloadAudioFiles(progress, gdb, audioDir, server.URL+"/")
close(done)
}()
for range progress {
}
<-done
if _, err := os.Stat(orphanPath); !os.IsNotExist(err) {
t.Errorf("orphan file %s should have been removed", orphanPath)
}
var count int64
gdb.Model(&db.AudioTrack{}).Where("filename = ?", "ghost.wav").Count(&count)
if count != 0 {
t.Errorf("orphan DB record ghost.wav should have been deleted, count=%d", count)
}
if _, err := os.Stat(keptPath); os.IsNotExist(err) {
t.Errorf("kept file %s should still exist after download", keptPath)
}
}