package main import ( "encoding/json" "reflect" "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) } events, before := len(h.said()), h.mem.snapshot(IssuesState) out, err := h.call(t, "tracking", map[string]any{}) if err != nil { t.Fatal(err) } // A read: no event said, nothing written. if len(h.said()) != events || !reflect.DeepEqual(h.mem.snapshot(IssuesState), before) { t.Fatal("tracking said an event or wrote an issue") } 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") } }