history : historique structure par session + am history (issue #7) - migration one-shot de history.txt, filtres kind/cwd/search/failed/session, enregistrement REPL avec duree et exit
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
//! 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).unwrap();
|
||||
assert_eq!(code, 0);
|
||||
// Migration created the structured file.
|
||||
assert!(history::dir(&app).join("migrated.jsonl").exists());
|
||||
}
|
||||
|
||||
#[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())
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user