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" .}}`) 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, `compare`) 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, `dolt`) assert.Contains(t, out, "Logged in as") } // renderList executes the srht-repo-list partial alone against v. func renderList(t *testing.T, v RepoList) string { t.Helper() tpl := MustAttach(template.New("t")) tpl, err := tpl.Parse(`{{template "srht-repo-list" .}}`) require.NoError(t, err) var b strings.Builder require.NoError(t, tpl.Execute(&b, v)) return b.String() } func TestRepoListCards(t *testing.T) { out := renderList(t, RepoList{Items: []ListItem{ {Href: "/~a/pub", Title: "~a/pub", Visibility: "PUBLIC", Description: "described"}, {Href: "/~a/unl", Title: "~a/unl", Visibility: "UNLISTED"}, {Href: "/~a/prv", Title: "~a/prv", Visibility: "PRIVATE"}, {Href: "/~a/none", Title: "~a/none"}, // service without visibility (spec spaces) }}) assert.Equal(t, 4, strings.Count(out, `class="event"`)) assert.Contains(t, out, "

described

") // PUBLIC and empty visibility render no label; the others render lowercase. assert.Equal(t, 1, strings.Count(out, ">unlisted")) assert.Equal(t, 1, strings.Count(out, ">private")) assert.Equal(t, 2, strings.Count(out, "pull-right")) } func TestRepoListEmptyState(t *testing.T) { out := renderList(t, RepoList{Empty: "No databases yet."}) assert.NotContains(t, out, "event-list") assert.Contains(t, out, "No databases yet.") } 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) }