Say in a condition's message which issue tracks it and where that issue stands (issue 400)
mesh/delivery-group group feat/400-alert-names-its-issue checking: 0 of 2 member(s) ready
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds mesh-issues, messenger → 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-group group feat/400-alert-names-its-issue checking: 0 of 2 member(s) ready
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds mesh-issues, messenger → 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)
The operator could not tell from a notification whether its cause was known, tracked and being fixed, or whether they had to act. The issue tracker gains the verb tracking, answering for every condition key an issue links which issues link it and where each stands; the operator channel's router asks it outside the work and says one line from it in every condition's message, per hq ADR 0285.
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Which issues track which conditions (novox/hq issue 400, ADR 0285).
|
||||
|
||||
const mountsKey = "module.mounts.shanks.unhealthy"
|
||||
|
||||
func TestTrackingAnswersTheIssuesThatLinkEachCondition(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
now := clocked(h)
|
||||
a := h.open(t, "mounts fail on shanks", "mesh-catalog")
|
||||
b := h.open(t, "a wait for the operator's secret", "mesh-controller")
|
||||
c := h.open(t, "rolled-back is repaired by hand", "mesh-controller")
|
||||
d := h.open(t, "unrelated", "hq")
|
||||
for _, l := range []LinkAsk{
|
||||
{Number: a, Kind: LinkCondition, Target: mountsKey},
|
||||
{Number: b, Kind: LinkCondition, Target: mountsKey},
|
||||
{Number: c, Kind: LinkCondition, Target: "mesh.hand-acts.rolled-back.healer-wanted"},
|
||||
{Number: d, Kind: LinkRelatesTo, Target: "365"},
|
||||
} {
|
||||
if _, err := h.Link(l); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if _, err := h.Move(MoveAsk{Number: b, Status: StatusFixing, By: "fix-386@g14"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := h.Set(SetAsk{Number: c, Needs: "operator"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
*now = now.Add(25 * time.Hour)
|
||||
|
||||
got, err := h.Tracking(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got.Conditions) != 2 || got.Said != "Issues track 2 conditions." {
|
||||
t.Fatalf("%+v", got)
|
||||
}
|
||||
m := got.Conditions[mountsKey]
|
||||
if len(m) != 2 || m[0].Number != a || m[1].Number != b {
|
||||
t.Fatalf("mounts: %+v", m)
|
||||
}
|
||||
if m[0].Worker != "" || m[0].Status != StatusOpen {
|
||||
t.Errorf("nobody works on %d: %+v", a, m[0])
|
||||
}
|
||||
if m[1].Worker != "fix-386@g14" || m[1].Since == nil || m[1].Active == nil || !m[1].Stale || m[1].Status != StatusFixing {
|
||||
t.Errorf("the worker of %d, gone quiet a day: %+v", b, m[1])
|
||||
}
|
||||
if r := got.Conditions["mesh.hand-acts.rolled-back.healer-wanted"]; len(r) != 1 || r[0].Number != c || r[0].Needs != "operator" {
|
||||
t.Errorf("rolled-back: %+v", r)
|
||||
}
|
||||
|
||||
// Only the keys asked; one nobody links is said.
|
||||
only, err := h.Tracking([]string{"machine.ace.silent"})
|
||||
if err != nil || len(only.Conditions) != 0 || only.Said != "No issue tracks machine.ace.silent." {
|
||||
t.Fatalf("%+v %v", only, err)
|
||||
}
|
||||
if _, err := h.Tracking([]string{"a key\nwith a line"}); err == nil {
|
||||
t.Fatal("a key not of a condition key's shape was taken")
|
||||
}
|
||||
}
|
||||
|
||||
// A resolved issue comes after those not closed, and the answer holds at most MostTrackingPerCondition for a key.
|
||||
func TestTrackingPutsOpenIssuesFirstAndIsBounded(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
var numbers []uint64
|
||||
for i := 0; i < MostTrackingPerCondition+2; i++ {
|
||||
n := h.open(t, "one of many", "hq")
|
||||
if _, err := h.Link(LinkAsk{Number: n, Kind: LinkCondition, Target: mountsKey}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
numbers = append(numbers, n)
|
||||
}
|
||||
first := numbers[0]
|
||||
h.fixing(t, first)
|
||||
if _, err := h.Move(MoveAsk{Number: first, Status: StatusResolved, LocatedIn: []string{"hq"}, FixedBy: []string{"hq#1"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := h.Tracking(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
list := got.Conditions[mountsKey]
|
||||
if len(list) != MostTrackingPerCondition {
|
||||
t.Fatalf("%d issues, want %d", len(list), MostTrackingPerCondition)
|
||||
}
|
||||
for _, is := range list {
|
||||
if is.Number == first {
|
||||
t.Fatalf("the resolved issue was not put last: %+v", list)
|
||||
}
|
||||
}
|
||||
if list[0].Number != numbers[1] {
|
||||
t.Errorf("not by number: %+v", list)
|
||||
}
|
||||
}
|
||||
|
||||
// Where a fix stands, one word per case, in DeliveryLine's order.
|
||||
func TestFixStateSaysWhereTheFixIs(t *testing.T) {
|
||||
merged := true
|
||||
cases := []struct {
|
||||
name string
|
||||
is Issue
|
||||
want string
|
||||
}{
|
||||
{"no fix named", Issue{}, ""},
|
||||
{"nothing heard", Issue{FixedBy: []string{"mesh-catalog#1"}}, "unheard"},
|
||||
{"not merged", Issue{FixedBy: []string{"mesh-catalog#1"}, Fixes: []Fix{{PullRequest: "mesh-catalog#1", State: "open"}}}, "not-merged"},
|
||||
{"merged", Issue{FixedBy: []string{"mesh-catalog#1"}, Fixes: []Fix{{PullRequest: "mesh-catalog#1", State: "merged", Merged: merged}}}, "merged"},
|
||||
{"held", Issue{FixedBy: []string{"mesh-catalog#1"}, Fixes: []Fix{{PullRequest: "mesh-catalog#1", State: "held", Merged: merged}}}, "held"},
|
||||
{"failed", Issue{FixedBy: []string{"mesh-catalog#1"}, Fixes: []Fix{{PullRequest: "mesh-catalog#1", State: "failed", Merged: merged}}}, "failed"},
|
||||
{"delivered", Issue{FixedBy: []string{"mesh-catalog#1"}, Delivered: []string{"mesh-catalog#1"}}, "delivered"},
|
||||
{"one of two failed", Issue{FixedBy: []string{"mesh-catalog#1", "mesh-controller#2"}, Delivered: []string{"mesh-catalog#1"},
|
||||
Fixes: []Fix{{PullRequest: "mesh-controller#2", State: "stopped", Merged: merged}}}, "failed"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := FixState(&c.is); got != c.want {
|
||||
t.Errorf("%s: %q, want %q (its line: %q)", c.name, got, c.want, DeliveryLine(&c.is))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The verb as the tool runner calls it; its answer carries no report, note or title (nothing the router does not
|
||||
// need), and so no secret an issue's words might hold.
|
||||
func TestTheTrackingVerb(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
n := h.open(t, "mounts fail", "hq")
|
||||
if _, err := h.Link(LinkAsk{Number: n, Kind: LinkCondition, Target: mountsKey}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := h.Note(n, "the share's password=hunter2 was typed here"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out, err := h.call(t, "tracking", map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, _ := json.Marshal(out)
|
||||
if !strings.Contains(string(raw), `"`+mountsKey+`":[{"number":`) {
|
||||
t.Fatalf("%s", raw)
|
||||
}
|
||||
for _, leak := range []string{"hunter2", "mounts fail", "seen:"} {
|
||||
if strings.Contains(string(raw), leak) {
|
||||
t.Errorf("the answer carries %q: %s", leak, raw)
|
||||
}
|
||||
}
|
||||
if _, err := h.call(t, "tracking", map[string]any{"conditions": []string{mountsKey}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := h.call(t, "tracking", map[string]any{"number": 1}); err == nil {
|
||||
t.Fatal("an argument it does not take was accepted")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user