package com.cube.gossip;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.*;
import java.util.concurrent.*;
/**
* Tests for Gossip Protocol
*/
public class GossipProtocolTest {
private GossipProtocol gossip1;
private GossipProtocol gossip2;
private GossipProtocol gossip3;
@BeforeEach
public void setup() {
GossipProtocol.GossipConfig config = GossipProtocol.GossipConfig.defaultConfig();
gossip1 = new GossipProtocol("node-1", "localhost", 8080, config);
gossip2 = new GossipProtocol("node-2", "localhost", 8081,
new GossipProtocol.GossipConfig(1000, 3, 5000, 15000, 3, 7947));
gossip3 = new GossipProtocol("node-3", "localhost", 8082,
new GossipProtocol.GossipConfig(1000, 3, 5000, 15000, 3, 7948));
}
@AfterEach
public void teardown() {
if (gossip1 != null) gossip1.shutdown();
if (gossip2 != null) gossip2.shutdown();
if (gossip3 != null) gossip3.shutdown();
}
@Test
public void testSingleNodeStartup() {
gossip1.start();
// Should have local node
Map<String, GossipProtocol.NodeState> state = gossip1.getClusterState();
assertEquals(1, state.size());
assertTrue(state.containsKey("node-1"));
GossipProtocol.NodeState localNode = state.get("node-1");
assertEquals(GossipProtocol.NodeState.Status.ALIVE, localNode.getStatus());
}
@Test
public void testTwoNodeCluster() throws Exception {
gossip1.start();
gossip2.start();
// Node 2 joins via node 1
gossip2.join(Arrays.asList("localhost:7946"));
// Wait for gossip to propagate
Thread.sleep(3000);
// Both nodes should see each other
Map<String, GossipProtocol.NodeState> state1 = gossip1.getClusterState();
Map<String, GossipProtocol.NodeState> state2 = gossip2.getClusterState();
assertEquals(2, state1.size());
assertEquals(2, state2.size());
assertTrue(state1.containsKey("node-1"));
assertTrue(state1.containsKey("node-2"));
assertTrue(state2.containsKey("node-1"));
assertTrue(state2.containsKey("node-2"));
}
@Test
public void testThreeNodeCluster() throws Exception {
gossip1.start();
gossip2.start();
gossip3.start();
// Nodes join
gossip2.join(Arrays.asList("localhost:7946"));
Thread.sleep(1000);
gossip3.join(Arrays.asList("localhost:7946", "localhost:7947"));
// Wait for convergence
Thread.sleep(5000);
// All nodes should see all nodes
assertEquals(3, gossip1.getClusterState().size());
assertEquals(3, gossip2.getClusterState().size());
assertEquals(3, gossip3.getClusterState().size());
}
@Test
public void testAliveNodes() throws Exception {
gossip1.start();
gossip2.start();
gossip2.join(Arrays.asList("localhost:7946"));
Thread.sleep(3000);
List<GossipProtocol.NodeState> aliveNodes1 = gossip1.getAliveNodes();
List<GossipProtocol.NodeState> aliveNodes2 = gossip2.getAliveNodes();
assertEquals(2, aliveNodes1.size());
assertEquals(2, aliveNodes2.size());
}
@Test
public void testEventListener() throws Exception {
CountDownLatch joinLatch = new CountDownLatch(1);
CountDownLatch aliveLatch = new CountDownLatch(1);
gossip1.addListener(new GossipProtocol.GossipListener() {
@Override
public void onNodeJoined(GossipProtocol.NodeState node) {
if (node.getNodeId().equals("node-2")) {
joinLatch.countDown();
}
}
@Override
public void onNodeLeft(GossipProtocol.NodeState node) {}
@Override
public void onNodeSuspected(GossipProtocol.NodeState node) {}
@Override
public void onNodeAlive(GossipProtocol.NodeState node) {
if (node.getNodeId().equals("node-2")) {
aliveLatch.countDown();
}
}
@Override
public void onNodeDead(GossipProtocol.NodeState node) {}
});
gossip1.start();
gossip2.start();
gossip2.join(Arrays.asList("localhost:7946"));
// Wait for join event
assertTrue(joinLatch.await(5, TimeUnit.SECONDS));
assertTrue(aliveLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testStatistics() throws Exception {
gossip1.start();
gossip2.start();
gossip3.start();
gossip2.join(Arrays.asList("localhost:7946"));
gossip3.join(Arrays.asList("localhost:7946"));
Thread.sleep(3000);
Map<String, Object> stats = gossip1.getStatistics();
assertEquals("node-1", stats.get("localNodeId"));
assertEquals(3, stats.get("totalNodes"));
assertEquals(3L, stats.get("aliveNodes"));
assertEquals(0L, stats.get("suspectedNodes"));
assertEquals(0L, stats.get("deadNodes"));
}
@Test
public void testGracefulLeave() throws Exception {
gossip1.start();
gossip2.start();
gossip2.join(Arrays.asList("localhost:7946"));
Thread.sleep(3000);
assertEquals(2, gossip1.getClusterState().size());
// Node 2 leaves gracefully
gossip2.leave();
gossip2.shutdown();
// Wait for propagation
Thread.sleep(3000);
// Node 1 should still see node 2 but as leaving/dead
GossipProtocol.NodeState node2State = gossip1.getClusterState().get("node-2");
assertNotNull(node2State);
assertTrue(node2State.getStatus() == GossipProtocol.NodeState.Status.LEAVING ||
node2State.getStatus() == GossipProtocol.NodeState.Status.DEAD);
}
@Test
public void testHeartbeatIncrement() throws Exception {
gossip1.start();
GossipProtocol.NodeState localNode = gossip1.getClusterState().get("node-1");
long initialHeartbeat = localNode.getHeartbeatCounter();
// Wait for a few gossip rounds
Thread.sleep(5000);
long newHeartbeat = localNode.getHeartbeatCounter();
assertTrue(newHeartbeat > initialHeartbeat);
}
}