projects : am projects (agregats par depot depuis le journal) + profils de projet dans la config (issue #29) - agent dominant, sessions, durees, cache projects.json rafraichi par doctor --fix
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
//! projects: aggregation from the journal, profiles from the config.
|
||||
|
||||
mod common;
|
||||
|
||||
use agent_manager::commands::projects_cmd;
|
||||
use agent_manager::projects;
|
||||
|
||||
#[test]
|
||||
fn aggregates_projects_from_fixture_events() {
|
||||
let app = common::test_app("proj", &["projects"]);
|
||||
let month = chrono::Utc::now().format("%Y%m").to_string();
|
||||
let path = app.events_dir().join(format!("events-{month}.jsonl"));
|
||||
std::fs::create_dir_all(app.events_dir()).unwrap();
|
||||
std::fs::write(
|
||||
&path,
|
||||
[
|
||||
r#"{"ts":"2026-08-01T10:00:00Z","kind":"run","agent":"claude-code","project":"/home/x/api","session":"s1","duration_s":10,"exit_code":0}"#,
|
||||
r#"{"ts":"2026-08-02T10:00:00Z","kind":"run","agent":"claude-code","project":"/home/x/api","session":"s2","duration_s":20,"exit_code":0}"#,
|
||||
r#"{"ts":"2026-08-03T10:00:00Z","kind":"run","agent":"aider","project":"/home/x/api","session":"s3","duration_s":5,"exit_code":0}"#,
|
||||
r#"{"ts":"2026-08-04T10:00:00Z","kind":"run","agent":"smelt","project":"/home/x/rusty","session":"s4","duration_s":7,"exit_code":0}"#,
|
||||
]
|
||||
.join("
|
||||
"),
|
||||
)
|
||||
.unwrap();
|
||||
let out = projects::aggregate(&app);
|
||||
assert_eq!(out.len(), 2);
|
||||
let api = out.iter().find(|s| s.root == "/home/x/api").unwrap();
|
||||
assert_eq!(api.agents, vec!["aider".to_string(), "claude-code".to_string()]);
|
||||
assert_eq!(api.dominant.as_deref(), Some("claude-code"));
|
||||
assert_eq!(api.sessions, 3);
|
||||
assert_eq!(api.launches, 3);
|
||||
assert_eq!(api.total_duration_s, 35);
|
||||
assert!(projects::cache_file(&app).exists());
|
||||
// La commande tourne (table + detail).
|
||||
assert_eq!(projects_cmd::run(&app, None).unwrap(), 0);
|
||||
assert_eq!(projects_cmd::run(&app, Some("api")).unwrap(), 0);
|
||||
assert!(projects_cmd::run(&app, Some("inconnu")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_config_parses_and_resolves() {
|
||||
let dir = std::env::temp_dir().join(format!("am-prof-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let root = dir.join("api");
|
||||
std::fs::create_dir_all(&root).unwrap();
|
||||
let cfg = common::write_config(
|
||||
&dir,
|
||||
&format!(
|
||||
r#"
|
||||
projects:
|
||||
api:
|
||||
root: '{}'
|
||||
default_agent: claude-code
|
||||
env:
|
||||
FOO: bar
|
||||
"#,
|
||||
root.display()
|
||||
),
|
||||
);
|
||||
use agent_manager::cli::Cli;
|
||||
use clap::Parser;
|
||||
let cli = Cli::parse_from(["am", "--config", cfg.to_str().unwrap(), "projects"]);
|
||||
let mut app = agent_manager::app::App::from_cli(cli).unwrap();
|
||||
common::isolate(&mut app, &dir);
|
||||
assert_eq!(app.config.projects.len(), 1);
|
||||
let sub = root.join("src");
|
||||
std::fs::create_dir_all(&sub).unwrap();
|
||||
let (name, profile) = projects::profile_for(&app, &sub).unwrap();
|
||||
assert_eq!(name, "api");
|
||||
assert_eq!(profile.default_agent.as_deref(), Some("claude-code"));
|
||||
assert_eq!(
|
||||
projects::default_agent_for_dir(&app, &sub).as_deref(),
|
||||
Some("claude-code")
|
||||
);
|
||||
// Hors du projet : pas de profil.
|
||||
assert!(projects::default_agent_for_dir(&app, &std::env::temp_dir()).is_none());
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
Reference in New Issue
Block a user