Files
member-console/internal/server/operator_billing_render_test.go
T
cgalo5758 8e3c68c6be Make UI surfaces honestly reflect system state
- Add deployment-name branding to titles, mastheads, and OG tags
- Share one grant delivery-state query with lineage across grants
  surfaces
- Show pool status/usage, org owners, and config readiness
- Make billing views projection-aware with recency and sync vocabulary
- Guard FedWiki creation without domains and render route-aware 404s
2026-08-23 01:45:52 -05:00

98 lines
3.7 KiB
Go

package server
import (
"bytes"
"html/template"
"io/fs"
"strings"
"testing"
"git.coopcloud.tech/wiki-cafe/member-console/internal/embeds"
"git.coopcloud.tech/wiki-cafe/member-console/internal/web"
)
// renderOperatorBilling executes the real operator_billing.html wrapper
// template, mirroring how NewOperatorPartialsHandler loads the operator
// partials. renderBody is stubbed (as in other isolated partial render
// tests): this test only exercises the wrapper's own markup (nav pills, the
// Stripe Sync vocabulary legend, the webhook-recency stamp), not the inner
// per-tab tables.
func renderOperatorBilling(t *testing.T, data OperatorBillingWrapperData) string {
t.Helper()
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 "" },
})
tmpl, err = tmpl.ParseFS(partialsSub, "operator_*.html")
if err != nil {
t.Fatalf("ParseFS: %v", err)
}
var buf bytes.Buffer
if err := tmpl.ExecuteTemplate(&buf, "operator_billing.html", data); err != nil {
t.Fatalf("ExecuteTemplate: %v", err)
}
return buf.String()
}
// TestOperatorBillingNotMappedVocabularyIsDefinedInPlace covers the
// operator-billing-views delta: every one of the four billing sub-pages
// shares this one wrapper, so a legend rendered here reaches all four
// without per-tab duplication (design.md; tasks 4.1).
func TestOperatorBillingNotMappedVocabularyIsDefinedInPlace(t *testing.T) {
out := renderOperatorBilling(t, OperatorBillingWrapperData{
ActiveSection: "accounts",
InnerTemplate: "operator_billing_accounts.html",
DataAsOf: "Jan 2, 2026 15:04 MST",
})
if !strings.Contains(out, "Not Mapped") {
t.Error("legend missing the vocabulary term it defines")
}
if !strings.Contains(out, "no Stripe object is linked") {
t.Error("legend does not state what an unmapped record means")
}
if !strings.Contains(out, "check its sync status") && !strings.Contains(out, "Stripe integration settings") {
t.Error("legend does not say what to do about an unmapped record")
}
}
// TestOperatorBillingRecencyStamp covers the operator-billing-views delta's
// projection-recency requirement (design.md Decision 3; task 4.2): every
// billing sub-page states when data was last projected, or states plainly
// that nothing has been received yet, rather than presenting rows or
// emptiness as if current.
func TestOperatorBillingRecencyStamp(t *testing.T) {
t.Run("recency stamp renders with data", func(t *testing.T) {
out := renderOperatorBilling(t, OperatorBillingWrapperData{
ActiveSection: "invoices",
InnerTemplate: "operator_invoices.html",
DataAsOf: "Jan 2, 2026 15:04 MST",
})
if !strings.Contains(out, "Data as of Jan 2, 2026 15:04 MST") {
t.Errorf("recency stamp missing or malformed: %s", out)
}
if strings.Contains(out, "No billing events have been received") {
t.Error("no-events copy rendered even though DataAsOf was set")
}
})
t.Run("no events ever processed is stated", func(t *testing.T) {
out := renderOperatorBilling(t, OperatorBillingWrapperData{
ActiveSection: "payments",
InnerTemplate: "operator_payments.html",
NoEventsProcessed: true,
})
if !strings.Contains(out, "No billing events have been received") {
t.Errorf("missing the no-events-ever statement: %s", out)
}
if strings.Contains(out, "Data as of") {
t.Error("stale 'Data as of' copy rendered with no processed events")
}
})
}