//go:build integration package main import ( "encoding/json" "strings" "testing" "time" ) // novox/hq ADR 0505 over the real bus, in the bed of condition_bus_test.go: the router whole, its walks kept in its // state, the controller's walk and condition events handed to it as the runtime hands them. // controllerEvent hands the bundle one of the controller's events, as the runtime does. func (b *conditionBed) controllerEvent(t *testing.T, event string, body any) { t.Helper() raw, err := json.Marshal(body) if err != nil { t.Fatal(err) } if _, err := b.r.request("mesh/event", map[string]any{"envelope": map[string]any{"key": ConditionSeat + "." + event, "node": "test", "body": json.RawMessage(raw)}}); err != nil { t.Fatal(err) } } // viewEvent hands the bundle one of the delivery view's events (moving, settled, stuck), as the runtime does. func (b *conditionBed) viewEvent(t *testing.T, event string, body any) { t.Helper() raw, err := json.Marshal(body) if err != nil { t.Fatal(err) } if _, err := b.r.request("mesh/event", map[string]any{"envelope": map[string]any{"key": ViewModule + "." + event, "node": "test", "body": json.RawMessage(raw)}}); err != nil { t.Fatal(err) } } // conditionEvent is a condition as the controller raises it (mesh-controller probes.go D1, signals.go). func conditionEvent(key, scope, id, machine, headline string) map[string]any { now := time.Now().UTC().Format(time.RFC3339) return map[string]any{"key": key, "kind": key[strings.LastIndex(key, ".")+1:], "severity": Urgent, "subject": map[string]string{"scope": scope, "id": id, "machine": machine}, "summary": headline, "headline": headline, "explanation": "Needs you: look at it. " + headline + ".", "needs": "look at it.", "raised": now, "at": now} } func (b *conditionBed) toldWith(words string) *string { for _, m := range b.told() { if strings.Contains(m.Title+"\n"+m.Body, words) { text := m.Title + "\n" + m.Body return &text } } return nil } // ADR 0505 §1, §2b, §3: during a walk, a condition on a machine whose declaration it changes is held, and one on an // untouched machine reaches the desk at once ending "No delivery running."; after the walk delivered, the held one // reaches the desk only once the settle (two seconds here) has passed, inside the walk's end message. func TestAMovingModulesMachineIsHeldUntilItSettledOverTheBus(t *testing.T) { b := newConditionBedWith(t, `{"settle-seconds": 2}`) started := time.Now() b.viewEvent(t, EventMoving, map[string]any{"node": "novox", "module": "gitea", "from_fp": "a", "to_fp": "b", "since": started.UTC().Format(time.RFC3339Nano), "bound_seconds": 600}) b.controllerEvent(t, EventRaised, conditionEvent("machine.novox.uncomposable", "machine", "novox", "novox", "Novox cannot get new instructions")) b.controllerEvent(t, EventRaised, conditionEvent("machine.ace.silent", "machine", "ace", "ace", "Ace is silent")) b.r.until("the untouched machine's condition reaches the desk with its delivery line", 15*time.Second, func() bool { m := b.toldWith("Ace is silent") return m != nil && strings.HasSuffix(*m, "\nNo delivery running.") }) if b.toldWith("Novox cannot get new instructions") != nil { t.Fatalf("the declared machine's condition was told during the walk") } b.viewEvent(t, EventSettled, map[string]any{"node": "novox", "module": "gitea", "at": time.Now().UTC().Format(time.RFC3339Nano)}) time.Sleep(time.Second) if b.toldWith("Novox cannot get new instructions") != nil || b.toldWith("Delivered") != nil { t.Fatalf("told before the settle passed: %+v", b.told()) } b.r.until("the walk's end tells what it held once it settled", 30*time.Second, func() bool { m := b.toldWith("Still open after the delivery of gitea to novox:") return m != nil && strings.Contains(*m, "Novox cannot get new instructions") }) }