monochromatic-git-policy-cli 0.5.31

Repository-owned Git policy wrapper: forwards Git commands and checks commits against configured policies.
Documentation
//! Controls for the service's Git queries against real disposable repositories and a Git that never answers.

use super::*;
use crate::test_support::{fixture, git, remove};
use std::path::Path;

/// Argument list from string literals.
fn argv(parts: &[&str]) -> Vec<OsString> {
    return parts.iter().map(OsString::from).collect();
}

/// Environment of the fixture Git, matching the test support fixtures.
fn fixture_environment() -> Vec<(OsString, OsString)> {
    let mut environment: Vec<(OsString, OsString)> = [
        ("PATH", "/usr/bin:/bin"),
        ("GIT_CONFIG_NOSYSTEM", "1"),
        ("GIT_CONFIG_GLOBAL", "/nonexistent-global-config"),
        ("GIT_AUTHOR_NAME", "Fixture"),
        ("GIT_AUTHOR_EMAIL", "fixture@example.invalid"),
        ("GIT_COMMITTER_NAME", "Fixture"),
        ("GIT_COMMITTER_EMAIL", "fixture@example.invalid"),
    ]
    .iter()
    .map(|(name, value)| {
        return (OsString::from(name), OsString::from(value));
    })
    .collect();
    // The per-user scratch directory of the child runners is made under a home beside the fixtures.
    environment.extend(crate::test_support::scratch_environment());
    return environment;
}

/// Coordinator Git for a fixture repository.
fn push_git(root: &Path) -> PushGit {
    return PushGit {
        real_git: PathBuf::from("/usr/bin/git"),
        cwd: root.to_path_buf(),
        environment: fixture_environment(),
        lfs: false,
    };
}

/// A new empty fixture repository on `main`.
fn repository(name: &str) -> PathBuf {
    let root = fixture(name);
    git(&root, &["init", "--quiet", "--initial-branch=main"]);
    commit(&root, "base");
    return root;
}

/// Land an empty commit with real Git and return its OID.
fn commit(root: &Path, message: &str) -> String {
    let message_argument = format!("--message={message}");
    git(
        root,
        &[
            "commit",
            "--quiet",
            "--allow-empty",
            message_argument.as_str(),
        ],
    );
    return head_of(root, "HEAD");
}

/// The OID a revision names in a fixture.
fn head_of(root: &Path, revision: &str) -> String {
    return String::from_utf8_lossy(&git(root, &["rev-parse", revision]).stdout)
        .trim()
        .to_owned();
}

/// A failed Git query names the arguments it ran in its error text.
#[test]
fn a_failed_query_names_its_arguments() {
    let root = repository("sf-query-failure");
    let failure = git_output(
        &push_git(&root),
        &argv(&["rev-parse", "--verify", "no-such-ref^{commit}"]),
    )
    .expect_err("an unknown ref fails");
    match &failure {
        SingleFlightError::Git {
            arguments, code, ..
        } => {
            assert_eq!(arguments, "rev-parse --verify no-such-ref^{commit}");
            assert_eq!(*code, Some(128));
        }
        other => panic!("expected a Git failure, got {other}"),
    }
    assert!(
        failure
            .to_string()
            .starts_with("git rev-parse --verify no-such-ref^{commit} exited with Some(128): "),
        "{failure}"
    );
    remove(&root);
}

/// A Git query that stalls is stopped at the query deadline and reported as a timeout,
///  so a stuck query cannot hold the commit.
#[test]
fn a_stalled_query_is_reported_instead_of_waited_for() {
    use std::os::unix::fs::PermissionsExt;
    let root = fixture("sf-stalled-query");
    let fake = root.join("fake-git");
    std::fs::write(&fake, "#!/bin/sh\nexec sleep 8\n").expect("fake git");
    std::fs::set_permissions(&fake, std::fs::Permissions::from_mode(0o755)).expect("mode");
    let mut git = push_git(&root);
    git.real_git = fake;
    git.environment.push((
        OsString::from(crate::bounded_push::PUSH_DEADLINE_VARIABLE),
        OsString::from("1"),
    ));
    let failure = git_output(&git, &argv(&["rev-parse", "HEAD"])).expect_err("stalls");
    assert!(
        matches!(&failure, SingleFlightError::TimedOut { arguments, seconds }
            if arguments == "rev-parse HEAD" && *seconds == 1),
        "{failure}"
    );
    remove(&root);
}