package web
import (
"strings"
"testing"
)
// A line is scaled to its own peak so the shape is the change rather
// than the absolute — the absolute is the number it sits beside. A
// quiet hour is a flat line at the floor, not a missing one: the strip's
// four lines sit side by side, and an absent one reads as a card that
// does not work rather than a signal at zero.
func TestOpsLineDrawsFlatWhenQuietAndScaledWhenNot(t *testing.T) {
if got := lineSVG(nil, "x"); got != "" {
t.Errorf("no series drew %q", got)
}
if got := lineSVG([]int{3}, "x"); got != "" {
t.Errorf("a single point drew %q", got)
}
flat := string(lineSVG([]int{0, 0, 0, 0}, "x"))
if !strings.Contains(flat, "ops-spark-flat") || !strings.Contains(flat, "<polyline") {
t.Errorf("an empty window did not draw its floor: %s", flat)
}
svg := string(lineSVG([]int{0, 5, 0}, "renders begun each minute, last hour"))
if !strings.Contains(svg, "<polyline") || strings.Contains(svg, "ops-spark-flat") {
t.Fatalf("no scaled polyline: %s", svg)
}
// The peak touches the top of the box and the floor sits at the
// bottom, so a reader sees the full swing of the window.
if !strings.Contains(svg, "0.0,24.00") || !strings.Contains(svg, "50.0,2.00") {
t.Errorf("series is not scaled to its own peak: %s", svg)
}
// Named, not silent: the shape is the only content, so it carries
// the label a reader hovers and a screen reader announces.
if !strings.Contains(svg, `aria-label="renders begun each minute, last hour"`) ||
!strings.Contains(svg, "<title>renders begun each minute, last hour</title>") {
t.Errorf("the line is unlabelled: %s", svg)
}
}
// The strip is the four golden signals, each with the hour behind it:
// traffic, latency, errors, saturation. Every line names what it
// measures, because a line beside a number is read as that number's
// history and must be exactly that.
func TestTheStripDrawsTheFourSignals(t *testing.T) {
var v opsView
remarshal(map[string]any{
"liveSubprocess": 5, "peakSubprocess": 9, "routesOver": 2, "routes": 9, "budgetMs": 500,
"signals": map[string]any{"minutes": 3, "renders": []int{1, 4, 2}, "slow": []int{0, 1, 0}, "busy": []int{2, 5, 1}, "errors": []int{0, 0, 2}},
}, &v)
var buf strings.Builder
if err := noHooks.template(opsPage).Execute(&buf, pageData{Title: "Operations", Ops: v}); err != nil {
t.Fatal(err)
}
body := buf.String()
if n := strings.Count(body, `class="ops-spark"`); n != 4 {
t.Fatalf("want 4 scaled lines in the strip, got %d:\n%s", n, body)
}
for _, want := range []string{"renders begun each minute", "renders over budget each minute", "server errors each minute", "most store calls running at once"} {
if !strings.Contains(body, `aria-label="`+want) {
t.Errorf("no line labelled %q", want)
}
}
// Routes over budget is the latency figure, and the last whole
// minute is the rate, since the current one is still filling.
if !strings.Contains(body, `data-over="2"`) || !strings.Contains(body, `<span class="ops-met-n">4</span>`) {
t.Errorf("the strip's figures are off:\n%s", body)
}
// Starting, the strip is off: the panels read as an empty forge
// until the stores are up, and the phase is the only true thing.
var starting opsView
remarshal(map[string]any{"phase": map[string]any{"name": "starting", "repos": 24, "ready": 9}}, &starting)
buf.Reset()
if err := noHooks.template(opsPage).Execute(&buf, pageData{Title: "Operations", Ops: starting}); err != nil {
t.Fatal(err)
}
if strings.Contains(buf.String(), `data-hook="ops-strip"`) {
t.Error("the strip is drawn while the forge is starting")
}
if !strings.Contains(buf.String(), "Starting: 9 of 24 stores ready") {
t.Errorf("the status line does not carry the phase:\n%s", buf.String())
}
}