package web
import (
"strings"
"testing"
"git.fabledsword.com/bvandeusen/minstrel/internal/config"
)
const sampleIndexHTML = `
{{ .AppName }}
`
func TestApplyBrandingTemplate_Defaults(t *testing.T) {
out, err := applyBrandingTemplate(sampleIndexHTML, config.BrandingConfig{
AppName: "Minstrel",
Description: "Self-hosted music server.",
})
if err != nil {
t.Fatalf("apply: %v", err)
}
body := string(out)
if !strings.Contains(body, "Minstrel") {
t.Errorf("missing default title; body=%s", body)
}
}
func TestApplyBrandingTemplate_CustomBranding(t *testing.T) {
out, err := applyBrandingTemplate(sampleIndexHTML, config.BrandingConfig{
AppName: "Family Jukebox",
Description: "Our home library.",
})
if err != nil {
t.Fatalf("apply: %v", err)
}
body := string(out)
if !strings.Contains(body, "Family Jukebox") {
t.Errorf("custom AppName not in title; body=%s", body)
}
if !strings.Contains(body, `og:title" content="Family Jukebox"`) {
t.Errorf("custom AppName not in og:title; body=%s", body)
}
if !strings.Contains(body, `appName: "Family Jukebox"`) {
t.Errorf("custom AppName not in inline window.__MINSTREL__; body=%s", body)
}
}
func TestApplyBrandingTemplate_XSSEscape_ScriptContext(t *testing.T) {
// Operator config is trusted, but defensive escaping is still required:
// an operator who doesn't realize their value is interpolated into HTML
// shouldn't be able to accidentally break out of contexts.
out, err := applyBrandingTemplate(sampleIndexHTML, config.BrandingConfig{
AppName: "
",
Description: "ok",
})
if err != nil {
t.Fatalf("apply: %v", err)
}
body := string(out)
// With html/template escaping intact, the inline-script block has
// exactly one . If escaping silently regressed (e.g. someone
// swapped html/template for text/template), the AppName payload would
// inject a second one and break out of the JS-string context.
if got := strings.Count(body, ""); got != 1 {
t.Errorf("expected exactly one in body (escaping intact), got %d; body=%q", got, body)
}
// And the JS object literal's closing `};` should still live inside
// the script block — i.e. the block didn't get truncated by the payload.
scriptOpen := strings.Index(body, "")
if scriptClose < 0 {
t.Fatalf("inline script block has no close; body=%q", body)
}
scriptBody := body[scriptOpen : scriptOpen+scriptClose]
if !strings.Contains(scriptBody, "};") {
t.Errorf("inline script block truncated before object literal closed: %q", scriptBody)
}
}
func TestApplyBrandingTemplate_XSSEscape_AttrContext(t *testing.T) {
out, err := applyBrandingTemplate(sampleIndexHTML, config.BrandingConfig{
AppName: `">
`,
Description: "ok",
})
if err != nil {
t.Fatalf("apply: %v", err)
}
body := string(out)
// The unescaped HTML should NOT appear as a live tag in attribute context.
if strings.Contains(body, `">
`) {
t.Errorf("unescaped attribute breakout: %s", body)
}
}