use std::path::PathBuf;
use std::process::Command;
fn asd_bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_asd"))
}
fn run_onboard(dir: &std::path::Path) -> (bool, String, String) {
run_onboard_args(dir, &[])
}
fn run_onboard_args(dir: &std::path::Path, extra: &[&str]) -> (bool, String, String) {
let mut cmd = Command::new(asd_bin());
cmd.arg("onboard").arg("--no-hooks");
for a in extra {
cmd.arg(a);
}
let out = cmd.current_dir(dir).output().expect("spawn asd onboard");
(
out.status.success(),
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
)
}
fn seed_minimal_python_project(dir: &std::path::Path) {
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(
dir.join("src").join("hello.py"),
"def hello():\n return 'hi'\n",
)
.unwrap();
let _ = Command::new("git")
.arg("init")
.arg("-q")
.current_dir(dir)
.output();
}
#[test]
fn onboard_on_fresh_project_succeeds_end_to_end() {
let tmp = tempfile::tempdir().expect("tempdir");
seed_minimal_python_project(tmp.path());
let (ok, _stdout, stderr) = run_onboard(tmp.path());
assert!(
ok,
"asd onboard must exit 0 on fresh project; stderr:\n{stderr}"
);
assert!(
stderr.contains("[1/4]")
&& stderr.contains("[2/4]")
&& stderr.contains("[3/4]")
&& stderr.contains("[4/4]"),
"all four onboard steps must run; stderr:\n{stderr}"
);
assert!(
tmp.path().join(".asd-state.db").is_file(),
"asd init must create .asd-state.db"
);
assert!(
stderr.contains("1 symbol") || stderr.contains("symbols"),
"index must report at least 1 symbol; stderr:\n{stderr}"
);
}
#[test]
fn onboard_runs_mcp_step_by_default() {
let tmp = tempfile::tempdir().expect("tempdir");
seed_minimal_python_project(tmp.path());
let (ok, _stdout, stderr) = run_onboard(tmp.path());
assert!(
ok,
"onboard must exit 0 even if MCP step warns; stderr:\n{stderr}"
);
assert!(
stderr.contains("[4/4] asd mcp install"),
"MCP step must run by default; stderr:\n{stderr}"
);
let wrote_cfg = tmp.path().join(".mcp.json").is_file();
let warned = stderr.contains("MCP registration skipped");
assert!(
wrote_cfg || warned,
"MCP step must either write .mcp.json or warn; stderr:\n{stderr}"
);
}
#[test]
fn onboard_no_mcp_skips_registration() {
let tmp = tempfile::tempdir().expect("tempdir");
seed_minimal_python_project(tmp.path());
let (ok, _stdout, stderr) = run_onboard_args(tmp.path(), &["--no-mcp"]);
assert!(ok, "onboard --no-mcp must succeed; stderr:\n{stderr}");
assert!(
stderr.contains("[4/4] asd mcp install — skipped (--no-mcp)"),
"--no-mcp must skip registration; stderr:\n{stderr}"
);
assert!(
!tmp.path().join(".mcp.json").is_file(),
"--no-mcp must not write .mcp.json"
);
}
#[test]
fn onboard_is_idempotent() {
let tmp = tempfile::tempdir().expect("tempdir");
seed_minimal_python_project(tmp.path());
let (ok1, _, _) = run_onboard(tmp.path());
assert!(ok1, "first onboard must succeed");
let (ok2, _, stderr2) = run_onboard(tmp.path());
assert!(
ok2,
"second onboard must succeed (idempotent); stderr:\n{stderr2}"
);
}
#[test]
fn onboard_skips_conclusions_import_when_dir_missing_before_first_init() {
let tmp = tempfile::tempdir().expect("tempdir");
seed_minimal_python_project(tmp.path());
let (ok1, _, _) = run_onboard(tmp.path());
assert!(ok1);
assert!(
tmp.path().join(".asd/conclusions").is_dir(),
"init must scaffold .asd/conclusions/"
);
let (ok2, _, stderr2) = run_onboard(tmp.path());
assert!(ok2);
assert!(
!stderr2.contains("skipped (.asd/conclusions/ doesn't exist"),
"second onboard must hit the import branch, not the skip branch; stderr:\n{stderr2}"
);
}