Files
2026-06-09 22:20:26 +07:00

77 lines
3.0 KiB
Rust

use std::path::PathBuf;
use spacesh_proto::codec::{read_frame, write_frame};
use spacesh_proto::{Cmd, Envelope, SurfaceId};
use spacesh_proto::status::SurfaceState;
use spacesh_cli::client;
use tokio::net::UnixListener;
// These tests mutate the process-global SPACESH_SOCK env var, so they must not
// run concurrently. Serialize them on a process-wide lock (poison-tolerant).
static SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn serial() -> std::sync::MutexGuard<'static, ()> {
SERIAL.lock().unwrap_or_else(|e| e.into_inner())
}
fn tmp_sock(name: &str) -> PathBuf {
let mut p = std::env::temp_dir();
let n = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos();
p.push(format!("spacesh-cli-{name}-{n}.sock"));
p
}
/// One-shot mock daemon: accept one connection, read one request, send `reply`.
fn mock_daemon(sock: PathBuf, reply: Envelope) {
let listener = UnixListener::bind(&sock).unwrap();
tokio::spawn(async move {
if let Ok((mut stream, _)) = listener.accept().await {
if let Ok(Some(_req)) = read_frame(&mut stream).await {
let _ = write_frame(&mut stream, &reply).await;
}
}
});
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn request_returns_data_from_daemon() {
let _g = serial();
let sock = tmp_sock("req");
std::env::set_var("SPACESH_SOCK", &sock);
mock_daemon(sock.clone(), Envelope::Res {
id: 1, ok: true, data: serde_json::json!({ "workspace_id": "w_1" }), error: None,
});
tokio::time::sleep(std::time::Duration::from_millis(50)).await; // let the listener bind
let data = client::request(Cmd::Open { path: "/tmp".into() }).await.unwrap();
std::env::remove_var("SPACESH_SOCK");
let _ = std::fs::remove_file(&sock);
assert_eq!(data["workspace_id"], "w_1");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn request_surfaces_daemon_error() {
let _g = serial();
let sock = tmp_sock("err");
std::env::set_var("SPACESH_SOCK", &sock);
mock_daemon(sock.clone(), Envelope::Res {
id: 1, ok: false, data: serde_json::Value::Null,
error: Some(spacesh_proto::ErrorBody { code: "NOT_FOUND".into(), msg: "surface".into() }),
});
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
let res = client::request(Cmd::Close { surface_id: SurfaceId("s_x".into()) }).await;
std::env::remove_var("SPACESH_SOCK");
let _ = std::fs::remove_file(&sock);
assert!(res.is_err());
assert!(res.unwrap_err().to_string().contains("NOT_FOUND"));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn notify_with_no_daemon_is_silent_success() {
let _g = serial();
let sock = tmp_sock("nodaemon"); // never bound
std::env::set_var("SPACESH_SOCK", &sock);
let r = client::notify(Cmd::SetState { surface_id: SurfaceId("s_1".into()), state: SurfaceState::Done }).await;
std::env::remove_var("SPACESH_SOCK");
assert!(r.is_ok(), "notify must be a silent success when no daemon is listening");
}