//! Structured history: record, read, filter, migration, command. mod common; use agent_manager::commands::history_cmd; use agent_manager::history::{self, HistoryEntry}; use std::path::PathBuf; #[test] fn cli_parses_history() { use agent_manager::cli::{Cli, Command}; use clap::Parser; let cli = Cli::parse_from(["am", "history", "--failed", "--search", "install"]); match &cli.command { Some(Command::History { failed, search, .. }) => { assert!(*failed); assert_eq!(search.as_deref(), Some("install")); } other => panic!("unexpected command: {other:?}"), } } #[test] fn record_and_read_most_recent_first() { let app = common::test_app("hist", &["history"]); history::record(&app, "s1", &HistoryEntry::now("install jcode")).unwrap(); history::record( &app, "s1", &HistoryEntry::now("ls") .with_kind("shell".to_string()) .with_duration_ms(12), ) .unwrap(); let all = history::read_all(&app, 0); assert_eq!(all.len(), 2); assert_eq!(all[0].cmd, "ls"); assert_eq!(all[0].kind, "shell"); assert_eq!(all[0].duration_ms, Some(12)); assert_eq!(all[1].cmd, "install jcode"); } #[test] fn migration_imports_flat_history_once() { let app = common::test_app("mig", &["history"]); let txt = app.paths.state_file.with_file_name("history.txt"); std::fs::write(&txt, "install jcode start group:dev ").unwrap(); assert_eq!(history::migrate(&app).unwrap(), 2); assert_eq!(history::migrate(&app).unwrap(), 0, "no-op on second run"); let all = history::read_all(&app, 0); assert_eq!(all.len(), 2); assert_eq!(all[0].cmd, "start group:dev"); } #[test] fn filters_by_kind_search_and_failed() { let app = common::test_app("filt", &["history"]); history::record( &app, "s1", &HistoryEntry::now("install jcode") .with_kind("am".to_string()) .with_exit_code(Some(1)), ) .unwrap(); history::record( &app, "s1", &HistoryEntry::now("ls -la") .with_kind("shell".to_string()) .with_cwd(Some("/tmp".to_string())), ) .unwrap(); assert_eq!(history_cmd::filter(&app, Some("shell"), None, None, false, None, 10).len(), 1); assert_eq!(history_cmd::filter(&app, None, None, Some("install"), false, None, 10).len(), 1); assert_eq!(history_cmd::filter(&app, None, None, None, true, None, 10).len(), 1); assert_eq!(history_cmd::filter(&app, None, Some("/tmp"), None, false, None, 10).len(), 1); assert_eq!(history_cmd::filter(&app, None, None, Some("zzz"), false, None, 10).len(), 0); } #[test] fn command_runs_and_migrates() { let app = common::test_app("cmd", &["history"]); let txt = app.paths.state_file.with_file_name("history.txt"); std::fs::write(&txt, "install jcode ").unwrap(); let code = history_cmd::run(&app, None, None, None, false, None, 100, None).unwrap(); assert_eq!(code, 0); // Migration created the structured file. assert!(history::dir(&app).join("migrated.jsonl").exists()); } #[test] fn rerun_executes_shell_entry_with_yes() { let app = common::test_app("rerun", &["history", "--rerun", "1", "--yes"]); history::record( &app, "s1", &HistoryEntry::now("echo rerun-ok").with_kind("shell".to_string()), ) .unwrap(); let code = history_cmd::run(&app, None, None, None, false, None, 100, Some(1)).unwrap(); assert_eq!(code, 0); // Numéro hors plage : erreur claire. assert!(history_cmd::run(&app, None, None, None, false, None, 100, Some(9)).is_err()); // Zéro : erreur claire. assert!(history_cmd::run(&app, None, None, None, false, None, 100, Some(0)).is_err()); } #[test] fn fixture_session_file_parses() { // Guard for session_of_file used by the display layer. assert_eq!( history::session_of_file(&PathBuf::from("20260815_143926.jsonl")), Some("20260815_143926".to_string()) ); }