Capacity, nodes, models and replicas come first, then a rule, then traffic, people, what is being run and usage. The people and per-model bar charts, which repeated the tables under them, are gone. The page swaps its content every few seconds with htmx instead of reloading, so a reader keeps their place, and it stops while the tab is hidden. Without script the meta refresh still reloads it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
652 lines
24 KiB
Go
652 lines
24 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"html/template"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
|
|
)
|
|
|
|
func get(t *testing.T, h http.Handler, target string, headers map[string]string) (int, string) {
|
|
t.Helper()
|
|
r := httptest.NewRequest(http.MethodGet, target, nil)
|
|
for k, v := range headers {
|
|
r.Header.Set(k, v)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
return w.Code, w.Body.String()
|
|
}
|
|
|
|
func TestLandingPageTellsYouHowToConnect(t *testing.T) {
|
|
code, body := get(t, Landing("v1.2.3", true, false, "", ""), "http://llmgw.example/", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("status %d", code)
|
|
}
|
|
for _, want := range []string{"http://llmgw.example/v1", "Authorization: Bearer", "v1.2.3"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("the page does not mention %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLandingPageSaysNothingAboutWhatIsHere(t *testing.T) {
|
|
// It is served without a key, to anyone who can reach the address. Anything
|
|
// on it is public, so nothing about the cluster belongs on it.
|
|
_, body := get(t, Landing("v1", true, false, "", ""), "http://llmgw.example/", nil)
|
|
for _, leak := range []string{"llama", "qwen", "10.0.0.", "replica", "node01"} {
|
|
if strings.Contains(strings.ToLower(body), leak) {
|
|
t.Errorf("the landing page leaks %q", leak)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLandingPageWarnsWhenTheGatewayIsOpen(t *testing.T) {
|
|
// Someone who reaches this page on an unauthenticated gateway should learn
|
|
// that from the page rather than by discovering they can use it.
|
|
_, open := get(t, Landing("v1", false, false, "", ""), "http://llmgw.example/", nil)
|
|
if !strings.Contains(open, "no API keys configured") {
|
|
t.Error("an open gateway does not say so")
|
|
}
|
|
_, closed := get(t, Landing("v1", true, false, "", ""), "http://llmgw.example/", nil)
|
|
if strings.Contains(closed, "no API keys configured") {
|
|
t.Error("a closed gateway claims to be open")
|
|
}
|
|
}
|
|
|
|
func TestTheExampleURLSurvivesAReverseProxy(t *testing.T) {
|
|
// The process sees http and an internal host; the client used https and a
|
|
// public name. An example naming the internal address is one nobody can
|
|
// paste anywhere.
|
|
_, body := get(t, Landing("v1", true, false, "", ""), "http://10.0.0.140:8080/", map[string]string{
|
|
"X-Forwarded-Proto": "https",
|
|
"X-Forwarded-Host": "llmgw.mynodes.xyz",
|
|
})
|
|
if !strings.Contains(body, "https://llmgw.mynodes.xyz/v1") {
|
|
t.Error("the example does not use the address the client actually used")
|
|
}
|
|
if strings.Contains(body, "10.0.0.140") {
|
|
t.Error("the internal address leaked into the page")
|
|
}
|
|
}
|
|
|
|
func TestForwardedHeaderChainsTakeTheFirstEntry(t *testing.T) {
|
|
// A chain of proxies appends rather than replaces.
|
|
_, body := get(t, Landing("v1", true, false, "", ""), "http://internal/", map[string]string{
|
|
"X-Forwarded-Proto": "https, http",
|
|
"X-Forwarded-Host": "llmgw.example, internal",
|
|
})
|
|
if !strings.Contains(body, "https://llmgw.example/v1") {
|
|
t.Errorf("the base URL was built from the wrong entry")
|
|
}
|
|
}
|
|
|
|
func TestOnlyTheRootIsTheLandingPage(t *testing.T) {
|
|
// A catch-all would turn every mistyped path into this page, hiding real
|
|
// 404s from anyone debugging a client.
|
|
code, _ := get(t, Landing("v1", true, false, "", ""), "http://x/v1/chat/completionz", nil)
|
|
if code != http.StatusNotFound {
|
|
t.Errorf("a mistyped path returned %d", code)
|
|
}
|
|
}
|
|
|
|
// fakeSource is a cluster for the dashboard to render.
|
|
type fakeSource struct{}
|
|
|
|
func (fakeSource) Nodes() []adminapi.NodeView {
|
|
return []adminapi.NodeView{{
|
|
ID: "llmgw-node01", State: "ready", GPUTotal: 8, GPUFree: 4,
|
|
Classes: []string{"h100-80g"}, Fabrics: []string{"ib-a"},
|
|
Replicas: 1, LastSeenAgo: "2s", AgentVersion: "v0.5.0",
|
|
Unhealthy: []string{"gpu 3: ECC errors"},
|
|
}}
|
|
}
|
|
|
|
func (fakeUsage) UsageByUser(context.Context, time.Time) ([]store.UserUsage, error) {
|
|
return []store.UserUsage{
|
|
{UserID: "usr-1", Email: "ilker@example.com", Keys: 2,
|
|
Requests: 40, Prompt: 8000, Completion: 4000, CostMicros: 4_500_000, LastSeen: time.Now()},
|
|
{UserID: "", Keys: 1, Requests: 3, Prompt: 90, Completion: 40, Errors: 1, LastSeen: time.Now()},
|
|
}, nil
|
|
}
|
|
|
|
func (fakeUsage) UsageByModel(context.Context, time.Time) ([]store.ModelUsage, error) {
|
|
return []store.ModelUsage{
|
|
{Model: "qwen-coder", Requests: 30, Prompt: 6000, Completion: 3000, AvgLatency: 240},
|
|
{Model: "qwen", Requests: 13, Prompt: 2090, Completion: 1040, Errors: 1, AvgLatency: 90},
|
|
}, nil
|
|
}
|
|
|
|
func (fakeSource) Summary() adminapi.Summary {
|
|
return adminapi.Summary{
|
|
Nodes: adminapi.NodeTotals{Total: 1, Ready: 1},
|
|
GPUs: []adminapi.ClassUse{
|
|
{Class: "h100-80g", Total: 8, Used: 3, Unhealthy: 1},
|
|
},
|
|
Fabrics: []adminapi.FabricUse{
|
|
{Fabric: "ib-a", Nodes: 1, LargestFreeBlock: 0, NodesWithWeights: 1, Models: 2},
|
|
},
|
|
IdleReplicas: []adminapi.IdleReplica{
|
|
{ID: "served-abc", Model: "served", IdleSec: 3600, GPUs: 4},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (fakeSource) Models() []adminapi.ModelView {
|
|
return []adminapi.ModelView{
|
|
{Name: "served", Ready: 1, Min: 1, Max: 2, Profiles: []string{"tp4"}},
|
|
{Name: "stuck", Ready: 0, Min: 1, Max: 1, Profiles: []string{"tp8"}},
|
|
}
|
|
}
|
|
|
|
func (fakeSource) Replicas(string) []adminapi.ReplicaView {
|
|
return []adminapi.ReplicaView{{
|
|
ID: "served-1", Model: "served", State: "ready", Pool: "ib-a/h100-80g",
|
|
Endpoint: "http://10.0.0.141:20000", AgeSec: 3600,
|
|
Shards: []adminapi.ShardView{{ID: "served-1-r0", Node: "llmgw-node01"}},
|
|
}}
|
|
}
|
|
|
|
func (fakeSource) Explain(model string) adminapi.ExplainView {
|
|
if model != "stuck" {
|
|
return adminapi.ExplainView{Model: model, Placed: true}
|
|
}
|
|
return adminapi.ExplainView{Model: "stuck", Rejections: []adminapi.RejectionView{
|
|
{Profile: "tp8", Node: "llmgw-node01", Reason: "has 4 free h100-80g GPUs, needs 8"},
|
|
}}
|
|
}
|
|
|
|
type fakeUsage struct {
|
|
err error
|
|
buckets []store.Bucket
|
|
}
|
|
|
|
func (f fakeUsage) UsageBuckets(context.Context, time.Time, time.Duration) ([]store.Bucket, error) {
|
|
if f.err != nil {
|
|
return nil, f.err
|
|
}
|
|
return f.buckets, nil
|
|
}
|
|
|
|
func (f fakeUsage) UsageSince(context.Context, time.Time) ([]store.KeyUsage, error) {
|
|
if f.err != nil {
|
|
return nil, f.err
|
|
}
|
|
return []store.KeyUsage{{
|
|
KeyID: "key-1", KeyName: "laptop", KeyPrefix: "sk-llmgw-ab12…",
|
|
ProjectID: "chat", Requests: 42, Errors: 2,
|
|
Prompt: 1000, Completion: 3000, CostMicros: 4_500_000,
|
|
LastAt: time.Now().Add(-time.Minute),
|
|
}}, nil
|
|
}
|
|
|
|
func TestDashboardShowsTheCluster(t *testing.T) {
|
|
h := Dashboard(fakeSource{}, fakeUsage{}, "v1", time.Second, true, NavExtras{}, nil)
|
|
code, body := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("status %d", code)
|
|
}
|
|
for _, want := range []string{
|
|
"llmgw-node01", "h100-80g", "served-1", "http://10.0.0.141:20000",
|
|
"ECC errors", "laptop", "4.50", // spend shown in units, not micro-units
|
|
} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("the dashboard does not show %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDashboardExplainsOnlyWhatIsUnplaced(t *testing.T) {
|
|
// Asking why a satisfied model was not placed gives an empty answer, and a
|
|
// page full of empty answers hides the one that matters.
|
|
h := Dashboard(fakeSource{}, nil, "v1", time.Second, true, NavExtras{}, nil)
|
|
_, body := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
|
|
if !strings.Contains(body, "needs 8") {
|
|
t.Error("the unplaced model has no explanation")
|
|
}
|
|
if strings.Count(body, "Why nothing was placed") != 1 {
|
|
t.Error("the explanation section is missing or duplicated")
|
|
}
|
|
}
|
|
|
|
func TestDashboardSurvivesAFailingUsageQuery(t *testing.T) {
|
|
// A reporting query failing must not take the page with it: the cluster
|
|
// state is what someone is most likely here for.
|
|
h := Dashboard(fakeSource{}, fakeUsage{err: context.DeadlineExceeded}, "v1", time.Second, true, NavExtras{}, nil)
|
|
code, body := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("status %d", code)
|
|
}
|
|
if !strings.Contains(body, "llmgw-node01") {
|
|
t.Error("the cluster state was lost with the usage query")
|
|
}
|
|
}
|
|
|
|
func TestDashboardSaysWhenThereIsNothingToAccount(t *testing.T) {
|
|
h := Dashboard(fakeSource{}, nil, "v1", time.Second, true, NavExtras{}, nil)
|
|
_, body := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
if !strings.Contains(body, "Authentication is off") {
|
|
t.Error("the page does not explain the empty usage section")
|
|
}
|
|
}
|
|
|
|
func TestDashboardRendersAnEmptyCluster(t *testing.T) {
|
|
// The state a fresh install is in, and the one most likely to be looked at
|
|
// with a template that has never seen it.
|
|
h := Dashboard(emptySource{}, nil, "v1", time.Second, true, NavExtras{}, nil)
|
|
code, body := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("status %d", code)
|
|
}
|
|
if !strings.Contains(body, "No agents have registered") {
|
|
t.Error("an empty cluster renders nothing useful")
|
|
}
|
|
}
|
|
|
|
type emptySource struct{}
|
|
|
|
func (emptySource) Nodes() []adminapi.NodeView { return nil }
|
|
func (emptySource) Models() []adminapi.ModelView { return nil }
|
|
func (emptySource) Replicas(string) []adminapi.ReplicaView { return nil }
|
|
func (emptySource) Explain(string) adminapi.ExplainView { return adminapi.ExplainView{} }
|
|
func (emptySource) Summary() adminapi.Summary { return adminapi.Summary{} }
|
|
|
|
func TestStaticServesAssetsAndNothingElse(t *testing.T) {
|
|
// The assets live in their own embed. A file server over the tree that also
|
|
// carries the templates would serve those as well, and a directory listing
|
|
// would publish the shape of the tree to nobody's benefit.
|
|
h := Static()
|
|
for _, path := range []string{
|
|
"/_static/base.css",
|
|
"/_static/katex/katex.min.css",
|
|
"/_static/katex/katex.min.js",
|
|
"/_static/katex/auto-render.min.js",
|
|
"/_static/katex/fonts/KaTeX_Main-Regular.woff2",
|
|
} {
|
|
if code, _ := get(t, h, "http://x"+path, nil); code != http.StatusOK {
|
|
t.Errorf("%s is not served: %d", path, code)
|
|
}
|
|
}
|
|
for _, path := range []string{
|
|
"/_static/dashboard.html", "/_static/chat.html",
|
|
"/_static/", "/_static/katex/", "/_static/katex/fonts/",
|
|
"/_static/../web.go",
|
|
} {
|
|
if code, _ := get(t, h, "http://x"+path, nil); code == http.StatusOK {
|
|
t.Errorf("%s was served", path)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheLandingPageSaysWhereToGetAKey(t *testing.T) {
|
|
// A page that explains the API and leaves the reader with no route to using
|
|
// it is where onboarding stops.
|
|
_, body := get(t, Landing("v1", true, false, "platform@example.com", ""), "http://x/", nil)
|
|
if !strings.Contains(body, `href="mailto:platform@example.com"`) {
|
|
t.Error("an address was not turned into a mailto link")
|
|
}
|
|
// With signup closed, the route is a person; the page still has to say what
|
|
// arriving looks like.
|
|
if !strings.Contains(body, "shown once") {
|
|
t.Error("the page does not say a key cannot be recovered")
|
|
}
|
|
}
|
|
|
|
func TestContactRenderingByKind(t *testing.T) {
|
|
cases := []struct{ in, text, href string }{
|
|
{"platform@example.com", "platform@example.com", "mailto:platform@example.com"},
|
|
{"mailto:ops@example.com", "mailto:ops@example.com", "mailto:ops@example.com"},
|
|
{"https://wiki.example/llmgw", "https://wiki.example/llmgw", "https://wiki.example/llmgw"},
|
|
{"http://wiki.example", "http://wiki.example", "http://wiki.example"},
|
|
// Not addresses and not URLs: shown, not linked.
|
|
{"#platform on Slack", "#platform on Slack", ""},
|
|
{"Ayse", "Ayse", ""},
|
|
{"", "", ""},
|
|
{" ", "", ""},
|
|
}
|
|
for _, c := range cases {
|
|
text, href := contactLink(c.in)
|
|
if text != c.text || href != c.href {
|
|
t.Errorf("contactLink(%q) = (%q, %q), want (%q, %q)", c.in, text, href, c.text, c.href)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestADangerousContactSchemeIsNeverLinked(t *testing.T) {
|
|
// This string comes from configuration, and configuration is not always
|
|
// written by whoever runs the gateway. A javascript: href on a page served
|
|
// to every visitor is a stored XSS with a very short path to it; escaping
|
|
// the text does not make the scheme safe.
|
|
for _, bad := range []string{
|
|
"javascript:alert(1)",
|
|
"JavaScript:alert(1)",
|
|
"data:text/html,<script>alert(1)</script>",
|
|
"vbscript:msgbox(1)",
|
|
"file:///etc/passwd",
|
|
} {
|
|
text, href := contactLink(bad)
|
|
if href != "" {
|
|
t.Errorf("contactLink(%q) produced href %q", bad, href)
|
|
}
|
|
if text == "" {
|
|
t.Errorf("contactLink(%q) dropped the text entirely", bad)
|
|
}
|
|
|
|
_, body := get(t, Landing("v1", true, false, bad, ""), "http://x/", nil)
|
|
if strings.Contains(body, "href=\"javascript") || strings.Contains(body, "href=\"data:") {
|
|
t.Errorf("%q reached the page as a link", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheNoticeIsEscaped(t *testing.T) {
|
|
_, body := get(t, Landing("v1", true, false, "", "<script>alert(1)</script>"), "http://x/", nil)
|
|
if strings.Contains(body, "<script>alert(1)</script>") {
|
|
t.Error("the notice was rendered as markup")
|
|
}
|
|
if !strings.Contains(body, "<script>") {
|
|
t.Error("the notice was dropped rather than escaped")
|
|
}
|
|
}
|
|
|
|
func TestWithoutAContactThePageStillSaysSomethingUseful(t *testing.T) {
|
|
_, body := get(t, Landing("v1", true, false, "", ""), "http://x/", nil)
|
|
if !strings.Contains(body, "whoever operates this gateway") {
|
|
t.Error("an unconfigured gateway leaves the reader with nothing")
|
|
}
|
|
}
|
|
|
|
func TestWithSignupOpenTheLandingPagePointsAtIt(t *testing.T) {
|
|
// The route from "I found this address" to "I have a key" has to be on the
|
|
// page, or the reader is stuck whatever else it says.
|
|
_, body := get(t, Landing("v1", true, true, "", ""), "http://x/", nil)
|
|
for _, want := range []string{`href="/signup"`, `href="/login"`, `href="/profile"`} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("the page does not link to %s", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheDashboardDrawsTrafficWhenThereIsSome(t *testing.T) {
|
|
now := time.Now().Truncate(time.Hour)
|
|
usage := fakeUsage{buckets: []store.Bucket{
|
|
{Start: now.Add(-2 * time.Hour), Requests: 40, Prompt: 4000, Completion: 900, LatencyMs: 120},
|
|
{Start: now.Add(-time.Hour)},
|
|
{Start: now, Requests: 95, Prompt: 9000, Completion: 2100, LatencyMs: 310},
|
|
}}
|
|
h := Dashboard(fakeSource{}, usage, "v1", time.Second, true, NavExtras{}, nil)
|
|
_, body := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
|
|
if !strings.Contains(body, "<svg") {
|
|
t.Fatal("no chart was drawn")
|
|
}
|
|
// The empty hour in the middle must still be a slot, or the shape lies
|
|
// about when traffic happened.
|
|
if n := strings.Count(body, `<rect`); n < 4 {
|
|
t.Errorf("%d bars across three charts and three hours", n)
|
|
}
|
|
if !strings.Contains(body, "peak 95 requests/h") {
|
|
t.Error("the request peak is not labelled")
|
|
}
|
|
}
|
|
|
|
func TestTheDashboardSaysWhenThereIsNoTrafficYet(t *testing.T) {
|
|
h := Dashboard(fakeSource{}, fakeUsage{}, "v1", time.Second, true, NavExtras{}, nil)
|
|
_, body := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
if !strings.Contains(body, "No traffic recorded yet") {
|
|
t.Error("an empty history renders nothing at all")
|
|
}
|
|
}
|
|
|
|
func TestTheDashboardOffersOnlyLinksThatExistWhereItIsServed(t *testing.T) {
|
|
// The same page is served twice with different neighbours. Metrics, pprof
|
|
// and the management API exist only on the admin listener; account
|
|
// administration and the profile only on the client-facing one.
|
|
//
|
|
// Getting this wrong was not a 404: on the public port those paths fall
|
|
// through to the API-key middleware, so a visitor clicking "metrics" was
|
|
// told "no api key was provided", which is a far more confusing way to say
|
|
// the link does not belong there.
|
|
_, admin := get(t, Dashboard(fakeSource{}, nil, "v1", time.Second, true, NavExtras{}, nil),
|
|
"http://127.0.0.1:9090/", nil)
|
|
for _, want := range []string{`href="/metrics"`, `href="/debug/pprof/"`, `href="/api/v1/nodes"`} {
|
|
if !strings.Contains(admin, want) {
|
|
t.Errorf("the admin listener's page is missing %s", want)
|
|
}
|
|
}
|
|
if strings.Contains(admin, `href="/admin/users"`) {
|
|
t.Error("the admin listener's page links to a page only the public port serves")
|
|
}
|
|
|
|
_, public := get(t, Dashboard(fakeSource{}, nil, "v1", time.Second, false, NavExtras{}, nil),
|
|
"https://llmgw.example/admin", nil)
|
|
for _, unwanted := range []string{`href="/metrics"`, `href="/debug/pprof/"`, `href="/api/v1/nodes"`} {
|
|
if strings.Contains(public, unwanted) {
|
|
t.Errorf("the public page links to %s, which is not served there", unwanted)
|
|
}
|
|
}
|
|
if !strings.Contains(public, `href="/admin/users"`) {
|
|
t.Error("the public page does not link to account administration")
|
|
}
|
|
// And it says where the missing ones are, rather than pretending they do
|
|
// not exist.
|
|
if !strings.Contains(public, "admin listener") {
|
|
t.Error("the public page does not say where metrics and pprof live")
|
|
}
|
|
}
|
|
|
|
func TestTheDashboardShowsCapacityWithoutPrometheus(t *testing.T) {
|
|
// The whole point of putting this on the page: knowing how full a cluster
|
|
// is should not require standing up a monitoring stack beside it. The
|
|
// tables say what exists; this says how much of it is in use, which is the
|
|
// question asked first.
|
|
h := Dashboard(fakeSource{}, fakeUsage{}, "v1", time.Second, true, NavExtras{}, nil)
|
|
code, page := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("status %d", code)
|
|
}
|
|
for _, want := range []string{
|
|
"Capacity", "h100-80g", "3/8", // used of total
|
|
"Fabrics", "ib-a",
|
|
"Holding hardware nothing is asking for", "served-abc",
|
|
} {
|
|
if !strings.Contains(page, want) {
|
|
t.Errorf("the dashboard does not show %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAFaultyCardIsShownAsNeitherUsedNorFree(t *testing.T) {
|
|
// Counting a faulty card as free is how a cluster looks like it has room it
|
|
// does not, and the page must not disagree with the scheduler about that.
|
|
h := Dashboard(fakeSource{}, fakeUsage{}, "v1", time.Second, true, NavExtras{}, nil)
|
|
code, page := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("status %d", code)
|
|
}
|
|
if !strings.Contains(page, "Faulty") {
|
|
t.Error("faulty cards are not shown")
|
|
}
|
|
// 8 total, 3 used, 1 faulty, so 4 free.
|
|
if !strings.Contains(page, ">4<") {
|
|
t.Errorf("free count is wrong or missing:\n%s", page)
|
|
}
|
|
}
|
|
|
|
func TestAUsageMeterCannotOverrunItsColumn(t *testing.T) {
|
|
// The two counts it divides can disagree for an instant — a GPU freed
|
|
// between one read and the next — and a bar wider than its cell wrecks the
|
|
// table.
|
|
pct, ok := funcs["pct"].(func(int, int) int)
|
|
if !ok {
|
|
t.Fatal("the pct helper is missing")
|
|
}
|
|
for _, tc := range [][2]int{{0, 0}, {9, 8}, {-1, 8}, {100, 8}} {
|
|
if got := pct(tc[0], tc[1]); got < 0 || got > 100 {
|
|
t.Errorf("pct(%d,%d) = %d", tc[0], tc[1], got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheDashboardShowsWhoIsUsingTheCluster(t *testing.T) {
|
|
// Per key is the right unit for revoking something and the wrong one for
|
|
// answering who is using this cluster: one person holds a laptop key, a CI
|
|
// key and a notebook key, and adding three rows up by eye gets skipped.
|
|
h := Dashboard(fakeSource{}, fakeUsage{}, "v1", time.Second, true, NavExtras{}, nil)
|
|
code, page := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("status %d", code)
|
|
}
|
|
for _, want := range []string{"People", "ilker@example.com", "12.0k"} {
|
|
if !strings.Contains(page, want) {
|
|
t.Errorf("the dashboard does not show %q", want)
|
|
}
|
|
}
|
|
// A key with no owner predates accounts. Calling it "unknown" would imply
|
|
// something is wrong; it is an operator-issued key doing its job.
|
|
if !strings.Contains(page, "operator-issued keys") {
|
|
t.Error("an unowned key is not explained")
|
|
}
|
|
}
|
|
|
|
func TestTheDashboardShowsWhatIsActuallyRunning(t *testing.T) {
|
|
h := Dashboard(fakeSource{}, fakeUsage{}, "v1", time.Second, true, NavExtras{}, nil)
|
|
_, page := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
for _, want := range []string{"What is being run", "qwen-coder", "240 ms"} {
|
|
if !strings.Contains(page, want) {
|
|
t.Errorf("the dashboard does not show %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAReportingFailureDoesNotTakeThePageWithIt(t *testing.T) {
|
|
// The cluster state is what someone is most likely here for, and a usage
|
|
// query is the part most likely to be slow or broken.
|
|
h := Dashboard(fakeSource{}, brokenUsage{}, "v1", time.Second, true, NavExtras{}, nil)
|
|
code, page := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("status %d; a reporting failure took the page down", code)
|
|
}
|
|
if !strings.Contains(page, "llmgw-node01") {
|
|
t.Error("the cluster state was lost along with the usage query")
|
|
}
|
|
}
|
|
|
|
// brokenUsage fails every query, as a database under pressure would.
|
|
type brokenUsage struct{}
|
|
|
|
func (brokenUsage) UsageSince(context.Context, time.Time) ([]store.KeyUsage, error) {
|
|
return nil, errUsage
|
|
}
|
|
|
|
func (brokenUsage) UsageBuckets(context.Context, time.Time, time.Duration) ([]store.Bucket, error) {
|
|
return nil, errUsage
|
|
}
|
|
|
|
func (brokenUsage) UsageByUser(context.Context, time.Time) ([]store.UserUsage, error) {
|
|
return nil, errUsage
|
|
}
|
|
|
|
func (brokenUsage) UsageByModel(context.Context, time.Time) ([]store.ModelUsage, error) {
|
|
return nil, errUsage
|
|
}
|
|
|
|
var errUsage = errors.New("the database is unhappy")
|
|
|
|
func TestEveryClientExampleIsInThePageItself(t *testing.T) {
|
|
// The tabs are radio buttons and a stylesheet, nothing more. If a panel were
|
|
// fetched when its tab was picked, a reader with no JavaScript would choose
|
|
// a tab and get an empty box — and this is the page people arrive on before
|
|
// they have any reason to trust the site enough to enable anything.
|
|
_, body := get(t, Landing("v1", true, false, "", ""), "http://llmgw.example/", nil)
|
|
for _, want := range []string{
|
|
"curl http://llmgw.example/v1/chat/completions",
|
|
`base_url="http://llmgw.example/v1"`,
|
|
`"baseURL": "http://llmgw.example/v1"`,
|
|
} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("the page does not carry %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAHostileForwardedHostCannotWriteMarkup(t *testing.T) {
|
|
// Nothing overwrites X-Forwarded-Host when the gateway is reached directly,
|
|
// so its value is whatever the caller sent, and it is now reflected into
|
|
// several places on the page rather than one. Escaping is the template's
|
|
// job; this is what says so out loud, and what would fail if any of those
|
|
// places ever stopped being one.
|
|
_, body := get(t, Landing("v1", true, false, "", ""), "http://internal/", map[string]string{
|
|
"X-Forwarded-Host": `evil"><script>alert(1)</script><x y="`,
|
|
})
|
|
if strings.Contains(body, "<script>alert(1)</script>") {
|
|
t.Error("a forwarded host reached the page as markup")
|
|
}
|
|
if !strings.Contains(body, "evil") {
|
|
t.Error("the host was dropped rather than escaped, so the test proves nothing")
|
|
}
|
|
}
|
|
|
|
func TestTheLandingPageDiagramDescribesItself(t *testing.T) {
|
|
// It carries the one idea the prose does not: that fabrics are separate. A
|
|
// reader using a screen reader gets that from the label or not at all.
|
|
_, body := get(t, Landing("v1", true, false, "", ""), "http://x/", nil)
|
|
if !strings.Contains(body, `role="img"`) || !strings.Contains(body, "aria-label=") {
|
|
t.Error("the schematic is decoration to anyone not looking at it")
|
|
}
|
|
}
|
|
|
|
func TestTheStylesheetIsNamedByItsContent(t *testing.T) {
|
|
// Cached for a day: under a fixed name, a deploy's new styles reached
|
|
// people a day after its markup did.
|
|
b, err := staticFiles.ReadFile("static/base.css")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sum := sha256.Sum256(b)
|
|
want := "/_static/base.css?v=" + hex.EncodeToString(sum[:4])
|
|
if stylesheetURL != want {
|
|
t.Errorf("stylesheet at %q, want %q", stylesheetURL, want)
|
|
}
|
|
var out strings.Builder
|
|
if err := template.Must(template.New("").Funcs(funcs).Parse(`<link href="{{stylesheet}}">`)).Execute(&out, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out.String(), "v="+hex.EncodeToString(sum[:4])) {
|
|
t.Errorf("rendered as %s", out.String())
|
|
}
|
|
}
|
|
|
|
func TestTheClusterPageLeadsWithHealthAndRefreshesInPlace(t *testing.T) {
|
|
h := Dashboard(fakeSource{}, fakeUsage{}, "v1", time.Second, true, NavExtras{}, nil)
|
|
_, body := get(t, h, "http://127.0.0.1:9090/", nil)
|
|
|
|
// What the cluster is, before how it is being used.
|
|
nodes, replicas := strings.Index(body, "<h2>Nodes</h2>"), strings.Index(body, "<h2>Replicas</h2>")
|
|
people, usage := strings.Index(body, "<h2>People</h2>"), strings.Index(body, "<h2>Usage")
|
|
if nodes < 0 || replicas < 0 || people < 0 || usage < 0 {
|
|
t.Fatalf("a section is missing: nodes %d replicas %d people %d usage %d", nodes, replicas, people, usage)
|
|
}
|
|
if !(nodes < replicas && replicas < people && people < usage) {
|
|
t.Errorf("sections out of order: nodes %d replicas %d people %d usage %d", nodes, replicas, people, usage)
|
|
}
|
|
// Swapped in place, so a reader keeps their place; reloaded only without
|
|
// script.
|
|
if !strings.Contains(body, `id="live" hx-get="/"`) || !strings.Contains(body, `hx-select="#live"`) {
|
|
t.Error("the page does not refresh its content in place")
|
|
}
|
|
if !strings.Contains(body, `<noscript><meta http-equiv="refresh"`) {
|
|
t.Error("without script the page should still refresh")
|
|
}
|
|
}
|