mod common;
use agent_os_client::{ClientError, OpenShellOptions, StdinInput};
use futures::StreamExt;
#[tokio::test]
async fn shell_surface_open_write_data_resize_close() {
if !common::sidecar_available() {
eprintln!("skipping shell_surface_open_write_data_resize_close: sidecar binary not built");
return;
}
let os = common::new_vm().await;
assert!(
matches!(
os.write_shell("shell-missing", StdinInput::Text("x".to_string())),
Err(ClientError::ShellNotFound(_))
),
"write_shell(unknown) must return ShellNotFound"
);
assert!(
matches!(os.resize_shell("shell-missing", 80, 24), Err(ClientError::ShellNotFound(_))),
"resize_shell(unknown) must return ShellNotFound"
);
assert!(
matches!(os.close_shell("shell-missing"), Err(ClientError::ShellNotFound(_))),
"close_shell(unknown) must return ShellNotFound"
);
assert!(
matches!(os.on_shell_data("shell-missing"), Err(ClientError::ShellNotFound(_))),
"on_shell_data(unknown) must return ShellNotFound"
);
if !common::wasm_commands_available(&os).await {
eprintln!(
"skipping shell PTY assertions: WASM PTY shell (sh) not present in this environment \
(ShellNotFound contract above still executed)"
);
os.shutdown().await.expect("shutdown");
return;
}
let shell = os
.open_shell(OpenShellOptions {
cols: Some(80),
rows: Some(24),
..Default::default()
})
.expect("open_shell");
assert!(
shell.shell_id.starts_with("shell-"),
"open_shell must return a synthetic shell-N id (not a pid), got {}",
shell.shell_id
);
let mut data = os
.on_shell_data(&shell.shell_id)
.expect("on_shell_data for live shell");
let _stderr = os
.on_shell_stderr(&shell.shell_id)
.expect("on_shell_stderr for live shell");
os.write_shell(
&shell.shell_id,
StdinInput::Text("echo shell-marker\n".to_string()),
)
.expect("write_shell");
let saw_marker = tokio::time::timeout(std::time::Duration::from_secs(10), async {
let mut acc = Vec::<u8>::new();
while let Some(chunk) = data.next().await {
acc.extend_from_slice(&chunk);
if String::from_utf8_lossy(&acc).contains("shell-marker") {
return true;
}
}
false
})
.await
.unwrap_or(false);
assert!(
saw_marker,
"the shell's data stream should surface the echoed `shell-marker` output"
);
os.resize_shell(&shell.shell_id, 120, 40)
.expect("resize_shell on a live shell must succeed");
os.close_shell(&shell.shell_id).expect("close_shell");
let err = os
.write_shell(&shell.shell_id, StdinInput::Text("x".to_string()))
.expect_err("write to a closed shell must error");
assert!(
matches!(err, ClientError::ShellNotFound(id) if id == shell.shell_id),
"closed shell must report ShellNotFound"
);
match os.on_shell_data("shell-does-not-exist") {
Err(ClientError::ShellNotFound(_)) => {}
Ok(_) => panic!("unknown shell id must error"),
Err(other) => panic!("expected ShellNotFound, got {other:?}"),
}
os.shutdown().await.expect("shutdown");
}