package web
import (
"net/url"
"os"
"regexp"
"strings"
"testing"
)
// opsFleetFixture is a page with findings of two kinds and five stores.
func opsFleetFixture(t *testing.T, query string) string {
t.Helper()
var v opsView
remarshal(map[string]any{
"liveSubprocess": 0, "peakSubprocess": 6, "routesOver": 2, "routes": 57, "budgetMs": 500,
"signals": map[string]any{"minutes": 4, "renders": []int{9, 18, 12, 20}, "slow": []int{0, 2, 0, 1}, "busy": []int{1, 6, 2, 1}, "errors": []int{0, 0, 0, 0}},
"findings": []map[string]any{
{"key": "slow:GET /r/{repo}", "about": "slow", "severity": "warn", "text": "GET /r/{repo} p95 3.74s over the 500.0ms budget", "figure": "3.74s", "note": "7.5× the budget"},
{"key": "slow:GET /fleet", "about": "slow", "severity": "warn", "text": "GET /fleet p95 992.1ms over the 500.0ms budget", "figure": "992.1ms", "note": "2.0× the budget"},
{"key": "store:tcl", "about": "store", "severity": "warn", "text": "tcl index behind: last successful round 1h24m ago", "figure": "1h24m", "note": "behind"},
{"key": "store:zit", "about": "store", "severity": "warn", "text": "zit index behind: last successful round 9m ago", "figure": "9m", "note": "behind"},
{"key": "store:fossil", "about": "store", "severity": "warn", "text": "fossil index behind: last successful round 5m ago", "figure": "5m", "note": "behind"},
},
"stores": []map[string]any{
{"name": "tcl", "state": "behind", "storeBytes": 300, "indexBytes": 900, "lastSync": "2026-10-30T15:00:00Z", "roundSeconds": 0.3, "checkins": 40000},
{"name": "Notify", "state": "current", "storeBytes": 5, "indexBytes": 27, "lastSync": "2026-10-30T16:40:00Z", "roundSeconds": 0.06, "checkins": 324},
{"name": "althttpd", "state": "current", "storeBytes": 1, "indexBytes": 11, "lastSync": "2026-10-30T16:41:00Z", "roundSeconds": 0.06, "checkins": 323},
{"name": "fossil", "state": "current", "storeBytes": 134, "indexBytes": 181, "lastSync": "2026-10-30T16:39:00Z", "roundSeconds": 0.18, "checkins": 21030},
{"name": "zit", "state": "current", "storeBytes": 20, "indexBytes": 40, "lastSync": "2026-10-30T16:42:00Z", "roundSeconds": 0.07, "checkins": 900},
},
}, &v)
q, _ := url.ParseQuery(query)
v.StoreSort, v.Query = q.Get("stores"), q
var buf strings.Builder
if err := noHooks.template(opsPage).Execute(&buf, pageData{Title: "Operations", Ops: v}); err != nil {
t.Fatal(err)
}
return buf.String()
}
func storeOrder(body string) string {
var names []string
for _, m := range regexp.MustCompile(`data-hook="ops-store" data-store="([^"]+)"`).FindAllStringSubmatch(body, -1) {
names = append(names, m[1])
}
return strings.Join(names, ",")
}
// Every store is listed, in a scroll box, and a header sorts by its
// column: names A to Z, sizes largest first, syncs oldest first, and the
// header already sorting reverses it.
func TestTheStoresTableSortsByItsHeaders(t *testing.T) {
for q, want := range map[string]string{
"": "tcl,Notify,althttpd,fossil,zit",
"stores=name": "althttpd,fossil,Notify,tcl,zit",
"stores=-name": "zit,tcl,Notify,fossil,althttpd",
"stores=checkins": "tcl,fossil,zit,Notify,althttpd",
"stores=sync": "tcl,fossil,Notify,althttpd,zit",
"stores=nonsense": "tcl,Notify,althttpd,fossil,zit",
} {
body := opsFleetFixture(t, q)
if got := storeOrder(body); got != want {
t.Errorf("FAIL: ?%s lists %s, want %s", q, got, want)
}
}
body := opsFleetFixture(t, "stores=checkins&find=x")
if !strings.Contains(body, `href="?find=x&stores=-checkins#stores" data-hook="ops-store-sort" data-key="checkins"`) {
t.Errorf("FAIL: the sorting header does not reverse, or drops the page's other parameters")
}
if !strings.Contains(body, `aria-sort="descending"><a href="?find=x&stores=-checkins#stores"`) {
t.Errorf("FAIL: the sorting header does not say its direction")
}
if !strings.Contains(body, `class="tw ops-scroll"`) {
t.Errorf("FAIL: the stores table is not in a scroll box")
}
}
// Each finding sets its figure apart, and the header counts the kinds.
func TestFindingsLeadWithTheirFigure(t *testing.T) {
body := opsFleetFixture(t, "")
if n := strings.Count(body, `data-hook="ops-figure"`); n != 5 {
t.Errorf("FAIL: %d figures, want 5", n)
}
if !strings.Contains(body, `<span class="ops-att-f ov-warn" data-hook="ops-figure">3.74s<small>7.5× the budget</small></span>`) {
t.Errorf("FAIL: the slow route's figure is not set apart")
}
if !strings.Contains(body, "3 store · 2 slow · what notifies") {
t.Errorf("FAIL: the header does not count the kinds")
}
if !strings.Contains(body, `class="ops-att ops-scroll"`) {
t.Errorf("FAIL: the findings are not in a scroll box")
}
if out := os.Getenv("OPS_FLEET_HTML"); out != "" {
os.WriteFile(out, []byte(`<!doctype html><meta charset=utf-8><style>`+sheet+`</style><body><main style="max-width:72rem;margin:0 auto;padding:1rem">`+body+`</main>`), 0o644)
}
}