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 }