Prove over the real bus that a refusal the gate answers is stalled only once its fresh check did not cure it (ADR 0505 §4)

This commit is contained in:
2026-10-22 14:18:33 +02:00
parent e5608f7b00
commit 34277ef60f
@@ -0,0 +1,88 @@
//go:build integration
package main
import (
"context"
"encoding/json"
"strconv"
"strings"
"testing"
"time"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
// novox/hq ADR 0505 §4 over the real bus, in the bed of bus_test.go: the gate whole, its deliveries kept in its buckets.
// call asks the bundle one of its verbs, as the runtime does, and answers its result's text.
func (r *runtime) call(t *testing.T, name string, args map[string]any) string {
t.Helper()
r.mu.Lock()
r.next++
id := "adr505-" + strconv.Itoa(r.next)
ch := make(chan json.RawMessage, 1)
r.wait[id] = ch
r.mu.Unlock()
r.say(map[string]any{"id": id, "method": "tools/call", "params": map[string]any{"name": name, "arguments": args}})
select {
case raw := <-ch:
return string(raw)
case <-time.After(30 * time.Second):
t.Fatalf("%s was not answered in 30 s", name)
}
return ""
}
// ADR 0505 §4: an approved head the merge tool refused because its trunk moved is shown by `show` at once but is not in
// `stalled` while its fresh check is unanswered; once that check passed and the merge is refused again, it is.
func TestATrunkMovedRefusalIsStalledOnlyOnceItsFreshCheckDidNotCureItOverARealBus(t *testing.T) {
b := newTestBus(t)
ctx := context.Background()
rt := b.start(t)
evctx, cancel := context.WithCancel(ctx)
defer cancel()
rt.deliverEvents(evctx)
rt.until("the module to subscribe to the events it follows", 20*time.Second, func() bool {
rt.mu.Lock()
defer rt.mu.Unlock()
return len(rt.subs) >= len(followed)
})
rt.untilHolding()
const head = "1111111111111111111111111111111111111111"
id := IDOf("novox/app", head)
announced := pr("novox/app", 7, head, "fix/x", "src/a.go")
announced.BaseSHA = "t1t1t1t1t1t1"
b.publish(t, "mesh.mod.gitea.event.pull.updated", announced)
b.publish(t, "mesh.seat."+ControllerSeat+".event.checked", judgedAt("novox/app", 7, head, "pass", "t1t1t1t1t1t1"))
var key string
rt.until("the ask to be written", 10*time.Second, func() bool { key = onlyAsk(b); return key != "" })
b.answer(t, key, OptionApprove)
rt.until("the merge to be handed to the bridge", 10*time.Second, func() bool { return len(mergeAt(b, "fix/x").Pulls) > 0 })
refused := func() {
b.forgeSays(t, sdk.Forge{Kind: sdk.ForgeRefused, Act: "merge", Repository: "novox/app", Number: 7, Head: head,
Words: errAskedAgain.Error()})
}
refused()
rt.until("show to name the refusal at once", 10*time.Second, func() bool {
return strings.Contains(rt.call(t, Seat+".show", map[string]any{"id": id}), "its verdict was judged against trunk")
})
if got := rt.call(t, Seat+".stalled", map[string]any{}); strings.Contains(got, id) {
t.Fatalf("said as stalled while its fresh check runs: %s", got)
}
// The trunk moved; the head is announced again and its fresh check passes; the merge is refused again on it.
b.publish(t, "mesh.mod.gitea.event.pull.merged", merged("novox/app", 9, "9999999999999999999999999999999999999999", "t2t2t2t2t2t2"))
b.publish(t, "mesh.mod.gitea.event.pull.updated", announced)
time.Sleep(time.Second)
b.publish(t, "mesh.seat."+ControllerSeat+".event.checked", judgedAt("novox/app", 7, head, "pass", "t2t2t2t2t2t2"))
rt.until("the fresh verdict to be heard", 10*time.Second, func() bool {
return strings.Contains(rt.call(t, Seat+".show", map[string]any{"id": id}), "t2t2t2t2t2t2")
})
refused()
rt.until("the refusal on the fresh verdict to be said as stalled", 10*time.Second, func() bool {
return strings.Contains(rt.call(t, Seat+".stalled", map[string]any{}), id)
})
}