Files
mesh-delivery/modules/mesh-delivery-builder/buildrun/standing_test.go
T
jschoubben 71a35a6376 MV1: move contracts and sdk under modules/, importers follow
The delivery repository's contracts and SDK live under modules/ as
mesh-delivery-contracts and mesh-delivery-sdk, their Go module paths
renamed to match the folders (the TypeScript names already did), and
every importer in mesh-delivery, mesh-catalog, mesh-host, mesh-tools
and mesh-sdk follows in the same group: a design as it should be, one
path per module, checked by the group's check building every importer.

Builds: design 47 G §427, §88

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 05:09:56 +01:00

120 lines
4.2 KiB
Go

package buildrun
import (
"bytes"
"context"
"errors"
"log"
"strings"
"testing"
"time"
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
meshwants "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
)
// placing is a value of the state `assignments` placing modules on node.
func placing(t *testing.T, node string, modules ...string) (string, []byte) {
t.Helper()
p := meshmoduleassigner.Assignments{Node: node}
for _, m := range modules {
p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m})
}
values, err := meshmoduleassignerclient.AssignmentsValues(p)
if err != nil {
t.Fatal(err)
}
key, _ := meshmoduleassignerclient.NodeKey(node)
return key, values[key]
}
// The placements the builder counts nodes by (ADR 0624 §1): a settled assignments names the nodes its modules stand on, a
// revision of the assignment half the same; they hold no toolchain (design 47 G §546: a toolchain is forgotten on the
// node-engine seat's stopped, never on a placement).
func TestPlacedNodes(t *testing.T) {
old := time.Date(2026, 10, 23, 6, 29, 18, 0, time.UTC)
three := []string{"g14", "novox", "shanks"}
t.Run("the assignments names the counted nodes once it is settled", func(t *testing.T) {
gate := NewPlacedNodes(func(string, ...any) {})
for _, node := range three {
k, v := placing(t, node, testNode)
if gate.Assignments(k, v, false, old) {
t.Fatal("an assignments before it is settled changed the counted nodes")
}
}
gate.Settled()
if got := strings.Join(gate.Nodes(), ","); got != "g14,novox,shanks" {
t.Fatalf("nodes = %q", got)
}
k, v := placing(t, "ace", testGo)
if !gate.Assignments(k, v, false, old) || len(gate.Nodes()) != 4 {
t.Fatalf("ace placed: nodes %v", gate.Nodes())
}
if !gate.Assignments(k, nil, true, old.Add(time.Hour)) || len(gate.Nodes()) != 3 {
t.Fatalf("ace's assignments deleted: nodes %v", gate.Nodes())
}
})
t.Run("the assignment half: the first revision settles, a later one moves the nodes", func(t *testing.T) {
gate := NewPlacedNodes(func(string, ...any) {})
revision := func(n int64, nodes ...string) meshwants.AssignmentRevision {
at := old.Add(time.Duration(n) * time.Hour)
r := meshwants.AssignmentRevision{Revision: n, Kind: "trunk", At: &at}
for _, node := range nodes {
r.Entries = append(r.Entries, meshwants.NodeEntryRef{Node: node, Module: testNode, Digest: "d-" + node})
}
return r
}
if !gate.Revision(revision(7, three...)) || len(gate.Nodes()) != 3 {
t.Fatalf("the first revision: nodes %v", gate.Nodes())
}
if gate.Revision(revision(7, three...)) {
t.Fatal("the same revision again changed the counted nodes")
}
if !gate.Revision(revision(9, "novox", "shanks")) || len(gate.Nodes()) != 2 {
t.Fatalf("g14 taken off: nodes %v", gate.Nodes())
}
})
t.Run("assignments the bus refuses is said, and the part goes on", func(t *testing.T) {
var logs bytes.Buffer
logger := log.New(&logs, "", 0)
refused := errors.New("nats: permissions violation for publish to $JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_assignments")
gate := NewPlacedNodes(logger.Printf)
for _, bus := range []refusingBus{{err: refused}, {refuseOpen: true, err: refused}} {
if _, err := FollowAssignmentsIn(context.Background(), bus, "acme-assigner_assignments", gate, func(context.Context) {}, logger.Printf); err != nil {
t.Errorf("a refused assignments stopped the start: %v", err)
}
}
if !strings.Contains(logs.String(), "cannot be watched") || !strings.Contains(logs.String(), "cannot be read") {
t.Errorf("the refusals were not said: %q", logs.String())
}
})
}
// refusingBus is a bus that refuses: every open (refuseOpen), or every watch of a bucket it opens, with err.
type refusingBus struct {
refuseOpen bool
err error
}
func (r refusingBus) KeyValue(context.Context, string) (jetstream.KeyValue, error) {
if r.refuseOpen {
return nil, r.err
}
return refusedWatch{err: r.err}, nil
}
type refusedWatch struct {
jetstream.KeyValue
err error
}
func (r refusedWatch) WatchAll(context.Context, ...jetstream.WatchOpt) (jetstream.KeyWatcher, error) {
return nil, r.err
}