use proptest::prelude::*;
use ricecoder_execution::{
ExecutionPlan, ExecutionRiskScorer, ExecutionStep, RiskLevel, StepAction, StepStatus,
};
use std::time::Duration;
use uuid::Uuid;
fn file_path_strategy() -> impl Strategy<Value = String> {
r"[a-zA-Z0-9_\-./]{1,50}\.rs".prop_map(|s| s.to_string())
}
fn execution_step_strategy() -> impl Strategy<Value = ExecutionStep> {
(
"[a-zA-Z0-9 ]{1,50}",
prop_oneof![
(file_path_strategy(), ".*").prop_map(|(path, diff)| StepAction::ModifyFile {
path,
diff: diff.to_string(),
}),
file_path_strategy().prop_map(|path| StepAction::DeleteFile { path }),
(file_path_strategy(), ".*").prop_map(|(path, content)| StepAction::CreateFile {
path,
content: content.to_string(),
}),
],
)
.prop_map(|(desc, action)| ExecutionStep {
id: Uuid::new_v4().to_string(),
description: desc.to_string(),
action,
risk_score: Default::default(),
dependencies: Vec::new(),
rollback_action: None,
status: StepStatus::Pending,
})
}
fn execution_plan_strategy() -> impl Strategy<Value = ExecutionPlan> {
prop::collection::vec(execution_step_strategy(), 0..10).prop_map(|steps| ExecutionPlan {
id: Uuid::new_v4().to_string(),
name: "Test Plan".to_string(),
steps,
risk_score: Default::default(),
estimated_duration: Duration::from_secs(0),
estimated_complexity: Default::default(),
requires_approval: false,
editable: true,
})
}
proptest! {
#[test]
fn prop_risk_score_consistency(plan in execution_plan_strategy()) {
let scorer = ExecutionRiskScorer::new();
let score1 = scorer.score_plan(&plan);
let score2 = scorer.score_plan(&plan);
let score3 = scorer.score_plan(&plan);
prop_assert_eq!(score1.score, score2.score, "Risk scores should be consistent");
prop_assert_eq!(score2.score, score3.score, "Risk scores should be consistent");
prop_assert_eq!(score1.level, score2.level, "Risk levels should be consistent");
prop_assert_eq!(score2.level, score3.level, "Risk levels should be consistent");
}
#[test]
fn prop_risk_score_non_negative(plan in execution_plan_strategy()) {
let scorer = ExecutionRiskScorer::new();
let score = scorer.score_plan(&plan);
prop_assert!(score.score >= 0.0, "Risk score should be non-negative");
}
#[test]
fn prop_risk_score_increases_with_files(
mut plan in execution_plan_strategy(),
additional_steps in prop::collection::vec(execution_step_strategy(), 1..5),
) {
let scorer = ExecutionRiskScorer::new();
let score_before = scorer.score_plan(&plan);
plan.steps.extend(additional_steps);
let score_after = scorer.score_plan(&plan);
prop_assert!(
score_after.score >= score_before.score,
"Adding steps should not decrease risk score"
);
}
#[test]
fn prop_risk_level_matches_score(plan in execution_plan_strategy()) {
let scorer = ExecutionRiskScorer::new();
let score = scorer.score_plan(&plan);
match score.level {
RiskLevel::Low => prop_assert!(score.score < 0.5),
RiskLevel::Medium => prop_assert!(score.score >= 0.5 && score.score < 1.5),
RiskLevel::High => prop_assert!(score.score >= 1.5 && score.score < 2.5),
RiskLevel::Critical => prop_assert!(score.score >= 2.5),
}
}
#[test]
fn prop_risk_factors_sum_to_score(plan in execution_plan_strategy()) {
let scorer = ExecutionRiskScorer::new();
let score = scorer.score_plan(&plan);
let factors_sum: f32 = score.factors.iter().map(|f| f.weight).sum();
prop_assert!(
(factors_sum - score.score).abs() < 0.01,
"Risk factors should sum to total score"
);
}
#[test]
fn prop_approval_threshold_consistent(plan in execution_plan_strategy()) {
let scorer = ExecutionRiskScorer::new();
let score = scorer.score_plan(&plan);
let requires_approval_1 = scorer.requires_approval(&score);
let requires_approval_2 = scorer.requires_approval(&score);
prop_assert_eq!(
requires_approval_1, requires_approval_2,
"Approval requirement should be consistent"
);
}
}