feat: add main features
Agents for developpement added + frontend add + backend added. Git viewer created + agent and template creator + layout and project creator
This commit is contained in:
169
crates/infrastructure/tests/profile_store.rs
Normal file
169
crates/infrastructure/tests/profile_store.rs
Normal file
@ -0,0 +1,169 @@
|
||||
//! L5 integration tests for [`FsProfileStore`] against a real temp directory,
|
||||
//! using a real [`LocalFileSystem`] so the full persistence path (camelCase
|
||||
//! `profiles.json`, upsert, delete, first-run marker) is exercised end-to-end.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::ids::ProfileId;
|
||||
use domain::ports::{FileSystem, ProfileStore, RemotePath, StoreError};
|
||||
use domain::profile::{AgentProfile, ContextInjection};
|
||||
use infrastructure::{FsProfileStore, LocalFileSystem};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// A unique scratch 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-l5-profile-{}", Uuid::new_v4()));
|
||||
std::fs::create_dir_all(&p).unwrap();
|
||||
Self(p)
|
||||
}
|
||||
fn app_data_dir(&self) -> String {
|
||||
self.0.to_string_lossy().into_owned()
|
||||
}
|
||||
fn child(&self, name: &str) -> RemotePath {
|
||||
RemotePath::new(self.0.join(name).to_string_lossy().into_owned())
|
||||
}
|
||||
}
|
||||
impl Drop for TempDir {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn store(tmp: &TempDir) -> FsProfileStore {
|
||||
let fs: Arc<dyn FileSystem> = Arc::new(LocalFileSystem::new());
|
||||
FsProfileStore::new(fs, tmp.app_data_dir())
|
||||
}
|
||||
|
||||
fn sample(id: u128, name: &str, command: &str) -> AgentProfile {
|
||||
AgentProfile::new(
|
||||
ProfileId::from_uuid(Uuid::from_u128(id)),
|
||||
name,
|
||||
command,
|
||||
Vec::new(),
|
||||
ContextInjection::convention_file("CLAUDE.md").unwrap(),
|
||||
Some(format!("{command} --version")),
|
||||
"{projectRoot}",
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn save_then_list_roundtrips() {
|
||||
let tmp = TempDir::new();
|
||||
let store = store(&tmp);
|
||||
|
||||
let p = sample(1, "Claude", "claude");
|
||||
store.save(&p).await.unwrap();
|
||||
|
||||
let listed = store.list().await.unwrap();
|
||||
assert_eq!(listed, vec![p]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn save_upserts_by_id_without_duplicating() {
|
||||
let tmp = TempDir::new();
|
||||
let store = store(&tmp);
|
||||
|
||||
let first = sample(1, "before", "claude");
|
||||
store.save(&first).await.unwrap();
|
||||
|
||||
let updated = sample(1, "after", "claude-renamed");
|
||||
store.save(&updated).await.unwrap();
|
||||
|
||||
let listed = store.list().await.unwrap();
|
||||
assert_eq!(listed.len(), 1, "upsert must not duplicate by id");
|
||||
assert_eq!(listed[0], updated);
|
||||
assert_eq!(listed[0].name, "after");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_removes_profile() {
|
||||
let tmp = TempDir::new();
|
||||
let store = store(&tmp);
|
||||
|
||||
let a = sample(1, "A", "a");
|
||||
let b = sample(2, "B", "b");
|
||||
store.save(&a).await.unwrap();
|
||||
store.save(&b).await.unwrap();
|
||||
|
||||
store.delete(a.id).await.unwrap();
|
||||
|
||||
let listed = store.list().await.unwrap();
|
||||
assert_eq!(listed, vec![b]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_unknown_is_not_found() {
|
||||
let tmp = TempDir::new();
|
||||
let store = store(&tmp);
|
||||
store.save(&sample(1, "A", "a")).await.unwrap();
|
||||
|
||||
let err = store
|
||||
.delete(ProfileId::from_uuid(Uuid::from_u128(999)))
|
||||
.await
|
||||
.expect_err("deleting unknown id fails");
|
||||
assert!(matches!(err, StoreError::NotFound), "got {err:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_configured_false_before_any_write() {
|
||||
let tmp = TempDir::new();
|
||||
let store = store(&tmp);
|
||||
// First run: no profiles.json yet.
|
||||
assert!(!store.is_configured().await.unwrap());
|
||||
assert!(store.list().await.unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_configured_true_after_save() {
|
||||
let tmp = TempDir::new();
|
||||
let store = store(&tmp);
|
||||
store.save(&sample(1, "A", "a")).await.unwrap();
|
||||
assert!(store.is_configured().await.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mark_configured_creates_file_with_empty_profiles() {
|
||||
let tmp = TempDir::new();
|
||||
let store = store(&tmp);
|
||||
|
||||
assert!(!store.is_configured().await.unwrap());
|
||||
store.mark_configured().await.unwrap();
|
||||
assert!(store.is_configured().await.unwrap(), "marker materialised");
|
||||
assert!(
|
||||
store.list().await.unwrap().is_empty(),
|
||||
"empty profile list recorded"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profiles_file_is_camelcase_versioned() {
|
||||
let tmp = TempDir::new();
|
||||
let store = store(&tmp);
|
||||
|
||||
let p = sample(1, "Claude", "claude");
|
||||
store.save(&p).await.unwrap();
|
||||
|
||||
let fs = LocalFileSystem::new();
|
||||
let bytes = fs.read(&tmp.child("profiles.json")).await.unwrap();
|
||||
let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
|
||||
|
||||
assert_eq!(json["version"], 1);
|
||||
let profiles = json
|
||||
.get("profiles")
|
||||
.and_then(|v| v.as_array())
|
||||
.expect("top-level `profiles` array");
|
||||
assert_eq!(profiles.len(), 1);
|
||||
|
||||
let entry = &profiles[0];
|
||||
assert_eq!(entry["name"], "Claude");
|
||||
assert_eq!(entry["command"], "claude");
|
||||
// camelCase fields, tagged contextInjection.
|
||||
assert!(entry.get("cwdTemplate").is_some(), "camelCase cwdTemplate");
|
||||
assert!(entry.get("cwd_template").is_none(), "no snake_case leak");
|
||||
assert_eq!(entry["contextInjection"]["strategy"], "conventionFile");
|
||||
assert_eq!(entry["contextInjection"]["target"], "CLAUDE.md");
|
||||
}
|
||||
Reference in New Issue
Block a user