package enginereads import ( "context" "encoding/json" "strings" "testing" "time" ) // The answer and the event are judged as the contract lays them out and its reader checks: the layout's version, a node // that is a name, no empty capability, a time, and an event's why one of the two. func TestTheAnswerAndTheEventAreJudged(t *testing.T) { const at = "2026-11-05T12:00:00Z" for name, c := range map[string]struct{ body, bad string }{ "whole": {`{"v":1,"node":"ace","manifest-reads":["mesh-config.resolved"],"stated-at":"` + at + `"}`, ""}, "old layout": {`{"v":2,"node":"ace","stated-at":"` + at + `"}`, "version"}, "bad node": {`{"v":1,"node":"a.b","stated-at":"` + at + `"}`, "no node"}, "empty read": {`{"v":1,"node":"ace","manifest-reads":[" "],"stated-at":"` + at + `"}`, "empty reader"}, "no time": {`{"v":1,"node":"ace","stated-at":"yesterday"}`, "RFC 3339"}, } { _, _, err := DecodeAnswer([]byte(c.body)) if (c.bad == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), c.bad)) { t.Errorf("answer %s: %v, want %q", name, err, c.bad) } } for name, c := range map[string]struct{ body, bad string }{ "whole": {`{"v":1,"node":"ace","why":"started","manifest-reads":["a.b"],"at":"` + at + `"}`, ""}, "returned": {`{"v":1,"node":"ace","why":"returned","at":"` + at + `"}`, ""}, "old layout": {`{"v":2,"node":"ace","why":"started","at":"` + at + `"}`, "version"}, "no node": {`{"v":1,"node":"","why":"started","at":"` + at + `"}`, "no node"}, "another why": {`{"v":1,"node":"ace","why":"bored","at":"` + at + `"}`, "bored"}, "no time": {`{"v":1,"node":"ace","why":"started","at":"yesterday"}`, "RFC 3339"}, } { _, err := DecodeChanged([]byte(c.body)) if (c.bad == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), c.bad)) { t.Errorf("event %s: %v, want %q", name, err, c.bad) } } // Issue 1966 note 155, ADR 0668 ยง4: a part whose first call of a node timed out, and whose consumer was bound after // the node's event was said, calls every node once at the bind, and the node is then held as it answered. calls := map[string]int{} k := &Keeper{Sync: true, Call: func(node, _ string, _ any) (json.RawMessage, error) { calls[node]++ if node == "ace" && calls[node] == 1 { return nil, context.DeadlineExceeded } return json.RawMessage(`{"v":1,"node":"` + node + `","manifest-reads":["a.b"],"stated-at":"` + at + `"}`), nil }} k.AskNode("ace", "the first view") if k.Statements()["ace"].Answered { t.Errorf("a call that timed out reads as answered") } k.AtBind(func() []string { return []string{"ace", "g14"} })(time.Unix(0, 0)) if p := k.Present([]string{"ace", "g14"}); len(p["ace"]) != 1 || len(p["g14"]) != 1 || calls["ace"] != 2 || calls["g14"] != 1 { t.Errorf("after the bind: present %v, calls %v", p, calls) } if Verb != "manifest-reads" || Event != "manifest-reads-changed" || Consumes != "node-engine.manifest-reads-changed" || Invoke != "seat:node-engine.manifest-reads" { t.Errorf("the names are %s %s %s %s", Verb, Event, Consumes, Invoke) } }