use std::fs;
use std::path::Path;
use std::process::{Command, Output};
fn mkit_bin() -> &'static str {
env!("CARGO_BIN_EXE_mkit")
}
fn run_in(cwd: &Path, args: &[&str]) -> Output {
let xdg = tempfile::tempdir().expect("xdg tempdir");
let out = Command::new(mkit_bin())
.args(args)
.current_dir(cwd)
.env("XDG_CONFIG_HOME", xdg.path())
.output()
.expect("spawn mkit");
drop(xdg);
out
}
fn init_repo() -> tempfile::TempDir {
let td = tempfile::tempdir().unwrap();
assert!(run_in(td.path(), &["init"]).status.success());
td
}
#[test]
fn remote_add_rejects_newline_in_url() {
let td = init_repo();
let evil = "mkit+https://example.com/p\nuser.identity = ed25519:deadbeef";
let out = run_in(td.path(), &["remote", "add", evil]);
assert!(
!out.status.success(),
"remote add must reject a URL with control characters"
);
let cfg = fs::read_to_string(td.path().join(".mkit/config")).unwrap_or_default();
assert!(
!cfg.contains("user.identity"),
"injected key leaked into config: {cfg}"
);
}
#[test]
fn remote_add_rejects_newline_in_name() {
let td = init_repo();
let out = run_in(
td.path(),
&["remote", "add", "bad\nname", "mkit+https://example.com/p"],
);
assert!(
!out.status.success(),
"remote add must reject a remote name with control characters"
);
}
#[test]
fn clone_rejects_newline_in_url() {
let parent = tempfile::tempdir().unwrap();
let evil = "mkit+https://example.com/p\nuser.identity = ed25519:deadbeef";
let out = run_in(parent.path(), &["clone", evil, "dest"]);
assert!(
!out.status.success(),
"clone must reject a URL with control characters"
);
let cfg = fs::read_to_string(parent.path().join("dest/.mkit/config")).unwrap_or_default();
assert!(
!cfg.contains("user.identity"),
"injected key leaked into clone config: {cfg}"
);
}
#[test]
fn remote_add_accepts_clean_url() {
let td = init_repo();
let out = run_in(
td.path(),
&["remote", "add", "mkit+https://example.com/project"],
);
assert!(
out.status.success(),
"a clean URL must still be accepted: {}",
String::from_utf8_lossy(&out.stderr)
);
}