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) } }