#![cfg(feature = "localfs")]
#![allow(clippy::unwrap_used, clippy::expect_used)]
mod common;
use common::kernel_at;
use std::fs;
fn parse_json(text: &str) -> serde_json::Value {
serde_json::from_str(text).unwrap_or_else(|e| panic!("expected valid JSON, got error {e}; text was: {text:?}"))
}
#[tokio::test]
async fn diff_json_identical_shape_has_differ_and_hunks() {
let tmp = tempfile::tempdir().unwrap();
fs::write(tmp.path().join("a.txt"), "same content\n").unwrap();
fs::write(tmp.path().join("b.txt"), "same content\n").unwrap();
let kernel = kernel_at(tmp.path());
let result = kernel
.execute("diff --json a.txt b.txt")
.await
.expect("diff should succeed");
assert_eq!(result.code, 0, "identical files → exit 0");
let json = parse_json(&result.text_out());
assert_eq!(json["old_file"], "a.txt", "old_file must always be present: {json}");
assert_eq!(json["new_file"], "b.txt", "new_file must always be present: {json}");
assert_eq!(json["differ"], false, "identical files: differ must be false: {json}");
assert!(
json["hunks"].is_array(),
"hunks must be present as an array in non-quiet mode: {json}"
);
assert_eq!(
json["hunks"].as_array().unwrap().len(),
0,
"identical files: hunks must be empty: {json}"
);
}
#[tokio::test]
async fn diff_json_full_diff_shape_has_differ_and_hunks() {
let tmp = tempfile::tempdir().unwrap();
fs::write(tmp.path().join("old.txt"), "line1\nline2\nline3\n").unwrap();
fs::write(tmp.path().join("new.txt"), "line1\nchanged\nline3\n").unwrap();
let kernel = kernel_at(tmp.path());
let result = kernel
.execute("diff --json old.txt new.txt")
.await
.expect("diff should execute (exit 1 is not an Err)");
assert_eq!(result.code, 1, "differing files → exit 1");
let json = parse_json(&result.text_out());
assert_eq!(json["old_file"], "old.txt", "old_file must always be present: {json}");
assert_eq!(json["new_file"], "new.txt", "new_file must always be present: {json}");
assert_eq!(
json["differ"], true,
"full diff: differ must be true — this is the shape-consistency bug: {json}"
);
assert!(
json["hunks"].is_array(),
"hunks must be present as an array in non-quiet mode: {json}"
);
assert!(
!json["hunks"].as_array().unwrap().is_empty(),
"differing files: hunks must be non-empty: {json}"
);
}
#[tokio::test]
async fn diff_json_quiet_shape_has_differ_no_hunks() {
let tmp = tempfile::tempdir().unwrap();
fs::write(tmp.path().join("x.txt"), "alpha\n").unwrap();
fs::write(tmp.path().join("y.txt"), "beta\n").unwrap();
let kernel = kernel_at(tmp.path());
let result = kernel
.execute("diff -q --json x.txt y.txt")
.await
.expect("diff -q should execute");
assert_eq!(result.code, 1, "differing files → exit 1");
let json = parse_json(&result.text_out());
assert_eq!(json["old_file"], "x.txt", "old_file must always be present: {json}");
assert_eq!(json["new_file"], "y.txt", "new_file must always be present: {json}");
assert_eq!(json["differ"], true, "quiet differ: differ must be true: {json}");
assert!(
json.get("hunks").is_none(),
"quiet mode must NOT include hunks (per CHANGELOG contract): {json}"
);
}
#[tokio::test]
async fn diff_json_all_paths_have_core_keys() {
let tmp = tempfile::tempdir().unwrap();
fs::write(tmp.path().join("same1.txt"), "abc\n").unwrap();
fs::write(tmp.path().join("same2.txt"), "abc\n").unwrap();
fs::write(tmp.path().join("diff1.txt"), "abc\n").unwrap();
fs::write(tmp.path().join("diff2.txt"), "xyz\n").unwrap();
let kernel = kernel_at(tmp.path());
let scenarios: &[(&str, i64)] = &[
("diff --json same1.txt same2.txt", 0), ("diff --json diff1.txt diff2.txt", 1), ("diff -q --json diff1.txt diff2.txt", 1), ];
for (cmd, expected_code) in scenarios {
let result = kernel
.execute(cmd)
.await
.expect("diff should execute");
assert_eq!(result.code, *expected_code, "exit code for `{cmd}`");
let json = parse_json(&result.text_out());
for key in &["old_file", "new_file", "differ"] {
assert!(
json.get(*key).is_some(),
"`{key}` must be present in all `diff --json` shapes; cmd=`{cmd}`; json={json}"
);
}
}
}