#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use std::time::Duration;
use libtmux::test::TestServer;
use rmcp::model::CallToolRequestParams;
use rmcp::service::RunningService;
use rmcp::{RoleClient, ServiceExt as _, serve_server};
use serde_json::json;
use tmux_mcp::{Selection, TmuxTools};
#[tokio::test(flavor = "multi_thread")]
async fn paste_text_with_enter_runs_the_line_in_a_bracketing_shell() {
let Ok(bash) = which_bash() else {
eprintln!("skipping: no bash to bracket a paste with");
return;
};
let guard = TestServer::builder().start().await.expect("tmux starts");
let session = guard
.server()
.new_session(libtmux::NewSessionOptions::new("paste-enter").command(bash.as_str()))
.await
.expect("bash session");
let panes = session.panes().await.expect("panes");
let pane = panes.first().expect("a new session has a pane");
let mut running = String::new();
let ready = libtmux::test::retry_until(Duration::from_secs(5), async || {
let Ok(fresh) = pane.refreshed().await else {
return false;
};
running = fresh
.current_command()
.and_then(|command| command.as_str().ok().map(str::to_owned))
.unwrap_or_default();
running.contains("bash")
})
.await;
assert!(
ready.is_ok(),
"this case needs a shell that brackets pastes; the pane runs {running:?}",
);
pane.wait_for_quiet(Duration::from_millis(300), Duration::from_secs(5))
.await
.expect("bash settles");
let tools = TmuxTools::builder(guard.server().clone())
.selection(Selection::parse(Some("execute"), None, None).expect("selection"))
.build();
let (client_transport, server_transport) = tokio::io::duplex(1 << 20);
let server = tokio::spawn(async move {
let service = serve_server(tools, server_transport)
.await
.expect("server starts");
let _ = service.waiting().await;
});
let client: RunningService<RoleClient, ()> =
().serve(client_transport).await.expect("client connects");
let request = CallToolRequestParams::new("paste_text").with_arguments(
json!({"pane": pane.id().to_string(), "text": "printf 'RAN%s\\n' OK", "enter": true})
.as_object()
.cloned()
.expect("object arguments"),
);
let result = client.call_tool(request).await.expect("paste_text answers");
assert!(
!result.is_error.unwrap_or(false),
"paste_text refused: {result:?}"
);
let ran = libtmux::test::retry_until(Duration::from_secs(5), async || {
pane.capture().await.is_ok_and(|screen| {
screen
.iter()
.any(|line| line.as_str().is_ok_and(|text| text.contains("RANOK")))
})
})
.await;
let screen = pane.capture().await.unwrap_or_default();
assert!(
ran.is_ok(),
"paste_text reported success but the line never ran; pane held:\n{}",
screen
.iter()
.filter_map(|line| line.as_str().ok())
.collect::<Vec<_>>()
.join("\n"),
);
server.abort();
}
fn which_bash() -> Result<String, ()> {
for candidate in ["/bin/bash", "/usr/bin/bash"] {
if std::path::Path::new(candidate).exists() {
return Ok(candidate.to_owned());
}
}
Err(())
}