Store each client's play queue in the database
The queue lived in memory and was shared by all clients, so a restart or a finished sync cleared it. Every client now has its own queue in the new queue and queue_song tables (added to the undeployed migration 000006). The music endpoints return the queue ID in the X-Queue-Id header and expect it back. A request without an ID, or with an ended one, starts a new queue. /music/reset ends the queue and returns a new ID, and a sync ends all running queues. Ended queues are kept as history. The deprecated legacy routes without the /api/v1 prefix are removed.
This commit is contained in:
1 parent
b5dc42b654
commit
a705d7b476
28 files changed
+1103
-320
No files matched your search
@@ -0,0 +1,89 @@
|
||||
//go:build integration
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"music-server/internal/db/repository"
|
||||
"music-server/internal/testutil/pgtest"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestQueueWithPostgres checks the queue queries against a real database.
|
||||
func TestQueueWithPostgres(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
database := pgtest.NewMigratedDatabase(t)
|
||||
q := repository.New(database.Pool)
|
||||
s := NewSyncService(q, copyTestMusic(t), nil)
|
||||
require.True(t, s.Start(false))
|
||||
s.Wait()
|
||||
|
||||
m := NewMusicService(q, t.TempDir())
|
||||
queueID, err := m.ResolveQueue(ctx, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, m.AddLatestToQueue(ctx, queueID), "nothing fetched yet")
|
||||
var paths []string
|
||||
for range 3 {
|
||||
path, err := m.RandomSong(ctx, queueID)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, m.AddLatestToQueue(ctx, queueID))
|
||||
paths = append(paths, path)
|
||||
}
|
||||
require.NoError(t, m.AddLatestToQueue(ctx, queueID), "a fetched song is only added once")
|
||||
|
||||
played, err := m.PlayedSongs(ctx, queueID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, played, 3)
|
||||
for i, song := range played {
|
||||
assert.Equal(t, i, song.SongNo)
|
||||
assert.Equal(t, i == 2, song.CurrentlyPlaying, "the last added song is current")
|
||||
assert.NotEmpty(t, song.Soundtrack)
|
||||
assert.NotEqual(t, uuid.Nil, song.SoundtrackUuid)
|
||||
}
|
||||
|
||||
path, err := m.PreviousSong(ctx, queueID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, paths[1], path)
|
||||
require.NoError(t, m.AddLatestPlayed(ctx, queueID))
|
||||
info, err := m.SongInfo(ctx, queueID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, info.SongNo)
|
||||
assert.Equal(t, int32(1), info.SongPlayed)
|
||||
|
||||
// A new service, as after a restart, sees the same queue.
|
||||
restarted := NewMusicService(q, t.TempDir())
|
||||
same, err := restarted.ResolveQueue(ctx, queueID.String())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, queueID, same)
|
||||
|
||||
newID, err := m.NewQueue(ctx, queueID)
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, queueID, newID)
|
||||
resolved, err := m.ResolveQueue(ctx, queueID.String())
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, queueID, resolved, "an ended queue is not used again")
|
||||
|
||||
other, err := m.ResolveQueue(ctx, "")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, m.EndAllQueues(ctx))
|
||||
var active int
|
||||
require.NoError(t, database.Pool.QueryRow(ctx, "SELECT COUNT(*) FROM queue WHERE ended_at IS NULL").Scan(&active))
|
||||
assert.Zero(t, active)
|
||||
_, err = m.ResolveQueue(ctx, other.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Ended queues keep their songs, also after the songs are removed from the library.
|
||||
require.NoError(t, q.ClearSongs(ctx))
|
||||
played, err = m.PlayedSongs(ctx, queueID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, played, 3)
|
||||
assert.NotEmpty(t, played[0].Song)
|
||||
_, err = m.Song(ctx, queueID, 0)
|
||||
assert.ErrorIs(t, err, ErrNotFound)
|
||||
}
|
||||
Reference in new issue
Block a user