use super::support::{Fixture, Observed, bounded, bounded_for, fixture, git, observe, porcelain, remove, repository};
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
const SHOT_URL: &str = "https://lfs.example.com/org/repo/de7030234493a8bea844dbe1d8676e68a2c1a4b014c721f0425a22b6df66faec/pkg/asset/shot.png";
const RELATIVE: &[u8] = b"# T\n\n\n";
fn plain_path(fixture: &Fixture) -> OsString {
let mut path: OsString = fixture.root.join("bin").into_os_string();
path.push(":/usr/bin:/bin");
return path;
}
fn run_with(fixture: &Fixture, path: &OsStr, repo: &Path, arguments: &[&str]) -> Observed {
return observe(
bounded(fixture, fixture.root.join("bin/git").as_path(), path)
.current_dir(repo)
.args(arguments),
b"",
);
}
fn lfs_repository(fixture: &Fixture, setting: &str) -> PathBuf {
let repo: PathBuf = repository(fixture, OsStr::new("repo"));
std::fs::create_dir_all(repo.join("pkg/asset")).expect("tree");
std::fs::write(
repo.join(".lfsconfig"),
b"[lfs]\n\turl = https://lfs.example.com/org/repo\n",
)
.expect("lfsconfig");
std::fs::write(
repo.join(".gitattributes"),
b"*.png filter=lfs diff=lfs merge=lfs -text\n",
)
.expect("attributes");
std::fs::write(repo.join("pkg/asset/shot.png"), b"image bytes").expect("image");
std::fs::write(
repo.join("cli-git.config.jsonc"),
format!(
"{{ \"policies\": {{ \"final-newline\": \"off\", \"markdown/autofix\": {setting} }} }}\n"
),
)
.expect("configuration");
git(fixture, repo.as_path(), &["add", "--all"]);
git(
fixture,
repo.as_path(),
&["commit", "--quiet", "--message=base"],
);
return repo;
}
fn autofix(sequence: u64, trigger: &str, severity: &str, path: &str, fix: &str) -> String {
return format!(
"{{\"schemaVersion\":1,\"sequence\":{sequence},\"type\":\"finding\",\"trigger\":\"{trigger}\",\
\"policyId\":\"markdown/autofix\",\"severity\":\"{severity}\",\"code\":\"markdown/autofix/markdown-autofix\",\
\"message\":\"monochromatic-lint --fix (markdown/lfs-image-url) rewrites {path}.\",\"path\":\"{path}\",\"fix\":\"{fix}\"}}\n"
);
}
fn staged(fixture: &Fixture, repo: &Path, path: &str) -> Vec<u8> {
return git(fixture, repo, &["show", format!(":{path}").as_str()]).stdout;
}
#[test]
fn the_linked_linter_rewrites_through_add_check_and_fix() {
let fixture: Fixture = fixture("markdown-real");
let path: OsString = plain_path(&fixture);
let repo: PathBuf = lfs_repository(&fixture, "[\"warn\", { \"exclude\": [\"package/ssg/\"] }]");
std::fs::write(repo.join("pkg/README.md"), RELATIVE).expect("relative link");
std::fs::create_dir_all(repo.join("package/ssg")).expect("excluded tree");
std::fs::write(
repo.join("package/ssg/a.md"),
b"\n",
)
.expect("excluded");
std::fs::write(
repo.join("pkg/done.md"),
format!("\n").as_bytes(),
)
.expect("already rewritten");
let added: Observed = run_with(&fixture, &path, &repo, &["add", "--", "pkg/README.md"]);
assert_eq!(
(
added.code,
String::from_utf8_lossy(&added.stderr).into_owned()
),
(
Some(0),
autofix(0, "pre-forward", "warn", "pkg/README.md", "none")
)
);
assert_eq!(staged(&fixture, repo.as_path(), "pkg/README.md"), RELATIVE);
let quiet: Observed = run_with(
&fixture,
&path,
&repo,
&["add", "--", "package/ssg/a.md", "pkg/done.md"],
);
assert_eq!((quiet.code, quiet.stderr), (Some(0), Vec::new()));
let checked: Observed = run_with(&fixture, &path, &repo, &["cli-git", "check", "--all"]);
assert_eq!(
(
checked.code,
String::from_utf8_lossy(&checked.stdout).into_owned()
),
(
Some(0),
autofix(0, "direct-check", "warn", "pkg/README.md", "none")
)
);
let index_before: Vec<u8> = std::fs::read(repo.join(".git/index")).expect("index");
let fixed: Observed = run_with(&fixture, &path, &repo, &["cli-git", "fix", "--all"]);
assert_eq!(
(
fixed.code,
String::from_utf8_lossy(&fixed.stdout).into_owned()
),
(
Some(0),
String::from(
"{\"schemaVersion\":1,\"sequence\":0,\"type\":\"fix-summary\",\"trigger\":\"direct-fix\",\
\"passes\":1,\"changedPaths\":[\"pkg/README.md\"]}\n"
)
)
);
assert_eq!(
std::fs::read(repo.join("pkg/README.md")).expect("rewritten"),
format!("# T\n\n\n").into_bytes()
);
assert_eq!(
std::fs::read(repo.join(".git/index")).expect("index"),
index_before
);
assert_eq!(staged(&fixture, repo.as_path(), "pkg/README.md"), RELATIVE);
std::fs::write(
repo.join("cli-git.config.jsonc"),
"{ \"policies\": { \"final-newline\": \"off\", \"markdown/autofix\": \"error\" } }\n",
)
.expect("configuration");
std::fs::write(repo.join("pkg/new.md"), RELATIVE).expect("new file");
let stopped: Observed = run_with(&fixture, &path, &repo, &["add", "--", "pkg/new.md"]);
assert_eq!(
(
stopped.code,
String::from_utf8_lossy(&stopped.stderr).into_owned()
),
(
Some(1),
autofix(0, "pre-forward", "error", "pkg/new.md", "none")
)
);
assert!(
porcelain(&fixture, repo.as_path()).contains("?? pkg/new.md\n"),
"{}",
porcelain(&fixture, repo.as_path())
);
remove(&fixture);
}
fn unchecked_without_path(trigger: &str, reason: &str) -> String {
let message: String = serde_json::to_string(&format!(
"{reason}. The Markdown candidate was not checked and nothing was changed; fix the cause, \
or pass --no-enforce-markdown/autofix to skip this policy for one command."
))
.expect("message as JSON");
return format!(
"{{\"schemaVersion\":1,\"sequence\":0,\"type\":\"engine-failure\",\"code\":\"policy-incomplete\",\
\"message\":{message},\
\"trigger\":\"{trigger}\",\"policyId\":\"markdown/autofix\"}}\n"
);
}
#[test]
fn an_exclude_that_does_not_compile_stops_only_markdown_work() {
let fixture: Fixture = fixture("markdown-unchecked");
let path: OsString = plain_path(&fixture);
let repo: PathBuf = lfs_repository(&fixture, "[\"warn\", { \"exclude\": [\"a{b\"] }]");
std::fs::write(repo.join("a.md"), RELATIVE).expect("markdown");
std::fs::write(repo.join("b.txt"), b"text\n").expect("text");
let reason: String = git_policy_cli::markdown_exclude::compile_exclude(&[String::from("a{b")])
.map(|_| { return String::new(); })
.expect_err("the pattern does not compile");
let stopped: Observed = run_with(&fixture, &path, &repo, &["add", "--", "a.md"]);
assert_eq!(
(
stopped.code,
String::from_utf8_lossy(&stopped.stderr).into_owned()
),
(Some(2), unchecked_without_path("pre-forward", reason.as_str()))
);
let checked: Observed = run_with(&fixture, &path, &repo, &["cli-git", "check", "--", "a.md"]);
assert_eq!(checked.code, Some(2));
assert_eq!(
String::from_utf8_lossy(&checked.stdout).into_owned(),
unchecked_without_path("direct-check", reason.as_str())
);
let text: Observed = run_with(&fixture, &path, &repo, &["add", "--", "b.txt"]);
assert_eq!((text.code, text.stderr), (Some(0), Vec::new()));
let escaped: Observed = run_with(
&fixture,
&path,
&repo,
&["add", "--no-enforce-markdown/autofix", "--", "a.md"],
);
assert_eq!((escaped.code, escaped.stderr), (Some(0), Vec::new()));
assert_eq!(porcelain(&fixture, repo.as_path()), "A a.md\nA b.txt\n");
remove(&fixture);
}
const COMMIT_BOUND_SECONDS: &str = "120";
fn head(fixture: &Fixture, repo: &Path) -> String {
return String::from_utf8_lossy(&git(fixture, repo, &["rev-parse", "HEAD"]).stdout)
.trim()
.to_owned();
}
#[test]
fn a_markdown_commit_is_checked_in_process_with_no_linter_on_the_path() {
let fixture: Fixture = fixture("markdown-post-commit");
let path: OsString = plain_path(&fixture);
let repo: PathBuf = lfs_repository(&fixture, "\"warn\"");
let base: String = head(&fixture, repo.as_path());
let stale: String = format!("\n", "b".repeat(64));
std::fs::write(repo.join("doc.md"), stale.as_bytes()).expect("markdown");
git(&fixture, repo.as_path(), &["add", "--", "doc.md"]);
let committed: Observed = observe(
bounded_for(
&fixture,
fixture.root.join("bin/git").as_path(),
path.as_os_str(),
COMMIT_BOUND_SECONDS,
)
.current_dir(&repo)
.args(["commit", "--quiet", "--message=doc", "--", "doc.md"]),
b"",
);
let stderr: String = String::from_utf8_lossy(&committed.stderr).into_owned();
assert_eq!(committed.code, Some(0), "{stderr}");
assert!(
stderr.contains(format!("markdown/autofix/{}", git_policy_cli::policy_markdown::MARKDOWN_VIOLATION_CODE).as_str()),
"{stderr}"
);
assert_ne!(head(&fixture, repo.as_path()), base);
remove(&fixture);
}
#[test]
fn a_commit_keeps_its_git_input_in_the_repository_scratch_directory() {
let fixture: Fixture = fixture("commit-scratch-location");
let mut path: OsString = fixture.root.join("bin").into_os_string();
path.push(":/usr/bin:/bin");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
std::fs::write(repo.join("base.txt"), b"base\n").expect("base");
git(&fixture, repo.as_path(), &["add", "--", "base.txt"]);
git(&fixture, repo.as_path(), &["commit", "--quiet", "--message=base"]);
let base: String = head(&fixture, repo.as_path());
std::fs::write(repo.join("doc.txt"), b"plain\n").expect("text");
git(&fixture, repo.as_path(), &["add", "--", "doc.txt"]);
let unusable: PathBuf = fixture.root.join("no-such-tmpdir");
let committed: Observed = observe(
bounded_for(
&fixture,
fixture.root.join("bin/git").as_path(),
path.as_os_str(),
COMMIT_BOUND_SECONDS,
)
.current_dir(&repo)
.env("TMPDIR", &unusable)
.args(["commit", "--quiet", "--message=doc", "--", "doc.txt"]),
b"",
);
assert_eq!(
(committed.code, String::from_utf8_lossy(&committed.stderr).into_owned()),
(Some(0), String::new())
);
assert_ne!(head(&fixture, repo.as_path()), base, "the commit landed");
let scratch: PathBuf = repo.join(".git").join("cli-git-scratch");
let left: Vec<OsString> = std::fs::read_dir(&scratch)
.expect("the repository scratch directory exists")
.map(|entry| { return entry.expect("entry").file_name(); })
.collect();
assert!(left.is_empty(), "nothing is left in {}: {left:?}", scratch.display());
assert!(!unusable.exists(), "nothing created the unusable temporary directory");
remove(&fixture);
}