mod common;
use std::fs::{create_dir, write};
use std::path::PathBuf;
use predicates::prelude::*;
use tempfile::{tempdir, TempDir};
use common::slopguard;
fn ai_fixture(ai_enabled: bool) -> (TempDir, PathBuf) {
let proj = tempdir().unwrap();
write(
proj.path().join("main.rs"),
"fn main() {\n // SAFETY: trust me\n let _x = 1;\n}\n",
)
.unwrap();
let rules_dir = proj.path().join("custom-rules");
create_dir(&rules_dir).unwrap();
write(
rules_dir.join("ai.yml"),
r#"
id: ai-safety-demo
language: rust
severity: warning
category: slop
message: "AI: SAFETY comment may be meaningless"
rule:
kind: line_comment
regex: 'SAFETY'
ai_check:
prompt: "Is this SAFETY comment meaningful?\n{{code}}"
"#,
)
.unwrap();
let config_path = proj.path().join("slopguard.toml");
let custom_dir = rules_dir.to_str().unwrap().replace('\\', "/");
write(
&config_path,
format!(
"[rulesets]\nslop = false\nsecurity = false\ncorrectness = false\n\n\
[rules]\ncustom_dirs = [\"{custom_dir}\"]\n\n\
[ai]\nenabled = {ai_enabled}\nprovider = \"api\"\nvendor = \"anthropic\"\n"
),
)
.unwrap();
(proj, config_path)
}
#[test]
fn scan_no_ai_makes_no_llm_call_and_is_silent() {
let (proj, config) = ai_fixture(true);
slopguard()
.args([
"scan",
"--no-ai",
"--no-colors",
"--config",
config.to_str().unwrap(),
proj.path().to_str().unwrap(),
])
.assert()
.success()
.stdout(predicate::str::contains("0 errors, 0 warnings"))
.stderr(predicate::str::is_empty());
}
#[test]
fn scan_without_provider_warns_and_skips_ai_rules() {
let (proj, config) = ai_fixture(false);
slopguard()
.args([
"scan",
"--no-colors",
"--config",
config.to_str().unwrap(),
proj.path().to_str().unwrap(),
])
.assert()
.success()
.stdout(predicate::str::contains("0 errors, 0 warnings"))
.stderr(predicate::str::contains("AI rules skipped"))
.stderr(predicate::str::contains("AI is disabled"));
}
#[test]
fn list_shows_ai_rules_with_type_column() {
slopguard()
.args(["list"])
.assert()
.success()
.stdout(predicate::str::contains("type"))
.stdout(predicate::str::contains("ai-safety-comment-validation"))
.stdout(
predicate::str::is_match(r"ai-safety-comment-validation\s+rust\s+\S+\s+security\s+ai")
.unwrap(),
);
}
#[test]
fn explain_ai_rule_shows_prompt_template() {
slopguard()
.args(["explain", "ai-safety-comment-validation"])
.assert()
.success()
.stdout(predicate::str::contains("prompt:"))
.stdout(predicate::str::contains("type: ai"));
}