Skip to main content

opencode_voice/approval/
queue.rs

1//! FIFO queue for pending approval items (permissions and questions).
2
3use std::collections::VecDeque;
4
5use crate::approval::types::{PendingApproval, PermissionRequest, QuestionRequest};
6
7/// FIFO queue for pending permission and question approvals.
8pub struct ApprovalQueue {
9    items: VecDeque<PendingApproval>,
10}
11
12impl ApprovalQueue {
13    pub fn new() -> Self {
14        ApprovalQueue {
15            items: VecDeque::new(),
16        }
17    }
18
19    /// Returns the number of pending items.
20    pub fn len(&self) -> usize {
21        self.items.len()
22    }
23
24    /// Returns true if there are pending items.
25    pub fn has_pending(&self) -> bool {
26        !self.items.is_empty()
27    }
28
29    /// Returns a reference to the front item without removing it.
30    pub fn peek(&self) -> Option<&PendingApproval> {
31        self.items.front()
32    }
33
34    /// Adds a permission request to the back of the queue.
35    pub fn add_permission(&mut self, request: PermissionRequest) {
36        self.items.push_back(PendingApproval::Permission(request));
37    }
38
39    /// Adds a question request to the back of the queue.
40    pub fn add_question(&mut self, request: QuestionRequest) {
41        self.items.push_back(PendingApproval::Question(request));
42    }
43
44    /// Removes all items from the queue.
45    pub fn clear(&mut self) {
46        self.items.clear();
47    }
48
49    /// Removes the item with the given request ID. Returns true if found and removed.
50    pub fn remove(&mut self, request_id: &str) -> bool {
51        if let Some(pos) = self.items.iter().position(|item| item.id() == request_id) {
52            self.items.remove(pos);
53            true
54        } else {
55            false
56        }
57    }
58}
59
60impl Default for ApprovalQueue {
61    fn default() -> Self {
62        Self::new()
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use crate::approval::types::{PermissionRequest, QuestionRequest};
70
71    fn make_permission(id: &str) -> PermissionRequest {
72        PermissionRequest {
73            id: id.to_string(),
74            permission: "bash".to_string(),
75            metadata: serde_json::Value::Null,
76        }
77    }
78
79    fn make_question(id: &str) -> QuestionRequest {
80        QuestionRequest {
81            id: id.to_string(),
82            questions: vec![],
83        }
84    }
85
86    #[test]
87    fn test_new_queue_is_empty() {
88        let q = ApprovalQueue::new();
89        assert!(!q.has_pending());
90        assert_eq!(q.len(), 0);
91        assert!(q.peek().is_none());
92    }
93
94    #[test]
95    fn test_add_permission_and_peek() {
96        let mut q = ApprovalQueue::new();
97        q.add_permission(make_permission("perm-1"));
98        assert_eq!(q.len(), 1);
99        assert!(q.has_pending());
100        let item = q.peek().unwrap();
101        assert_eq!(item.id(), "perm-1");
102        assert!(matches!(item, PendingApproval::Permission(_)));
103    }
104
105    #[test]
106    fn test_add_question_and_peek() {
107        let mut q = ApprovalQueue::new();
108        q.add_question(make_question("q-1"));
109        assert_eq!(q.len(), 1);
110        let item = q.peek().unwrap();
111        assert_eq!(item.id(), "q-1");
112        assert!(matches!(item, PendingApproval::Question(_)));
113    }
114
115    #[test]
116    fn test_fifo_ordering() {
117        let mut q = ApprovalQueue::new();
118        q.add_permission(make_permission("first"));
119        q.add_question(make_question("second"));
120        q.add_permission(make_permission("third"));
121
122        // Peek should return first
123        assert_eq!(q.peek().unwrap().id(), "first");
124        assert_eq!(q.len(), 3);
125    }
126
127    #[test]
128    fn test_remove_found() {
129        let mut q = ApprovalQueue::new();
130        q.add_permission(make_permission("perm-1"));
131        q.add_question(make_question("q-1"));
132
133        let removed = q.remove("perm-1");
134        assert!(removed);
135        assert_eq!(q.len(), 1);
136        assert_eq!(q.peek().unwrap().id(), "q-1");
137    }
138
139    #[test]
140    fn test_remove_not_found() {
141        let mut q = ApprovalQueue::new();
142        q.add_permission(make_permission("perm-1"));
143
144        let removed = q.remove("nonexistent");
145        assert!(!removed);
146        assert_eq!(q.len(), 1);
147    }
148
149    #[test]
150    fn test_remove_middle_item() {
151        let mut q = ApprovalQueue::new();
152        q.add_permission(make_permission("a"));
153        q.add_permission(make_permission("b"));
154        q.add_permission(make_permission("c"));
155
156        q.remove("b");
157        assert_eq!(q.len(), 2);
158        assert_eq!(q.peek().unwrap().id(), "a");
159    }
160
161    #[test]
162    fn test_has_pending_tracks_state() {
163        let mut q = ApprovalQueue::new();
164        assert!(!q.has_pending());
165
166        q.add_permission(make_permission("x"));
167        assert!(q.has_pending());
168    }
169
170    #[test]
171    fn test_clear_empties_queue() {
172        let mut q = ApprovalQueue::new();
173        q.add_permission(make_permission("a"));
174        q.add_question(make_question("b"));
175        q.add_permission(make_permission("c"));
176        assert_eq!(q.len(), 3);
177
178        q.clear();
179        assert_eq!(q.len(), 0);
180        assert!(!q.has_pending());
181        assert!(q.peek().is_none());
182    }
183
184    #[test]
185    fn test_clear_on_empty_queue() {
186        let mut q = ApprovalQueue::new();
187        q.clear(); // Should not panic.
188        assert_eq!(q.len(), 0);
189    }
190
191    #[test]
192    fn test_clear_then_reuse() {
193        let mut q = ApprovalQueue::new();
194        q.add_permission(make_permission("a"));
195        q.add_question(make_question("b"));
196        q.clear();
197
198        // Queue should be fully functional after clear.
199        q.add_permission(make_permission("c"));
200        assert_eq!(q.len(), 1);
201        assert!(q.has_pending());
202        assert_eq!(q.peek().unwrap().id(), "c");
203    }
204
205    // --- Additional tests added to expand coverage ---
206
207    #[test]
208    fn test_default_creates_empty_queue() {
209        let q = ApprovalQueue::default();
210        assert_eq!(q.len(), 0);
211    }
212
213    #[test]
214    fn test_remove_last_item_leaves_empty() {
215        let mut q = ApprovalQueue::new();
216        q.add_permission(make_permission("only"));
217        let removed = q.remove("only");
218        assert!(removed);
219        assert_eq!(q.len(), 0);
220        assert!(q.peek().is_none());
221    }
222
223    #[test]
224    fn test_remove_preserves_fifo_order() {
225        let mut q = ApprovalQueue::new();
226        q.add_permission(make_permission("a"));
227        q.add_question(make_question("b"));
228        q.add_permission(make_permission("c"));
229
230        // Remove middle item
231        q.remove("b");
232
233        // Remaining items should be in original order
234        assert_eq!(q.len(), 2);
235        assert_eq!(q.peek().unwrap().id(), "a");
236    }
237
238    #[test]
239    fn test_peek_does_not_remove() {
240        let mut q = ApprovalQueue::new();
241        q.add_permission(make_permission("p1"));
242        q.add_permission(make_permission("p2"));
243
244        // Peek multiple times — should always return same item
245        assert_eq!(q.peek().unwrap().id(), "p1");
246        assert_eq!(q.peek().unwrap().id(), "p1");
247        assert_eq!(q.len(), 2);
248    }
249
250    #[test]
251    fn test_insertion_order_preserved() {
252        let mut q = ApprovalQueue::new();
253        q.add_permission(make_permission("first"));
254        q.add_question(make_question("second"));
255        q.add_permission(make_permission("third"));
256
257        assert_eq!(q.len(), 3);
258        assert_eq!(q.peek().unwrap().id(), "first");
259    }
260
261    #[test]
262    fn test_len_increments_correctly() {
263        let mut q = ApprovalQueue::new();
264        assert_eq!(q.len(), 0);
265        q.add_permission(make_permission("a"));
266        assert_eq!(q.len(), 1);
267        q.add_question(make_question("b"));
268        assert_eq!(q.len(), 2);
269        q.add_permission(make_permission("c"));
270        assert_eq!(q.len(), 3);
271    }
272
273    #[test]
274    fn test_remove_not_found_does_not_change_len() {
275        let mut q = ApprovalQueue::new();
276        q.add_permission(make_permission("a"));
277        q.add_permission(make_permission("b"));
278
279        let removed = q.remove("nonexistent");
280        assert!(!removed);
281        assert_eq!(q.len(), 2);
282    }
283
284    #[test]
285    fn test_mixed_permission_and_question_types() {
286        let mut q = ApprovalQueue::new();
287        q.add_permission(make_permission("perm"));
288        q.add_question(make_question("quest"));
289
290        let first = q.peek().unwrap();
291        assert!(matches!(first, PendingApproval::Permission(_)));
292        assert_eq!(first.id(), "perm");
293
294        q.remove("perm");
295        let second = q.peek().unwrap();
296        assert!(matches!(second, PendingApproval::Question(_)));
297        assert_eq!(second.id(), "quest");
298    }
299}