The indexer's, the assigner's, the builder's and the watchman's consumers that were not there or could not be heard only said so in the log, so the part stayed healthy while it heard nothing (ADR 0615 §5). sdk/heard is the one shared helper: the finding family-not-heard names the observer and the family, written when the consumer does not bind and deleted when it does. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
55 lines
2.1 KiB
Go
55 lines
2.1 KiB
Go
package heard
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/health"
|
|
)
|
|
|
|
// Review round 1 of the registry milestone (ADR 0615 §5): a consumer that does not bind is a finding naming the observer
|
|
// and the family, with why; binding deletes it; no findings state still says it in the log.
|
|
func TestAConsumerNotHeardIsAFindingUntilItBinds(t *testing.T) {
|
|
at := time.Date(2026, 11, 4, 17, 0, 0, 0, time.UTC)
|
|
for _, row := range []struct {
|
|
name string
|
|
bindErr error
|
|
noState bool
|
|
want string // the finding's summary, or "" when none stands
|
|
}{
|
|
{name: "not there yet", bindErr: errors.New("consumer not found"),
|
|
want: "mesh-delivery-indexer does not hear assigner-views: the placement is the first view's alone (consumer not found)"},
|
|
{name: "bound"},
|
|
{name: "no findings state", bindErr: errors.New("consumer not found"), noState: true},
|
|
} {
|
|
kept := map[string][]byte{Key("mesh-delivery-indexer", "assigner-views"): []byte("{}")}
|
|
var logs []string
|
|
w := Writer{Observer: "mesh-delivery-indexer", Now: func() time.Time { return at },
|
|
Logf: func(f string, a ...any) { logs = append(logs, f) }}
|
|
if !row.noState {
|
|
w.Put = func(k string, v []byte) error { kept[k] = v; return nil }
|
|
w.Delete = func(k string) error { delete(kept, k); return nil }
|
|
}
|
|
err := w.Bind("assigner-views", "the placement is the first view's alone", func() error { return row.bindErr })
|
|
if !errors.Is(err, row.bindErr) {
|
|
t.Errorf("%s: Bind answered %v, want %v", row.name, err, row.bindErr)
|
|
}
|
|
raw, stands := kept[Key("mesh-delivery-indexer", "assigner-views")]
|
|
switch {
|
|
case row.noState:
|
|
if len(logs) == 0 {
|
|
t.Errorf("%s: nothing said", row.name)
|
|
}
|
|
case row.want == "" && stands:
|
|
t.Errorf("%s: the finding stands once bound", row.name)
|
|
case row.want != "":
|
|
f, err := health.DecodeFinding(raw)
|
|
if err != nil || f.Kind != Kind || f.Summary != row.want || f.Observer != "mesh-delivery-indexer" || !strings.Contains(f.Subject.ID, "assigner-views") {
|
|
t.Errorf("%s: the finding %+v (%v), want summary %q", row.name, f, err, row.want)
|
|
}
|
|
}
|
|
}
|
|
}
|