feat: add port forwarding and optimize connection status checks

This commit is contained in:
liumangmang
2026-06-11 14:10:30 +08:00
parent 4a17f0106e
commit e418e6ecc2
30 changed files with 1789 additions and 150 deletions
@@ -3,19 +3,20 @@ package com.sshmanager.service;
import com.sshmanager.dto.ConnectionStatusCheckRequest;
import com.sshmanager.dto.ConnectionStatusResponseDto;
import com.sshmanager.entity.Connection;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doReturn;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@@ -24,49 +25,128 @@ class ConnectionStatusServiceTest {
@Mock
private ConnectionService connectionService;
@InjectMocks
@Mock
private TcpProbe tcpProbe;
private ConnectionStatusService connectionStatusService;
@Test
void checkStatusesAggregatesOnlineAndOfflineResults() {
Connection onlineConnection = new Connection();
onlineConnection.setId(1L);
onlineConnection.setUserId(99L);
onlineConnection.setName("prod");
@BeforeEach
void setUp() {
connectionStatusService = new ConnectionStatusService(connectionService, tcpProbe);
}
Connection offlineConnection = new Connection();
offlineConnection.setId(2L);
offlineConnection.setUserId(99L);
offlineConnection.setName("test");
@Test
void checkStatusesRejectsEmptyConnectionIds() {
ConnectionStatusCheckRequest request = new ConnectionStatusCheckRequest();
IllegalArgumentException error = assertThrows(
IllegalArgumentException.class,
() -> connectionStatusService.checkStatuses(1L, request)
);
assertEquals("At least one connection is required", error.getMessage());
}
@Test
void checkStatusesMarksReachableAsOnline() throws Exception {
Connection conn = new Connection();
conn.setId(1L);
conn.setUserId(99L);
conn.setName("reachable-host");
conn.setHost("10.0.0.1");
conn.setPort(22);
when(connectionService.getConnectionForSsh(1L, 99L)).thenReturn(conn);
ConnectionStatusCheckRequest request = new ConnectionStatusCheckRequest();
request.setConnectionIds(Arrays.asList(1L, 2L));
request.setConnectionIds(Arrays.asList(1L));
when(connectionService.getConnectionForSsh(1L, 99L)).thenReturn(onlineConnection);
when(connectionService.getConnectionForSsh(2L, 99L)).thenReturn(offlineConnection);
doReturn(onlineConnection).when(connectionService).testConnection(eq(onlineConnection), eq(null), eq(null), eq(null));
doThrow(new RuntimeException("Connection refused")).when(connectionService).testConnection(eq(offlineConnection), eq(null), eq(null), eq(null));
ConnectionStatusResponseDto response = connectionStatusService.checkStatuses(99L, request);
assertEquals(1, response.getTotal());
assertEquals(1, response.getOnlineCount());
assertEquals(0, response.getOfflineCount());
assertEquals("online", response.getResults().get(0).getStatus());
assertEquals("TCP port reachable", response.getResults().get(0).getMessage());
}
@Test
void checkStatusesMarksUnreachableAsOffline() throws Exception {
Connection conn = new Connection();
conn.setId(2L);
conn.setUserId(99L);
conn.setName("unreachable-host");
conn.setHost("10.0.0.2");
conn.setPort(22);
when(connectionService.getConnectionForSsh(2L, 99L)).thenReturn(conn);
doAnswer(invocation -> { throw new RuntimeException("Connection refused"); })
.when(tcpProbe).checkReachable(conn, 5);
ConnectionStatusCheckRequest request = new ConnectionStatusCheckRequest();
request.setConnectionIds(Arrays.asList(2L));
ConnectionStatusResponseDto response = connectionStatusService.checkStatuses(99L, request);
assertEquals(1, response.getTotal());
assertEquals(0, response.getOnlineCount());
assertEquals(1, response.getOfflineCount());
assertEquals("offline", response.getResults().get(0).getStatus());
}
@Test
void checkStatusesAggregatesMixedResults() throws Exception {
Connection online = new Connection();
online.setId(10L);
online.setUserId(99L);
online.setName("server-a");
online.setHost("10.0.0.1");
online.setPort(22);
Connection offline = new Connection();
offline.setId(20L);
offline.setUserId(99L);
offline.setName("server-b");
offline.setHost("10.0.0.2");
offline.setPort(22);
when(connectionService.getConnectionForSsh(10L, 99L)).thenReturn(online);
when(connectionService.getConnectionForSsh(20L, 99L)).thenReturn(offline);
// Answer with argument matching based on connection ID
doAnswer(invocation -> {
Connection c = invocation.getArgument(0);
if (c.getId() == 20L) throw new RuntimeException("Connection refused");
return null;
}).when(tcpProbe).checkReachable(any(Connection.class), anyInt());
ConnectionStatusCheckRequest request = new ConnectionStatusCheckRequest();
request.setConnectionIds(Arrays.asList(10L, 20L));
ConnectionStatusResponseDto response = connectionStatusService.checkStatuses(99L, request);
assertEquals(2, response.getTotal());
assertEquals(1, response.getOnlineCount());
assertEquals(1, response.getOfflineCount());
assertEquals("online", response.getResults().get(0).getStatus());
assertEquals("offline", response.getResults().get(1).getStatus());
assertEquals("prod", response.getResults().get(0).getConnectionName());
assertEquals("Connection refused", response.getResults().get(1).getMessage());
}
@Test
void checkStatusesRejectsEmptyConnectionIds() {
void checkStatusesHandlesAllOfflineGracefully() throws Exception {
Connection conn = new Connection();
conn.setId(99L);
conn.setUserId(99L);
conn.setName("fail-host");
conn.setHost("10.0.0.1");
conn.setPort(22);
when(connectionService.getConnectionForSsh(99L, 99L)).thenReturn(conn);
doAnswer(invocation -> { throw new RuntimeException("timeout"); })
.when(tcpProbe).checkReachable(conn, 5);
ConnectionStatusCheckRequest request = new ConnectionStatusCheckRequest();
request.setConnectionIds(Collections.singletonList(99L));
IllegalArgumentException error = assertThrows(
IllegalArgumentException.class,
() -> connectionStatusService.checkStatuses(1L, request)
);
ConnectionStatusResponseDto response = connectionStatusService.checkStatuses(99L, request);
assertEquals("At least one connection is required", error.getMessage());
assertEquals(1, response.getTotal());
assertEquals(0, response.getOnlineCount());
assertEquals(1, response.getOfflineCount());
}
}