Files
so-many-crows/askowner_test.go
T

142 lines
4.6 KiB
Go

package main
import (
"net/http"
"net/http/httptest"
"testing"
"time"
)
// askNest posts a standard ask_owner from Quill and returns the nest id and
// the stored record. Quill is an owner-facing service role, so this is the
// shape the spec (CHATROOM_SPEC section 4/6) calls for.
func askNest(t *testing.T, ts *httptest.Server) (string, map[string]any) {
t.Helper()
id := newNest(t, ts, "Lab", `"Hugin","Munin"`)
code, ask := postMsg(t, ts, id, `{"author":"Quill","kind":"ask_owner","to":"owner",`+
`"text":"Soot wrote fiction on a retrieval job and is quarantined. Reply with the phrase to reinstate the crow.",`+
`"approval_phrase":"reinstate Hugin"}`)
if code != http.StatusCreated {
t.Fatalf("ask_owner post: want 201, got %d %v", code, ask)
}
return id, ask
}
// askResolved reads the nest log and returns the ask's computed resolved field.
func askResolved(t *testing.T, ts *httptest.Server, nestID, askID string) string {
t.Helper()
for _, m := range nestMessages(t, ts, nestID) {
if m["msg_id"] == askID {
r, ok := m["resolved"].(string)
if !ok {
t.Fatalf("ask record has no string resolved field: %v", m)
}
return r
}
}
t.Fatalf("ask %s not in the nest log", askID)
return ""
}
func TestAskOwnerPostedAndSurfaced(t *testing.T) {
ts, _ := newTestNestServer(t)
id, ask := askNest(t, ts)
if ask["kind"] != "ask_owner" || ask["author"] != "Quill" || ask["to"] != "owner" {
t.Fatalf("bad ask record: %v", ask)
}
if ask["approval_phrase"] != "reinstate Hugin" {
t.Fatalf("POST record must carry approval_phrase, got %v", ask["approval_phrase"])
}
if r := ask["resolved"]; r != "" {
t.Fatalf("a fresh ask must read unresolved, got %v", r)
}
for _, m := range nestMessages(t, ts, id) {
if m["msg_id"] == ask["msg_id"] {
if m["approval_phrase"] != "reinstate Hugin" {
t.Fatalf("GET must surface approval_phrase, got %v", m)
}
return
}
}
t.Fatalf("ask not in GET log")
}
func TestAskOwnerOnlyOwnerResolves(t *testing.T) {
ts, _ := newTestNestServer(t)
id, ask := askNest(t, ts)
askID := ask["msg_id"].(string)
code, reply := postMsg(t, ts, id, `{"author":"Hugin","kind":"say","refers_to":"`+askID+`","text":"reinstate Hugin"}`)
if code != http.StatusCreated {
t.Fatalf("crow reply: %d %v", code, reply)
}
if r := askResolved(t, ts, id, askID); r == "approved" {
t.Fatalf("a crow's reply must never approve an ask_owner, got resolved=%v", r)
}
code, yes := postMsg(t, ts, id, `{"author":"owner","kind":"say","refers_to":"`+askID+`","text":"Reinstate Hugin"}`)
if code != http.StatusCreated {
t.Fatalf("owner yes: %d %v", code, yes)
}
if r := askResolved(t, ts, id, askID); r != "approved" {
t.Fatalf("the owner's exact phrase must approve (case-insensitive), got resolved=%v", r)
}
found := false
for _, m := range nestMessages(t, ts, id) {
if m["author"] == "owner" && m["refers_to"] == askID {
found = true
}
}
if !found {
t.Fatalf("the owner's reply message must be in the log")
}
}
func TestAskOwnerWrongPhraseStaysOpen(t *testing.T) {
ts, _ := newTestNestServer(t)
id, ask := askNest(t, ts)
askID := ask["msg_id"].(string)
code, out := postMsg(t, ts, id, `{"author":"owner","kind":"say","refers_to":"`+askID+`","text":"sounds good"}`)
if code != http.StatusCreated {
t.Fatalf("owner reply: %d %v", code, out)
}
if r := askResolved(t, ts, id, askID); r != "" {
t.Fatalf("anything but the exact phrase is not a yes, got resolved=%v", r)
}
}
func TestAskOwnerTimeoutFailSafe(t *testing.T) {
ts, s := newTestNestServer(t)
id := newNest(t, ts, "Lab", `"Hugin"`)
stale, err := s.msgs.Append(Message{
NestID: id,
Kind: "ask_owner",
Author: "Sentry",
Text: "Inky keeps drifting off task. Retire the crow?",
To: "owner",
ApprovalPhrase: "retire inky",
TaggedBy: "header",
Timestamp: time.Now().UTC().Add(-time.Hour).Format(time.RFC3339),
})
if err != nil {
t.Fatal(err)
}
s.askTimeout.Store(int64(time.Minute))
if r := askResolved(t, ts, id, stale.MsgID()); r != "rejected" {
t.Fatalf("an ask unanswered past the window is not a yes, got resolved=%v", r)
}
}
func TestAskOwnerCrowHeaderRetagged(t *testing.T) {
ts, _ := newTestNestServer(t)
id := newNest(t, ts, "Lab", `"Hugin"`)
code, out := postMsg(t, ts, id, `{"author":"Hugin","kind":"ask_owner","to":"owner","text":"may I retire the old flyer template?"}`)
if code != http.StatusCreated {
t.Fatalf("crow ask_owner header: want 201 with a retag, got %d %v", code, out)
}
if out["kind"] != "say" {
t.Fatalf("crows may not post ask_owner: the header must drop to the default tag, got kind=%v", out["kind"])
}
}