use cua_daemon::{
Runtime, RuntimeConfig,
server::{self, DaemonHandle, ServerConfig},
};
use cua_sdk::{CheckpointSummary, Cua, CuaError, SpaceCreateOptions};
use std::{sync::Arc, time::Duration};
#[derive(Clone, Copy, Debug)]
enum Topology {
Embedded,
#[cfg_attr(not(unix), allow(dead_code))]
DaemonSocket,
}
async fn world(t: Topology) -> (tempfile::TempDir, Option<DaemonHandle>, Arc<Cua>) {
let dirs = tempfile::tempdir().unwrap();
let runtime = Runtime::new(RuntimeConfig {
state_dir: Some(dirs.path().join("sandboxes")),
spaces_home: Some(dirs.path().join("cua")),
env_probe_timeout: Some(Duration::from_secs(5)),
..Default::default()
})
.unwrap();
match t {
Topology::Embedded => (dirs, None, Cua::from_runtime(runtime)),
Topology::DaemonSocket => {
let cfg = ServerConfig {
socket_path: Some(dirs.path().join("cua.sock")),
loopback: None,
token: "daemon-token".into(),
discovery_path: Some(dirs.path().join("daemon.json")),
bridge_ticket_ttl: Duration::from_secs(30),
};
let h = server::start(runtime, cfg).await.unwrap();
let cua = Cua::connect(
Some(h.socket_path.as_ref().unwrap().display().to_string()),
None,
)
.unwrap();
(dirs, Some(h), cua)
}
}
}
async fn suite(t: Topology) {
let (_dirs, _daemon, cua) = world(t).await;
let spaces = cua.spaces();
let e = spaces
.create(SpaceCreateOptions {
image: Some("checkpoint:nowhere".into()),
..Default::default()
})
.await
.unwrap_err();
assert!(
matches!(&e, CuaError::NotFound(m) if m.contains("no machine of yours holds it")),
"{t:?} {e:?}"
);
let e = spaces
.create(SpaceCreateOptions {
image: Some("checkpoint:world".into()),
command: Some(vec!["true".into()]),
disk_gb: Some(20),
..Default::default()
})
.await
.unwrap_err();
assert!(
matches!(&e, CuaError::InvalidArgument(m) if m.contains("takes no command, disk size")),
"{t:?} {e:?}"
);
let e = spaces
.create(SpaceCreateOptions {
image: Some("checkpoint:world".into()),
on: Some("cloud".into()),
..Default::default()
})
.await
.unwrap_err();
assert!(matches!(e, CuaError::InvalidArgument(_)), "{t:?} {e:?}");
let sbx = cua.sandboxes();
let none: Vec<CheckpointSummary> = sbx.checkpoints().await.unwrap();
assert!(none.is_empty(), "{t:?} {none:?}");
let e = sbx.delete_checkpoint("nowhere".into()).await.unwrap_err();
assert!(matches!(e, CuaError::NotFound(_)), "{t:?} {e:?}");
let e = sbx
.checkpoint("no-such-space".into(), "cp".into(), false)
.await
.unwrap_err();
assert!(matches!(e, CuaError::NotFound(_)), "{t:?} {e:?}");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn embedded() {
suite(Topology::Embedded).await;
}
#[cfg(unix)]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn daemon_over_unix_socket() {
suite(Topology::DaemonSocket).await;
}