Fixed some small bugs after merge
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"music-server/internal/logging"
|
||||
|
||||
@@ -59,6 +60,26 @@ func (db *Database) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
// Health checks the health of the database connection by pinging the database.
|
||||
// It returns a map with keys indicating various health statistics.
|
||||
func (db *Database) Health() map[string]string {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
stats := make(map[string]string)
|
||||
|
||||
// Ping the database
|
||||
err := db.Pool.Ping(ctx)
|
||||
if err != nil {
|
||||
stats["status"] = "down"
|
||||
stats["error"] = err.Error()
|
||||
return stats
|
||||
}
|
||||
|
||||
stats["status"] = "up"
|
||||
return stats
|
||||
}
|
||||
|
||||
// RunMigrations runs all pending database migrations to the latest version.
|
||||
// Uses the existing pool to extract connection details.
|
||||
func (db *Database) RunMigrations() error {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -80,9 +79,9 @@ func TestMigrationsStepByStep(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range songs {
|
||||
_, err := db.Exec(`INSERT INTO song (game_id, song_name, path)
|
||||
VALUES ($1, $2, $3)`,
|
||||
s.gameID, s.name, s.path)
|
||||
_, err := db.Exec(`INSERT INTO song (game_id, song_name, path, hash)
|
||||
VALUES ($1, $2, $3, $4)`,
|
||||
s.gameID, s.name, s.path, fmt.Sprintf("song-hash-%s", s.name))
|
||||
require.NoError(t, err, "Failed to insert song %s", s.name)
|
||||
}
|
||||
|
||||
@@ -95,9 +94,9 @@ func TestMigrationsStepByStep(t *testing.T) {
|
||||
var songCount int
|
||||
err = db.QueryRow("SELECT COUNT(*) FROM song").Scan(&songCount)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 8, songCount, "Expected 8 songs")
|
||||
require.Equal(t, 9, songCount, "Expected 9 songs")
|
||||
|
||||
t.Log("✓ Manually inserted 5 games with 8 songs")
|
||||
t.Log("✓ Manually inserted 5 games with 9 songs")
|
||||
})
|
||||
|
||||
// Step 3: Apply migration 5 (rename game→soundtrack)
|
||||
@@ -126,7 +125,7 @@ func TestMigrationsStepByStep(t *testing.T) {
|
||||
var songCount int
|
||||
err = db.QueryRow("SELECT COUNT(*) FROM song").Scan(&songCount)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 8, songCount, "Expected 8 songs after migration")
|
||||
require.Equal(t, 9, songCount, "Expected 9 songs after migration")
|
||||
|
||||
// Verify data integrity: soundtrack_name values
|
||||
rows, err := db.Query("SELECT soundtrack_name FROM soundtrack ORDER BY id")
|
||||
@@ -215,13 +214,18 @@ func applyMigrations(t *testing.T, host, port, user, password, dbname string, st
|
||||
require.NoError(t, err)
|
||||
|
||||
m, err := migrate.NewWithDatabaseInstance(
|
||||
"file://internal/db/migrations",
|
||||
"file://migrations",
|
||||
"postgres", driver)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get current version
|
||||
version, _, err := m.Version()
|
||||
require.NoError(t, err)
|
||||
if err != nil && err != migrate.ErrNilVersion {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if err == migrate.ErrNilVersion {
|
||||
version = 0
|
||||
}
|
||||
t.Logf("Current migration version: %d", version)
|
||||
|
||||
// Apply exactly 'steps' migrations
|
||||
@@ -237,6 +241,11 @@ func applyMigrations(t *testing.T, host, port, user, password, dbname string, st
|
||||
|
||||
// Get new version
|
||||
newVersion, _, err := m.Version()
|
||||
require.NoError(t, err)
|
||||
if err != nil && err != migrate.ErrNilVersion {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if err == migrate.ErrNilVersion {
|
||||
newVersion = 0
|
||||
}
|
||||
t.Logf("Migration version after applying %d steps: %d", steps, newVersion)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ ALTER TABLE song RENAME COLUMN game_id TO soundtrack_id;
|
||||
-- Update song primary key
|
||||
ALTER TABLE song DROP CONSTRAINT IF EXISTS song_pkey;
|
||||
ALTER TABLE song ADD PRIMARY KEY (soundtrack_id, path);
|
||||
ALTER TABLE song RENAME CONSTRAINT song_pkey TO song_pkey_soundtrack;
|
||||
|
||||
-- Update song_list table references
|
||||
ALTER TABLE song_list RENAME COLUMN game_name TO soundtrack_name;
|
||||
|
||||
@@ -138,8 +138,8 @@ LIMIT $1;
|
||||
-- name: GetStatisticsSummary :one
|
||||
SELECT
|
||||
COUNT(*) as total_soundtracks,
|
||||
SUM(CASE WHEN times_played > 0 THEN 1 ELSE 0 END) as played_soundtracks,
|
||||
SUM(CASE WHEN times_played = 0 THEN 1 ELSE 0 END) as never_played_soundtracks,
|
||||
COALESCE(SUM(CASE WHEN times_played > 0 THEN 1 ELSE 0 END), 0)::bigint as played_soundtracks,
|
||||
COALESCE(SUM(CASE WHEN times_played = 0 THEN 1 ELSE 0 END), 0)::bigint as never_played_soundtracks,
|
||||
COALESCE(SUM(times_played), 0)::bigint as total_soundtrack_plays,
|
||||
COALESCE(AVG(times_played), 0)::float as avg_soundtrack_plays,
|
||||
COALESCE(MAX(times_played), 0)::bigint as max_soundtrack_plays,
|
||||
|
||||
@@ -10,23 +10,6 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type IDMigrationStatus struct {
|
||||
TableName string `json:"table_name"`
|
||||
TotalRows int32 `json:"total_rows"`
|
||||
MigratedRows int32 `json:"migrated_rows"`
|
||||
Completed bool `json:"completed"`
|
||||
StartedAt *time.Time `json:"started_at"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
Token string `json:"token"`
|
||||
IpAddress string `json:"ip_address"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
ClientType *string `json:"client_type"`
|
||||
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
Token string `json:"token"`
|
||||
IpAddress string `json:"ip_address"`
|
||||
@@ -44,7 +27,6 @@ type Song struct {
|
||||
Hash string `json:"hash"`
|
||||
FileName *string `json:"file_name"`
|
||||
ID pgtype.Int4 `json:"id"`
|
||||
Uuid pgtype.UUID `json:"uuid"`
|
||||
}
|
||||
|
||||
type SongList struct {
|
||||
@@ -56,17 +38,16 @@ type SongList struct {
|
||||
}
|
||||
|
||||
type Soundtrack struct {
|
||||
ID int32 `json:"id"`
|
||||
SoundtrackName string `json:"soundtrack_name"`
|
||||
Added time.Time `json:"added"`
|
||||
Deleted *time.Time `json:"deleted"`
|
||||
LastChanged *time.Time `json:"last_changed"`
|
||||
Path string `json:"path"`
|
||||
TimesPlayed int32 `json:"times_played"`
|
||||
LastPlayed *time.Time `json:"last_played"`
|
||||
NumberOfSongs int32 `json:"number_of_songs"`
|
||||
Hash string `json:"hash"`
|
||||
Uuid pgtype.UUID `json:"uuid"`
|
||||
ID int32 `json:"id"`
|
||||
SoundtrackName string `json:"soundtrack_name"`
|
||||
Added time.Time `json:"added"`
|
||||
Deleted *time.Time `json:"deleted"`
|
||||
LastChanged *time.Time `json:"last_changed"`
|
||||
Path string `json:"path"`
|
||||
TimesPlayed int32 `json:"times_played"`
|
||||
LastPlayed *time.Time `json:"last_played"`
|
||||
NumberOfSongs int32 `json:"number_of_songs"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
type Vgmq struct {
|
||||
|
||||
@@ -110,7 +110,7 @@ func (q *Queries) ClearSongsBySoundtrackId(ctx context.Context, soundtrackID int
|
||||
}
|
||||
|
||||
const fetchAllSongs = `-- name: FetchAllSongs :many
|
||||
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id, uuid FROM song
|
||||
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id FROM song
|
||||
`
|
||||
|
||||
func (q *Queries) FetchAllSongs(ctx context.Context) ([]Song, error) {
|
||||
@@ -130,7 +130,6 @@ func (q *Queries) FetchAllSongs(ctx context.Context) ([]Song, error) {
|
||||
&i.Hash,
|
||||
&i.FileName,
|
||||
&i.ID,
|
||||
&i.Uuid,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -143,7 +142,7 @@ func (q *Queries) FetchAllSongs(ctx context.Context) ([]Song, error) {
|
||||
}
|
||||
|
||||
const findSongsFromSoundtrack = `-- name: FindSongsFromSoundtrack :many
|
||||
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id, uuid
|
||||
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id
|
||||
FROM song
|
||||
WHERE soundtrack_id = $1
|
||||
`
|
||||
@@ -165,7 +164,6 @@ func (q *Queries) FindSongsFromSoundtrack(ctx context.Context, soundtrackID int3
|
||||
&i.Hash,
|
||||
&i.FileName,
|
||||
&i.ID,
|
||||
&i.Uuid,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -178,7 +176,7 @@ func (q *Queries) FindSongsFromSoundtrack(ctx context.Context, soundtrackID int3
|
||||
}
|
||||
|
||||
const getSongById = `-- name: GetSongById :one
|
||||
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id, uuid FROM song WHERE id = $1
|
||||
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id FROM song WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetSongById(ctx context.Context, id pgtype.Int4) (Song, error) {
|
||||
@@ -192,13 +190,12 @@ func (q *Queries) GetSongById(ctx context.Context, id pgtype.Int4) (Song, error)
|
||||
&i.Hash,
|
||||
&i.FileName,
|
||||
&i.ID,
|
||||
&i.Uuid,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getSongWithHash = `-- name: GetSongWithHash :one
|
||||
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id, uuid FROM song WHERE hash = $1
|
||||
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id FROM song WHERE hash = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetSongWithHash(ctx context.Context, hash string) (Song, error) {
|
||||
@@ -212,7 +209,6 @@ func (q *Queries) GetSongWithHash(ctx context.Context, hash string) (Song, error
|
||||
&i.Hash,
|
||||
&i.FileName,
|
||||
&i.ID,
|
||||
&i.Uuid,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func (q *Queries) ClearSoundtracks(ctx context.Context) error {
|
||||
}
|
||||
|
||||
const findAllSoundtracks = `-- name: FindAllSoundtracks :many
|
||||
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash, uuid
|
||||
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash
|
||||
FROM soundtrack
|
||||
WHERE deleted IS NULL
|
||||
ORDER BY soundtrack_name
|
||||
@@ -54,7 +54,6 @@ func (q *Queries) FindAllSoundtracks(ctx context.Context) ([]Soundtrack, error)
|
||||
&i.LastPlayed,
|
||||
&i.NumberOfSongs,
|
||||
&i.Hash,
|
||||
&i.Uuid,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -67,7 +66,7 @@ func (q *Queries) FindAllSoundtracks(ctx context.Context) ([]Soundtrack, error)
|
||||
}
|
||||
|
||||
const getAllSoundtracksIncludingDeleted = `-- name: GetAllSoundtracksIncludingDeleted :many
|
||||
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash, uuid
|
||||
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash
|
||||
FROM soundtrack
|
||||
ORDER BY soundtrack_name
|
||||
`
|
||||
@@ -92,7 +91,6 @@ func (q *Queries) GetAllSoundtracksIncludingDeleted(ctx context.Context) ([]Soun
|
||||
&i.LastPlayed,
|
||||
&i.NumberOfSongs,
|
||||
&i.Hash,
|
||||
&i.Uuid,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -116,7 +114,7 @@ func (q *Queries) GetIdBySoundtrackName(ctx context.Context, soundtrackName stri
|
||||
}
|
||||
|
||||
const getSoundtrackById = `-- name: GetSoundtrackById :one
|
||||
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash, uuid
|
||||
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash
|
||||
FROM soundtrack
|
||||
WHERE id = $1
|
||||
AND deleted IS NULL
|
||||
@@ -136,7 +134,6 @@ func (q *Queries) GetSoundtrackById(ctx context.Context, id int32) (Soundtrack,
|
||||
&i.LastPlayed,
|
||||
&i.NumberOfSongs,
|
||||
&i.Hash,
|
||||
&i.Uuid,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -398,8 +398,8 @@ func (q *Queries) GetOldestPlayedGames(ctx context.Context, limit int32) ([]GetO
|
||||
const getStatisticsSummary = `-- name: GetStatisticsSummary :one
|
||||
SELECT
|
||||
COUNT(*) as total_soundtracks,
|
||||
SUM(CASE WHEN times_played > 0 THEN 1 ELSE 0 END) as played_soundtracks,
|
||||
SUM(CASE WHEN times_played = 0 THEN 1 ELSE 0 END) as never_played_soundtracks,
|
||||
COALESCE(SUM(CASE WHEN times_played > 0 THEN 1 ELSE 0 END), 0)::bigint as played_soundtracks,
|
||||
COALESCE(SUM(CASE WHEN times_played = 0 THEN 1 ELSE 0 END), 0)::bigint as never_played_soundtracks,
|
||||
COALESCE(SUM(times_played), 0)::bigint as total_soundtrack_plays,
|
||||
COALESCE(AVG(times_played), 0)::float as avg_soundtrack_plays,
|
||||
COALESCE(MAX(times_played), 0)::bigint as max_soundtrack_plays,
|
||||
|
||||
@@ -54,8 +54,19 @@ func TestSetupDB(t *testing.T) {
|
||||
t.Fatalf("Failed to initialize test database: %v", err)
|
||||
}
|
||||
|
||||
// Clean up any existing schema to ensure clean state
|
||||
ctx := context.Background()
|
||||
_, err = TestDatabase.Pool.Exec(ctx, "DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public;")
|
||||
if err != nil {
|
||||
t.Logf("Warning: Could not clean schema: %v", err)
|
||||
// Continue anyway, migrations might still work
|
||||
}
|
||||
|
||||
// Run migrations
|
||||
if err := TestDatabase.RunMigrations(); err != nil {
|
||||
// Clean up on failure to prevent nil pointer issues in other tests
|
||||
TestDatabase.Close()
|
||||
TestDatabase = nil
|
||||
t.Fatalf("Failed to run migrations: %v", err)
|
||||
}
|
||||
})
|
||||
@@ -97,10 +108,11 @@ func createTestDatabase(host, port, dbname, user, password string) {
|
||||
// "closed pool" errors when tests run sequentially
|
||||
func TestTearDownDB(t *testing.T) {
|
||||
// CloseDb() // Disabled to prevent pool closure between sequential tests
|
||||
if TestDatabase != nil {
|
||||
TestDatabase.Close()
|
||||
TestDatabase = nil
|
||||
}
|
||||
// Note: We also don't nil TestDatabase to allow reuse across tests
|
||||
// if TestDatabase != nil {
|
||||
// TestDatabase.Close()
|
||||
// TestDatabase = nil
|
||||
// }
|
||||
}
|
||||
|
||||
// TestClearDatabase clears all data from the test database
|
||||
@@ -112,10 +124,13 @@ func TestClearDatabase(t *testing.T) {
|
||||
|
||||
// Clear all tables in reverse order to respect foreign keys
|
||||
// Note: This assumes the tables exist and have the expected structure
|
||||
// After migration 000005, game table was renamed to soundtrack
|
||||
tables := []string{
|
||||
"song_list",
|
||||
"song",
|
||||
"game",
|
||||
"soundtrack",
|
||||
"vgmq",
|
||||
"sessions",
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -126,9 +141,10 @@ func TestClearDatabase(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset sequences
|
||||
_, err := TestDatabase.Pool.Exec(ctx, "SELECT setval('game_id_seq', 1, false)")
|
||||
if err != nil {
|
||||
t.Logf("Failed to reset game_id_seq: %v", err)
|
||||
// Reset sequences (renamed from game_id_seq to soundtrack_id_seq in migration 000005)
|
||||
var seqErr error
|
||||
_, seqErr = TestDatabase.Pool.Exec(ctx, "SELECT setval('soundtrack_id_seq', 1, false)")
|
||||
if seqErr != nil {
|
||||
t.Logf("Failed to reset soundtrack_id_seq: %v", seqErr)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user