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 games 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 games repo := repository.New(db.Dbpool) gamesBefore, err := repo.FindAllGames(db.Ctx) assert.NoError(t, err) beforeCount := len(gamesBefore) t.Logf("Games 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 games gamesAfter, err := repo.FindAllGames(db.Ctx) assert.NoError(t, err) afterCount := len(gamesAfter) t.Logf("Games after sync: %d", afterCount) // Should have more games than before (unless database was already populated) assert.True(t, afterCount > 0, "Database should have games 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 games repo := repository.New(db.Dbpool) gamesBefore, err := repo.FindAllGames(db.Ctx) assert.NoError(t, err) assert.Equal(t, 0, len(gamesBefore), "Should have no games 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 games gamesAfter, err := repo.FindAllGames(db.Ctx) assert.NoError(t, err) assert.True(t, len(gamesAfter) > 0, "Should have games 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") } // TestSyncGamesNewOnlyChanges verifies the incremental sync endpoint func TestSyncGamesNewOnlyChanges(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(db.Dbpool) gamesBefore, _ := repo.FindAllGames(db.Ctx) beforeCount := len(gamesBefore) // 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 gamesAfter, _ := repo.FindAllGames(db.Ctx) afterCount := len(gamesAfter) // Note: This might not be exactly equal due to timing, but should be close t.Logf("Games before incremental sync: %d, after: %d", beforeCount, afterCount) } // TestResetGames verifies the reset endpoint clears the database // RUN THIS LAST func TestResetGames(t *testing.T) { db.TestSetupDB(t) defer db.TestTearDownDB(t) e := StartTestServer(t) // First ensure we have data repo := repository.New(db.Dbpool) gamesBefore, _ := repo.FindAllGames(db.Ctx) beforeCount := len(gamesBefore) 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 } gamesBefore, _ = repo.FindAllGames(db.Ctx) beforeCount = len(gamesBefore) } t.Logf("Games before reset: %d", beforeCount) assert.True(t, beforeCount > 0, "Should have games 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) gamesAfter, _ := repo.FindAllGames(db.Ctx) afterCount := len(gamesAfter) t.Logf("Games after reset: %d", afterCount) assert.Equal(t, 0, afterCount, "Database should be empty after reset") } // TestSyncGamesNewFull verifies the full sync endpoint // RUN THIS LAST (before TestResetGames) func TestSyncGamesNewFull(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(db.Dbpool) games, err := repo.FindAllGames(db.Ctx) assert.NoError(t, err) assert.True(t, len(games) > 0, "Database should be populated after full sync") t.Logf("Full sync populated %d games", len(games)) }