CI / test (push) Successful in 1m19s
Two things the dashboard was missing: it could not act, and it was a column of numbers. Account administration moves to /admin/users — approving into a project, suspending, reinstating. That is the work an operator does constantly and it should not need a terminal. Three things stay on the CLI, and the line is not arbitrary: promoting someone to admin, resetting a password, and issuing a key on another person's behalf each turn a stolen admin session into more than it was — permanent access, or somebody else's account. Approving and suspending do not. Suspending signs the account out everywhere, or suspension is advisory until the session happens to expire, and an admin cannot suspend themselves, which is not a thing an interface should let you do by misclicking. The charts are inline SVG generated in Go. A charting library would mean a JavaScript toolchain in CI and a megabyte of bundle for what is a list of rectangles, and the single-binary goal rules that out. Hourly over a day: fine enough to show a working day, coarse enough that one slow minute is not an incident. Empty hours are filled in, because a chart with hours missing where nothing happened reads as a chart with hours missing from the data. The SVG is marked as trusted HTML, so everything that reaches it is either a number this package computed or escaped explicitly, and a bar's colour can only be one of four names — a colour that could be anything is a way to write CSS into every page the chart appears on. There are tests for both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
107 lines
3.3 KiB
Go
107 lines
3.3 KiB
Go
package web
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestABarChartDrawsOneBarPerValue(t *testing.T) {
|
|
out := string(BarChart(Series{Values: []float64{1, 5, 3}}, "requests"))
|
|
if n := strings.Count(out, "<rect"); n != 3 {
|
|
t.Errorf("%d bars for 3 values", n)
|
|
}
|
|
if !strings.Contains(out, "peak 5 requests") {
|
|
t.Errorf("the peak is not labelled: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestAnHourWithTrafficNeverLooksLikeOneWithout(t *testing.T) {
|
|
// One request in an hour against a peak of ten thousand rounds to a bar of
|
|
// zero height, and a chart where a busy hour and an idle one look identical
|
|
// is worse than no chart.
|
|
out := string(BarChart(Series{Values: []float64{10000, 1}}, "requests"))
|
|
for _, frag := range []string{`height="0.00"`, `height="0"`} {
|
|
if strings.Contains(out, frag) {
|
|
t.Errorf("a non-zero value was drawn with no height: %s", out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAnEmptySeriesStillRendersAFrame(t *testing.T) {
|
|
// A missing chart reads as a broken page; a flat one reads as no traffic,
|
|
// which is the truth.
|
|
for _, s := range []Series{{}, {Values: []float64{0, 0, 0}}} {
|
|
out := string(BarChart(s, "requests"))
|
|
if !strings.Contains(out, "<svg") {
|
|
t.Error("no frame was drawn")
|
|
}
|
|
if !strings.Contains(out, "nothing recorded") {
|
|
t.Errorf("an empty chart does not say so: %s", out)
|
|
}
|
|
if strings.Contains(out, "<rect") {
|
|
t.Error("bars were drawn for an empty series")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLabelsAreEscaped(t *testing.T) {
|
|
// Tooltips carry text that came from somewhere else, and the output of this
|
|
// function is marked as trusted HTML.
|
|
out := string(BarChart(Series{
|
|
Values: []float64{1},
|
|
Labels: []string{`</title><script>alert(1)</script>`},
|
|
}, "requests"))
|
|
|
|
if strings.Contains(out, "<script>") {
|
|
t.Fatalf("a label reached the markup unescaped: %s", out)
|
|
}
|
|
if !strings.Contains(out, "<script>") {
|
|
t.Errorf("the label was dropped rather than escaped: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestTheUnitIsEscapedToo(t *testing.T) {
|
|
out := string(BarChart(Series{Values: []float64{1}}, `"><script>alert(1)</script>`))
|
|
if strings.Contains(out, "<script>") {
|
|
t.Fatalf("the unit reached the markup unescaped: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestOnlyKnownColoursReachTheStyleAttribute(t *testing.T) {
|
|
// A colour that could be anything is a way to write CSS into every page the
|
|
// chart appears on.
|
|
out := string(BarChart(Series{Values: []float64{1}, Colour: "red;}</style><script>x"}, "u"))
|
|
if strings.Contains(out, "</style>") || strings.Contains(out, "script") {
|
|
t.Fatalf("a colour escaped its attribute: %s", out)
|
|
}
|
|
if !strings.Contains(out, "var(--accent)") {
|
|
t.Errorf("an unknown colour did not fall back: %s", out)
|
|
}
|
|
|
|
for _, ok := range []string{"--accent", "--ok", "--warn", "--bad"} {
|
|
if got := cssVar(ok); got != ok {
|
|
t.Errorf("cssVar(%q) = %q", ok, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCompactNumbers(t *testing.T) {
|
|
cases := map[float64]string{
|
|
0: "0", 7: "7", 1500: "1.5k", 2_400_000: "2.4M", 3_100_000_000: "3.1B", 1.25: "1.2",
|
|
}
|
|
for in, want := range cases {
|
|
if got := compact(in); got != want {
|
|
t.Errorf("compact(%v) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHourLabelsSayWhenAndHowMuch(t *testing.T) {
|
|
at := time.Date(2026, 9, 16, 14, 0, 0, 0, time.UTC)
|
|
got := hourLabel(at, 1500, "requests")
|
|
if !strings.Contains(got, "1.5k requests") {
|
|
t.Errorf("label = %q", got)
|
|
}
|
|
}
|