use std::path::{Path, PathBuf};
use svccat::drift;
use svccat::manifest::Manifest;
#[test]
fn test_rule_expression_parsing() {
let rule = svccat::rules::Rule {
id: "test_rule".to_string(),
description: "Test rule".to_string(),
expression: "name matches ^service-".to_string(),
severity: "error".to_string(),
base: None,
};
let result = svccat::rules::RuleEngine::compile(&[rule]);
assert!(result.is_ok(), "Valid rule should compile successfully");
}
#[test]
fn test_invalid_rule_expression() {
let rule = svccat::rules::Rule {
id: "bad_rule".to_string(),
description: "Invalid rule".to_string(),
expression: "invalid expression syntax".to_string(),
severity: "error".to_string(),
base: None,
};
let result = svccat::rules::RuleEngine::compile(&[rule]);
assert!(result.is_err(), "Invalid rule should fail to compile");
}
#[test]
fn test_rule_evaluation_with_manifest() {
let manifest_path = PathBuf::from("tests/fixtures/rules/manifest-basic.yaml");
let manifest = Manifest::load(&manifest_path).expect("Failed to load test manifest");
assert!(
!manifest.policy.rules.is_empty(),
"Manifest should have rules defined"
);
assert_eq!(manifest.policy.rules.len(), 3, "Should have 3 custom rules");
let engine = svccat::rules::RuleEngine::compile(&manifest.policy.rules)
.expect("Failed to compile rules");
for svc in &manifest.services {
let violations = engine.evaluate(svc);
match svc.name.as_str() {
"service-api" => {
assert!(
violations.iter().all(|v| v.rule_id != "naming_convention"),
"service-api should pass naming convention"
);
assert!(
violations.iter().all(|v| v.rule_id != "required_team"),
"service-api should have team"
);
}
"invalid-service" => {
let naming_violations: Vec<_> = violations
.iter()
.filter(|v| v.rule_id == "naming_convention")
.collect();
assert!(
!naming_violations.is_empty(),
"invalid-service should fail naming convention"
);
let team_violations: Vec<_> = violations
.iter()
.filter(|v| v.rule_id == "required_team")
.collect();
assert!(
!team_violations.is_empty(),
"invalid-service should have no team"
);
}
"service-auth" => {
assert!(
violations.iter().all(|v| v.rule_id != "naming_convention"),
"service-auth should pass naming convention"
);
let team_violations: Vec<_> = violations
.iter()
.filter(|v| v.rule_id == "required_team")
.collect();
assert!(
!team_violations.is_empty(),
"service-auth should have no team"
);
}
_ => {}
}
}
}
#[test]
fn test_rule_violations_in_drift_report() {
let manifest_path = PathBuf::from("tests/fixtures/rules/manifest-basic.yaml");
let manifest = Manifest::load(&manifest_path).expect("Failed to load test manifest");
let discovered = Vec::new();
let report = drift::analyze(&manifest, &discovered, Path::new("tests/fixtures/rules"));
let policy_violations: Vec<_> = report
.drifts
.iter()
.filter(|d| d.kind == drift::DriftKind::PolicyViolation)
.collect();
assert!(
!policy_violations.is_empty(),
"Should have policy violations from custom rules"
);
let invalid_violations: Vec<_> = policy_violations
.iter()
.filter(|d| d.service == "invalid-service")
.collect();
assert!(
invalid_violations.len() >= 2,
"invalid-service should have at least 2 violations"
);
}
#[test]
fn test_rule_severity_levels() {
let rules = vec![
svccat::rules::Rule {
id: "error_rule".to_string(),
description: "This is an error rule".to_string(),
expression: "name matches ^error-".to_string(),
severity: "error".to_string(),
base: None,
},
svccat::rules::Rule {
id: "warning_rule".to_string(),
description: "This is a warning rule".to_string(),
expression: "name matches ^warn-".to_string(),
severity: "warning".to_string(),
base: None,
},
];
let engine = svccat::rules::RuleEngine::compile(&rules).expect("Failed to compile rules");
let mut warn_service = svccat::manifest::ServiceEntry::default();
warn_service.name = "warn-test".to_string();
let violations = engine.evaluate(&warn_service);
let error_violations: Vec<_> = violations
.iter()
.filter(|v| v.severity == "error")
.collect();
assert_eq!(error_violations.len(), 1, "Should have 1 error violation");
let warning_violations: Vec<_> = violations
.iter()
.filter(|v| v.severity == "warning")
.collect();
assert_eq!(
warning_violations.len(),
0,
"Should have 0 warning violations"
);
}
#[test]
fn test_multiple_rules_on_service() {
let manifest_path = PathBuf::from("tests/fixtures/rules/manifest-basic.yaml");
let manifest = Manifest::load(&manifest_path).expect("Failed to load test manifest");
let engine = svccat::rules::RuleEngine::compile(&manifest.policy.rules)
.expect("Failed to compile rules");
let invalid_service = manifest
.services
.iter()
.find(|s| s.name == "invalid-service")
.expect("Should find invalid-service");
let violations = engine.evaluate(invalid_service);
let rule_ids: Vec<_> = violations.iter().map(|v| v.rule_id.as_str()).collect();
assert!(
rule_ids.contains(&"naming_convention"),
"Should have naming_convention violation"
);
assert!(
rule_ids.contains(&"required_team"),
"Should have required_team violation"
);
}
#[test]
fn test_platform_in_list_rule() {
let rules = vec![svccat::rules::Rule {
id: "approved_platforms".to_string(),
description: "Only approved platforms".to_string(),
expression: "platform in [Cloud Run, GKE, Heroku]".to_string(),
severity: "warning".to_string(),
base: None,
}];
let engine = svccat::rules::RuleEngine::compile(&rules).expect("Failed to compile rules");
let mut approved_service = svccat::manifest::ServiceEntry::default();
approved_service.name = "test-service".to_string();
approved_service.platform = Some("Cloud Run".to_string());
let violations = engine.evaluate(&approved_service);
assert_eq!(violations.len(), 0, "Approved platform should pass");
let mut unapproved_service = svccat::manifest::ServiceEntry::default();
unapproved_service.name = "test-service".to_string();
unapproved_service.platform = Some("Lambda".to_string());
let violations = engine.evaluate(&unapproved_service);
assert_eq!(violations.len(), 1, "Unapproved platform should fail");
}