use super::*;
#[test]
fn test_parse_simple_command() {
let args: Vec<String> = vec!["echo", "hello", "world"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.command, "echo hello world");
assert!(result.wrapper_options.isolated.is_none());
assert!(!result.wrapper_options.attached);
assert!(!result.wrapper_options.detached);
}
#[test]
fn test_parse_with_separator() {
let args: Vec<String> = vec!["--", "npm", "test"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.command, "npm test");
}
#[test]
fn test_parse_isolated_option() {
let args: Vec<String> = vec!["--isolated", "tmux", "--", "npm", "test"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.isolated, Some("tmux".to_string()));
assert_eq!(result.command, "npm test");
}
#[test]
fn test_parse_shorthand() {
let args: Vec<String> = vec!["-i", "screen", "-d", "--", "npm", "start"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.isolated, Some("screen".to_string()));
assert!(result.wrapper_options.detached);
}
#[test]
fn test_attached_detached_conflict() {
let args: Vec<String> = vec!["--attached", "--detached", "--", "npm", "test"]
.into_iter()
.map(String::from)
.collect();
assert!(parse_args(&args).is_err());
}
#[test]
fn test_docker_uses_default_image() {
let args: Vec<String> = vec!["--isolated", "docker", "--", "npm", "test"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.isolated, Some("docker".to_string()));
assert!(
result.wrapper_options.image.is_some(),
"Expected default image to be set"
);
let known_defaults = vec![
"alpine:latest",
"ubuntu:latest",
"debian:latest",
"archlinux:latest",
"fedora:latest",
"centos:latest",
];
let image = result.wrapper_options.image.as_ref().unwrap();
assert!(
known_defaults.contains(&image.as_str()),
"Expected image to be one of {:?}, got {}",
known_defaults,
image
);
}
#[test]
fn test_docker_with_image() {
let args: Vec<String> = vec![
"--isolated",
"docker",
"--image",
"node:20",
"--",
"npm",
"test",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.isolated, Some("docker".to_string()));
assert_eq!(result.wrapper_options.image, Some("node:20".to_string()));
}
#[test]
fn test_docker_volumes_repeatable() {
let args: Vec<String> = vec![
"--isolated",
"docker",
"--volume",
"/host/a:/container/a",
"-v",
"/host/b:/container/b:ro",
"--",
"ls",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.volumes,
vec![
"/host/a:/container/a".to_string(),
"/host/b:/container/b:ro".to_string()
]
);
}
#[test]
fn test_docker_volume_eq_form() {
let args: Vec<String> = vec![
"-i",
"docker",
"--volume=/host/a:/c/a",
"-v=/host/b:/c/b",
"--",
"ls",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.volumes,
vec!["/host/a:/c/a".to_string(), "/host/b:/c/b".to_string()]
);
}
#[test]
fn test_docker_mounts_repeatable() {
let args: Vec<String> = vec![
"-i",
"docker",
"--mount",
"type=bind,src=/h,dst=/c",
"--mount=type=volume,src=vol,dst=/data",
"--",
"ls",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.mounts,
vec![
"type=bind,src=/h,dst=/c".to_string(),
"type=volume,src=vol,dst=/data".to_string()
]
);
}
#[test]
fn test_docker_env_repeatable() {
let args: Vec<String> = vec![
"-i",
"docker",
"--env",
"FOO=bar",
"-e",
"GH_TOKEN=secret",
"--env=BAZ=qux",
"--",
"env",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.env,
vec![
"FOO=bar".to_string(),
"GH_TOKEN=secret".to_string(),
"BAZ=qux".to_string()
]
);
}
#[test]
fn test_docker_privileged() {
let args: Vec<String> = vec!["-i", "docker", "--privileged", "--", "ls"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert!(result.wrapper_options.privileged);
}
#[test]
fn test_docker_runtime_options_default_empty() {
let args: Vec<String> = vec!["-i", "docker", "--", "ls"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert!(result.wrapper_options.volumes.is_empty());
assert!(result.wrapper_options.mounts.is_empty());
assert!(result.wrapper_options.env.is_empty());
assert!(!result.wrapper_options.privileged);
}
#[test]
fn test_volume_requires_argument() {
let args: Vec<String> = vec!["-i", "docker", "--volume", "--", "ls"]
.into_iter()
.map(String::from)
.collect();
let err = parse_args(&args).unwrap_err();
assert!(err.contains("requires a volume argument"));
}
#[test]
fn test_volume_rejected_for_non_docker() {
let args: Vec<String> = vec!["-i", "tmux", "-v", "/a:/b", "--", "ls"]
.into_iter()
.map(String::from)
.collect();
let err = parse_args(&args).unwrap_err();
assert!(err.contains("--volume option is only valid with --isolated docker"));
}
#[test]
fn test_privileged_rejected_without_docker() {
let args: Vec<String> = vec!["--privileged", "--", "ls"]
.into_iter()
.map(String::from)
.collect();
let err = parse_args(&args).unwrap_err();
assert!(err.contains("--privileged option is only valid with --isolated docker"));
}
#[test]
fn test_docker_env_short_flag() {
let args: Vec<String> = vec!["-i", "docker", "-e", "TOKEN=abc", "--", "ls"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.env, vec!["TOKEN=abc".to_string()]);
}
#[test]
fn test_docker_env_eq_form() {
let args: Vec<String> = vec!["-i", "docker", "--env=TOKEN=abc", "--", "ls"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.env, vec!["TOKEN=abc".to_string()]);
}
#[test]
fn test_docker_mount_eq_form() {
let args: Vec<String> = vec![
"-i",
"docker",
"--mount=type=bind,src=/h,dst=/c",
"--",
"ls",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.mounts,
vec!["type=bind,src=/h,dst=/c".to_string()]
);
}
#[test]
fn test_docker_combined_runtime_options() {
let args: Vec<String> = vec![
"-i",
"docker",
"--image",
"konard/hive-mind-dind:latest",
"--privileged",
"-v",
"/h/a:/c/a",
"-e",
"TOKEN=abc",
"--",
"solve",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.image,
Some("konard/hive-mind-dind:latest".to_string())
);
assert!(result.wrapper_options.privileged);
assert_eq!(
result.wrapper_options.volumes,
vec!["/h/a:/c/a".to_string()]
);
assert_eq!(result.wrapper_options.env, vec!["TOKEN=abc".to_string()]);
}
#[test]
fn test_mount_requires_argument() {
let args: Vec<String> = vec!["-i", "docker", "--mount", "--", "ls"]
.into_iter()
.map(String::from)
.collect();
let err = parse_args(&args).unwrap_err();
assert!(err.contains("requires a mount spec argument"));
}
#[test]
fn test_env_requires_argument() {
let args: Vec<String> = vec!["-i", "docker", "--env", "--", "ls"]
.into_iter()
.map(String::from)
.collect();
let err = parse_args(&args).unwrap_err();
assert!(err.contains("requires a KEY=VALUE argument"));
}
#[test]
fn test_mount_rejected_for_non_docker() {
let args: Vec<String> = vec![
"-i",
"tmux",
"--mount",
"type=bind,src=/h,dst=/c",
"--",
"ls",
]
.into_iter()
.map(String::from)
.collect();
let err = parse_args(&args).unwrap_err();
assert!(err.contains("--mount option is only valid with --isolated docker"));
}
#[test]
fn test_env_rejected_for_non_docker() {
let args: Vec<String> = vec!["-i", "screen", "-e", "A=1", "--", "ls"]
.into_iter()
.map(String::from)
.collect();
let err = parse_args(&args).unwrap_err();
assert!(err.contains("--env option is only valid with --isolated docker"));
}
#[test]
fn test_ssh_requires_endpoint() {
let args: Vec<String> = vec!["--isolated", "ssh", "--", "npm", "test"]
.into_iter()
.map(String::from)
.collect();
assert!(parse_args(&args).is_err());
}
#[test]
fn test_ssh_with_endpoint() {
let args: Vec<String> = vec!["--isolated", "ssh", "--endpoint", "user@host", "--", "ls"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.isolated, Some("ssh".to_string()));
assert_eq!(
result.wrapper_options.endpoint,
Some("user@host".to_string())
);
}
#[test]
fn test_isolated_user() {
let args: Vec<String> = vec!["--isolated-user", "--", "npm", "test"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert!(result.wrapper_options.user);
assert!(result.wrapper_options.user_name.is_none());
}
#[test]
fn test_isolated_user_with_name() {
let args: Vec<String> = vec!["--isolated-user", "myuser", "--", "npm", "test"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert!(result.wrapper_options.user);
assert_eq!(result.wrapper_options.user_name, Some("myuser".to_string()));
}
#[test]
fn test_generate_session_name() {
let name1 = generate_session_name(None);
let name2 = generate_session_name(None);
assert!(name1.starts_with("start-"));
assert_ne!(name1, name2);
}
#[test]
fn test_generate_session_name_with_prefix() {
let name = generate_session_name(Some("custom"));
assert!(name.starts_with("custom-"));
}
#[test]
fn test_has_isolation() {
let mut options = WrapperOptions::default();
assert!(!has_isolation(&options));
options.isolated = Some("tmux".to_string());
assert!(has_isolation(&options));
}
#[test]
fn test_get_effective_mode() {
let mut options = WrapperOptions::default();
assert_eq!(get_effective_mode(&options), "attached");
options.detached = true;
assert_eq!(get_effective_mode(&options), "detached");
}
#[test]
fn test_keep_user_requires_user() {
let args: Vec<String> = vec!["--keep-user", "--", "npm", "test"]
.into_iter()
.map(String::from)
.collect();
assert!(parse_args(&args).is_err());
}
#[test]
fn test_use_command_stream() {
let args: Vec<String> = vec!["--use-command-stream", "--", "npm", "test"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert!(result.wrapper_options.use_command_stream);
}
#[test]
fn test_status_option() {
let args: Vec<String> = vec!["--status", "a1b2c3d4-e5f6-7890-abcd-ef1234567890"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.status,
Some("a1b2c3d4-e5f6-7890-abcd-ef1234567890".to_string())
);
}
#[test]
fn test_status_with_output_format() {
let args: Vec<String> = vec![
"--status",
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"--output-format",
"json",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.status,
Some("a1b2c3d4-e5f6-7890-abcd-ef1234567890".to_string())
);
assert_eq!(
result.wrapper_options.output_format,
Some("json".to_string())
);
}
#[test]
fn test_status_with_links_notation() {
let args: Vec<String> = vec!["--status", "uuid-here", "--output-format", "links-notation"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.output_format,
Some("links-notation".to_string())
);
}
#[test]
fn test_status_with_text_format() {
let args: Vec<String> = vec!["--status", "uuid-here", "--output-format", "text"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.output_format,
Some("text".to_string())
);
}
#[test]
fn test_status_equals_syntax() {
let args: Vec<String> = vec!["--status=my-uuid-here"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.status,
Some("my-uuid-here".to_string())
);
}
#[test]
fn test_upload_log_option() {
let args: Vec<String> = vec!["--upload-log", "my-session"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.upload_log,
Some("my-session".to_string())
);
assert!(result.command.is_empty());
}
#[test]
fn test_upload_log_equals_syntax() {
let args: Vec<String> = vec!["--upload-log=my-session"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.upload_log,
Some("my-session".to_string())
);
}
#[test]
fn test_upload_log_requires_identifier() {
let args: Vec<String> = vec!["--upload-log"].into_iter().map(String::from).collect();
assert!(parse_args(&args).is_err());
}
#[test]
fn test_stop_option() {
let args: Vec<String> = vec!["--stop", "my-session"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.stop, Some("my-session".to_string()));
assert!(result.command.is_empty());
}
#[test]
fn test_stop_equals_syntax() {
let args: Vec<String> = vec!["--stop=my-session"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.stop, Some("my-session".to_string()));
}
#[test]
fn test_terminate_option() {
let args: Vec<String> = vec!["--terminate", "my-session"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.terminate,
Some("my-session".to_string())
);
assert!(result.command.is_empty());
}
#[test]
fn test_terminate_equals_syntax() {
let args: Vec<String> = vec!["--terminate=my-session"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.terminate,
Some("my-session".to_string())
);
}
#[test]
fn test_stop_requires_identifier() {
let args: Vec<String> = vec!["--stop"].into_iter().map(String::from).collect();
assert!(parse_args(&args).is_err());
}
#[test]
fn test_terminate_requires_identifier() {
let args: Vec<String> = vec!["--terminate"].into_iter().map(String::from).collect();
assert!(parse_args(&args).is_err());
}
#[test]
fn test_query_and_control_modes_are_mutually_exclusive() {
let args: Vec<String> = vec!["--status", "uuid", "--stop", "my-session"]
.into_iter()
.map(String::from)
.collect();
let error = parse_args(&args).unwrap_err();
assert!(error.contains(
"Cannot combine --status, --list, --upload-log, --stop, --terminate, or --cleanup"
));
}
#[test]
fn test_upload_log_and_control_modes_are_mutually_exclusive() {
let args: Vec<String> = vec!["--upload-log", "uuid", "--terminate", "my-session"]
.into_iter()
.map(String::from)
.collect();
let error = parse_args(&args).unwrap_err();
assert!(error.contains(
"Cannot combine --status, --list, --upload-log, --stop, --terminate, or --cleanup"
));
}
#[test]
fn test_output_format_equals_syntax() {
let args: Vec<String> = vec!["--status=my-uuid", "--output-format=json"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(
result.wrapper_options.output_format,
Some("json".to_string())
);
}
#[test]
fn test_list_option() {
let args: Vec<String> = vec!["--list"].into_iter().map(String::from).collect();
let result = parse_args(&args).unwrap();
assert!(result.wrapper_options.list);
assert!(result.command.is_empty());
}
#[test]
fn test_list_with_output_format() {
let args: Vec<String> = vec!["--list", "--output-format", "json"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert!(result.wrapper_options.list);
assert_eq!(
result.wrapper_options.output_format,
Some("json".to_string())
);
}
#[test]
fn test_status_requires_uuid() {
let args: Vec<String> = vec!["--status"].into_iter().map(String::from).collect();
assert!(parse_args(&args).is_err());
}
#[test]
fn test_output_format_requires_format() {
let args: Vec<String> = vec!["--status", "uuid", "--output-format"]
.into_iter()
.map(String::from)
.collect();
assert!(parse_args(&args).is_err());
}
#[test]
fn test_invalid_output_format() {
let args: Vec<String> = vec!["--status", "uuid", "--output-format", "invalid"]
.into_iter()
.map(String::from)
.collect();
assert!(parse_args(&args).is_err());
}
#[test]
fn test_output_format_without_status() {
let args: Vec<String> = vec!["--output-format", "json", "--", "npm", "test"]
.into_iter()
.map(String::from)
.collect();
assert!(parse_args(&args).is_err());
}
#[test]
fn test_cleanup_option() {
let args: Vec<String> = vec!["--cleanup"].into_iter().map(String::from).collect();
let result = parse_args(&args).unwrap();
assert!(result.wrapper_options.cleanup);
assert!(!result.wrapper_options.cleanup_dry_run);
}
#[test]
fn test_cleanup_dry_run_option() {
let args: Vec<String> = vec!["--cleanup-dry-run"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert!(result.wrapper_options.cleanup);
assert!(result.wrapper_options.cleanup_dry_run);
}
#[test]
fn test_shell_default_is_auto() {
let args: Vec<String> = vec!["echo", "hello"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.shell, "auto");
}
#[test]
fn test_shell_bash() {
let args: Vec<String> = vec![
"--isolated",
"docker",
"--shell",
"bash",
"--",
"npm",
"test",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.shell, "bash");
}
#[test]
fn test_shell_zsh() {
let args: Vec<String> = vec![
"--isolated",
"docker",
"--shell",
"zsh",
"--",
"npm",
"test",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.shell, "zsh");
}
#[test]
fn test_shell_sh() {
let args: Vec<String> = vec!["--isolated", "docker", "--shell", "sh", "--", "npm", "test"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.shell, "sh");
}
#[test]
fn test_shell_auto() {
let args: Vec<String> = vec![
"--isolated",
"docker",
"--shell",
"auto",
"--",
"npm",
"test",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.shell, "auto");
}
#[test]
fn test_shell_equals_syntax() {
let args: Vec<String> = vec!["--isolated", "docker", "--shell=bash", "--", "npm", "test"]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.shell, "bash");
}
#[test]
fn test_shell_lowercase_normalization() {
let args: Vec<String> = vec![
"--isolated",
"docker",
"--shell",
"BASH",
"--",
"npm",
"test",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.shell, "bash");
}
#[test]
fn test_shell_invalid() {
let args: Vec<String> = vec![
"--isolated",
"docker",
"--shell",
"fish",
"--",
"echo",
"hi",
]
.into_iter()
.map(String::from)
.collect();
assert!(parse_args(&args).is_err());
}
#[test]
fn test_shell_missing_argument() {
let args: Vec<String> = vec!["--isolated", "docker", "--shell"]
.into_iter()
.map(String::from)
.collect();
assert!(parse_args(&args).is_err());
}
#[test]
fn test_shell_with_ssh() {
let args: Vec<String> = vec![
"--isolated",
"ssh",
"--endpoint",
"user@host",
"--shell",
"bash",
"--",
"echo",
"hi",
]
.into_iter()
.map(String::from)
.collect();
let result = parse_args(&args).unwrap();
assert_eq!(result.wrapper_options.shell, "bash");
assert_eq!(result.wrapper_options.isolated, Some("ssh".to_string()));
}