Skip to content

Commit ebeaad8

Browse files
2 parents 0ca16c1 + 5d0db50 commit ebeaad8

File tree

7 files changed

+2712
-23
lines changed

7 files changed

+2712
-23
lines changed

src/server/apiserver/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ base64 = "0.22"
2424
tokio = { version = "1.43.1", features = ["macros", "rt-multi-thread"] }
2525
tower-http ={ version = "0.6.1", features = ["cors"]}
2626
tower = "0.4"
27-
tokio-stream = "0.1"
27+
tokio-stream = "0.1"
28+
29+
[dev-dependencies]
30+
futures = "0.3"

src/server/apiserver/src/diagnostics.rs

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,231 @@ pub async fn check_service_connectivity(ip: &str, port: u16) -> bool {
3333
pub async fn check_node_agent_connectivity(ip: &str) -> bool {
3434
check_service_connectivity(ip, 47004).await
3535
}
36+
37+
#[cfg(test)]
38+
mod tests {
39+
use super::*;
40+
use std::net::TcpListener;
41+
use tokio::net::TcpListener as TokioTcpListener;
42+
use tokio::time::Duration;
43+
44+
/// Helper function to start a mock TCP server for testing
45+
async fn start_mock_server(port: u16) -> Result<TokioTcpListener, std::io::Error> {
46+
let addr = format!("127.0.0.1:{}", port);
47+
TokioTcpListener::bind(addr).await
48+
}
49+
50+
/// Helper function to find an available port for testing
51+
fn find_available_port() -> u16 {
52+
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
53+
listener.local_addr().unwrap().port()
54+
}
55+
56+
#[tokio::test]
57+
async fn test_check_service_connectivity_success() {
58+
// Start a mock server on an available port
59+
let port = find_available_port();
60+
let _listener = start_mock_server(port).await.unwrap();
61+
62+
// Test successful connection
63+
let result = check_service_connectivity("127.0.0.1", port).await;
64+
assert!(result, "Should successfully connect to mock server");
65+
}
66+
67+
#[tokio::test]
68+
async fn test_check_service_connectivity_connection_refused() {
69+
// Test connection to a port where no service is running
70+
let port = find_available_port(); // Get an available port but don't start a server
71+
let result = check_service_connectivity("127.0.0.1", port).await;
72+
assert!(!result, "Should fail to connect to non-existent service");
73+
}
74+
75+
#[tokio::test]
76+
async fn test_check_service_connectivity_invalid_ip() {
77+
// Test connection to invalid IP address
78+
let result = check_service_connectivity("999.999.999.999", 8080).await;
79+
assert!(!result, "Should fail to connect to invalid IP address");
80+
}
81+
82+
#[tokio::test]
83+
async fn test_check_service_connectivity_localhost_variations() {
84+
// Test different localhost representations
85+
let port = find_available_port();
86+
let _listener = start_mock_server(port).await.unwrap();
87+
88+
// Test with 127.0.0.1
89+
let result1 = check_service_connectivity("127.0.0.1", port).await;
90+
assert!(result1, "Should connect to 127.0.0.1");
91+
92+
// Test with localhost (may not work in all environments)
93+
let result2 = check_service_connectivity("localhost", port).await;
94+
// Note: This might fail in some test environments, so we just test it doesn't panic
95+
// In a real environment, localhost should resolve to 127.0.0.1
96+
println!("Localhost connection result: {}", result2);
97+
}
98+
99+
#[tokio::test]
100+
async fn test_check_service_connectivity_timeout_simulation() {
101+
// Test with an IP that should cause timeout (non-routable IP)
102+
// Using 10.254.254.254 which is typically non-routable
103+
let result = check_service_connectivity("10.254.254.254", 12345).await;
104+
assert!(!result, "Should timeout and return false");
105+
}
106+
107+
#[tokio::test]
108+
async fn test_check_service_connectivity_various_ports() {
109+
// Test with different port numbers
110+
let port = find_available_port();
111+
let _listener = start_mock_server(port).await.unwrap();
112+
113+
// Test with the actual port
114+
let result1 = check_service_connectivity("127.0.0.1", port).await;
115+
assert!(result1, "Should connect to correct port");
116+
117+
// Test with a different port (should fail)
118+
let wrong_port = if port == 65535 { port - 1 } else { port + 1 };
119+
let result2 = check_service_connectivity("127.0.0.1", wrong_port).await;
120+
assert!(!result2, "Should fail to connect to wrong port");
121+
}
122+
123+
#[tokio::test]
124+
async fn test_check_service_connectivity_edge_case_ports() {
125+
// Test with edge case port numbers
126+
127+
// Test with port 0 (should fail)
128+
let result1 = check_service_connectivity("127.0.0.1", 0).await;
129+
assert!(!result1, "Should fail to connect to port 0");
130+
131+
// Test with high port number (but valid)
132+
let result2 = check_service_connectivity("127.0.0.1", 65534).await;
133+
assert!(
134+
!result2,
135+
"Should fail to connect to non-listening high port"
136+
);
137+
}
138+
139+
#[tokio::test]
140+
async fn test_check_node_agent_connectivity_success() {
141+
// Test the NodeAgent specific function with a mock server on port 47004
142+
// Note: We can't easily bind to 47004 in tests as it might be in use
143+
// So we test the function call structure
144+
let result = check_node_agent_connectivity("127.0.0.1").await;
145+
// We don't assert the result since no actual NodeAgent is running
146+
// but we verify the function executes without panic
147+
println!("NodeAgent connectivity test result: {}", result);
148+
}
149+
150+
#[tokio::test]
151+
async fn test_check_node_agent_connectivity_with_mock() {
152+
// Create a mock server on port 47004 if available
153+
match start_mock_server(47004).await {
154+
Ok(_listener) => {
155+
let result = check_node_agent_connectivity("127.0.0.1").await;
156+
assert!(result, "Should connect to mock NodeAgent on port 47004");
157+
}
158+
Err(_) => {
159+
// Port 47004 might be in use, test with unavailable service
160+
let result = check_node_agent_connectivity("127.0.0.1").await;
161+
// Just verify it doesn't panic - result depends on what's running on 47004
162+
println!(
163+
"NodeAgent connectivity result (port might be in use): {}",
164+
result
165+
);
166+
}
167+
}
168+
}
169+
170+
#[tokio::test]
171+
async fn test_check_node_agent_connectivity_invalid_ip() {
172+
// Test NodeAgent connectivity with invalid IP
173+
let result = check_node_agent_connectivity("999.999.999.999").await;
174+
assert!(!result, "Should fail to connect NodeAgent at invalid IP");
175+
}
176+
177+
#[tokio::test]
178+
async fn test_check_node_agent_connectivity_unreachable_ip() {
179+
// Test NodeAgent connectivity with unreachable IP
180+
let result = check_node_agent_connectivity("10.254.254.254").await;
181+
assert!(
182+
!result,
183+
"Should fail to connect NodeAgent at unreachable IP"
184+
);
185+
}
186+
187+
#[tokio::test]
188+
async fn test_connectivity_functions_with_empty_ip() {
189+
// Test with empty IP string
190+
let result1 = check_service_connectivity("", 8080).await;
191+
assert!(!result1, "Should fail with empty IP string");
192+
193+
let result2 = check_node_agent_connectivity("").await;
194+
assert!(!result2, "Should fail NodeAgent connectivity with empty IP");
195+
}
196+
197+
#[tokio::test]
198+
async fn test_connectivity_timeout_behavior() {
199+
// Test that the timeout is actually working (should complete within reasonable time)
200+
let start = std::time::Instant::now();
201+
let result = check_service_connectivity("10.254.254.254", 12345).await;
202+
let duration = start.elapsed();
203+
204+
assert!(!result, "Should return false for unreachable service");
205+
assert!(
206+
duration >= Duration::from_secs(3),
207+
"Should wait at least 3 seconds for timeout"
208+
);
209+
assert!(
210+
duration < Duration::from_secs(5),
211+
"Should not wait more than 5 seconds total"
212+
);
213+
}
214+
215+
#[tokio::test]
216+
async fn test_multiple_concurrent_connectivity_checks() {
217+
// Test multiple concurrent connectivity checks
218+
let port = find_available_port();
219+
let _listener = start_mock_server(port).await.unwrap();
220+
221+
let tasks = vec![
222+
tokio::spawn(check_service_connectivity("127.0.0.1", port)),
223+
tokio::spawn(check_service_connectivity("127.0.0.1", port)),
224+
tokio::spawn(check_service_connectivity("127.0.0.1", port)),
225+
];
226+
227+
let results = futures::future::join_all(tasks).await;
228+
229+
for result in results {
230+
let connectivity_result = result.unwrap();
231+
assert!(
232+
connectivity_result,
233+
"All concurrent connections should succeed"
234+
);
235+
}
236+
}
237+
238+
#[tokio::test]
239+
async fn test_connectivity_with_ipv6_localhost() {
240+
// Test with IPv6 localhost address
241+
let result = check_service_connectivity("::1", 8080).await;
242+
// This may fail in environments without IPv6 support, so we just ensure no panic
243+
println!("IPv6 localhost connectivity result: {}", result);
244+
}
245+
246+
#[tokio::test]
247+
async fn test_service_connectivity_function_parameters() {
248+
// Test various parameter combinations to ensure robustness
249+
let test_cases = vec![
250+
("127.0.0.1", 1), // Low port number
251+
("127.0.0.1", 65535), // High port number
252+
("0.0.0.0", 8080), // All interfaces IP
253+
("255.255.255.255", 8080), // Broadcast IP
254+
];
255+
256+
for (ip, port) in test_cases {
257+
let result = check_service_connectivity(ip, port).await;
258+
// We don't assert specific results as they depend on network configuration
259+
// but ensure no panics occur
260+
println!("Connectivity test for {}:{} = {}", ip, port, result);
261+
}
262+
}
263+
}

0 commit comments

Comments
 (0)