//! stats + log: aggregation and filtering over the event journal. mod common; use agent_manager::app::App; use agent_manager::commands::log_cmd; use agent_manager::commands::stats_cmd; use agent_manager::events; fn app_with_events(tag: &str, lines: &[&str]) -> App { let app = common::test_app(tag, &["stats"]); let month = chrono::Utc::now().format("%Y%m").to_string(); let path = app.events_dir().join(format!("events-{month}.jsonl")); let mut text = String::new(); for l in lines { text.push_str(l); text.push('\n'); } std::fs::create_dir_all(app.events_dir()).unwrap(); std::fs::write(&path, text).unwrap(); app } #[test] fn stats_aggregate_counts_everything() { let app = app_with_events( "agg", &[ r#"{"ts":"2026-08-01T10:00:00Z","kind":"run","agent":"a","exit_code":0,"duration_s":10}"#, r#"{"ts":"2026-08-02T10:00:00Z","kind":"run","agent":"a","exit_code":0,"duration_s":30}"#, r#"{"ts":"2026-08-03T10:00:00Z","kind":"run","agent":"b","exit_code":1,"duration_s":5}"#, r#"{"ts":"2026-08-03T11:00:00Z","kind":"install","agent":"a"}"#, r#"{"ts":"2026-08-03T12:00:00Z","kind":"update","agent":"a"}"#, ], ); let rows = stats_cmd::aggregate(&app, None, None).unwrap(); let a = rows.iter().find(|r| r.agent == "a").unwrap(); assert_eq!(a.launches, 2); assert_eq!(a.total_duration_s, 40); assert_eq!(a.avg_duration_s, 20); assert_eq!(a.failures, 0); assert_eq!(a.installs, 1); assert_eq!(a.updates, 1); let b = rows.iter().find(|r| r.agent == "b").unwrap(); assert_eq!(b.failures, 1); assert_eq!(b.launches, 1); } #[test] fn stats_period_cuts_old_events() { let app = app_with_events( "period", &[r#"{"ts":"2020-01-01T10:00:00Z","kind":"run","agent":"old","exit_code":0}"#], ); assert!(stats_cmd::aggregate(&app, None, Some("30d")).unwrap().is_empty()); assert_eq!(stats_cmd::aggregate(&app, None, None).unwrap().len(), 1); assert!(stats_cmd::aggregate(&app, None, Some("7d")).unwrap().is_empty()); } #[test] fn log_filters_by_agent_kind_and_limit() { let app = app_with_events( "logf", &[ r#"{"ts":"2026-08-01T10:00:00Z","kind":"run","agent":"a","exit_code":0}"#, r#"{"ts":"2026-08-01T10:00:01Z","kind":"start","agent":"a","pid":42}"#, r#"{"ts":"2026-08-01T10:00:02Z","kind":"run","agent":"b","exit_code":1}"#, ], ); let all = log_cmd::filter_events(&app, None, None, None, 50).unwrap(); assert_eq!(all.len(), 3); assert_eq!(all[0].agent.as_deref(), Some("b"), "most recent first"); let only_a = log_cmd::filter_events(&app, Some("a"), None, None, 50).unwrap(); assert_eq!(only_a.len(), 2); let starts = log_cmd::filter_events(&app, None, Some("start"), None, 50).unwrap(); assert_eq!(starts.len(), 1); assert_eq!(starts[0].pid, Some(42)); let limited = log_cmd::filter_events(&app, None, None, None, 1).unwrap(); assert_eq!(limited.len(), 1); let since = log_cmd::filter_events(&app, None, None, Some("2026-09-01"), 50).unwrap(); assert!(since.is_empty()); } #[test] fn stats_command_runs() { let app = app_with_events( "cmd", &[r#"{"ts":"2026-08-01T10:00:00Z","kind":"run","agent":"a","exit_code":0}"#], ); let code = stats_cmd::run(&app, None, None).unwrap(); assert_eq!(code, 0); } #[test] fn journal_reads_back_through_events_api() { let app = app_with_events("api", &[]); let mut log = events::EventLog::new(app.events_dir()); log.append( &events::Event::now(events::EventKind::Run) .with_agent("x") .with_exit_code(0), ) .unwrap(); let all = events::read_events(&app.events_dir(), 0); assert_eq!(all.len(), 1); }