#![cfg(feature = "rest")]
use boxlite::{
BoxCommand, BoxOptions, BoxliteRestOptions, BoxliteRuntime, CloneOptions, ExportOptions,
};
fn rest_runtime() -> BoxliteRuntime {
let url = std::env::var("BOXLITE_REST_URL").unwrap_or_else(|_| "http://localhost:8080".into());
BoxliteRuntime::rest(
BoxliteRestOptions::new(&url).with_credentials("test-client".into(), "test-secret".into()),
)
.expect("failed to create REST runtime")
}
#[tokio::test]
async fn test_rest_auth() {
let rt = rest_runtime();
let _boxes = rt
.list_info()
.await
.expect("list_info failed — auth broken?");
}
#[tokio::test]
async fn test_rest_create_and_get_box() {
let rt = rest_runtime();
let opts = BoxOptions::default();
let litebox = rt
.create(opts, Some("test-create-get".into()))
.await
.expect("create failed");
let id_str = litebox.id().to_string();
assert!(!id_str.is_empty());
let info = rt
.get_info(&id_str)
.await
.expect("get_info failed")
.expect("box not found after create");
assert_eq!(info.id.to_string(), id_str);
rt.remove(&id_str, true).await.ok();
}
#[tokio::test]
async fn test_rest_list_boxes() {
let rt = rest_runtime();
let opts = BoxOptions::default();
let litebox = rt
.create(opts, Some("test-list".into()))
.await
.expect("create failed");
let id_str = litebox.id().to_string();
let list = rt.list_info().await.expect("list_info failed");
assert!(
list.iter().any(|b| b.id.to_string() == id_str),
"created box not found in list"
);
rt.remove(&id_str, true).await.ok();
}
#[tokio::test]
async fn test_rest_box_exists() {
let rt = rest_runtime();
let opts = BoxOptions::default();
let litebox = rt
.create(opts, Some("test-exists".into()))
.await
.expect("create failed");
let id_str = litebox.id().to_string();
assert!(
rt.exists(&id_str).await.expect("exists check failed"),
"box should exist"
);
assert!(
!rt.exists("nonexistent-id-12345")
.await
.expect("exists check failed"),
"non-existent box should not exist"
);
rt.remove(&id_str, true).await.ok();
}
#[tokio::test]
async fn test_rest_start_stop_box() {
let rt = rest_runtime();
let opts = BoxOptions {
auto_remove: false, ..Default::default()
};
let litebox = rt
.create(opts, Some("test-lifecycle".into()))
.await
.expect("create failed");
let id_str = litebox.id().to_string();
litebox.start().await.expect("start failed");
let info = rt
.get_info(&id_str)
.await
.expect("get_info failed")
.expect("box not found");
assert_eq!(info.status.to_string(), "running");
litebox.stop().await.expect("stop failed");
let info = rt
.get_info(&id_str)
.await
.expect("get_info failed")
.expect("box not found");
assert_eq!(info.status.to_string(), "stopped");
rt.remove(&id_str, true).await.ok();
}
#[tokio::test]
async fn test_rest_run_command_and_stream() {
let rt = rest_runtime();
let opts = BoxOptions::default();
let litebox = rt
.create(opts, Some("test-cmd".into()))
.await
.expect("create failed");
let id_str = litebox.id().to_string();
litebox.start().await.expect("start failed");
let cmd = BoxCommand::new("echo").arg("hello");
let mut handle = litebox.exec(cmd).await.expect("command execution failed");
let mut stdout_output = String::new();
if let Some(mut stdout) = handle.stdout() {
use tokio_stream::StreamExt;
while let Some(chunk) = stdout.next().await {
stdout_output.push_str(&chunk);
}
}
let result = handle.wait().await.expect("wait failed");
assert_eq!(result.exit_code, 0, "echo should succeed with exit code 0");
assert!(
stdout_output.contains("hello"),
"stdout should contain 'hello', got: {:?}",
stdout_output
);
litebox.stop().await.ok();
rt.remove(&id_str, true).await.ok();
}
#[tokio::test]
async fn test_rest_remove_box() {
let rt = rest_runtime();
let opts = BoxOptions::default();
let litebox = rt
.create(opts, Some("test-remove".into()))
.await
.expect("create failed");
let id_str = litebox.id().to_string();
rt.remove(&id_str, true).await.expect("remove failed");
let result = rt.get_info(&id_str).await.expect("get_info failed");
assert!(result.is_none(), "box should be gone after remove");
}
#[tokio::test]
async fn test_rest_runtime_metrics() {
let rt = rest_runtime();
let metrics = rt.metrics().await.expect("metrics failed");
let _ = metrics.boxes_created_total();
let _ = metrics.num_running_boxes();
}
#[tokio::test]
async fn test_rest_not_found() {
let rt = rest_runtime();
let result = rt
.get("does-not-exist-12345")
.await
.expect("get should not error for missing box");
assert!(result.is_none(), "non-existent box should return None");
}
#[tokio::test]
async fn test_rest_snapshot_lifecycle() {
let rt = rest_runtime();
let opts = BoxOptions {
auto_remove: false,
..Default::default()
};
let litebox = rt
.create(opts, Some("test-snapshot".into()))
.await
.expect("create failed");
let id_str = litebox.id().to_string();
litebox.start().await.expect("start failed");
litebox.stop().await.expect("stop failed");
let snapshot = litebox
.snapshots()
.create(Default::default(), "snap1")
.await
.expect("snapshot create failed");
assert_eq!(snapshot.name, "snap1");
let snapshots = litebox
.snapshots()
.list()
.await
.expect("snapshot list failed");
assert!(snapshots.iter().any(|s| s.name == "snap1"));
let got = litebox
.snapshots()
.get("snap1")
.await
.expect("snapshot get failed");
assert!(got.is_some(), "snapshot should exist");
litebox
.snapshots()
.restore("snap1")
.await
.expect("snapshot restore failed");
litebox
.snapshots()
.remove("snap1")
.await
.expect("snapshot remove failed");
let after_remove = litebox
.snapshots()
.get("snap1")
.await
.expect("snapshot get after remove failed");
assert!(after_remove.is_none(), "snapshot should be removed");
rt.remove(&id_str, true).await.ok();
}
#[tokio::test]
async fn test_rest_clone_from_snapshot() {
let rt = rest_runtime();
let opts = BoxOptions {
auto_remove: false,
..Default::default()
};
let source = rt
.create(opts, Some("test-clone-source".into()))
.await
.expect("create failed");
let source_id = source.id().to_string();
source.start().await.expect("start failed");
source.stop().await.expect("stop failed");
source
.snapshots()
.create(Default::default(), "snap-clone")
.await
.expect("snapshot create failed");
let clone_opts = CloneOptions::default();
let cloned = source
.clone_box(clone_opts, Some("test-clone-child".into()))
.await
.expect("clone failed");
let cloned_id = cloned.id().to_string();
assert_ne!(source_id, cloned_id, "clone should create new box");
rt.remove(&cloned_id, true).await.ok();
rt.remove(&source_id, true).await.ok();
}
#[tokio::test]
async fn test_rest_export_box() {
let rt = rest_runtime();
let opts = BoxOptions {
auto_remove: false,
..Default::default()
};
let litebox = rt
.create(opts, Some("test-export".into()))
.await
.expect("create failed");
let id_str = litebox.id().to_string();
litebox.start().await.expect("start failed");
litebox.stop().await.expect("stop failed");
let temp_dir = tempfile::tempdir().expect("failed to create temp dir");
let archive = litebox
.export(ExportOptions::default(), temp_dir.path())
.await
.expect("export failed");
assert!(archive.path().exists(), "export archive file should exist");
let metadata = std::fs::metadata(archive.path()).expect("failed to stat export archive");
assert!(metadata.len() > 0, "export archive should be non-empty");
rt.remove(&id_str, true).await.ok();
}