Files
mesh-catalog/modules/mesh-vault/cmd/mesh-vault/placementview_test.go
T
jschoubben af487d38ed mesh-vault: adopt from the bus node's kept have when the placement names it
A have of the bus module heard while the placement did not yet name its node as holding the bus
adopted nothing, and nothing adopted it again until the have changed, so a vault could stay without
the bus node's adoption and the loaded list's revision for good. The vault now keeps each node's
latest have and, on the placement's next change, adopts from the bus node's kept have if it was not
adopted: the placement's event, no timer. The rotation test now writes the have before the
placement (its wait for the vault's picture is gone), so it takes this path every run.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 20:04:06 +01:00

60 lines
2.1 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"sync"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/views"
)
// ADR 0615 §6: the vault started while the assigner is down keeps asking, never reads its bucket, and past the bound
// writes a finding naming the owner and the tool for the condition keeper; the answer deletes it. Before the answer a read
// of the placement is an error.
func TestAnUnansweredFirstViewOfThePlacementIsAFindingTheAnswerClears(t *testing.T) {
var mu sync.Mutex
up := false
held := map[string]json.RawMessage{}
ask := func(string, any) (json.RawMessage, error) {
mu.Lock()
defer mu.Unlock()
if !up {
return nil, errors.New("nats: no responders available for request")
}
raw, _ := json.Marshal(views.Answer{V: 1, Owner: "mesh-delivery-assigner", State: "placement", Revision: 1, Created: time.Now(),
Values: map[string]json.RawMessage{"g14.node": json.RawMessage(`{}`)}})
return raw, nil
}
put := func(k string, v json.RawMessage) error { mu.Lock(); held[k] = v; mu.Unlock(); return nil }
drop := func(k string) error { mu.Lock(); delete(held, k); mu.Unlock(); return nil }
f := newPlacementView(ask, func(string) {}, nil, put, drop)
f.Bound, f.Wait = 30*time.Millisecond, 10*time.Millisecond
reader := viewPlacement(func() *views.Follower { return f })
if _, err := reader.Keys(); err == nil {
t.Fatal("the placement was read before its first view")
}
done := make(chan error, 1)
go func() { done <- f.Start(context.Background()) }()
count := func() int { mu.Lock(); defer mu.Unlock(); return len(held) }
for end := time.Now().Add(5 * time.Second); count() == 0 && time.Now().Before(end); time.Sleep(10 * time.Millisecond) {
}
if count() != 1 {
t.Fatalf("past its bound the vault wrote %d findings, want one", count())
}
mu.Lock()
up = true
mu.Unlock()
if err := <-done; err != nil {
t.Fatal(err)
}
if count() != 0 {
t.Errorf("the answer left %d findings", count())
}
if keys, err := reader.Keys(); err != nil || len(keys) != 1 || keys[0] != "g14.node" {
t.Errorf("keys %v, %v", keys, err)
}
}