//go:build linux package application import ( "testing" "gorm.io/gorm" "rhemawavetech.com/rhemawave/internal/db" "rhemawavetech.com/rhemawave/internal/page" ) // Regression: The app used to reach READY before audio downloads completed, // showing "Status: READY" while "0 / 14 Files Downloaded" was still in progress. // The fix gates READY on downloadsComplete being true. // TestStateTransitionsToDownloadingAssets verifies that when all config // prerequisites are met but downloads are not complete, the state transitions // to DOWNLOADING_ASSETS instead of READY. func TestStateTransitionsToDownloadingAssets(t *testing.T) { // Set up minimal application state app := &Application{ state: FIRST_RUN, DB: setupTestDB(t), running: true, } // Simulate: network is up, email and license are set networkIsUp = true config, err := db.GetConfig(app.DB) if err != nil { t.Fatal(err) } config.EmailAddress = "test@example.com" config.LicenseKey = "test-key-123" app.DB.Save(config) // Downloads are NOT complete app.downloadsComplete = false // Create a minimal router with channels downloadDone := make(chan struct{}) app.PageRouter = &page.Router{ Context: page.PageCtx{ DbPtr: app.DB, DownloadDone: downloadDone, }, } // Run checkState app.checkState() // Should be in DOWNLOADING_ASSETS, not READY if app.state != DOWNLOADING_ASSETS { t.Errorf("state = %v, want DOWNLOADING_ASSETS (downloads not complete)", app.state) } } // TestStateTransitionsToReadyWhenDownloadsComplete verifies that the state // transitions to READY only after downloads are complete. func TestStateTransitionsToReadyWhenDownloadsComplete(t *testing.T) { app := &Application{ state: FIRST_RUN, DB: setupTestDB(t), running: true, } networkIsUp = true config, err := db.GetConfig(app.DB) if err != nil { t.Fatal(err) } config.EmailAddress = "test@example.com" config.LicenseKey = "test-key-123" app.DB.Save(config) // Downloads ARE complete app.downloadsComplete = true downloadDone := make(chan struct{}) app.PageRouter = &page.Router{ Context: page.PageCtx{ DbPtr: app.DB, DownloadDone: downloadDone, }, } app.checkState() if app.state != READY { t.Errorf("state = %v, want READY", app.state) } } // Regression: Download failure should prevent the app from reaching READY. // If downloads fail, the user sees an error and must restart to retry. func TestDownloadFailurePreventsReady(t *testing.T) { app := &Application{ state: FIRST_RUN, DB: setupTestDB(t), running: true, } networkIsUp = true config, err := db.GetConfig(app.DB) if err != nil { t.Fatal(err) } config.EmailAddress = "test@example.com" config.LicenseKey = "test-key-123" app.DB.Save(config) // Downloads completed but failed app.downloadsComplete = false app.downloadFailed = true downloadDone := make(chan struct{}) app.PageRouter = &page.Router{ Context: page.PageCtx{ DbPtr: app.DB, DownloadDone: downloadDone, }, } app.checkState() // Should NOT be READY — download failed if app.state == READY { t.Error("state should not be READY when download has failed") } } // TestStateStaysReadyOnceSet verifies that once the state reaches READY, // checkState() is a no-op (doesn't reset or re-navigate). func TestStateStaysReadyOnceSet(t *testing.T) { app := &Application{ state: READY, DB: setupTestDB(t), running: true, } downloadDone := make(chan struct{}) app.PageRouter = &page.Router{ Context: page.PageCtx{ DbPtr: app.DB, DownloadDone: downloadDone, }, } app.checkState() if app.state != READY { t.Errorf("state = %v, want READY (should not change once set)", app.state) } } // setupTestDB creates a temporary in-memory SQLite database for testing. func setupTestDB(t *testing.T) *gorm.DB { t.Helper() d, err := db.Connect(":memory:") if err != nil { t.Fatal(err) } if err := db.Migrate(d); err != nil { t.Fatal(err) } // Create default config cfg := &db.AppConfig{ Model: gorm.Model{ID: 1}, DataDir: t.TempDir(), AssetsDir: t.TempDir(), SystemLevel: "BASIC", SelectedAudioDevice: "default", InitialFontSize: 128, StandardFontSize: 32, LinePattern: "fibonacci", AudioEnabled: true, SavedVolume: 1.0, EmailAddress: "", LicenseKey: "", } if err := d.Create(cfg).Error; err != nil { t.Fatal(err) } return d }