package main import ( "encoding/json" "errors" "fmt" "sort" "strconv" "sync" "testing" "time" stdio "git.novox.be/novox/mesh-sdk/go" ) // memory is Store kept in this process with the bus's compare-and-set: a create is refused when the key // has a value, an update when its revision is not the one given — each refusal stdio.ErrChanged, as the // runtime answers it. type memory struct { mu sync.Mutex rev uint64 data map[string]map[string]Entry // fail, when set, is the error the next write of that state answers. fail map[string]error } func newMemory() *memory { return &memory{data: map[string]map[string]Entry{IssuesState: {}, CountersState: {}, GroomingState: {}, ReservationsState: {}}, fail: map[string]error{}} } func (m *memory) bucket(state string) (map[string]Entry, error) { b, ok := m.data[state] if !ok { return nil, fmt.Errorf("%s is not declared", state) } return b, nil } func (m *memory) Get(state, key string) (*Entry, error) { m.mu.Lock() defer m.mu.Unlock() b, err := m.bucket(state) if err != nil { return nil, err } e, ok := b[key] if !ok { return nil, nil } return &Entry{Value: append(json.RawMessage(nil), e.Value...), Revision: e.Revision}, nil } func (m *memory) write(state, key string, value any, check func(Entry, bool) bool) (uint64, error) { raw, err := json.Marshal(value) if err != nil { return 0, err } m.mu.Lock() defer m.mu.Unlock() if err := m.fail[state]; err != nil { delete(m.fail, state) return 0, err } b, err := m.bucket(state) if err != nil { return 0, err } e, ok := b[key] if !check(e, ok) { return 0, fmt.Errorf("%s.%s: %w", state, key, stdio.ErrChanged) } m.rev++ b[key] = Entry{Value: raw, Revision: m.rev} return m.rev, nil } func (m *memory) Create(state, key string, value any) (uint64, error) { return m.write(state, key, value, func(_ Entry, there bool) bool { return !there }) } func (m *memory) Update(state, key string, value any, revision uint64) (uint64, error) { if revision == 0 { return 0, errors.New("an update names the revision it read") } return m.write(state, key, value, func(e Entry, there bool) bool { return there && e.Revision == revision }) } func (m *memory) put(state, key string, value any) { _, _ = m.write(state, key, value, func(Entry, bool) bool { return true }) } func (m *memory) Keys(state string) ([]string, error) { m.mu.Lock() defer m.mu.Unlock() b, err := m.bucket(state) if err != nil { return nil, err } var keys []string for k := range b { keys = append(keys, k) } sort.Strings(keys) return keys, nil } // Increment is the SDK's (mesh-sdk go/state.go), over this store. func (m *memory) Increment(state, key string, attempts int) (uint64, error) { for i := 0; i < attempts; i++ { e, err := m.Get(state, key) if err != nil { return 0, err } var next uint64 = 1 if e == nil { _, err = m.Create(state, key, next) } else { var n uint64 if err := json.Unmarshal(e.Value, &n); err != nil { return 0, err } next = n + 1 _, err = m.Update(state, key, next, e.Revision) } if err == nil { return next, nil } if !errors.Is(err, stdio.ErrChanged) { return 0, err } } return 0, fmt.Errorf("%s: %w", key, stdio.ErrChanged) } func (m *memory) counter(t *testing.T) uint64 { t.Helper() e, _ := m.Get(CountersState, IssueCounter) if e == nil { return 0 } n, err := strconv.ParseUint(string(e.Value), 10, 64) if err != nil { t.Fatalf("the counter holds %s", e.Value) } return n } // said is one event the tracker said. type said struct { Event string Body map[string]any } type harness struct { *Tracker mem *memory mu sync.Mutex events []said settings Settings emitErr error } func newHarness(t *testing.T) *harness { t.Helper() h := &harness{mem: newMemory(), settings: Settings{FirstNumber: 365, Owners: []string{ "hq", "mesh-catalog", "mesh-controller", "mesh-host", "mesh-sdk", "mesh-tools", "mesh-catalog modules/nats", "mesh-catalog modules/records", "mesh-catalog modules/mesh-issues"}}} clock := time.Date(2026, 10, 10, 12, 0, 0, 0, time.UTC) h.Tracker = &Tracker{ Store: h.mem, Emit: func(event string, body any) error { h.mu.Lock() defer h.mu.Unlock() if h.emitErr != nil { return h.emitErr } raw, _ := json.Marshal(body) var m map[string]any _ = json.Unmarshal(raw, &m) h.events = append(h.events, said{event, m}) return nil }, Settings: func() (Settings, error) { return h.settings, nil }, Now: func() time.Time { return clock }, Logf: t.Logf, Sleep: func(time.Duration) {}, } return h } func (h *harness) said() []said { h.mu.Lock() defer h.mu.Unlock() return append([]said(nil), h.events...) } func (h *harness) open(t *testing.T, title string, located ...string) uint64 { t.Helper() o, err := h.Open(OpenAsk{Title: title, Report: "seen: " + title, LocatedIn: located}) if err != nil { t.Fatalf("open %q: %v", title, err) } return o.Number } // fixing moves an issue to fixing, as the agent that starts its fix does (ADR 0283). func (h *harness) fixing(t *testing.T, n uint64) { t.Helper() if _, err := h.Move(MoveAsk{Number: n, Status: StatusFixing, By: "fix@g14"}); err != nil { t.Fatalf("move %d to fixing: %v", n, err) } } // call runs one verb as the tool runner does: by its tool, with arguments as JSON gives them. func (h *harness) call(t *testing.T, verb string, args map[string]any) (any, error) { t.Helper() for _, tool := range Tools(h.Tracker) { if tool.Name == Seat+"."+verb { raw, _ := json.Marshal(args) var m map[string]any _ = json.Unmarshal(raw, &m) return tool.Run(m) } } t.Fatalf("no verb %s", verb) return nil, nil } // snapshot is every value of a state, by key, as kept now. func (m *memory) snapshot(state string) map[string]string { m.mu.Lock() defer m.mu.Unlock() out := map[string]string{} for k, e := range m.data[state] { out[k] = string(e.Value) } return out }