24a9111333
- Add Database struct in internal/db/database.go with Pool, Ctx, and RunMigrations() - Update server.go to use Database struct with NewServerInstance() - Add backend.go with InitBackend(), BackendRepo(), BackendCtx(), BackendPool() - Update music.go and sync.go to use BackendRepo() and BackendCtx() instead of db.Dbpool/db.Ctx - Update token_handler.go to accept pool parameter - Update routes.go to use s.db.Pool for middleware - Update cmd/main.go to use NewServerInstance() and HTTPServer() - Update test_helpers.go to initialize backend with test database - Update test files to use backend.BackendPool() and backend.BackendCtx() Benefits: - Easier to mock database for unit tests - Follows Go best practices (dependency injection) - Better architecture with explicit dependencies - RunMigrations() replaces old Migrate_db() function Note: Global db.Dbpool and db.Ctx still exist in dbHelper.go for backward compatibility with test_helpers.go, but production code no longer uses them. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
290 lines
8.2 KiB
Go
290 lines
8.2 KiB
Go
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(backend.BackendPool())
|
|
gamesBefore, err := repo.FindAllGames(backend.BackendCtx())
|
|
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(backend.BackendCtx())
|
|
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(backend.BackendPool())
|
|
gamesBefore, err := repo.FindAllGames(backend.BackendCtx())
|
|
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(backend.BackendCtx())
|
|
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(backend.BackendPool())
|
|
gamesBefore, _ := repo.FindAllGames(backend.BackendCtx())
|
|
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(backend.BackendCtx())
|
|
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(backend.BackendPool())
|
|
gamesBefore, _ := repo.FindAllGames(backend.BackendCtx())
|
|
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(backend.BackendCtx())
|
|
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(backend.BackendCtx())
|
|
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(backend.BackendPool())
|
|
games, err := repo.FindAllGames(backend.BackendCtx())
|
|
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))
|
|
}
|