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) } }