Files
IdeA/crates/infrastructure/tests/permission_store.rs
Blomios 27597eb64e feat(permissions): voie projection CLI (LP0→LP3) + checkpoint Codex/input
Jalon vert regroupant deux chantiers entrelacés dans le working tree,
indissociables au niveau fichier mais tous deux verts (cargo test
--workspace + tests frontend permissions au vert).

Permissions — voie « projection CLI » (advisory), complète :
- LP0 domaine pur : modèle PermissionSet/EffectivePermissions, resolve
  deny-wins + postures Allow<Ask<Deny (crates/domain/src/permission.rs).
- LP1 store : FsPermissionStore (.ideai/permissions.json).
- LP2 use cases : Get/Update project, Update agent override, Resolve.
- LP3 projecteurs Claude/Codex (settings.local.json / config.toml),
  câblage launch-path + PermissionProjectorRegistry, nettoyage des
  fichiers Replace orphelins au swap de profil (LP3-4), composition root
  + commandes Tauri, UI PermissionsPanel (projet + override agent).
- ports.rs : PermissionStore + FileSystem::remove_file (cleanup au swap).

Reste ouvert (hors scope, marqué dans le code) : LP4 enforcement OS
airtight (Landlock fichiers) + résumé de permissions injecté.

Inclut aussi le chantier Codex/input/sessions structurées en cours
(McpConfigStrategy, StructuredAdapter, gestion d'input) partageant les
mêmes fichiers (lifecycle.rs, commands.rs, dto.rs, state.rs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:39:18 +02:00

87 lines
2.5 KiB
Rust

//! L2 integration tests for [`FsPermissionStore`] against a real temp project.
use std::path::PathBuf;
use std::sync::Arc;
use domain::ids::{AgentId, ProjectId};
use domain::ports::{FileSystem, PermissionStore};
use domain::project::{Project, ProjectPath};
use domain::remote::RemoteRef;
use domain::{
AgentPermissionOverride, PermissionSet, Posture, ProjectPermissions, PERMISSIONS_VERSION,
};
use infrastructure::{FsPermissionStore, LocalFileSystem};
use uuid::Uuid;
/// A unique scratch project directory under the OS temp dir, cleaned up on drop.
struct TempDir(PathBuf);
impl TempDir {
fn new() -> Self {
let p = std::env::temp_dir().join(format!("idea-l2-permissions-{}", Uuid::new_v4()));
std::fs::create_dir_all(&p).unwrap();
Self(p)
}
fn project_root(&self) -> String {
self.0.to_string_lossy().into_owned()
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn store() -> FsPermissionStore {
let fs: Arc<dyn FileSystem> = Arc::new(LocalFileSystem::new());
FsPermissionStore::new(fs)
}
fn project(tmp: &TempDir) -> Project {
Project::new(
ProjectId::new_random(),
"permissions",
ProjectPath::new(tmp.project_root()).unwrap(),
RemoteRef::local(),
1_700_000_000_000,
)
.unwrap()
}
#[tokio::test]
async fn missing_permissions_file_returns_default_document() {
let tmp = TempDir::new();
let project = project(&tmp);
let loaded = store().load_permissions(&project).await.unwrap();
assert_eq!(loaded, ProjectPermissions::default());
assert_eq!(loaded.version, PERMISSIONS_VERSION);
}
#[tokio::test]
async fn save_then_load_roundtrips_project_defaults_and_agent_override() {
let tmp = TempDir::new();
let project = project(&tmp);
let agent = AgentId::new_random();
let doc = ProjectPermissions::new(
Some(PermissionSet::new(vec![], Posture::Ask)),
vec![AgentPermissionOverride::new(
agent,
PermissionSet::new(vec![], Posture::Deny),
)],
);
let store = store();
store.save_permissions(&project, &doc).await.unwrap();
let loaded = store.load_permissions(&project).await.unwrap();
assert_eq!(loaded, doc);
assert_eq!(loaded.resolve_for(agent).unwrap().fallback(), Posture::Deny);
let path = tmp.0.join(".ideai").join("permissions.json");
assert!(path.exists(), "store writes under .ideai/permissions.json");
}