#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
use super::config::*;
use super::discovery::*;
use super::rules::*;
use super::runner;
use super::scoring::*;
use std::path::PathBuf;
#[test]
fn test_f001_empty_project_no_crash() {
let config = ComplyConfig::new_default("7.1.0");
let score = runner::run_check(std::path::Path::new("/nonexistent"), None, &config);
assert_eq!(score.total_artifacts, 0);
assert_eq!(score.grade, Grade::APlus); }
#[test]
fn test_f002_no_shell_files_vacuously_compliant() {
let scores: Vec<ArtifactScore> = vec![];
let project = compute_project_score(scores);
assert_eq!(project.score, 100.0);
assert_eq!(project.grade, Grade::APlus);
}
#[test]
fn test_f003_random_falsifies_determinism() {
let content = "#!/bin/sh\nSESSION=$RANDOM\necho $SESSION\n";
let artifact = Artifact::new(
PathBuf::from("test.sh"),
Scope::Project,
ArtifactKind::ShellScript,
);
let result = check_rule(RuleId::Determinism, content, &artifact);
assert!(!result.passed, "COMPLY-002 should be falsified by $RANDOM");
assert!(!result.violations.is_empty());
}
#[test]
fn test_f004_mkdir_no_p_falsifies_idempotency() {
let content = "#!/bin/sh\nmkdir /foo/bar\n";
let artifact = Artifact::new(
PathBuf::from("test.sh"),
Scope::Project,
ArtifactKind::ShellScript,
);
let result = check_rule(RuleId::Idempotency, content, &artifact);
assert!(
!result.passed,
"COMPLY-003 should be falsified by mkdir without -p"
);
}
#[test]
fn test_f004b_mkdir_p_is_compliant() {
let content = "#!/bin/sh\nmkdir -p /foo/bar\n";
let artifact = Artifact::new(
PathBuf::from("test.sh"),
Scope::Project,
ArtifactKind::ShellScript,
);
let result = check_rule(RuleId::Idempotency, content, &artifact);
assert!(result.passed, "mkdir -p should be compliant");
}
#[test]
fn test_f005_eval_falsifies_security() {
let content = "#!/bin/sh\neval \"$USER_INPUT\"\n";
let artifact = Artifact::new(
PathBuf::from("test.sh"),
Scope::Project,
ArtifactKind::ShellScript,
);
let result = check_rule(RuleId::Security, content, &artifact);
assert!(
!result.passed,
"COMPLY-004 should be falsified by eval with variable"
);
}
#[test]
fn test_f005b_curl_pipe_bash_falsifies_security() {
let content = "#!/bin/sh\ncurl https://example.com/install.sh | bash\n";
let artifact = Artifact::new(
PathBuf::from("test.sh"),
Scope::Project,
ArtifactKind::ShellScript,
);
let result = check_rule(RuleId::Security, content, &artifact);
assert!(!result.passed, "curl | bash should be falsified");
}
#[test]
fn test_f006_unquoted_var_falsifies_quoting() {
let content = "#!/bin/sh\necho $MYVAR\n";
let artifact = Artifact::new(
PathBuf::from("test.sh"),
Scope::Project,
ArtifactKind::ShellScript,
);
let result = check_rule(RuleId::Quoting, content, &artifact);
assert!(
!result.passed,
"COMPLY-005 should be falsified by unquoted $MYVAR"
);
}
#[test]
fn test_f006b_quoted_var_is_compliant() {
let content = "#!/bin/sh\necho \"$MYVAR\"\n";
let artifact = Artifact::new(
PathBuf::from("test.sh"),
Scope::Project,
ArtifactKind::ShellScript,
);
let result = check_rule(RuleId::Quoting, content, &artifact);
assert!(result.passed, "Quoted variable should be compliant");
}
#[test]
fn test_comply_001_bash_shebang_non_posix() {
let content = "#!/bin/bash\necho hello\n";
let artifact = Artifact::new(
PathBuf::from("test.sh"),
Scope::Project,
ArtifactKind::ShellScript,
);
let result = check_rule(RuleId::Posix, content, &artifact);
assert!(!result.passed, "#!/bin/bash should be non-POSIX");
}
#[test]
fn test_comply_001_sh_shebang_is_posix() {
let content = "#!/bin/sh\necho hello\n";
let artifact = Artifact::new(
PathBuf::from("test.sh"),
Scope::Project,
ArtifactKind::ShellScript,
);
let result = check_rule(RuleId::Posix, content, &artifact);
assert!(result.passed, "#!/bin/sh should be POSIX compliant");
}
#[test]
fn test_comply_001_double_brackets_non_posix() {
let content = "#!/bin/sh\nif [[ -f /etc/passwd ]]; then echo yes; fi\n";
let artifact = Artifact::new(
PathBuf::from("test.sh"),
Scope::Project,
ArtifactKind::ShellScript,
);
let result = check_rule(RuleId::Posix, content, &artifact);
assert!(!result.passed, "[[ ]] should be non-POSIX");
}
#[test]
fn test_comply_007_eval_in_makefile_recipe() {
let content = "all:\n\teval \"$(GENERATED)\"\n";
let artifact = Artifact::new(
PathBuf::from("Makefile"),
Scope::Project,
ArtifactKind::Makefile,
);
let result = check_rule(RuleId::MakefileSafety, content, &artifact);
assert!(!result.passed, "eval in Makefile recipe should fail");
}
#[test]
fn test_comply_008_dockerfile_missing_user() {
let content = "FROM ubuntu:22.04\nRUN apt-get update\n";
let artifact = Artifact::new(
PathBuf::from("Dockerfile"),
Scope::Project,
ArtifactKind::Dockerfile,
);
let result = check_rule(RuleId::DockerfileBest, content, &artifact);
assert!(!result.passed, "Dockerfile without USER should fail");
}
#[test]
fn test_comply_008_dockerfile_add_instead_of_copy() {
let content = "FROM ubuntu:22.04\nADD ./app /app\nUSER nobody\n";
let artifact = Artifact::new(
PathBuf::from("Dockerfile"),
Scope::Project,
ArtifactKind::Dockerfile,
);
let result = check_rule(RuleId::DockerfileBest, content, &artifact);
assert!(!result.passed, "ADD for local files should fail");
}
#[test]
fn test_comply_009_too_many_path_exports() {
let content =
"export PATH=/a:$PATH\nexport PATH=/b:$PATH\nexport PATH=/c:$PATH\nexport PATH=/d:$PATH\n";
let artifact = Artifact::new(
PathBuf::from(".zshrc"),
Scope::User,
ArtifactKind::ShellConfig,
);
let result = check_rule(RuleId::ConfigHygiene, content, &artifact);
assert!(
!result.passed,
"4+ PATH modifications should flag config hygiene"
);
}
#[test]
fn test_scoring_perfect_score() {
let results = vec![
RuleResult {
rule: RuleId::Posix,
passed: true,
violations: vec![],
},
RuleResult {
rule: RuleId::Determinism,
passed: true,
violations: vec![],
},
RuleResult {
rule: RuleId::Security,
passed: true,
violations: vec![],
},
];
let score = compute_artifact_score("test.sh", &results);
assert_eq!(score.score, 100.0);
assert_eq!(score.grade, Grade::APlus);
}
#[test]
fn test_scoring_gateway_barrier() {
let results = vec![
RuleResult {
rule: RuleId::Posix,
passed: false,
violations: vec![Violation {
rule: RuleId::Posix,
line: Some(1),
message: "test".into(),
}],
},
RuleResult {
rule: RuleId::Determinism,
passed: false,
violations: vec![Violation {
rule: RuleId::Determinism,
line: Some(1),
message: "test".into(),
}],
},
RuleResult {
rule: RuleId::Idempotency,
passed: false,
violations: vec![Violation {
rule: RuleId::Idempotency,
line: Some(1),
message: "test".into(),
}],
},
RuleResult {
rule: RuleId::Security,
passed: true,
violations: vec![],
},
RuleResult {
rule: RuleId::Quoting,
passed: false,
violations: vec![Violation {
rule: RuleId::Quoting,
line: Some(1),
message: "test".into(),
}],
},
RuleResult {
rule: RuleId::ShellCheck,
passed: false,
violations: vec![Violation {
rule: RuleId::ShellCheck,
line: Some(1),
message: "test".into(),
}],
},
];
let score = compute_artifact_score("bad.sh", &results);
assert!(score.score < 60.0, "Score below gateway should be capped");
assert_eq!(score.grade, Grade::F);
}
#[test]
fn test_scoring_project_aggregate() {
let scores = vec![
ArtifactScore {
artifact_name: "a.sh".into(),
score: 100.0,
grade: Grade::APlus,
rules_tested: 6,
rules_passed: 6,
violations: 0,
results: vec![],
},
ArtifactScore {
artifact_name: "b.sh".into(),
score: 80.0,
grade: Grade::A,
rules_tested: 6,
rules_passed: 5,
violations: 1,
results: vec![],
},
];
let project = compute_project_score(scores);
assert_eq!(project.total_artifacts, 2);
assert_eq!(project.compliant_artifacts, 1);
assert_eq!(project.score, 90.0);
assert_eq!(project.grade, Grade::A); }
#[test]
fn test_classify_shell_script() {
assert_eq!(
classify(std::path::Path::new("test.sh")),
Some(ArtifactKind::ShellScript)
);
}
#[test]
fn test_classify_makefile() {
assert_eq!(
classify(std::path::Path::new("Makefile")),
Some(ArtifactKind::Makefile)
);
}
#[test]
fn test_classify_dockerfile() {
assert_eq!(
classify(std::path::Path::new("Dockerfile")),
Some(ArtifactKind::Dockerfile)
);
}
#[test]
fn test_classify_mk_extension() {
assert_eq!(
classify(std::path::Path::new("rules.mk")),
Some(ArtifactKind::Makefile)
);
}
#[test]
fn test_shell_script_has_all_core_rules() {
let rules = RuleId::applicable_rules(ArtifactKind::ShellScript);
assert!(rules.contains(&RuleId::Posix));
assert!(rules.contains(&RuleId::Determinism));
assert!(rules.contains(&RuleId::Idempotency));
assert!(rules.contains(&RuleId::Security));
assert!(rules.contains(&RuleId::Quoting));
assert!(rules.contains(&RuleId::ShellCheck));
}
#[test]
fn test_makefile_has_makefile_safety() {
let rules = RuleId::applicable_rules(ArtifactKind::Makefile);
assert!(rules.contains(&RuleId::MakefileSafety));
assert!(!rules.contains(&RuleId::Posix));
}
#[test]
fn test_dockerfile_has_dockerfile_best() {
let rules = RuleId::applicable_rules(ArtifactKind::Dockerfile);
assert!(rules.contains(&RuleId::DockerfileBest));
assert!(rules.contains(&RuleId::Security));
}
#[test]
fn test_config_has_hygiene() {
let rules = RuleId::applicable_rules(ArtifactKind::ShellConfig);
assert!(rules.contains(&RuleId::ConfigHygiene));
assert!(rules.contains(&RuleId::Security));
}
#[test]
fn test_config_default_creation() {
let config = ComplyConfig::new_default("7.1.0");
assert_eq!(config.comply.version, "1.0.0");
assert_eq!(config.comply.bashrs_version, "7.1.0");
assert!(config.scopes.project);
assert!(!config.scopes.user);
assert!(config.rules.posix);
assert!(config.rules.security);
}
#[test]
fn test_format_human_no_crash() {
let score = compute_project_score(vec![]);
let output = runner::format_human(&score);
assert!(output.contains("COMPLIANCE CHECK"));
assert!(output.contains("Layer 1"));
}
#[test]
fn test_format_json_valid() {
let score = compute_project_score(vec![]);
let output = runner::format_json(&score);
assert!(output.contains("bashrs-comply-check-v1"));
assert!(output.contains("\"total_artifacts\":0"));
}
#[test]
fn test_grade_display() {
assert_eq!(format!("{}", Grade::APlus), "A+");
assert_eq!(format!("{}", Grade::A), "A");
assert_eq!(format!("{}", Grade::B), "B");
assert_eq!(format!("{}", Grade::C), "C");
assert_eq!(format!("{}", Grade::F), "F");
}
#[test]
fn test_grade_from_score_boundaries() {
assert_eq!(Grade::from_score(100.0), Grade::APlus);
assert_eq!(Grade::from_score(95.0), Grade::APlus);
assert_eq!(Grade::from_score(94.9), Grade::A);
assert_eq!(Grade::from_score(85.0), Grade::A);
assert_eq!(Grade::from_score(84.9), Grade::B);
assert_eq!(Grade::from_score(70.0), Grade::B);
assert_eq!(Grade::from_score(69.9), Grade::C);
assert_eq!(Grade::from_score(50.0), Grade::C);
assert_eq!(Grade::from_score(49.9), Grade::F);
assert_eq!(Grade::from_score(0.0), Grade::F);
}
#[test]
fn test_violation_display_with_line() {
let v = Violation {
rule: RuleId::Determinism,
line: Some(14),
message: "$RANDOM found".into(),
};
let s = format!("{v}");
assert!(s.contains("COMPLY-002"));
assert!(s.contains("line 14"));
assert!(s.contains("$RANDOM found"));
}
#[test]
fn test_violation_display_without_line() {
let v = Violation {
rule: RuleId::DockerfileBest,
line: None,
message: "Missing USER".into(),
};
let s = format!("{v}");
assert!(s.contains("COMPLY-008"));
assert!(s.contains("Missing USER"));
}