Files
member-console/internal/server/operator_domains_render_test.go
T

75 lines
2.8 KiB
Go

// DB-free render coverage for the operator Domains moderation list's
// force-release affordance: offered exactly where the registry would admit
// it. A rendered control the server always refuses is the dead-end-affordance
// failure mode (issues.md 2026-07-26 — the domains walkthrough timed out on
// a placed claim's button).
package server
import (
"html/template"
"io/fs"
"log/slog"
"net/http/httptest"
"strings"
"testing"
"git.coopcloud.tech/wiki-cafe/member-console/internal/embeds"
"git.coopcloud.tech/wiki-cafe/member-console/internal/web"
)
func renderOperatorDomains(t *testing.T, data OperatorDomainsData) string {
t.Helper()
sub, err := fs.Sub(embeds.Templates, "templates/partials")
if err != nil {
t.Fatalf("sub templates FS: %v", err)
}
tmpl, err := template.New("operator-domains").Funcs(template.FuncMap{
"routeURL": web.RouteURL,
}).ParseFS(sub, "operator_domains.html")
if err != nil {
t.Fatalf("parse operator_domains.html: %v", err)
}
rec := httptest.NewRecorder()
NewSafeTemplates(tmpl, slog.Default()).Render(rec, "operator_domains.html", data)
if rec.Code != 200 {
t.Fatalf("render = %d: %s", rec.Code, rec.Body.String())
}
return rec.Body.String()
}
// TestOperatorDomainsForceReleaseOnlyWhereAdmissible pins the affordance to
// the registry's own guards: unplaced non-root claims get the button; placed
// claims read "in use"; operator roots read "operator root" even when they
// hold placements (roots always do — every hosted site places under one).
func TestOperatorDomainsForceReleaseOnlyWhereAdmissible(t *testing.T) {
body := renderOperatorDomains(t, OperatorDomainsData{
Claims: []OperatorDomainClaimRow{
{ClaimID: "claim-free", Root: "spare.example.org", Kind: "external",
Status: "active", Releasable: true},
{ClaimID: "claim-used", Root: "busy.example.org", Kind: "external",
Status: "active", Placements: 2, Placed: true},
{ClaimID: "claim-root", Root: "hosting.test", Kind: "operator_root",
Status: "active", Placements: 5},
},
})
if !strings.Contains(body, "/partials/operator/domains/claim-free/force-release") {
t.Error("unplaced non-root claim must offer force-release")
}
if strings.Contains(body, "/partials/operator/domains/claim-used/force-release") {
t.Error("placed claim must not offer force-release — the registry's placement guard refuses it")
}
if strings.Contains(body, "/partials/operator/domains/claim-root/force-release") {
t.Error("operator root must not offer force-release")
}
if !strings.Contains(body, ">in use<") {
t.Error("placed claim should be labeled 'in use'")
}
if !strings.Contains(body, ">operator root<") {
t.Error("operator root should keep its label even when it holds placements")
}
if got := strings.Count(body, "Force-release {{"); got != 0 {
t.Errorf("unexecuted template fragments in output: %d", got)
}
}