Files
mesh-delivery/modules/mesh-delivery-assigner/internal/outside/outside_test.go
T
jschoubben 48dfe36084 Assigner: a moved module's failing check on an outside cause is a wait that ends by itself
The wait (step outside, on, bound, clock-from, the settle, conditions-unread), its events, finding and ask,
assigner_waits and assigner_release with how, the settings, and a machine's own open network holding its wants.

Builds: design 47 G §706 (assigner), §498, §276, §633, §700

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:46:58 +01:00

170 lines
6.2 KiB
Go

package outside
import (
"slices"
"testing"
"time"
)
var t0 = time.Date(2026, 11, 11, 10, 0, 0, 0, time.UTC)
func TestCauses(t *testing.T) {
conds := []Cond{
{Key: "machine.ace.rewritten"},
{Key: "machine.ace.systemd-resolved.rewritten"},
{Key: "machine.ace.nats.rewritten"},
{Key: "machine.hub.unreachable", Also: []string{"ace", "g14"}},
{Key: "machine.lap.unreachable", Also: []string{"g14"}},
{Key: "machine.hub2.network", Also: []string{"ace"}},
{Key: "machine.hub3.silent", Also: []string{"ace"}},
{Key: "machine.ace.network", Also: []string{"ace"}},
{Key: "module.nats.ace.unhealthy", Machine: "ace"},
}
moved := func(o string) bool { return o == "nats" }
var got []string
for _, c := range Causes(conds, "ace", moved) {
got = append(got, c.On)
}
want := []string{"unreachable:hub", "network:hub2", "silent:hub3", "rewritten", "rewritten:systemd-resolved"}
slices.Sort(got)
slices.Sort(want)
if !slices.Equal(got, want) {
t.Errorf("causes = %v, want %v (a rewritten naming a moved module, an unreachable not listing ace and ace's own network are none)", got, want)
}
if own := OwnNetwork(conds); !own["ace"] || !own["hub2"] || own["hub"] {
t.Errorf("own network = %v", own)
}
}
func facts(now time.Time, conds ...Cond) Facts {
return Facts{Now: now, Failing: true, Read: true, Conds: conds, Node: "ace", Bound: 30 * time.Minute, Settle: 90 * time.Second}
}
func TestJudge(t *testing.T) {
hub := Cond{Key: "machine.hub.unreachable", Also: []string{"ace"}}
hubNet := Cond{Key: "machine.hub.network", Also: []string{"ace"}}
step := func(prev *Wait, f Facts) (Decision, *Wait) {
d := Judge(prev, "ace", "nats", "src2:aa", f)
return d, d.Wait
}
tests := []struct {
name string
run func(t *testing.T)
}{
{"a failing check with a cause open is a wait said at once, ended cleared on its clearing", func(t *testing.T) {
d, w := step(nil, facts(t0, hub))
if !d.Held || !d.Waiting || w.On != "unreachable:hub" || w.Bound != "30m0s" || !w.ClockFrom.Equal(t0) || d.Own {
t.Fatalf("decision %+v wait %+v", d, w)
}
f := facts(t0.Add(time.Minute))
f.Failing, f.Healthy = false, true
if d, _ := step(w, f); d.End != HowCleared || d.Wait != nil {
t.Errorf("healthy once cleared: %+v", d)
}
f.Conds = []Cond{hub}
if d, _ := step(w, f); d.End != HowHealthy {
t.Errorf("healthy with the cause open: %+v", d)
}
}},
{"a cause that does not list the machine leaves the failure to settle, then the delivery's", func(t *testing.T) {
d, w := step(nil, facts(t0, Cond{Key: "machine.hub.unreachable", Also: []string{"g14"}}))
if d.Held || !w.Settling || !w.SettleEnds.Equal(t0.Add(90*time.Second)) || !d.Waiting || d.Own {
t.Fatalf("settle: %+v %+v", d, w)
}
if d, _ := step(w, facts(t0.Add(89*time.Second))); !d.Waiting || d.Own {
t.Errorf("within the settle: %+v", d)
}
if d, _ := step(w, facts(t0.Add(90*time.Second))); !d.Own || d.Waiting {
t.Errorf("past the settle: %+v", d)
}
}},
{"a condition raised within the settle turns it into a wait", func(t *testing.T) {
_, w := step(nil, facts(t0))
d, w2 := step(w, facts(t0.Add(30*time.Second), hub))
if !d.Held || w2.Settling || w2.On != "unreachable:hub" {
t.Fatalf("%+v %+v", d, w2)
}
}},
{"a cause that turns into another keeps the wait and its clock", func(t *testing.T) {
_, w := step(nil, facts(t0, hub))
d, w2 := step(w, facts(t0.Add(5*time.Minute), hubNet))
if !d.Held || w2.On != "network:hub" || !w2.ClockFrom.Equal(t0) || d.End != "" {
t.Fatalf("%+v %+v", d, w2)
}
}},
{"judged again failing with no cause open ends own-failure", func(t *testing.T) {
_, w := step(nil, facts(t0, hub))
d, w2 := step(w, facts(t0.Add(time.Minute)))
if d.End != HowOwnFailure || !d.Own || w2.Decided != HowOwnFailure {
t.Fatalf("%+v", d)
}
}},
{"the keeper silent holds as the settle does, then waits on conditions-unread, and is judged when it answers", func(t *testing.T) {
f := facts(t0)
f.Read = false
d, w := step(nil, f)
if d.Held || !w.Settling || !d.Waiting {
t.Fatalf("%+v %+v", d, w)
}
f.Now = t0.Add(90 * time.Second)
d, w = step(w, f)
if !d.Held || w.On != OnUnread || w.Settling || !w.ClockFrom.Equal(t0) {
t.Fatalf("%+v %+v", d, w)
}
if d, w2 := step(w, facts(t0.Add(100*time.Second), hub)); !d.Held || w2.On != "unreachable:hub" || !w2.ClockFrom.Equal(w.ClockFrom) {
t.Errorf("answered: %+v %+v", d, w2)
}
}},
{"the keeper's own module failing is its failure with no read", func(t *testing.T) {
f := facts(t0, hub)
f.KeeperSelf = true
if d, w := step(nil, f); !d.Own || w != nil {
t.Fatalf("%+v", d)
}
}},
{"past the bound the operator is asked; Go on counts it healthy, Count as failed is the delivery's", func(t *testing.T) {
_, w := step(nil, facts(t0, hub))
d, w := step(w, facts(t0.Add(30*time.Minute), hub))
if !d.Ask || d.Held || d.End != "" {
t.Fatalf("%+v", d)
}
for how, want := range map[string]Decision{HowGoOn: {Counted: true}, HowFailed: {Own: true}} {
w2 := *w
w2.Release = how
d, kept := step(&w2, facts(t0.Add(31*time.Minute), hub))
if d.End != how || d.Counted != want.Counted || d.Own != want.Own || kept.Decided != how {
t.Errorf("%s: %+v", how, d)
}
// decided: a check that goes on failing is not waited on again, until it runs healthy
if d, _ := step(kept, facts(t0.Add(32*time.Minute), hub)); d.Held || d.Counted != want.Counted || d.Own != want.Own {
t.Errorf("%s again: %+v", how, d)
}
}
}},
{"a release to judge again changes nothing by itself", func(t *testing.T) {
_, w := step(nil, facts(t0, hub))
w.Release = HowRejudge
if d, w2 := step(w, facts(t0.Add(time.Minute))); d.End != HowOwnFailure || w2.Release != "" {
t.Fatalf("%+v", d)
}
}},
}
for _, tt := range tests {
t.Run(tt.name, tt.run)
}
}
func TestWaitRoundTrip(t *testing.T) {
raw, err := Encode(Wait{Node: "ace", Module: "nats", FP: "src2:aa", On: "silent:g14", Since: t0, ClockFrom: t0})
if err != nil {
t.Fatal(err)
}
w, err := Decode(raw)
if err != nil || w.On != "silent:g14" || !w.ClockFrom.Equal(t0) {
t.Fatalf("%+v %v", w, err)
}
if _, err := Decode([]byte(`{"v":2}`)); err == nil {
t.Error("an unknown version is read")
}
}