Files
mesh-catalog/modules/mesh-issues/cmd/mesh-issues/tracker_test.go
T
jochen a73b04843d
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
mesh/delivery-group group feat/399-fixing-status delivered: every member is delivered
mesh-issues: a take-over note records who acted, not the worker named; tests for the remaining rules (hq issue 399)
The note's by is who acted as the runtime proves it (ADR 0275 §5); the
note's text names both workers. Tests for a resolved issue corrected, link
and set keeping the worker current, and a take-over reason with a secret's
shape. The ready-to-verify note says an issue resolves from fixing.
2026-10-10 21:10:36 +02:00

653 lines
24 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, By: "fix-365@g14"}); 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, ADR 0283): one case per row, each refused case changing nothing.
// A case starts from open, or, with from, from that status reached first (fixing by fix@g14).
func TestTheCyclesRulesHoldAtTheWrite(t *testing.T) {
const fx = StatusFixing
cases := []struct {
name string
located []string // at open
from string // the status it is moved to first; "" stays 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, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"hq#1"}}, "without located-in"},
{"resolved without fixed-by", []string{"hq"}, fx, MoveAsk{Status: StatusResolved}, "without fixed-by"},
{"resolved", []string{"hq"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"hq#280"}}, ""},
{"resolved from open", []string{"hq"}, "", MoveAsk{Status: StatusResolved, FixedBy: []string{"hq#280"}}, "only from fixing"},
{"resolved from located", []string{"hq"}, StatusLocated, MoveAsk{Status: StatusResolved, FixedBy: []string{"hq#280"}}, "only from fixing"},
{"resolved from diagnosing", []string{"hq"}, StatusDiagnosing, MoveAsk{Status: StatusResolved, FixedBy: []string{"hq#280"}}, "only from fixing"},
{"a core issue resolved without a replay", []string{"mesh-controller"}, fx, 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"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-catalog#9"}}, "core issue"},
{"a core issue resolved with a replay", []string{"mesh-tools node-tools/internal/bus"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-tools#25"}, Replay: "R361"}, ""},
{"a core issue resolved with replay-none", []string{"mesh-sdk"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-sdk#16"}, ReplayNone: "it needs two machines"}, ""},
{"a replay that names none", []string{"mesh-sdk"}, fx, MoveAsk{Status: StatusResolved, FixedBy: []string{"mesh-sdk#16"}, Replay: "yes"}, "names no replay"},
{"replay and replay-none", []string{"mesh-sdk"}, fx, 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"}, ""},
{"wontfix from fixing", nil, fx, MoveAsk{Status: StatusWontfix, Reason: "the module is retired"}, ""},
{"a reason on another status", nil, "", MoveAsk{Status: StatusDiagnosing, By: "a@g14", Reason: "x"}, "what wontfix needs"},
{"to the status it has", nil, "", MoveAsk{Status: StatusOpen}, "already open"},
{"diagnosing", nil, "", MoveAsk{Status: StatusDiagnosing, By: "fix-365@g14"}, ""},
{"diagnosing without by", nil, "", MoveAsk{Status: StatusDiagnosing}, "needs by"},
{"fixing", nil, "", MoveAsk{Status: StatusFixing, By: "Jochen@shanks"}, ""},
{"fixing without by", nil, "", MoveAsk{Status: StatusFixing}, "needs by"},
{"fixing with a by that names no node", nil, "", MoveAsk{Status: StatusFixing, By: "an agent"}, "who@node"},
{"fixing with a by that carries a secret", nil, "", MoveAsk{Status: StatusFixing, By: "sk-ant-api03-abcdefghijklmnopqrstuvwxyz@g14"}, "carries no secret"},
{"by on another status", []string{"hq"}, "", MoveAsk{Status: StatusLocated, By: "a@g14"}, "only diagnosing and fixing"},
{"take-over on another status", []string{"hq"}, "", MoveAsk{Status: StatusLocated, TakeOver: "x"}, "take-over is for"},
{"take-over with nobody to take from", nil, "", MoveAsk{Status: StatusFixing, By: "a@g14", TakeOver: "x"}, "nobody works on"},
{"an amended design that is not one", nil, "", MoveAsk{Status: StatusDiagnosing, By: "a@g14", AmendedDesign: []string{"../etc/passwd"}}, "not a design document"},
{"a take-over reason that carries a secret", nil, StatusDiagnosing, MoveAsk{Status: StatusFixing, By: "b@ace", TakeOver: "the key is sk-ant-api03-abcdefghijklmnopqrstuvwxyz"}, "carries no secret"},
{"a fixed-by of two lines", []string{"hq"}, fx, 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...)
from := StatusOpen
switch c.from {
case "":
case StatusFixing:
h.fixing(t, n)
from = StatusFixing
case StatusDiagnosing:
if _, err := h.Move(MoveAsk{Number: n, Status: c.from, By: "fix@g14"}); err != nil {
t.Fatal(err)
}
from = c.from
default:
if _, err := h.Move(MoveAsk{Number: n, Status: c.from}); err != nil {
t.Fatal(err)
}
from = c.from
}
before, _ := h.mem.Get(IssuesState, keyOf(n))
events := len(h.said())
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()) != events {
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 != from || 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)
}
wantBy := Unproven
if worked(c.ask.Status) {
wantBy = strings.ToLower(c.ask.By)
if is.Worker == nil || is.Worker.By != wantBy || is.Worker.Since.IsZero() {
t.Fatalf("worker %+v", is.Worker)
}
} else if is.Worker != nil {
t.Fatalf("a worker kept at %s: %+v", is.Status, is.Worker)
}
ev := h.said()
if last := ev[len(ev)-1]; last.Event != EventMoved || last.Body["to"] != c.ask.Status || last.Body["from"] != from ||
last.Body["by"] != wantBy || last.Body["caller"] != Unproven {
t.Fatalf("event %+v", last)
}
})
}
}
func TestReopeningClearsWhenItClosed(t *testing.T) {
h := newHarness(t)
n := h.open(t, "x", "hq")
h.fixing(t, n)
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, By: "a@g14"}); !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, By: "a@g14"}); 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)
}
}
}