1use crate::approval::ApprovalSummary;
7use crate::models::RiskLevel;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ApprovalUIState {
12 Waiting,
14 Approved,
16 Rejected,
18}
19
20#[derive(Debug, Clone)]
22pub struct ApprovalUI {
23 pub request_id: String,
25 pub summary: ApprovalSummary,
27 pub state: ApprovalUIState,
29 pub comments: Option<String>,
31}
32
33impl ApprovalUI {
34 pub fn new(request_id: String, summary: ApprovalSummary) -> Self {
36 ApprovalUI {
37 request_id,
38 summary,
39 state: ApprovalUIState::Waiting,
40 comments: None,
41 }
42 }
43
44 pub fn approve(&mut self, comments: Option<String>) {
46 self.state = ApprovalUIState::Approved;
47 self.comments = comments;
48 }
49
50 pub fn reject(&mut self, comments: Option<String>) {
52 self.state = ApprovalUIState::Rejected;
53 self.comments = comments;
54 }
55
56 pub fn risk_color(&self) -> &'static str {
60 match self.summary.risk_level {
61 RiskLevel::Low => "green",
62 RiskLevel::Medium => "yellow",
63 RiskLevel::High => "red",
64 RiskLevel::Critical => "bright-red",
65 }
66 }
67
68 pub fn risk_emoji(&self) -> &'static str {
70 match self.summary.risk_level {
71 RiskLevel::Low => "✓",
72 RiskLevel::Medium => "⚠",
73 RiskLevel::High => "⚠⚠",
74 RiskLevel::Critical => "🚨",
75 }
76 }
77
78 pub fn format_display(&self) -> String {
82 let status = match self.state {
83 ApprovalUIState::Waiting => "⏳ Waiting for approval",
84 ApprovalUIState::Approved => "✅ Approved",
85 ApprovalUIState::Rejected => "❌ Rejected",
86 };
87
88 let mut display = format!(
89 "{}\n\n{} Risk Level: {:?}\n\nPlan: {}\nSteps: {}\nRisk Score: {:.2}\nEstimated Duration: {}s\n\nRisk Factors:\n{}",
90 status,
91 self.risk_emoji(),
92 self.summary.risk_level,
93 self.summary.plan_name,
94 self.summary.step_count,
95 self.summary.risk_score,
96 self.summary.estimated_duration_secs,
97 self.summary.risk_factors
98 );
99
100 if let Some(comments) = &self.comments {
101 display.push_str(&format!("\n\nComments: {}", comments));
102 }
103
104 display
105 }
106
107 pub fn get_prompt(&self) -> String {
109 match self.summary.risk_level {
110 RiskLevel::Critical => {
111 "⚠️ CRITICAL RISK - This plan has critical risk factors. Review carefully before approving.\n\nApprove? (y/n): ".to_string()
112 }
113 RiskLevel::High => {
114 "⚠️ HIGH RISK - This plan has high risk factors. Review before approving.\n\nApprove? (y/n): ".to_string()
115 }
116 RiskLevel::Medium => {
117 "Plan requires approval.\n\nApprove? (y/n): ".to_string()
118 }
119 RiskLevel::Low => {
120 "Plan is ready for execution.\n\nApprove? (y/n): ".to_string()
121 }
122 }
123 }
124
125 pub fn get_instructions(&self) -> String {
127 format!(
128 "Plan: {}\nSteps: {}\nRisk Level: {:?}\n\nPress 'y' to approve, 'n' to reject, or 'q' to cancel",
129 self.summary.plan_name,
130 self.summary.step_count,
131 self.summary.risk_level
132 )
133 }
134}
135
136pub struct ApprovalUIBuilder {
138 request_id: Option<String>,
139 summary: Option<ApprovalSummary>,
140}
141
142impl ApprovalUIBuilder {
143 pub fn new() -> Self {
145 ApprovalUIBuilder {
146 request_id: None,
147 summary: None,
148 }
149 }
150
151 pub fn request_id(mut self, request_id: String) -> Self {
153 self.request_id = Some(request_id);
154 self
155 }
156
157 pub fn summary(mut self, summary: ApprovalSummary) -> Self {
159 self.summary = Some(summary);
160 self
161 }
162
163 pub fn build(self) -> Result<ApprovalUI, String> {
165 let request_id = self
166 .request_id
167 .ok_or_else(|| "request_id is required".to_string())?;
168 let summary = self
169 .summary
170 .ok_or_else(|| "summary is required".to_string())?;
171
172 Ok(ApprovalUI::new(request_id, summary))
173 }
174}
175
176impl Default for ApprovalUIBuilder {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 fn create_test_summary() -> ApprovalSummary {
187 ApprovalSummary {
188 plan_id: "plan1".to_string(),
189 plan_name: "Test Plan".to_string(),
190 step_count: 3,
191 risk_level: RiskLevel::High,
192 risk_score: 1.8,
193 risk_factors: "- file_count: 1 file modified".to_string(),
194 estimated_duration_secs: 60,
195 requires_approval: true,
196 }
197 }
198
199 #[test]
200 fn test_create_approval_ui() {
201 let summary = create_test_summary();
202 let ui = ApprovalUI::new("req1".to_string(), summary);
203
204 assert_eq!(ui.request_id, "req1");
205 assert_eq!(ui.state, ApprovalUIState::Waiting);
206 assert_eq!(ui.summary.plan_name, "Test Plan");
207 }
208
209 #[test]
210 fn test_approve() {
211 let summary = create_test_summary();
212 let mut ui = ApprovalUI::new("req1".to_string(), summary);
213
214 ui.approve(Some("Looks good".to_string()));
215
216 assert_eq!(ui.state, ApprovalUIState::Approved);
217 assert_eq!(ui.comments, Some("Looks good".to_string()));
218 }
219
220 #[test]
221 fn test_reject() {
222 let summary = create_test_summary();
223 let mut ui = ApprovalUI::new("req1".to_string(), summary);
224
225 ui.reject(Some("Needs changes".to_string()));
226
227 assert_eq!(ui.state, ApprovalUIState::Rejected);
228 assert_eq!(ui.comments, Some("Needs changes".to_string()));
229 }
230
231 #[test]
232 fn test_risk_color() {
233 let mut summary = create_test_summary();
234 summary.risk_level = RiskLevel::Low;
235 let ui = ApprovalUI::new("req1".to_string(), summary);
236 assert_eq!(ui.risk_color(), "green");
237
238 let mut summary = create_test_summary();
239 summary.risk_level = RiskLevel::Medium;
240 let ui = ApprovalUI::new("req1".to_string(), summary);
241 assert_eq!(ui.risk_color(), "yellow");
242
243 let mut summary = create_test_summary();
244 summary.risk_level = RiskLevel::High;
245 let ui = ApprovalUI::new("req1".to_string(), summary);
246 assert_eq!(ui.risk_color(), "red");
247
248 let mut summary = create_test_summary();
249 summary.risk_level = RiskLevel::Critical;
250 let ui = ApprovalUI::new("req1".to_string(), summary);
251 assert_eq!(ui.risk_color(), "bright-red");
252 }
253
254 #[test]
255 fn test_risk_emoji() {
256 let mut summary = create_test_summary();
257 summary.risk_level = RiskLevel::Low;
258 let ui = ApprovalUI::new("req1".to_string(), summary);
259 assert_eq!(ui.risk_emoji(), "✓");
260
261 let mut summary = create_test_summary();
262 summary.risk_level = RiskLevel::Medium;
263 let ui = ApprovalUI::new("req1".to_string(), summary);
264 assert_eq!(ui.risk_emoji(), "⚠");
265
266 let mut summary = create_test_summary();
267 summary.risk_level = RiskLevel::High;
268 let ui = ApprovalUI::new("req1".to_string(), summary);
269 assert_eq!(ui.risk_emoji(), "⚠⚠");
270
271 let mut summary = create_test_summary();
272 summary.risk_level = RiskLevel::Critical;
273 let ui = ApprovalUI::new("req1".to_string(), summary);
274 assert_eq!(ui.risk_emoji(), "🚨");
275 }
276
277 #[test]
278 fn test_format_display() {
279 let summary = create_test_summary();
280 let ui = ApprovalUI::new("req1".to_string(), summary);
281
282 let display = ui.format_display();
283 assert!(display.contains("Waiting for approval"));
284 assert!(display.contains("Test Plan"));
285 assert!(display.contains("Steps: 3"));
286 }
287
288 #[test]
289 fn test_get_prompt_critical() {
290 let mut summary = create_test_summary();
291 summary.risk_level = RiskLevel::Critical;
292 let ui = ApprovalUI::new("req1".to_string(), summary);
293
294 let prompt = ui.get_prompt();
295 assert!(prompt.contains("CRITICAL RISK"));
296 }
297
298 #[test]
299 fn test_get_prompt_high() {
300 let mut summary = create_test_summary();
301 summary.risk_level = RiskLevel::High;
302 let ui = ApprovalUI::new("req1".to_string(), summary);
303
304 let prompt = ui.get_prompt();
305 assert!(prompt.contains("HIGH RISK"));
306 }
307
308 #[test]
309 fn test_get_instructions() {
310 let summary = create_test_summary();
311 let ui = ApprovalUI::new("req1".to_string(), summary);
312
313 let instructions = ui.get_instructions();
314 assert!(instructions.contains("Test Plan"));
315 assert!(instructions.contains("Steps: 3"));
316 }
317
318 #[test]
319 fn test_approval_ui_builder() {
320 let summary = create_test_summary();
321 let ui = ApprovalUIBuilder::new()
322 .request_id("req1".to_string())
323 .summary(summary)
324 .build()
325 .unwrap();
326
327 assert_eq!(ui.request_id, "req1");
328 assert_eq!(ui.state, ApprovalUIState::Waiting);
329 }
330
331 #[test]
332 fn test_approval_ui_builder_missing_request_id() {
333 let summary = create_test_summary();
334 let result = ApprovalUIBuilder::new().summary(summary).build();
335
336 assert!(result.is_err());
337 }
338
339 #[test]
340 fn test_approval_ui_builder_missing_summary() {
341 let result = ApprovalUIBuilder::new()
342 .request_id("req1".to_string())
343 .build();
344
345 assert!(result.is_err());
346 }
347}