package web
import (
"bytes"
"strings"
"testing"
)
// renderOps executes the operations page far enough to read the prose
// about the durable store. The panels themselves are fed by rings and
// render the same whatever the store is doing — which is the whole
// reason this line has to exist.
func renderOps(t *testing.T, v opsView) string {
t.Helper()
var buf bytes.Buffer
if err := noHooks.template(opsPage).ExecuteTemplate(&buf, "ops-body", pageData{Title: "operations", Ops: v}); err != nil {
t.Fatal(err)
}
return buf.String()
}
// Off is a choice; broken is a fault. A panel that renders identically
// for both teaches an operator to trust a forge that is recording
// nothing — which is how the reserved-word schema failure would have
// reached production instead of a test run.
func TestOpsPageSaysWhenHistoryIsOff(t *testing.T) {
var v opsView
v.BudgetMs = 500
body := renderOps(t, v)
if !strings.Contains(body, "History is off") {
t.Errorf("an unconfigured store renders no notice:\n%s", body)
}
if strings.Contains(body, "NOT being recorded") {
t.Error("switched off is reported as a failure")
}
}
func TestOpsPageSaysWhenHistoryIsBroken(t *testing.T) {
var v opsView
v.BudgetMs = 500
v.Store.Configured = true
v.Store.Path = "/data/ops.duckdb"
v.Store.Err = "schema: at is reserved"
body := renderOps(t, v)
if !strings.Contains(body, "NOT being recorded") {
t.Errorf("a broken store renders no warning:\n%s", body)
}
if !strings.Contains(body, "schema: at is reserved") {
t.Error("the warning does not say what went wrong")
}
if !strings.Contains(body, "ci-fail-text") {
t.Error("the warning is not marked as a failure")
}
if strings.Contains(body, "History is off") {
t.Error("a broken store is reported as switched off")
}
}
func TestOpsPageNamesTheStoreAndItsWindow(t *testing.T) {
var v opsView
v.BudgetMs = 500
v.Store.Configured = true
v.Store.Path = "/data/ops.duckdb"
v.Store.Kept = "14d"
body := renderOps(t, v)
if !strings.Contains(body, "/data/ops.duckdb") {
t.Errorf("a healthy store is not named:\n%s", body)
}
if !strings.Contains(body, "kept for 14d") {
t.Error("the retention window is not stated, so nobody was asked about it")
}
if strings.Contains(body, "NOT being recorded") || strings.Contains(body, "History is off") {
t.Error("a healthy store renders a fault")
}
// Retention off must not render a dangling "kept for".
v.Store.Kept = ""
if body := renderOps(t, v); strings.Contains(body, "kept for") {
t.Error("retention off still renders a window")
}
}
// SQL has no timing category of its own: ncruces/go-sqlite3 builds its
// connector through an unexported constructor, so there is no
// driver.Connector to wrap. The panel's answer is subtraction, and
// subtraction the reader has to do in their head is not an answer — so
// the leftover is a column.
func TestOpsPageNamesTheUntimedRemainder(t *testing.T) {
var v opsView
v.BudgetMs = 500
v.Renders = append(v.Renders, opsRenderRow(300, 120, 40))
body := renderOps(t, v)
if !strings.Contains(body, ">180ms<") {
t.Errorf("300ms total against 120ms of ops does not report 180ms of rest:\n%s", body)
}
if !strings.Contains(body, "SQL is not timed separately") {
t.Error("the page does not say why SQL has no column of its own")
}
}
// The two numbers are sampled separately, so rounding can put the parts
// above the whole. A negative remainder would read as a bug in the
// forge rather than in the measurement.
func TestRestNeverGoesNegative(t *testing.T) {
var v opsView
v.BudgetMs = 500
v.Renders = append(v.Renders, opsRenderRow(100, 140, 40))
body := renderOps(t, v)
if strings.Contains(body, "-40ms") {
t.Errorf("parts above the whole rendered a negative remainder:\n%s", body)
}
if !strings.Contains(body, ">0ms<") {
t.Errorf("expected the remainder clamped to 0ms:\n%s", body)
}
}
// opsRenderRow builds one row of the recent-renders table.
func opsRenderRow(elapsed, ops, zit int64) opsRenderView {
return opsRenderView{At: "2026-08-25T12:00:00", Path: "GET /{$}",
ElapsedMs: elapsed, OpsMs: ops, ZitMs: zit, Status: 200}
}
// A landing title is the first line of the message its author wrote,
// and authors write long ones. On the landing's own page the title IS
// the page; on a timeline row it is a decoration, and one that wraps to
// three lines has stopped decorating and started competing.
func TestClipBreaksOnAWordBoundary(t *testing.T) {
long := "Three tickets from the observability queue: the first real finding the ops panel produced"
got := clip(72, long)
if len([]rune(got)) > 73 {
t.Errorf("clip(72) returned %d characters: %q", len([]rune(got)), got)
}
if !strings.HasSuffix(got, "…") {
t.Errorf("a clipped title does not say it was clipped: %q", got)
}
if strings.HasSuffix(strings.TrimSuffix(got, "…"), " ") {
t.Errorf("trailing space before the ellipsis: %q", got)
}
// A title that fits is left exactly as written.
if got := clip(72, "Closes 2058808c3504."); got != "Closes 2058808c3504." {
t.Errorf("a short title was altered: %q", got)
}
// No room to break on a word: still bounded, still marked.
if got := clip(8, "supercalifragilistic"); len([]rune(got)) > 9 || !strings.HasSuffix(got, "…") {
t.Errorf("clip(8) of one long word gave %q", got)
}
}