Files
mesh-catalog/modules/mesh-issues/cmd/mesh-issues/import_test.go
T
jochen 983d434623
mesh/merge-gate fail: builds mesh-issues → novox; no bus step; a manifest the change touches fails the module check: modules/mesh-issues/module.json: this …
mesh/repo-check fail: its merge-check.sh failed: files asking for a setting without saying whether it is trusted: 25
mesh/delivery superseded: a newer head of the same pull request
mesh-issues: keep every issue in the log changes and the bucket as its fold, so the record outlives the bucket's cap and a week of events (hq ADR 0300, issues 501, 502)
Every write appends one operation at the issue's last sequence before the
bucket is written; a refused append writes nothing. The start carries what
the bucket holds, refolds what is behind and indexes every issue in memory,
so list answers from memory with a ranked search and a before cursor.
2026-10-12 02:41:54 +02:00

381 lines
14 KiB
Go

package main
import (
"os"
"path/filepath"
"reflect"
"strings"
"sync"
"testing"
)
// The import of slice 3 (to-be 53 §8) over the real reports in testdata/hq, against the tracker kept in
// this process: it creates each issue at its own number, touches no counter, never overwrites, and run
// again changes nothing and says so.
// sampleRecords is the record played from testdata/hq, at a commit the test names.
type sampleRecords struct {
dirRecords
commit string
// moveTo, when set, is the commit every read after the first answers: the record moving mid-import.
moveTo string
mu sync.Mutex
reads int
}
func newSample(t *testing.T) *sampleRecords {
t.Helper()
return &sampleRecords{dirRecords: dirRecords{dir: filepath.Join("testdata", "hq")}, commit: "1ea678c6bd48f62a45db3b5e4b21b9c46867d31a"}
}
func (s *sampleRecords) Standing() (string, error) { return s.commit, nil }
func (s *sampleRecords) Read(path string) (string, string, error) {
content, _, err := s.dirRecords.Read(path)
if err != nil {
return "", "", err
}
s.mu.Lock()
defer s.mu.Unlock()
s.reads++
if s.moveTo != "" && s.reads > 1 {
return content, s.moveTo, nil
}
return content, s.commit, nil
}
var sampleNumbers = []uint64{7, 43, 80, 150, 236, 345, 362}
func TestTheImportCreatesEveryFolderAtItsOwnNumberAndLeavesTheCounterAlone(t *testing.T) {
h := newHarness(t)
rec := newSample(t)
out, err := h.Import(ImportAsk{Commit: "1ea678c"}, rec)
if err != nil {
t.Fatal(err)
}
if out.Folders != 7 || out.Created != 7 || out.Filled+out.Same+out.Differs+out.Refused != 0 || out.DryRun {
t.Fatalf("%+v", out)
}
if !reflect.DeepEqual(out.ByStatus, map[string]int{StatusResolved: 5, StatusDiagnosing: 1, StatusWontfix: 1}) {
t.Fatalf("by status %v", out.ByStatus)
}
if out.Announced != 7 || len(h.said()) != 7 || h.said()[0].Event != EventImported {
t.Fatalf("announced %d, said %v", out.Announced, h.said())
}
for i, n := range sampleNumbers {
if out.Rows[i].Number != n || out.Rows[i].Action != ActionCreate {
t.Fatalf("row %d: %+v", i, out.Rows[i])
}
}
if h.mem.counter(t) != 0 {
t.Fatalf("the import moved the counter to %d", h.mem.counter(t))
}
// A report names only issues that exist: 362's report names issues the sample does not hold, which are
// said and not linked; its links to decisions stay.
if is, _ := h.Get(362); len(is.Links) == 0 {
t.Fatalf("362 has no links: %+v", is.Links)
}
for _, l := range (func() *Issue { is, _ := h.Get(362); return is })().Links {
if l.Kind == LinkRelatesTo {
n, _ := numberOfKey(l.Target)
if _, err := h.Get(n); err != nil {
t.Fatalf("362 links issue %d, which does not exist", n)
}
}
}
// The acceptance: get shows what the folder said, and who/when is the import, with the commit.
is, err := h.Get(362)
if err != nil {
t.Fatal(err)
}
if is.Status != StatusResolved || is.Record != "04-ISSUES/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person" ||
len(is.LocatedIn) != 2 || is.OpenedBy != ImportedBy || is.Opened.Format("2006-01-02") != "2026-10-10" ||
is.Imported == nil || is.Imported.Commit != rec.commit || is.Imported.From != is.Record+"/00-report.md" ||
is.Imported.By != Unproven || is.Imported.At != h.now() || !strings.HasPrefix(is.Report, "## Symptom") {
t.Fatalf("362 kept as %+v", is)
}
if is, _ := h.Get(362); is.Replay != "R362" || !reflect.DeepEqual(is.FixedBy, []string{"mesh-catalog#171, mesh-controller#197"}) {
t.Fatalf("362 kept as %+v", is)
}
if is, _ := h.Get(236); is.Replay != "R236" || is.Closed != nil {
t.Fatalf("236 kept as %+v", is)
}
if is, _ := h.Get(43); is.Closed == nil || is.Closed.Format("2006-01-02") != "2026-09-12" {
t.Fatalf("43 kept as %+v", is)
}
// The owners hq named that the registered list does not know are kept, and marked.
if is, _ := h.Get(7); !reflect.DeepEqual(is.LocatedInUnverified, []string{"hal (the instance)"}) || len(is.LocatedIn) != 3 {
t.Fatalf("7 kept as %+v", is)
}
if !reflect.DeepEqual(out.UnverifiedOwners["hal (the instance)"], []uint64{7}) {
t.Fatalf("unverified %v", out.UnverifiedOwners)
}
// The next open takes the number after the setting, as if nothing had been imported below it.
if got := h.open(t, "after the import"); got != 365 {
t.Fatalf("opened %d after the import, want 365", got)
}
l, _ := h.List(ListAsk{Status: StatusResolved})
if l.Matched != 5 || l.Rows[0].Number != 362 {
t.Fatalf("listed %+v", l)
}
}
func TestASecondImportChangesNothingAndSaysSo(t *testing.T) {
h := newHarness(t)
if _, err := h.Import(ImportAsk{}, newSample(t)); err != nil {
t.Fatal(err)
}
before := h.mem.snapshot(IssuesState)
said := len(h.said())
out, err := h.Import(ImportAsk{}, newSample(t))
if err != nil {
t.Fatal(err)
}
if out.Created != 0 || out.Filled != 0 || out.Same != 7 || out.Differs != 0 || out.Refused != 0 || !strings.Contains(out.Said, "Nothing changed") {
t.Fatalf("%+v", out)
}
if !reflect.DeepEqual(h.mem.snapshot(IssuesState), before) || len(h.said()) != said {
t.Fatal("a second import changed an issue or said an event")
}
}
func TestAnIssueTheTrackerHoldsDifferentlyIsNeverOverwritten(t *testing.T) {
h := newHarness(t)
if _, err := h.Import(ImportAsk{}, newSample(t)); err != nil {
t.Fatal(err)
}
// The tracker is the truth once imported: 345 moves on, 80 takes a note.
if _, err := h.Move(MoveAsk{Number: 345, Status: StatusLocated, LocatedIn: []string{"mesh-catalog modules/mesh-issues"}}); err != nil {
t.Fatal(err)
}
if _, err := h.Note(80, "a note"); err != nil {
t.Fatal(err)
}
before := h.mem.snapshot(IssuesState)
out, err := h.Import(ImportAsk{}, newSample(t))
if err != nil {
t.Fatal(err)
}
if out.Differs != 1 || out.Same != 6 || out.Created != 0 {
t.Fatalf("%+v", out)
}
var row ImportRow
for _, r := range out.Rows {
if r.Number == 345 {
row = r
}
}
if row.Action != ActionDiffers || !strings.Contains(row.Why, "status, located-in") || !strings.Contains(row.Why, "the tracker is the truth") {
t.Fatalf("row %+v", row)
}
if !reflect.DeepEqual(h.mem.snapshot(IssuesState), before) {
t.Fatal("the import changed an issue the tracker holds")
}
}
func TestAFolderBackedIssueTheTrackerOpenedTakesItsStatusFromItsFolderOnce(t *testing.T) {
h := newHarness(t)
// Before slice 3, an issue opened with a folder had no status in the tracker; its folder kept it.
h.mem.put(IssuesState, "362", Issue{Number: 362, Title: "opened through the tracker", Report: "r", OpenedBy: Unproven,
Record: "04-ISSUES/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person", Opening: "x",
Notes: []Note{{By: Unproven, Text: "kept"}}})
h.settings.FirstNumber = 300 // so 362 is a number the tracker gave
out, err := h.Import(ImportAsk{}, newSample(t))
if err != nil {
t.Fatal(err)
}
// 345 is past the first number too, with no issue in the tracker: refused, as a folder made without it.
if out.Filled != 1 || out.Created != 5 || out.Refused != 1 {
t.Fatalf("%+v", out)
}
is, _ := h.Get(362)
if is.Status != StatusResolved || is.Title != "opened through the tracker" || is.Report != "r" || len(is.Notes) != 1 ||
is.OpenedBy != Unproven || is.Imported == nil || len(is.LocatedIn) != 2 || len(is.Links) == 0 {
t.Fatalf("362 filled as %+v", is)
}
if !strings.Contains(strings.Join(out.Odd, "\n"), "362: the tracker's title") {
t.Fatalf("odd %v", out.Odd)
}
out, _ = h.Import(ImportAsk{}, newSample(t))
if out.Filled != 0 || out.Same != 6 || out.Refused != 1 {
t.Fatalf("second: %+v", out)
}
}
func TestATrackerIssueWithoutAStatusAndWithoutAFolderBecomesOpen(t *testing.T) {
h := newHarness(t)
h.mem.put(IssuesState, "375", Issue{Number: 375, Title: "t", Report: "r", OpenedBy: Unproven, Record: "04-ISSUES/375-not-merged", Opening: "x"})
out, err := h.Import(ImportAsk{DryRun: true}, newSample(t))
if err != nil {
t.Fatal(err)
}
if out.Filled != 1 || out.Rows[len(out.Rows)-1].Action != ActionWould+ActionFill || out.Rows[len(out.Rows)-1].Number != 375 {
t.Fatalf("dry: %+v", out)
}
if is, _ := h.Get(375); is.Status != "" {
t.Fatal("a dry run wrote a status")
}
if _, err := h.Import(ImportAsk{}, newSample(t)); err != nil {
t.Fatal(err)
}
if is, _ := h.Get(375); is.Status != StatusOpen || is.Imported == nil || is.Imported.From != "" {
t.Fatalf("375 as %+v", is)
}
}
func TestADryRunReadsEverythingAndWritesNothing(t *testing.T) {
h := newHarness(t)
out, err := h.Import(ImportAsk{DryRun: true}, newSample(t))
if err != nil {
t.Fatal(err)
}
if !out.DryRun || out.Created != 7 || out.Rows[0].Action != ActionWould+ActionCreate || !strings.Contains(out.Said, "would create 7") ||
!strings.Contains(out.Said, "Nothing was written") {
t.Fatalf("%+v", out)
}
if keys, _ := h.mem.Keys(IssuesState); len(keys) != 0 || len(h.said()) != 0 || h.mem.counter(t) != 0 {
t.Fatal("a dry run wrote")
}
}
func TestTheImportRefusesWhatItMustNotWrite(t *testing.T) {
// A folder numbered past the counter: the counter's number, not a folder's to take. Nothing is imported.
h := newHarness(t)
h.mem.put(CountersState, IssueCounter, 100)
if _, err := h.Import(ImportAsk{}, newSample(t)); err == nil || !strings.Contains(err.Error(), "past the counter") {
t.Fatalf("past the counter: %v", err)
}
if keys, _ := h.mem.Keys(IssuesState); len(keys) != 0 {
t.Fatal("imported past the counter")
}
// A folder at or past the first number the tracker holds no issue for: made without the tracker.
h = newHarness(t)
h.settings.FirstNumber = 300
out, err := h.Import(ImportAsk{}, newSample(t))
if err != nil {
t.Fatal(err)
}
if out.Refused != 2 || out.Created != 5 {
t.Fatalf("%+v", out)
}
for _, r := range out.Rows {
if (r.Number == 345 || r.Number == 362) && (r.Action != ActionRefused || !strings.Contains(r.Why, "made without it")) {
t.Fatalf("row %+v", r)
}
}
// The record standing elsewhere than asked.
h = newHarness(t)
if _, err := h.Import(ImportAsk{Commit: "abcdef0"}, newSample(t)); err == nil || !strings.Contains(err.Error(), "stands at hq commit 1ea678c6bd48") {
t.Fatalf("another commit: %v", err)
}
if _, err := h.Import(ImportAsk{Commit: "main"}, newSample(t)); err == nil || !strings.Contains(err.Error(), "hex digits") {
t.Fatalf("a commit that is no commit: %v", err)
}
// A status-less issue the tracker holds under another folder's name is left alone and said.
h = newHarness(t)
h.settings.FirstNumber = 300
h.mem.put(IssuesState, "345", Issue{Number: 345, Title: "t", Report: "r", OpenedBy: Unproven, Record: "04-ISSUES/345-another-name", Opening: "x"})
out, err = h.Import(ImportAsk{}, newSample(t))
if err != nil {
t.Fatal(err)
}
for _, r := range out.Rows {
if r.Number == 345 && (r.Action != ActionDiffers || !strings.Contains(r.Why, "not its own")) {
t.Fatalf("row %+v", r)
}
}
if is, _ := h.Get(345); is.Status != "" {
t.Fatal("a status was taken from a folder that is not the issue's")
}
// The record moving while it is read.
h = newHarness(t)
rec := newSample(t)
rec.moveTo = "ffffffffffff"
if _, err := h.Import(ImportAsk{}, rec); err == nil || !strings.Contains(err.Error(), "moved while it was read") {
t.Fatalf("a moving record: %v", err)
}
if keys, _ := h.mem.Keys(IssuesState); len(keys) != 0 {
t.Fatal("imported from a moving record")
}
// An issue that appears under the number while the import runs is never overwritten.
h = newHarness(t)
h.log.failNext = createAttempts
out, err = h.Import(ImportAsk{}, newSample(t))
if err != nil {
t.Fatal(err)
}
if out.Refused != 1 || out.Created != 6 || !strings.Contains(out.Rows[0].Why, "could not be written") {
t.Fatalf("%+v", out)
}
}
func TestAReportThatCannotBeReadIsRefusedAloneAndSaidWhy(t *testing.T) {
dir := t.TempDir()
write := func(folder, text string) {
if err := os.MkdirAll(filepath.Join(dir, IssuesFolder, folder), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, IssuesFolder, folder, "00-report.md"), []byte(text), 0o644); err != nil {
t.Fatal(err)
}
}
write("001-good", "---\nstatus: open\nopened: 2026-10-01\nlocated-in: [mesh-controller]\n---\n# 1. Good\n\nbody\n")
write("002-sixth-status", "---\nstatus: fixed\n---\n# 2. t\n\nbody\n")
write("003-no-title", "---\nstatus: open\n---\nbody\n")
write("004-a-secret", "---\nstatus: open\n---\n# 4. t\n\nthe key is sk-ant-api03-abcdefghijklmnopqrstuvwxyz\n")
write("005-twice", "---\nstatus: open\n---\n# 5. t\n\nbody\n")
write("005-again", "---\nstatus: open\n---\n# 5. t\n\nbody\n")
write("006-no-report", "")
if err := os.Remove(filepath.Join(dir, IssuesFolder, "006-no-report", "00-report.md")); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(dir, IssuesFolder, "README"), 0o755); err != nil {
t.Fatal(err)
}
h := newHarness(t)
out, err := h.Import(ImportAsk{}, dirRecords{dir: dir})
if err != nil {
t.Fatal(err)
}
if out.Folders != 7 || out.Created != 1 || out.Refused != 6 {
t.Fatalf("%+v", out)
}
why := map[uint64]string{}
for _, r := range out.Rows {
why[r.Number] = r.Why
}
for n, want := range map[uint64]string{2: "not one of", 3: "no title", 4: "an Anthropic key", 5: "two folders are numbered 5", 6: "could not be read"} {
if !strings.Contains(why[n], want) {
t.Errorf("%d: %q lacks %q", n, why[n], want)
}
}
if !strings.Contains(strings.Join(out.Odd, "\n"), "README is not an issue folder") {
t.Fatalf("odd %v", out.Odd)
}
if is, err := h.Get(1); err != nil || is.Status != StatusOpen || is.Title != "Good" {
t.Fatalf("1: %+v %v", is, err)
}
}
func TestTheImportVerbReadsItsArgumentsStrictly(t *testing.T) {
h := newHarness(t)
h.Records = newSample(t)
if _, err := h.call(t, "import", map[string]any{"path": "/tmp"}); err == nil || !strings.Contains(err.Error(), "does not take path") {
t.Fatalf("an unknown argument: %v", err)
}
if _, err := h.call(t, "import", map[string]any{"dry-run": "yes"}); err == nil {
t.Fatal("dry-run as text")
}
out, err := h.call(t, "import", map[string]any{"dry-run": true, "commit": "1ea678c"})
if err != nil {
t.Fatal(err)
}
if got := out.(*Imported); !got.DryRun || got.Created != 7 {
t.Fatalf("%+v", got)
}
}