use forge_engine::*;
use tempfile::TempDir;
#[tokio::test]
async fn f1_host_backend_runs_command_captures_output() {
let config = ForgeConfig::default();
let backend = HostBackend::new(&config);
let dir = TempDir::new().unwrap();
let output = backend
.run_command(dir.path(), "echo", &["hello"], &[], 10)
.await
.unwrap();
assert_eq!(output.stdout.trim(), "hello");
assert_eq!(output.exit_code, 0);
}
#[tokio::test]
async fn f2_host_backend_timeout() {
let config = ForgeConfig::default();
let backend = HostBackend::new(&config);
let dir = TempDir::new().unwrap();
let result = backend
.run_command(dir.path(), "sleep", &["60"], &[], 1)
.await;
assert!(result.is_err());
let msg = format!("{}", result.unwrap_err());
assert!(
msg.contains("timeout") || msg.contains("Timeout"),
"Expected timeout error, got: {msg}"
);
}
#[test]
fn f4_container_backend_autodetect_fallback_host() {
let config = ForgeConfig {
execution_backend_preference: "auto".to_string(),
..ForgeConfig::default()
};
let result = select_backend(&config);
assert!(result.is_ok(), "Backend selection should succeed");
}
#[test]
fn f5_container_backend_sealed_requires_no_network() {
let config = ForgeConfig {
mode: "sealed_local".to_string(),
execution_backend_preference: "container".to_string(),
..ForgeConfig::default()
};
match ContainerBackend::new(&config) {
Ok(cb) => {
let _runtime = cb.runtime();
}
Err(e) => {
let msg = format!("{e}");
assert!(
msg.contains("container") || msg.contains("runtime"),
"Should fail due to no container runtime: {msg}"
);
}
}
}
#[test]
fn g1_cargo_adapter_detects_cargo_project() {
let with_cargo = TempDir::new().unwrap();
std::fs::write(
with_cargo.path().join("Cargo.toml"),
"[package]\nname = \"test\"\n",
)
.unwrap();
assert!(CargoAdapter::detect(with_cargo.path()));
let without_cargo = TempDir::new().unwrap();
assert!(!CargoAdapter::detect(without_cargo.path()));
}
#[cfg(unix)]
#[test]
fn copy_dir_recursive_skips_symlinks() {
let src = tempfile::tempdir().unwrap();
let dst = tempfile::tempdir().unwrap();
std::fs::write(src.path().join("real.txt"), "content").unwrap();
std::os::unix::fs::symlink("/etc/passwd", src.path().join("evil.txt")).unwrap();
forge_engine::exec::host::copy_dir_recursive_pub(src.path(), dst.path()).unwrap();
assert!(dst.path().join("real.txt").exists());
assert!(
!dst.path().join("evil.txt").exists(),
"Symlink should not be copied"
);
}
#[test]
fn env_allowlist_blocks_secrets_passes_cargo() {
assert!(is_env_allowed("CARGO_HOME"));
assert!(is_env_allowed("RUSTUP_HOME"));
assert!(is_env_allowed("PATH"));
assert!(is_env_allowed("CARGO_INCREMENTAL"));
assert!(!is_env_allowed("AWS_SECRET_ACCESS_KEY"));
assert!(!is_env_allowed("ANTHROPIC_API_KEY"));
assert!(!is_env_allowed("DATABASE_URL"));
assert!(!is_env_allowed("GITHUB_TOKEN"));
assert!(!is_env_allowed("OPENAI_API_KEY"));
assert!(!is_env_allowed("DOCKER_AUTH_CONFIG"));
}
#[test]
fn g3_cargo_adapter_parses_clippy_json() {
let adapter = CargoAdapter;
let config = ForgeConfig::default();
let cmds = adapter.check_commands(&config);
let clippy_cmd = &cmds[1];
let clippy_json = r#"{"reason":"compiler-message","message":{"code":{"code":"clippy::needless_return"},"level":"warning","message":"unneeded return","spans":[{"file_name":"src/lib.rs","line_start":42,"column_start":5}]}}"#;
let output = adapter.parse_check_output(clippy_cmd, "", clippy_json, 0);
assert!(!output.effects.is_empty(), "Should parse clippy effects");
assert_eq!(
output.effects[0].sig.message_class,
"clippy::needless_return"
);
assert_eq!(output.effects[0].line, Some(42));
}