rusty-hook 0.11.2

git hook utility
Documentation
use super::*;

const EXP_NO_CONFIG_FILE_FOUND_ERROR_CODE: i32 = 3;

const EXP_VERSION: &str = env!("CARGO_PKG_VERSION");
const EXP_HOOK_FILE_TEMPLATE: &str = include_str!("hook_files/hook_script.sh");
const EXP_HOOK_CLI_SCRIPT_FILE_TEMPLATE: &str = include_str!("hook_files/cli.sh");
const EXP_HOOK_SEMVER_SCRIPT_FILE_TEMPLATE: &str = include_str!("hook_files/semver.sh");

const EXP_HOOK_NAMES: [&str; 19] = [
    "applypatch-msg",
    "pre-applypatch",
    "post-applypatch",
    "pre-commit",
    "prepare-commit-msg",
    "commit-msg",
    "post-commit",
    "pre-rebase",
    "post-checkout",
    "post-merge",
    "pre-push",
    "pre-receive",
    "update",
    "post-receive",
    "post-update",
    "push-to-checkout",
    "pre-auto-gc",
    "post-rewrite",
    "sendemail-validate",
];

const EXP_CLI_SCRIPT_NAME: &str = "cli.sh";
const EXP_SEMVER_SCRIPT_NAME: &str = "semver.sh";

const EXP_MINIMUM_CLI_MAJOR_VERSION: i32 = 0;
const EXP_MINIMUM_CLI_MINOR_VERSION: i32 = 11;
const EXP_MINIMUM_CLI_PATCH_VERSION: i32 = 0;
const EXP_MINIMUM_CLI_VERSION_ALLOW_PRERELEASE: bool = false;

const EXP_HOOK_CREATION_ERROR: &str =
    "Fatal error encountered while trying to create git hook files";

fn get_expected_hook_file_contents() -> String {
    String::from(EXP_HOOK_FILE_TEMPLATE).replace("{{VERSION}}", EXP_VERSION)
}

fn get_expected_cli_script_file_contents() -> String {
    let exit_code = &EXP_NO_CONFIG_FILE_FOUND_ERROR_CODE.to_string();
    let minimum_major = &EXP_MINIMUM_CLI_MAJOR_VERSION.to_string();
    let minimum_minor = &EXP_MINIMUM_CLI_MINOR_VERSION.to_string();
    let minimum_patch = &EXP_MINIMUM_CLI_PATCH_VERSION.to_string();
    let minimum_allow_pre = &EXP_MINIMUM_CLI_VERSION_ALLOW_PRERELEASE.to_string();
    String::from(EXP_HOOK_CLI_SCRIPT_FILE_TEMPLATE)
        .replace("{{VERSION}}", VERSION)
        .replace("\n# shellcheck disable=SC2170,SC1083", "")
        .replace("{{NO_CONFIG_FILE_EXIT_CODE}}", exit_code)
        .replace("{{MINIMUM_MAJOR}}", minimum_major)
        .replace("{{MINIMUM_MINOR}}", minimum_minor)
        .replace("{{MINIMUM_PATCH}}", minimum_patch)
        .replace("{{MINIMUM_ALLOW_PRE}}", minimum_allow_pre)
}

fn get_expected_semver_script_file_contents() -> String {
    String::from(EXP_HOOK_SEMVER_SCRIPT_FILE_TEMPLATE).replace("{{VERSION}}", VERSION)
}

#[cfg(test)]
mod constants_tests {
    use super::*;

    #[test]
    fn should_use_correct_hook_names() {
        for (&exp_hook, &act_hook) in EXP_HOOK_NAMES.iter().zip(HOOK_NAMES.iter()) {
            assert_eq!(exp_hook, act_hook);
        }
    }
}

#[cfg(test)]
mod get_hook_file_contents_tests {
    use super::*;

    #[test]
    fn provides_correct_hook_file_contents() {
        let exp = get_expected_hook_file_contents();
        assert_eq!(get_hook_file_contents(), exp);
    }
}

#[cfg(test)]
mod get_cli_script_file_contents_tests {
    use super::*;

    #[test]
    fn provides_correct_cli_script_file_contents() {
        let exp = get_expected_cli_script_file_contents();
        assert_eq!(get_cli_script_file_contents(), exp);
    }
}

#[cfg(test)]
mod get_semver_script_file_contents_tests {
    use super::*;

    #[test]
    fn provides_correct_semver_script_file_contents() {
        let exp = get_expected_semver_script_file_contents();
        assert_eq!(get_semver_script_file_contents(), exp);
    }
}

#[cfg(test)]
mod create_hook_files_tests {
    use super::*;

    #[test]
    fn errors_when_hook_write_fails() {
        let write_file = |path: &str, _contents: &str, _make_executable: bool| {
            let file_name = &&path[(path.rfind('/').unwrap() + 1)..];
            match *file_name {
                EXP_CLI_SCRIPT_NAME => Ok(()),
                EXP_SEMVER_SCRIPT_NAME => Ok(()),
                _ => Err(String::from("")),
            }
        };
        let result = create_hook_files(write_file, "", "");
        assert_eq!(result, Err(String::from(EXP_HOOK_CREATION_ERROR)));
    }

    #[test]
    fn errors_when_cli_script_write_fails() {
        let write_file = |path: &str, _contents: &str, _make_executable: bool| {
            let file_name = &&path[(path.rfind('/').unwrap() + 1)..];
            match *file_name {
                EXP_CLI_SCRIPT_NAME => Err(String::from("")),
                EXP_SEMVER_SCRIPT_NAME => Ok(()),
                _ => Ok(()),
            }
        };
        let result = create_hook_files(write_file, "", "");
        assert_eq!(result, Err(String::from(EXP_HOOK_CREATION_ERROR)));
    }

    #[test]
    fn errors_when_semver_script_write_fails() {
        let write_file = |path: &str, _contents: &str, _make_executable: bool| {
            let file_name = &&path[(path.rfind('/').unwrap() + 1)..];
            match *file_name {
                EXP_CLI_SCRIPT_NAME => Ok(()),
                EXP_SEMVER_SCRIPT_NAME => Err(String::from("")),
                _ => Ok(()),
            }
        };
        let result = create_hook_files(write_file, "", "");
        assert_eq!(result, Err(String::from(EXP_HOOK_CREATION_ERROR)));
    }

    #[test]
    fn creates_all_hooks() {
        let root_dir = "/usr/repos/foo";
        let git_hooks = ".git/hooks";
        let exp_contents = get_expected_hook_file_contents();
        let exp_cli_contents = get_expected_cli_script_file_contents();
        let exp_cli_path = &format!("{}/{}/{}", root_dir, git_hooks, EXP_CLI_SCRIPT_NAME);
        let exp_semver_path = &format!("{}/{}/{}", root_dir, git_hooks, EXP_SEMVER_SCRIPT_NAME);
        let exp_semver_contents = get_expected_semver_script_file_contents();
        let write_file = |path: &str, contents: &str, make_executable: bool| {
            let file_name = &&path[(path.rfind('/').unwrap() + 1)..];
            match *file_name {
                EXP_CLI_SCRIPT_NAME => {
                    assert_eq!(exp_cli_path, path);
                    assert_eq!(exp_cli_contents, contents);
                }
                EXP_SEMVER_SCRIPT_NAME => {
                    assert_eq!(exp_semver_path, path);
                    assert_eq!(exp_semver_contents, contents);
                }
                _ => {
                    let exp_hook = EXP_HOOK_NAMES.iter().find(|&n| n == file_name).unwrap();
                    let exp_path = &format!("{}/{}/{}", root_dir, git_hooks, exp_hook);
                    assert_eq!(exp_path, path);
                    assert_eq!(exp_contents, contents);
                }
            }
            assert_eq!(true, make_executable);
            Ok(())
        };
        let result = create_hook_files(write_file, root_dir, git_hooks);
        assert_eq!(result, Ok(()));
    }
}