package api
import (
"encoding/json"
"fmt"
"strings"
"testing"
)
// decidedTo is the signoff.decided events held for account.
func (g *setupRig) decidedTo(account string) []AttentionEvent {
var out []AttentionEvent
for _, e := range g.attn.to(account) {
if e.Kind == "signoff.decided" {
out = append(out, e)
}
}
return out
}
// The asking agent is told, once, on its own attention stream, when its
// request is decided; the id, the op, the repository and the new state
// ride on it, and nobody else is told.
func TestTheAskerIsToldOnceWhenItsRequestIsDecided(t *testing.T) {
for _, tc := range []struct {
name string
approve bool
state string
}{{"approved", true, "approved"}, {"denied", false, "denied"}} {
t.Run(tc.name, func(t *testing.T) {
g := newSetupRig(t)
id := g.ask("zeitgeist", pushBody, "")
if n := len(g.decidedTo("zeitgeist")); n != 0 {
t.Fatalf("FAIL: told %d times before any decision", n)
}
if err := g.decide("dmestas", id, tc.approve); err != nil {
t.Fatal(err)
}
got := g.decidedTo("zeitgeist")
if len(got) != 1 {
t.Fatalf("FAIL: the asker got %d signoff.decided events", len(got))
}
e := got[0]
if e.Repo != "" || e.Link != "/api/v1/signoffs/"+id || e.Summary != "agent.setup "+tc.state {
t.Errorf("FAIL: the event: %+v", e)
}
for _, other := range []string{"dmestas", "jva", "dmestas-bot", "jva-bot", "suit-ci", ""} {
if n := len(g.decidedTo(other)); n != 0 {
t.Errorf("FAIL: %q got %d signoff.decided events", other, n)
}
}
if !t.Failed() {
t.Log("ok")
}
})
}
}
// A request whose approval ran and failed tells the asker it failed.
func TestTheAskerIsToldWhenAnApprovedCallFailed(t *testing.T) {
g := newSetupRig(t)
id := g.ask("zeitgeist", pushBody, "")
if err := g.db.AddAccount("suit-push", "a-long-enough-password", false); err != nil {
t.Fatal(err)
}
if err := g.decide("dmestas", id, true); err == nil {
t.Fatalf("FAIL: approving a request whose name was taken succeeded")
}
got := g.decidedTo("zeitgeist")
if len(got) != 1 || got[0].Summary != "agent.setup failed" {
t.Fatalf("FAIL: the asker's events: %+v", got)
}
if !t.Failed() {
t.Log("ok")
}
}
// Nothing a pickup or a minted token holds rides the event.
func TestTheDecidedEventCarriesNoSecret(t *testing.T) {
g := newSetupRig(t)
id := g.ask("zeitgeist", pushBody, "")
if err := g.decide("dmestas", id, true); err != nil {
t.Fatal(err)
}
var sec struct{ Value string }
if err := json.Unmarshal(g.get("zeitgeist", id).Secret, &sec); err != nil || !strings.HasPrefix(sec.Value, "qat_") {
t.Fatalf("FAIL: the asker's first read lacks the value")
}
got := g.decidedTo("zeitgeist")
if len(got) != 1 {
t.Fatalf("FAIL: %d events", len(got))
}
raw, _ := json.Marshal(got[0])
if strings.Contains(string(raw), sec.Value) || strings.Contains(string(raw), "qat_") {
t.Errorf("FAIL: the event carries the credential")
}
// A request bound to a pickup code: neither the public code nor the
// key it hashes from rides the event.
key, code := newPickupKey(t)
pid := g.ask("dmestas-bot", `{"name":"e-p","kind":"read","repos":"suit"}`, code)
if err := g.decide("dmestas", pid, true); err != nil {
t.Fatal(err)
}
pe := g.decidedTo("dmestas-bot")
if len(pe) != 1 {
t.Fatalf("FAIL: %d events for the pickup-bound request", len(pe))
}
raw, _ = json.Marshal(pe[0])
if strings.Contains(string(raw), code) || strings.Contains(string(raw), fmt.Sprint(key)) {
t.Errorf("FAIL: the event carries the pickup code")
}
if !t.Failed() {
t.Log("ok")
}
}
// A broker that is down changes nothing about the decision.
func TestAnEmitFailureDoesNotChangeTheDecision(t *testing.T) {
g := newSetupRig(t)
id := g.ask("zeitgeist", pushBody, "")
g.attnDown = true
if err := g.decide("dmestas", id, true); err != nil {
t.Fatalf("FAIL: the decision failed with the broker down: %v", err)
}
got := g.get("zeitgeist", id)
if got.Request.State != "approved" || got.Setup == nil || !got.Setup.Done {
t.Errorf("FAIL: the request: %+v", got.Request)
}
if _, ok := g.db.AccountByName("suit-push"); !ok {
t.Errorf("FAIL: the agent was not made")
}
if n := len(g.decidedTo("zeitgeist")); n != 0 {
t.Errorf("FAIL: an event arrived through a down broker")
}
if !t.Failed() {
t.Log("ok")
}
}