Initial project snapshot
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPeerRecoveryPlanMaintainsBoundedReadyPeers(t *testing.T) {
|
||||
now := time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)
|
||||
plan := PlanPeerRecovery(PeerRecoveryPlanConfig{
|
||||
PeerCache: PeerCacheSnapshot{
|
||||
Entries: []PeerCacheEntry{
|
||||
recoveryPlanPeer("node-a", true, false, "route_adjacent"),
|
||||
recoveryPlanPeer("node-b", true, false, "route_adjacent"),
|
||||
recoveryPlanPeer("node-c", true, false, "peer_endpoint"),
|
||||
recoveryPlanPeer("node-d", true, false, "peer_endpoint"),
|
||||
},
|
||||
},
|
||||
Connections: PeerConnectionSnapshot{Entries: []PeerConnectionState{
|
||||
{NodeID: "node-a", State: PeerConnectionReady, LastLatencyMs: 40},
|
||||
{NodeID: "node-b", State: PeerConnectionReady, LastLatencyMs: 20},
|
||||
{NodeID: "node-c", State: PeerConnectionReady, LastLatencyMs: 30},
|
||||
{NodeID: "node-d", State: PeerConnectionReady, LastLatencyMs: 10},
|
||||
}},
|
||||
Now: now,
|
||||
})
|
||||
|
||||
if plan.Mode != PeerRecoveryModeSteady || !plan.Healthy {
|
||||
t.Fatalf("unexpected plan health: %+v", plan)
|
||||
}
|
||||
if plan.TargetReadyPeers != DefaultStablePeerTarget || len(plan.Candidates) != DefaultStablePeerTarget {
|
||||
t.Fatalf("unexpected bounded candidates: %+v", plan)
|
||||
}
|
||||
for _, candidate := range plan.Candidates {
|
||||
if candidate.Reason != "maintain_ready" {
|
||||
t.Fatalf("unexpected candidate reason: %+v", candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeerRecoveryPlanAddsRecoverySeedWhenReadyDeficit(t *testing.T) {
|
||||
now := time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)
|
||||
plan := PlanPeerRecovery(PeerRecoveryPlanConfig{
|
||||
PeerCache: PeerCacheSnapshot{
|
||||
Entries: []PeerCacheEntry{
|
||||
recoveryPlanPeer("node-a", true, false, "route_adjacent"),
|
||||
recoveryPlanPeer("node-b", true, false, "route_adjacent"),
|
||||
recoveryPlanPeer("node-seed", false, true, ""),
|
||||
},
|
||||
},
|
||||
Connections: PeerConnectionSnapshot{Entries: []PeerConnectionState{
|
||||
{NodeID: "node-a", State: PeerConnectionReady, LastLatencyMs: 20},
|
||||
{NodeID: "node-b", State: PeerConnectionBackoff, BackoffUntil: now.Add(time.Minute)},
|
||||
}},
|
||||
Now: now,
|
||||
})
|
||||
|
||||
if plan.Mode != PeerRecoveryModeRecovery || plan.Healthy {
|
||||
t.Fatalf("unexpected recovery mode: %+v", plan)
|
||||
}
|
||||
if plan.Deficit != 2 || plan.RecoverySeedCandidateCount != 1 {
|
||||
t.Fatalf("unexpected deficit/seed count: %+v", plan)
|
||||
}
|
||||
if !recoveryPlanHasCandidate(plan, "node-seed", "recover_seed") {
|
||||
t.Fatalf("recovery seed was not selected: %+v", plan.Candidates)
|
||||
}
|
||||
if recoveryPlanHasCandidate(plan, "node-b", "") {
|
||||
t.Fatalf("active backoff peer should not be selected: %+v", plan.Candidates)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeerRecoveryPlanMaintainsRelayReadyPeersInSteadyMode(t *testing.T) {
|
||||
now := time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)
|
||||
plan := PlanPeerRecovery(PeerRecoveryPlanConfig{
|
||||
PeerCache: PeerCacheSnapshot{
|
||||
Entries: []PeerCacheEntry{
|
||||
{
|
||||
NodeID: "node-c",
|
||||
Endpoint: "http://relay:19001",
|
||||
Warm: true,
|
||||
WarmReason: "rendezvous_lease",
|
||||
RendezvousLeaseID: "lease-1",
|
||||
RelayNodeID: "node-r",
|
||||
RelayEndpoint: "http://relay:19001",
|
||||
RelayControl: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Connections: PeerConnectionSnapshot{Entries: []PeerConnectionState{
|
||||
{NodeID: "node-c", State: PeerConnectionRelayReady, LastLatencyMs: 15},
|
||||
}},
|
||||
Now: now,
|
||||
})
|
||||
|
||||
if plan.Mode != PeerRecoveryModeSteady || !plan.Healthy {
|
||||
t.Fatalf("unexpected steady plan: %+v", plan)
|
||||
}
|
||||
if !recoveryPlanHasCandidate(plan, "node-c", "maintain_ready") {
|
||||
t.Fatalf("relay-ready peer was not maintained: %+v", plan.Candidates)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeerRecoveryPlanCapsTargetByConnectablePeers(t *testing.T) {
|
||||
now := time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)
|
||||
plan := PlanPeerRecovery(PeerRecoveryPlanConfig{
|
||||
PeerCache: PeerCacheSnapshot{Entries: []PeerCacheEntry{
|
||||
{NodeID: "node-a", Warm: true, WarmReason: "route_adjacent"},
|
||||
recoveryPlanPeer("node-b", true, false, "route_adjacent"),
|
||||
}},
|
||||
Connections: PeerConnectionSnapshot{Entries: []PeerConnectionState{
|
||||
{NodeID: "node-b", State: PeerConnectionReady},
|
||||
}},
|
||||
Now: now,
|
||||
})
|
||||
|
||||
if plan.TargetReadyPeers != 1 || !plan.Healthy {
|
||||
t.Fatalf("target should be capped by connectable peers: %+v", plan)
|
||||
}
|
||||
}
|
||||
|
||||
func recoveryPlanPeer(nodeID string, warm bool, recoverySeed bool, warmReason string) PeerCacheEntry {
|
||||
return PeerCacheEntry{
|
||||
NodeID: nodeID,
|
||||
Endpoint: "http://" + nodeID + ":19001",
|
||||
Warm: warm,
|
||||
WarmReason: warmReason,
|
||||
RecoverySeed: recoverySeed,
|
||||
}
|
||||
}
|
||||
|
||||
func recoveryPlanHasCandidate(plan PeerRecoveryPlan, nodeID string, reason string) bool {
|
||||
for _, candidate := range plan.Candidates {
|
||||
if candidate.NodeID != nodeID {
|
||||
continue
|
||||
}
|
||||
return reason == "" || candidate.Reason == reason
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user