Gate READY on download completion; move audio to ~/.radiantwave/audio
State machine now uses DOWNLOADING_ASSETS before READY. Downloads closing the DownloadDone channel signals checkState() to proceed. Download failure sets downloadFailed flag which prevents READY. Audio files moved from ~/.local/share/rhemawave/audio to ~/.radiantwave/audio/. Regression tests in application_test.go (linux build-tagged due to network package D-Bus init). Version 0.6.
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
//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
|
||||
}
|
||||
Reference in New Issue
Block a user