package handacts import ( "encoding/json" "errors" "reflect" "strings" "testing" ) // ADR 0565 clauses 10 and 11 (issue 1953 L5-S2, S4): an act reads the controller's entries unchanged, and a recorder // records through the seat, through the controller's verb only while the seat has no holder and the lease is the // controller's, saying which log took it, and refuses to record nowhere. func TestAnActIsRecordedInTheLogThatAnswers(t *testing.T) { // The shape of mesh-controller internal/link HandAct, as its bucket keeps an entry (handacts.go at 0fbb5ef3). raw := `{"id":"act-1","at":"2026-10-28T09:00:00Z","by":"person.jochen","verb":"hand-act record","why":"a stuck consumer",` + `"cause":"consumer-lost","condition":"bus.EVENTS.x.consumer-lost","via":"telegram","ask":"a-1","proofs":["P1"],` + `"requested-by":"bus.EVENTS.x.consumer-lost","outcome":"done"}` var a Act if err := json.Unmarshal([]byte(raw), &a); err != nil { t.Fatal(err) } again, _ := json.Marshal(a) var want, got map[string]any _ = json.Unmarshal([]byte(raw), &want) _ = json.Unmarshal(again, &got) if !reflect.DeepEqual(want, got) || !a.Repair() || (Act{Cause: CauseDrill}).Repair() || (Act{Verb: VerbDrill}).Repair() { t.Errorf("the controller's entry reads back as %s", again) } noResponders := errors.New("nats: no responders available for request") for _, c := range []struct { name string seat error ctrl error away bool log string refused string asked string warrants bool }{ {name: "the seat answers", log: LogSeat, asked: "seat:mesh-hand-acts.hand-act"}, {name: "no holder yet", seat: noResponders, log: LogController, asked: "seat:mesh-hand-acts.hand-act,seat:mesh-controller.hand-act"}, {name: "no holder, the lease handed away", seat: noResponders, away: true, refused: "handed its lease away", asked: "seat:mesh-hand-acts.hand-act"}, {name: "the seat refuses", seat: errors.New("why is required"), refused: "refused", asked: "seat:mesh-hand-acts.hand-act"}, {name: "neither answers", seat: noResponders, ctrl: noResponders, refused: "refused too", asked: "seat:mesh-hand-acts.hand-act,seat:mesh-controller.hand-act"}, {name: "a warrant without a holder", seat: noResponders, warrants: true, log: LogController, asked: "seat:mesh-hand-acts.warranted,seat:mesh-controller.warranted"}, } { var asked []string r := Recorder{LeaseAway: func() bool { return c.away }, Ask: func(key string, body any) (json.RawMessage, error) { asked = append(asked, key) if strings.HasPrefix(key, "seat:mesh-hand-acts.") { return json.RawMessage(`{}`), c.seat } return json.RawMessage(`{}`), c.ctrl }} var taken Taken var err error if c.warrants { taken, err = r.RecordWarrant("mesh-config", "ask-1") } else { taken, err = r.Record(Request{What: "silenced", Why: "known", Cause: "silent", Verb: "conditions silence"}) } if (c.refused != "") != (err != nil) || (err != nil && !strings.Contains(err.Error(), c.refused)) || taken.Log != c.log || strings.Join(asked, ",") != c.asked { t.Errorf("%s: took %q, %v, asked %v", c.name, taken.Log, err, asked) } } if _, err := (Recorder{}).Record(Request{What: "x"}); err == nil { t.Error("an act with no why was recorded") } }