diff --git a/modules/mesh-issues/cmd/mesh-issues/claims_test.go b/modules/mesh-issues/cmd/mesh-issues/claims_test.go index aee2f4e6..2f79d92a 100644 --- a/modules/mesh-issues/cmd/mesh-issues/claims_test.go +++ b/modules/mesh-issues/cmd/mesh-issues/claims_test.go @@ -305,3 +305,103 @@ func mustView(t *testing.T, h *harness, n uint64) *View { } return v } + +// A pull request closed without merging is withdrawn: mesh-delivery says it `stopped` with no merged_as (its +// `closed` event). It is not a fix merged and stuck, not delivered, and not an open pull request: the operator is +// never told it is merged and not delivered, and a worker with nothing else in review holds a stale claim. +func TestAPullRequestClosedUnmergedIsWithdrawn(t *testing.T) { + for _, via := range []string{"transition", "checks"} { + t.Run(via, func(t *testing.T) { + h := newHarness(t) + now := clocked(h) + note := ¬ified{} + h.Notify = note.notify + n := h.open(t, "x", "mesh-catalog modules/records") + if _, err := h.Move(MoveAsk{Number: n, Status: StatusFixing, By: "fix-x@g14"}); err != nil { + t.Fatal(err) + } + linkPR(t, h, n, "mesh-catalog#9") + closed := now.Add(-time.Hour) + if via == "transition" { + transition(t, h, 9, "ready", "its merge", "", closed.Add(-time.Hour)) + transition(t, h, 9, "stopped", "the pull request was closed without merging", "", closed) + } else { + h.Deliveries = func(string, string) (Delivery, error) { + return Delivery{State: "stopped", Why: "the pull request was closed without merging", Since: closed}, nil + } + } + *now = now.Add(25 * time.Hour) + for i := 0; i < 2; i++ { + if _, err := h.Sweep(); err != nil { + t.Fatal(err) + } + } + if len(note.said) != 0 { + t.Fatalf("a pull request closed unmerged was said to the operator: %q", note.said) + } + is := get(t, h, n) + if len(is.Fixes) != 1 || is.Fixes[0].Merged || is.Fixes[0].MergedAt != nil { + t.Fatalf("a pull request closed unmerged is kept as merged: %+v", is.Fixes) + } + v := asJSON(t, mustView(t, h, n)) + if v["delivered-not-resolved"] == true || countEvents(h, EventReadyToVerify) != 0 { + t.Fatalf("a withdrawn pull request reads as delivered: %v", v) + } + if v["stale-claim"] != true { + t.Fatalf("a withdrawn pull request reads as open, so the claim is not stale: %v", v) + } + if d, _ := v["delivery"].(string); !strings.HasPrefix(d, "fix withdrawn: mesh-catalog#9") { + t.Fatalf("delivery %q", d) + } + // A second fix linked and delivered is all the issue waits for: the withdrawn one is no fix. + linkPR(t, h, n, "mesh-catalog#10") + transition(t, h, 10, "delivered", "", "abc", *now) + if v := mustView(t, h, n); !v.DeliveredNotResolved || v.Delivery != "delivered to every machine it should reach" { + t.Fatalf("%+v %q", v.DeliveredNotResolved, v.Delivery) + } + }) + } +} + +// One pull request linked to several issues is measured on each, and each is warned and cleared on its own. +func TestOnePullRequestLinkedToSeveralIssues(t *testing.T) { + h := newHarness(t) + now := clocked(h) + note := ¬ified{} + h.Notify = note.notify + var ns []uint64 + for _, w := range []string{"fix-a@g14", "fix-b@shanks"} { + n := h.open(t, "x "+w, "mesh-catalog modules/records") + if _, err := h.Move(MoveAsk{Number: n, Status: StatusFixing, By: w}); err != nil { + t.Fatal(err) + } + linkPR(t, h, n, "mesh-catalog#9") + ns = append(ns, n) + } + transition(t, h, 9, "delivered", "", "abc", *now) + if countEvents(h, EventReadyToVerify) != 2 { + t.Fatalf("%d ready-to-verify", countEvents(h, EventReadyToVerify)) + } + *now = now.Add(2 * time.Hour) + if _, err := h.Sweep(); err != nil { + t.Fatal(err) + } + if len(note.said) != 2 || !strings.Contains(strings.Join(note.said, "\n"), "fix-a@g14") || + !strings.Contains(strings.Join(note.said, "\n"), "fix-b@shanks") { + t.Fatalf("said %q", note.said) + } + if _, err := h.Move(MoveAsk{Number: ns[0], Status: StatusResolved, FixedBy: []string{"mesh-catalog#9"}}); err != nil { + t.Fatal(err) + } + if err := h.OnMoved(envelopeOf(ns[0])); err != nil { + t.Fatal(err) + } + if _, err := h.Sweep(); err != nil { + t.Fatal(err) + } + if len(note.clear) != 1 || note.clear[0] != notResolvedKeyOf(ns[0]) || len(note.said) != 2 { + t.Fatalf("cleared %q, said %q", note.clear, note.said) + } +} + +func notResolvedKeyOf(n uint64) string { return "issue-tracker.delivered-not-resolved." + keyOf(n) }