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)
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
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
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>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user