use bashkit::{Bash, FileSystem};
async fn run(script: &str) -> bashkit::ExecResult {
let mut bash = Bash::new();
bash.exec(script).await.unwrap()
}
#[tokio::test]
async fn history_shows_previous_commands() {
let mut bash = Bash::new();
bash.exec("echo hello").await.unwrap();
bash.exec("echo world").await.unwrap();
let result = bash.exec("history").await.unwrap();
assert!(
result.stdout.contains("echo hello"),
"should contain first command"
);
assert!(
result.stdout.contains("echo world"),
"should contain second command"
);
}
#[tokio::test]
async fn history_n_limits_output() {
let mut bash = Bash::new();
bash.exec("echo a").await.unwrap();
bash.exec("echo b").await.unwrap();
bash.exec("echo c").await.unwrap();
let result = bash.exec("history 2").await.unwrap();
assert!(
!result.stdout.contains("echo a"),
"should not contain first command"
);
assert!(result.stdout.contains("echo b") || result.stdout.contains("echo c"));
}
#[tokio::test]
async fn history_clear() {
let mut bash = Bash::new();
bash.exec("echo hello").await.unwrap();
bash.exec("history -c").await.unwrap();
let result = bash.exec("history").await.unwrap();
assert!(
!result.stdout.contains("echo hello"),
"history should be cleared"
);
}
#[tokio::test]
async fn history_grep() {
let mut bash = Bash::new();
bash.exec("echo hello").await.unwrap();
bash.exec("ls /tmp").await.unwrap();
bash.exec("echo world").await.unwrap();
let result = bash.exec("history --grep echo").await.unwrap();
assert!(result.stdout.contains("echo hello"));
assert!(result.stdout.contains("echo world"));
assert!(!result.stdout.contains("ls /tmp"));
}
#[tokio::test]
async fn history_failed() {
let mut bash = Bash::new();
bash.exec("true").await.unwrap();
bash.exec("false").await.unwrap();
let result = bash.exec("history --failed").await.unwrap();
assert!(
result.stdout.contains("false"),
"should show failed command"
);
assert!(
!result.stdout.contains("true"),
"should not show successful command"
);
}
#[tokio::test]
async fn history_cwd_filter() {
let mut bash = Bash::new();
bash.exec("echo in-home").await.unwrap();
bash.exec("cd /tmp && echo in-tmp").await.unwrap();
let result = bash.exec("history --cwd /tmp").await.unwrap();
assert!(result.stdout.contains("echo in-tmp") || result.stdout.contains("cd /tmp"));
}
#[tokio::test]
async fn history_invalid_option() {
let result = run("history --invalid").await;
assert_eq!(result.exit_code, 1);
assert!(result.stderr.contains("unrecognized option"));
}
#[tokio::test]
async fn history_grep_missing_arg() {
let result = run("history --grep").await;
assert_eq!(result.exit_code, 1);
assert!(result.stderr.contains("requires an argument"));
}
#[tokio::test]
async fn history_since_filter() {
let mut bash = Bash::new();
bash.exec("echo recent").await.unwrap();
let result = bash.exec("history --since 1h").await.unwrap();
assert!(
result.stdout.contains("echo recent"),
"recent entry should appear"
);
}
#[tokio::test]
async fn history_since_invalid_duration() {
let result = run("history --since xyz").await;
assert_eq!(result.exit_code, 1);
assert!(result.stderr.contains("invalid duration"));
}
#[tokio::test]
async fn history_numbered_output() {
let mut bash = Bash::new();
bash.exec("echo test").await.unwrap();
let result = bash.exec("history").await.unwrap();
assert!(
result.stdout.contains(" 1 echo test"),
"output should be numbered: {}",
result.stdout
);
}
#[tokio::test]
async fn history_persists_to_vfs() {
let mut bash = Bash::builder()
.history_file("/home/user/.bash_history")
.build();
bash.exec("echo persisted").await.unwrap();
let result = bash.exec("cat /home/user/.bash_history").await.unwrap();
assert!(
result.stdout.contains("echo persisted"),
"history file should contain command: {}",
result.stdout
);
}
#[tokio::test]
async fn history_loads_from_vfs() {
use std::sync::Arc;
let fs = Arc::new(bashkit::InMemoryFs::new());
let history_content = "1700000000|0|10|/home/user|echo preloaded\n";
fs.mkdir(std::path::Path::new("/home/user"), true)
.await
.unwrap();
fs.write_file(
std::path::Path::new("/home/user/.bash_history"),
history_content.as_bytes(),
)
.await
.unwrap();
let mut bash = Bash::builder()
.fs(fs)
.history_file("/home/user/.bash_history")
.build();
let result = bash.exec("history").await.unwrap();
assert!(
result.stdout.contains("echo preloaded"),
"should load preexisting history: {}",
result.stdout
);
}
#[tokio::test]
async fn history_empty_when_no_commands() {
let result = run("history").await;
assert_eq!(result.stdout, "");
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn history_does_not_record_comments() {
let mut bash = Bash::new();
bash.exec("# this is a comment").await.unwrap();
bash.exec("echo visible").await.unwrap();
let result = bash.exec("history").await.unwrap();
assert!(!result.stdout.contains("comment"));
assert!(result.stdout.contains("echo visible"));
}
#[tokio::test]
async fn history_does_not_record_blank_lines() {
let mut bash = Bash::new();
bash.exec(" ").await.unwrap();
bash.exec("echo visible").await.unwrap();
let result = bash.exec("history").await.unwrap();
let lines: Vec<&str> = result.stdout.lines().collect();
assert_eq!(lines.len(), 1, "should only have one entry: {:?}", lines);
}