use std::{
env,
io::{Read, Write},
path::Path,
process::{Command, Stdio},
time::{SystemTime, UNIX_EPOCH},
};
use pxh::test_utils::pxh_path;
use tempfile::TempDir;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
fn pxh_command() -> Command {
let mut cmd = Command::new(pxh_path());
if let Ok(profile_file) = env::var("LLVM_PROFILE_FILE") {
cmd.env("LLVM_PROFILE_FILE", profile_file);
}
if let Ok(llvm_cov) = env::var("CARGO_LLVM_COV") {
cmd.env("CARGO_LLVM_COV", llvm_cov);
}
cmd
}
fn insert_test_command(db_path: &Path, command: &str, days_ago: Option<u32>) -> Result<()> {
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
let timestamp = match days_ago {
Some(days) => now - (days as u64 * 86400),
None => now,
};
let output = pxh_command()
.args([
"--db",
db_path.to_str().unwrap(),
"insert",
"--shellname",
"bash",
"--hostname",
"test-host",
"--username",
"test-user",
"--session-id",
"1",
"--start-unix-timestamp",
×tamp.to_string(),
command,
])
.output()?;
if !output.status.success() {
return Err(format!(
"Failed to insert command: {}",
String::from_utf8_lossy(&output.stderr)
)
.into());
}
let seal_output = pxh_command()
.args([
"--db",
db_path.to_str().unwrap(),
"seal",
"--session-id",
"1",
"--exit-status",
"0",
"--end-unix-timestamp",
&(timestamp + 100).to_string(),
])
.output()?;
if !seal_output.status.success() {
return Err(format!(
"Failed to seal command: {}",
String::from_utf8_lossy(&seal_output.stderr)
)
.into());
}
Ok(())
}
fn count_commands(db_path: &Path) -> Result<i64> {
use rusqlite::Connection;
let conn = Connection::open(db_path)?;
let count: i64 =
conn.prepare("SELECT COUNT(*) FROM command_history")?.query_row([], |row| row.get(0))?;
Ok(count)
}
fn spawn_sync_processes(
client_args: Vec<String>,
server_args: Vec<String>,
) -> Result<(std::process::Child, std::process::Child)> {
let mut server = pxh_command()
.args(server_args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let server_stdin = server.stdin.take().expect("Failed to get server stdin");
let server_stdout = server.stdout.take().expect("Failed to get server stdout");
std::thread::sleep(std::time::Duration::from_millis(50));
let client = pxh_command()
.args(client_args)
.stdin(server_stdout)
.stdout(server_stdin)
.stderr(Stdio::piped())
.spawn()?;
Ok((client, server))
}
fn create_test_db_with_commands(db_path: &Path, commands: &[&str]) -> Result<()> {
for (i, command) in commands.iter().enumerate() {
insert_test_command(db_path, command, None)?;
if i < commands.len() - 1 {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
Ok(())
}
fn create_test_db_pair(
temp_dir: &Path,
client_commands: &[&str],
server_commands: &[&str],
) -> Result<(std::path::PathBuf, std::path::PathBuf)> {
let client_db = temp_dir.join("client.db");
let server_db = temp_dir.join("server.db");
create_test_db_with_commands(&client_db, client_commands)?;
create_test_db_with_commands(&server_db, server_commands)?;
Ok((client_db, server_db))
}
#[test]
fn test_directory_sync() -> Result<()> {
let temp_dir = TempDir::new()?;
let sync_dir = temp_dir.path().join("sync_dir");
std::fs::create_dir(&sync_dir)?;
let db1 = sync_dir.join("db1.db");
let db2 = sync_dir.join("db2.db");
let output_db = temp_dir.path().join("output.db");
insert_test_command(&db1, "echo from_db1_1", None)?;
insert_test_command(&db1, "echo from_db1_2", None)?;
insert_test_command(&db2, "echo from_db2_1", None)?;
insert_test_command(&db2, "echo from_db2_2", None)?;
let output = pxh_command()
.args(["--db", output_db.to_str().unwrap(), "sync", sync_dir.to_str().unwrap()])
.output()?;
assert!(output.status.success());
assert_eq!(count_commands(&output_db)?, 4);
Ok(())
}
#[test]
fn test_directory_sync_ignores_since() -> Result<()> {
let temp_dir = TempDir::new()?;
let source_db = temp_dir.path().join("source.db");
let dest_db = temp_dir.path().join("dest.db");
insert_test_command(&source_db, "old command 1", Some(10))?;
insert_test_command(&source_db, "mid command 1", Some(5))?;
insert_test_command(&source_db, "recent command 1", Some(1))?;
insert_test_command(&source_db, "current command 1", Some(0))?;
let output = pxh_command()
.args([
"--db",
dest_db.to_str().unwrap(),
"sync",
"--since",
"3",
temp_dir.path().to_str().unwrap(),
])
.output()?;
assert!(output.status.success());
assert_eq!(count_commands(&dest_db)?, 4);
Ok(())
}
#[test]
fn test_bidirectional_sync_via_stdin_stdout() -> Result<()> {
let temp_dir = TempDir::new()?;
let (client_db, server_db) = create_test_db_pair(
temp_dir.path(),
&["echo client1", "echo client2", "echo unique_client"],
&["echo server1", "echo server2", "echo unique_server"],
)?;
let server_args = vec![
"--db".to_string(),
server_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--server".to_string(),
];
let client_args = vec![
"--db".to_string(),
client_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--stdin-stdout".to_string(),
];
let (client, server) = spawn_sync_processes(client_args, server_args)?;
let client_output = client.wait_with_output()?;
let server_output = server.wait_with_output()?;
assert!(
client_output.status.success(),
"Client failed: {}",
String::from_utf8_lossy(&client_output.stderr)
);
assert!(
server_output.status.success(),
"Server failed: {}",
String::from_utf8_lossy(&server_output.stderr)
);
let client_count = count_commands(&client_db)?;
let server_count = count_commands(&server_db)?;
assert_eq!(client_count, server_count);
assert_eq!(client_count, 6);
Ok(())
}
#[test]
fn test_send_only_sync() -> Result<()> {
let temp_dir = TempDir::new()?;
let (client_db, server_db) =
create_test_db_pair(temp_dir.path(), &["echo from_client1", "echo from_client2"], &[])?;
let server_args = vec![
"--db".to_string(),
server_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--server".to_string(),
];
let client_args = vec![
"--db".to_string(),
client_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--stdin-stdout".to_string(),
"--send-only".to_string(),
];
let (client, server) = spawn_sync_processes(client_args, server_args)?;
let client_output = client.wait_with_output()?;
let server_output = server.wait_with_output()?;
assert!(
client_output.status.success(),
"Client failed: {}",
String::from_utf8_lossy(&client_output.stderr)
);
assert!(
server_output.status.success(),
"Server failed: {}",
String::from_utf8_lossy(&server_output.stderr)
);
assert_eq!(count_commands(&server_db)?, 2);
assert_eq!(count_commands(&client_db)?, 2);
Ok(())
}
#[test]
fn test_receive_only_sync() -> Result<()> {
let temp_dir = TempDir::new()?;
let (client_db, server_db) =
create_test_db_pair(temp_dir.path(), &[], &["echo from_server1", "echo from_server2"])?;
let server_args = vec![
"--db".to_string(),
server_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--server".to_string(),
];
let client_args = vec![
"--db".to_string(),
client_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--stdin-stdout".to_string(),
"--receive-only".to_string(),
];
let (client, server) = spawn_sync_processes(client_args, server_args)?;
let client_output = client.wait_with_output()?;
let server_output = server.wait_with_output()?;
assert!(
client_output.status.success(),
"Client failed: {}",
String::from_utf8_lossy(&client_output.stderr)
);
assert!(
server_output.status.success(),
"Server failed: {}",
String::from_utf8_lossy(&server_output.stderr)
);
assert_eq!(count_commands(&client_db)?, 2);
assert_eq!(count_commands(&server_db)?, 2);
Ok(())
}
#[test]
fn test_sync_with_since_option() -> Result<()> {
let temp_dir = TempDir::new()?;
let client_db = temp_dir.path().join("client.db");
let server_db = temp_dir.path().join("server.db");
insert_test_command(&client_db, "echo old_client", Some(10))?;
insert_test_command(&client_db, "echo recent_client", Some(1))?;
insert_test_command(&server_db, "echo old_server", Some(10))?;
insert_test_command(&server_db, "echo medium_server", Some(5))?;
insert_test_command(&server_db, "echo recent_server", Some(1))?;
let server_args = vec![
"--db".to_string(),
server_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--server".to_string(),
"--since".to_string(),
"7".to_string(),
];
let client_args = vec![
"--db".to_string(),
client_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--stdin-stdout".to_string(),
"--since".to_string(),
"7".to_string(),
];
let (client, server) = spawn_sync_processes(client_args, server_args)?;
let client_output = client.wait_with_output()?;
let server_output = server.wait_with_output()?;
assert!(
client_output.status.success(),
"Client failed: {}",
String::from_utf8_lossy(&client_output.stderr)
);
assert!(
server_output.status.success(),
"Server failed: {}",
String::from_utf8_lossy(&server_output.stderr)
);
assert_eq!(count_commands(&client_db)?, 4);
assert_eq!(count_commands(&server_db)?, 4);
Ok(())
}
#[test]
fn test_sync_error_handling() -> Result<()> {
let temp_dir = TempDir::new()?;
let (client_db, server_db) =
create_test_db_pair(temp_dir.path(), &["echo test"], &["echo test"])?;
let server_args = vec![
"--db".to_string(),
server_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--server".to_string(),
];
let client_args = vec![
"--db".to_string(),
client_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--stdin-stdout".to_string(),
"--send-only".to_string(),
"--receive-only".to_string(),
];
let result = spawn_sync_processes(client_args, server_args);
match result {
Ok((client, _server)) => {
let client_output = client.wait_with_output()?;
assert!(!client_output.status.success());
}
Err(_) => {
}
}
Ok(())
}
#[test]
fn test_ssh_sync_command_parsing() -> Result<()> {
let output = pxh_command().args(["sync", "--remote", "user@host", "--help"]).output()?;
assert!(String::from_utf8(output.stdout)?.contains("Remote host to sync with"));
Ok(())
}
#[test]
fn test_scrub_dir_mode_scan() -> Result<()> {
let temp_dir = TempDir::new()?;
let db1 = temp_dir.path().join("db1.db");
let db2 = temp_dir.path().join("db2.db");
insert_test_command(&db1, "aws configure set aws_access_key_id AKIAIOSFODNN7EXAMPLE", None)?;
insert_test_command(&db1, "echo normal command", None)?;
insert_test_command(&db2, "export AWS_SECRET_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE2", None)?;
insert_test_command(&db2, "ls -la", None)?;
let output = pxh_command()
.args([
"--db",
temp_dir.path().join("unused.db").to_str().unwrap(), "scrub",
"--scan",
"--dir",
temp_dir.path().to_str().unwrap(),
"--dry-run",
])
.output()?;
assert!(output.status.success(), "scrub --dir failed: {:?}", output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("database files"), "Should report found database files");
assert_eq!(count_commands(&db1)?, 2);
assert_eq!(count_commands(&db2)?, 2);
Ok(())
}
#[test]
fn test_scrub_dir_mode_with_contraband_pattern() -> Result<()> {
let temp_dir = TempDir::new()?;
let db1 = temp_dir.path().join("db1.db");
let db2 = temp_dir.path().join("db2.db");
insert_test_command(&db1, "echo TOPSECRET_VALUE=abc123", None)?;
insert_test_command(&db1, "echo normal command", None)?;
insert_test_command(&db2, "export MY_TOPSECRET=password", None)?;
insert_test_command(&db2, "ls -la", None)?;
assert_eq!(count_commands(&db1)?, 2);
assert_eq!(count_commands(&db2)?, 2);
let output = pxh_command()
.args([
"--db",
temp_dir.path().join("unused.db").to_str().unwrap(),
"scrub",
"--dir",
temp_dir.path().to_str().unwrap(),
"-y", "TOPSECRET", ])
.output()?;
assert!(
output.status.success(),
"scrub --dir failed: stdout={} stderr={}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(count_commands(&db1)?, 1, "db1 should have 1 command after scrub");
assert_eq!(count_commands(&db2)?, 1, "db2 should have 1 command after scrub");
let conn1 = rusqlite::Connection::open(&db1)?;
let has_normal1: bool = conn1.query_row(
"SELECT EXISTS(SELECT 1 FROM command_history WHERE full_command LIKE '%normal command%')",
[],
|r| r.get(0),
)?;
assert!(has_normal1, "Normal command should still exist in db1");
let conn2 = rusqlite::Connection::open(&db2)?;
let has_normal2: bool = conn2.query_row(
"SELECT EXISTS(SELECT 1 FROM command_history WHERE full_command LIKE '%ls -la%')",
[],
|r| r.get(0),
)?;
assert!(has_normal2, "Normal command should still exist in db2");
Ok(())
}
#[test]
fn test_sync_filters_secrets_by_default() -> Result<()> {
let temp_dir = TempDir::new()?;
let client_db = temp_dir.path().join("client.db");
let server_db = temp_dir.path().join("server.db");
insert_test_command(&client_db, "echo normal", None)?;
insert_test_command(
&server_db,
"aws configure set aws_access_key_id AKIAIOSFODNN7EXAMPLE",
None,
)?;
insert_test_command(&server_db, "echo safe_command", None)?;
let server_args = vec![
"--db".to_string(),
server_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--server".to_string(),
];
let client_args = vec![
"--db".to_string(),
client_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--stdin-stdout".to_string(),
];
let (client, server) = spawn_sync_processes(client_args, server_args)?;
let client_output = client.wait_with_output()?;
let server_output = server.wait_with_output()?;
assert!(
client_output.status.success(),
"Client failed: {}",
String::from_utf8_lossy(&client_output.stderr)
);
assert!(
server_output.status.success(),
"Server failed: {}",
String::from_utf8_lossy(&server_output.stderr)
);
let client_stderr = String::from_utf8_lossy(&client_output.stderr);
let client_count = count_commands(&client_db)?;
let filtering_occurred = client_count == 2 || client_stderr.contains("filtered");
assert!(
filtering_occurred,
"Secret filtering should have occurred. Client has {} commands, stderr: {}",
client_count, client_stderr
);
let conn = rusqlite::Connection::open(&client_db)?;
let has_secret: bool = conn.query_row(
"SELECT EXISTS(SELECT 1 FROM command_history WHERE full_command LIKE '%AKIAIOSFODNN7%')",
[],
|r| r.get(0),
)?;
assert!(!has_secret, "AWS key command should NOT be in client database after sync");
Ok(())
}
#[test]
fn test_sync_no_secret_filter_flag() -> Result<()> {
let temp_dir = TempDir::new()?;
let client_db = temp_dir.path().join("client.db");
let server_db = temp_dir.path().join("server.db");
insert_test_command(&client_db, "echo init", None)?;
insert_test_command(&server_db, "export TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", None)?;
insert_test_command(&server_db, "echo normal", None)?;
let server_args = vec![
"--db".to_string(),
server_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--server".to_string(),
];
let client_args = vec![
"--db".to_string(),
client_db.to_str().unwrap().to_string(),
"sync".to_string(),
"--stdin-stdout".to_string(),
"--no-secret-filter".to_string(),
];
let (client, server) = spawn_sync_processes(client_args, server_args)?;
let client_output = client.wait_with_output()?;
let server_output = server.wait_with_output()?;
assert!(
client_output.status.success(),
"Client failed: {}",
String::from_utf8_lossy(&client_output.stderr)
);
assert!(
server_output.status.success(),
"Server failed: {}",
String::from_utf8_lossy(&server_output.stderr)
);
let client_count = count_commands(&client_db)?;
assert_eq!(client_count, 3, "Client should have all 3 commands (no filtering)");
Ok(())
}
#[test]
fn test_remote_scrub_via_v2_protocol() -> Result<()> {
let temp_dir = TempDir::new()?;
let server_db = temp_dir.path().join("server.db");
insert_test_command(&server_db, "export AWS_KEY=AKIAIOSFODNN7EXAMPLE", None)?;
insert_test_command(&server_db, "echo normal", None)?;
let initial_count = count_commands(&server_db)?;
assert_eq!(initial_count, 2);
let mut server = pxh_command()
.args(["--db", server_db.to_str().unwrap(), "sync", "--server"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let mut stdin = server.stdin.take().unwrap();
let mut stdout = server.stdout.take().unwrap();
stdin.write_all(b"scrub-v2\n")?;
stdin.write_all(b"{\"scrub_scan\":true}\n")?;
stdin.flush()?;
drop(stdin);
let mut response = String::new();
stdout.read_to_string(&mut response)?;
let status = server.wait()?;
assert!(status.success(), "Server scrub should succeed");
assert!(
response.contains("entries")
|| response.contains("scrub")
|| response.contains("No entries"),
"Response should mention scrub result: {}",
response
);
Ok(())
}
#[test]
fn test_scrub_help_shows_new_options() -> Result<()> {
let output = pxh_command().args(["scrub", "--help"]).output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("--dir"), "Help should mention --dir option");
assert!(stdout.contains("--remote"), "Help should mention --remote option");
Ok(())
}
#[test]
fn test_sync_help_shows_no_secret_filter() -> Result<()> {
let output = pxh_command().args(["sync", "--help"]).output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("--no-secret-filter"), "Help should mention --no-secret-filter option");
Ok(())
}
#[test]
fn test_remote_scrub_with_explicit_pattern() -> Result<()> {
let temp_dir = TempDir::new()?;
let server_db = temp_dir.path().join("server.db");
insert_test_command(&server_db, "export MYSECRETKEY=value123", None)?;
insert_test_command(&server_db, "echo normal", None)?;
let initial_count = count_commands(&server_db)?;
assert_eq!(initial_count, 2);
let mut server = pxh_command()
.args(["--db", server_db.to_str().unwrap(), "sync", "--server"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let mut stdin = server.stdin.take().unwrap();
let mut stdout = server.stdout.take().unwrap();
stdin.write_all(b"scrub-v2\n")?;
stdin.write_all(b"{\"scrub\":\"MYSECRETKEY\"}\n")?;
stdin.flush()?;
drop(stdin);
let mut response = String::new();
stdout.read_to_string(&mut response)?;
let status = server.wait()?;
assert!(status.success(), "Server scrub should succeed");
assert!(
response.contains("Scrubbed 1 entries") || response.contains("1 entries"),
"Response should indicate 1 entry scrubbed: {}",
response
);
let final_count = count_commands(&server_db)?;
assert_eq!(final_count, 1, "Server should have 1 command after scrub");
Ok(())
}
#[test]
fn test_scan_dir_mode() -> Result<()> {
let temp_dir = TempDir::new()?;
let db1 = temp_dir.path().join("db1.db");
let db2 = temp_dir.path().join("db2.db");
insert_test_command(&db1, "aws configure set aws_access_key_id AKIAIOSFODNN7EXAMPLE", None)?;
insert_test_command(&db1, "echo normal command", None)?;
insert_test_command(&db2, "echo safe command", None)?;
insert_test_command(&db2, "ls -la", None)?;
let output = pxh_command()
.args([
"--db",
temp_dir.path().join("unused.db").to_str().unwrap(),
"scan",
"--dir",
temp_dir.path().to_str().unwrap(),
])
.output()?;
assert!(
output.status.success(),
"scan --dir failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("database files"), "Should report scanning database files");
assert!(
stdout.contains("AKIA") || stdout.contains("AWS") || stdout.contains("potential secret"),
"Should find AWS key pattern in output: {}",
stdout
);
Ok(())
}
#[test]
fn test_scan_help_shows_dir_option() -> Result<()> {
let output = pxh_command().args(["scan", "--help"]).output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("--dir"), "Help should mention --dir option");
Ok(())
}
#[test]
fn test_scrub_dir_requires_scan_or_pattern() -> Result<()> {
let temp_dir = TempDir::new()?;
let db = temp_dir.path().join("test.db");
insert_test_command(&db, "echo test", None)?;
let output = pxh_command()
.args([
"--db",
temp_dir.path().join("unused.db").to_str().unwrap(),
"scrub",
"--dir",
temp_dir.path().to_str().unwrap(),
])
.output()?;
assert!(!output.status.success(), "scrub --dir without --scan or pattern should fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("--scan") || stderr.contains("contraband") || stderr.contains("pattern"),
"Error should mention requiring --scan or pattern: {}",
stderr
);
Ok(())
}
#[test]
fn test_scrub_remote_requires_scan_or_pattern() -> Result<()> {
let temp_dir = TempDir::new()?;
let output = pxh_command()
.args([
"--db",
temp_dir.path().join("unused.db").to_str().unwrap(),
"scrub",
"--remote",
"fake-host",
])
.output()?;
assert!(!output.status.success(), "scrub --remote without --scan or pattern should fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("--scan") || stderr.contains("contraband") || stderr.contains("pattern"),
"Error should mention requiring --scan or pattern: {}",
stderr
);
Ok(())
}
#[test]
fn test_v2_protocol_malformed_json_fails() -> Result<()> {
let temp_dir = TempDir::new()?;
let db_path = temp_dir.path().join("server.db");
insert_test_command(&db_path, "echo hello", None)?;
let mut server = pxh_command()
.args(["--db", db_path.to_str().unwrap(), "sync", "--server"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let stdin = server.stdin.as_mut().unwrap();
writeln!(stdin, "receive-v2")?;
writeln!(stdin, "{{invalid json")?;
stdin.flush()?;
let output = server.wait_with_output()?;
assert!(!output.status.success(), "Server should fail on malformed v2 JSON");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Failed to parse v2 protocol options"),
"Error should mention parsing failure: {}",
stderr
);
Ok(())
}
#[test]
fn test_directory_sync_filters_secrets() -> Result<()> {
let temp_dir = TempDir::new()?;
let source_db = temp_dir.path().join("source.db");
insert_test_command(&source_db, "export AWS_KEY=AKIAIOSFODNN7EXAMPLE", None)?;
insert_test_command(&source_db, "echo normal command", None)?;
let target_db = temp_dir.path().join("target.db");
insert_test_command(&target_db, "echo existing", None)?;
let output = pxh_command()
.args(["--db", target_db.to_str().unwrap(), "sync", temp_dir.path().to_str().unwrap()])
.output()?;
assert!(output.status.success(), "Sync should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("filtered") || count_commands(&target_db)? == 2,
"Secret should be filtered during directory sync. Output: {}",
stdout
);
let target_count = count_commands(&target_db)?;
assert!(
target_count == 2, "Expected 2 commands in target (existing + normal), got {}",
target_count
);
Ok(())
}