use crate::core::shell_escape::sh_squote;
const FLAG: &str = "__fj_diverged";
pub fn assert_that(condition: &str, converged_marker: &str, divergent_marker: &str) -> String {
format!(
"if {condition}; then echo {}; else echo {}; {FLAG}=1; fi",
sh_squote(converged_marker),
sh_squote(divergent_marker)
)
}
pub fn assert_block(condition: &str, on_converged: &str, on_divergent: &str) -> String {
format!("if {condition}; then\n {on_converged}\nelse\n {on_divergent}\n {FLAG}=1\nfi")
}
pub fn always_diverged(marker: &str) -> String {
format!("echo {}; {FLAG}=1", sh_squote(marker))
}
pub fn check_script_from(assertions: &[String]) -> String {
if assertions.is_empty() {
return "echo 'forjar=no-assertions'\nexit 1".to_string();
}
format!("{FLAG}=0\n{}\nexit \"${FLAG}\"", assertions.join("\n"))
}
pub fn single(condition: &str, converged_marker: &str, divergent_marker: &str) -> String {
check_script_from(&[assert_that(condition, converged_marker, divergent_marker)])
}
#[cfg(test)]
mod tests {
use super::*;
fn run(script: &str) -> i32 {
std::process::Command::new("sh")
.arg("-c")
.arg(script)
.output()
.expect("sh")
.status
.code()
.unwrap_or(-1)
}
fn stdout(script: &str) -> String {
String::from_utf8_lossy(
&std::process::Command::new("sh")
.arg("-c")
.arg(script)
.output()
.expect("sh")
.stdout,
)
.to_string()
}
#[test]
fn converged_assertion_exits_zero_and_prints_its_marker() {
let s = single("true", "exists:x", "missing:x");
assert_eq!(run(&s), 0);
assert!(stdout(&s).contains("exists:x"));
}
#[test]
fn divergent_assertion_exits_nonzero_and_prints_its_marker() {
let s = single("false", "exists:x", "missing:x");
assert_ne!(run(&s), 0, "this is the whole point of the module");
assert!(stdout(&s).contains("missing:x"));
}
#[test]
fn every_assertion_reports_even_after_one_fails() {
let s = check_script_from(&[
assert_that("false", "installed:a", "missing:a"),
assert_that("false", "installed:b", "missing:b"),
]);
let out = stdout(&s);
assert!(out.contains("missing:a"), "{out}");
assert!(out.contains("missing:b"), "{out}");
assert_ne!(run(&s), 0);
}
#[test]
fn one_failure_among_many_still_fails_the_whole_check() {
let s = check_script_from(&[
assert_that("true", "ok:a", "bad:a"),
assert_that("false", "ok:b", "bad:b"),
assert_that("true", "ok:c", "bad:c"),
]);
assert_ne!(run(&s), 0);
}
#[test]
fn all_converged_exits_zero() {
let s = check_script_from(&[
assert_that("true", "ok:a", "bad:a"),
assert_that("true", "ok:b", "bad:b"),
]);
assert_eq!(run(&s), 0);
}
#[test]
fn empty_assertion_set_is_a_failure_not_a_pass() {
assert_ne!(run(&check_script_from(&[])), 0);
}
#[test]
fn always_diverged_fails() {
let s = check_script_from(&[always_diverged("unsupported:provider")]);
assert_ne!(run(&s), 0);
assert!(stdout(&s).contains("unsupported:provider"));
}
#[test]
fn assert_block_raises_the_flag_on_the_divergent_branch() {
let s = check_script_from(&[assert_block("false", "echo 'match:m'", "echo 'mismatch:m'")]);
assert_ne!(run(&s), 0);
assert!(stdout(&s).contains("mismatch:m"));
let s = check_script_from(&[assert_block("true", "echo 'match:m'", "echo 'mismatch:m'")]);
assert_eq!(run(&s), 0);
}
#[test]
fn markers_containing_shell_metacharacters_are_quoted() {
let s = single(
"true",
"installed:'; touch /tmp/forjar-pwn-verdict; '",
"missing:x",
);
assert_eq!(run(&s), 0);
assert!(
!std::path::Path::new("/tmp/forjar-pwn-verdict").exists(),
"marker escaped its quoting and executed a command"
);
}
#[test]
fn works_under_set_u() {
let s = format!("set -eu\n{}", single("true", "ok:x", "bad:x"));
assert_eq!(run(&s), 0);
let s = format!("set -eu\n{}", single("false", "ok:x", "bad:x"));
assert_ne!(run(&s), 0);
}
}