package api_test import ( "net/http" "testing" ) func TestStatsTop_CountsIncidentsWithSparkAndMeans(t *testing.T) { s := newTS(t) red := newTeam(t, s, "red") postToIntegration(t, s, red.key, "fp-1", "DiskFull") postToIntegration(t, s, red.key, "fp-2", "DiskFull") // same group key: one incident postToIntegration(t, s, red.key, "fp-3", "Memory") top := list(t, red.call(http.MethodGet, "/api/stats/alerts/top", nil)) if len(top) != 2 { t.Fatalf("expected 2 names, got %d", len(top)) } for _, row := range top { if row["count"].(float64) != 1 { t.Errorf("%v: count %v, want 1 incident", row["name"], row["count"]) } daily := row["daily"].([]any) if len(daily) != 30 { t.Fatalf("daily has %d days, want 30", len(daily)) } if row["mean_resolve_seconds"] != nil { t.Errorf("unresolved incident must have null mean, got %v", row["mean_resolve_seconds"]) } } } func TestStatsHeatmap_ShapeAndScope(t *testing.T) { s := newTS(t) red := newTeam(t, s, "red") blue := newTeam(t, s, "blue") postToIntegration(t, s, red.key, "fp-r", "RedOnly") postToIntegration(t, s, blue.key, "fp-b", "BlueOnly") var out struct { Grid [][]int64 `json:"grid"` } decode(t, red.call(http.MethodGet, "/api/stats/alerts/heatmap", nil), &out) if len(out.Grid) != 7 || len(out.Grid[0]) != 24 { t.Fatalf("grid is %dx?, want 7x24", len(out.Grid)) } var sum int64 for _, row := range out.Grid { for _, c := range row { sum += c } } if sum != 1 { t.Errorf("red's heatmap counted %d incidents, want 1", sum) } } func TestStatsAlert_DrilldownAndScope(t *testing.T) { s := newTS(t) red := newTeam(t, s, "red") blue := newTeam(t, s, "blue") postToIntegration(t, s, red.key, "fp-r", "DiskFull") postToIntegration(t, s, blue.key, "fp-b", "BlueOnly") var d map[string]any decode(t, red.call(http.MethodGet, "/api/stats/alerts/DiskFull", nil), &d) if d["count"].(float64) != 1 { t.Errorf("count = %v, want 1", d["count"]) } if len(d["by_hour"].([]any)) != 24 || len(d["by_weekday"].([]any)) != 7 { t.Errorf("by_hour/by_weekday have wrong lengths") } if occ := d["occurrences"].([]any); len(occ) != 1 { t.Errorf("occurrences = %d, want 1", len(occ)) } if d["mean_ack_seconds"] != nil || d["longest_open_seconds"] != nil { t.Errorf("nothing acked or resolved yet, means must be null: %v", d) } // Another team's alert, and a name nobody has, are both a plain 404. for _, name := range []string{"BlueOnly", "Nope"} { resp := red.call(http.MethodGet, "/api/stats/alerts/"+name, nil) resp.Body.Close() if resp.StatusCode != http.StatusNotFound { t.Errorf("%s: expected 404, got %d", name, resp.StatusCode) } } }