From 2036f0bd4c11bdcacfd7b5e4cf785e555ce1d976 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 26 May 2026 17:41:02 +0900 Subject: [PATCH] test(#742): add git-fixture test for diff changed_file_count dedup; fixes unreachable branch in #740 coverage --- ROADMAP.md | 2 + .../tests/output_format_contract.rs | 80 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index d5ad47e5..9804074a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7649,3 +7649,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 740. **Test coverage gap for ROADMAP #733: `diff_json_has_status_and_result_field_702` did not assert `changed_file_count` contract** — dogfooded 2026-05-26 on `d5f0d6ed`. The test asserts `kind`, `status`, `result`, `action`, `working_directory` but not the new `changed_file_count` field added by #733. Coverage gap: (a) no assertion that the field exists, (b) no assertion of numeric type in git repos, (c) no regression guard for dedupe behavior (staged+unstaged to the same file = 1 changed file). Fix: extend the test to assert `changed_file_count: null` in non-git repos and `changed_file_count: u64` in git repos. Source: gaebal-gajae dogfood on `d5f0d6ed`, 2026-05-26. 741. **`claw config list`, `claw config show`, `claw config bogus` --output-format json returned `hint: null` — the unsupported_config_section error envelope had no `hint` field populated, so callers reading `.hint` get null with no actionable guidance** — dogfooded 2026-05-26 on `5d072d21`. The `render_config_json` unsupported-section branch returned a JSON object with `error` (contains the section list) but no `hint` field. Notably `config list` and `config show` are natural verb patterns that users type expecting a list/show subcommand, but claw config uses `claw config` (no args) for list and `claw config
` for show — the error gave no indication of this. Fix: add `hint` field to unsupported_config_section error; verbs (`list`, `show`, `help`, `info`) get a hint explaining the correct idiom (`claw config` / `claw config
`); other unknown sections get a "not a config section" hint listing valid values. Source: Jobdori dogfood on `5d072d21`, 2026-05-26. + +742. **ROADMAP #740 test coverage gap: the new `changed_file_count` branch for git repos was unreachable — the fixture is a plain `unique_temp_dir` (no `git init`), so the test always exercises the `no_git_repo` path and never proves the numeric contract or deduplication behavior** — confirmed by gaebal-gajae on `5d072d21`, fixed on `6e78c1fc`. Fix: add `diff_json_changed_file_count_deduplication_733` test that (a) `git init`s a temp repo, (b) commits a file, (c) asserts `result:"clean"` + `changed_file_count:0`, (d) stages an edit + makes an unstaged edit to the same file, (e) asserts `result:"changes"` + `changed_file_count:1` — proving the BTreeSet deduplication actually works. Source: gaebal-gajae dogfood on `5d072d21`, 2026-05-26. diff --git a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs index 1fc8165b..e9ecb216 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -1376,6 +1376,86 @@ fn diff_json_has_status_and_result_field_702() { } } +#[test] +fn diff_json_changed_file_count_deduplication_733() { + // #733/#742: changed_file_count must be numeric in a git repo, be 0 for clean, + // and deduplicate staged+unstaged edits to the same file (1 file changed = count 1). + use std::process::Command; + let root = unique_temp_dir("diff-changed-dedup"); + fs::create_dir_all(&root).expect("temp dir"); + + // git init + identity config + initial commit + Command::new("git") + .args(["init"]) + .current_dir(&root) + .output() + .expect("git init"); + Command::new("git") + .args(["config", "user.email", "test@claw.test"]) + .current_dir(&root) + .output() + .expect("git config email"); + Command::new("git") + .args(["config", "user.name", "Test"]) + .current_dir(&root) + .output() + .expect("git config name"); + fs::write(root.join("tracked.txt"), b"v1").expect("write tracked"); + Command::new("git") + .args(["add", "tracked.txt"]) + .current_dir(&root) + .output() + .expect("git add"); + Command::new("git") + .args(["commit", "-m", "init"]) + .current_dir(&root) + .output() + .expect("git commit"); + + // Clean state: changed_file_count must be 0 + let bin = env!("CARGO_BIN_EXE_claw"); + let clean = Command::new(bin) + .current_dir(&root) + .args(["--output-format", "json", "diff"]) + .output() + .expect("claw diff clean"); + let clean_json: serde_json::Value = + serde_json::from_slice(&clean.stdout).expect("diff clean stdout must be valid JSON"); + assert_eq!(clean_json["result"], "clean", "fresh repo must be clean"); + assert_eq!( + clean_json["changed_file_count"].as_u64(), + Some(0), + "clean repo must have changed_file_count:0 (#733)" + ); + + // Make a staged edit AND an unstaged edit to the same file + fs::write(root.join("tracked.txt"), b"v2").expect("staged write"); + Command::new("git") + .args(["add", "tracked.txt"]) + .current_dir(&root) + .output() + .expect("git add staged"); + fs::write(root.join("tracked.txt"), b"v3").expect("unstaged write"); + + // Dirty state: same file appears in staged+unstaged — must deduplicate to count 1 + let dirty = Command::new(bin) + .current_dir(&root) + .args(["--output-format", "json", "diff"]) + .output() + .expect("claw diff dirty"); + let dirty_json: serde_json::Value = + serde_json::from_slice(&dirty.stdout).expect("diff dirty stdout must be valid JSON"); + assert_eq!( + dirty_json["result"], "changes", + "dirty repo must have result:changes (#733)" + ); + assert_eq!( + dirty_json["changed_file_count"].as_u64(), + Some(1), + "staged+unstaged edits to same file must deduplicate to changed_file_count:1 (#733)" + ); +} + #[test] fn export_json_has_kind_702() { // #458/#702: `claw export --output-format json` must emit kind:export.