Test that a seed indexer_pending could not answer ends nothing (issue 1966, note 197)
mesh/delivery merged: source.main moved to novox/mesh-delivery@532e4d740fdd
mesh/check checking e82da39d2999 on 532e4d740fdd

seedTakeIn with a failing call and with an unreadable answer leaves a previous run's take-in-waits finding
and a held wait standing; it fails if the error path seeded an empty answer.

Builds: design 47 "Taken in by the indexer it names" (A wait is bounded, said and recoverable); 1966/AC195-5

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-10 23:48:45 +01:00
parent 3ed2a3dd33
commit e82da39d29
@@ -13,6 +13,8 @@ import (
"git.novox.be/novox/mesh-sdk/go/process"
"git.novox.be/novox/mesh-sdk/go/views"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
"mesh-delivery-watchman/internal/watch"
"github.com/nats-io/nats.go/jetstream"
@@ -77,6 +79,34 @@ func TestRunNeedsItsCredential(t *testing.T) { // 1135/AC6
if err == nil || !strings.Contains(err.Error(), "MESH_BROKER_FILE") {
t.Fatalf("run = %v, want a refusal naming MESH_BROKER_FILE", err)
}
t.Run("1966/AC195-5: a seed indexer_pending could not answer, or answered unreadably, ends nothing", func(t *testing.T) {
for name, call := range map[string]func(string, any) (json.RawMessage, error){
"cannot be asked": func(string, any) (json.RawMessage, error) { return nil, errors.New("no answer") },
"unreadable": func(string, any) (json.RawMessage, error) { return json.RawMessage(`{"waits":`), nil },
} {
out := &findingsOut{findings: map[string]bool{}}
w := watch.New(out, time.Now)
const key = "m.00000000000000000042"
old := watch.Finding{Scope: watch.ScopeDelivery, ID: key, Kind: watch.KindTakeInWaits, Severity: watch.Warning, Source: "take-in",
Since: time.Now().Add(-3 * time.Hour), Summary: "held", Said: "held", Headline: "held", Explanation: "held", Needs: "a take-in"}
value, err := watch.EncodeFinding(old)
if err != nil {
t.Fatal(err)
}
out.findings[old.Key()] = true
w.SeedFinding(old.Key(), value)
body, err := takein.EncodeTakeIn(takein.TakeIn{Kind: takein.TakeInPending, Key: "m.00000000000000000043", What: takein.TakeInRevision, Since: time.Now(), WaitsFor: "src2:x",
Merges: []takein.Merge{{Repository: "novox/a", Number: 1, Merge: "c0ffee"}}})
if err != nil {
t.Fatal(err)
}
w.TakeIn(takein.TakeInPendingEvent, body)
seedTakeIn(w, call, func(string, ...any) {})
if !out.findings[old.Key()] || out.pulseDeletes != 0 {
t.Errorf("%s: finding stands %v, signals ended %d: want the previous run's finding and the held wait kept", name, out.findings[old.Key()], out.pulseDeletes)
}
}
})
t.Run("A2j: a want stood since the assigner's bus wrote it, through the first view", func(t *testing.T) {
// The watchman's want wait starts at the entry's time (createdOr): through mesh-sdk's views.ProcessReader it is
// the time the owner's bus wrote the key, never the watchman's start.
@@ -113,11 +143,14 @@ type refusingBus struct{ err error }
func (r refusingBus) KeyValue(context.Context, string) (jetstream.KeyValue, error) { return nil, r.err }
// findingsOut keeps which findings stand.
type findingsOut struct{ findings map[string]bool }
type findingsOut struct {
findings map[string]bool
pulseDeletes int
}
func (o *findingsOut) PutPresence(string, bool) error { return nil }
func (o *findingsOut) PutPulse(string, []byte, time.Duration) error { return nil }
func (o *findingsOut) DeletePulse(string) error { return nil }
func (o *findingsOut) DeletePulse(string) error { o.pulseDeletes++; return nil }
func (o *findingsOut) PutStuck(string, []byte) error { return nil }
func (o *findingsOut) PutFinding(key string, _ []byte) error { o.findings[key] = true; return nil }
func (o *findingsOut) DeleteFinding(key string) error { delete(o.findings, key); return nil }