104 lines
3.3 KiB
Go
104 lines
3.3 KiB
Go
package main
|
|
|
|
// api_stats_test.go — REST shape of /api/sessions/:id/stats and /api/stats.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"testing"
|
|
)
|
|
|
|
func TestAPISessionStatsShape(t *testing.T) {
|
|
ts, store := newTestServer(t)
|
|
appendAgentEnd(t, store, "s1", 1, `{"usage":{"inputTokens":1200,"outputTokens":340,"totalCost":0.25}}`)
|
|
appendAgentEnd(t, store, "s1", 2, `{}`)
|
|
|
|
code, body := apiReq(t, http.MethodGet, ts.URL+"/api/sessions/s1/stats", testToken, "")
|
|
if code != http.StatusOK {
|
|
t.Fatalf("stats status = %d", code)
|
|
}
|
|
var stats map[string]any
|
|
if err := json.Unmarshal([]byte(body), &stats); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if stats["turns"].(float64) != 2 ||
|
|
stats["inputTokens"].(float64) != 1200 ||
|
|
stats["outputTokens"].(float64) != 340 ||
|
|
stats["totalCost"].(float64) != 0.25 {
|
|
t.Fatalf("stats = %v", stats)
|
|
}
|
|
if _, ok := stats["sessionsCount"]; ok {
|
|
t.Fatalf("per-session stats must not carry sessionsCount: %v", stats)
|
|
}
|
|
|
|
// unknown session: 200 with zeros (matches events endpoint behavior)
|
|
code, body = apiReq(t, http.MethodGet, ts.URL+"/api/sessions/ghost/stats", testToken, "")
|
|
if code != http.StatusOK {
|
|
t.Fatalf("ghost status = %d", code)
|
|
}
|
|
var ghost map[string]any
|
|
if err := json.Unmarshal([]byte(body), &ghost); err != nil {
|
|
t.Fatalf("decode ghost: %v", err)
|
|
}
|
|
if len(ghost) != 4 || ghost["turns"].(float64) != 0 {
|
|
t.Fatalf("ghost stats = %v, want four zero fields", ghost)
|
|
}
|
|
}
|
|
|
|
func TestAPIStatsTotalsAndOnlineCount(t *testing.T) {
|
|
ts, store := newTestServer(t)
|
|
appendAgentEnd(t, store, "s1", 1, `{"usage":{"inputTokens":1200000,"outputTokens":340000,"totalCost":4.2}}`)
|
|
appendAgentEnd(t, store, "s2", 1, `{"usage":{"inputTokens":50,"outputTokens":10,"totalCost":0}}`)
|
|
|
|
get := func() map[string]any {
|
|
t.Helper()
|
|
code, body := apiReq(t, http.MethodGet, ts.URL+"/api/stats", testToken, "")
|
|
if code != http.StatusOK {
|
|
t.Fatalf("/api/stats status = %d", code)
|
|
}
|
|
var m map[string]any
|
|
if err := json.Unmarshal([]byte(body), &m); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
return m
|
|
}
|
|
|
|
totals := get()
|
|
if totals["turns"].(float64) != 2 ||
|
|
totals["inputTokens"].(float64) != 1200050 ||
|
|
totals["outputTokens"].(float64) != 340010 ||
|
|
totals["totalCost"].(float64) != 4.2 ||
|
|
totals["sessionsCount"].(float64) != 2 {
|
|
t.Fatalf("totals = %v", totals)
|
|
}
|
|
if totals["onlineCount"].(float64) != 0 {
|
|
t.Fatalf("onlineCount = %v, want 0 with no agents", totals["onlineCount"])
|
|
}
|
|
|
|
// one registered agent connection must show up as online
|
|
ws := dialAgent(t, ts)
|
|
if err := ws.WriteJSON(helloFrame("s-live")); err != nil {
|
|
t.Fatalf("hello: %v", err)
|
|
}
|
|
if _, _, err := ws.ReadMessage(); err != nil { // welcome
|
|
t.Fatalf("welcome: %v", err)
|
|
}
|
|
online := get()
|
|
if online["onlineCount"].(float64) != 1 {
|
|
t.Fatalf("onlineCount = %v, want 1 after hello", online["onlineCount"])
|
|
}
|
|
}
|
|
|
|
func TestAPIStatsClosedStore500(t *testing.T) {
|
|
ts, store := newTestServer(t)
|
|
if err := store.Close(); err != nil {
|
|
t.Fatalf("close store: %v", err)
|
|
}
|
|
if code, _ := apiReq(t, http.MethodGet, ts.URL+"/api/stats", testToken, ""); code != http.StatusInternalServerError {
|
|
t.Fatal("stats with broken store must 500")
|
|
}
|
|
if code, _ := apiReq(t, http.MethodGet, ts.URL+"/api/sessions/s1/stats", testToken, ""); code != http.StatusInternalServerError {
|
|
t.Fatal("session stats with broken store must 500")
|
|
}
|
|
}
|