package selfcompose import ( "context" "strings" "sync/atomic" "testing" "time" "git.novox.be/novox/mesh-sdk/go/delivery" "github.com/novox/mesh-host/internal/agent" ) // Issue 1965 (shanks 2026-11-05 22:27: the verb compose gave no answer in 30 s while a pass waited on the bus): an ask // that comes while a pass runs is answered at once, says one more pass is queued and what the running one waits on, and // the queued pass is made when the running one ends. func TestComposeAnsweredWhileAPassRuns(t *testing.T) { b := newFakeBus() ctx, cancel := context.WithCancel(context.Background()) defer cancel() env := b.env() hold := make(chan struct{}) var passes atomic.Int64 reached := make(chan struct{}, 8) env.Running = func() []delivery.Composed { passes.Add(1) reached <- struct{}{} if passes.Load() == 1 { <-hold // the first pass waits on the bus } return nil } c := New(env) out, err := c.Watch(ctx, "n1.>") if err != nil { t.Fatal(err) } have, _ := delivery.EncodeHaveNode(delivery.HaveNode{Record: &delivery.NodeRecord{Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}}) place, _ := delivery.EncodePlacement(delivery.Placement{Node: "n1", Decided: delivery.NodeDecided{Hub: true, Address: "10.77.0.1"}}) nodeWant, _ := delivery.EncodeNodeWant(delivery.NodeWant{Self: true}) b.put(t, delivery.PlacementBucket, "placement.n1", place) b.put(t, delivery.HaveBucket, "n1.node", have) b.put(t, delivery.AssignerWantsBucket, "want.n1.node", nodeWant) for _, bucket := range []string{delivery.AssignerWantsBucket, delivery.PlacementBucket, delivery.HaveBucket, delivery.ModulesBucket, ResolvedBucket, delivery.BuilderBuildsBucket} { b.send(bucket, agent.Update{Caught: true}) } go func() { for range out { } }() select { case <-reached: case <-time.After(5 * time.Second): t.Fatal("no pass began") } answered := make(chan Pass, 1) go func() { p, _ := c.ComposeNow(ctx) answered <- p }() select { case p := <-answered: if !strings.Contains(p.Queued, "a pass is running") { t.Errorf("answered %+v; want it to say a pass is running and one is queued", p) } case <-time.After(2 * time.Second): t.Fatal("the verb compose gave no answer while a pass ran") } close(hold) select { case <-reached: // the queued pass case <-time.After(5 * time.Second): t.Fatal("the queued pass was not made when the running one ended") } }