mod common;
use common::{missing, Repo};
const HELM_TMPL: &str =
"{{- if .Values.enabled }}\nkind: Deployment\nmetadata:\n name: x\n{{- end }}\n";
#[test]
fn invalid_json_is_rejected_and_valid_json_passes() {
if missing("node") {
return;
}
let r = Repo::new();
r.stage("bad.json", "{\"a\": 1,,}\n");
assert!(!r.hook("pre-commit-lint-json-yaml", &[]).passed());
let r = Repo::new();
r.stage("ok.json", "{\"a\": 1}\n");
assert!(r.hook("pre-commit-lint-json-yaml", &[]).passed());
}
#[test]
fn invalid_yaml_is_rejected_and_valid_yaml_passes() {
if missing("yq") {
return;
}
let r = Repo::new();
r.stage("bad.yaml", "a:\n\tb: 1\n"); assert!(!r.hook("pre-commit-lint-json-yaml", &[]).passed());
let r = Repo::new();
r.stage("ok.yaml", "a:\n b: 1\n");
assert!(r.hook("pre-commit-lint-json-yaml", &[]).passed());
}
#[test]
fn a_broken_yml_is_rejected_like_a_broken_yaml() {
if missing("yq") {
return;
}
let r = Repo::new();
r.stage("bad.yml", "a:\n\tb: 1\n"); assert!(
!r.hook("pre-commit-lint-json-yaml", &[]).passed(),
".yml was declared in scope but never actually parsed"
);
let r = Repo::new();
r.stage("ok.yml", "a:\n b: 1\n");
assert!(r.hook("pre-commit-lint-json-yaml", &[]).passed());
}
#[test]
fn a_helm_chart_template_named_yml_is_skipped() {
if missing("yq") {
return;
}
let r = Repo::new();
r.write("chart/Chart.yaml", "name: c\n");
r.stage("chart/templates/deploy.yml", HELM_TMPL);
assert!(r.hook("pre-commit-lint-json-yaml", &[]).passed());
}
#[test]
fn a_helm_chart_template_is_skipped() {
if missing("yq") {
return;
}
let r = Repo::new();
r.write("chart/Chart.yaml", "name: c\n");
r.stage("chart/templates/deploy.yaml", HELM_TMPL);
assert!(r.hook("pre-commit-lint-json-yaml", &[]).passed());
}
#[test]
fn go_template_yaml_outside_a_chart_still_fails() {
if missing("yq") {
return;
}
let r = Repo::new();
r.stage("k/deploy.yaml", HELM_TMPL);
assert!(!r.hook("pre-commit-lint-json-yaml", &[]).passed());
}
#[test]
fn a_dash_prefixed_filename_is_still_content_checked() {
if missing("node") {
return;
}
let r = Repo::new();
r.stage("-weird.json", "{\"a\": 1}\n");
assert!(r.hook("pre-commit-lint-json-yaml", &[]).passed());
}
#[test]
fn a_dash_prefixed_yaml_filename_is_still_content_checked() {
if missing("yq") {
return;
}
let r = Repo::new();
r.stage("-weird.yaml", "a: 1\n");
assert!(r.hook("pre-commit-lint-json-yaml", &[]).passed());
}
#[test]
fn yamllint_does_nothing_without_a_repo_config() {
let r = Repo::new();
r.stage("a.yaml", "a: 1\n");
let run = r.hook("pre-commit-yamllint", &[]);
assert!(run.silent(), "expected silence, got:\n{}", run.output());
}
#[test]
fn yamllint_runs_when_the_repo_opts_in() {
if missing("yamllint") {
return;
}
let r = Repo::new();
r.write(".yamllint", "rules:\n trailing-spaces: enable\n");
r.stage("a.yaml", "a: 1 \n"); assert!(!r.hook("pre-commit-yamllint", &[]).passed());
}
#[test]
fn a_dash_prefixed_filename_is_still_content_checked_by_yamllint() {
if missing("yamllint") {
return;
}
let r = Repo::new();
r.write(".yamllint", "rules:\n trailing-spaces: enable\n");
r.stage("-weird.yaml", "a: 1\n");
assert!(r.hook("pre-commit-yamllint", &[]).passed());
}
#[test]
fn lint_js_skips_a_repo_with_no_eslint_config() {
let r = Repo::new();
r.stage("a.ts", "const x = 1\n");
let run = r.hook("pre-commit-lint-js", &[]);
assert!(run.passed());
assert!(run.says("no eslint config"));
}
#[test]
fn lint_js_reports_a_real_error() {
if missing("eslint") {
return;
}
let r = Repo::new();
r.write(
"eslint.config.js",
"module.exports = [{rules:{'no-undef':'error'}}];\n",
);
r.stage("a.js", "undefinedFunction();\n");
assert!(!r.hook("pre-commit-lint-js", &[]).passed());
}
#[test]
fn prettier_does_nothing_without_config_or_a_local_binary() {
let r = Repo::new();
r.stage("a.ts", "const x =1\n");
assert!(r.hook("pre-commit-prettier", &[]).passed());
}
#[test]
fn prettier_flags_an_unformatted_file_when_the_repo_opts_in() {
if missing("prettier") {
return;
}
let r = Repo::new();
r.write(".prettierrc", "{}\n");
r.stage("a.ts", "const x =1\n");
assert!(!r.hook("pre-commit-prettier", &[]).passed());
let r2 = Repo::new();
r2.write(".prettierrc", "{}\n");
r2.stage("b.ts", "const x = 1;\n");
assert!(r2.hook("pre-commit-prettier", &[]).passed());
}
#[test]
fn a_dash_prefixed_filename_is_still_content_checked_by_prettier() {
if missing("prettier") {
return;
}
let r = Repo::new();
r.write(".prettierrc", "{}\n");
r.stage("-weird.ts", "const x =1\n");
assert!(!r.hook("pre-commit-prettier", &[]).passed());
}
#[test]
fn ruff_skips_a_repo_with_no_ruff_config() {
let r = Repo::new();
r.stage("a.py", "import os\n");
assert!(r.hook("pre-commit-ruff", &[]).passed());
}
#[test]
fn ruff_reports_lint_and_format_problems_when_the_repo_opts_in() {
if missing("ruff") && missing("uvx") {
return;
}
let r = Repo::new();
r.write("pyproject.toml", "[tool.ruff]\n");
r.stage("a.py", "import os\n"); assert!(!r.hook("pre-commit-ruff", &[]).passed());
}
#[test]
fn ruff_fixes_what_it_can_and_still_blocks_on_the_rest() {
if missing("ruff") && missing("uvx") {
return;
}
let r = Repo::new();
r.write("pyproject.toml", "[tool.ruff]\n");
r.git(&["config", "amont.fix", "true"]);
r.stage("a.py", "import os\nprint( undefined_name )\n");
let run = r.hook("pre-commit-ruff", &[]);
assert!(
!run.passed(),
"an unfixable finding must still block:\n{}",
run.output()
);
let on_disk = std::fs::read_to_string(r.path("a.py")).expect("read");
assert!(
!on_disk.contains("import os"),
"the fixable finding should have been repaired: {on_disk:?}"
);
let staged = r.git(&["show", ":a.py"]);
assert_eq!(
String::from_utf8_lossy(&staged.stdout),
on_disk,
"whatever ruff did fix must be staged, so the next attempt starts from there"
);
}
#[test]
fn ruff_leaves_files_alone_when_fixing_is_off() {
if missing("ruff") && missing("uvx") {
return;
}
let r = Repo::new();
r.write("pyproject.toml", "[tool.ruff]\n");
r.stage("a.py", "import os\n");
assert!(!r.hook("pre-commit-ruff", &[]).passed());
assert_eq!(
std::fs::read_to_string(r.path("a.py")).expect("read"),
"import os\n",
"it rewrote a file nobody asked it to rewrite"
);
}
#[test]
fn pyright_skips_a_repo_with_no_pyright_config() {
let r = Repo::new();
r.stage("a.py", "x: int = 'nope'\n");
let run = r.hook("pre-commit-pyright", &[]);
assert!(run.passed());
assert!(
run.silent(),
"a repo that never opted in must hear nothing:\n{}",
run.output()
);
}
#[test]
fn pyright_reports_a_type_error() {
if missing("pyright") {
return;
}
let r = Repo::new();
r.write("pyrightconfig.json", "{}\n");
r.stage("a.py", "x: int = 'nope'\n");
let run = r.hook("pre-commit-pyright", &[]);
assert!(
!run.passed(),
"a type error must block a Severity::Block check:\n{}",
run.output()
);
assert!(
!run.says("does not exist"),
"it blocked over an argument it could not resolve, not over the code:\n{}",
run.output()
);
assert!(
run.says("a.py"),
"the report must name the offending file:\n{}",
run.output()
);
}
#[test]
fn pyright_passes_clean_code() {
if missing("pyright") {
return;
}
let r = Repo::new();
r.write("pyrightconfig.json", "{}\n");
r.stage("a.py", "x: int = 1\n");
let run = r.hook("pre-commit-pyright", &[]);
assert!(run.passed(), "clean code must not block:\n{}", run.output());
}
#[test]
fn a_dash_prefixed_filename_is_still_content_checked_by_pyright() {
if missing("pyright") {
return;
}
let r = Repo::new();
r.write("pyrightconfig.json", "{}\n");
r.stage("-weird.py", "x: int = 1\n");
let run = r.hook("pre-commit-pyright", &[]);
assert!(
run.passed(),
"a clean file named like a flag must still pass:\n{}",
run.output()
);
let r2 = Repo::new();
r2.write("pyrightconfig.json", "{}\n");
r2.stage("-weird.py", "x: int = 'nope'\n");
assert!(
!r2.hook("pre-commit-pyright", &[]).passed(),
"a file named like a flag was not actually type-checked"
);
}