//! 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()); // am start contextuel (issue #30) : résolution pure sans changer de cwd. use agent_manager::commands::run_cmd; assert_eq!( run_cmd::resolve_start_target(&app, &sub, None).unwrap(), "claude-code" ); assert_eq!( run_cmd::resolve_start_target(&app, &sub, Some("aider")).unwrap(), "aider" ); assert!(run_cmd::resolve_start_target(&app, &std::env::temp_dir(), None).is_err()); let _ = std::fs::remove_dir_all(&dir); }