package server import ( "bytes" "context" "errors" "html/template" "io" "io/fs" "log/slog" "regexp" "strconv" "strings" "testing" "git.coopcloud.tech/wiki-cafe/member-console/internal/billing" "git.coopcloud.tech/wiki-cafe/member-console/internal/config" "git.coopcloud.tech/wiki-cafe/member-console/internal/embeds" "git.coopcloud.tech/wiki-cafe/member-console/internal/entitlements" "git.coopcloud.tech/wiki-cafe/member-console/internal/identity" "git.coopcloud.tech/wiki-cafe/member-console/internal/organization" "git.coopcloud.tech/wiki-cafe/member-console/internal/web" ) // Render coverage for the operator landing surface's overview regions ("At a // glance" + "System"). Template-level only: it executes operator.html against // hand-built OperatorPageData, so it needs no database and runs in every // environment. The loader that fills that data (loadOverview) is exercised // separately by the pure-function tests at the bottom of this file. // overviewTemplate parses the operator template set the same way // NewOperatorPartialsHandler does — partials first, then the shell — so this // test fails on the same parse errors production would hit. func overviewTemplate(t *testing.T) *template.Template { t.Helper() sub, err := fs.Sub(embeds.Templates, "templates") if err != nil { t.Fatalf("fs.Sub: %v", err) } partialsSub, err := fs.Sub(embeds.Templates, "templates/partials") if err != nil { t.Fatalf("fs.Sub partials: %v", err) } tmpl := template.New("operator").Funcs(template.FuncMap{ "renderBody": func(string, any) (template.HTML, error) { return "", nil }, "routeURL": web.RouteURL, "fieldErr": func(_, _, _, _ string, _, _ any) string { return "" }, "stripeEntityURL": func(string, string) string { return "" }, "deploymentName": config.DeploymentName, }) if tmpl, err = tmpl.ParseFS(partialsSub, "operator_*.html"); err != nil { t.Fatalf("ParseFS partials: %v", err) } if tmpl, err = tmpl.ParseFS(sub, "operator.html"); err != nil { t.Fatalf("ParseFS operator.html: %v", err) } return tmpl } func renderOperator(t *testing.T, data OperatorPageData) string { t.Helper() var buf bytes.Buffer if err := overviewTemplate(t).ExecuteTemplate(&buf, "operator.html", data); err != nil { t.Fatalf("ExecuteTemplate: %v", err) } return buf.String() } // landingRegion slices the rendered document down to the landing surface // itself. The navbar, sidebar, toast container and confirmation modal are // outside
(or outside #operator-body) and carry their own markup — // including an
in the modal — so assertions about this page's structure // must not see them. func landingRegion(t *testing.T, out string) string { t.Helper() const openTag = `
` start := strings.Index(out, openTag) if start < 0 { t.Fatalf("rendered output has no #operator-body region") } end := strings.Index(out, "
") if end < start { t.Fatalf("rendered output has no after #operator-body") } return out[start+len(openTag) : end] } // populatedOverview is a representative landing payload: every tile // available, a mix of healthy and unhealthy integrations, and a draining // queue. func populatedOverview() OperatorPageData { return OperatorPageData{ CSRFToken: "csrf", IAPosition: "runtime:landing", Overview: OverviewData{ Stats: []OverviewStat{ {Label: "People", Value: "1,204", Caption: "212 joined in the last 30 days", Href: "/operator/persons", Available: true}, {Label: "Team organizations", Value: "38", Caption: "Personal orgs excluded", Href: "/operator/organizations", Available: true}, {Label: "Monthly recurring", Value: "$2,295.00", Caption: "205 active subscriptions; 9 trialing", Href: "/operator/billing/subscriptions", Available: true}, {Label: "Open invoices", Value: "3", Caption: "$372.00 outstanding; issued and awaiting payment", Href: "/operator/billing/invoices", Available: true}, {Label: "Delivering grants", Value: "7", Caption: "Plus 80 signup defaults", Href: "/operator/grants", Available: true}, }, Providers: []OverviewProvider{ {Slug: "alpha", DisplayName: "Alpha", Kind: "provisioning", Status: "active", SurfacePath: "/operator/alpha-sites", Healthy: true, Configured: true}, {Slug: "beta", DisplayName: "Beta", Kind: "billing", Status: "disabled", MissingKeysText: "beta-api-key"}, }, Queue: OverviewQueue{Pending: 4, Retrying: 1, Available: true}, }, Activity: []ActivityEvent{{ EventType: "grant_issued", Timestamp: "Jan 2, 2026 3:04 PM", OrgID: "org-1", OrgName: "Example Org", Summary: "Issued Example Plan (promo)", Href: "/operator/organizations/org-1", }}, } } func TestOperatorOverviewRendersStatTiles(t *testing.T) { out := landingRegion(t, renderOperator(t, populatedOverview())) for _, want := range []string{ "At a glance", "People", `href="/operator/persons"`, // tile drills into the directory (ux-operator-scale) "1,204", // grouped value, formatted in Go "Team organizations", // deliberately created orgs, not personal ones `href="/operator/organizations"`, // tile drills into its section "Monthly recurring", // commitment as money, not a bare count "$2,295.00", // largest-currency-bucket value `href="/operator/billing/subscriptions"`, "Delivering grants", // operator-issued grants; defaults ride the caption "Plus 80 signup defaults", // composition caption `href="/operator/grants"`, "Open invoices", // receivables tile `href="/operator/billing/invoices"`, // drills into the invoices page "$372.00 outstanding", // caption leads with the balance } { if !strings.Contains(out, want) { t.Errorf("landing surface missing %q", want) } } // A tile with no Href must not render as a link (an unresolvable count // destination renders a static card, never an empty link target). if strings.Contains(out, `href=""`) { t.Errorf("hrefless stat tile rendered an empty link target") } } // An unavailable count must render an em dash. Rendering 0 would be an // actively misleading answer to "how many organizations are there". func TestOperatorOverviewUnavailableStatIsNotZero(t *testing.T) { data := OperatorPageData{ CSRFToken: "csrf", Overview: OverviewData{Stats: []OverviewStat{ {Label: "Organizations", Caption: "Active organizations", Href: "/operator/organizations", Available: false}, }}, } out := landingRegion(t, renderOperator(t, data)) if !strings.Contains(out, "—") { t.Errorf("unavailable stat did not render an em dash") } if !strings.Contains(out, "Count unavailable") { t.Errorf("unavailable stat did not explain itself") } // The tile still renders its label and link; only the number is withheld. if !strings.Contains(out, "Organizations") { t.Errorf("unavailable stat dropped its label") } if regexp.MustCompile(`display-6[^>]*>\s*0\s*<`).MatchString(out) { t.Errorf("unavailable stat rendered a zero value") } } func TestOperatorOverviewSystemPanel(t *testing.T) { out := landingRegion(t, renderOperator(t, populatedOverview())) for _, want := range []string{ "System", "Integrations", "Alpha", `href="/operator/alpha-sites"`, // provider with a surface links to it "provisioning", // provider kind is shown `Beta`) { t.Errorf("surfaceless provider rendered an empty link") } // Alpha is fully configured; only Beta's row should carry the // not-configured marker (ux-honest-surfaces: the signal is a negative // marker, never a redundant positive claim). if n := strings.Count(out, "Not configured"); n != 1 { t.Errorf("system panel shows %d \"Not configured\" markers, want exactly 1 (Beta only)", n) } // The registry-status badge must not read as a live health/connectivity // claim (integration-settings spec: "cannot read as a health check"). if strings.Contains(out, "read live") || strings.Contains(out, "Connected") || strings.Contains(out, "connected to") { t.Errorf("system panel implies a live health/connectivity check") } } // Dead-lettered outbox work is the one queue bucket nothing will clear on its // own, so it must be called out rather than shown as another neutral number. func TestOperatorOverviewDeadLetterRaisesAlarm(t *testing.T) { data := populatedOverview() data.Overview.Queue = OverviewQueue{Pending: 2, Retrying: 0, DeadLetter: 3, Available: true} out := landingRegion(t, renderOperator(t, data)) if !strings.Contains(out, "text-danger") { t.Errorf("dead-lettered work did not render with alarm styling") } if !strings.Contains(out, "exhausted its retries") { t.Errorf("dead-letter state did not explain what an operator must do") } if strings.Contains(out, "draining normally") { t.Errorf("dead-lettered queue still claimed to be draining normally") } } // The landing surface is the first thing a fresh deployment shows, so every // region needs a legible empty state rather than a blank panel. func TestOperatorOverviewEmptyStates(t *testing.T) { out := landingRegion(t, renderOperator(t, OperatorPageData{CSRFToken: "csrf"})) for _, want := range []string{ "Counts are unavailable.", "No activity recorded yet.", "No integrations registered.", "Queue health is unavailable.", } { if !strings.Contains(out, want) { t.Errorf("empty landing surface missing %q", want) } } // The lookup affordance is the one thing that must work on an empty // deployment, so it renders regardless. if !strings.Contains(out, "Find a person or organization") { t.Errorf("empty landing surface dropped the lookup affordance") } } // TestOperatorLandingRegionOrder covers operator-panel-navigation ("Landing // surface orders attention before totals"; maintainer 2026-08-23: the counts // an operator checks daily outrank the system cards): setup (while required // steps are incomplete) precedes At a glance, At a glance precedes System, // and System precedes Recent activity — so attention-shaped content is still // never the page's last region. func TestOperatorLandingRegionOrder(t *testing.T) { data := populatedOverview() data.Setup = SetupState{Steps: []SetupStep{{Key: "step", Required: true, Complete: false}}} out := landingRegion(t, renderOperator(t, data)) setupIdx := strings.Index(out, `id="overview-setup-heading"`) systemIdx := strings.Index(out, `id="overview-system-heading"`) glanceIdx := strings.Index(out, `id="overview-glance-heading"`) activityIdx := strings.Index(out, `id="overview-activity-heading"`) for name, idx := range map[string]int{"setup": setupIdx, "system": systemIdx, "glance": glanceIdx, "activity": activityIdx} { if idx < 0 { t.Fatalf("landing surface missing the %s region", name) } } if !(setupIdx < glanceIdx && glanceIdx < systemIdx && systemIdx < activityIdx) { t.Errorf("landing regions out of order: setup=%d glance=%d system=%d activity=%d, want setup < glance < system < activity", setupIdx, glanceIdx, systemIdx, activityIdx) } } // TestOperatorLandingGlanceBeforeSystemEvenWithoutSetup covers the same // requirement's scenario directly (the metric tiles render before the // system-status region) for the common case where setup is not showing. func TestOperatorLandingGlanceBeforeSystemEvenWithoutSetup(t *testing.T) { out := landingRegion(t, renderOperator(t, populatedOverview())) systemIdx := strings.Index(out, `id="overview-system-heading"`) glanceIdx := strings.Index(out, `id="overview-glance-heading"`) if systemIdx < 0 || glanceIdx < 0 { t.Fatalf("landing surface missing System or At a glance region") } if glanceIdx >= systemIdx { t.Errorf("At a glance must render before System status (glance=%d system=%d)", glanceIdx, systemIdx) } } // TestOperatorActivityFeedLinksToSubject covers operator-panel-navigation // ("Feed entries link to their subjects"): a grant/transition entry's // summary links to its organization's detail page, an invoice/payment // entry's summary links to the corresponding billing view, and an entry // with no addressable surface (Href empty) renders plain text rather than a // dead link. func TestOperatorActivityFeedLinksToSubject(t *testing.T) { data := OperatorPageData{ CSRFToken: "csrf", Activity: []ActivityEvent{ {EventType: "grant_issued", Timestamp: "t", OrgID: "org-1", OrgName: "Example Org", Summary: "Issued Example Plan", Href: "/operator/organizations/org-1"}, {EventType: "invoice_created", Timestamp: "t", OrgID: "org-1", OrgName: "Example Org", Summary: "Invoice $10.00", Href: "/operator/billing/invoices"}, {EventType: "payment_received", Timestamp: "t", OrgID: "org-1", OrgName: "Example Org", Summary: "Payment $10.00", Href: "/operator/billing/payments"}, {EventType: "grant_issued", Timestamp: "t", Summary: "No addressable subject"}, }, } out := landingRegion(t, renderOperator(t, data)) if !strings.Contains(out, `Issued Example Plan`) { t.Errorf("grant entry summary must link to the org detail page, got:\n%s", out) } if !strings.Contains(out, `Invoice $10.00`) { t.Errorf("invoice entry summary must link to the billing invoices view, got:\n%s", out) } if !strings.Contains(out, `Payment $10.00`) { t.Errorf("payment entry summary must link to the billing payments view, got:\n%s", out) } if strings.Contains(out, `No addressable subject`) { t.Errorf("an entry with no addressable surface must not render a dead (empty-href) link, got:\n%s", out) } if !strings.Contains(out, `No addressable subject`) { t.Errorf("an entry with no addressable surface must render plain text, got:\n%s", out) } // Organization column always links straight to the org, independent of // the row's own Href (which for invoice/payment rows points elsewhere). if n := strings.Count(out, `Example Org`); n != 3 { t.Errorf("expected 3 Organization-column links to the org, got %d in:\n%s", n, out) } } // The landing surface is the page screenshotted for the project README, so // its document outline has to be defensible: exactly one H1, no skipped // levels. The older operator pages run H1 straight to H6; that pattern must // not spread here. func TestOperatorOverviewHeadingHierarchy(t *testing.T) { out := landingRegion(t, renderOperator(t, populatedOverview())) matches := regexp.MustCompile(`(?i)]`).FindAllStringSubmatch(out, -1) if len(matches) == 0 { t.Fatalf("landing surface rendered no headings at all") } levels := make([]int, 0, len(matches)) for _, m := range matches { n, err := strconv.Atoi(m[1]) if err != nil { t.Fatalf("unparsable heading level %q", m[1]) } levels = append(levels, n) } if levels[0] != 1 { t.Errorf("first heading is h%d, want h1", levels[0]) } h1Count := 0 for _, l := range levels { if l == 1 { h1Count++ } } if h1Count != 1 { t.Errorf("found %d h1 elements, want exactly 1", h1Count) } for i := 1; i < len(levels); i++ { if levels[i] > levels[i-1]+1 { t.Errorf("heading level skips from h%d to h%d at position %d (levels: %v)", levels[i-1], levels[i], i, levels) } } // Guard the specific regression: no h4/h5/h6 on this surface at all. for _, l := range levels { if l > 3 { t.Errorf("landing surface uses h%d; the overview outline stops at h3 (levels: %v)", l, levels) } } } // Strict CSP: nothing on this surface may rely on an inline script, an inline // event handler, or a style attribute. func TestOperatorOverviewIsCSPClean(t *testing.T) { out := landingRegion(t, renderOperator(t, populatedOverview())) if regexp.MustCompile(`(?i)]*)?>[^<]`).MatchString(out) { t.Errorf("landing surface contains an inline