mesh-issues: an issue says who works on it, and resolves only from fixing (hq issue 399)
mesh/delivery-group group feat/399-fixing-status checking: 0 of 2 member(s) ready
mesh/merge-gate pass: builds mesh-issues → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
mesh/delivery-group group feat/399-fixing-status checking: 0 of 2 member(s) ready
mesh/merge-gate pass: builds mesh-issues → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
Two agents could start on one issue without seeing each other, and an issue could be resolved without anyone having said they started its fix. A move to diagnosing or fixing now names by, the worker as who@node; get and list show the worker with since and stale; another worker is refused while it is current unless take-over says why (noted); a worker untouched for worker-stale-after (24h) may be taken over from without a reason. Resolved is reached only from fixing. The moved event carries by, since and caller. Fixing counts as not closed for the triage and the duplicate check. hq ADR 0283.
This commit is contained in:
@@ -247,7 +247,7 @@ func TestAnIssueWithAFolderIsNumberedTitledAndPointsAtItsFolder(t *testing.T) {
|
||||
t.Fatalf("answer %+v", o)
|
||||
}
|
||||
// Since the import of slice 3, an issue with a folder is whole in the tracker: its status moves here.
|
||||
if _, err := h.Move(MoveAsk{Number: 365, Status: StatusDiagnosing}); err != nil {
|
||||
if _, err := h.Move(MoveAsk{Number: 365, Status: StatusDiagnosing, By: "fix-365@g14"}); err != nil {
|
||||
t.Fatalf("a move of an issue with a folder: %v", err)
|
||||
}
|
||||
if _, err := h.Note(365, "notes are taken"); err != nil {
|
||||
@@ -259,42 +259,75 @@ func TestAnIssueWithAFolderIsNumberedTitledAndPointsAtItsFolder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The cycle's rules at the write (to-be 53 §5): one case per row, each refused case changing nothing.
|
||||
// The cycle's rules at the write (to-be 53 §5, ADR 0283): one case per row, each refused case changing nothing.
|
||||
// A case starts from open, or, with from, from that status reached first (fixing by fix@g14).
|
||||
func TestTheCyclesRulesHoldAtTheWrite(t *testing.T) {
|
||||
const fx = StatusFixing
|
||||
cases := []struct {
|
||||
name string
|
||||
located []string // at open
|
||||
from string // the status it is moved to first; "" stays open
|
||||
ask MoveAsk
|
||||
refused string
|
||||
}{
|
||||
{"an unknown status", nil, MoveAsk{Status: "closed"}, "not one of"},
|
||||
{"located without located-in", nil, MoveAsk{Status: StatusLocated}, "without located-in"},
|
||||
{"located with an unregistered owner", nil, MoveAsk{Status: StatusLocated, LocatedIn: []string{"elsewhere"}}, "no registered owner"},
|
||||
{"located with located-in", nil, MoveAsk{Status: StatusLocated, LocatedIn: []string{"mesh-catalog modules/records"}}, ""},
|
||||
{"located, named at open", []string{"hq"}, MoveAsk{Status: StatusLocated}, ""},
|
||||
{"resolved without located-in", nil, MoveAsk{Status: StatusResolved, FixedBy: []string{"hq#1"}}, "without located-in"},
|
||||
{"resolved without fixed-by", []string{"hq"}, MoveAsk{Status: StatusResolved}, "without fixed-by"},
|
||||
{"resolved", []string{"hq"}, MoveAsk{Status: StatusResolved, FixedBy: []string{"hq#280"}}, ""},
|
||||
{"a core issue resolved without a replay", []string{"mesh-controller"}, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-controller#9"}}, "core issue"},
|
||||
{"a core issue in the bus resolved without a replay", []string{"mesh-catalog modules/nats"}, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-catalog#9"}}, "core issue"},
|
||||
{"a core issue resolved with a replay", []string{"mesh-tools node-tools/internal/bus"}, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-tools#25"}, Replay: "R361"}, ""},
|
||||
{"a core issue resolved with replay-none", []string{"mesh-sdk"}, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-sdk#16"}, ReplayNone: "it needs two machines"}, ""},
|
||||
{"a replay that names none", []string{"mesh-sdk"}, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-sdk#16"}, Replay: "yes"}, "names no replay"},
|
||||
{"replay and replay-none", []string{"mesh-sdk"}, MoveAsk{Status: StatusResolved, FixedBy: []string{"x"}, Replay: "R1", ReplayNone: "y"}, "not both"},
|
||||
{"a replay on another status", []string{"mesh-sdk"}, MoveAsk{Status: StatusLocated, Replay: "R1"}, "what resolving"},
|
||||
{"wontfix without a reason", nil, MoveAsk{Status: StatusWontfix}, "without a reason"},
|
||||
{"wontfix", nil, MoveAsk{Status: StatusWontfix, Reason: "the module is retired"}, ""},
|
||||
{"a reason on another status", nil, MoveAsk{Status: StatusDiagnosing, Reason: "x"}, "what wontfix needs"},
|
||||
{"to the status it has", nil, MoveAsk{Status: StatusOpen}, "already open"},
|
||||
{"diagnosing", nil, MoveAsk{Status: StatusDiagnosing}, ""},
|
||||
{"an amended design that is not one", nil, MoveAsk{Status: StatusDiagnosing, AmendedDesign: []string{"../etc/passwd"}}, "not a design document"},
|
||||
{"a fixed-by of two lines", []string{"hq"}, MoveAsk{Status: StatusResolved, FixedBy: []string{"a\nb"}}, "one line"},
|
||||
{"an unknown status", nil, "", MoveAsk{Status: "closed"}, "not one of"},
|
||||
{"located without located-in", nil, "", MoveAsk{Status: StatusLocated}, "without located-in"},
|
||||
{"located with an unregistered owner", nil, "", MoveAsk{Status: StatusLocated, LocatedIn: []string{"elsewhere"}}, "no registered owner"},
|
||||
{"located with located-in", nil, "", MoveAsk{Status: StatusLocated, LocatedIn: []string{"mesh-catalog modules/records"}}, ""},
|
||||
{"located, named at open", []string{"hq"}, "", MoveAsk{Status: StatusLocated}, ""},
|
||||
{"resolved without located-in", nil, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"hq#1"}}, "without located-in"},
|
||||
{"resolved without fixed-by", []string{"hq"}, fx, MoveAsk{Status: StatusResolved}, "without fixed-by"},
|
||||
{"resolved", []string{"hq"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"hq#280"}}, ""},
|
||||
{"resolved from open", []string{"hq"}, "", MoveAsk{Status: StatusResolved, FixedBy: []string{"hq#280"}}, "only from fixing"},
|
||||
{"resolved from located", []string{"hq"}, StatusLocated, MoveAsk{Status: StatusResolved, FixedBy: []string{"hq#280"}}, "only from fixing"},
|
||||
{"resolved from diagnosing", []string{"hq"}, StatusDiagnosing, MoveAsk{Status: StatusResolved, FixedBy: []string{"hq#280"}}, "only from fixing"},
|
||||
{"a core issue resolved without a replay", []string{"mesh-controller"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-controller#9"}}, "core issue"},
|
||||
{"a core issue in the bus resolved without a replay", []string{"mesh-catalog modules/nats"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-catalog#9"}}, "core issue"},
|
||||
{"a core issue resolved with a replay", []string{"mesh-tools node-tools/internal/bus"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-tools#25"}, Replay: "R361"}, ""},
|
||||
{"a core issue resolved with replay-none", []string{"mesh-sdk"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-sdk#16"}, ReplayNone: "it needs two machines"}, ""},
|
||||
{"a replay that names none", []string{"mesh-sdk"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-sdk#16"}, Replay: "yes"}, "names no replay"},
|
||||
{"replay and replay-none", []string{"mesh-sdk"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"x"}, Replay: "R1", ReplayNone: "y"}, "not both"},
|
||||
{"a replay on another status", []string{"mesh-sdk"}, "", MoveAsk{Status: StatusLocated, Replay: "R1"}, "what resolving"},
|
||||
{"wontfix without a reason", nil, "", MoveAsk{Status: StatusWontfix}, "without a reason"},
|
||||
{"wontfix", nil, "", MoveAsk{Status: StatusWontfix, Reason: "the module is retired"}, ""},
|
||||
{"wontfix from fixing", nil, fx, MoveAsk{Status: StatusWontfix, Reason: "the module is retired"}, ""},
|
||||
{"a reason on another status", nil, "", MoveAsk{Status: StatusDiagnosing, By: "a@g14", Reason: "x"}, "what wontfix needs"},
|
||||
{"to the status it has", nil, "", MoveAsk{Status: StatusOpen}, "already open"},
|
||||
{"diagnosing", nil, "", MoveAsk{Status: StatusDiagnosing, By: "fix-365@g14"}, ""},
|
||||
{"diagnosing without by", nil, "", MoveAsk{Status: StatusDiagnosing}, "needs by"},
|
||||
{"fixing", nil, "", MoveAsk{Status: StatusFixing, By: "Jochen@shanks"}, ""},
|
||||
{"fixing without by", nil, "", MoveAsk{Status: StatusFixing}, "needs by"},
|
||||
{"fixing with a by that names no node", nil, "", MoveAsk{Status: StatusFixing, By: "an agent"}, "who@node"},
|
||||
{"fixing with a by that carries a secret", nil, "", MoveAsk{Status: StatusFixing, By: "sk-ant-api03-abcdefghijklmnopqrstuvwxyz@g14"}, "carries no secret"},
|
||||
{"by on another status", []string{"hq"}, "", MoveAsk{Status: StatusLocated, By: "a@g14"}, "only diagnosing and fixing"},
|
||||
{"take-over on another status", []string{"hq"}, "", MoveAsk{Status: StatusLocated, TakeOver: "x"}, "take-over is for"},
|
||||
{"take-over with nobody to take from", nil, "", MoveAsk{Status: StatusFixing, By: "a@g14", TakeOver: "x"}, "nobody works on"},
|
||||
{"an amended design that is not one", nil, "", MoveAsk{Status: StatusDiagnosing, By: "a@g14", AmendedDesign: []string{"../etc/passwd"}}, "not a design document"},
|
||||
{"a fixed-by of two lines", []string{"hq"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"a\nb"}}, "one line"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
n := h.open(t, "an issue", c.located...)
|
||||
from := StatusOpen
|
||||
switch c.from {
|
||||
case "":
|
||||
case StatusFixing:
|
||||
h.fixing(t, n)
|
||||
from = StatusFixing
|
||||
case StatusDiagnosing:
|
||||
if _, err := h.Move(MoveAsk{Number: n, Status: c.from, By: "fix@g14"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
from = c.from
|
||||
default:
|
||||
if _, err := h.Move(MoveAsk{Number: n, Status: c.from}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
from = c.from
|
||||
}
|
||||
before, _ := h.mem.Get(IssuesState, keyOf(n))
|
||||
events := len(h.said())
|
||||
c.ask.Number = n
|
||||
m, err := h.Move(c.ask)
|
||||
if c.refused != "" {
|
||||
@@ -305,7 +338,7 @@ func TestTheCyclesRulesHoldAtTheWrite(t *testing.T) {
|
||||
if after.Revision != before.Revision || string(after.Value) != string(before.Value) {
|
||||
t.Fatal("a refused move changed the issue")
|
||||
}
|
||||
if len(h.said()) != 1 {
|
||||
if len(h.said()) != events {
|
||||
t.Fatalf("a refused move said %v", h.said())
|
||||
}
|
||||
return
|
||||
@@ -314,16 +347,25 @@ func TestTheCyclesRulesHoldAtTheWrite(t *testing.T) {
|
||||
t.Fatalf("refused: %v", err)
|
||||
}
|
||||
is, _ := h.Get(n)
|
||||
if is.Status != c.ask.Status || m.From != StatusOpen || m.To != c.ask.Status {
|
||||
if is.Status != c.ask.Status || m.From != from || m.To != c.ask.Status {
|
||||
t.Fatalf("moved %+v, kept %+v", m, is)
|
||||
}
|
||||
closed := c.ask.Status == StatusResolved || c.ask.Status == StatusWontfix
|
||||
if (is.Closed != nil) != closed {
|
||||
t.Fatalf("closed %v at %s", is.Closed, is.Status)
|
||||
}
|
||||
wantBy := Unproven
|
||||
if worked(c.ask.Status) {
|
||||
wantBy = strings.ToLower(c.ask.By)
|
||||
if is.Worker == nil || is.Worker.By != wantBy || is.Worker.Since.IsZero() {
|
||||
t.Fatalf("worker %+v", is.Worker)
|
||||
}
|
||||
} else if is.Worker != nil {
|
||||
t.Fatalf("a worker kept at %s: %+v", is.Status, is.Worker)
|
||||
}
|
||||
ev := h.said()
|
||||
if last := ev[len(ev)-1]; last.Event != EventMoved || last.Body["to"] != c.ask.Status || last.Body["from"] != StatusOpen ||
|
||||
last.Body["by"] != Unproven {
|
||||
if last := ev[len(ev)-1]; last.Event != EventMoved || last.Body["to"] != c.ask.Status || last.Body["from"] != from ||
|
||||
last.Body["by"] != wantBy || last.Body["caller"] != Unproven {
|
||||
t.Fatalf("event %+v", last)
|
||||
}
|
||||
})
|
||||
@@ -333,6 +375,7 @@ func TestTheCyclesRulesHoldAtTheWrite(t *testing.T) {
|
||||
func TestReopeningClearsWhenItClosed(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
n := h.open(t, "x", "hq")
|
||||
h.fixing(t, n)
|
||||
if _, err := h.Move(MoveAsk{Number: n, Status: StatusResolved, FixedBy: []string{"hq#1"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -347,7 +390,7 @@ func TestReopeningClearsWhenItClosed(t *testing.T) {
|
||||
|
||||
func TestAMoveOfAnIssueThatDoesNotExistIsRefused(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
if _, err := h.Move(MoveAsk{Number: 9, Status: StatusDiagnosing}); !errors.Is(err, ErrNoIssue) {
|
||||
if _, err := h.Move(MoveAsk{Number: 9, Status: StatusDiagnosing, By: "a@g14"}); !errors.Is(err, ErrNoIssue) {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if _, err := h.Get(9); err == nil || !strings.Contains(err.Error(), "there is no issue 9") {
|
||||
@@ -483,7 +526,7 @@ func TestListIsNewestFirstAndNarrows(t *testing.T) {
|
||||
h.open(t, "the bus drops a message", "mesh-catalog modules/nats")
|
||||
h.open(t, "a check passes and did nothing", "mesh-controller internal/catalogue")
|
||||
n := h.open(t, "the console names a ghost")
|
||||
if _, err := h.Move(MoveAsk{Number: n, Status: StatusDiagnosing}); err != nil {
|
||||
if _, err := h.Move(MoveAsk{Number: n, Status: StatusDiagnosing, By: "a@g14"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
l, err := h.List(ListAsk{})
|
||||
|
||||
Reference in New Issue
Block a user