An AI agent built to do Ralph loops - plan mode for planning and ralph mode for implementing.
at main 191 lines 5.6 kB view raw
1use chrono::Utc; 2use rustagent::config::Config; 3use rustagent::ralph::RalphLoop; 4use rustagent::spec::{Spec, Task, TaskStatus}; 5use std::fs; 6use tempfile::TempDir; 7 8#[tokio::test] 9async fn test_ralph_loop_creation() { 10 let temp = TempDir::new().unwrap(); 11 let config_path = temp.path().join("config.toml"); 12 let spec_path = temp.path().join("test.json"); 13 14 fs::write( 15 &config_path, 16 r#" 17[llm] 18provider = "anthropic" 19model = "claude-sonnet-4-20250514" 20 21[anthropic] 22api_key = "test-key" 23 24[rustagent] 25spec_dir = "specs" 26"#, 27 ) 28 .unwrap(); 29 30 let spec = Spec { 31 name: "test".to_string(), 32 description: "Test".to_string(), 33 branch_name: "feature/test".to_string(), 34 created_at: Utc::now(), 35 tasks: vec![], 36 learnings: vec![], 37 }; 38 spec.save(&spec_path).unwrap(); 39 40 let config = Config::load(&config_path).unwrap(); 41 let ralph = RalphLoop::new(config, spec_path.to_str().unwrap().to_string(), None).unwrap(); 42 43 assert!(ralph.spec_path.ends_with("test.json")); 44} 45 46#[test] 47fn test_find_next_pending_task() { 48 let spec = Spec { 49 name: "test".to_string(), 50 description: "Test".to_string(), 51 branch_name: "feature/test".to_string(), 52 created_at: Utc::now(), 53 tasks: vec![ 54 Task { 55 id: "task-1".to_string(), 56 title: "Task 1".to_string(), 57 description: "First task".to_string(), 58 acceptance_criteria: vec![], 59 status: TaskStatus::Complete, 60 blocked_reason: None, 61 completed_at: Some(Utc::now()), 62 }, 63 Task { 64 id: "task-2".to_string(), 65 title: "Task 2".to_string(), 66 description: "Second task".to_string(), 67 acceptance_criteria: vec![], 68 status: TaskStatus::Pending, 69 blocked_reason: None, 70 completed_at: None, 71 }, 72 ], 73 learnings: vec![], 74 }; 75 76 let next = spec.find_next_task(); 77 assert!(next.is_some()); 78 assert_eq!(next.unwrap().id, "task-2"); 79} 80 81#[test] 82fn test_find_next_task_skips_blocked() { 83 let spec = Spec { 84 name: "test".to_string(), 85 description: "Test".to_string(), 86 branch_name: "feature/test".to_string(), 87 created_at: Utc::now(), 88 tasks: vec![ 89 Task { 90 id: "task-1".to_string(), 91 title: "Task 1".to_string(), 92 description: "First task".to_string(), 93 acceptance_criteria: vec![], 94 status: TaskStatus::Blocked, 95 blocked_reason: Some("Missing dep".to_string()), 96 completed_at: None, 97 }, 98 Task { 99 id: "task-2".to_string(), 100 title: "Task 2".to_string(), 101 description: "Second task".to_string(), 102 acceptance_criteria: vec![], 103 status: TaskStatus::Pending, 104 blocked_reason: None, 105 completed_at: None, 106 }, 107 ], 108 learnings: vec![], 109 }; 110 111 let next = spec.find_next_task(); 112 assert!(next.is_some()); 113 assert_eq!(next.unwrap().id, "task-2"); 114} 115 116#[test] 117fn test_find_next_task_skips_in_progress() { 118 let spec = Spec { 119 name: "test".to_string(), 120 description: "Test".to_string(), 121 branch_name: "feature/test".to_string(), 122 created_at: Utc::now(), 123 tasks: vec![ 124 Task { 125 id: "task-1".to_string(), 126 title: "Task 1".to_string(), 127 description: "First task".to_string(), 128 acceptance_criteria: vec![], 129 status: TaskStatus::InProgress, 130 blocked_reason: None, 131 completed_at: None, 132 }, 133 Task { 134 id: "task-2".to_string(), 135 title: "Task 2".to_string(), 136 description: "Second task".to_string(), 137 acceptance_criteria: vec![], 138 status: TaskStatus::Pending, 139 blocked_reason: None, 140 completed_at: None, 141 }, 142 ], 143 learnings: vec![], 144 }; 145 146 let next = spec.find_next_task(); 147 assert!(next.is_some()); 148 assert_eq!(next.unwrap().id, "task-2"); 149} 150 151#[test] 152fn test_find_next_task_returns_none_when_all_complete() { 153 let spec = Spec { 154 name: "test".to_string(), 155 description: "Test".to_string(), 156 branch_name: "feature/test".to_string(), 157 created_at: Utc::now(), 158 tasks: vec![Task { 159 id: "task-1".to_string(), 160 title: "Task 1".to_string(), 161 description: "First task".to_string(), 162 acceptance_criteria: vec![], 163 status: TaskStatus::Complete, 164 blocked_reason: None, 165 completed_at: Some(Utc::now()), 166 }], 167 learnings: vec![], 168 }; 169 170 let next = spec.find_next_task(); 171 assert!(next.is_none()); 172} 173 174#[test] 175fn test_add_learning() { 176 let mut spec = Spec { 177 name: "test".to_string(), 178 description: "Test".to_string(), 179 branch_name: "feature/test".to_string(), 180 created_at: Utc::now(), 181 tasks: vec![], 182 learnings: vec![], 183 }; 184 185 spec.add_learning("First learning".to_string()); 186 spec.add_learning("Second learning".to_string()); 187 188 assert_eq!(spec.learnings.len(), 2); 189 assert_eq!(spec.learnings[0], "First learning"); 190 assert_eq!(spec.learnings[1], "Second learning"); 191}