22// Tests HTTP/HTTPS and WebSocket proxy functionality
33
44use std:: process:: { Child , Command } ;
5- use std:: sync:: OnceLock ;
65use std:: sync:: { Mutex , Once } ;
76use tokio:: time:: Duration ;
87
98static INIT : Once = Once :: new ( ) ;
10- static SERVER_PROCESS : OnceLock < Mutex < Option < Child > > > = OnceLock :: new ( ) ;
9+ static SERVER_PROCESS : Mutex < Option < Child > > = Mutex :: new ( None ) ;
1110
1211/// Get test server port from environment variable or use default 8080
1312fn get_test_port ( ) -> u16 {
@@ -27,6 +26,33 @@ fn get_ws_base_url() -> String {
2726 format ! ( "ws://localhost:{}" , get_test_port( ) )
2827}
2928
29+ /// Cleanup function to kill the test server
30+ fn cleanup_test_server ( ) {
31+ println ! ( "🧹 Cleaning up test server..." ) ;
32+ if let Ok ( mut guard) = SERVER_PROCESS . lock ( ) {
33+ if let Some ( mut child) = guard. take ( ) {
34+ let pid = child. id ( ) ;
35+ println ! ( "🛑 Killing server process (PID: {})" , pid) ;
36+
37+ // On Unix, kill the entire process group
38+ #[ cfg( unix) ]
39+ {
40+ use std:: process:: Command as SysCommand ;
41+ // Try to kill the process group first
42+ let _ = SysCommand :: new ( "kill" )
43+ . args ( & [ "-TERM" , & format ! ( "-{}" , pid) ] )
44+ . status ( ) ;
45+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 500 ) ) ;
46+ }
47+
48+ // Then kill the process itself
49+ let _ = child. kill ( ) ;
50+ let _ = child. wait ( ) ;
51+ println ! ( "✅ Server process stopped" ) ;
52+ }
53+ }
54+ }
55+
3056/// Initialize test environment (start server, wait for it to be ready)
3157fn setup_test_server ( ) {
3258 INIT . call_once ( || {
@@ -54,16 +80,22 @@ fn setup_test_server() {
5480 println ! ( "🔧 Starting server on port {}" , port) ;
5581
5682 // Build server command with environment variables
83+ // Use a process group so we can kill all children later
5784 let mut server_cmd = Command :: new ( "./target/release/ss-proxy" ) ;
5885 server_cmd
5986 . args ( & [ "--port" , & port. to_string ( ) , "--log-level" , "debug" ] )
6087 . env ( "TEST_PORT" , port. to_string ( ) ) ; // Ensure child process knows the port
6188
89+ // On Unix, set process group ID to enable killing the entire group
90+ #[ cfg( unix) ]
91+ {
92+ use std:: os:: unix:: process:: CommandExt ;
93+ server_cmd. process_group ( 0 ) ;
94+ }
95+
6296 let server = server_cmd. spawn ( ) . expect ( "Failed to start server" ) ;
6397
64- SERVER_PROCESS
65- . set ( Mutex :: new ( Some ( server) ) )
66- . expect ( "Failed to set server process" ) ;
98+ * SERVER_PROCESS . lock ( ) . unwrap ( ) = Some ( server) ;
6799
68100 // Wait for server to start
69101 println ! ( "⏳ Waiting for server to start..." ) ;
@@ -72,17 +104,36 @@ fn setup_test_server() {
72104 } ) ;
73105}
74106
75- /// Cleanup function (called when tests complete)
76- /// Note: The server will run for all tests and will be cleaned up when the test process exits
77- impl Drop for ServerGuard {
107+ /// Cleanup function to kill the server when all tests are done
108+ pub struct TestCleanup ;
109+
110+ impl Drop for TestCleanup {
78111 fn drop ( & mut self ) {
79- // Don't kill the server on each test - it should persist across all tests
80- // The server process will be cleaned up when the test process exits
112+ // This will be called when CLEANUP_GUARD is dropped
113+ // In practice, this may not be called by the test runner
114+ // Use cleanup_test_server() explicitly or run ./cleanup_test_server.sh
115+ cleanup_test_server ( ) ;
81116 }
82117}
83118
119+ // Global cleanup guard - attempt to cleanup when binary exits
120+ // NOTE: Rust's test runner may not call this reliably
121+ // If tests leave the server running, manually run: ./cleanup_test_server.sh <PORT>
122+ #[ allow( dead_code) ]
123+ static CLEANUP_GUARD : TestCleanup = TestCleanup ;
124+
125+ // Alternative: implement a custom test macro or add cleanup to each test
126+ // For now, users should run cleanup_test_server.sh after tests if needed
127+
84128struct ServerGuard ;
85129
130+ impl Drop for ServerGuard {
131+ fn drop ( & mut self ) {
132+ // Don't kill the server on each test - it should persist across all tests
133+ // Cleanup will happen via the global CLEANUP_GUARD when all tests finish
134+ }
135+ }
136+
86137// =============================================================================
87138// HTTP/HTTPS Proxy Tests
88139// =============================================================================
@@ -152,7 +203,11 @@ async fn test_http_proxy_with_query_params() {
152203 setup_test_server ( ) ;
153204 let _guard = ServerGuard ;
154205
155- let client = reqwest:: Client :: new ( ) ;
206+ let client = reqwest:: Client :: builder ( )
207+ . timeout ( Duration :: from_secs ( 60 ) ) // Increase timeout to 60 seconds
208+ . build ( )
209+ . expect ( "Failed to build client" ) ;
210+
156211 let response = client
157212 . get ( format ! (
158213 "{}/test-http/get?foo=bar&hello=world" ,
@@ -239,7 +294,10 @@ async fn test_websocket_echo() {
239294 let ( mut write, mut read) = ws_stream. split ( ) ;
240295
241296 // Skip the initial greeting message from echo.websocket.org
242- let _ = tokio:: time:: timeout ( Duration :: from_secs ( 2 ) , read. next ( ) ) . await ;
297+ // Read and discard the greeting message (e.g., "Request served by ...")
298+ if let Ok ( Some ( Ok ( msg) ) ) = tokio:: time:: timeout ( Duration :: from_secs ( 3 ) , read. next ( ) ) . await {
299+ println ! ( "Skipped greeting message: {:?}" , msg) ;
300+ }
243301
244302 // Send a text message
245303 let test_message = "Hello WebSocket!" ;
@@ -248,19 +306,35 @@ async fn test_websocket_echo() {
248306 . await
249307 . expect ( "Failed to send message" ) ;
250308
251- // Receive the echo
252- let received = tokio:: time:: timeout ( Duration :: from_secs ( 5 ) , read. next ( ) )
253- . await
254- . expect ( "Timeout waiting for message" )
255- . expect ( "No message received" )
256- . expect ( "Error receiving message" ) ;
257-
258- match received {
259- Message :: Text ( text) => {
260- assert_eq ! ( text. to_string( ) , test_message) ;
309+ // Receive the echo - may need to skip additional greeting messages
310+ let mut received_echo = None ;
311+ for _ in 0 ..3 {
312+ let received = tokio:: time:: timeout ( Duration :: from_secs ( 5 ) , read. next ( ) )
313+ . await
314+ . expect ( "Timeout waiting for message" )
315+ . expect ( "No message received" )
316+ . expect ( "Error receiving message" ) ;
317+
318+ match received {
319+ Message :: Text ( text) => {
320+ let text_str = text. to_string ( ) ;
321+ // Skip greeting messages from echo.websocket.org
322+ if text_str. starts_with ( "Request served by" ) {
323+ println ! ( "Skipping server info: {}" , text_str) ;
324+ continue ;
325+ }
326+ // This is our echo
327+ received_echo = Some ( text_str) ;
328+ break ;
329+ }
330+ _ => panic ! ( "Expected text message, got: {:?}" , received) ,
261331 }
262- _ => panic ! ( "Expected text message, got: {:?}" , received) ,
263332 }
333+
334+ assert_eq ! (
335+ received_echo. expect( "Did not receive echo message" ) ,
336+ test_message
337+ ) ;
264338}
265339
266340#[ tokio:: test]
@@ -279,7 +353,10 @@ async fn test_websocket_binary_message() {
279353 let ( mut write, mut read) = ws_stream. split ( ) ;
280354
281355 // Skip the initial greeting message from echo.websocket.org
282- let _ = tokio:: time:: timeout ( Duration :: from_secs ( 2 ) , read. next ( ) ) . await ;
356+ // Read and discard the greeting message (e.g., "Request served by ...")
357+ if let Ok ( Some ( Ok ( msg) ) ) = tokio:: time:: timeout ( Duration :: from_secs ( 3 ) , read. next ( ) ) . await {
358+ println ! ( "Skipped greeting message: {:?}" , msg) ;
359+ }
283360
284361 // Send binary data
285362 let test_data = vec ! [ 1u8 , 2 , 3 , 4 , 5 ] ;
@@ -288,19 +365,38 @@ async fn test_websocket_binary_message() {
288365 . await
289366 . expect ( "Failed to send binary message" ) ;
290367
291- // Receive the echo
292- let received = tokio:: time:: timeout ( Duration :: from_secs ( 5 ) , read. next ( ) )
293- . await
294- . expect ( "Timeout waiting for message" )
295- . expect ( "No message received" )
296- . expect ( "Error receiving message" ) ;
297-
298- match received {
299- Message :: Binary ( data) => {
300- assert_eq ! ( data. to_vec( ) , test_data) ;
368+ // Receive the echo - may need to skip additional text messages
369+ let mut received_binary = None ;
370+ for _ in 0 ..3 {
371+ let received = tokio:: time:: timeout ( Duration :: from_secs ( 5 ) , read. next ( ) )
372+ . await
373+ . expect ( "Timeout waiting for message" )
374+ . expect ( "No message received" )
375+ . expect ( "Error receiving message" ) ;
376+
377+ match received {
378+ Message :: Binary ( data) => {
379+ // This is our echo
380+ received_binary = Some ( data. to_vec ( ) ) ;
381+ break ;
382+ }
383+ Message :: Text ( text) => {
384+ let text_str = text. to_string ( ) ;
385+ // Skip greeting messages from echo.websocket.org
386+ if text_str. starts_with ( "Request served by" ) {
387+ println ! ( "Skipping server info: {}" , text_str) ;
388+ continue ;
389+ }
390+ panic ! ( "Expected binary message, got unexpected text: {}" , text_str) ;
391+ }
392+ _ => panic ! ( "Expected binary message, got: {:?}" , received) ,
301393 }
302- _ => panic ! ( "Expected binary message, got: {:?}" , received) ,
303394 }
395+
396+ assert_eq ! (
397+ received_binary. expect( "Did not receive binary echo" ) ,
398+ test_data
399+ ) ;
304400}
305401
306402#[ tokio:: test]
@@ -369,3 +465,18 @@ async fn test_websocket_multiple_messages() {
369465 }
370466 }
371467}
468+
469+ // =============================================================================
470+ // Cleanup Test - Runs last to kill the server
471+ // =============================================================================
472+
473+ /// This test should ideally run last to clean up the server
474+ /// Note: Test execution order is not guaranteed in Rust
475+ /// If server is still running after tests, use: ./cleanup_test_server.sh <PORT>
476+ #[ tokio:: test]
477+ async fn test_zzz_cleanup ( ) {
478+ // Use zzz_ prefix to encourage this test to run last (though not guaranteed)
479+ println ! ( "🧹 Running cleanup test..." ) ;
480+ cleanup_test_server ( ) ;
481+ println ! ( "✅ Cleanup test complete" ) ;
482+ }
0 commit comments