From a83b921af0a7ab27d1d9bd40fac70864b1ffab12 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 17 Oct 2026 14:03:26 +0200 Subject: [PATCH] issue 974: acceptance 5: two merges within a minute leave each open pull request one queued check --- cmd/mesh-controller/checks_queue_test.go | 95 ++++++++++++++++++++++++ testdata/store-tests | 1 + 2 files changed, 96 insertions(+) create mode 100644 cmd/mesh-controller/checks_queue_test.go diff --git a/cmd/mesh-controller/checks_queue_test.go b/cmd/mesh-controller/checks_queue_test.go new file mode 100644 index 00000000..98dfb31a --- /dev/null +++ b/cmd/mesh-controller/checks_queue_test.go @@ -0,0 +1,95 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/nats-io/nats.go" + + "github.com/novox/mesh-controller/internal/broker" + "github.com/novox/mesh-controller/internal/catalogue" + "github.com/novox/mesh-controller/internal/inventory" + "github.com/novox/mesh-controller/internal/link" +) + +// Acceptance 5 of milestone 2 (novox/hq ADR 0406 ยง3, issue 974), live, with the store and a real bus: two merges into one +// repository within a minute, with two open pull requests, leave each pull request with exactly one queued check, by the +// stream's rule. Each merge moves the trunk, and mesh-delivery asks the controller to check both open pull requests again +// against it (`delivery check`, which is the controller's own path for a head announced); nothing moves a request, nothing +// asks the forge, and the queue is read back whole. +func TestTwoMergesWithinAMinuteLeaveEachOpenPullRequestOneQueuedCheck(t *testing.T) { + open := aMesh(t) + ctx := t.Context() + js, _ := aBuildQueue(t) + servingBus.Store(js) + t.Cleanup(func() { servingBus.Store(nil) }) + holdTheDeliverySeat(t, open) + was := askDeliveryOwner + t.Cleanup(func() { askDeliveryOwner = was }) + askDeliveryOwner = func(_ context.Context, _ *nats.Conn, verb string, _ map[string]any) (json.RawMessage, error) { + if verb != "groups" { + t.Fatalf("asked the owner %s", verb) + } + return json.RawMessage(`[]`), nil + } + // The repository the two pull requests are into, as a module the mesh builds from it. + if err := open.inventory.RegisterModule(ctx, catalogue.Manifest{Module: "mesh-controller", Version: "1"}, + inventory.Source{Repository: "novox/mesh-controller", Ref: "main", BuiltFrom: "c0", Head: "c0"}); err != nil { + t.Fatal(err) + } + + heads := map[int]string{7: "7777777777777777777777777777777777777777", 8: "8888888888888888888888888888888888888888"} + check := func(number int, trunk string) { + t.Helper() + p := orderMember{ID: fmt.Sprintf("novox/mesh-controller@%.12s", heads[number]), Repository: "novox/mesh-controller", + Base: "main", Head: heads[number], Number: number, Paths: []string{"main.go"}, ModuleDirsSaid: true, + BaseSHA: trunk, Ready: number == 8}.pull() + p.MergeCheck, p.MergeCheckSaid = true, true + if err := (following{open: open, oneShot: true}).PullUpdated(ctx, p); err != nil { + t.Fatal(err) + } + } + // Announced against the trunk as it stands, then asked again after each of two merges. + for _, trunk := range []string{"1111111111111111111111111111111111111111", "2222222222222222222222222222222222222222", + "3333333333333333333333333333333333333333"} { + check(7, trunk) + check(8, trunk) + } + + q, err := link.ReadQueue(ctx, js, link.TheBuildMachine) + if err != nil { + t.Fatal(err) + } + seen := map[int]string{} + for _, a := range q.Asks { + c := a.Request.Check + if c == nil { + t.Fatalf("%s is no check", a.ID) + } + if _, twice := seen[c.Number]; twice { + t.Errorf("pull request %d has two queued checks", c.Number) + } + seen[c.Number] = c.Trunk + if a.State != link.AskWaiting { + t.Errorf("%s is %s", a.ID, a.State) + } + if want := "3333333333333333333333333333333333333333"; c.Trunk != want { + t.Errorf("pull request %d is queued against %.8s; want the newest trunk %.8s", c.Number, c.Trunk, want) + } + } + if len(seen) != 2 { + t.Fatalf("queued checks for %v; want one for each of the two open pull requests", seen) + } + // By the streams' own count: the ready one on its stream, the other on its. + for stream, want := range map[string]uint64{broker.ChecksReadyStream: 1, broker.ChecksOtherStream: 1} { + info, err := js.Context().StreamInfo(stream) + if err != nil { + t.Fatal(err) + } + if info.State.Msgs != want { + t.Errorf("%s holds %d request(s); want %d", stream, info.State.Msgs, want) + } + } +} diff --git a/testdata/store-tests b/testdata/store-tests index 81cc53c1..61113e7e 100644 --- a/testdata/store-tests +++ b/testdata/store-tests @@ -136,3 +136,4 @@ ./cmd/mesh-controller TestEveryMoveAMergeAndTheCutMakeOfAPlanIsSaid ./cmd/mesh-controller TestARolloutAdvancesOnEventsAndAReplacedControllerResumesIt ./cmd/mesh-controller TestAWalkLetGoByTheDeliverVerbIsAskedWithNoOtherEvent +./cmd/mesh-controller TestTwoMergesWithinAMinuteLeaveEachOpenPullRequestOneQueuedCheck