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 delivered
mesh/delivery-group group feat/400-alert-names-its-issue delivered: every member is delivered
166 lines
6.0 KiB
Go
166 lines
6.0 KiB
Go
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")
|
|
}
|
|
}
|