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) } }