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 delivered
The operator asked for the judgement that already reads the backlog to fill what an issuer cannot know, and for dependencies between issues, an issue being workable only once what it waits for is on the machines. The triage fills only empty values, adds links and removes none; readiness follows mesh-delivery's own events, said once per spell, through a duplicate to its original, and loud when a delivery cannot be asked.
609 lines
22 KiB
Go
609 lines
22 KiB
Go
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"reflect"
|
||
"strings"
|
||
"sync"
|
||
"testing"
|
||
"time"
|
||
|
||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||
)
|
||
|
||
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"},
|
||
}
|
||
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(), "may have been written") || !strings.Contains(err.Error(), "get 367") {
|
||
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)
|
||
}
|
||
}
|
||
|
||
// lostEvery writes the issue on its first create and then answers every create with a failure.
|
||
type lostEvery struct{ *memory }
|
||
|
||
func (l lostEvery) Create(state, key string, value any) (uint64, error) {
|
||
if state == IssuesState {
|
||
_, _ = l.memory.Create(state, key, value)
|
||
return 0, errors.New("no answer in 30 s")
|
||
}
|
||
return l.memory.Create(state, key, value)
|
||
}
|
||
|
||
// unreadable answers a taken key with a refusal and cannot read it back.
|
||
type unreadable struct{ *memory }
|
||
|
||
func (u unreadable) Create(state, key string, value any) (uint64, error) {
|
||
if state == IssuesState {
|
||
return 0, fmt.Errorf("%s: %w", key, stdio.ErrChanged)
|
||
}
|
||
return u.memory.Create(state, key, value)
|
||
}
|
||
func (u unreadable) Get(state, key string) (*Entry, error) {
|
||
if state == IssuesState {
|
||
return nil, errors.New("no answer in 30 s")
|
||
}
|
||
return u.memory.Get(state, key)
|
||
}
|
||
|
||
// An open whose write may have landed never says nothing was opened (the review of 2026-10-10).
|
||
func TestAnOpenWhoseWriteMayHaveLandedNeverSaysNothingWasOpened(t *testing.T) {
|
||
h := newHarness(t)
|
||
h.Store = lostEvery{h.mem}
|
||
if o, err := h.Open(OpenAsk{Title: "t", Report: "r"}); err != nil || o.Number != 365 {
|
||
t.Fatalf("a write that landed, every answer lost: %v %v", o, err)
|
||
}
|
||
h.Store = unreadable{h.mem}
|
||
_, err := h.Open(OpenAsk{Title: "t", Report: "r"})
|
||
if err == nil || strings.Contains(err.Error(), "nothing was opened") || !strings.Contains(err.Error(), "get 366") {
|
||
t.Fatalf("a taken key that cannot be read back: %v", 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 != StatusOpen {
|
||
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 {
|
||
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 != StatusDiagnosing {
|
||
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: LinkCondition, Target: "token=abcdefgh123"}, "secret"},
|
||
{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.(*View).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 modules/nats",
|
||
"mesh-catalog modules/does-not-exist": "",
|
||
"mesh-catalog checks/words": "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)
|
||
}
|
||
}
|
||
}
|