Skip to content

Commit ac74aa2

Browse files
committed
test(ws): cover terminal channel attach replay and error paths
1 parent fe24fe1 commit ac74aa2

1 file changed

Lines changed: 107 additions & 0 deletions

File tree

apps/server/src/ws/server.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,113 @@ mod tests {
901901
}
902902
}
903903

904+
#[test]
905+
fn handle_terminal_channel_attach_returns_correct_replay_data() {
906+
#[cfg(test)]
907+
{
908+
use serde_json::Value;
909+
910+
let (app, _shutdown_rx) = RuntimeHandle::new();
911+
let conn = rusqlite::Connection::open_in_memory().unwrap();
912+
crate::init_db(&conn).unwrap();
913+
*app.state().db.lock().unwrap() = Some(conn);
914+
915+
// Register a gateway runtime
916+
app.state().terminal_runtimes.lock().unwrap().insert(
917+
GatewayTerminalRuntime::new(
918+
"runtime-1".to_string(),
919+
"ws-1".to_string(),
920+
"session-1".to_string(),
921+
"claude".to_string(),
922+
"tmux-fake".to_string(),
923+
"%1".to_string(),
924+
),
925+
);
926+
927+
// Set up session -> terminal binding
928+
let terminal_id = 42u64;
929+
app.state().session_runtime_bindings.lock().unwrap().insert(
930+
crate::services::session_runtime::session_runtime_key("ws-1", "session-1"),
931+
terminal_id,
932+
);
933+
app.state().terminal_runtime_bindings.lock().unwrap().insert(
934+
terminal_id,
935+
crate::services::session_runtime::session_runtime_key("ws-1", "session-1"),
936+
);
937+
938+
// Seed the output buffer with known data
939+
let expected_data = "hello from replay";
940+
let terminal_runtime = Arc::new(TerminalRuntime {
941+
io: TerminalIo::Mock,
942+
output: Mutex::new(expected_data.to_string()),
943+
size: Mutex::new((80u16, 24u16)),
944+
persist_workspace_terminal: true,
945+
child: None,
946+
killer: None,
947+
process_id: None,
948+
process_group_leader: None,
949+
});
950+
app.state().terminals.lock().unwrap().insert(
951+
crate::ws::server::terminal_key("ws-1", terminal_id),
952+
terminal_runtime,
953+
);
954+
955+
let payload = serde_json::json!({
956+
"workspace_id": "ws-1",
957+
"fencing_token": 1,
958+
"runtime_id": "runtime-1",
959+
});
960+
961+
let result = super::handle_terminal_channel_attach(&app, payload);
962+
assert!(result.is_ok(), "attach should succeed: {:?}", result);
963+
964+
let captured = app.state().captured_transport_events.lock().unwrap().clone();
965+
let replay_events: Vec<_> = captured
966+
.iter()
967+
.filter(|(event, payload)| {
968+
*event == "terminal://channel_replay"
969+
&& payload.get("data").and_then(Value::as_str) == Some(expected_data)
970+
})
971+
.collect();
972+
assert!(
973+
!replay_events.is_empty(),
974+
"expected terminal://channel_replay with data='{}', got: {:?}",
975+
expected_data,
976+
captured
977+
);
978+
}
979+
}
980+
981+
#[test]
982+
fn handle_terminal_channel_attach_returns_error_for_unknown_runtime() {
983+
#[cfg(test)]
984+
{
985+
let (app, _shutdown_rx) = RuntimeHandle::new();
986+
let conn = rusqlite::Connection::open_in_memory().unwrap();
987+
crate::init_db(&conn).unwrap();
988+
*app.state().db.lock().unwrap() = Some(conn);
989+
990+
// No gateway runtime registered — use a non-existent runtime_id
991+
let payload = serde_json::json!({
992+
"workspace_id": "ws-1",
993+
"fencing_token": 1,
994+
"runtime_id": "nonexistent-runtime",
995+
});
996+
997+
let result = super::handle_terminal_channel_attach(&app, payload);
998+
assert!(
999+
result.is_err(),
1000+
"attach with unknown runtime_id should fail, got: {:?}",
1001+
result
1002+
);
1003+
assert_eq!(
1004+
result.unwrap_err(),
1005+
"terminal_runtime_not_found",
1006+
"expected 'terminal_runtime_not_found' error"
1007+
);
1008+
}
1009+
}
1010+
9041011
#[test]
9051012
fn ws_client_envelope_parses_terminal_channel_attach() {
9061013
let raw = serde_json::json!({

0 commit comments

Comments
 (0)