test(bf-95tx): integration test tracing positions through simulator→registry→fusion
bf-95tx wants a single demonstrable verification that node positions flow
through every runtime stage the position-propagation work touched. bf-69ym
locked in simulator→fleet-registry; bf-u7ds locked in simulator→fusion. This
adds the missing end-to-end lock-in that ties both together in one run.
TestPositionPropagation_FullPipeline wires the production chain behind the
SimulatorHandler (handler → bridge.SyncToRegistry → adapter → fleet.Registry
SQLite, with adapter forwardPos → fleet.Manager.ForwardNodePosition → sink →
fusion.Engine.SetNodePosition), posts one node with an explicit position,
then reads that SAME position back from all three stages and asserts they
agree — at both AddNode and UpdateNode. A pipelineStages helper logs each
stage's value so `go test -v` visibly traces the value's path:
[create] position propagation simulator={1.5 2.5 1} registry={1.5 2.5 1} fusion={1.5 2.5 1}
[update] position propagation simulator={3.5 4.5 2} registry={3.5 4.5 2} fusion={3.5 4.5 2}
The update leg also asserts no stage still holds the stale pre-update value
and that a single node stays a single node at every stage (no orphan/dup).
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
dbb5cd0cc4
commit
c69b0f7ca1
1 changed files with 122 additions and 0 deletions
|
|
@ -658,3 +658,125 @@ func TestSimulatorHandlerToRegistry_OriginNodesGetSpreadGeometry(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPositionPropagation_FullPipeline (bf-95tx) is the end-to-end lock-in for
|
||||
// the COMPLETE simulator → fleet registry → fusion engine position path in a
|
||||
// single test. One node position, posted once through the simulator REST API,
|
||||
// must arrive — unchanged and in order — at every stage the runtime cares about:
|
||||
//
|
||||
// 1. simulator VirtualNodeStore — the source of truth for virtual nodes
|
||||
// 2. fleet.Registry (SQLite) — persisted; read by the dashboard/REST
|
||||
// 3. fusion.Engine nodePos mirror — what Fuse localizes against at runtime
|
||||
//
|
||||
// bf-69ym locked in stages 1→2; bf-u7ds locked in stages 1→3. This test ties
|
||||
// them together: it traces ONE position value through all three stages in a
|
||||
// single run and asserts they agree at both AddNode and UpdateNode, with
|
||||
// per-stage logging so the full data path is observable under `go test -v`.
|
||||
// That is the demonstrable full-pipeline verification the position-propagation
|
||||
// bead (bf-95tx) requires.
|
||||
func TestPositionPropagation_FullPipeline(t *testing.T) {
|
||||
// Wire the full chain exactly as cmd/mothership/main.go does:
|
||||
// handler → bridge.SyncToRegistry → adapter → fleet.Registry (SQLite),
|
||||
// with the adapter's forwardPos → fleet.Manager.ForwardNodePosition →
|
||||
// nodePositionSink → fusion.Engine.SetNodePosition.
|
||||
reg := newFleetRegistry(t)
|
||||
mgr := fleet.NewManager(reg)
|
||||
engine := fusion.NewEngine(nil)
|
||||
mgr.SetNodePositionSink(func(mac string, x, y, z float64) {
|
||||
engine.SetNodePosition(mac, x, y, z)
|
||||
})
|
||||
adapter := &fleetRegistryTestAdapter{
|
||||
reg: reg,
|
||||
forwardPos: mgr.ForwardNodePosition,
|
||||
}
|
||||
h := newWiredHandler(t, adapter) // real VirtualNodeStore + bridge + adapter
|
||||
|
||||
// pipelineStages reads the posted node's position back from all three stages
|
||||
// and logs each, so a `go test -v` run visibly traces the value's path:
|
||||
// simulator → registry → fusion. nodeID keys the simulator store; name keys
|
||||
// the fleet registry row (the bridge's virtualMAC is unexported); the single
|
||||
// positioned node keys the fusion engine mirror.
|
||||
pipelineStages := func(label, nodeID, name string) (simPos, regPos, fusPos simulator.Point) {
|
||||
t.Helper()
|
||||
|
||||
// Stage 1 — simulator VirtualNodeStore (the source).
|
||||
vs, err := h.virtualStore.GetNode(nodeID)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: simulator store lookup %q: %v", label, nodeID, err)
|
||||
}
|
||||
simPos = simulator.Point{X: vs.Position.X, Y: vs.Position.Y, Z: vs.Position.Z}
|
||||
|
||||
// Stage 2 — fleet.Registry (SQLite), matched by Name.
|
||||
byName := registryNodesByName(t, reg)
|
||||
rec, ok := byName[name]
|
||||
if !ok {
|
||||
t.Fatalf("%s: node %q missing from fleet registry (have %d nodes)", label, name, len(byName))
|
||||
}
|
||||
regPos = simulator.Point{X: rec.PosX, Y: rec.PosY, Z: rec.PosZ}
|
||||
|
||||
// Stage 3 — fusion.Engine nodePos mirror (what Fuse localizes against).
|
||||
fusPos = singleEnginePosition(t, engine)
|
||||
|
||||
t.Logf("[%s] position propagation simulator=%v registry=%v fusion=%v",
|
||||
label, simPos, regPos, fusPos)
|
||||
return simPos, regPos, fusPos
|
||||
}
|
||||
|
||||
// --- Create leg: post one node with an explicit position. ---
|
||||
const nodeID, nodeName = "node-A", "Node A"
|
||||
createPos := simulator.Point{X: 1.5, Y: 2.5, Z: 1.0}
|
||||
postNode(t, h, map[string]interface{}{
|
||||
"id": nodeID,
|
||||
"name": nodeName,
|
||||
"position": map[string]interface{}{
|
||||
"x": createPos.X, "y": createPos.Y, "z": createPos.Z,
|
||||
},
|
||||
})
|
||||
|
||||
simPos, regPos, fusPos := pipelineStages("create", nodeID, nodeName)
|
||||
if simPos != createPos {
|
||||
t.Errorf("create: simulator stage %v != posted %v", simPos, createPos)
|
||||
}
|
||||
if regPos != createPos {
|
||||
t.Errorf("create: registry stage %v != posted %v", regPos, createPos)
|
||||
}
|
||||
if fusPos != createPos {
|
||||
t.Errorf("create: fusion stage %v != posted %v", fusPos, createPos)
|
||||
}
|
||||
|
||||
// --- Update leg: move the node and re-trace the full pipeline. ---
|
||||
updatePos := simulator.Point{X: 3.5, Y: 4.5, Z: 2.0}
|
||||
putNode(t, h, nodeID, map[string]interface{}{
|
||||
"id": nodeID,
|
||||
"name": nodeName,
|
||||
"position": map[string]interface{}{
|
||||
"x": updatePos.X, "y": updatePos.Y, "z": updatePos.Z,
|
||||
},
|
||||
})
|
||||
|
||||
simPos, regPos, fusPos = pipelineStages("update", nodeID, nodeName)
|
||||
if simPos != updatePos {
|
||||
t.Errorf("update: simulator stage %v != posted %v", simPos, updatePos)
|
||||
}
|
||||
if regPos != updatePos {
|
||||
t.Errorf("update: registry stage %v != posted %v", regPos, updatePos)
|
||||
}
|
||||
if fusPos != updatePos {
|
||||
t.Errorf("update: fusion stage %v != posted %v", fusPos, updatePos)
|
||||
}
|
||||
|
||||
// No stage may still hold the pre-update value after the position moved.
|
||||
if simPos == createPos || regPos == createPos || fusPos == createPos {
|
||||
t.Errorf("update: a stage still holds the stale pre-update position %v "+
|
||||
"(sim=%v reg=%v fus=%v)", createPos, simPos, regPos, fusPos)
|
||||
}
|
||||
|
||||
// Cross-stage consistency: a single node must stay a single node at every
|
||||
// stage — an update must not orphan or duplicate it anywhere in the path.
|
||||
if got := len(registryNodesByName(t, reg)); got != 1 {
|
||||
t.Errorf("update: expected 1 fleet registry node, got %d", got)
|
||||
}
|
||||
if got := engine.NodeCount(); got != 1 {
|
||||
t.Errorf("update: expected 1 fusion engine node, got %d", got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue