#[path = "common/mod.rs"]
mod common;
use common::{fixture_path, parse_json, redact_all, run_fallow, run_fallow_in_root};
use tempfile::tempdir;
#[test]
fn dupes_json_output_has_clone_groups() {
let output = run_fallow("dupes", "duplicate-code", &["--format", "json", "--quiet"]);
let json = parse_json(&output);
assert!(
json.get("clone_groups").is_some(),
"dupes JSON should have clone_groups key"
);
let groups = json["clone_groups"].as_array().unwrap();
assert!(
!groups.is_empty(),
"duplicate-code fixture should have clone groups"
);
}
#[test]
fn dupes_json_has_stats() {
let output = run_fallow("dupes", "duplicate-code", &["--format", "json", "--quiet"]);
let json = parse_json(&output);
assert!(
json.get("stats").is_some(),
"dupes JSON should have stats key"
);
}
#[test]
fn dupes_strict_mode_accepted() {
let output = run_fallow(
"dupes",
"duplicate-code",
&["--mode", "strict", "--format", "json", "--quiet"],
);
assert!(
output.code == 0 || output.code == 1,
"dupes --mode strict should not crash, got exit code {}",
output.code
);
}
#[test]
fn dupes_mild_mode_accepted() {
let output = run_fallow(
"dupes",
"duplicate-code",
&["--mode", "mild", "--format", "json", "--quiet"],
);
assert!(
output.code == 0 || output.code == 1,
"dupes --mode mild should not crash"
);
}
#[test]
fn dupes_min_tokens_filter() {
let output = run_fallow(
"dupes",
"duplicate-code",
&["--min-tokens", "1000", "--format", "json", "--quiet"],
);
let json = parse_json(&output);
let groups = json["clone_groups"].as_array().unwrap();
assert!(
groups.is_empty(),
"high min-tokens should filter out all clones"
);
}
#[test]
fn dupes_top_flag() {
let output = run_fallow(
"dupes",
"duplicate-code",
&["--top", "1", "--format", "json", "--quiet"],
);
let json = parse_json(&output);
let groups = json["clone_groups"].as_array().unwrap();
assert!(
groups.len() <= 1,
"--top 1 should return at most 1 clone group"
);
}
#[test]
fn dupes_save_baseline_creates_parent_directory() {
let dir = tempdir().unwrap();
std::fs::write(
dir.path().join("package.json"),
r#"{"name":"dupes-save","version":"1.0.0"}"#,
)
.unwrap();
std::fs::create_dir_all(dir.path().join("src")).unwrap();
let clone = "export function shared(value) {\n if (value > 1) {\n return value * 2;\n }\n return value + 1;\n}\n";
std::fs::write(dir.path().join("src/one.ts"), clone).unwrap();
std::fs::write(dir.path().join("src/two.ts"), clone).unwrap();
let baseline_path = dir.path().join("fallow-baselines/dupes.json");
let output = run_fallow_in_root(
"dupes",
dir.path(),
&[
"--save-baseline",
baseline_path.to_str().unwrap(),
"--format",
"json",
"--quiet",
],
);
let rendered = redact_all(&format!("{}\n{}", output.stdout, output.stderr), dir.path());
assert!(
output.code == 0 || output.code == 1,
"dupes save baseline should not crash: {rendered}"
);
assert!(
baseline_path.exists(),
"dupes save baseline should create nested file: {rendered}"
);
}
#[test]
fn dupes_json_paths_are_relative() {
let output = run_fallow("dupes", "duplicate-code", &["--format", "json", "--quiet"]);
let json = parse_json(&output);
let groups = json["clone_groups"].as_array().unwrap();
assert!(!groups.is_empty(), "fixture should have clone groups");
for group in groups {
for instance in group["instances"].as_array().unwrap() {
let path = instance["file"].as_str().unwrap();
assert!(
!path.starts_with('/'),
"clone group instance path should be relative, got: {path}"
);
}
}
if let Some(families) = json.get("clone_families").and_then(|f| f.as_array()) {
for family in families {
if let Some(files) = family.get("files").and_then(|f| f.as_array()) {
for file in files {
let path = file.as_str().unwrap();
assert!(
!path.starts_with('/'),
"clone family file path should be relative, got: {path}"
);
}
}
}
}
}
#[test]
fn dupes_human_output_snapshot() {
let output = run_fallow("dupes", "duplicate-code", &["--quiet"]);
let root = fixture_path("duplicate-code");
let redacted = redact_all(&output.stdout, &root);
insta::assert_snapshot!("dupes_human_output", redacted);
}