1use crate::error::SpecError;
7use crate::models::{ApprovalGate, SpecPhase, SpecWritingSession};
8use chrono::Utc;
9
10#[derive(Debug, Clone)]
12pub struct ApprovalManager;
13
14impl ApprovalManager {
15 pub fn new() -> Self {
17 ApprovalManager
18 }
19
20 pub fn initialize_gates() -> Vec<ApprovalGate> {
25 vec![
26 ApprovalGate {
27 phase: SpecPhase::Discovery,
28 approved: false,
29 approved_at: None,
30 approved_by: None,
31 feedback: None,
32 },
33 ApprovalGate {
34 phase: SpecPhase::Requirements,
35 approved: false,
36 approved_at: None,
37 approved_by: None,
38 feedback: None,
39 },
40 ApprovalGate {
41 phase: SpecPhase::Design,
42 approved: false,
43 approved_at: None,
44 approved_by: None,
45 feedback: None,
46 },
47 ApprovalGate {
48 phase: SpecPhase::Tasks,
49 approved: false,
50 approved_at: None,
51 approved_by: None,
52 feedback: None,
53 },
54 ApprovalGate {
55 phase: SpecPhase::Execution,
56 approved: false,
57 approved_at: None,
58 approved_by: None,
59 feedback: None,
60 },
61 ]
62 }
63
64 pub fn approve_phase(
79 session: &mut SpecWritingSession,
80 approver: &str,
81 feedback: Option<String>,
82 ) -> Result<(), SpecError> {
83 let gate = session
85 .approval_gates
86 .iter_mut()
87 .find(|g| g.phase == session.phase)
88 .ok_or_else(|| {
89 SpecError::InvalidFormat(format!(
90 "No approval gate found for phase {:?}",
91 session.phase
92 ))
93 })?;
94
95 gate.approved = true;
97 gate.approved_at = Some(Utc::now());
98 gate.approved_by = Some(approver.to_string());
99 gate.feedback = feedback;
100
101 session.updated_at = Utc::now();
103
104 Ok(())
105 }
106
107 pub fn transition_to_next_phase(session: &mut SpecWritingSession) -> Result<(), SpecError> {
120 let current_gate = session
122 .approval_gates
123 .iter()
124 .find(|g| g.phase == session.phase)
125 .ok_or_else(|| {
126 SpecError::InvalidFormat(format!(
127 "No approval gate found for phase {:?}",
128 session.phase
129 ))
130 })?;
131
132 if !current_gate.approved {
133 return Err(SpecError::InvalidFormat(format!(
134 "Cannot transition from {:?}: phase not approved",
135 session.phase
136 )));
137 }
138
139 let next_phase = match session.phase {
141 SpecPhase::Discovery => SpecPhase::Requirements,
142 SpecPhase::Requirements => SpecPhase::Design,
143 SpecPhase::Design => SpecPhase::Tasks,
144 SpecPhase::Tasks => SpecPhase::Execution,
145 SpecPhase::Execution => {
146 return Err(SpecError::InvalidFormat(
147 "Already at final phase (Execution)".to_string(),
148 ))
149 }
150 };
151
152 if !session.approval_gates.iter().any(|g| g.phase == next_phase) {
154 return Err(SpecError::InvalidFormat(format!(
155 "No approval gate found for next phase {:?}",
156 next_phase
157 )));
158 }
159
160 session.phase = next_phase;
162 session.updated_at = Utc::now();
163
164 Ok(())
165 }
166
167 pub fn can_transition(session: &SpecWritingSession) -> bool {
171 let current_gate = session
173 .approval_gates
174 .iter()
175 .find(|g| g.phase == session.phase);
176
177 if let Some(gate) = current_gate {
178 if !gate.approved {
179 return false;
180 }
181 } else {
182 return false;
183 }
184
185 !matches!(session.phase, SpecPhase::Execution)
187 }
188
189 pub fn get_phase_approval(
193 session: &SpecWritingSession,
194 phase: SpecPhase,
195 ) -> Option<&ApprovalGate> {
196 session.approval_gates.iter().find(|g| g.phase == phase)
197 }
198
199 pub fn get_all_approvals(session: &SpecWritingSession) -> &[ApprovalGate] {
201 &session.approval_gates
202 }
203
204 pub fn are_phases_approved_up_to(
208 session: &SpecWritingSession,
209 target_phase: SpecPhase,
210 ) -> bool {
211 let phases = vec![
212 SpecPhase::Discovery,
213 SpecPhase::Requirements,
214 SpecPhase::Design,
215 SpecPhase::Tasks,
216 SpecPhase::Execution,
217 ];
218
219 for phase in phases {
220 if let Some(gate) = session.approval_gates.iter().find(|g| g.phase == phase) {
221 if !gate.approved {
222 return false;
223 }
224 }
225
226 if phase == target_phase {
227 break;
228 }
229 }
230
231 true
232 }
233}
234
235impl Default for ApprovalManager {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use chrono::Utc;
245
246 fn create_test_session() -> SpecWritingSession {
247 let now = Utc::now();
248 SpecWritingSession {
249 id: "test-session".to_string(),
250 spec_id: "test-spec".to_string(),
251 phase: SpecPhase::Discovery,
252 conversation_history: vec![],
253 approval_gates: ApprovalManager::initialize_gates(),
254 created_at: now,
255 updated_at: now,
256 }
257 }
258
259 #[test]
260 fn test_initialize_gates() {
261 let gates = ApprovalManager::initialize_gates();
262
263 assert_eq!(gates.len(), 5);
264 assert_eq!(gates[0].phase, SpecPhase::Discovery);
265 assert_eq!(gates[1].phase, SpecPhase::Requirements);
266 assert_eq!(gates[2].phase, SpecPhase::Design);
267 assert_eq!(gates[3].phase, SpecPhase::Tasks);
268 assert_eq!(gates[4].phase, SpecPhase::Execution);
269
270 for gate in gates {
271 assert!(!gate.approved);
272 assert!(gate.approved_at.is_none());
273 assert!(gate.approved_by.is_none());
274 assert!(gate.feedback.is_none());
275 }
276 }
277
278 #[test]
279 fn test_approve_phase() {
280 let mut session = create_test_session();
281
282 let result = ApprovalManager::approve_phase(
283 &mut session,
284 "reviewer",
285 Some("Looks good".to_string()),
286 );
287 assert!(result.is_ok());
288
289 let gate = ApprovalManager::get_phase_approval(&session, SpecPhase::Discovery).unwrap();
290 assert!(gate.approved);
291 assert_eq!(gate.approved_by, Some("reviewer".to_string()));
292 assert_eq!(gate.feedback, Some("Looks good".to_string()));
293 assert!(gate.approved_at.is_some());
294 }
295
296 #[test]
297 fn test_approve_phase_without_feedback() {
298 let mut session = create_test_session();
299
300 let result = ApprovalManager::approve_phase(&mut session, "reviewer", None);
301 assert!(result.is_ok());
302
303 let gate = ApprovalManager::get_phase_approval(&session, SpecPhase::Discovery).unwrap();
304 assert!(gate.approved);
305 assert!(gate.feedback.is_none());
306 }
307
308 #[test]
309 fn test_cannot_transition_without_approval() {
310 let session = create_test_session();
311
312 assert!(!ApprovalManager::can_transition(&session));
313 }
314
315 #[test]
316 fn test_transition_to_next_phase_fails_without_approval() {
317 let mut session = create_test_session();
318
319 let result = ApprovalManager::transition_to_next_phase(&mut session);
320 assert!(result.is_err());
321 assert_eq!(session.phase, SpecPhase::Discovery);
322 }
323
324 #[test]
325 fn test_transition_to_next_phase_succeeds_with_approval() {
326 let mut session = create_test_session();
327
328 ApprovalManager::approve_phase(&mut session, "reviewer", None).unwrap();
330
331 let result = ApprovalManager::transition_to_next_phase(&mut session);
333 assert!(result.is_ok());
334 assert_eq!(session.phase, SpecPhase::Requirements);
335 }
336
337 #[test]
338 fn test_sequential_phase_progression() {
339 let mut session = create_test_session();
340
341 let phases = vec![
342 SpecPhase::Discovery,
343 SpecPhase::Requirements,
344 SpecPhase::Design,
345 SpecPhase::Tasks,
346 SpecPhase::Execution,
347 ];
348
349 for (i, phase) in phases.iter().enumerate() {
350 assert_eq!(session.phase, *phase);
351
352 ApprovalManager::approve_phase(&mut session, "reviewer", None).unwrap();
354
355 if i < phases.len() - 1 {
357 ApprovalManager::transition_to_next_phase(&mut session).unwrap();
358 }
359 }
360
361 assert_eq!(session.phase, SpecPhase::Execution);
362 }
363
364 #[test]
365 fn test_cannot_transition_from_execution() {
366 let mut session = create_test_session();
367
368 session.phase = SpecPhase::Execution;
370
371 ApprovalManager::approve_phase(&mut session, "reviewer", None).unwrap();
373
374 let result = ApprovalManager::transition_to_next_phase(&mut session);
376 assert!(result.is_err());
377 }
378
379 #[test]
380 fn test_get_phase_approval() {
381 let session = create_test_session();
382
383 let gate = ApprovalManager::get_phase_approval(&session, SpecPhase::Requirements);
384 assert!(gate.is_some());
385 assert_eq!(gate.unwrap().phase, SpecPhase::Requirements);
386 assert!(!gate.unwrap().approved);
387 }
388
389 #[test]
390 fn test_get_all_approvals() {
391 let session = create_test_session();
392
393 let gates = ApprovalManager::get_all_approvals(&session);
394 assert_eq!(gates.len(), 5);
395 }
396
397 #[test]
398 fn test_are_phases_approved_up_to() {
399 let mut session = create_test_session();
400
401 assert!(!ApprovalManager::are_phases_approved_up_to(
403 &session,
404 SpecPhase::Requirements
405 ));
406
407 ApprovalManager::approve_phase(&mut session, "reviewer", None).unwrap();
409 ApprovalManager::transition_to_next_phase(&mut session).unwrap();
410
411 ApprovalManager::approve_phase(&mut session, "reviewer", None).unwrap();
413
414 assert!(ApprovalManager::are_phases_approved_up_to(
416 &session,
417 SpecPhase::Requirements
418 ));
419
420 assert!(!ApprovalManager::are_phases_approved_up_to(
422 &session,
423 SpecPhase::Design
424 ));
425 }
426
427 #[test]
428 fn test_can_transition_after_approval() {
429 let mut session = create_test_session();
430
431 assert!(!ApprovalManager::can_transition(&session));
432
433 ApprovalManager::approve_phase(&mut session, "reviewer", None).unwrap();
434
435 assert!(ApprovalManager::can_transition(&session));
436 }
437
438 #[test]
439 fn test_cannot_transition_from_final_phase() {
440 let mut session = create_test_session();
441
442 session.phase = SpecPhase::Execution;
443 ApprovalManager::approve_phase(&mut session, "reviewer", None).unwrap();
444
445 assert!(!ApprovalManager::can_transition(&session));
446 }
447
448 #[test]
449 fn test_approval_timestamps_are_recorded() {
450 let mut session = create_test_session();
451 let before = Utc::now();
452
453 ApprovalManager::approve_phase(&mut session, "reviewer", None).unwrap();
454
455 let after = Utc::now();
456 let gate = ApprovalManager::get_phase_approval(&session, SpecPhase::Discovery).unwrap();
457
458 assert!(gate.approved_at.is_some());
459 let approved_at = gate.approved_at.unwrap();
460 assert!(approved_at >= before);
461 assert!(approved_at <= after);
462 }
463
464 #[test]
465 fn test_session_updated_at_changes_on_approval() {
466 let mut session = create_test_session();
467 let original_updated_at = session.updated_at;
468
469 std::thread::sleep(std::time::Duration::from_millis(10));
471
472 ApprovalManager::approve_phase(&mut session, "reviewer", None).unwrap();
473
474 assert!(session.updated_at > original_updated_at);
475 }
476
477 #[test]
478 fn test_session_updated_at_changes_on_transition() {
479 let mut session = create_test_session();
480
481 ApprovalManager::approve_phase(&mut session, "reviewer", None).unwrap();
482
483 let updated_at_after_approval = session.updated_at;
484
485 std::thread::sleep(std::time::Duration::from_millis(10));
487
488 ApprovalManager::transition_to_next_phase(&mut session).unwrap();
489
490 assert!(session.updated_at > updated_at_after_approval);
491 }
492}