1use std::collections::{HashMap, HashSet};
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::time::{Duration, Instant};
11
12use tokio::sync::{Mutex, mpsc, oneshot};
13
14const PENDING_CLEANUP_THRESHOLD: usize = 50;
16
17const PENDING_MAX_AGE: Duration = Duration::from_secs(300);
19
20use super::{
21 BatchPermissionRequest, BatchPermissionResponse, Grant, GrantTarget, PermissionRequest,
22};
23use crate::controller::types::{ControllerEvent, TurnId};
24
25#[derive(Debug, Clone)]
27pub struct PendingPermissionInfo {
28 pub tool_use_id: String,
30 pub session_id: i64,
32 pub request: PermissionRequest,
34 pub turn_id: Option<TurnId>,
36}
37
38#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
40pub struct PermissionPanelResponse {
41 pub granted: bool,
43 #[serde(skip)]
45 pub grant: Option<Grant>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub message: Option<String>,
49}
50
51static BATCH_COUNTER: AtomicU64 = AtomicU64::new(1);
53
54pub fn generate_batch_id() -> String {
56 let id = BATCH_COUNTER.fetch_add(1, Ordering::SeqCst);
57 format!("batch-{}", id)
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum PermissionError {
63 NotFound,
65 AlreadyResponded,
67 SendFailed,
69 EventSendFailed,
71 BatchAlreadyProcessed,
73}
74
75impl std::fmt::Display for PermissionError {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 match self {
78 PermissionError::NotFound => write!(f, "No pending permission request found"),
79 PermissionError::AlreadyResponded => write!(f, "Permission already responded to"),
80 PermissionError::SendFailed => write!(f, "Failed to send response"),
81 PermissionError::EventSendFailed => write!(f, "Failed to send event notification"),
82 PermissionError::BatchAlreadyProcessed => write!(f, "Batch has already been processed"),
83 }
84 }
85}
86
87impl std::error::Error for PermissionError {}
88
89struct PendingRequest {
91 session_id: i64,
92 request: PermissionRequest,
93 turn_id: Option<TurnId>,
94 responder: oneshot::Sender<PermissionPanelResponse>,
95 created_at: Instant,
96}
97
98struct PendingBatch {
100 session_id: i64,
101 #[allow(dead_code)]
103 requests: Vec<PermissionRequest>,
104 #[allow(dead_code)]
106 turn_id: Option<TurnId>,
107 responder: oneshot::Sender<BatchPermissionResponse>,
108 created_at: Instant,
109}
110
111pub struct PermissionRegistry {
142 session_grants: Mutex<HashMap<i64, Vec<Grant>>>,
144 pending_requests: Mutex<HashMap<String, PendingRequest>>,
146 pending_batches: Mutex<HashMap<String, PendingBatch>>,
148 event_tx: mpsc::Sender<ControllerEvent>,
150}
151
152impl PermissionRegistry {
153 pub fn new(event_tx: mpsc::Sender<ControllerEvent>) -> Self {
158 Self {
159 session_grants: Mutex::new(HashMap::new()),
160 pending_requests: Mutex::new(HashMap::new()),
161 pending_batches: Mutex::new(HashMap::new()),
162 event_tx,
163 }
164 }
165
166 pub async fn add_grant(&self, session_id: i64, grant: Grant) {
176 let mut grants = self.session_grants.lock().await;
177 let session_grants = grants.entry(session_id).or_insert_with(Vec::new);
178 session_grants.push(grant);
179 }
180
181 pub async fn add_grants(&self, session_id: i64, new_grants: Vec<Grant>) {
187 let mut grants = self.session_grants.lock().await;
188 let session_grants = grants.entry(session_id).or_insert_with(Vec::new);
189 session_grants.extend(new_grants);
190 }
191
192 pub async fn cleanup_expired(&self, session_id: i64) {
194 let mut grants = self.session_grants.lock().await;
195 if let Some(session_grants) = grants.get_mut(&session_id) {
196 session_grants.retain(|g| !g.is_expired());
197 }
198 }
199
200 pub async fn revoke_grants(&self, session_id: i64, target: &GrantTarget) -> usize {
209 let mut grants = self.session_grants.lock().await;
210 if let Some(session_grants) = grants.get_mut(&session_id) {
211 let original_len = session_grants.len();
212 session_grants.retain(|g| &g.target != target);
213 original_len - session_grants.len()
214 } else {
215 0
216 }
217 }
218
219 pub async fn get_grants(&self, session_id: i64) -> Vec<Grant> {
227 let grants = self.session_grants.lock().await;
228 grants.get(&session_id).cloned().unwrap_or_default()
229 }
230
231 pub async fn clear_grants(&self, session_id: i64) {
236 let mut grants = self.session_grants.lock().await;
237 grants.remove(&session_id);
238 }
239
240 pub async fn check(&self, session_id: i64, request: &PermissionRequest) -> bool {
258 let grants = self.session_grants.lock().await;
259 if let Some(session_grants) = grants.get(&session_id) {
260 session_grants.iter().any(|grant| grant.satisfies(request))
261 } else {
262 false
263 }
264 }
265
266 pub async fn check_batch(
275 &self,
276 session_id: i64,
277 requests: &[PermissionRequest],
278 ) -> HashSet<String> {
279 let grants = self.session_grants.lock().await;
280 let session_grants = grants.get(&session_id);
281
282 let mut granted = HashSet::new();
283 for request in requests {
284 if let Some(sg) = session_grants
285 && sg.iter().any(|grant| grant.satisfies(request))
286 {
287 granted.insert(request.id.clone());
288 }
289 }
290 granted
291 }
292
293 pub async fn find_satisfying_grant(
302 &self,
303 session_id: i64,
304 request: &PermissionRequest,
305 ) -> Option<Grant> {
306 let grants = self.session_grants.lock().await;
307 if let Some(session_grants) = grants.get(&session_id) {
308 session_grants
309 .iter()
310 .find(|grant| grant.satisfies(request))
311 .cloned()
312 } else {
313 None
314 }
315 }
316
317 pub async fn request_permission(
334 &self,
335 session_id: i64,
336 request: PermissionRequest,
337 turn_id: Option<TurnId>,
338 ) -> Result<oneshot::Receiver<PermissionPanelResponse>, PermissionError> {
339 if self.check(session_id, &request).await {
349 let (tx, rx) = oneshot::channel();
350 let _ = tx.send(PermissionPanelResponse {
351 granted: true,
352 grant: None, message: None,
354 });
355 return Ok(rx);
356 }
357
358 let (tx, rx) = oneshot::channel();
359 let request_id = request.id.clone();
360
361 {
363 let mut pending = self.pending_requests.lock().await;
364
365 if pending.len() >= PENDING_CLEANUP_THRESHOLD {
367 let now = Instant::now();
368 pending.retain(|id, req| {
369 let keep = now.duration_since(req.created_at) < PENDING_MAX_AGE;
370 if !keep {
371 tracing::warn!(
372 request_id = %id,
373 age_secs = now.duration_since(req.created_at).as_secs(),
374 "Cleaning up stale pending permission request"
375 );
376 }
377 keep
378 });
379 }
380
381 pending.insert(
382 request_id.clone(),
383 PendingRequest {
384 session_id,
385 request: request.clone(),
386 turn_id: turn_id.clone(),
387 responder: tx,
388 created_at: Instant::now(),
389 },
390 );
391 }
392
393 self.event_tx
395 .send(ControllerEvent::PermissionRequired {
396 session_id,
397 tool_use_id: request_id,
398 request,
399 turn_id,
400 })
401 .await
402 .map_err(|_| PermissionError::EventSendFailed)?;
403
404 Ok(rx)
405 }
406
407 pub async fn respond_to_request(
416 &self,
417 request_id: &str,
418 response: PermissionPanelResponse,
419 ) -> Result<(), PermissionError> {
420 let pending = {
421 let mut pending = self.pending_requests.lock().await;
422 pending
423 .remove(request_id)
424 .ok_or(PermissionError::NotFound)?
425 };
426
427 if response.granted
429 && let Some(ref g) = response.grant
430 {
431 self.add_grant(pending.session_id, g.clone()).await;
432 }
433
434 pending
435 .responder
436 .send(response)
437 .map_err(|_| PermissionError::SendFailed)
438 }
439
440 pub async fn cancel(&self, request_id: &str) -> Result<(), PermissionError> {
451 let mut pending = self.pending_requests.lock().await;
452 if pending.remove(request_id).is_some() {
453 Ok(())
455 } else {
456 Err(PermissionError::NotFound)
457 }
458 }
459
460 pub async fn pending_for_session(&self, session_id: i64) -> Vec<PendingPermissionInfo> {
468 let pending = self.pending_requests.lock().await;
469 pending
470 .iter()
471 .filter(|(_, req)| req.session_id == session_id)
472 .map(|(tool_use_id, req)| PendingPermissionInfo {
473 tool_use_id: tool_use_id.clone(),
474 session_id: req.session_id,
475 request: req.request.clone(),
476 turn_id: req.turn_id.clone(),
477 })
478 .collect()
479 }
480
481 pub async fn is_granted(&self, session_id: i64, request: &PermissionRequest) -> bool {
493 self.check(session_id, request).await
494 }
495
496 pub async fn register_batch(
513 &self,
514 session_id: i64,
515 requests: Vec<PermissionRequest>,
516 turn_id: Option<TurnId>,
517 ) -> Result<oneshot::Receiver<BatchPermissionResponse>, PermissionError> {
518 let auto_approved = self.check_batch(session_id, &requests).await;
520
521 let needs_approval: Vec<_> = requests
523 .iter()
524 .filter(|r| !auto_approved.contains(&r.id))
525 .cloned()
526 .collect();
527
528 if needs_approval.is_empty() {
530 let (tx, rx) = oneshot::channel();
531 let response =
532 BatchPermissionResponse::with_auto_approved(generate_batch_id(), auto_approved);
533 let _ = tx.send(response);
534 return Ok(rx);
535 }
536
537 let batch_id = generate_batch_id();
538 let (tx, rx) = oneshot::channel();
539
540 let batch = BatchPermissionRequest::new(batch_id.clone(), needs_approval.clone());
542
543 {
545 let mut pending = self.pending_batches.lock().await;
546
547 if pending.len() >= PENDING_CLEANUP_THRESHOLD {
549 let now = Instant::now();
550 pending.retain(|id, batch| {
551 let keep = now.duration_since(batch.created_at) < PENDING_MAX_AGE;
552 if !keep {
553 tracing::warn!(
554 batch_id = %id,
555 age_secs = now.duration_since(batch.created_at).as_secs(),
556 "Cleaning up stale pending batch permission request"
557 );
558 }
559 keep
560 });
561 }
562
563 pending.insert(
564 batch_id.clone(),
565 PendingBatch {
566 session_id,
567 requests: needs_approval,
568 turn_id: turn_id.clone(),
569 responder: tx,
570 created_at: Instant::now(),
571 },
572 );
573 }
574
575 self.event_tx
577 .send(ControllerEvent::BatchPermissionRequired {
578 session_id,
579 batch,
580 turn_id,
581 })
582 .await
583 .map_err(|_| PermissionError::EventSendFailed)?;
584
585 Ok(rx)
586 }
587
588 pub async fn respond_to_batch(
597 &self,
598 batch_id: &str,
599 mut response: BatchPermissionResponse,
600 ) -> Result<(), PermissionError> {
601 let pending = {
602 let mut pending = self.pending_batches.lock().await;
603 pending.remove(batch_id).ok_or(PermissionError::NotFound)?
604 };
605
606 if !response.approved_grants.is_empty() {
608 self.add_grants(pending.session_id, response.approved_grants.clone())
609 .await;
610 }
611
612 response.batch_id = batch_id.to_string();
614
615 pending
616 .responder
617 .send(response)
618 .map_err(|_| PermissionError::SendFailed)
619 }
620
621 pub async fn cancel_batch(&self, batch_id: &str) -> Result<(), PermissionError> {
632 let mut pending = self.pending_batches.lock().await;
633 if pending.remove(batch_id).is_some() {
634 Ok(())
636 } else {
637 Err(PermissionError::NotFound)
638 }
639 }
640
641 pub async fn cancel_session(&self, session_id: i64) {
652 {
654 let mut pending = self.pending_requests.lock().await;
655 pending.retain(|_, p| p.session_id != session_id);
656 }
657
658 {
660 let mut pending = self.pending_batches.lock().await;
661 pending.retain(|_, p| p.session_id != session_id);
662 }
663 }
664
665 pub async fn clear_session(&self, session_id: i64) {
670 self.cancel_session(session_id).await;
671 self.clear_grants(session_id).await;
672 }
673
674 pub async fn has_pending(&self, session_id: i64) -> bool {
682 let individual_pending = {
683 let pending = self.pending_requests.lock().await;
684 pending.values().any(|p| p.session_id == session_id)
685 };
686
687 if individual_pending {
688 return true;
689 }
690
691 let pending = self.pending_batches.lock().await;
692 pending.values().any(|p| p.session_id == session_id)
693 }
694
695 pub async fn pending_count(&self) -> usize {
697 let individual = self.pending_requests.lock().await.len();
698 let batch = self.pending_batches.lock().await.len();
699 individual + batch
700 }
701
702 pub async fn pending_request_ids(&self, session_id: i64) -> Vec<String> {
704 let pending = self.pending_requests.lock().await;
705 pending
706 .iter()
707 .filter(|(_, p)| p.session_id == session_id)
708 .map(|(id, _)| id.clone())
709 .collect()
710 }
711
712 pub async fn pending_batch_ids(&self, session_id: i64) -> Vec<String> {
714 let pending = self.pending_batches.lock().await;
715 pending
716 .iter()
717 .filter(|(_, p)| p.session_id == session_id)
718 .map(|(id, _)| id.clone())
719 .collect()
720 }
721}
722
723#[cfg(test)]
724mod tests {
725 use super::*;
726 use crate::permissions::PermissionLevel;
727
728 fn create_read_request(id: &str, path: &str) -> PermissionRequest {
729 PermissionRequest::file_read(id, path)
730 }
731
732 fn create_write_request(id: &str, path: &str) -> PermissionRequest {
733 PermissionRequest::file_write(id, path)
734 }
735
736 #[tokio::test]
737 async fn test_add_and_check_grant() {
738 let (tx, _rx) = mpsc::channel(10);
739 let registry = PermissionRegistry::new(tx);
740
741 let grant = Grant::read_path("/project/src", true);
742 registry.add_grant(1, grant).await;
743
744 let request = create_read_request("req-1", "/project/src/main.rs");
745 assert!(registry.check(1, &request).await);
746
747 assert!(!registry.check(2, &request).await);
749 }
750
751 #[tokio::test]
752 async fn test_level_hierarchy() {
753 let (tx, _rx) = mpsc::channel(10);
754 let registry = PermissionRegistry::new(tx);
755
756 let grant = Grant::write_path("/project", true);
758 registry.add_grant(1, grant).await;
759
760 let read_request = create_read_request("req-1", "/project/file.rs");
762 assert!(registry.check(1, &read_request).await);
763
764 let write_request = create_write_request("req-2", "/project/file.rs");
766 assert!(registry.check(1, &write_request).await);
767 }
768
769 #[tokio::test]
770 async fn test_level_hierarchy_insufficient() {
771 let (tx, _rx) = mpsc::channel(10);
772 let registry = PermissionRegistry::new(tx);
773
774 let grant = Grant::read_path("/project", true);
776 registry.add_grant(1, grant).await;
777
778 let write_request = create_write_request("req-1", "/project/file.rs");
780 assert!(!registry.check(1, &write_request).await);
781 }
782
783 #[tokio::test]
784 async fn test_recursive_path_grant() {
785 let (tx, _rx) = mpsc::channel(10);
786 let registry = PermissionRegistry::new(tx);
787
788 let grant = Grant::read_path("/project", true);
790 registry.add_grant(1, grant).await;
791
792 let request = create_read_request("req-1", "/project/src/utils/mod.rs");
794 assert!(registry.check(1, &request).await);
795 }
796
797 #[tokio::test]
798 async fn test_non_recursive_path_grant() {
799 let (tx, _rx) = mpsc::channel(10);
800 let registry = PermissionRegistry::new(tx);
801
802 let grant = Grant::read_path("/project/src", false);
804 registry.add_grant(1, grant).await;
805
806 let direct = create_read_request("req-1", "/project/src/main.rs");
808 assert!(registry.check(1, &direct).await);
809
810 let nested = create_read_request("req-2", "/project/src/utils/mod.rs");
812 assert!(!registry.check(1, &nested).await);
813 }
814
815 #[tokio::test]
816 async fn test_check_batch() {
817 let (tx, _rx) = mpsc::channel(10);
818 let registry = PermissionRegistry::new(tx);
819
820 let grant = Grant::read_path("/project/src", true);
822 registry.add_grant(1, grant).await;
823
824 let requests = vec![
825 create_read_request("req-1", "/project/src/main.rs"),
826 create_read_request("req-2", "/project/tests/test.rs"), create_read_request("req-3", "/project/src/lib.rs"),
828 ];
829
830 let granted = registry.check_batch(1, &requests).await;
831
832 assert!(granted.contains("req-1"));
833 assert!(!granted.contains("req-2"));
834 assert!(granted.contains("req-3"));
835 }
836
837 #[tokio::test]
838 async fn test_request_permission_auto_approve() {
839 let (tx, mut rx) = mpsc::channel(10);
840 let registry = PermissionRegistry::new(tx);
841
842 let grant = Grant::read_path("/project", true);
844 registry.add_grant(1, grant).await;
845
846 let request = create_read_request("req-1", "/project/file.rs");
848 let result_rx = registry.request_permission(1, request, None).await.unwrap();
849
850 let response = result_rx.await.unwrap();
852 assert!(response.granted);
853
854 assert!(rx.try_recv().is_err());
856 }
857
858 #[tokio::test]
859 async fn test_request_permission_needs_approval() {
860 let (tx, mut rx) = mpsc::channel(10);
861 let registry = PermissionRegistry::new(tx);
862
863 let request = create_read_request("req-1", "/project/file.rs");
865 let result_rx = registry.request_permission(1, request, None).await.unwrap();
866
867 let event = rx.recv().await.unwrap();
869 if let ControllerEvent::PermissionRequired { tool_use_id, .. } = event {
870 assert_eq!(tool_use_id, "req-1");
871 } else {
872 panic!("Expected PermissionRequired event");
873 }
874
875 let grant = Grant::read_path("/project", true);
877 let response = PermissionPanelResponse {
878 granted: true,
879 grant: Some(grant),
880 message: None,
881 };
882 registry
883 .respond_to_request("req-1", response)
884 .await
885 .unwrap();
886
887 let response = result_rx.await.unwrap();
889 assert!(response.granted);
890
891 let new_request = create_read_request("req-2", "/project/other.rs");
893 assert!(registry.check(1, &new_request).await);
894 }
895
896 #[tokio::test]
897 async fn test_request_permission_denied() {
898 let (tx, mut rx) = mpsc::channel(10);
899 let registry = PermissionRegistry::new(tx);
900
901 let request = create_read_request("req-1", "/project/file.rs");
902 let result_rx = registry.request_permission(1, request, None).await.unwrap();
903
904 let _ = rx.recv().await.unwrap();
906
907 let response = PermissionPanelResponse {
909 granted: false,
910 grant: None,
911 message: None,
912 };
913 registry
914 .respond_to_request("req-1", response)
915 .await
916 .unwrap();
917
918 let response = result_rx.await.unwrap();
920 assert!(!response.granted);
921 }
922
923 #[tokio::test]
924 async fn test_register_batch() {
925 let (tx, mut rx) = mpsc::channel(10);
926 let registry = PermissionRegistry::new(tx);
927
928 let requests = vec![
929 create_read_request("req-1", "/project/src/main.rs"),
930 create_read_request("req-2", "/project/src/lib.rs"),
931 ];
932
933 let result_rx = registry.register_batch(1, requests, None).await.unwrap();
934
935 let event = rx.recv().await.unwrap();
937 let batch_id = if let ControllerEvent::BatchPermissionRequired { batch, .. } = event {
938 assert_eq!(batch.requests.len(), 2);
939 assert!(!batch.suggested_grants.is_empty());
940 batch.batch_id.clone()
941 } else {
942 panic!("Expected BatchPermissionRequired event");
943 };
944
945 let grant = Grant::read_path("/project/src", true);
947 let response = BatchPermissionResponse::all_granted(&batch_id, vec![grant]);
948 registry
949 .respond_to_batch(&batch_id, response)
950 .await
951 .unwrap();
952
953 let result = result_rx.await.unwrap();
955 assert!(!result.approved_grants.is_empty());
956 }
957
958 #[tokio::test]
959 async fn test_register_batch_partial_auto_approve() {
960 let (tx, mut rx) = mpsc::channel(10);
961 let registry = PermissionRegistry::new(tx);
962
963 let grant = Grant::read_path("/project/src", true);
965 registry.add_grant(1, grant).await;
966
967 let requests = vec![
968 create_read_request("req-1", "/project/src/main.rs"), create_read_request("req-2", "/project/tests/test.rs"), ];
971
972 let result_rx = registry.register_batch(1, requests, None).await.unwrap();
973
974 let event = rx.recv().await.unwrap();
976 let batch_id = if let ControllerEvent::BatchPermissionRequired { batch, .. } = event {
977 assert_eq!(batch.requests.len(), 1);
978 assert_eq!(batch.requests[0].id, "req-2");
979 batch.batch_id.clone()
980 } else {
981 panic!("Expected BatchPermissionRequired event");
982 };
983
984 let grant = Grant::read_path("/project/tests", true);
986 let response = BatchPermissionResponse::all_granted(&batch_id, vec![grant]);
987 registry
988 .respond_to_batch(&batch_id, response)
989 .await
990 .unwrap();
991
992 let _ = result_rx.await.unwrap();
993 }
994
995 #[tokio::test]
996 async fn test_register_batch_all_auto_approved() {
997 let (tx, mut rx) = mpsc::channel(10);
998 let registry = PermissionRegistry::new(tx);
999
1000 let grant = Grant::read_path("/project", true);
1002 registry.add_grant(1, grant).await;
1003
1004 let requests = vec![
1005 create_read_request("req-1", "/project/src/main.rs"),
1006 create_read_request("req-2", "/project/tests/test.rs"),
1007 ];
1008
1009 let result_rx = registry.register_batch(1, requests, None).await.unwrap();
1010
1011 let result = result_rx.await.unwrap();
1013 assert!(result.auto_approved.contains("req-1"));
1014 assert!(result.auto_approved.contains("req-2"));
1015
1016 assert!(rx.try_recv().is_err());
1018 }
1019
1020 #[tokio::test]
1021 async fn test_revoke_grants() {
1022 let (tx, _rx) = mpsc::channel(10);
1023 let registry = PermissionRegistry::new(tx);
1024
1025 let grant1 = Grant::read_path("/project/src", true);
1026 let grant2 = Grant::read_path("/project/tests", true);
1027 registry.add_grant(1, grant1).await;
1028 registry.add_grant(1, grant2).await;
1029
1030 let target = GrantTarget::path("/project/src", true);
1032 let revoked = registry.revoke_grants(1, &target).await;
1033 assert_eq!(revoked, 1);
1034
1035 let request1 = create_read_request("req-1", "/project/src/file.rs");
1037 assert!(!registry.check(1, &request1).await);
1038
1039 let request2 = create_read_request("req-2", "/project/tests/test.rs");
1041 assert!(registry.check(1, &request2).await);
1042 }
1043
1044 #[tokio::test]
1045 async fn test_clear_session() {
1046 let (tx, _rx) = mpsc::channel(10);
1047 let registry = PermissionRegistry::new(tx);
1048
1049 let grant = Grant::read_path("/project", true);
1050 registry.add_grant(1, grant).await;
1051
1052 registry.clear_session(1).await;
1054
1055 let request = create_read_request("req-1", "/project/file.rs");
1057 assert!(!registry.check(1, &request).await);
1058 }
1059
1060 #[tokio::test]
1061 async fn test_cancel_session() {
1062 let (tx, _rx) = mpsc::channel(10);
1063 let registry = PermissionRegistry::new(tx);
1064
1065 let request = create_read_request("req-1", "/project/file.rs");
1067 let result_rx = registry.request_permission(1, request, None).await.unwrap();
1068
1069 assert!(registry.has_pending(1).await);
1070
1071 registry.cancel_session(1).await;
1073
1074 assert!(!registry.has_pending(1).await);
1076
1077 assert!(result_rx.await.is_err());
1079 }
1080
1081 #[tokio::test]
1082 async fn test_domain_grant() {
1083 let (tx, _rx) = mpsc::channel(10);
1084 let registry = PermissionRegistry::new(tx);
1085
1086 let grant = Grant::domain("*.github.com", PermissionLevel::Read);
1087 registry.add_grant(1, grant).await;
1088
1089 let request =
1090 PermissionRequest::network_access("req-1", "api.github.com", PermissionLevel::Read);
1091 assert!(registry.check(1, &request).await);
1092
1093 let other_domain =
1094 PermissionRequest::network_access("req-2", "api.gitlab.com", PermissionLevel::Read);
1095 assert!(!registry.check(1, &other_domain).await);
1096 }
1097
1098 #[tokio::test]
1099 async fn test_command_grant() {
1100 let (tx, _rx) = mpsc::channel(10);
1101 let registry = PermissionRegistry::new(tx);
1102
1103 let grant = Grant::command("git *", PermissionLevel::Execute);
1104 registry.add_grant(1, grant).await;
1105
1106 let request = PermissionRequest::command_execute("req-1", "git status");
1107 assert!(registry.check(1, &request).await);
1108
1109 let other_cmd = PermissionRequest::command_execute("req-2", "docker run nginx");
1110 assert!(!registry.check(1, &other_cmd).await);
1111 }
1112
1113 #[tokio::test]
1114 async fn test_find_satisfying_grant() {
1115 let (tx, _rx) = mpsc::channel(10);
1116 let registry = PermissionRegistry::new(tx);
1117
1118 let grant = Grant::write_path("/project", true);
1119 registry.add_grant(1, grant.clone()).await;
1120
1121 let request = create_read_request("req-1", "/project/file.rs");
1122 let found = registry.find_satisfying_grant(1, &request).await;
1123 assert!(found.is_some());
1124 assert_eq!(found.unwrap().target, grant.target);
1125 }
1126
1127 #[tokio::test]
1128 async fn test_pending_counts() {
1129 let (tx, _rx) = mpsc::channel(10);
1130 let registry = PermissionRegistry::new(tx);
1131
1132 assert_eq!(registry.pending_count().await, 0);
1133
1134 let request = create_read_request("req-1", "/project/file.rs");
1135 let _ = registry.request_permission(1, request, None).await;
1136
1137 assert_eq!(registry.pending_count().await, 1);
1138
1139 let ids = registry.pending_request_ids(1).await;
1140 assert_eq!(ids.len(), 1);
1141 assert_eq!(ids[0], "req-1");
1142 }
1143}