Add mesh-issues, the issue tracker, so an issue's number comes from one counter no two openers share (hq ADR 0275, to-be 53 slice 1)
mesh/merge-gate pass: builds new: modules/mesh-issues, sent nowhere; 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/merge-gate pass: builds new: modules/mesh-issues, sent nowhere; 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
The seat issue-tracker, held once for the mesh: open, get, list, move, note and link. The counter is advanced by the SDK's compare-and-set Increment (go/v0.1.12) and started once at the setting first-number (365, after hq's highest), never below an issue kept. The cycle's issue rules are refused at the write; every change is an event. The runtime does not yet say who called, so every change is recorded as by "unproven" and no argument can name a caller.
This commit is contained in:
@@ -0,0 +1,562 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTheFirstIssueGetsTheSettingsNumberAndEachNextOneMore(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
for want := uint64(365); want < 368; want++ {
|
||||
if got := h.open(t, "a symptom"); got != want {
|
||||
t.Fatalf("opened %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
is, err := h.Get(366)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if is.Status != StatusOpen || is.Title != "a symptom" || is.Report != "seen: a symptom" || is.OpenedBy != Unproven ||
|
||||
is.Opened.IsZero() || is.Closed != nil {
|
||||
t.Fatalf("kept %+v", is)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheCounterNeverStartsBelowAnIssueAlreadyKept(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
h.mem.put(IssuesState, "400", Issue{Number: 400, Title: "kept before", Status: StatusOpen, Opening: "x"})
|
||||
if got := h.open(t, "after it"); got != 401 {
|
||||
t.Fatalf("opened %d, want 401: the counter started below an issue kept", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheCounterIsStartedOnlyOnceAndTheSettingIsThenNotRead(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
h.open(t, "first")
|
||||
h.settings.FirstNumber = 10 // a later setting cannot move a number
|
||||
if got := h.open(t, "second"); got != 366 {
|
||||
t.Fatalf("opened %d, want 366", got)
|
||||
}
|
||||
h2 := newHarness(t)
|
||||
h2.settings.FirstNumber = 0
|
||||
if _, err := h2.Open(OpenAsk{Title: "t", Report: "r"}); err == nil || !strings.Contains(err.Error(), "first-number") {
|
||||
t.Fatalf("an unset first number: %v", err)
|
||||
}
|
||||
if h2.mem.counter(t) != 0 {
|
||||
t.Fatal("a counter was started without its first number")
|
||||
}
|
||||
}
|
||||
|
||||
func TestARefusedOpenSpendsNoNumber(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
refused := []OpenAsk{
|
||||
{Title: "", Report: "r"},
|
||||
{Title: "t", Report: " "},
|
||||
{Title: "two\nlines", Report: "r"},
|
||||
{Title: strings.Repeat("x", MostTitle+1), Report: "r"},
|
||||
{Title: "t", Report: strings.Repeat("x", MostReport+1)},
|
||||
{Title: "t", Report: "r", LocatedIn: []string{"somewhere-else"}},
|
||||
{Title: "t", Report: "r", LocatedIn: []string{"mesh-controllerx"}},
|
||||
{Title: "t", Report: "r", Record: "../escape"},
|
||||
{Title: "t", Report: "r", Record: "04-ISSUES/365-x"},
|
||||
{Title: "t", Report: "r", Record: "Upper"},
|
||||
{Title: "t", Report: "the key is sk-ant-api03-abcdefghijklmnopqrstuvwxyz"},
|
||||
{Title: "t\x1b[31m", Report: "r"},
|
||||
{Title: "t", Report: "r"},
|
||||
{Title: "t", Report: "r\x00"},
|
||||
{Title: "t", Report: "r", Record: "a-folder", LocatedIn: []string{"mesh-controller"}},
|
||||
}
|
||||
for i, a := range refused {
|
||||
if _, err := h.Open(a); err == nil {
|
||||
t.Errorf("case %d (%+v) was opened", i, a)
|
||||
}
|
||||
}
|
||||
if h.mem.counter(t) != 0 || len(h.said()) != 0 {
|
||||
t.Fatalf("a refused open moved the counter to %d or said %v", h.mem.counter(t), h.said())
|
||||
}
|
||||
if got := h.open(t, "valid"); got != 365 {
|
||||
t.Fatalf("the first valid open got %d, want 365", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnIssuesKeyIsNeverOverwrittenAndTheFaultIsSaid(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
h.open(t, "first") // 365, the counter started
|
||||
h.mem.put(IssuesState, "366", Issue{Number: 366, Title: "already here", Opening: "other"})
|
||||
_, err := h.Open(OpenAsk{Title: "second", Report: "r"})
|
||||
if err == nil || !strings.Contains(err.Error(), "already kept") || !strings.Contains(err.Error(), "366 stays unused") {
|
||||
t.Fatalf("open over a kept key: %v", err)
|
||||
}
|
||||
is, _ := h.Get(366)
|
||||
if is.Title != "already here" {
|
||||
t.Fatal("the kept issue was overwritten")
|
||||
}
|
||||
if got := h.open(t, "third"); got != 367 {
|
||||
t.Fatalf("the next open got %d, want 367", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestANumberTakenAndNotWrittenIsSaidAndNeverHandedOutAgain(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
h.open(t, "first")
|
||||
h.mem.fail[IssuesState] = errors.New("the bus did not answer")
|
||||
// One failure is tried again and succeeds: no gap.
|
||||
if got := h.open(t, "second"); got != 366 {
|
||||
t.Fatalf("got %d", got)
|
||||
}
|
||||
// Failing every time leaves a gap, said.
|
||||
failing := &failingStore{memory: h.mem, failCreates: createAttempts}
|
||||
h.Store = failing
|
||||
_, err := h.Open(OpenAsk{Title: "third", Report: "r"})
|
||||
if err == nil || !strings.Contains(err.Error(), "367 stays unused") {
|
||||
t.Fatalf("a write that never landed: %v", err)
|
||||
}
|
||||
h.Store = h.mem
|
||||
if got := h.open(t, "fourth"); got != 368 {
|
||||
t.Fatalf("got %d, want 368: a number is never handed out twice", got)
|
||||
}
|
||||
}
|
||||
|
||||
// failingStore fails the next creates of issues, as a bus that takes no write.
|
||||
type failingStore struct {
|
||||
*memory
|
||||
failCreates int
|
||||
}
|
||||
|
||||
func (f *failingStore) Create(state, key string, value any) (uint64, error) {
|
||||
if state == IssuesState && f.failCreates > 0 {
|
||||
f.failCreates--
|
||||
return 0, errors.New("the bus did not answer")
|
||||
}
|
||||
return f.memory.Create(state, key, value)
|
||||
}
|
||||
|
||||
// lostAnswer writes the issue and then says it failed, as a bus whose answer was lost.
|
||||
type lostAnswer struct {
|
||||
*memory
|
||||
once bool
|
||||
}
|
||||
|
||||
func (l *lostAnswer) Create(state, key string, value any) (uint64, error) {
|
||||
rev, err := l.memory.Create(state, key, value)
|
||||
if state == IssuesState && !l.once && err == nil {
|
||||
l.once = true
|
||||
return 0, errors.New("no answer in 30 s")
|
||||
}
|
||||
return rev, err
|
||||
}
|
||||
|
||||
func TestAWriteWhoseAnswerWasLostIsKnownAsItsOwn(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
h.Store = &lostAnswer{memory: h.mem}
|
||||
o, err := h.Open(OpenAsk{Title: "t", Report: "r"})
|
||||
if err != nil || o.Number != 365 {
|
||||
t.Fatalf("%v %v", o, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenSaysAnEventWithWhatChangedAndWhoChangedIt(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
o, err := h.Open(OpenAsk{Title: "a title", Report: "a report", LocatedIn: []string{"mesh-controller internal/broker"}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !o.Announced || o.By != Unproven || o.Status != StatusOpen || !strings.Contains(o.Said, "Opened issue 365") {
|
||||
t.Fatalf("answer %+v", o)
|
||||
}
|
||||
ev := h.said()
|
||||
if len(ev) != 1 || ev[0].Event != EventOpened || ev[0].Body["number"] != float64(365) || ev[0].Body["title"] != "a title" ||
|
||||
ev[0].Body["by"] != Unproven || ev[0].Body["status"] != StatusOpen {
|
||||
t.Fatalf("events %+v", ev)
|
||||
}
|
||||
if _, carried := ev[0].Body["report"]; carried {
|
||||
t.Fatal("the opened event carries the whole report; it says what changed, and get has the rest")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAChangeWhoseEventCannotBeSaidIsKeptAndSaysSo(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
h.emitErr = errors.New("the bus refused it")
|
||||
o, err := h.Open(OpenAsk{Title: "t", Report: "r"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o.Announced || !strings.Contains(o.Warning, "could not be said") {
|
||||
t.Fatalf("a change not announced was reported as announced: %+v", o)
|
||||
}
|
||||
if _, err := h.Get(o.Number); err != nil {
|
||||
t.Fatal("the change was not kept")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnIssueWithAFolderIsNumberedTitledAndPointsAtItsFolder(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
o, err := h.Open(OpenAsk{Title: "t", Report: "r", Record: "the-counter-skips-a-number"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o.Record != "04-ISSUES/365-the-counter-skips-a-number" || o.Status != "" {
|
||||
t.Fatalf("answer %+v", o)
|
||||
}
|
||||
_, err = h.Move(MoveAsk{Number: 365, Status: StatusDiagnosing})
|
||||
if err == nil || !strings.Contains(err.Error(), "keeps its status in its folder") {
|
||||
t.Fatalf("a move of an issue with a folder: %v", err)
|
||||
}
|
||||
if _, err := h.Note(365, "notes are taken"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
l, _ := h.List(ListAsk{})
|
||||
if l.Rows[0].Status != InFolder {
|
||||
t.Fatalf("listed %+v", l.Rows)
|
||||
}
|
||||
}
|
||||
|
||||
// The cycle's rules at the write (to-be 53 §5): one case per row, each refused case changing nothing.
|
||||
func TestTheCyclesRulesHoldAtTheWrite(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
located []string // at 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"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
n := h.open(t, "an issue", c.located...)
|
||||
before, _ := h.mem.Get(IssuesState, keyOf(n))
|
||||
c.ask.Number = n
|
||||
m, err := h.Move(c.ask)
|
||||
if c.refused != "" {
|
||||
if err == nil || !strings.Contains(err.Error(), c.refused) {
|
||||
t.Fatalf("want refused with %q, got %v", c.refused, err)
|
||||
}
|
||||
after, _ := h.mem.Get(IssuesState, keyOf(n))
|
||||
if after.Revision != before.Revision || string(after.Value) != string(before.Value) {
|
||||
t.Fatal("a refused move changed the issue")
|
||||
}
|
||||
if len(h.said()) != 1 {
|
||||
t.Fatalf("a refused move said %v", h.said())
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("refused: %v", err)
|
||||
}
|
||||
is, _ := h.Get(n)
|
||||
if is.Status != c.ask.Status || m.From != StatusOpen || 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)
|
||||
}
|
||||
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 {
|
||||
t.Fatalf("event %+v", last)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReopeningClearsWhenItClosed(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
n := h.open(t, "x", "hq")
|
||||
if _, err := h.Move(MoveAsk{Number: n, Status: StatusResolved, FixedBy: []string{"hq#1"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := h.Move(MoveAsk{Number: n, Status: StatusOpen}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
is, _ := h.Get(n)
|
||||
if is.Closed != nil || is.Status != StatusOpen {
|
||||
t.Fatalf("%+v", is)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAMoveOfAnIssueThatDoesNotExistIsRefused(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
if _, err := h.Move(MoveAsk{Number: 9, Status: StatusDiagnosing}); !errors.Is(err, ErrNoIssue) {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if _, err := h.Get(9); err == nil || !strings.Contains(err.Error(), "there is no issue 9") {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotesAreAppendedNeverEditedAndEachIsSaid(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
n := h.open(t, "x")
|
||||
for _, text := range []string{"first finding", "second, over\ntwo lines"} {
|
||||
if _, err := h.Note(n, text); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
is, _ := h.Get(n)
|
||||
if len(is.Notes) != 2 || is.Notes[0].Text != "first finding" || is.Notes[1].Text != "second, over\ntwo lines" ||
|
||||
is.Notes[0].By != Unproven {
|
||||
t.Fatalf("%+v", is.Notes)
|
||||
}
|
||||
ev := h.said()
|
||||
if ev[2].Event != EventNoted || ev[2].Body["text"] != "second, over\ntwo lines" || ev[2].Body["number"] != float64(n) {
|
||||
t.Fatalf("%+v", ev)
|
||||
}
|
||||
for _, bad := range []string{"", "\x07", "password=hunter2hunter2", strings.Repeat("x", MostNote+1)} {
|
||||
if _, err := h.Note(n, bad); err == nil {
|
||||
t.Errorf("note %q taken", bad)
|
||||
}
|
||||
}
|
||||
if _, err := h.Note(404, "x"); !errors.Is(err, ErrNoIssue) {
|
||||
t.Fatalf("a note on no issue: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestANoteThatWouldMakeTheIssueTooLargeIsRefusedAndNothingChanges(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
n := h.open(t, "x")
|
||||
note := strings.Repeat("word ", MostNote/5-1)
|
||||
var err error
|
||||
for i := 0; i < 40 && err == nil; i++ {
|
||||
_, err = h.Note(n, note)
|
||||
}
|
||||
if err == nil || !strings.Contains(err.Error(), "256 KiB") {
|
||||
t.Fatalf("no refusal: %v", err)
|
||||
}
|
||||
e, _ := h.mem.Get(IssuesState, keyOf(n))
|
||||
if len(e.Value) > MostIssueBytes {
|
||||
t.Fatalf("kept %d bytes", len(e.Value))
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentNotesAreAllKept(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
h.Sleep = time.Sleep
|
||||
n := h.open(t, "x")
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 20; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if _, err := h.Note(n, "a note"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
is, _ := h.Get(n)
|
||||
if len(is.Notes) != 20 {
|
||||
t.Fatalf("kept %d of 20 notes: a write over a stale read lost some", len(is.Notes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinksAreTypedAddedOnceAndRemoved(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
a, b := h.open(t, "a"), h.open(t, "b")
|
||||
good := []LinkAsk{
|
||||
{Kind: LinkDuplicates, Target: keyOf(b)},
|
||||
{Kind: LinkRelatesTo, Target: keyOf(b)},
|
||||
{Kind: LinkPullRequest, Target: "mesh-controller#246"},
|
||||
{Kind: LinkDecision, Target: "0275"},
|
||||
{Kind: LinkDesign, Target: "03-DESIGN/01-to-be/53-the-issue-tracker.md"},
|
||||
{Kind: LinkCondition, Target: "healer-wanted.ace.backup"},
|
||||
{Kind: LinkDelivery, Target: "d-2026-10-10.3"},
|
||||
}
|
||||
for _, l := range good {
|
||||
l.Number = a
|
||||
if _, err := h.Link(l); err != nil {
|
||||
t.Fatalf("%+v: %v", l, err)
|
||||
}
|
||||
}
|
||||
bad := []struct {
|
||||
l LinkAsk
|
||||
says string
|
||||
}{
|
||||
{LinkAsk{Kind: "blocks", Target: "1"}, "not a kind"},
|
||||
{LinkAsk{Kind: LinkDuplicates, Target: keyOf(a)}, "itself"},
|
||||
{LinkAsk{Kind: LinkDuplicates, Target: "999"}, "there is no issue 999"},
|
||||
{LinkAsk{Kind: LinkPullRequest, Target: "mesh-controller#246"}, "already has"},
|
||||
{LinkAsk{Kind: LinkPullRequest, Target: "../x#1"}, "target is"},
|
||||
{LinkAsk{Kind: LinkDecision, Target: "275"}, "four-digit"},
|
||||
{LinkAsk{Kind: LinkDesign, Target: "03-DESIGN/../../etc/passwd"}, "design document"},
|
||||
{LinkAsk{Kind: LinkCondition, Target: "a key\nwith a line"}, "condition"},
|
||||
{LinkAsk{Kind: LinkRelatesTo, Target: keyOf(b), Remove: true}, ""},
|
||||
{LinkAsk{Kind: LinkRelatesTo, Target: keyOf(b), Remove: true}, "no relates-to link"},
|
||||
}
|
||||
for _, c := range bad {
|
||||
c.l.Number = a
|
||||
_, err := h.Link(c.l)
|
||||
if c.says == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("%+v: %v", c.l, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err == nil || !strings.Contains(err.Error(), c.says) {
|
||||
t.Fatalf("%+v: want %q, got %v", c.l, c.says, err)
|
||||
}
|
||||
}
|
||||
is, _ := h.Get(a)
|
||||
if len(is.Links) != len(good)-1 {
|
||||
t.Fatalf("kept %d links", len(is.Links))
|
||||
}
|
||||
ev := h.said()
|
||||
last := ev[len(ev)-1]
|
||||
if last.Event != EventLinked || last.Body["removed"] != true || last.Body["kind"] != LinkRelatesTo || last.Body["by"] != Unproven {
|
||||
t.Fatalf("%+v", last)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListIsNewestFirstAndNarrows(t *testing.T) {
|
||||
h := newHarness(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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
l, err := h.List(ListAsk{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got []uint64
|
||||
for _, r := range l.Rows {
|
||||
got = append(got, r.Number)
|
||||
}
|
||||
if !reflect.DeepEqual(got, []uint64{367, 366, 365}) || l.Rows[1].Owner != "mesh-controller internal/catalogue" ||
|
||||
l.Rows[0].Status != StatusDiagnosing {
|
||||
t.Fatalf("%+v", l)
|
||||
}
|
||||
for _, c := range []struct {
|
||||
ask ListAsk
|
||||
want []uint64
|
||||
}{
|
||||
{ListAsk{Status: StatusOpen}, []uint64{366, 365}},
|
||||
{ListAsk{Owner: "mesh-controller"}, []uint64{366}},
|
||||
{ListAsk{Owner: "mesh-catalog"}, []uint64{365}},
|
||||
{ListAsk{Owner: "mesh-catalog modules/nats"}, []uint64{365}},
|
||||
{ListAsk{Phrase: "GHOST"}, []uint64{367}},
|
||||
{ListAsk{Phrase: "seen: a check"}, []uint64{366}},
|
||||
{ListAsk{Limit: 1}, []uint64{367}},
|
||||
{ListAsk{Status: StatusResolved}, nil},
|
||||
} {
|
||||
l, err := h.List(c.ask)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got []uint64
|
||||
for _, r := range l.Rows {
|
||||
got = append(got, r.Number)
|
||||
}
|
||||
if !reflect.DeepEqual(got, c.want) {
|
||||
t.Errorf("%+v: %v, want %v (%s)", c.ask, got, c.want, l.Said)
|
||||
}
|
||||
}
|
||||
if _, err := h.List(ListAsk{Status: "closed"}); err == nil {
|
||||
t.Fatal("an unknown status was listed")
|
||||
}
|
||||
}
|
||||
|
||||
// The caller is the bus credential, never an argument (ADR 0275 §5): no verb takes one, so none can be
|
||||
// named, and what is recorded is never what an argument said.
|
||||
func TestNoArgumentNamesWhoActed(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
for _, verb := range Verbs {
|
||||
for _, field := range []string{"by", "actor", "caller", "opened-by", "user"} {
|
||||
_, err := h.call(t, verb, map[string]any{field: "operator", "number": 1, "title": "t", "report": "r",
|
||||
"status": "open", "text": "x", "kind": LinkDecision, "target": "0001"})
|
||||
if err == nil || !strings.Contains(err.Error(), "does not take "+field) && !strings.Contains(err.Error(), field) {
|
||||
t.Errorf("%s with %s: %v", verb, field, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
out, err := h.call(t, "open", map[string]any{"title": "t", "report": "r"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if out.(*Opened).By != Unproven {
|
||||
t.Fatal("open recorded a caller nothing proved")
|
||||
}
|
||||
is, _ := h.Get(out.(*Opened).Number)
|
||||
raw, _ := json.Marshal(is)
|
||||
if strings.Contains(string(raw), "operator") {
|
||||
t.Fatal("an argument's name reached the record")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheVerbsReadTheirArgumentsStrictly(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
n := h.open(t, "x")
|
||||
refusals := []struct {
|
||||
verb string
|
||||
args map[string]any
|
||||
}{
|
||||
{"open", map[string]any{"title": 5, "report": "r"}},
|
||||
{"open", map[string]any{"title": "t", "report": "r", "located-in": []any{1}}},
|
||||
{"get", map[string]any{}},
|
||||
{"get", map[string]any{"number": -1}},
|
||||
{"get", map[string]any{"number": 1.5}},
|
||||
{"get", map[string]any{"number": "../365"}},
|
||||
{"get", map[string]any{"number": "0365"}},
|
||||
{"move", map[string]any{"number": n, "status": true}},
|
||||
{"note", map[string]any{"number": n}},
|
||||
{"link", map[string]any{"number": n, "kind": LinkDecision, "target": "0275", "remove": "yes"}},
|
||||
{"list", map[string]any{"limit": "all"}},
|
||||
}
|
||||
for _, r := range refusals {
|
||||
if _, err := h.call(t, r.verb, r.args); err == nil {
|
||||
t.Errorf("%s %v was taken", r.verb, r.args)
|
||||
}
|
||||
}
|
||||
if out, err := h.call(t, "get", map[string]any{"number": keyOf(n)}); err != nil || out.(*Issue).Number != n {
|
||||
t.Fatalf("a number as text: %v", err)
|
||||
}
|
||||
if out, err := h.call(t, "open", map[string]any{"title": "t", "report": "r", "located-in": "hq"}); err != nil ||
|
||||
!reflect.DeepEqual(out.(*Opened).LocatedIn, []string{"hq"}) {
|
||||
t.Fatalf("one owner as text: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnOwnersEntryIsTheRegisteredOneOrStartsWithIt(t *testing.T) {
|
||||
o := Owners{"mesh-catalog", "mesh-catalog modules/nats", "mesh-controller"}
|
||||
for entry, want := range map[string]string{
|
||||
"mesh-catalog": "mesh-catalog",
|
||||
"mesh-catalog modules/nats": "mesh-catalog modules/nats",
|
||||
"mesh-catalog modules/nats/module.go": "mesh-catalog",
|
||||
"mesh-catalog modules/nats (the gate)": "mesh-catalog modules/nats",
|
||||
"mesh-controller cmd/mesh-controller": "mesh-controller",
|
||||
"mesh-controllers": "",
|
||||
"hal": "",
|
||||
} {
|
||||
if got := o.owns(entry); got != want {
|
||||
t.Errorf("%q is owned by %q, want %q", entry, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user