Skip to main content

oven_cli/pipeline/
state.rs

1use crate::db::RunStatus;
2
3impl RunStatus {
4    /// Determine the next state in the pipeline based on review findings and cycle count.
5    ///
6    /// - After implementing, always review.
7    /// - If reviewer finds issues and we haven't hit max cycles, fix.
8    /// - If reviewer finds issues at max cycles, fail.
9    /// - Clean review goes to merging.
10    /// - After fixing, go back to reviewing.
11    /// - After merging, complete.
12    #[must_use]
13    pub const fn next(self, has_findings: bool, cycle: u32) -> Self {
14        match self {
15            Self::Pending => Self::Implementing,
16            Self::Implementing | Self::Fixing => Self::Reviewing,
17            Self::Reviewing if has_findings && cycle < 2 => Self::Fixing,
18            Self::Reviewing if has_findings => Self::Failed,
19            Self::Reviewing => Self::Merging,
20            Self::Merging => Self::Complete,
21            Self::Complete | Self::Failed => Self::Failed,
22        }
23    }
24
25    pub const fn is_terminal(self) -> bool {
26        matches!(self, Self::Complete | Self::Failed)
27    }
28}
29
30#[cfg(test)]
31mod tests {
32    use proptest::prelude::*;
33
34    use super::*;
35
36    const ALL_STATUSES: [RunStatus; 7] = [
37        RunStatus::Pending,
38        RunStatus::Implementing,
39        RunStatus::Reviewing,
40        RunStatus::Fixing,
41        RunStatus::Merging,
42        RunStatus::Complete,
43        RunStatus::Failed,
44    ];
45
46    proptest! {
47        #[test]
48        fn next_never_panics(idx in 0..7usize, has_findings: bool, cycle in 0..50u32) {
49            let status = ALL_STATUSES[idx];
50            // Should never panic regardless of inputs
51            let _ = status.next(has_findings, cycle);
52        }
53
54        #[test]
55        fn terminal_states_stay_terminal(has_findings: bool, cycle in 0..50u32) {
56            assert!(RunStatus::Complete.next(has_findings, cycle).is_terminal());
57            assert!(RunStatus::Failed.next(has_findings, cycle).is_terminal());
58        }
59
60        #[test]
61        fn reviewing_with_findings_past_max_always_fails(cycle in 2..50u32) {
62            assert_eq!(RunStatus::Reviewing.next(true, cycle), RunStatus::Failed);
63        }
64
65        #[test]
66        fn reviewing_clean_always_merges(cycle in 0..50u32) {
67            assert_eq!(RunStatus::Reviewing.next(false, cycle), RunStatus::Merging);
68        }
69    }
70
71    #[test]
72    fn pending_to_implementing() {
73        assert_eq!(RunStatus::Pending.next(false, 0), RunStatus::Implementing);
74    }
75
76    #[test]
77    fn implementing_to_reviewing() {
78        assert_eq!(RunStatus::Implementing.next(false, 0), RunStatus::Reviewing);
79    }
80
81    #[test]
82    fn clean_review_to_merging() {
83        assert_eq!(RunStatus::Reviewing.next(false, 1), RunStatus::Merging);
84    }
85
86    #[test]
87    fn findings_under_max_cycles_to_fixing() {
88        assert_eq!(RunStatus::Reviewing.next(true, 1), RunStatus::Fixing);
89    }
90
91    #[test]
92    fn findings_at_max_cycles_to_failed() {
93        assert_eq!(RunStatus::Reviewing.next(true, 2), RunStatus::Failed);
94    }
95
96    #[test]
97    fn fixing_back_to_reviewing() {
98        assert_eq!(RunStatus::Fixing.next(false, 1), RunStatus::Reviewing);
99    }
100
101    #[test]
102    fn merging_to_complete() {
103        assert_eq!(RunStatus::Merging.next(false, 0), RunStatus::Complete);
104    }
105
106    #[test]
107    fn terminal_states_go_to_failed() {
108        assert_eq!(RunStatus::Complete.next(false, 0), RunStatus::Failed);
109        assert_eq!(RunStatus::Failed.next(false, 0), RunStatus::Failed);
110    }
111
112    #[test]
113    fn is_terminal() {
114        assert!(RunStatus::Complete.is_terminal());
115        assert!(RunStatus::Failed.is_terminal());
116        assert!(!RunStatus::Pending.is_terminal());
117        assert!(!RunStatus::Implementing.is_terminal());
118        assert!(!RunStatus::Reviewing.is_terminal());
119        assert!(!RunStatus::Fixing.is_terminal());
120        assert!(!RunStatus::Merging.is_terminal());
121    }
122}