use predicates::prelude::*;
use std::io::Write;
use tempfile::NamedTempFile;
mod common;
#[test]
fn box_full_user_journey_birth_to_death() {
let mut ctx = common::boxlite();
let name = "journey";
ctx.cmd.args(["pull", "alpine:latest"]);
ctx.cmd.assert().success();
ctx.new_cmd()
.args([
"run",
"-d",
"--name",
name,
"alpine:latest",
"sleep",
"3600",
])
.assert()
.success();
ctx.new_cmd()
.arg("ps")
.assert()
.success()
.stdout(predicate::str::contains(name))
.stdout(predicate::str::contains("Running"));
ctx.new_cmd()
.args(["exec", name, "--", "echo", "alive"])
.assert()
.success()
.stdout("alive\n");
let host_payload = b"lifecycle-journey-payload\n";
let mut tmp_in = NamedTempFile::new().expect("tempfile");
tmp_in.write_all(host_payload).expect("write tempfile");
let host_in = tmp_in.path().to_string_lossy().to_string();
ctx.new_cmd()
.args(["cp", &host_in, &format!("{name}:/root/payload.txt")])
.assert()
.success();
ctx.new_cmd()
.args(["exec", name, "--", "cat", "/root/payload.txt"])
.assert()
.success()
.stdout(predicate::str::contains("lifecycle-journey-payload"));
ctx.new_cmd()
.args([
"exec",
name,
"--",
"sh",
"-c",
"echo echoed-from-box > /root/echoed.txt",
])
.assert()
.success();
let out_dir = tempfile::tempdir().expect("tempdir");
let host_out = out_dir.path().join("echoed.txt");
ctx.new_cmd()
.args([
"cp",
&format!("{name}:/root/echoed.txt"),
&host_out.to_string_lossy(),
])
.assert()
.success();
let read_back = std::fs::read_to_string(&host_out).expect("read cp-out");
assert!(
read_back.contains("echoed-from-box"),
"cp from box returned wrong content: {read_back:?}"
);
ctx.new_cmd().args(["stats", name]).assert().success();
ctx.new_cmd().args(["stop", name]).assert().success();
ctx.new_cmd()
.args(["inspect", name])
.assert()
.success()
.stdout(predicate::str::contains("\"Running\": false"));
ctx.new_cmd().args(["start", name]).assert().success();
ctx.new_cmd()
.args(["exec", name, "--", "cat", "/root/payload.txt"])
.assert()
.success()
.stdout(predicate::str::contains("lifecycle-journey-payload"));
ctx.new_cmd().args(["restart", name]).assert().success();
ctx.new_cmd()
.args(["rm", "--force", name])
.assert()
.success();
ctx.new_cmd().args(["inspect", name]).assert().failure();
}
#[test]
fn cp_roundtrip_persistent_path_binary_fidelity() {
let mut ctx = common::boxlite();
let name = "cp-rt";
ctx.cmd
.args(["run", "-d", "--name", name, "alpine:latest", "sleep", "120"])
.assert()
.success();
let mut payload: Vec<u8> = (0u8..=255).collect();
payload.extend_from_slice(b"\n");
let mut tmp_in = NamedTempFile::new().unwrap();
tmp_in.write_all(&payload).unwrap();
ctx.new_cmd()
.args([
"cp",
&tmp_in.path().to_string_lossy(),
&format!("{name}:/root/blob"),
])
.assert()
.success();
let out_dir = tempfile::tempdir().unwrap();
let host_out = out_dir.path().join("blob");
ctx.new_cmd()
.args([
"cp",
&format!("{name}:/root/blob"),
&host_out.to_string_lossy(),
])
.assert()
.success();
let read_back = std::fs::read(&host_out).expect("read cp-out");
assert_eq!(
read_back, payload,
"binary cp round-trip mangled or truncated bytes"
);
ctx.cleanup_box(name);
}
#[test]
#[ignore = "boxlite logs surfaces guest console, not container stdout — see doc"]
fn logs_captures_box_stdout() {
let mut ctx = common::boxlite();
let name = "logs-cap";
ctx.cmd
.args([
"run",
"-d",
"--name",
name,
"alpine:latest",
"sh",
"-c",
"echo MARK_LOGS_42; sleep 60",
])
.assert()
.success();
std::thread::sleep(std::time::Duration::from_secs(2));
ctx.new_cmd()
.args(["logs", name])
.assert()
.success()
.stdout(predicate::str::contains("MARK_LOGS_42"));
ctx.cleanup_box(name);
}
#[test]
fn stats_json_snapshot_for_running_box() {
let mut ctx = common::boxlite();
let name = "stats-cap";
ctx.cmd
.args(["run", "-d", "--name", name, "alpine:latest", "sleep", "60"])
.assert()
.success();
let out = ctx
.new_cmd()
.args(["stats", "--format", "json", name])
.assert()
.success()
.get_output()
.clone();
let body = String::from_utf8_lossy(&out.stdout).to_string();
assert!(
body.trim_start().starts_with('{') || body.trim_start().starts_with('['),
"stats --format json did not produce JSON: {body:?}"
);
ctx.cleanup_box(name);
}
#[test]
fn error_paths_along_the_journey() {
let mut ctx = common::boxlite();
let name = "err-journey";
ctx.cmd
.args(["run", "-d", "--name", name, "alpine:latest", "sleep", "60"])
.assert()
.success();
ctx.new_cmd().args(["rm", name]).assert().failure();
ctx.new_cmd()
.args(["start", "nonexistent-box-xyz"])
.assert()
.failure();
ctx.new_cmd()
.args(["stop", "nonexistent-box-xyz"])
.assert()
.failure();
ctx.new_cmd()
.args(["inspect", "nonexistent-box-xyz"])
.assert()
.failure();
ctx.new_cmd()
.args(["logs", "nonexistent-box-xyz"])
.assert()
.failure();
ctx.new_cmd()
.args(["rm", "--force", name])
.assert()
.success();
}