use std::fs;
use std::process::Command;
fn mkit_bin() -> &'static str {
env!("CARGO_BIN_EXE_mkit")
}
fn run_in(cwd: &std::path::Path, args: &[&str]) -> std::process::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_with_commit(content: &[u8]) -> tempfile::TempDir {
let td = tempfile::tempdir().unwrap();
assert!(run_in(td.path(), &["init"]).status.success());
assert!(run_in(td.path(), &["keygen"]).status.success());
fs::write(td.path().join("a.txt"), content).unwrap();
assert!(run_in(td.path(), &["add", "a.txt"]).status.success());
assert!(
run_in(td.path(), &["commit", "-m", "initial"])
.status
.success()
);
td
}
fn extract_string(line: &str, prefix: &str) -> String {
let start = line.find(prefix).expect(prefix) + prefix.len();
let rest = &line[start..];
let mut iter = rest.char_indices();
let mut end = 0;
while let Some((i, c)) = iter.next() {
if c == '\\' {
iter.next();
continue;
}
if c == '"' {
end = i;
break;
}
}
rest[..end].to_string()
}
#[test]
fn branch_json_lists_current_and_other_branches() {
let td = init_with_commit(b"hello");
assert!(run_in(td.path(), &["branch", "feature"]).status.success());
let out = run_in(td.path(), &["branch", "--format=json"]);
assert!(out.status.success(), "branch failed: {out:?}");
let stdout = String::from_utf8(out.stdout).unwrap();
let lines: Vec<_> = stdout.lines().collect();
assert_eq!(lines.len(), 2, "expected 2 branches, got: {stdout:?}");
let trues = lines
.iter()
.filter(|l| l.contains("\"current\":true"))
.count();
let falses = lines
.iter()
.filter(|l| l.contains("\"current\":false"))
.count();
assert_eq!(trues, 1, "expected exactly one current=true: {stdout:?}");
assert_eq!(falses, 1, "expected exactly one current=false: {stdout:?}");
for line in &lines {
let hash = extract_string(line, "\"hash\":\"");
assert_eq!(hash.len(), 64, "hash should be 64 hex: {line:?}");
}
}
#[test]
fn remote_json_emits_empty_stdout_when_unset() {
let td = init_with_commit(b"hello");
let out = run_in(td.path(), &["remote", "--format=json"]);
assert!(out.status.success());
let stdout = String::from_utf8(out.stdout).unwrap();
assert!(
stdout.is_empty(),
"unconfigured remote should produce empty stdout, got: {stdout:?}"
);
}
#[test]
fn remote_json_emits_url_and_transport_when_set() {
let td = init_with_commit(b"hello");
assert!(
run_in(td.path(), &["remote", "add", "mkit+file:///tmp/example"])
.status
.success()
);
let out = run_in(td.path(), &["remote", "--format=json"]);
assert!(out.status.success());
let stdout = String::from_utf8(out.stdout).unwrap();
assert!(
stdout.contains("\"url\":\"mkit+file:///tmp/example\""),
"missing url: {stdout:?}"
);
assert!(
stdout.contains("\"transport\":\"file\""),
"missing transport: {stdout:?}"
);
}
#[test]
fn config_json_show_all_emits_flat_object_with_known_keys() {
let td = init_with_commit(b"hello");
let out = run_in(td.path(), &["config", "--format=json"]);
assert!(out.status.success());
let stdout = String::from_utf8(out.stdout).unwrap();
assert_eq!(
stdout.lines().count(),
1,
"config --format=json should emit one line, got: {stdout:?}"
);
for key in [
"user.identity",
"default_branch",
"remote_endpoint",
"ssh.strict_host_key_checking",
] {
let needle = format!("\"{key}\":");
assert!(
stdout.contains(&needle),
"config json missing key '{key}': {stdout:?}"
);
}
}
#[test]
fn config_json_show_one_emits_single_key_object() {
let td = init_with_commit(b"hello");
let out = run_in(td.path(), &["config", "default_branch", "--format=json"]);
assert!(out.status.success());
let stdout = String::from_utf8(out.stdout).unwrap();
assert!(
stdout.contains("\"default_branch\":\"main\""),
"expected default_branch=main in: {stdout:?}"
);
}
#[test]
fn blame_json_emits_one_record_per_line() {
let td = init_with_commit(b"line one\nline two\nline three\n");
let out = run_in(td.path(), &["blame", "--format=json", "a.txt"]);
assert!(out.status.success(), "blame failed: {out:?}");
let stdout = String::from_utf8(out.stdout).unwrap();
let lines: Vec<_> = stdout.lines().collect();
assert_eq!(lines.len(), 3, "expected 3 records, got: {stdout:?}");
for (i, line) in lines.iter().enumerate() {
let expected_lineno = format!("\"line_num\":{}", i + 1);
assert!(
line.contains(&expected_lineno),
"missing line_num {} in: {line:?}",
i + 1
);
let hash = extract_string(line, "\"hash\":\"");
assert_eq!(hash.len(), 64);
let author = extract_string(line, "\"author\":\"");
assert!(
author.starts_with("ed25519:") || author.starts_with("mid:"),
"unexpected author '{author}' in: {line:?}"
);
}
assert!(lines[0].contains("\"text\":\"line one\""));
assert!(lines[1].contains("\"text\":\"line two\""));
assert!(lines[2].contains("\"text\":\"line three\""));
}