Task 3-1: Integration Test: Connection Lifecycle
Phase: 3 Priority: Medium Module:
WawaServer,ConnectedClient,DynamicJmsListenerServiceDepends on: task-1-1, task-1-2 Reference: docs/BountyHunter-ControlServer/details/feature-iot-bridge/SPEC.md
Background
Connection lifecycle bao gồm nhiều component tương tác: WawaServer accept loop, ConnectedClient handshake/message handling, DynamicJmsListenerService listener creation/destruction, và RedisMachineRepository status tracking. Không có integration test nào hiện tại — chỉ có manual testing với máy thật. Cần automated test để verify (1) fix task-1-1 hoạt động đúng, (2) fix task-1-2 không gây corruption, và (3) concurrent connections không race condition.
Tasks
Note: Integration test này cần start actual
ServerSockettrên port ngẫu nhiên (tránh port conflict với production server 9999). Dùng@SpringBootTestvới test-specificapplication-test.propertiesđể override port. MockadminRMCCraneMachineClient,DynamicJmsListenerService, và Redis bằng@MockBeanhoặc embedded alternatives (Testcontainers Redis). Test thread safety bằngCountDownLatchvàExecutorService.
- [ ] Setup test infrastructure:
@SpringBootTest
@ActiveProfiles("test")
class ConnectionLifecycleIntegrationTest {
@MockBean
private AdminRMCCraneMachineClient adminClient;
@MockBean
private DynamicJmsListenerService jmsListenerService;
@Autowired
private WawaServer wawaServer;
// Helper: tạo mock TCP client và gửi handshake
private Socket connectMockMachine(String macAddress) throws IOException {
Socket socket = new Socket("localhost", testPort);
// Gửi AddNewIp packet với macAddress
byte[] handshakePacket = buildAddNewIpPacket(macAddress);
socket.getOutputStream().write(handshakePacket);
return socket;
}
}
- [ ] Test scenario 1: Single connect → allMachine updated, JMS listeners created:
@Test
void whenMachineConnects_thenRegisteredInAllMachineAndJmsListenersCreated() throws Exception {
Socket mockMachine = connectMockMachine("00:11:22:33:44:55");
Thread.sleep(100); // allow async processing
assertThat(WawaServer.allMachine).containsKey("00:11:22:33:44:55");
verify(jmsListenerService).startListeningGameplay("00:11:22:33:44:55");
verify(jmsListenerService).startListeningHealthCheck("00:11:22:33:44:55");
mockMachine.close();
}
- [ ] Test scenario 2: Disconnect → allMachine cleared, JMS listeners stopped:
@Test
void whenMachineDisconnects_thenRemovedFromAllMachineAndJmsListenersStopped() throws Exception {
Socket mockMachine = connectMockMachine("AA:BB:CC:DD:EE:FF");
Thread.sleep(100);
mockMachine.close(); // trigger disconnect
Thread.sleep(200);
assertThat(WawaServer.allMachine).doesNotContainKey("AA:BB:CC:DD:EE:FF");
verify(jmsListenerService).stopListeningGameplay("AA:BB:CC:DD:EE:FF");
verify(jmsListenerService).stopListeningHealthCheck("AA:BB:CC:DD:EE:FF");
}
- [ ] Test scenario 3: handleSocketClose với reason → machineSocketClose được gọi (validates task-1-1 fix):
@Test
void whenSocketCloseWithReason_thenBackendNotified() throws Exception {
Socket mockMachine = connectMockMachine("11:22:33:44:55:66");
Thread.sleep(100);
// Gửi SocketClose packet với reason code
byte[] closePacket = buildSocketClosePacket(SocketCloseReason.POWER_FAILURE);
mockMachine.getOutputStream().write(closePacket);
Thread.sleep(200);
verify(adminClient).machineSocketClose("11:22:33:44:55:66");
}
- [ ] Test scenario 4: Concurrent 10 machines connect simultaneously → no race condition:
@Test
void whenMultipleMachinesConnectConcurrently_thenAllRegistered() throws Exception {
int machineCount = 10;
CountDownLatch allConnected = new CountDownLatch(machineCount);
ExecutorService pool = Executors.newFixedThreadPool(machineCount);
for (int i = 0; i < machineCount; i++) {
final String mac = String.format("00:00:00:00:00:%02X", i);
pool.submit(() -> {
connectMockMachine(mac);
allConnected.countDown();
});
}
allConnected.await(5, TimeUnit.SECONDS);
Thread.sleep(300);
assertThat(WawaServer.allMachine).hasSize(machineCount);
}
- [ ] Test scenario 5: gPackageId không bị corrupt dưới concurrent load (validates task-1-2 fix):
@Test
void whenConcurrentCommandsSent_thenPackageIdsAreUnique() throws Exception {
// Connect 1 machine, send 100 concurrent startGame + moveLeft commands
// Collect all packet IDs from captured outbound data
// Assert: all IDs are unique
}
Verification / Acceptance Criteria
- [ ]
allMachine.size()đúng sau mỗi connect (tăng 1) và disconnect (giảm 1) - [ ] Không có orphaned JMS listeners sau disconnect (verify via mock verification)
- [ ]
machineSocketClose()được gọi khi socket close với reason (task-1-1 fix verified) - [ ] 10 concurrent connections →
allMachine.size() == 10, không cóConcurrentModificationException - [ ] Tất cả tests pass trong CI (không flaky do timing — dùng
Awaitilitynếu cần)
Files to Modify
src/test/java/.../integration/ConnectionLifecycleIntegrationTest.java← file mới tạosrc/test/resources/application-test.properties← overrideserver.portto random