Files
MusicServer/internal/backend/metrics_test.go
T
SansanandClaude Opus 5.5 f79f51ea4c
CI / integration (push) Failing after 14s
CI / docker (push) Failing after 20s
CI / test (push) Successful in 1m10s
CI / publish (push) Skipped
Expose Prometheus metrics on a separate port
Metrics are served on METRICS_PORT (default 2112, "off" to disable), never on
the public port. Besides the Go runtime and process metrics this adds:

- http_requests_total and http_request_duration_seconds per route pattern,
  from an Echo middleware.
- musicserver_songs_requested_total and musicserver_songs_played_total by
  pick mode (random, low, classic). The mode is now stored with each queued
  song (migration 000006, not released yet).
- Library size, sync duration, last successful sync, broken songs from the
  last full sync, and the database connection pool.

Refs #35, #36, #37

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:57:58 +02:00

109 lines
3.9 KiB
Go

package backend
import (
"context"
"path/filepath"
"testing"
"time"
"music-server/internal/db/repository"
"music-server/internal/metrics"
"music-server/internal/testutil/fakedb"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func requested(mode PickMode) float64 {
return testutil.ToFloat64(metrics.SongsRequested.WithLabelValues(string(mode)))
}
func played(mode PickMode) float64 {
return testutil.ToFloat64(metrics.SongsPlayed.WithLabelValues(string(mode)))
}
func syncCount(syncType string) uint64 {
m := &dto.Metric{}
_ = metrics.SyncDuration.WithLabelValues(syncType).(prometheus.Metric).Write(m)
return m.GetHistogram().GetSampleCount()
}
func TestSongMetricsByPickMode(t *testing.T) {
store := fakedb.New()
addSoundtrackWithSongs(t, store, t.TempDir(), "Game A", 0, "Song 1")
m := NewMusicService(store, t.TempDir())
ctx := context.Background()
q := newQueue(t, m)
before := map[PickMode][2]float64{}
for _, mode := range []PickMode{PickRandom, PickLow, PickClassic} {
before[mode] = [2]float64{requested(mode), played(mode)}
}
pick := map[PickMode]func(context.Context, uuid.UUID) (string, error){
PickRandom: func(ctx context.Context, id uuid.UUID) (string, error) { return m.RandomSong(ctx, id) },
PickLow: func(ctx context.Context, id uuid.UUID) (string, error) { return m.RandomSongLowChance(ctx, id) },
PickClassic: func(ctx context.Context, id uuid.UUID) (string, error) { return m.RandomSongClassic(ctx, id) },
}
for songNo, mode := range []PickMode{PickRandom, PickLow, PickClassic} {
_, err := pick[mode](ctx, q)
require.NoError(t, err)
require.NoError(t, m.AddLatestToQueue(ctx, q))
require.NoError(t, m.SetPlayed(ctx, q, songNo))
}
// Fetched but never added: requested, not played.
_, err := m.RandomSong(ctx, q)
require.NoError(t, err)
require.NoError(t, m.AddLatestPlayed(ctx, q), "the current song is the classic one")
assert.Equal(t, before[PickRandom][0]+2, requested(PickRandom))
assert.Equal(t, before[PickLow][0]+1, requested(PickLow))
assert.Equal(t, before[PickClassic][0]+1, requested(PickClassic))
assert.Equal(t, before[PickRandom][1]+1, played(PickRandom))
assert.Equal(t, before[PickLow][1]+1, played(PickLow))
assert.Equal(t, before[PickClassic][1]+2, played(PickClassic))
}
func TestSongsPlayedIgnoresSongsOutsideTheQueue(t *testing.T) {
m := NewMusicService(fakedb.New(), t.TempDir())
before := played(PickRandom)
require.NoError(t, m.SetPlayed(context.Background(), newQueue(t, m), 3))
assert.Equal(t, before, played(PickRandom))
}
func TestSyncMetrics(t *testing.T) {
f := newSyncFixture(t)
f.write("Game A/01.mp3", "a1")
f.write("Game A/02.mp3", "a2")
f.write("Game B/01.mp3", "b1")
changesBefore, fullBefore := syncCount("changes"), syncCount("full")
metrics.SyncLastSuccess.Set(0)
f.run(false)
assert.Equal(t, changesBefore+1, syncCount("changes"))
assert.Equal(t, 2.0, testutil.ToFloat64(metrics.LibrarySoundtracks))
assert.Equal(t, 3.0, testutil.ToFloat64(metrics.LibrarySongs))
assert.InDelta(t, float64(time.Now().Unix()), testutil.ToFloat64(metrics.SyncLastSuccess), 5)
f.store.AddTestSong(repository.Song{SoundtrackID: 999, SongName: "gone", Path: "/does/not/exist.mp3"})
f.run(true)
assert.Equal(t, fullBefore+1, syncCount("full"))
assert.Equal(t, 1.0, testutil.ToFloat64(metrics.BrokenSongs))
assert.Equal(t, 3.0, testutil.ToFloat64(metrics.LibrarySongs), "the broken song is removed")
}
func TestSyncWithErrorsIsNotALastSuccess(t *testing.T) {
metrics.SyncLastSuccess.Set(0)
s := NewSyncService(fakedb.New(), filepath.Join(t.TempDir(), "missing"), nil)
require.True(t, s.Start(false))
s.Wait()
require.NotEmpty(t, s.Result().CatchedErrors)
assert.Zero(t, testutil.ToFloat64(metrics.SyncLastSuccess))
}