Files
minstrel/internal/api/admin_status_test.go
T
bvandeusenandClaude Opus 5.5 c1305a547d
release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m35s
release / go (push) Successful in 2m0s
release / integration (push) Successful in 5m40s
release / android (push) Successful in 6m15s
release / Build signed APK (releases and dev) (push) Successful in 6m30s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
feat: the admin Overview shows who is listening, downloads, the library and its disk, and the server's version, uptime and recent errors; a download waiting on you, a client not answering or a full disk count in its verdict (M504 #5627 #5628 #5629 #5630)
The version moves to internal/buildinfo, so /rest/ping reports the build
instead of "dev".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:51:41 -04:00

90 lines
3.0 KiB
Go

package api
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"git.fabledsword.com/bvandeusen/minstrel/internal/buildinfo"
"git.fabledsword.com/bvandeusen/minstrel/internal/logging"
)
// openPlay records a play by user of track that started ago and has not
// ended.
func openPlay(t *testing.T, h *handlers, user, track pgtype.UUID, ago string) {
t.Helper()
ctx := context.Background()
var session pgtype.UUID
if err := h.pool.QueryRow(ctx,
`INSERT INTO play_sessions (user_id, started_at, last_event_at) VALUES ($1, now(), now()) RETURNING id`,
user).Scan(&session); err != nil {
t.Fatalf("session: %v", err)
}
if _, err := h.pool.Exec(ctx,
`INSERT INTO play_events (user_id, track_id, session_id, started_at, client_id)
VALUES ($1, $2, $3, now() - $4::interval, 'web')`, user, track, session, ago); err != nil {
t.Fatalf("play: %v", err)
}
}
// The Overview's status (M504): a play still inside its track's length is
// listening now; one long past it, never closed by its client, is not.
func TestAdminStatus_ListeningAndServer(t *testing.T) {
h, pool := testHandlers(t)
truncateLibrary(t, pool)
admin := seedUser(t, pool, "admin", "pw", true)
fresh, _ := seedTrackOnDisk(t, h, "statusfresh") // 30 s long
stale, _ := seedTrackOnDisk(t, h, "statusstale")
openPlay(t, h, admin.ID, fresh.ID, "10 seconds")
openPlay(t, h, admin.ID, stale.ID, "1 hour")
req := withUser(httptest.NewRequest(http.MethodGet, "/api/admin/status", nil), admin)
w := httptest.NewRecorder()
h.handleGetAdminStatus(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
var got adminStatusView
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
t.Fatalf("decode: %v", err)
}
var sawFresh, sawStale bool
for _, p := range got.Listening.Now {
switch p.TrackID {
case uuidToString(fresh.ID):
sawFresh = true
if p.Username != admin.Username || p.Client != "web" || p.Title != "AdmQ Track statusfresh" {
t.Errorf("listening row = %+v", p)
}
case uuidToString(stale.ID):
sawStale = true
}
}
if !sawFresh || sawStale {
t.Errorf("listening now: fresh %v (want true), stale %v (want false)", sawFresh, sawStale)
}
if got.Listening.PlaysDay < 2 || got.Listening.LastPlayAt == nil {
t.Errorf("last day: plays %d, last %v", got.Listening.PlaysDay, got.Listening.LastPlayAt)
}
if got.Library.Tracks != 2 || got.Library.Albums != 2 || got.Library.Bytes != 10 {
t.Errorf("library = %+v, want 2 tracks, 2 albums, 10 bytes", got.Library)
}
if got.Downloads.Live == nil || got.Downloads.ClientsDown == nil || got.Library.Disks == nil {
t.Error("lists must be [], never null")
}
if got.Server.Version != buildinfo.Version || got.Server.StartedAt == "" || got.Server.Migration < 92 {
t.Errorf("server = %+v", got.Server)
}
if _, total := logging.Errors.Snapshot(); got.Server.ErrorCount != total {
t.Errorf("error count = %d, ring says %d", got.Server.ErrorCount, total)
}
}