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")
}
}
// A call an agent ran on its own right is a row of its own kind: the
// page says so with a hook of its own, never reads as an approval, has
// no buttons, and the history chip says it too.
func TestAParkedCallIsNotShownAsAnApproval(t *testing.T) {
var b strings.Builder
s := meSignOff{ID: "abc", Agent: "jva-bot", Summary: "x", State: "ran", Requested: "2026-11-08T10:00:00Z",
Pickup: strings.Repeat("ab", 32)}
if err := noHooks.template(signOffPageTmpl).ExecuteTemplate(&b, "content", pageData{Title: "Ran", SignOff: &s}); err != nil {
t.Fatal(err)
}
got := b.String()
for what, want := range map[string]string{
"the parked hook": `data-hook="signoff-parked"`,
"its state": `data-state="ran"`,
"the pickup line": `data-hook="signoff-pickup"`,
"not collected": `data-hook="signoff-uncollected"`,
} {
if !strings.Contains(got, want) {
t.Errorf("FAIL: %s (%q)", what, want)
}
}
if strings.Contains(got, "<form") || strings.Contains(got, "approved") {
t.Error("FAIL: a parked row reads as an approval or can be answered")
}
s.Collected = "2026-11-08T11:00:00Z"
b.Reset()
if err := noHooks.template(signOffPageTmpl).ExecuteTemplate(&b, "content", pageData{Title: "Ran", SignOff: &s}); err != nil || !strings.Contains(b.String(), `data-hook="signoff-collected"`) {
t.Errorf("FAIL: a collected row says so: %v", err)
}
var h strings.Builder
v := requestsView{Tab: "history", History: []meSignOff{s}}
if err := noHooks.template(requestsPageTmpl).ExecuteTemplate(&h, "content", pageData{Title: "Requests", Requests: &v}); err != nil || !strings.Contains(h.String(), `data-state="ran"`) || strings.Contains(h.String(), ">denied<") {
t.Errorf("FAIL: the history row: %v", err)
}
if !t.Failed() {
t.Log("ok")
}
}