aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-client/src/web_client/unit
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2026-06-10 16:50:55 +0200
committerGitHub <noreply@github.com>2026-06-10 16:50:55 +0200
commitb6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0 (patch)
tree38c77a381cfb937b428752a588c09a45c81434c9 /zellij-client/src/web_client/unit
parentfeat: PWA support for the web client (#5184) (diff)
downloadsl0ppij-b6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0.tar.gz
sl0ppij-b6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0.tar.xz
sl0ppij-b6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0.zip
Mobile layout (#5241)
* phase 2+3 * initial mobile plugin (phases 4 + 5 + 6) * some bugfixes * implement stage 7 * mobile interface improvements * improve mobile pane/tab picker * more ui improvements and fixes * various mobile fixes * some mobile fixes * mobile keyboard triggering fix * some cleanups * initial plugin keyboard implementation * improve keyboard hit zones * improve keyboard * pinch resize * pinching fixes * implement scrolling + panning * initial keyboard resizing * fix: mobile first render issues * keyboard responsiveness * fix some issues with the keyboard * implement fit * fix mobile scrolling * add tilde cell to compact * improve mobile keyboard * fix starting mobile terminal font to natural keyboard size * fix mobile resize blank screen flash * shadow focus for mobile * more keyboard fixes * improve mobile status bar * fix: render plugins to mobile clients * allow opening new panes/tabs from the mobile plugin * allow configuring * fix dot slash * improve mobile native soft keyboard interaction * switch to native keyboard - initial implementation * dirty hacks, but phone keyboard works now * adjust keyboard help strip * fix cursor in mobile * fix keyboard and help line * rename keyboard to modifier bar * allow starting new session * make clicking session name bring us to new session menu * allow switching to desktop from the mobile ui * mobile welcome screen initial implementation * mobile welcome screen fixes * various welcome screen fixes * more mobile fixes * moar mobile fixes * mobile ui redesign * fix: swallowed backspace bug * switch session screen improvement * touch up some interfaces * refactor: move mobile state to own struct * refactor: move fit resizing to the server * remove dead flash code * some cleanups * refactor: clear fit on close * refactor: improve fit api * refactor: pane sync functions * refactor: mouse * refactor: break into modules * some cleanups for the sessions screen * fix scrolling and other stuff * refactor: sessions screen * refactor: panes screen * refactor: new session screen * refactor: menu screen * refactor: render * cleanups * refactor: plugin structure * remove unused welcome screen path * Rename api * refactor input.js * cleanup useless comments * fit/mobile refactoring * take out the garbage * remove more comments * remove mobile flash on startup/attach * fix: various mobile startup issues * fix space bugs * cleanup * rustfmt * fix windows build * adjust modifier bar ui * add pr
Diffstat (limited to 'zellij-client/src/web_client/unit')
-rw-r--r--zellij-client/src/web_client/unit/web_client_tests.rs179
1 files changed, 7 insertions, 172 deletions
diff --git a/zellij-client/src/web_client/unit/web_client_tests.rs b/zellij-client/src/web_client/unit/web_client_tests.rs
index 726e73a6..28c2e87c 100644
--- a/zellij-client/src/web_client/unit/web_client_tests.rs
+++ b/zellij-client/src/web_client/unit/web_client_tests.rs
@@ -44,7 +44,6 @@ mod web_client_tests {
.await
{
Ok(Ok(_)) => {
- // server ready
return Ok(());
},
Ok(Err(e)) => {
@@ -113,7 +112,6 @@ mod web_client_tests {
server_handle.abort();
- // time for cleanup
tokio::time::sleep(Duration::from_millis(100)).await;
}
@@ -190,7 +188,6 @@ mod web_client_tests {
server_handle.abort();
revoke_token(test_token_name).expect("Failed to revoke test token");
- // time for cleanup
tokio::time::sleep(Duration::from_millis(100)).await;
}
@@ -428,7 +425,7 @@ mod web_client_tests {
let messages = mock_api.get_sent_messages();
for msg in messages {
match msg {
- ClientToServerMsg::TerminalResize { new_size: _ } => {
+ ClientToServerMsg::TerminalResize { .. } => {
found_resize = true;
},
ClientToServerMsg::Key { .. }
@@ -465,12 +462,6 @@ mod web_client_tests {
#[tokio::test]
#[serial]
async fn test_terminal_metrics_translates_to_pixel_dimensions() {
- // Simulates a browser sending a TerminalMetrics control message
- // (the payload our websockets.js sendTerminalMetrics() helper
- // produces) and verifies the web server translates it into
- // ClientToServerMsg::TerminalPixelDimensions with field-for-field
- // accuracy. This guards the wire-format contract end-to-end
- // through serde + the match arm in websocket_handlers.rs.
let _ = delete_db();
let test_token_name = "test_token_terminal_metrics";
@@ -563,7 +554,6 @@ mod web_client_tests {
serde_json::from_str(&client_response.text().unwrap()).unwrap();
let web_client_id = client_data["web_client_id"].as_str().unwrap().to_string();
- // Open the control WebSocket and consume the initial SetConfig.
let control_ws_url = format!("ws://127.0.0.1:{}/ws/control", port);
let (control_ws, _) = timeout(
Duration::from_secs(5),
@@ -577,9 +567,6 @@ mod web_client_tests {
.await
.expect("Timeout waiting for initial control message");
- // The control channel only registers the client_id after the
- // first inbound message. Send a TerminalResize first to mirror
- // what the browser does at startup, then the TerminalMetrics.
let resize_msg = WebClientToWebServerControlMessage {
web_client_id: web_client_id.clone(),
payload: WebClientToWebServerControlMessagePayload::TerminalResize(Size {
@@ -594,11 +581,6 @@ mod web_client_tests {
.await
.expect("Failed to send TerminalResize");
- // Send the actual TerminalMetrics payload — this is what the
- // browser-side sendTerminalMetrics() helper writes onto the wire.
- // Hand-construct the JSON to lock in the exact field names the
- // browser uses, rather than going through the serde Serialize
- // impl (which would mask any rename mismatch).
let metrics_json = serde_json::json!({
"web_client_id": web_client_id,
"payload": {
@@ -614,11 +596,8 @@ mod web_client_tests {
.await
.expect("Failed to send TerminalMetrics");
- // Give the server a moment to process both messages.
tokio::time::sleep(Duration::from_millis(500)).await;
- // Inspect the captured ClientToServerMsg traffic on the mock OS
- // API for this web client.
let mock_apis = factory_for_verification.mock_apis.lock().unwrap();
let mut found_pixel_dims: Option<ClientToServerMsg> = None;
for (_, mock_api) in mock_apis.iter() {
@@ -802,13 +781,10 @@ mod web_client_tests {
.await
.expect("Server failed to start");
- // Login and get session token
let session_token = login_and_get_session_token(port, &auth_token).await;
- // Create client session
let web_client_id = create_client_session(port, &session_token).await;
- // Establish control WebSocket connection
let control_ws_url = format!("ws://127.0.0.1:{}/ws/control", port);
let (control_ws, _) = timeout(
Duration::from_secs(5),
@@ -820,12 +796,10 @@ mod web_client_tests {
let (mut control_sink, mut control_stream) = control_ws.split();
- // Wait for initial SetConfig message
let _initial_msg = timeout(Duration::from_secs(2), control_stream.next())
.await
.expect("Timeout waiting for initial control message");
- // Send resize message to establish proper connection
let resize_msg = WebClientToWebServerControlMessage {
web_client_id: web_client_id.clone(),
payload: WebClientToWebServerControlMessagePayload::TerminalResize(Size {
@@ -839,7 +813,6 @@ mod web_client_tests {
.await
.expect("Failed to send resize message");
- // Establish terminal WebSocket connection
let terminal_ws_url = format!(
"ws://127.0.0.1:{}/ws/terminal?web_client_id={}",
port, web_client_id
@@ -854,10 +827,8 @@ mod web_client_tests {
let (_terminal_sink, mut terminal_stream) = terminal_ws.split();
- // Trigger server shutdown
server_handle.abort();
- // Verify control WebSocket receives close frame
let control_close_result = timeout(Duration::from_secs(3), control_stream.next()).await;
match control_close_result {
Ok(Some(Ok(Message::Close(_)))) => {
@@ -880,7 +851,6 @@ mod web_client_tests {
},
}
- // Verify terminal WebSocket receives close frame or connection ends
let terminal_close_result = timeout(Duration::from_secs(3), terminal_stream.next()).await;
match terminal_close_result {
Ok(Some(Ok(Message::Close(_)))) => {
@@ -904,7 +874,6 @@ mod web_client_tests {
}
revoke_token(test_token_name).expect("Failed to revoke test token");
- // time for cleanup
tokio::time::sleep(Duration::from_millis(100)).await;
}
@@ -949,14 +918,11 @@ mod web_client_tests {
.await
.expect("Server failed to start");
- // Login and get session token
let session_token = login_and_get_session_token(port, &auth_token).await;
- // Create multiple client sessions
let client_id_1 = create_client_session(port, &session_token).await;
let client_id_2 = create_client_session(port, &session_token).await;
- // Establish WebSocket connections for both clients
let control_ws_url_1 = format!("ws://127.0.0.1:{}/ws/control", port);
let (control_ws_1, _) = timeout(
Duration::from_secs(5),
@@ -979,11 +945,9 @@ mod web_client_tests {
let (mut control_sink_2, mut control_stream_2) = control_ws_2.split();
- // Wait for initial messages and establish connections
let _initial_msg_1 = timeout(Duration::from_secs(2), control_stream_1.next()).await;
let _initial_msg_2 = timeout(Duration::from_secs(2), control_stream_2.next()).await;
- // Send messages to establish proper connections
let resize_msg_1 = WebClientToWebServerControlMessage {
web_client_id: client_id_1.clone(),
payload: WebClientToWebServerControlMessagePayload::TerminalResize(Size {
@@ -1010,7 +974,6 @@ mod web_client_tests {
.await
.expect("Failed to send resize message for client 2");
- // Establish terminal connections
let terminal_ws_url_1 = format!(
"ws://127.0.0.1:{}/ws/terminal?web_client_id={}",
port, client_id_1
@@ -1025,7 +988,6 @@ mod web_client_tests {
let (_terminal_sink_1, _terminal_stream_1) = terminal_ws_1.split();
- // Verify both clients are initially present by checking mock APIs
tokio::time::sleep(Duration::from_millis(200)).await;
let initial_api_count = factory_for_verification.mock_apis.lock().unwrap().len();
assert!(
@@ -1033,13 +995,10 @@ mod web_client_tests {
"Should have at least 2 client APIs created"
);
- // Close connection for client 1 by closing WebSocket
let _ = control_sink_1.close().await;
- // Allow time for cleanup
tokio::time::sleep(Duration::from_millis(500)).await;
- // Verify client 2 is still functional by sending another message
let resize_msg_2_again = WebClientToWebServerControlMessage {
web_client_id: client_id_2.clone(),
payload: WebClientToWebServerControlMessagePayload::TerminalResize(Size {
@@ -1059,7 +1018,6 @@ mod web_client_tests {
Err(e) => println!("Client 2 send failed (may be expected): {:?}", e),
}
- // Verify messages were received by checking mock APIs
let mock_apis = factory_for_verification.mock_apis.lock().unwrap();
let mut total_resize_messages: usize = 0;
@@ -1123,11 +1081,9 @@ mod web_client_tests {
.await
.expect("Server failed to start");
- // Login and create session
let session_token = login_and_get_session_token(port, &auth_token).await;
let web_client_id = create_client_session(port, &session_token).await;
- // Establish terminal WebSocket connection
let terminal_ws_url = format!(
"ws://127.0.0.1:{}/ws/terminal?web_client_id={}",
port, web_client_id
@@ -1142,16 +1098,13 @@ mod web_client_tests {
let (mut terminal_sink, mut terminal_stream) = terminal_ws.split();
- // Send some data to ensure connection is active and render loop is running
terminal_sink
.send(Message::Text("test input\n".to_string()))
.await
.expect("Failed to send terminal input");
- // Allow connection to stabilize and render loop to start
tokio::time::sleep(Duration::from_millis(500)).await;
- // Trigger shutdown by aborting server - this should trigger cancellation tokens
server_handle.abort();
let mut connection_terminated = false;
@@ -1188,15 +1141,12 @@ mod web_client_tests {
connection_terminated = true;
},
Err(_) => {
- // Timeout on this iteration, continue monitoring
println!("Timeout on stream.next(), continuing to monitor...");
},
}
}
- // If connection hasn't terminated through normal means, check if it's due to server shutdown
if !connection_terminated {
- // Try one more time to see if the connection is actually closed
match timeout(Duration::from_millis(100), terminal_stream.next()).await {
Ok(None) => {
println!("✓ Terminal WebSocket stream ended after server abort");
@@ -1210,8 +1160,6 @@ mod web_client_tests {
},
_ => {
println!("Connection still active after server abort - this may indicate the cancellation token isn't working as expected in test environment");
- // In test environment, server abort might not trigger cancellation tokens immediately
- // We'll consider the test successful if we've aborted the server
termination_reason = "server_aborted";
connection_terminated = true;
},
@@ -1269,11 +1217,9 @@ mod web_client_tests {
.await
.expect("Server failed to start");
- // Login and create session
let session_token = login_and_get_session_token(port, &auth_token).await;
let web_client_id = create_client_session(port, &session_token).await;
- // Establish terminal WebSocket connection
let terminal_ws_url = format!(
"ws://127.0.0.1:{}/ws/terminal?web_client_id={}",
port, web_client_id
@@ -1288,19 +1234,15 @@ mod web_client_tests {
let (mut terminal_sink, mut terminal_stream) = terminal_ws.split();
- // Send terminal input to ensure connection is established
terminal_sink
.send(Message::Text("echo test\n".to_string()))
.await
.expect("Failed to send terminal input");
- // Allow connection to stabilize
tokio::time::sleep(Duration::from_millis(200)).await;
- // Create a mock API and simulate different exit scenarios by sending exit message
let mock_apis = factory_for_verification.mock_apis.lock().unwrap();
if let Some((_, mock_api)) = mock_apis.iter().next() {
- // Simulate ClientExited message being sent
mock_api
.messages_to_server
.lock()
@@ -1309,10 +1251,8 @@ mod web_client_tests {
}
drop(mock_apis);
- // Close the WebSocket connection to trigger cleanup
let _ = terminal_sink.close().await;
- // Monitor for connection termination
let close_result = timeout(Duration::from_secs(3), terminal_stream.next()).await;
match close_result {
Ok(Some(Ok(Message::Close(_)))) => {
@@ -1332,7 +1272,6 @@ mod web_client_tests {
},
}
- // Verify that ClientExited message was processed
let mock_apis = factory_for_verification.mock_apis.lock().unwrap();
let mut found_client_exited = false;
@@ -1353,7 +1292,6 @@ mod web_client_tests {
server_handle.abort();
revoke_token(test_token_name).expect("Failed to revoke test token");
- // time for cleanup
tokio::time::sleep(Duration::from_millis(100)).await;
}
@@ -1362,13 +1300,10 @@ mod web_client_tests {
async fn test_read_only_token_cannot_create_new_session() {
let _ = delete_db();
- // Create read-only token
let (auth_token, _) = create_token(Some("test-readonly".to_string()), true)
.expect("Failed to create read-only token");
- // Setup mocks
let mock_session_manager = Arc::new(MockSessionManager::new());
- // Do NOT mark any session as existing - we want to verify read-only cannot create new sessions
let mock_os_api_factory = Arc::new(MockClientOsApiFactory::new());
let session_manager_for_verification = mock_session_manager.clone();
@@ -1400,7 +1335,6 @@ mod web_client_tests {
.await
.expect("Server should start successfully");
- // Login and create client
let session_token = login_and_get_session_token(port, &auth_token).await;
let session_url = format!("http://127.0.0.1:{}/session", port);
@@ -1431,22 +1365,16 @@ mod web_client_tests {
assert_eq!(is_read_only, true, "Client should be marked as read-only");
- // Try to connect via control WebSocket
- // This will trigger the server_listener which should close the connection
- // because read-only client is trying to attach to non-existent session
let control_ws_url = format!("ws://127.0.0.1:{}/ws/control", port);
- // The connection might fail or close immediately
let _ws_result = timeout(
Duration::from_secs(3),
connect_async_with_cookie(&control_ws_url, &session_token),
)
.await;
- // Give time for server_listener to process and close connection
tokio::time::sleep(Duration::from_millis(500)).await;
- // Verify no session was created
assert!(
!session_manager_for_verification.was_session_created("default"),
"No session should be created for read-only token attempting to create new session"
@@ -1462,11 +1390,9 @@ mod web_client_tests {
async fn test_read_only_token_uses_watcher_message_type() {
let _ = delete_db();
- // Create both regular and read-only tokens
let (regular_token, _) = create_token(Some("regular".to_string()), false).unwrap();
let (readonly_token, _) = create_token(Some("readonly".to_string()), true).unwrap();
- // Setup mocks - mark all sessions as existing so clients use AttachClient/AttachWatcherClient
let mock_session_manager = Arc::new(MockSessionManager::with_all_sessions_existing());
let session_manager_for_verification = mock_session_manager.clone();
@@ -1500,11 +1426,9 @@ mod web_client_tests {
.await
.expect("Server should start");
- // First, attach with REGULAR token to establish baseline
let regular_session_token = login_and_get_session_token(port, &regular_token).await;
let regular_web_client_id = create_client_session(port, &regular_session_token).await;
- // Connect control websocket
let regular_control_ws_url = format!("ws://127.0.0.1:{}/ws/control", port);
let (regular_control_ws, _) = timeout(
Duration::from_secs(5),
@@ -1516,7 +1440,6 @@ mod web_client_tests {
let (mut regular_control_sink, _regular_control_stream) = regular_control_ws.split();
- // Connect terminal websocket to trigger server_listener
let regular_terminal_ws_url = format!(
"ws://127.0.0.1:{}/ws/terminal?web_client_id={}",
port, regular_web_client_id
@@ -1531,11 +1454,8 @@ mod web_client_tests {
let (mut regular_terminal_sink, _regular_terminal_stream) = regular_terminal_ws.split();
- // Wait for attachment to complete
tokio::time::sleep(Duration::from_millis(500)).await;
- // VERIFY: Regular client should use AttachClient (not FirstClientConnected, since session exists)
- // Check what sessions we actually have
let regular_msg = {
let all_messages = session_manager_for_verification
.first_messages_sent
@@ -1559,11 +1479,9 @@ mod web_client_tests {
regular_msg
);
- // Now attach with READ-ONLY token
let readonly_session_token = login_and_get_session_token(port, &readonly_token).await;
let readonly_web_client_id = create_client_session(port, &readonly_session_token).await;
- // Connect control websocket
let readonly_control_ws_url = format!("ws://127.0.0.1:{}/ws/control", port);
let (readonly_control_ws, _) = timeout(
Duration::from_secs(5),
@@ -1575,7 +1493,6 @@ mod web_client_tests {
let (mut readonly_control_sink, _readonly_control_stream) = readonly_control_ws.split();
- // Connect terminal websocket to trigger server_listener
let readonly_terminal_ws_url = format!(
"ws://127.0.0.1:{}/ws/terminal?web_client_id={}",
port, readonly_web_client_id
@@ -1590,23 +1507,19 @@ mod web_client_tests {
let (mut readonly_terminal_sink, _readonly_terminal_stream) = readonly_terminal_ws.split();
- // Wait for attachment to complete
tokio::time::sleep(Duration::from_millis(500)).await;
- // VERIFY: Read-only client should use AttachWatcherClient
let readonly_msg = {
let all_messages = session_manager_for_verification
.first_messages_sent
.lock()
.unwrap();
- // Should have at least 2 messages now (regular + readonly)
assert!(
all_messages.len() >= 2,
"Should have at least 2 messages (regular and readonly)"
);
- // Get the second message (readonly client)
let (_readonly_session_name, msg) = all_messages
.get(1)
.expect("Should have message for read-only client");
@@ -1620,7 +1533,6 @@ mod web_client_tests {
readonly_msg
);
- // Verify the terminal size is passed correctly
if let ClientToServerMsg::AttachWatcherClient { terminal_size, .. } = readonly_msg {
assert!(terminal_size.rows > 0 && terminal_size.cols > 0);
}
@@ -1639,12 +1551,9 @@ mod web_client_tests {
async fn test_regular_token_uses_first_client_connected_for_new_session() {
let _ = delete_db();
- // Create regular token
let (regular_token, _) = create_token(Some("regular".to_string()), false).unwrap();
- // Setup mocks
let mock_session_manager = Arc::new(MockSessionManager::new());
- // Do NOT mark session as existing - we want to create a new one
let session_manager_for_verification = mock_session_manager.clone();
let mock_os_api_factory = Arc::new(MockClientOsApiFactory::new());
@@ -1676,11 +1585,9 @@ mod web_client_tests {
.await
.expect("Server should start");
- // Login and create client
let session_token = login_and_get_session_token(port, &regular_token).await;
let web_client_id = create_client_session(port, &session_token).await;
- // Connect control websocket
let control_ws_url = format!("ws://127.0.0.1:{}/ws/control", port);
let (control_ws, _) = timeout(
Duration::from_secs(5),
@@ -1692,7 +1599,6 @@ mod web_client_tests {
let (mut control_sink, _control_stream) = control_ws.split();
- // Connect terminal websocket to trigger server_listener
let terminal_ws_url = format!(
"ws://127.0.0.1:{}/ws/terminal?web_client_id={}",
port, web_client_id
@@ -1707,17 +1613,13 @@ mod web_client_tests {
let (mut terminal_sink, _terminal_stream) = terminal_ws.split();
- // Wait for session creation to complete
tokio::time::sleep(Duration::from_millis(500)).await;
- // VERIFY: Regular client creating new session should use FirstClientConnected
- // The session name will be "default" or a generated name
let all_messages = session_manager_for_verification
.first_messages_sent
.lock()
.unwrap();
- // Find the first message (should be FirstClientConnected)
let msg = all_messages
.first()
.map(|(_, msg)| msg)
@@ -1729,7 +1631,6 @@ mod web_client_tests {
msg
);
- // Verify session was marked as created
let sessions_created = session_manager_for_verification
.sessions_created
.lock()
@@ -1751,11 +1652,9 @@ mod web_client_tests {
async fn test_read_only_status_tracked_in_connection_table() {
let _ = delete_db();
- // Create tokens
let (regular_token, _) = create_token(Some("regular".to_string()), false).unwrap();
let (readonly_token, _) = create_token(Some("readonly".to_string()), true).unwrap();
- // Setup mocks
let mock_session_manager = Arc::new(MockSessionManager::new());
let mock_os_api_factory = Arc::new(MockClientOsApiFactory::new());
@@ -1787,7 +1686,6 @@ mod web_client_tests {
.await
.expect("Server should start");
- // Create multiple clients with different tokens
let regular_session_token = login_and_get_session_token(port, &regular_token).await;
let session_url = format!("http://127.0.0.1:{}/session", port);
@@ -1840,7 +1738,6 @@ mod web_client_tests {
serde_json::from_str(&readonly_response.text().unwrap()).unwrap();
let readonly_is_read_only = readonly_client_data["is_read_only"].as_bool().unwrap();
- // Verify is_read_only flag in responses
assert_eq!(
regular_is_read_only, false,
"Regular client should not be read-only"
@@ -1856,7 +1753,6 @@ mod web_client_tests {
tokio::time::sleep(Duration::from_millis(100)).await;
}
- // Helper function to login and get session token
async fn login_and_get_session_token(port: u16, auth_token: &str) -> String {
let login_url = format!("http://127.0.0.1:{}/command/login", port);
let login_payload = serde_json::json!({
@@ -1891,7 +1787,6 @@ mod web_client_tests {
.to_string()
}
- // Helper function to create client session
async fn create_client_session(port: u16, session_token: &str) -> String {
let session_url = format!("http://127.0.0.1:{}/session", port);
let mut client_response = timeout(
@@ -1964,7 +1859,6 @@ mod web_client_tests {
let session_token = login_and_get_session_token(port, &auth_token).await;
let web_client_id = create_client_session(port, &session_token).await;
- // Establish terminal WebSocket connection
let terminal_ws_url = format!(
"ws://127.0.0.1:{}/ws/terminal?web_client_id={}",
port, web_client_id
@@ -1979,7 +1873,6 @@ mod web_client_tests {
let (_terminal_sink, mut terminal_stream) = terminal_ws.split();
- // Establish control WebSocket connection
let control_ws_url = format!("ws://127.0.0.1:{}/ws/control", port);
let (control_ws, _) = timeout(
Duration::from_secs(5),
@@ -1991,7 +1884,6 @@ mod web_client_tests {
let (mut control_sink, mut control_stream) = control_ws.split();
- // Wait for initial SetConfig and send resize to register client_id on the control channel
let _initial_msg = timeout(Duration::from_secs(2), control_stream.next()).await;
let resize_msg = WebClientToWebServerControlMessage {
web_client_id: web_client_id.clone(),
@@ -2005,10 +1897,8 @@ mod web_client_tests {
.await
.expect("Failed to send resize message");
- // Allow connection to stabilize
tokio::time::sleep(Duration::from_millis(200)).await;
- // Queue KickedByHost exit message into the mock API
{
let mock_apis = factory_for_verification.mock_apis.lock().unwrap();
assert!(
@@ -2022,8 +1912,6 @@ mod web_client_tests {
}
}
- // Wait for terminal WebSocket to close with code 4001
- // Use a polling loop to handle any non-close messages that may arrive first
let mut terminal_got_4001 = false;
let mut terminal_closed = false;
let deadline = tokio::time::Instant::now() + Duration::from_secs(8);
@@ -2040,7 +1928,6 @@ mod web_client_tests {
break;
},
Ok(Some(Ok(_other))) => {
- // skip non-close messages (e.g. buffered render data)
continue;
},
Ok(Some(Err(_))) | Ok(None) => {
@@ -2048,7 +1935,6 @@ mod web_client_tests {
break;
},
Err(_) => {
- // timeout on this iteration, continue polling
continue;
},
}
@@ -2062,7 +1948,6 @@ mod web_client_tests {
"Terminal WebSocket should close with code 4001 when kicked by host"
);
- // Wait for control WebSocket to close with code 4001 (skip any pending non-Close messages)
let mut control_got_4001 = false;
let mut control_closed = false;
let deadline = tokio::time::Instant::now() + Duration::from_secs(5);
@@ -2079,7 +1964,6 @@ mod web_client_tests {
break;
},
Ok(Some(Ok(_msg))) => {
- // Skip non-Close messages
continue;
},
Ok(Some(Err(_))) | Ok(None) => {
@@ -2148,7 +2032,6 @@ mod web_client_tests {
let session_token = login_and_get_session_token(port, &auth_token).await;
let web_client_id = create_client_session(port, &session_token).await;
- // Establish terminal WebSocket connection
let terminal_ws_url = format!(
"ws://127.0.0.1:{}/ws/terminal?web_client_id={}",
port, web_client_id
@@ -2163,7 +2046,6 @@ mod web_client_tests {
let (_terminal_sink, mut terminal_stream) = terminal_ws.split();
- // Establish control WebSocket connection
let control_ws_url = format!("ws://127.0.0.1:{}/ws/control", port);
let (control_ws, _) = timeout(
Duration::from_secs(5),
@@ -2175,7 +2057,6 @@ mod web_client_tests {
let (mut control_sink, mut control_stream) = control_ws.split();
- // Wait for initial SetConfig and send resize to register client_id on the control channel
let _initial_msg = timeout(Duration::from_secs(2), control_stream.next()).await;
let resize_msg = WebClientToWebServerControlMessage {
web_client_id: web_client_id.clone(),
@@ -2189,10 +2070,8 @@ mod web_client_tests {
.await
.expect("Failed to send resize message");
- // Allow connection to stabilize
tokio::time::sleep(Duration::from_millis(200)).await;
- // Queue Normal exit message into the mock API
{
let mock_apis = factory_for_verification.mock_apis.lock().unwrap();
assert!(
@@ -2206,8 +2085,6 @@ mod web_client_tests {
}
}
- // Wait for terminal WebSocket to close with NORMAL (1000) close code
- // Use a polling loop to handle any non-close messages that may arrive first
let mut terminal_got_normal = false;
let mut terminal_closed = false;
let deadline = tokio::time::Instant::now() + Duration::from_secs(8);
@@ -2220,7 +2097,6 @@ mod web_client_tests {
break;
},
Ok(Some(Ok(Message::Close(None)))) => {
- // Close with no code also counts as a normal close
terminal_got_normal = true;
terminal_closed = true;
break;
@@ -2262,9 +2138,6 @@ mod web_client_tests {
),
tokio_tungstenite::tungstenite::Error,
> {
- // Manually construct WebSocket request with required headers since we need to add a custom cookie.
- // When building the request manually, we must include all the standard WebSocket handshake headers
- // that would normally be added automatically by the WebSocket client library.
let request = Request::builder()
.uri(url)
.header("Cookie", format!("session_token={}", session_token))
@@ -2278,8 +2151,6 @@ mod web_client_tests {
connect_async(request).await
}
- // ========== Task 1: Security Headers Tests ==========
-
#[tokio::test]
#[serial]
async fn test_security_headers_present_on_all_responses() {
@@ -2362,7 +2233,6 @@ mod web_client_tests {
csp.contains("default-src 'self'"),
"CSP should contain default-src 'self'"
);
- // HSTS should NOT be present when is_https is false
assert!(
response
.headers()
@@ -2458,14 +2328,11 @@ mod web_client_tests {
tokio::time::sleep(Duration::from_millis(100)).await;
}
- // ========== Task 10: web_client_id binding tests ==========
-
#[tokio::test]
#[serial]
async fn test_control_websocket_rejects_foreign_web_client_id() {
let _ = delete_db();
- // Create a read-write token and a read-only token
let (rw_token, _) = create_token(Some("rw_token".to_string()), false).unwrap();
let (ro_token, _) = create_token(Some("ro_token".to_string()), true).unwrap();
@@ -2501,15 +2368,12 @@ mod web_client_tests {
.await
.expect("Server failed to start");
- // Login with read-write token and create a client session
let rw_session_token = login_and_get_session_token(port, &rw_token).await;
let rw_web_client_id = create_client_session(port, &rw_session_token).await;
- // Login with read-only token and create a client session
let ro_session_token = login_and_get_session_token(port, &ro_token).await;
let _ro_web_client_id = create_client_session(port, &ro_session_token).await;
- // Connect a control WebSocket using the read-only session token
let control_ws_url = format!("ws://127.0.0.1:{}/ws/control", port);
let (control_ws, _) = timeout(
Duration::from_secs(5),
@@ -2521,10 +2385,8 @@ mod web_client_tests {
let (mut control_sink, mut control_stream) = control_ws.split();
- // Wait for initial SetConfig message
let _initial_msg = timeout(Duration::from_secs(2), control_stream.next()).await;
- // Send a TerminalResize message with the READ-WRITE user's web_client_id
let resize_msg = WebClientToWebServerControlMessage {
web_client_id: rw_web_client_id.clone(),
payload: WebClientToWebServerControlMessagePayload::TerminalResize(Size {
@@ -2538,18 +2400,11 @@ mod web_client_tests {
.await
.expect("Failed to send resize message");
- // The WebSocket should be closed by the server
let close_result = timeout(Duration::from_secs(3), control_stream.next()).await;
match close_result {
- Ok(Some(Ok(Message::Close(_)))) | Ok(Some(Err(_))) | Ok(None) => {
- // Expected: connection was closed
- },
- Err(_) => {
- // Timeout - connection may have been silently dropped
- },
+ Ok(Some(Ok(Message::Close(_)))) | Ok(Some(Err(_))) | Ok(None) => {},
+ Err(_) => {},
Ok(Some(Ok(_))) => {
- // If we get another message, the connection wasn't closed - this is a failure
- // but let's check if it closes after
let second_result = timeout(Duration::from_secs(2), control_stream.next()).await;
assert!(
matches!(
@@ -2561,7 +2416,6 @@ mod web_client_tests {
},
}
- // Verify that NO TerminalResize message was forwarded on behalf of the rw client
tokio::time::sleep(Duration::from_millis(200)).await;
let mock_apis = factory_for_verification.mock_apis.lock().unwrap();
for (_, mock_api) in mock_apis.iter() {
@@ -2634,10 +2488,8 @@ mod web_client_tests {
let (mut control_sink, mut control_stream) = control_ws.split();
- // Wait for initial SetConfig message
let _initial_msg = timeout(Duration::from_secs(2), control_stream.next()).await;
- // Send a TerminalResize message with the CORRECT web_client_id
let resize_msg = WebClientToWebServerControlMessage {
web_client_id: web_client_id.clone(),
payload: WebClientToWebServerControlMessagePayload::TerminalResize(Size {
@@ -2651,10 +2503,8 @@ mod web_client_tests {
.await
.expect("Failed to send resize message");
- // Allow message processing
tokio::time::sleep(Duration::from_millis(500)).await;
- // Verify the resize was forwarded
let mock_apis = factory_for_verification.mock_apis.lock().unwrap();
let mut found_resize = false;
for (_, mock_api) in mock_apis.iter() {
@@ -2715,14 +2565,12 @@ mod web_client_tests {
.await
.expect("Server failed to start");
- // Create two sessions with different tokens
let rw_session_token = login_and_get_session_token(port, &rw_token).await;
let rw_web_client_id = create_client_session(port, &rw_session_token).await;
let ro_session_token = login_and_get_session_token(port, &ro_token).await;
let _ro_web_client_id = create_client_session(port, &ro_session_token).await;
- // Try to connect terminal WebSocket using ro session token but rw web_client_id
let terminal_ws_url = format!(
"ws://127.0.0.1:{}/ws/terminal?web_client_id={}",
port, rw_web_client_id
@@ -2735,25 +2583,15 @@ mod web_client_tests {
match ws_result {
Ok(Ok((ws, _))) => {
- // Connection was established but should be closed immediately
let (_sink, mut stream) = ws.split();
let msg = timeout(Duration::from_secs(2), stream.next()).await;
match msg {
- Ok(Some(Ok(Message::Close(_)))) | Ok(Some(Err(_))) | Ok(None) | Err(_) => {
- // Expected: connection was rejected/closed
- },
- Ok(Some(Ok(_))) => {
- // Unexpected: received data on a connection that should have been rejected
- // The server may close after initial processing
- },
+ Ok(Some(Ok(Message::Close(_)))) | Ok(Some(Err(_))) | Ok(None) | Err(_) => {},
+ Ok(Some(Ok(_))) => {},
}
},
- Ok(Err(_)) => {
- // Connection failed - expected behavior
- },
- Err(_) => {
- // Timeout - acceptable
- },
+ Ok(Err(_)) => {},
+ Err(_) => {},
}
server_handle.abort();
@@ -2761,8 +2599,6 @@ mod web_client_tests {
tokio::time::sleep(Duration::from_millis(100)).await;
}
- // ========== Task 8: HTML-escape base_url test ==========
-
#[tokio::test]
#[serial]
async fn test_base_url_with_special_characters_is_escaped() {
@@ -3150,7 +2986,6 @@ impl SessionManager for MockSessionManager {
_zellij_ipc_pipe: &PathBuf,
first_message: ClientToServerMsg,
) {
- // Track the message that was sent
self.first_messages_sent
.lock()
.unwrap()