package web
import (
"strings"
"testing"
"craftdesign.group/quarry/internal/evt"
)
func renderSettings(t *testing.T, d pageData) string {
t.Helper()
var sb strings.Builder
if err := noHooks.template(settingsPage).Execute(&sb, d); err != nil {
t.Fatalf("render: %v", err)
}
return sb.String()
}
// The section is gated on the PLANE, not on the list. A repository with
// no subscriptions must still show the form that creates the first one —
// gating on the list is how a settings screen becomes impossible to use.
func TestWebhookFormRendersWithNoSubscriptions(t *testing.T) {
got := renderSettings(t, pageData{Repo: "alpha", HasHooks: true})
if !strings.Contains(got, `action="/r/alpha/settings/hooks"`) {
t.Error("no webhook form on a repository with zero subscriptions")
}
if !strings.Contains(got, `value="add"`) {
t.Error("no add action")
}
}
// No store attached is not the same fact as no subscriptions, and only
// one of them invites a click.
func TestWebhookSectionAbsentWithoutAStore(t *testing.T) {
got := renderSettings(t, pageData{Repo: "alpha"})
if strings.Contains(got, "/settings/hooks") {
t.Error("drew a webhook form with no store behind it")
}
}
func TestWebhookRowsCarryTheirActions(t *testing.T) {
got := renderSettings(t, pageData{Repo: "alpha", HasHooks: true, Hooks: []HookSub{
{ID: 7, URL: "https://example.test/h", Events: "ticket", Active: true},
{ID: 8, URL: "https://example.test/j", Events: "*", Active: false},
}})
for _, want := range []string{
`value="7"`, `value="8"`,
`value="ticket"`, // the selection is editable in place
`value="pause"`, // an active subscription offers pause
`value="resume"`, // a paused one offers resume
`value="delete"`,
`value="events"`,
} {
if !strings.Contains(got, want) {
t.Errorf("missing %s", want)
}
}
}
// The secret exists in exactly one render and is never stored readable,
// so the one render has to actually show it.
func TestNewSecretShownOnce(t *testing.T) {
got := renderSettings(t, pageData{Repo: "alpha", HasHooks: true, NewHookSecret: "deadbeef"})
if !strings.Contains(got, "deadbeef") {
t.Error("a freshly minted secret was not shown")
}
if strings.Contains(renderSettings(t, pageData{Repo: "alpha", HasHooks: true}), "deadbeef") {
t.Error("the secret survived into a later render")
}
}
func TestCheckPatternsRefusesWhatCannotMatch(t *testing.T) {
for _, ok := range []string{"", "*", "ticket", "ticket.closed", "ticket landing.accepted"} {
if err := evt.CheckPatterns(ok); err != nil {
t.Errorf("evt.CheckPatterns(%q) = %v, want nil", ok, err)
}
}
// "tickets" is the typo that would otherwise deliver nothing and
// read as a broken forge.
for _, bad := range []string{"tickets", "ticket.exploded", "landing.merged"} {
if err := evt.CheckPatterns(bad); err == nil {
t.Errorf("evt.CheckPatterns(%q) accepted a pattern no event matches", bad)
}
}
}