package lua import ( "encoding/json" "os" "os/exec" "regexp" "strings" "testing" ) // The billing page rendered EMPTY in production while its data endpoint returned // 200 with real numbers. The cause was one line: render(st) referenced an // undefined `s` for two KPI cells, so the ReferenceError aborted the whole // render and every table stayed at its initial empty innerHTML. // // Nothing in the build, the tests or the API surfaced it. This file is the guard // for the whole class: a plugin's injected UI that references an undefined name, // or that depends on a container the page does not provide, fails silently. // billingUI returns the injected markup for the billing plugin: the full page // mount and the status-page element mount. func billingUI(t *testing.T) (page string, statusElement string) { t.Helper() src, err := os.ReadFile("plugins/billing.lua") if err != nil { t.Fatalf("read billing.lua: %v", err) } // Located BY CONTENT, not by index. The plugin also uses a long string for // its inline SVG icon, so "the first long string" is the icon and "the // second" is the page — which is exactly the kind of positional assumption // that breaks the next time an icon or a description is added. page = longStringContaining(t, string(src), "billing-root") statusElement = longStringContaining(t, string(src), "billing-status-tile") return page, statusElement } // longStringContaining returns the [==[ ... ]==] body that contains marker. func longStringContaining(t *testing.T, src, marker string) string { t.Helper() re := regexp.MustCompile(`(?s)\[==\[(.*?)\]==\]`) for _, m := range re.FindAllStringSubmatch(src, -1) { if strings.Contains(m[1], marker) { return m[1] } } t.Fatalf("no long string contains %q", marker) return "" } // TestBillingMountScriptExecutes is the guard for the production bug. // // The Billing page rendered empty while its data endpoint returned 200 with real // numbers. Cause: render(st) referenced an undefined `s` for two KPI cells, the // ReferenceError aborted the render, and every table kept its initial empty // innerHTML. Nothing in the build or the API surfaced it. // // Two earlier attempts at a static check were both wrong: a "declared names" // scan flagged every CSS class inside the inline HTML strings (class, div, td), // and a CSS-selector parse of the stylesheet reported the stylesheet itself as // broken. Static analysis of JS embedded in HTML strings is the wrong tool. // // So this actually RUNS the script, in node, against a minimal DOM stub, and // fails on any thrown error. Skipped when node is unavailable, with the reason // printed — never silently passing as if it had checked. func TestBillingMountScriptExecutes(t *testing.T) { page, el := billingUI(t) scripts := extractScripts(page) if len(scripts) == 0 { t.Fatal("no `) var out []string for _, m := range re.FindAllStringSubmatch(html, -1) { out = append(out, m[1]) } return out } // TestBillingPageCoversItsData is the other half: the page declares tables for // per-source / per-model / per-key / per-day and must actually render into them. // A table id that is never written to is exactly how "the page loads and shows // nothing" happens without an error. func TestBillingPageCoversItsData(t *testing.T) { page, _ := billingUI(t) for _, id := range []string{ "billing-kpis", "billing-by-source", "billing-by-model", "billing-by-key", "billing-by-day", } { if !strings.Contains(page, `id="`+id+`"`) { t.Errorf("the page has no container #%s", id) } } // Every container must be written to by the script, not just declared. scripts := extractScripts(page) all := strings.Join(scripts, "\n") for _, id := range []string{"billing-kpis", "billing-by-source", "billing-by-model", "billing-by-key", "billing-by-day"} { if !strings.Contains(all, `getElementById("`+id+`")`) { t.Errorf("#%s is declared but never read by the script — it stays empty forever", id) } } } // TestPluginIconIsNotARawEmoji guards the sidebar icon. The billing plugin // declared icon = "💰" and the WebUI drops that verbatim into the button, while // every native tab uses an inline SVG styled with `stroke: currentColor`. An // emoji there renders at the wrong size and ignores the theme, so it does not // match its neighbours — which is what the operator reported. func TestPluginIconIsNotARawEmoji(t *testing.T) { icon := billingIcon(mustBillingSource(t)) if icon == "" { t.Fatal("billing declares no icon; the sidebar entry would be blank") } if isEmojiIcon(icon) { t.Errorf("page icon is the raw emoji %q — the WebUI sidebar renders "+ "native tabs as inline SVG (stroke: currentColor), so an emoji is the "+ "wrong size and ignores the theme. Use an inline SVG path instead.", icon) } } // isEmojiIcon reports whether s is a pictographic emoji rather than markup or a // text glyph. Codepoints in the pictographic blocks, plus the regional-indicator // pair used by flags. func isEmojiIcon(s string) bool { r := []rune(s) if len(r) == 0 { return false } // Anything containing '<' is markup (an inline ), which is the fix. if strings.ContainsRune(s, '<') { return false } for _, c := range r { switch { case c >= 0x1F300 && c <= 0x1FAFF, // pictographs, symbols, supplemental c >= 0x1F000 && c <= 0x1F2FF, // mahjong/domino/cards c >= 0x2600 && c <= 0x27BF, // misc symbols + dingbats c >= 0x2B00 && c <= 0x2BFF, // arrows/misc symbols c == 0xFE0F, // variation selector-16 c >= 0x1F1E6 && c <= 0x1F1FF: // regional indicators (flags) return true } } return false } func mustBillingSource(t *testing.T) string { t.Helper() b, err := os.ReadFile("plugins/billing.lua") if err != nil { t.Fatal(err) } return string(b) } // billingIcon extracts the declared page icon. It accepts BOTH a quoted string // and a [==[ ... ]==] long string, because an inline SVG cannot be written as a // Lua short string without escaping every quote in it. func billingIcon(src string) string { if m := regexp.MustCompile(`(?m)^\s*icon\s*=\s*"([^"]*)"`).FindStringSubmatch(src); m != nil { return m[1] } if m := regexp.MustCompile(`(?s)\bicon\s*=\s*\[==\[(.*?)\]==\]`).FindStringSubmatch(src); m != nil { return m[1] } return "" } // TestBillingTableHeaderMatchesRowColumns catches column drift. // // row() gained cache columns (fresh / cache / cache%) while the header row did // not, in the same edit. The result is a table whose cells are shifted one // column left from "fresh" onward — so "cache%" sits under "completion" and the // last cell has no label. It renders, it has data, and it is wrong in a way that // takes a careful read to notice. func TestBillingTableHeaderMatchesRowColumns(t *testing.T) { page, _ := billingUI(t) js := strings.Join(extractScripts(page), "\n") if !strings.Contains(js, "") { t.Fatal("no table header found in the billing page script") } hStart := strings.Index(js, "function tableFor") if hStart < 0 { t.Fatal("no tableFor in the billing page script") } header := js[hStart:] th := strings.Count(header, "") rStart := strings.Index(js, "function row") rEnd := strings.Index(js, "function tableFor") if rStart < 0 || rEnd < 0 || rEnd <= rStart { t.Fatal("could not isolate row()") } row := js[rStart:rEnd] // Each cell closes with ; the first cell uses .. so // counting is exact. td := strings.Count(row, "") if th != td { t.Errorf("★ header declares %d columns but row() emits %d cells — the "+ "table is misaligned from the first differing column on", th, td) } }