checkleft 0.1.0-alpha.8

Experimental repository convention checker; API and behavior may change without notice
Documentation
#[derive(Clone)]
struct StaticFindingCheck {
    id: String,
    severity: Severity,
    remediation: Option<String>,
}

#[async_trait]
impl Check for StaticFindingCheck {
    fn id(&self) -> &str {
        &self.id
    }

    fn description(&self) -> &str {
        "emits one static finding"
    }

    fn configure(&self, _config: &toml::Value) -> Result<Arc<dyn ConfiguredCheck>> {
        Ok(Arc::new(self.clone()))
    }
}

#[async_trait]
impl ConfiguredCheck for StaticFindingCheck {
    async fn run(
        &self,
        changeset: &ChangeSet,
        _tree: &dyn SourceTree,
    ) -> Result<CheckResult> {
        let path = changeset
            .changed_files
            .first()
            .map(|changed| changed.path.clone())
            .unwrap_or_else(|| Path::new("unknown").to_path_buf());

        Ok(CheckResult {
            check_id: self.id().to_owned(),
            findings: vec![Finding {
                severity: self.severity,
                message: "synthetic policy finding".to_owned(),
                location: Some(Location {
                    path,
                    line: Some(1),
                    column: Some(1),
                }),
                remediation: self.remediation.clone(),
                suggested_fix: None,
            }],
        })
    }
}

#[tokio::test]
async fn runner_applies_policy_severity_override() {
    let temp = tempdir().expect("create temp dir");
    fs::create_dir_all(temp.path().join("docs")).expect("create dirs");
    fs::write(temp.path().join("docs/file.md"), "hello\n").expect("write file");
    fs::write(
        temp.path().join("CHECKS.toml"),
        r#"
[[checks]]
id = "policy-check"
check = "static-finding"

[checks.policy]
severity = "warning"
"#,
    )
    .expect("write config");

    let mut registry = CheckRegistry::new();
    registry
        .register(StaticFindingCheck {
            id: "static-finding".to_owned(),
            severity: Severity::Error,
            remediation: None,
        })
        .expect("register check");

    let runner = Runner::new(
        Arc::new(registry),
        Arc::new(ConfigResolver::new(temp.path()).expect("resolver")),
        Arc::new(LocalSourceTree::new(temp.path()).expect("tree")),
    );

    let results = runner
        .run_changeset(&ChangeSet::new(vec![ChangedFile {
            path: Path::new("docs/file.md").to_path_buf(),
            kind: ChangeKind::Modified,
            old_path: None,
        }]))
        .await
        .expect("run checks");

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].findings.len(), 1);
    assert_eq!(results[0].findings[0].severity, Severity::Warning);
}

#[tokio::test]
async fn runner_applies_policy_bypass_when_directive_exists() {
    let temp = tempdir().expect("create temp dir");
    fs::create_dir_all(temp.path().join("docs")).expect("create dirs");
    fs::write(temp.path().join("docs/file.md"), "hello\n").expect("write file");
    fs::write(
        temp.path().join("CHECKS.toml"),
        r#"
[[checks]]
id = "policy-check"
check = "static-finding"

[checks.policy]
allow_bypass = true
"#,
    )
    .expect("write config");

    let mut registry = CheckRegistry::new();
    registry
        .register(StaticFindingCheck {
            id: "static-finding".to_owned(),
            severity: Severity::Error,
            remediation: Some("fix me".to_owned()),
        })
        .expect("register check");

    let runner = Runner::new(
        Arc::new(registry),
        Arc::new(ConfigResolver::new(temp.path()).expect("resolver")),
        Arc::new(LocalSourceTree::new(temp.path()).expect("tree")),
    );

    let results = runner
        .run_changeset(
            &ChangeSet::new(vec![ChangedFile {
                path: Path::new("docs/file.md").to_path_buf(),
                kind: ChangeKind::Modified,
                old_path: None,
            }])
            .with_commit_description(Some(
                "BYPASS_POLICY_CHECK=Legitimate exception.".to_owned(),
            )),
        )
        .await
        .expect("run checks");

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].findings.len(), 1);
    assert_eq!(results[0].findings[0].severity, Severity::Warning);
    assert!(
        results[0].findings[0]
            .message
            .contains("BYPASS_POLICY_CHECK")
    );
}

#[tokio::test]
async fn runner_appends_bypass_guidance_when_enabled_and_missing() {
    let temp = tempdir().expect("create temp dir");
    fs::create_dir_all(temp.path().join("docs")).expect("create dirs");
    fs::write(temp.path().join("docs/file.md"), "hello\n").expect("write file");
    fs::write(
        temp.path().join("CHECKS.toml"),
        r#"
[[checks]]
id = "policy-check"
check = "static-finding"

[checks.policy]
allow_bypass = true
"#,
    )
    .expect("write config");

    let mut registry = CheckRegistry::new();
    registry
        .register(StaticFindingCheck {
            id: "static-finding".to_owned(),
            severity: Severity::Error,
            remediation: Some("fix me".to_owned()),
        })
        .expect("register check");

    let runner = Runner::new(
        Arc::new(registry),
        Arc::new(ConfigResolver::new(temp.path()).expect("resolver")),
        Arc::new(LocalSourceTree::new(temp.path()).expect("tree")),
    );

    let results = runner
        .run_changeset(&ChangeSet::new(vec![ChangedFile {
            path: Path::new("docs/file.md").to_path_buf(),
            kind: ChangeKind::Modified,
            old_path: None,
        }]))
        .await
        .expect("run checks");

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].findings.len(), 1);
    assert!(
        results[0].findings[0]
            .remediation
            .as_deref()
            .unwrap_or_default()
            .contains("Never use bypasses for convenience")
    );
}

#[tokio::test]
async fn runner_ignores_legacy_config_policy_fields() {
    let temp = tempdir().expect("create temp dir");
    fs::create_dir_all(temp.path().join("docs")).expect("create dirs");
    fs::write(temp.path().join("docs/file.md"), "hello\n").expect("write file");
    fs::write(
        temp.path().join("CHECKS.toml"),
        r#"
[[checks]]
id = "policy-check"
check = "static-finding"

[checks.config]
severity = "info"
allow_bypass = true
bypass_name = "BYPASS_LEGACY_POLICY_CHECK"
"#,
    )
    .expect("write config");

    let mut registry = CheckRegistry::new();
    registry
        .register(StaticFindingCheck {
            id: "static-finding".to_owned(),
            severity: Severity::Error,
            remediation: Some("fix me".to_owned()),
        })
        .expect("register check");

    let runner = Runner::new(
        Arc::new(registry),
        Arc::new(ConfigResolver::new(temp.path()).expect("resolver")),
        Arc::new(LocalSourceTree::new(temp.path()).expect("tree")),
    );

    let results_without_bypass = runner
        .run_changeset(&ChangeSet::new(vec![ChangedFile {
            path: Path::new("docs/file.md").to_path_buf(),
            kind: ChangeKind::Modified,
            old_path: None,
        }]))
        .await
        .expect("run checks");
    assert_eq!(results_without_bypass[0].findings[0].severity, Severity::Error);
    assert_eq!(
        results_without_bypass[0].findings[0]
            .remediation
            .as_deref(),
        Some("fix me")
    );

    let results_with_bypass = runner
        .run_changeset(
            &ChangeSet::new(vec![ChangedFile {
                path: Path::new("docs/file.md").to_path_buf(),
                kind: ChangeKind::Modified,
                old_path: None,
            }])
            .with_commit_description(Some(
                "BYPASS_LEGACY_POLICY_CHECK=Legacy fallback path.".to_owned(),
            )),
        )
        .await
        .expect("run checks");
    assert_eq!(results_with_bypass[0].findings[0].severity, Severity::Error);
    assert_eq!(
        results_with_bypass[0].findings[0].message,
        "synthetic policy finding"
    );
}

#[tokio::test]
async fn runner_does_not_apply_bypass_to_runner_generated_errors() {
    let temp = tempdir().expect("create temp dir");
    fs::create_dir_all(temp.path().join("docs")).expect("create dirs");
    fs::write(temp.path().join("docs/file.md"), "hello\n").expect("write file");
    fs::write(
        temp.path().join("CHECKS.toml"),
        r#"
[[checks]]
id = "missing-check"
check = "not-registered"

[checks.policy]
allow_bypass = true
"#,
    )
    .expect("write config");

    let runner = Runner::new(
        Arc::new(CheckRegistry::new()),
        Arc::new(ConfigResolver::new(temp.path()).expect("resolver")),
        Arc::new(LocalSourceTree::new(temp.path()).expect("tree")),
    );

    let results = runner
        .run_changeset(
            &ChangeSet::new(vec![ChangedFile {
                path: Path::new("docs/file.md").to_path_buf(),
                kind: ChangeKind::Modified,
                old_path: None,
            }])
            .with_commit_description(Some(
                "BYPASS_MISSING_CHECK=This should not bypass runner-generated errors.".to_owned(),
            )),
        )
        .await
        .expect("run checks");

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].findings[0].severity, Severity::Error);
    assert!(
        results[0].findings[0]
            .message
            .contains("unknown implementation")
    );
}