Files
galaxy/app/src/terminal/model/session_tests.rs
T

131 lines
4.4 KiB
Rust

use std::collections::HashMap;
use std::sync::Arc;
use galaxyui::elements::Empty;
use galaxyui::platform::WindowStyle;
use galaxyui::{App, AppContext, Element, Entity, ModelHandle, TypedActionView, View, ViewContext};
use super::command_executor::testing::TestCommandExecutor;
use super::{BootstrapSessionType, Session, SessionId, SessionInfo, Sessions, SessionsEvent};
struct TestView {
events: Vec<SessionsEvent>,
}
impl Entity for TestView {
type Event = usize;
}
impl View for TestView {
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish()
}
fn ui_name() -> &'static str {
"TestView"
}
}
impl TypedActionView for TestView {
type Action = ();
}
impl TestView {
fn new(model: ModelHandle<Sessions>, ctx: &mut ViewContext<Self>) -> Self {
ctx.subscribe_to_model(&model, |me, _, event, _| {
me.events.push(event.to_owned());
});
Self { events: Vec::new() }
}
}
#[test]
fn test_set_env_var_emits_event() {
App::test((), |mut app| async move {
let model_handle = app.add_model(|_| Sessions::new_for_test());
let session_id: SessionId = 0.into();
let (_, view_handle) = app.add_window(WindowStyle::NotStealFocus, |ctx| {
TestView::new(model_handle.clone(), ctx)
});
view_handle.read(&app, |view, _ctx| {
assert!(view.events.is_empty());
});
model_handle.update(&mut app, |sessions, ctx| {
let new_vars = HashMap::from_iter([("foo".to_string(), "bar".to_string())]);
sessions.set_env_vars_for_session(session_id, new_vars, ctx)
});
view_handle.read(&app, |view, _ctx| {
assert_eq!(view.events.len(), 1);
let expected_session_id = session_id;
let event = view.events.first().expect("checked length already");
if let SessionsEvent::EnvironmentVariablesUpdated { session_id } = event {
assert_eq!(*session_id, expected_session_id);
} else {
assert!(matches!(
event,
SessionsEvent::EnvironmentVariablesUpdated { .. }
));
}
});
});
}
#[test]
fn test_set_env_var_emits_no_event_when_no_change() {
App::test((), |mut app| async move {
let model_handle = app.add_model(|_| Sessions::new_for_test());
let session_id: SessionId = 0.into();
let (_, view_handle) = app.add_window(WindowStyle::NotStealFocus, |ctx| {
TestView::new(model_handle.clone(), ctx)
});
view_handle.read(&app, |view, _ctx| {
assert!(view.events.is_empty());
});
model_handle.update(&mut app, |sessions, ctx| {
let new_vars = HashMap::from_iter([("foo".to_string(), "bar".to_string())]);
sessions.set_env_vars_for_session(session_id, new_vars, ctx)
});
view_handle.read(&app, |view, _ctx| {
assert_eq!(view.events.len(), 1);
});
model_handle.update(&mut app, |sessions, ctx| {
let new_vars = HashMap::from_iter([("foo".to_string(), "bar".to_string())]);
sessions.set_env_vars_for_session(session_id, new_vars, ctx)
});
view_handle.read(&app, |view, _ctx| {
assert_eq!(view.events.len(), 1);
});
});
}
#[test]
fn test_malicious_histfile_path_does_not_execute_injected_commands() {
App::test((), |_app| async move {
// If escaping is missing, `touch /tmp/warp_injection_test` would execute
// as a side effect of reading history.
let marker = "/tmp/warp_injection_test";
// Clean up in case a previous broken run left the marker.
let _ = std::fs::remove_file(marker);
let malicious_histfile = format!("/tmp/x'; touch {marker}; echo '");
let session_info = SessionInfo::new_for_test()
.with_session_type(BootstrapSessionType::WarpifiedRemote)
.with_histfile(Some(malicious_histfile));
let session = Session::new(session_info, Arc::new(TestCommandExecutor::default()));
// read_history for a WarpifiedRemote session calls read_history_from_file,
// which builds `cat '{escaped_path}'` and executes it via TestCommandExecutor
let _ = session.read_history(false).await;
assert!(
!std::path::Path::new(marker).exists(),
"Injected command executed — escaping regression!"
);
});
}