Introduce a commercial license option alongside AGPL-3.0-only, require a CLA for contributors, and document the terms in COMMERCIAL.md and NOTICE. Add a script to stamp SPDX headers on Go files and apply it across the tree.
114 lines
3.9 KiB
Go
114 lines
3.9 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
package server
|
|
|
|
import (
|
|
"html/template"
|
|
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/web"
|
|
"io/fs"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/embeds"
|
|
"log/slog"
|
|
)
|
|
|
|
func renderEntitlements(t *testing.T, data EntitlementsData) string {
|
|
t.Helper()
|
|
sub, err := fs.Sub(embeds.Templates, "templates")
|
|
if err != nil {
|
|
t.Fatalf("sub templates FS: %v", err)
|
|
}
|
|
tmpl, err := template.New("root").ParseFS(sub, "partials/member_entitlements.html")
|
|
if err != nil {
|
|
t.Fatalf("parse member_entitlements.html: %v", err)
|
|
}
|
|
tmpl = template.Must(web.ParseUIPartials(tmpl))
|
|
rec := httptest.NewRecorder()
|
|
NewSafeTemplates(tmpl, slog.Default()).Render(rec, "member_entitlements.html", data)
|
|
if rec.Code != 200 {
|
|
t.Fatalf("render = %d, body: %s", rec.Code, rec.Body.String())
|
|
}
|
|
return rec.Body.String()
|
|
}
|
|
|
|
// Resource rows lead with the human label and integration attribution;
|
|
// the raw key is demoted to a title attribute. Two integrations'
|
|
// entitlements stay distinguishable by their attributions.
|
|
func TestEntitlementRowsLabeledAndAttributed(t *testing.T) {
|
|
body := renderEntitlements(t, EntitlementsData{
|
|
HasEntitlements: true,
|
|
Entitlements: []EntitlementViewModel{
|
|
{ResourceKey: "alpha_sites", Label: "Wiki Sites", IntegrationName: "Alpha", ResourceLimit: 2, CurrentUsage: 1},
|
|
{ResourceKey: "beta_widgets", Label: "Widgets", IntegrationName: "Beta", ResourceLimit: 5, CurrentUsage: 0},
|
|
},
|
|
})
|
|
|
|
for _, want := range []string{
|
|
`title="alpha_sites"`, ">Wiki Sites", "Alpha",
|
|
`title="beta_widgets"`, ">Widgets", "Beta",
|
|
} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("rendered entitlements missing %q", want)
|
|
}
|
|
}
|
|
// The raw key appears only as the demoted title attribute, never as
|
|
// headline text content.
|
|
if strings.Contains(body, ">alpha_sites<") {
|
|
t.Errorf("raw resource key rendered as headline text")
|
|
}
|
|
}
|
|
|
|
// A key with no display metadata falls back to the raw key, un-attributed
|
|
// (labelFor's contract) — and the view still renders.
|
|
func TestEntitlementLabelFallback(t *testing.T) {
|
|
got := labelFor(map[string]resourceLabel{}, "mystery_key")
|
|
if got.Label != "mystery_key" || got.IntegrationName != "" {
|
|
t.Errorf("labelFor fallback = %+v, want raw key un-attributed", got)
|
|
}
|
|
|
|
// Present but empty display name also falls back rather than
|
|
// rendering a blank headline.
|
|
got = labelFor(map[string]resourceLabel{"k": {Label: ""}}, "k")
|
|
if got.Label != "k" {
|
|
t.Errorf("empty display name should fall back to raw key, got %+v", got)
|
|
}
|
|
|
|
body := renderEntitlements(t, EntitlementsData{
|
|
HasEntitlements: true,
|
|
Entitlements: []EntitlementViewModel{{ResourceKey: "mystery_key", Label: "mystery_key", ResourceLimit: 1}},
|
|
})
|
|
if !strings.Contains(body, ">mystery_key") {
|
|
t.Errorf("fallback raw key not rendered")
|
|
}
|
|
}
|
|
|
|
// Granted booleans render as labeled "Included" rows without a usage
|
|
// meter; with no granted booleans, no Included badge appears (the handler
|
|
// filters lapsed rows server-side, so the template only ever sees granted
|
|
// ones).
|
|
func TestBooleanEntitlementRows(t *testing.T) {
|
|
body := renderEntitlements(t, EntitlementsData{
|
|
HasEntitlements: true,
|
|
BooleanEntitlements: []BooleanEntitlementViewModel{
|
|
{ResourceKey: "gamma_forum_access", Label: "Forum Access", IntegrationName: "Gamma"},
|
|
},
|
|
})
|
|
for _, want := range []string{">Forum Access", "Gamma", ">Included<", `title="gamma_forum_access"`} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("boolean row missing %q", want)
|
|
}
|
|
}
|
|
if strings.Contains(body, "<meter") {
|
|
t.Errorf("boolean-only view must not render a usage meter")
|
|
}
|
|
|
|
empty := renderEntitlements(t, EntitlementsData{HasEntitlements: true})
|
|
if strings.Contains(empty, ">Included<") {
|
|
t.Errorf("Included badge rendered with no granted booleans")
|
|
}
|
|
}
|