git-checks 4.2.1

Checks to run against a topic in git to enforce coding standards.
Documentation
// Copyright Kitware, Inc.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use derive_builder::Builder;
use git_checks_core::impl_prelude::*;

/// A check to allow robots to skip all checks.
///
/// Any actions performed by the identity are waved through and all other checks are ignored.
#[derive(Builder, Debug, Clone)]
#[builder(field(private))]
pub struct AllowRobot {
    /// The identity of the robot.
    ///
    /// Configuration: Required
    identity: Identity,
}

impl AllowRobot {
    /// Create a new builder.
    pub fn builder() -> AllowRobotBuilder {
        AllowRobotBuilder::default()
    }
}

impl BranchCheck for AllowRobot {
    fn name(&self) -> &str {
        "allow-robot"
    }

    fn check(&self, ctx: &CheckGitContext, _: &CommitId) -> Result<CheckResult, Box<dyn Error>> {
        let mut result = CheckResult::new();

        if *ctx.topic_owner() == self.identity {
            result.whitelist();
        }

        Ok(result)
    }
}

#[cfg(feature = "config")]
pub(crate) mod config {
    use git_checks_config::{register_checks, BranchCheckConfig, IntoCheck};
    use git_workarea::Identity;
    use serde::Deserialize;
    #[cfg(test)]
    use serde_json::json;

    #[cfg(test)]
    use crate::test;
    use crate::AllowRobot;

    /// Configuration for the `AllowRobot` check.
    ///
    /// The `name` and `email` fields are required and are both strings.
    ///
    /// This check is registered as a branch check with the name `"allow_robot"`.
    ///
    /// # Example
    ///
    /// ```json
    /// {
    ///     "name": "Robot Name",
    ///     "email": "robot@email.invalid"
    /// }
    /// ```
    #[derive(Deserialize, Debug)]
    pub struct AllowRobotConfig {
        name: String,
        email: String,
    }

    impl IntoCheck for AllowRobotConfig {
        type Check = AllowRobot;

        fn into_check(self) -> Self::Check {
            let identity = Identity::new(self.name, self.email);
            AllowRobot::builder()
                .identity(identity)
                .build()
                .expect("configuration mismatch for `AllowRobot`")
        }
    }

    register_checks! {
        AllowRobotConfig {
            "allow_robot" => BranchCheckConfig,
        },
    }

    #[test]
    fn test_allow_robot_config_empty() {
        let json = json!({});
        let err = serde_json::from_value::<AllowRobotConfig>(json).unwrap_err();
        test::check_missing_json_field(err, "name");
    }

    #[test]
    fn test_allow_robot_config_name_is_required() {
        let email = "robot@email.invalid";
        let json = json!({
            "email": email,
        });
        let err = serde_json::from_value::<AllowRobotConfig>(json).unwrap_err();
        test::check_missing_json_field(err, "name");
    }

    #[test]
    fn test_allow_robot_config_email_is_required() {
        let name = "Robot Name";
        let json = json!({
            "name": name,
        });
        let err = serde_json::from_value::<AllowRobotConfig>(json).unwrap_err();
        test::check_missing_json_field(err, "email");
    }

    #[test]
    fn test_allow_robot_config_minimum_fields() {
        let name = "Robot Name";
        let email = "robot@email.invalid";
        let json = json!({
            "name": name,
            "email": email,
        });
        let check: AllowRobotConfig = serde_json::from_value(json).unwrap();

        assert_eq!(check.name, name);
        assert_eq!(check.email, email);

        let check = check.into_check();

        assert_eq!(check.identity.name, name);
        assert_eq!(check.identity.email, email);
    }
}

#[cfg(test)]
mod tests {
    use git_checks_core::BranchCheck;
    use git_workarea::Identity;

    use crate::test::*;
    use crate::AllowRobot;

    const ALLOW_ROBOT_COMMIT: &str = "43adb8173eb6d7a39f98e1ec3351cf27414c9aa1";

    #[test]
    fn test_allow_robot_builder_default() {
        assert!(AllowRobot::builder().build().is_err());
    }

    #[test]
    fn test_allow_robot_builder_minimum_fields() {
        assert!(AllowRobot::builder()
            .identity(Identity::new("name", "email"))
            .build()
            .is_ok());
    }

    #[test]
    fn test_allow_robot_name_branch() {
        let check = AllowRobot::builder()
            .identity(Identity::new("name", "email"))
            .build()
            .unwrap();
        assert_eq!(BranchCheck::name(&check), "allow-robot");
    }

    #[test]
    fn test_allow_robot_allowed() {
        let check = AllowRobot::builder()
            .identity(Identity::new(
                "Rust Git Checks Tests",
                "rust-git-checks@example.com",
            ))
            .build()
            .unwrap();
        let result = run_branch_check("test_allow_robot_allowed", ALLOW_ROBOT_COMMIT, check);

        assert_eq!(result.warnings().len(), 0);
        assert_eq!(result.alerts().len(), 0);
        assert_eq!(result.errors().len(), 0);
        assert!(!result.temporary());
        assert!(result.allowed());
        assert!(result.pass());
    }

    #[test]
    fn test_allow_robot_not_robot_not_whitelisted() {
        let check = AllowRobot::builder()
            .identity(Identity::new(
                "Rust Git Checks 7ests",
                "rust-git-checks@example.com",
            ))
            .build()
            .unwrap();
        run_branch_check_ok(
            "test_allow_robot_not_robot_not_whitelisted",
            ALLOW_ROBOT_COMMIT,
            check,
        );
    }
}