package chrome
import (
"html/template"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vaughan0/go-ini"
)
// testConf synthesizes the shared instance config the way the services read
// it: one section per service plus the [sr.ht] block.
func testConf() ini.File {
return ini.File{
"sr.ht": {
"site-name": "srht.example",
"environment": "production",
},
"meta.sr.ht": {"origin": "https://meta.example"},
"git.sr.ht": {"origin": "https://git.example"},
"todo.sr.ht": {"origin": "https://todo.example"},
"builds.sr.ht": {"origin": "https://builds.example"},
"hub.sr.ht": {"origin": "https://hub.example"},
"paste.sr.ht": {"origin": "https://paste.example"},
"pages.sr.ht": {"origin": "https://pages.example"},
"compare.sr.ht": {"origin": "https://compare.example"},
"dolt.sr.ht": {"origin": "https://dolt.example"},
"ghost.sr.ht": {}, // no origin -> must not appear
"webhooks": {"private-key": "x"},
}
}
func TestBuildNavOrderExclusionsActive(t *testing.T) {
nav := BuildNav(testConf(), "compare.sr.ht")
var names []string
for _, it := range nav {
names = append(names, it.Name)
}
// Canonical services first in canonical order, customs alphabetical after;
// hub/paste/pages and the origin-less section excluded.
assert.Equal(t, []string{"git", "todo", "builds", "meta", "compare", "dolt"}, names)
for _, it := range nav {
assert.Equal(t, it.Name == "compare", it.Active, "active flag for %s", it.Name)
}
}
func TestPageURLsAndIdentity(t *testing.T) {
svc := NewService(testConf(), "compare.sr.ht")
r := httptest.NewRequest("GET", "/~alice/demo?a=1", nil)
p := svc.Page(r, "t", "alice")
assert.Equal(t, "srht.example", p.SiteName)
assert.Equal(t, "compare", p.SiteLabel)
assert.Equal(t, "https://meta.example/login?return_to="+
"https%3A%2F%2Fcompare.example%2F~alice%2Fdemo%3Fa%3D1", p.LoginURL)
assert.Equal(t, "https://meta.example/logout?return_to="+
"https%3A%2F%2Fcompare.example", p.LogoutURL)
// Hub is configured, so the profile link prefers hub's ~username page.
assert.Equal(t, "https://hub.example/~alice", p.ProfileURL)
assert.Equal(t, "container", p.ContainerClass)
assert.False(t, p.ShowBanner, "production must not show the env banner")
}
func TestPageProfileFallsBackToMeta(t *testing.T) {
conf := testConf()
delete(conf, "hub.sr.ht")
svc := NewService(conf, "compare.sr.ht")
r := httptest.NewRequest("GET", "/", nil)
assert.Equal(t, "https://meta.example/profile", svc.Page(r, "t", "alice").ProfileURL)
// Anonymous viewers get the meta profile link regardless of hub.
assert.Equal(t, "https://meta.example/profile",
NewService(testConf(), "compare.sr.ht").Page(r, "t", "").ProfileURL)
}
func TestPageEnvBanner(t *testing.T) {
conf := testConf()
conf["sr.ht"]["environment"] = "staging"
svc := NewService(conf, "compare.sr.ht")
p := svc.Page(httptest.NewRequest("GET", "/", nil), "t", "")
assert.True(t, p.ShowBanner)
assert.Equal(t, "STAGING", p.Environment)
}
// render executes a minimal layout that invokes both shared partials against v.
func render(t *testing.T, v any) string {
t.Helper()
tpl := template.New("layout")
tpl = MustAttach(tpl)
tpl, err := tpl.Parse(`{{template "srht-env-banner" .}}<nav>{{template "srht-nav" .}}</nav>`)
require.NoError(t, err)
var b strings.Builder
require.NoError(t, tpl.Execute(&b, v))
return b.String()
}
func TestNavTemplateLoggedIn(t *testing.T) {
svc := NewService(testConf(), "compare.sr.ht")
svc.ExtraNav = []NavItem{{Name: "tokens", Origin: "/tokens"}}
p := svc.Page(httptest.NewRequest("GET", "/", nil), "t", "alice")
out := render(t, p)
assert.Contains(t, out, "icon icon-circle")
assert.Contains(t, out, `<span class="text-danger">compare</span>`)
assert.Contains(t, out, `href="https://git.example"`)
assert.Contains(t, out, `href="/tokens"`, "extra nav entries must render")
assert.Contains(t, out, "Logged in as")
assert.NotContains(t, out, "ENVIRONMENT")
}
func TestNavTemplateAnonymous(t *testing.T) {
svc := NewService(testConf(), "compare.sr.ht")
p := svc.Page(httptest.NewRequest("GET", "/", nil), "t", "")
out := render(t, p)
// The switcher renders only for authenticated viewers.
assert.NotContains(t, out, `href="https://git.example"`)
assert.Contains(t, out, "Log in")
assert.Contains(t, out, "Register")
}
// TestNavTemplateEmbeddedPage guards the documented consumption pattern: a
// service view struct embedding Page resolves the promoted fields.
func TestNavTemplateEmbeddedPage(t *testing.T) {
type viewData struct {
Page
Data any
}
svc := NewService(testConf(), "dolt.sr.ht")
v := viewData{Page: svc.Page(httptest.NewRequest("GET", "/", nil), "t", "alice")}
out := render(t, v)
assert.Contains(t, out, `<span class="text-danger">dolt</span>`)
assert.Contains(t, out, "Logged in as")
}
func TestFuncs(t *testing.T) {
assert.Equal(t, "12345678", ShortSHA("1234567890abcdef"))
assert.Equal(t, "abc", ShortSHA("abc"))
m, err := Dict("a", 1, "b", "x")
require.NoError(t, err)
assert.Equal(t, map[string]any{"a": 1, "b": "x"}, m)
_, err = Dict("a")
require.Error(t, err)
_, err = Dict(1, "v")
require.Error(t, err)
}