use assert_cmd::Command;
use std::fs;
use tempfile::TempDir;
fn crb() -> Command
{
Command::cargo_bin( "crb" ).unwrap()
}
fn runbox_bin() -> Command
{
Command::cargo_bin( "runbox" ).unwrap()
}
#[ test ]
fn cli01_empty_argv_shows_usage()
{
let dir = TempDir::new().unwrap();
let out = crb()
.current_dir( dir.path() )
.output()
.unwrap();
assert!( out.status.success(), "empty argv must exit 0, got: {:?}", out.status );
let stdout = String::from_utf8_lossy( &out.stdout );
assert!( stdout.contains( ".init" ), "usage must mention .init, got: {stdout}" );
}
#[ test ]
fn cli02_help_flag_shows_usage()
{
let dir = TempDir::new().unwrap();
let out = crb()
.arg( "--help" )
.current_dir( dir.path() )
.output()
.unwrap();
assert!( out.status.success(), "--help must exit 0, got: {:?}", out.status );
let stdout = String::from_utf8_lossy( &out.stdout );
assert!( stdout.contains( ".init" ), "usage must mention .init, got: {stdout}" );
}
#[ test ]
fn cli03_h_flag_shows_usage()
{
let dir = TempDir::new().unwrap();
let out = crb()
.arg( "-h" )
.current_dir( dir.path() )
.output()
.unwrap();
assert!( out.status.success(), "-h must exit 0, got: {:?}", out.status );
let stdout = String::from_utf8_lossy( &out.stdout );
assert!( stdout.contains( ".init" ), "usage must mention .init, got: {stdout}" );
}
#[ test ]
fn cli04_init_without_image_exits_1()
{
let dir = TempDir::new().unwrap();
let out = crb()
.args( [ ".init" ] )
.current_dir( dir.path() )
.output()
.unwrap();
assert!( !out.status.success(), "must fail without image::" );
assert_eq!( out.status.code(), Some( 1 ), "exit code must be 1" );
let stderr = String::from_utf8_lossy( &out.stderr );
assert!(
stderr.contains( "missing required argument: image::" ),
"stderr must mention missing image::, got: {stderr}"
);
}
#[ test ]
fn cli05_unknown_ecosystem_exits_1()
{
let dir = TempDir::new().unwrap();
let out = crb()
.args( [ ".init", "image::my_img", "ecosystem::java" ] )
.current_dir( dir.path() )
.output()
.unwrap();
assert!( !out.status.success(), "must fail for unknown ecosystem" );
assert_eq!( out.status.code(), Some( 1 ), "exit code must be 1" );
let stderr = String::from_utf8_lossy( &out.stderr );
assert!(
stderr.contains( "unknown ecosystem: java" ),
"stderr must mention unknown ecosystem, got: {stderr}"
);
}
#[ test ]
fn cli06_init_creates_three_files()
{
let dir = TempDir::new().unwrap();
let out = crb()
.args( [ ".init", "image::my_img" ] )
.current_dir( dir.path() )
.output()
.unwrap();
assert!( out.status.success(), "must exit 0, got: {:?}\nstderr: {}", out.status, String::from_utf8_lossy( &out.stderr ) );
let runbox_dir = dir.path().join( "runbox" );
assert!( runbox_dir.is_dir(), "runbox/ must be created" );
assert!( runbox_dir.join( "runbox" ).is_file(), "runbox/runbox must exist" );
assert!( runbox_dir.join( "runbox.yml" ).is_file(), "runbox/runbox.yml must exist" );
assert!( runbox_dir.join( "runbox.dockerfile" ).is_file(), "runbox/runbox.dockerfile must exist" );
}
#[ test ]
fn cli07_wrapper_script_contains_discovery_function()
{
let dir = TempDir::new().unwrap();
crb()
.args( [ ".init", "image::my_img" ] )
.current_dir( dir.path() )
.output()
.unwrap();
let wrapper = fs::read_to_string( dir.path().join( "runbox/runbox" ) ).unwrap();
assert!( wrapper.starts_with( "#!/usr/bin/env bash" ), "must have shebang" );
assert!( wrapper.contains( "_find_runbox_run" ), "must contain discovery function" );
assert!( wrapper.contains( "runbox.yml" ), "must reference runbox.yml" );
}
#[ test ]
fn cli08_wrapper_script_is_executable()
{
let dir = TempDir::new().unwrap();
crb()
.args( [ ".init", "image::my_img" ] )
.current_dir( dir.path() )
.output()
.unwrap();
let wrapper_path = dir.path().join( "runbox/runbox" );
#[ cfg( unix ) ]
{
use std::os::unix::fs::PermissionsExt as _;
let mode = fs::metadata( &wrapper_path ).unwrap().permissions().mode();
assert!( mode & 0o111 != 0, "runbox/runbox must have executable bit set (mode: {mode:o})" );
}
#[ cfg( not( unix ) ) ]
assert!( wrapper_path.is_file(), "runbox/runbox must exist" );
}
#[ test ]
fn cli09_runbox_yml_contains_required_fields()
{
let dir = TempDir::new().unwrap();
crb()
.args( [ ".init", "image::my_img" ] )
.current_dir( dir.path() )
.output()
.unwrap();
let yml = fs::read_to_string( dir.path().join( "runbox/runbox.yml" ) ).unwrap();
assert!( yml.contains( "image:" ), "must have image field" );
assert!( yml.contains( "dockerfile:" ), "must have dockerfile field" );
assert!( yml.contains( "cache_dir:" ), "must have cache_dir field" );
assert!( yml.contains( "workspace_root:" ), "must have workspace_root field" );
assert!( yml.contains( "test_script:" ), "must have test_script field" );
assert!( yml.contains( "my_img" ), "must include image value" );
}
#[ test ]
fn cli10_cache_dir_matches_ecosystem()
{
for ( ecosystem, expected_cache_dir ) in [
( "rust", "target" ),
( "nodejs", "node_modules" ),
( "python", ".venv" ),
( "none", ".cache" ),
]
{
let dir = TempDir::new().unwrap();
let out = crb()
.args( [ ".init", "image::x", &format!( "ecosystem::{ecosystem}" ) ] )
.current_dir( dir.path() )
.output()
.unwrap();
assert!(
out.status.success(),
"ecosystem::{ecosystem} must succeed, stderr: {}",
String::from_utf8_lossy( &out.stderr )
);
let yml = fs::read_to_string( dir.path().join( "runbox/runbox.yml" ) ).unwrap();
assert!(
yml.contains( &format!( "cache_dir: {expected_cache_dir}" ) ),
"ecosystem::{ecosystem} must have cache_dir: {expected_cache_dir}, got:\n{yml}"
);
}
}
#[ test ]
fn cli11_test_script_defaults_to_l1()
{
let dir = TempDir::new().unwrap();
crb()
.args( [ ".init", "image::my_img" ] )
.current_dir( dir.path() )
.output()
.unwrap();
let yml = fs::read_to_string( dir.path().join( "runbox/runbox.yml" ) ).unwrap();
assert!(
yml.contains( "test_script: verb/test.d/l1" ),
"default test_script must be verb/test.d/l1, got:\n{yml}"
);
}
#[ test ]
fn cli12_test_script_override_written_to_yml()
{
let dir = TempDir::new().unwrap();
crb()
.args( [ ".init", "image::my_img", "test_script::my/custom/test" ] )
.current_dir( dir.path() )
.output()
.unwrap();
let yml = fs::read_to_string( dir.path().join( "runbox/runbox.yml" ) ).unwrap();
assert!(
yml.contains( "test_script: my/custom/test" ),
"custom test_script must appear in runbox.yml, got:\n{yml}"
);
assert!(
!yml.contains( "verb/test.d/l1" ),
"default must NOT appear when overridden, got:\n{yml}"
);
}
#[ test ]
fn cli13_existing_runbox_dir_exits_1()
{
let dir = TempDir::new().unwrap();
fs::create_dir( dir.path().join( "runbox" ) ).unwrap();
let out = crb()
.args( [ ".init", "image::my_img" ] )
.current_dir( dir.path() )
.output()
.unwrap();
assert!( !out.status.success(), "must fail when runbox/ exists" );
assert_eq!( out.status.code(), Some( 1 ), "exit code must be 1" );
let stderr = String::from_utf8_lossy( &out.stderr );
assert!(
stderr.contains( "runbox/ already exists" ),
"stderr must mention existing dir, got: {stderr}"
);
}
#[ test ]
fn cli14_runbox_binary_same_behaviour_as_crb()
{
let dir = TempDir::new().unwrap();
let out = runbox_bin()
.current_dir( dir.path() )
.output()
.unwrap();
assert!( out.status.success(), "runbox binary must exit 0 on empty argv, got: {:?}", out.status );
let stdout = String::from_utf8_lossy( &out.stdout );
assert!( stdout.contains( ".init" ), "runbox usage must mention .init, got: {stdout}" );
}