#![expect(
clippy::unwrap_used,
clippy::expect_used,
reason = "tests and benches use unwrap and expect to keep fixture setup concise"
)]
use std::fs;
use std::path::{Path, PathBuf};
use fallow_api::{
AUDIT_CONFIG_BASELINE_UNSUPPORTED, AnalysisOptions, AuditGate, AuditOptions, run_audit,
serialize_audit_programmatic_json,
};
use serde_json::Value;
use crate::common::{commit, git, write};
fn options(root: &Path, base: &str) -> AuditOptions {
AuditOptions {
analysis: AnalysisOptions {
root: Some(root.to_path_buf()),
no_cache: true,
..AnalysisOptions::default()
},
base: Some(base.to_string()),
..AuditOptions::default()
}
}
fn audit(options: &AuditOptions) -> Value {
run_audit(options)
.and_then(serialize_audit_programmatic_json)
.expect("run the programmatic audit")
}
fn inherited_finding_repository(audit: &str) -> (tempfile::TempDir, PathBuf) {
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path().join("project");
fs::create_dir_all(&root).unwrap();
git(&root, &["init", "-q", "-b", "main"]);
write(
&root,
"package.json",
r#"{"name":"audit-gate","private":true}"#,
);
write(
&root,
".fallowrc.json",
&format!(r#"{{"audit": {audit}, "entry": ["src/index.js"]}}"#),
);
write(&root, "src/index.js", "export const ready = true;\n");
write(&root, "src/orphan.js", "export const orphan = 1;\n");
commit(&root, "base");
write(&root, "src/orphan.js", "export const orphan = 2;\n");
commit(&root, "head");
(dir, root)
}
fn styling_repository(audit: &str) -> (tempfile::TempDir, PathBuf) {
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path().join("project");
fs::create_dir_all(&root).unwrap();
git(&root, &["init", "-q", "-b", "main"]);
write(
&root,
"package.json",
r#"{"name":"audit-css","private":true,"devDependencies":{"tailwindcss":"^4.0.0"}}"#,
);
write(
&root,
".fallowrc.json",
&format!(r#"{{"audit": {audit}, "entry": ["src/index.js"]}}"#),
);
write(&root, "src/index.js", "export const ready = true;\n");
write(
&root,
"src/styles.css",
"@theme {\n --color-danger: red;\n}\n",
);
commit(&root, "base");
write(
&root,
"src/styles.css",
"@theme {\n --color-danger: red;\n --shadow-glow: 0 0 8px red;\n}\n.btn { color: red; }\n",
);
commit(&root, "head");
(dir, root)
}
fn styling_sub_kinds(report: &Value) -> Vec<String> {
report["complexity"]["styling_findings"]
.as_array()
.map(|findings| {
findings
.iter()
.filter_map(|finding| finding["sub_kind"].as_str().map(str::to_string))
.collect()
})
.unwrap_or_default()
}
const LOCAL_STYLING: &str = "raw-style-value";
const DEEP_STYLING: &str = "unused-theme-token";
#[test]
fn gate_defaults_to_new_only() {
let (_dir, root) = inherited_finding_repository("{}");
let report = audit(&options(&root, "HEAD~1"));
assert_eq!(report["attribution"]["gate"], "new-only", "{report:#}");
assert_eq!(report["verdict"], "pass", "{report:#}");
}
#[test]
fn config_gate_all_fails_on_an_inherited_finding() {
let (_dir, root) = inherited_finding_repository(r#"{"gate": "all"}"#);
let report = audit(&options(&root, "HEAD~1"));
assert_eq!(report["attribution"]["gate"], "all", "{report:#}");
assert_eq!(report["verdict"], "fail", "{report:#}");
}
#[test]
fn gate_option_overrides_the_config() {
let (_dir, root) = inherited_finding_repository(r#"{"gate": "all"}"#);
let report = audit(&AuditOptions {
gate: Some(AuditGate::NewOnly),
..options(&root, "HEAD~1")
});
assert_eq!(report["attribution"]["gate"], "new-only", "{report:#}");
assert_eq!(report["verdict"], "pass", "{report:#}");
}
#[test]
fn config_gate_applies_to_an_empty_changeset() {
let (_dir, root) = inherited_finding_repository(r#"{"gate": "all"}"#);
let report = audit(&options(&root, "HEAD"));
assert_eq!(report["changed_files_count"], 0, "{report:#}");
assert_eq!(report["attribution"]["gate"], "all", "{report:#}");
}
#[test]
fn styling_runs_local_and_deep_by_default() {
let (_dir, root) = styling_repository("{}");
let kinds = styling_sub_kinds(&audit(&options(&root, "HEAD~1")));
assert!(kinds.iter().any(|kind| kind == LOCAL_STYLING), "{kinds:?}");
assert!(kinds.iter().any(|kind| kind == DEEP_STYLING), "{kinds:?}");
}
#[test]
fn config_css_false_skips_styling() {
let (_dir, root) = styling_repository(r#"{"css": false}"#);
let kinds = styling_sub_kinds(&audit(&options(&root, "HEAD~1")));
assert!(kinds.is_empty(), "{kinds:?}");
}
#[test]
fn config_css_deep_false_keeps_local_styling_only() {
let (_dir, root) = styling_repository(r#"{"cssDeep": false}"#);
let kinds = styling_sub_kinds(&audit(&options(&root, "HEAD~1")));
assert!(kinds.iter().any(|kind| kind == LOCAL_STYLING), "{kinds:?}");
assert!(!kinds.iter().any(|kind| kind == DEEP_STYLING), "{kinds:?}");
}
#[test]
fn css_deep_option_overrides_the_config() {
let (_dir, root) = styling_repository(r#"{"cssDeep": false}"#);
let kinds = styling_sub_kinds(&audit(&AuditOptions {
css_deep: Some(true),
..options(&root, "HEAD~1")
}));
assert!(kinds.iter().any(|kind| kind == DEEP_STYLING), "{kinds:?}");
}
#[test]
fn css_option_turns_styling_off_but_not_on() {
let (_dir, root) = styling_repository("{}");
let kinds = styling_sub_kinds(&audit(&AuditOptions {
css: Some(false),
..options(&root, "HEAD~1")
}));
assert!(kinds.is_empty(), "{kinds:?}");
let (_dir, root) = styling_repository(r#"{"css": false}"#);
let kinds = styling_sub_kinds(&audit(&AuditOptions {
css: Some(true),
css_deep: Some(true),
..options(&root, "HEAD~1")
}));
assert!(kinds.is_empty(), "{kinds:?}");
}
#[test]
fn config_baseline_does_not_fail_an_empty_changeset() {
let (_dir, root) = inherited_finding_repository(r#"{"healthBaseline": "baseline.json"}"#);
let report = audit(&options(&root, "HEAD"));
assert_eq!(report["changed_files_count"], 0, "{report:#}");
assert_eq!(report["verdict"], "pass", "{report:#}");
}
#[test]
fn config_baselines_fail_with_a_stable_code() {
for key in ["deadCodeBaseline", "healthBaseline", "dupesBaseline"] {
let (_dir, root) =
inherited_finding_repository(&format!(r#"{{"{key}": "baseline.json"}}"#));
let err = run_audit(&options(&root, "HEAD~1")).expect_err("a config baseline must fail");
assert_eq!(
err.code.as_deref(),
Some(AUDIT_CONFIG_BASELINE_UNSUPPORTED),
"{key}: {err:?}"
);
assert_eq!(
err.context.as_deref(),
Some(&*format!("audit.{key}")),
"{err:?}"
);
assert_eq!(err.exit_code, 2, "{err:?}");
}
}