package web
import (
"strings"
"testing"
)
// RFC 0028: requests live on a page of their own. Waiting ones answer
// with two POSTed buttons that return to the list (never a link, so
// nothing runs from a GET); decided ones fold into History, filterable
// by agent; and /me no longer draws them.
func TestRequestsHaveAPageOfTheirOwn(t *testing.T) {
render := func(v requestsView) string {
var b strings.Builder
if err := noHooks.template(requestsPageTmpl).ExecuteTemplate(&b, "content", pageData{Title: "Requests", Requests: &v}); err != nil {
t.Fatal(err)
}
return b.String()
}
waiting := render(requestsView{Tab: "waiting",
Waiting: []meSignOff{{ID: "abc", Agent: "zeitgeist", Summary: "agent.new for rajeev", Reason: "a QA tester", State: "pending", Requested: "2026-11-08T10:00:00Z"}}})
for what, want := range map[string]string{
"approve posts back to the list": `<form method="post" action="/me/signoffs/abc"><input type="hidden" name="approve" value="true"><input type="hidden" name="back" value="list">`,
"deny posts back to the list": `<form method="post" action="/me/signoffs/abc"><input type="hidden" name="approve" value="false"><input type="hidden" name="back" value="list">`,
"each row opens its request": `href="/me/requests/abc"`,
"the waiting tab is current": `data-hook="requests-tab-waiting">`,
} {
if !strings.Contains(waiting, want) {
t.Errorf("FAIL: %s (%q)", what, want)
}
}
history := render(requestsView{Tab: "history", Agent: "zeitgeist", Agents: []string{"zeitgeist", "jva-bot"},
History: []meSignOff{{ID: "def", Agent: "zeitgeist", Summary: "x", State: "denied", Requested: "2026-11-07T10:00:00Z"}}})
if !strings.Contains(history, `data-hook="request" data-state="denied"`) || strings.Contains(history, `action="/me/signoffs/def"`) {
t.Error("FAIL: a decided request is not in History, or can still be answered")
}
if !strings.Contains(history, `agent=jva-bot`) {
t.Error("FAIL: History cannot be narrowed to one agent")
}
if empty := render(requestsView{Tab: "waiting"}); !strings.Contains(empty, `data-hook="requests-none"`) {
t.Error("FAIL: an empty Waiting tab says nothing")
}
var me strings.Builder
if err := noHooks.template(mePageTmpl).ExecuteTemplate(&me, "content", pageData{Title: "ann", User: "ann", Me: &meView{Name: "ann", Notify: &prefsView{}}}); err != nil {
t.Fatal(err)
}
if strings.Contains(me.String(), `id="signoffs"`) {
t.Error("FAIL: /me still draws a sign-off section")
}
if h := requestsBadgeHTML(3); !strings.Contains(h, `id="requests-btn"`) || !strings.Contains(h, `data-count="3"`) || !strings.Contains(h, `href="/me/requests"`) {
t.Errorf("FAIL: the bar's badge: %s", h)
}
}
// RFC 0027: the page an email links to shows one request, who asks and
// why, and answers it with two posted buttons that return to it; a
// decided request has no buttons left.
func TestASignOffHasAPageOfItsOwn(t *testing.T) {
render := func(s meSignOff) string {
var b strings.Builder
if err := noHooks.template(signOffPageTmpl).ExecuteTemplate(&b, "content", pageData{Title: "Approve?", SignOff: &s}); err != nil {
t.Fatal(err)
}
return b.String()
}
got := render(meSignOff{ID: "abc", Agent: "zeitgeist", Summary: "agent.new for rajeev", Reason: "a QA tester", State: "pending", Requested: "2026-11-08T10:00:00Z"})
for what, want := range map[string]string{
"who asks": `data-hook="signoff-agent">zeitgeist<`,
"what runs": `data-hook="signoff-summary">agent.new for rajeev<`,
"why": `data-hook="signoff-reason"`,
"approve posts": `<form method="post" action="/me/signoffs/abc"><input type="hidden" name="approve" value="true"><input type="hidden" name="back" value="page">`,
"deny posts": `<form method="post" action="/me/signoffs/abc"><input type="hidden" name="approve" value="false"><input type="hidden" name="back" value="page">`,
"its state is read": `data-hook="signoff-page" data-state="pending"`,
} {
if !strings.Contains(got, want) {
t.Errorf("FAIL: %s (%q)", what, want)
}
}
if done := render(meSignOff{ID: "abc", Agent: "zeitgeist", Summary: "x", State: "approved", Requested: "2026-11-08T10:00:00Z"}); strings.Contains(done, "<form") {
t.Errorf("FAIL: an approved request can still be answered")
}
}