use proptest::prelude::*;
use ricecoder_execution::{RollbackAction, RollbackHandler, RollbackType};
use serde_json::json;
use tempfile::TempDir;
#[allow(dead_code)]
fn temp_file_path_strategy(temp_dir: &TempDir) -> impl Strategy<Value = String> {
let base_path = temp_dir.path().to_string_lossy().to_string();
r"[a-zA-Z0-9_\-]{1,20}\.txt".prop_map(move |filename| format!("{}/{}", base_path, filename))
}
#[allow(dead_code)]
fn delete_file_action_strategy(
temp_dir: &TempDir,
) -> impl Strategy<Value = (String, RollbackAction)> {
temp_file_path_strategy(temp_dir).prop_map(|path| {
let action = RollbackAction {
action_type: RollbackType::DeleteFile,
data: json!({ "file_path": path.clone() }),
};
(path, action)
})
}
#[allow(dead_code)]
fn restore_file_action_strategy(
temp_dir: &TempDir,
) -> impl Strategy<Value = (String, String, RollbackAction)> {
(
temp_file_path_strategy(temp_dir),
temp_file_path_strategy(temp_dir),
)
.prop_map(|(file_path, backup_path)| {
let action = RollbackAction {
action_type: RollbackType::RestoreFile,
data: json!({
"file_path": file_path.clone(),
"backup_path": backup_path.clone()
}),
};
(file_path, backup_path, action)
})
}
fn undo_command_action_strategy() -> impl Strategy<Value = RollbackAction> {
prop_oneof![
Just(RollbackAction {
action_type: RollbackType::RunCommand,
data: json!({
"command": "echo",
"args": ["test"]
}),
}),
Just(RollbackAction {
action_type: RollbackType::RunCommand,
data: json!({
"command": "true",
"args": []
}),
}),
]
}
proptest! {
#[test]
fn prop_rollback_completeness_empty_actions(_unit in Just(())) {
let mut handler = RollbackHandler::new();
let result = handler.execute_rollback();
prop_assert!(result.is_ok(), "Rollback should succeed with no actions");
let results = result.unwrap();
prop_assert_eq!(results.len(), 0, "Should have no rollback results");
}
#[test]
fn prop_rollback_handler_tracks_actions(action in undo_command_action_strategy()) {
let mut handler = RollbackHandler::new();
prop_assert_eq!(handler.action_count(), 0, "Handler should start with no actions");
handler.track_action("step-1".to_string(), action);
prop_assert_eq!(handler.action_count(), 1, "Handler should track one action");
}
#[test]
fn prop_rollback_handler_clears_actions(
actions in prop::collection::vec(undo_command_action_strategy(), 1..5)
) {
let mut handler = RollbackHandler::new();
for (i, action) in actions.iter().enumerate() {
handler.track_action(format!("step-{}", i), action.clone());
}
prop_assert_eq!(handler.action_count(), actions.len(), "Handler should track all actions");
handler.clear();
prop_assert_eq!(handler.action_count(), 0, "Handler should have no actions after clear");
}
#[test]
fn prop_rollback_completeness_verification(_unit in Just(())) {
let handler = RollbackHandler::new();
prop_assert!(!handler.is_in_progress(), "Handler should not be in progress initially");
prop_assert!(handler.verify_completeness(), "Verification should succeed when not in progress");
}
#[test]
fn prop_rollback_completeness_verification_in_progress(_unit in Just(())) {
let mut handler = RollbackHandler::new();
handler.track_action(
"step-1".to_string(),
RollbackAction {
action_type: RollbackType::RunCommand,
data: json!({
"command": "echo",
"args": ["test"]
}),
},
);
let mut handler_copy = RollbackHandler::new();
handler_copy.track_action(
"step-1".to_string(),
RollbackAction {
action_type: RollbackType::RunCommand,
data: json!({
"command": "echo",
"args": ["test"]
}),
},
);
prop_assert!(!handler_copy.is_in_progress(), "Handler should not be in progress initially");
}
#[test]
fn prop_rollback_actions_lifo_order(
actions in prop::collection::vec(undo_command_action_strategy(), 1..3)
) {
let mut handler = RollbackHandler::new();
for (i, action) in actions.iter().enumerate() {
handler.track_action(format!("step-{}", i), action.clone());
}
let initial_count = handler.action_count();
prop_assert_eq!(initial_count, actions.len(), "Handler should track all actions");
let result = handler.execute_rollback();
prop_assert!(result.is_ok(), "Rollback should succeed");
let results = result.unwrap();
prop_assert_eq!(results.len(), actions.len(), "Should execute all tracked actions");
for result in results {
prop_assert!(result.success, "All rollback actions should succeed");
}
}
#[test]
fn prop_partial_rollback_selective(
actions in prop::collection::vec(undo_command_action_strategy(), 2..4)
) {
let mut handler = RollbackHandler::new();
for (i, action) in actions.iter().enumerate() {
handler.track_action(format!("step-{}", i), action.clone());
}
let total_actions = handler.action_count();
prop_assert!(total_actions >= 2, "Should have at least 2 actions");
let result = handler.execute_partial_rollback(&["step-0".to_string()]);
prop_assert!(result.is_ok(), "Partial rollback should succeed");
let results = result.unwrap();
prop_assert_eq!(results.len(), 1, "Should execute only specified step");
}
#[test]
fn prop_rollback_handler_state_consistency(
actions in prop::collection::vec(undo_command_action_strategy(), 0..3)
) {
let mut handler = RollbackHandler::new();
prop_assert_eq!(handler.action_count(), 0, "Should start with no actions");
prop_assert!(!handler.is_in_progress(), "Should not be in progress initially");
for (i, action) in actions.iter().enumerate() {
handler.track_action(format!("step-{}", i), action.clone());
}
prop_assert_eq!(handler.action_count(), actions.len(), "Should track all actions");
prop_assert!(!handler.is_in_progress(), "Should not be in progress after tracking");
let result = handler.execute_rollback();
prop_assert!(result.is_ok(), "Rollback should succeed");
prop_assert!(!handler.is_in_progress(), "Should not be in progress after execution");
prop_assert!(handler.verify_completeness(), "Verification should succeed after execution");
}
#[test]
fn prop_rollback_result_contains_step_id(action in undo_command_action_strategy()) {
let mut handler = RollbackHandler::new();
let step_id = "test-step-123".to_string();
handler.track_action(step_id.clone(), action);
let result = handler.execute_rollback();
prop_assert!(result.is_ok(), "Rollback should succeed");
let results = result.unwrap();
prop_assert_eq!(results.len(), 1, "Should have one result");
prop_assert_eq!(&results[0].step_id, &step_id, "Result should contain correct step ID");
}
#[test]
fn prop_rollback_result_success_status(action in undo_command_action_strategy()) {
let mut handler = RollbackHandler::new();
handler.track_action("step-1".to_string(), action);
let result = handler.execute_rollback();
prop_assert!(result.is_ok(), "Rollback should succeed");
let results = result.unwrap();
prop_assert_eq!(results.len(), 1, "Should have one result");
prop_assert!(results[0].success, "Result should indicate success");
}
}