package server import ( "encoding/json" "net/http" "os" "testing" "time" "music-server/internal/backend" "music-server/internal/db" "music-server/internal/db/repository" "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" ) // waitForSyncCompletion polls the sync progress endpoint until sync is complete // Returns true if sync completed, false if timeout func waitForSyncCompletion(t *testing.T, e *echo.Echo, maxAttempts int) bool { for i := 0; i < maxAttempts; i++ { progressResp := MakeTestRequest(t, e, "GET", "/sync/progress") assert.Equal(t, http.StatusOK, progressResp.Code) // Try to parse as ProgressResponse first (while syncing) var progress backend.ProgressResponse err := json.Unmarshal(progressResp.Body.Bytes(), &progress) if err == nil && progress.Progress != "" { // Successfully parsed as ProgressResponse with non-empty progress t.Logf("Sync progress: %s%%", progress.Progress) if progress.Progress == "100" { t.Log("Sync completed!") // Wait for Syncing flag to be updated for j := 0; j < 50; j++ { if !backend.Syncing { return true } time.Sleep(50 * time.Millisecond) } return false } } else { // If Progress is empty or parse failed, it might be a SyncResponse (sync already completed) var result backend.SyncResponse err2 := json.Unmarshal(progressResp.Body.Bytes(), &result) if err2 == nil { t.Log("Sync already completed") // Wait for Syncing flag to be updated for j := 0; j < 50; j++ { if !backend.Syncing { return true } time.Sleep(50 * time.Millisecond) } return false } } time.Sleep(1 * time.Second) } return false } // TestSyncPopulatesDatabase verifies that sync populates the database with soundtracks func TestSyncPopulatesDatabase(t *testing.T) { db.TestSetupDB(t) defer db.TestTearDownDB(t) // Debug: Check MUSIC_PATH t.Logf("MUSIC_PATH: %s", os.Getenv("MUSIC_PATH")) e := StartTestServer(t) // Clear any existing data first db.TestClearDatabase(t) // Before sync - should have no soundtracks repo := repository.New(backend.BackendPool()) soundtracksBefore, err := repo.FindAllSoundtracks(backend.BackendCtx()) assert.NoError(t, err) beforeCount := len(soundtracksBefore) t.Logf("Soundtracks before sync: %d", beforeCount) assert.Equal(t, 0, beforeCount, "Database should be empty after clear") // Run sync resp := MakeTestRequest(t, e, "GET", "/sync/full") assert.Equal(t, http.StatusOK, resp.Code) // Wait for sync to complete if !waitForSyncCompletion(t, e, 60) { t.Error("Sync did not complete within timeout") } // After sync - should have soundtracks soundtracksAfter, err := repo.FindAllSoundtracks(backend.BackendCtx()) assert.NoError(t, err) afterCount := len(soundtracksAfter) t.Logf("Soundtracks after sync: %d", afterCount) // Should have more soundtracks than before (unless database was already populated) assert.True(t, afterCount > 0, "Database should have soundtracks after sync") } // TestSyncMakesDifference verifies that sync actually changes the database state func TestSyncMakesDifference(t *testing.T) { db.TestSetupDB(t) defer db.TestTearDownDB(t) e := StartTestServer(t) // Clear any existing data first db.TestClearDatabase(t) // Before sync - should have no soundtracks repo := repository.New(backend.BackendPool()) soundtracksBefore, err := repo.FindAllSoundtracks(backend.BackendCtx()) assert.NoError(t, err) assert.Equal(t, 0, len(soundtracksBefore), "Should have no soundtracks before sync") // Run sync resp := MakeTestRequest(t, e, "GET", "/sync/full") assert.Equal(t, http.StatusOK, resp.Code) // Wait for sync to complete if !waitForSyncCompletion(t, e, 60) { t.Error("Sync did not complete within timeout") } // After sync - should have soundtracks soundtracksAfter, err := repo.FindAllSoundtracks(backend.BackendCtx()) assert.NoError(t, err) assert.True(t, len(soundtracksAfter) > 0, "Should have soundtracks after sync") } // TestSyncProgress verifies the sync progress endpoint func TestSyncProgress(t *testing.T) { db.TestSetupDB(t) defer db.TestTearDownDB(t) e := StartTestServer(t) // Start sync in background go MakeTestRequest(t, e, "GET", "/sync/full") // Poll progress endpoint maxAttempts := 30 foundComplete := false for i := 0; i < maxAttempts; i++ { resp := MakeTestRequest(t, e, "GET", "/sync/progress") assert.Equal(t, http.StatusOK, resp.Code) // Try ProgressResponse first var progress backend.ProgressResponse err := json.Unmarshal(resp.Body.Bytes(), &progress) if err == nil && progress.Progress != "" { // Successfully parsed as ProgressResponse with non-empty progress t.Logf("Sync progress: %s%%", progress.Progress) // Verify we get valid progress values if progress.Progress != "0" { // Sync is making progress } if progress.Progress == "100" { foundComplete = true break } } else { // If Progress is empty or parse failed, it might be a SyncResponse (sync already completed) var result backend.SyncResponse err2 := json.Unmarshal(resp.Body.Bytes(), &result) if err2 == nil { foundComplete = true break } } time.Sleep(1 * time.Second) } // Note: foundNonZero might be false if sync completed too quickly // So we only assert that sync completed assert.True(t, foundComplete, "Should have seen completion") } // TestSyncSoundtracksNewOnlyChanges verifies the incremental sync endpoint func TestSyncSoundtracksNewOnlyChanges(t *testing.T) { db.TestSetupDB(t) defer db.TestTearDownDB(t) e := StartTestServer(t) // Run full sync first MakeTestRequest(t, e, "GET", "/sync/full") // Wait for it to complete if !waitForSyncCompletion(t, e, 60) { t.Error("Initial sync did not complete within timeout") return } // Get initial count repo := repository.New(backend.BackendPool()) soundtracksBefore, _ := repo.FindAllSoundtracks(backend.BackendCtx()) beforeCount := len(soundtracksBefore) // Run incremental sync (should not change count if nothing changed) resp := MakeTestRequest(t, e, "GET", "/sync/new") assert.Equal(t, http.StatusOK, resp.Code) // Wait a bit time.Sleep(2 * time.Second) // Count should be the same soundtracksAfter, _ := repo.FindAllSoundtracks(backend.BackendCtx()) afterCount := len(soundtracksAfter) // Note: This might not be exactly equal due to timing, but should be close t.Logf("Soundtracks before incremental sync: %d, after: %d", beforeCount, afterCount) } // TestResetSoundtracks verifies the reset endpoint clears the database // RUN THIS LAST func TestResetSoundtracks(t *testing.T) { db.TestSetupDB(t) defer db.TestTearDownDB(t) e := StartTestServer(t) // First ensure we have data repo := repository.New(backend.BackendPool()) soundtracksBefore, _ := repo.FindAllSoundtracks(backend.BackendCtx()) beforeCount := len(soundtracksBefore) if beforeCount == 0 { // Run sync to populate MakeTestRequest(t, e, "GET", "/sync/full") if !waitForSyncCompletion(t, e, 60) { t.Error("Sync did not complete within timeout") return } soundtracksBefore, _ = repo.FindAllSoundtracks(backend.BackendCtx()) beforeCount = len(soundtracksBefore) } t.Logf("Soundtracks before reset: %d", beforeCount) assert.True(t, beforeCount > 0, "Should have soundtracks to reset") // Call reset resp := MakeTestRequest(t, e, "GET", "/sync/reset") assert.Equal(t, http.StatusOK, resp.Code) // Verify database is cleared // Note: reset might take a moment to propagate time.Sleep(1 * time.Second) soundtracksAfter, _ := repo.FindAllSoundtracks(backend.BackendCtx()) afterCount := len(soundtracksAfter) t.Logf("Soundtracks after reset: %d", afterCount) assert.Equal(t, 0, afterCount, "Database should be empty after reset") } // TestSyncSoundtracksNewFull verifies the full sync endpoint // RUN THIS LAST (before TestResetSoundtracks) func TestSyncSoundtracksNewFull(t *testing.T) { db.TestSetupDB(t) defer db.TestTearDownDB(t) e := StartTestServer(t) // Clear database first db.TestClearDatabase(t) // Run full sync resp := MakeTestRequest(t, e, "GET", "/sync/full") assert.Equal(t, http.StatusOK, resp.Code) // Wait for sync to complete if !waitForSyncCompletion(t, e, 60) { t.Error("Full sync did not complete within timeout") } // Verify database is populated repo := repository.New(backend.BackendPool()) soundtracks, err := repo.FindAllSoundtracks(backend.BackendCtx()) assert.NoError(t, err) assert.True(t, len(soundtracks) > 0, "Database should be populated after full sync") t.Logf("Full sync populated %d soundtracks", len(soundtracks)) }