use std::sync::{Arc, Mutex}; use application::{ ResolveAgentPermissions, ResolveAgentPermissionsInput, UpdateAgentPermissions, UpdateAgentPermissionsInput, UpdateProjectPermissions, UpdateProjectPermissionsInput, }; use async_trait::async_trait; use domain::ids::{AgentId, ProjectId}; use domain::ports::{PermissionStore, StoreError}; use domain::project::{Project, ProjectPath}; use domain::remote::RemoteRef; use domain::{PermissionSet, Posture, ProjectPermissions}; #[derive(Default)] struct FakePermissionStore { doc: Mutex, saves: Mutex, } #[async_trait] impl PermissionStore for FakePermissionStore { async fn load_permissions(&self, _project: &Project) -> Result { Ok(self.doc.lock().unwrap().clone()) } async fn save_permissions( &self, _project: &Project, permissions: &ProjectPermissions, ) -> Result<(), StoreError> { *self.doc.lock().unwrap() = permissions.clone(); *self.saves.lock().unwrap() += 1; Ok(()) } } fn project() -> Project { Project::new( ProjectId::new_random(), "permissions", ProjectPath::new("/home/me/proj").unwrap(), RemoteRef::local(), 1_700_000_000_000, ) .unwrap() } #[tokio::test] async fn update_project_permissions_replaces_defaults_and_persists() { let store = Arc::new(FakePermissionStore::default()); let use_case = UpdateProjectPermissions::new(store.clone()); let out = use_case .execute(UpdateProjectPermissionsInput { project: project(), permissions: Some(PermissionSet::new(vec![], Posture::Ask)), }) .await .unwrap(); assert_eq!( out.permissions.project_defaults.unwrap().fallback(), Posture::Ask ); assert_eq!(*store.saves.lock().unwrap(), 1); } #[tokio::test] async fn update_agent_permissions_adds_and_removes_sparse_override() { let store = Arc::new(FakePermissionStore::default()); let use_case = UpdateAgentPermissions::new(store.clone()); let agent = AgentId::new_random(); use_case .execute(UpdateAgentPermissionsInput { project: project(), agent_id: agent, permissions: Some(PermissionSet::new(vec![], Posture::Deny)), }) .await .unwrap(); assert_eq!( store .doc .lock() .unwrap() .agent_permissions(agent) .unwrap() .fallback(), Posture::Deny ); let out = use_case .execute(UpdateAgentPermissionsInput { project: project(), agent_id: agent, permissions: None, }) .await .unwrap(); assert!(out.permissions.agent_permissions(agent).is_none()); assert_eq!(*store.saves.lock().unwrap(), 2); } #[tokio::test] async fn resolve_agent_permissions_returns_effective_policy() { let agent = AgentId::new_random(); let store = Arc::new(FakePermissionStore { doc: Mutex::new(ProjectPermissions::new( Some(PermissionSet::new(vec![], Posture::Ask)), vec![], )), saves: Mutex::new(0), }); let use_case = ResolveAgentPermissions::new(store); let out = use_case .execute(ResolveAgentPermissionsInput { project: project(), agent_id: agent, }) .await .unwrap(); assert_eq!(out.effective.unwrap().fallback(), Posture::Ask); }