package integration_test import ( "context" "fmt" "testing" "time" "chorus.services/bzzz/pkg/config" "chorus.services/bzzz/pkg/election" ) func TestElectionIntegration_ElectionLogic(t *testing.T) { // Test election management lifecycle ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() cfg := &config.Config{ Agent: config.AgentConfig{ ID: "test-node", }, Security: config.SecurityConfig{ ElectionConfig: config.ElectionConfig{ Enabled: true, HeartbeatTimeout: 5 * time.Second, ElectionTimeout: 10 * time.Second, }, }, } // Create a minimal election manager without full P2P (pass nils for deps we don't need) em := election.NewElectionManager(ctx, cfg, nil, nil, "test-node") if em == nil { t.Fatal("Expected NewElectionManager to return non-nil manager") } // Test election states initialState := em.GetElectionState() if initialState != election.StateIdle { t.Errorf("Expected initial state to be StateIdle, got %v", initialState) } // Test admin status methods currentAdmin := em.GetCurrentAdmin() if currentAdmin != "" { t.Logf("Current admin: %s", currentAdmin) } isAdmin := em.IsCurrentAdmin() t.Logf("Is current admin: %t", isAdmin) // Test trigger election (this is the real available method) em.TriggerElection(election.TriggerManual) // Test state after trigger newState := em.GetElectionState() t.Logf("State after trigger: %v", newState) t.Log("Election integration test completed successfully") } func TestElectionIntegration_AdminFailover(t *testing.T) { // Test admin failover scenarios using election triggers ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() cfg := &config.Config{ Agent: config.AgentConfig{ ID: "failover-test-node", }, Security: config.SecurityConfig{ ElectionConfig: config.ElectionConfig{ Enabled: true, HeartbeatTimeout: 3 * time.Second, ElectionTimeout: 6 * time.Second, }, }, } em := election.NewElectionManager(ctx, cfg, nil, nil, "failover-test-node") // Test initial state initialState := em.GetElectionState() t.Logf("Initial state: %v", initialState) // Test heartbeat timeout trigger (simulates admin failure) em.TriggerElection(election.TriggerHeartbeatTimeout) // Allow some time for state change time.Sleep(100 * time.Millisecond) afterFailureState := em.GetElectionState() t.Logf("State after heartbeat timeout: %v", afterFailureState) // Test split brain scenario em.TriggerElection(election.TriggerSplitBrain) time.Sleep(100 * time.Millisecond) splitBrainState := em.GetElectionState() t.Logf("State after split brain trigger: %v", splitBrainState) // Test quorum restoration em.TriggerElection(election.TriggerQuorumRestored) time.Sleep(100 * time.Millisecond) finalState := em.GetElectionState() t.Logf("State after quorum restored: %v", finalState) t.Log("Failover integration test completed") } func TestElectionIntegration_ConcurrentElections(t *testing.T) { // Test concurrent election triggers ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) defer cancel() cfg1 := &config.Config{ Agent: config.AgentConfig{ ID: "concurrent-node-1", }, Security: config.SecurityConfig{ ElectionConfig: config.ElectionConfig{ Enabled: true, HeartbeatTimeout: 4 * time.Second, ElectionTimeout: 8 * time.Second, }, }, } cfg2 := &config.Config{ Agent: config.AgentConfig{ ID: "concurrent-node-2", }, Security: config.SecurityConfig{ ElectionConfig: config.ElectionConfig{ Enabled: true, HeartbeatTimeout: 4 * time.Second, ElectionTimeout: 8 * time.Second, }, }, } em1 := election.NewElectionManager(ctx, cfg1, nil, nil, "concurrent-node-1") em2 := election.NewElectionManager(ctx, cfg2, nil, nil, "concurrent-node-2") // Trigger elections concurrently go func() { em1.TriggerElection(election.TriggerManual) }() go func() { em2.TriggerElection(election.TriggerManual) }() // Wait for processing time.Sleep(200 * time.Millisecond) // Check states state1 := em1.GetElectionState() state2 := em2.GetElectionState() t.Logf("Node 1 state: %v", state1) t.Logf("Node 2 state: %v", state2) // Both should be handling elections if state1 == election.StateIdle && state2 == election.StateIdle { t.Error("Expected at least one election manager to be in non-idle state") } t.Log("Concurrent elections test completed") } func TestElectionIntegration_ElectionCallbacks(t *testing.T) { // Test election callback system ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() cfg := &config.Config{ Agent: config.AgentConfig{ ID: "callback-test-node", }, Security: config.SecurityConfig{ ElectionConfig: config.ElectionConfig{ Enabled: true, HeartbeatTimeout: 5 * time.Second, ElectionTimeout: 10 * time.Second, }, }, } em := election.NewElectionManager(ctx, cfg, nil, nil, "callback-test-node") // Track callback invocations var adminChangedCalled bool var electionCompleteCalled bool var oldAdmin, newAdmin, winner string // Set up callbacks em.SetCallbacks( func(old, new string) { adminChangedCalled = true oldAdmin = old newAdmin = new t.Logf("Admin changed callback: %s -> %s", old, new) }, func(w string) { electionCompleteCalled = true winner = w t.Logf("Election complete callback: winner %s", w) }, ) // Trigger election em.TriggerElection(election.TriggerManual) // Give time for potential callback execution time.Sleep(200 * time.Millisecond) // Check state changes currentState := em.GetElectionState() t.Logf("Current election state: %v", currentState) isAdmin := em.IsCurrentAdmin() t.Logf("Is current admin: %t", isAdmin) currentAdminID := em.GetCurrentAdmin() t.Logf("Current admin ID: %s", currentAdminID) // Log callback results t.Logf("Admin changed callback called: %t", adminChangedCalled) t.Logf("Election complete callback called: %t", electionCompleteCalled) if adminChangedCalled { t.Logf("Admin change: %s -> %s", oldAdmin, newAdmin) } if electionCompleteCalled { t.Logf("Election winner: %s", winner) } t.Log("Election callback integration test completed") }