Files
mesh-catalog/modules/mesh-issues/cmd/mesh-issues/milestone_bus_test.go
T
jschoubben 76e4ec4dc6
mesh/delivery merged: source.main moved to novox/mesh-catalog@5a4ac4991395
mesh/delivery-group group feat/1781-milestone-records merged: every member is merged
mesh/check checking 76e4ec4dc680 on 26908a89506f | novox/mesh-catalog#506 in group feat/1781-milestone-records
mesh-issues and mesh-knowledge: milestones are records of the issue tracker, and progress reads them (ADR 0493 §1 to §4, issue 1781)
mesh-issues keeps each design's milestone in the state milestones (history 5), says milestone-set and
milestone-moved, and serves milestones, milestone-set and milestone-move. An issue's milestone is its own link
of kind milestone, so at most one milestone per issue is checked inside the issue's own write; kind commit
links a fixing commit. The triage links no milestone, pull request or commit. import with milestones=N seeds
a design's table once. mesh-knowledge's progress reads issue-tracker.milestones and its table parse is gone.

Squashed from four commits so the branch history does not carry a compiled bundle that was committed by
mistake (6 MB); the path is now in .gitignore.
2026-10-26 18:42:51 +01:00

85 lines
2.7 KiB
Go

//go:build integration
package main
import (
"encoding/json"
"fmt"
"strings"
"sync"
"testing"
)
// Issue 1781 (ADR 0493 §1, §2, §4), the module whole over a real bus: a milestone made by its verb is kept in the
// state the manifest declares, and two instances linking one issue to two milestones at the same moment leave it in
// exactly one; the other is refused naming the first, and the milestones and the list say the same.
func TestAnIssueRaceIntoTwoMilestonesOverTheBusEndsInOne(t *testing.T) {
b := newBus(t)
var events []event
var evMu sync.Mutex
settings := writeSettings(t, 365)
one, two := b.start(t, settings, &events, &evMu), b.start(t, settings, &events, &evMu)
must := func(raw json.RawMessage, err error) json.RawMessage {
t.Helper()
if err != nil {
t.Fatal(err)
}
return raw
}
must(one.call("open", map[string]any{"title": "a milestone's issue", "report": "seen"}))
must(one.call("milestone-set", map[string]any{"design": 47, "id": "R6", "outcome": "nodes apply their wants", "order": 1}))
must(two.call("milestone-set", map[string]any{"design": 47, "id": "3 fix", "outcome": "a group merges in one pass", "order": 2}))
errs := make([]error, 2)
var wg sync.WaitGroup
for k, c := range []struct {
i *instance
target string
}{{one, "47/R6"}, {two, "47/3-fix"}} {
wg.Add(1)
go func() {
defer wg.Done()
_, errs[k] = c.i.call("link", map[string]any{"number": 365, "kind": "milestone", "target": c.target})
}()
}
wg.Wait()
if (errs[0] == nil) == (errs[1] == nil) {
t.Fatalf("both or neither linked: %v", errs)
}
refused := errs[0]
if refused == nil {
refused = errs[1]
}
if !strings.Contains(refused.Error(), "is in milestone") {
t.Fatalf("refused with %v", refused)
}
var read MilestonesRead
_ = json.Unmarshal(must(two.call("milestones", map[string]any{"design": 47})), &read)
if len(read.Milestones) != 2 || read.Milestones[0].ID != "R6" || read.Milestones[1].ID != "3-fix" ||
len(read.Milestones[0].Issues)+len(read.Milestones[1].Issues) != 1 {
t.Fatalf("%+v", read.Milestones)
}
in := "47/R6"
if len(read.Milestones[1].Issues) == 1 {
in = "47/3-fix"
}
var l Listed
_ = json.Unmarshal(must(one.call("list", map[string]any{"linked": "milestone:" + in})), &l)
if len(l.Rows) != 1 || l.Rows[0].Number != 365 {
t.Fatalf("listed %+v", l)
}
var hist MilestonesRead
_ = json.Unmarshal(must(one.call("milestones", map[string]any{"one": "47/R6"})), &hist)
if len(hist.History) != 1 {
t.Fatalf("history %d values", len(hist.History))
}
evMu.Lock()
defer evMu.Unlock()
var kinds []string
for _, e := range events {
kinds = append(kinds, e.Key)
}
if fmt.Sprint(kinds) != "[opened milestone-set milestone-set linked]" {
t.Fatalf("events %v", kinds)
}
}