package main import ( "context" "github.com/novox/mesh-controller/internal/link" "strings" "testing" "time" "git.novox.be/novox/mesh-sdk/go/asks" ) // A rehearsal (the live acceptance of novox/hq ADR 0259): its approval is a warrant like any other โ€” claimed once, // its act checked against what the option bound, recorded as the operator's decision with who, how and the // proofs โ€” and it performs nothing. The reconciling of conditions leaves it open. func TestARehearsalsApprovalIsRecordedAndPerformsNothing(t *testing.T) { r := newAskerRig(t) q, options := rehearsalAsk("crehearsal", r.now, askerRehearsalFor) if err := q.Check(r.now); err != nil { t.Fatalf("the rehearsal's ask is refused: %v", err) } r.store["crehearsal"] = asked{ID: "crehearsal", Condition: q.About, Ask: q, Actions: rehearsalActions(), Options: options, State: askOpen, Opened: r.now, Rehearsal: true} if err := r.a.reconcile(context.Background()); err != nil { t.Fatal(err) } if got := r.store["crehearsal"]; got.State != askOpen { t.Fatalf("the reconciling of conditions ended the rehearsal: %+v", got) } approve, _ := q.Option("approve") w := asks.Warrant{Ask: "crehearsal", Asker: "mesh-controller", Outcome: asks.OutcomeChosen, Option: approve.ID, Label: approve.Label, Level: approve.Level, Channel: "telegram", Proofs: []string{"P1"}, At: r.now, AskDigest: q.Digest(), By: &asks.Person{Who: asks.Operator, Kind: "telegram", Identity: "42", Verified: "user id verified"}} answerWith(t, r, w) answerWith(t, r, w) // heard again if len(r.called)+len(r.silenced) != 0 { t.Errorf("a rehearsal performed something: %v %v", r.called, r.silenced) } if len(r.acts) != 1 { t.Fatalf("hand-acts %+v", r.acts) } act := r.acts[0] if act.Verb != handActWarrant || act.By != "the operator, as telegram identity 42" || act.Ask != "crehearsal" || strings.Join(act.Args, " ") != "rehearsal rehearsal=approve" || act.Outcome != "done" || strings.Join(act.Proofs, ",") != "P1" { t.Errorf("the rehearsal's record: %+v", act) } if !personsDecision(act) { t.Error("a rehearsal's answer counts as a repair") } } // Only the terminal starts a rehearsal: a verb's process is refused before anything is asked. func TestARehearsalIsTheTerminalsAlone(t *testing.T) { t.Setenv(verbVar, "mesh-controller.command") if err := rehearseCommand(context.Background(), nil); err == nil || !strings.Contains(err.Error(), "terminal") { t.Fatalf("a verb started a rehearsal: %v", err) } // Nor a mesh-cli line from anybody but the control-node's operator (hq ADR 0272 ยง4): run without a verb, // naming its caller, and without the terminal's mark โ€” and nor anything the serving controller started. for name, env := range map[string]map[string]string{ "an ordinary mesh-cli line": {verbVar: "", link.CallerVar: "laptop/agent"}, "a process the serving controller ran": {verbVar: "", servedVar: "1"}, } { t.Run(name, func(t *testing.T) { for k, v := range env { t.Setenv(k, v) } if err := rehearseCommand(context.Background(), nil); err == nil || !strings.Contains(err.Error(), "terminal") { t.Fatalf("%s started a rehearsal: %v", name, err) } }) } } // askerRehearsalFor is how long the test's rehearsal waits. const askerRehearsalFor = 15 * time.Minute