use std::env;
use std::fs;
use std::path::PathBuf;
use assert_cmd::Command;
use predicates::prelude::*;
use serde_json::Value;
use tempfile::TempDir;
use log::{debug, LevelFilter};
use cleansh::logger;
use cleansh::utils::app_state::AppState;
struct TestPaths {
_temp_dir: TempDir, app_state_file_path: PathBuf,
}
fn get_test_paths(test_name: &str) -> anyhow::Result<TestPaths> {
logger::init_logger(Some(LevelFilter::Debug));
debug!("Test setup: Initializing test paths for {}", test_name);
let mut temp_base_dir = PathBuf::from(env!("CARGO_TARGET_TMPDIR"));
temp_base_dir.push("cleansh_stats_tests_data"); temp_base_dir.push(test_name);
fs::create_dir_all(&temp_base_dir)?;
let temp_dir = tempfile::tempdir_in(temp_base_dir)?;
let app_state_file_path = temp_dir.path().join("app_state.json");
let initial_state = AppState::new();
initial_state.save(&app_state_file_path)?;
debug!("Test setup: App state file created at {:?}", app_state_file_path);
Ok(TestPaths {
_temp_dir: temp_dir,
app_state_file_path,
})
}
fn run_cleansh_cmd(app_state_file: &PathBuf) -> Command {
let mut cmd = Command::new(assert_cmd::cargo_bin!("cleansh"));
cmd.env("CLEANSH_STATE_FILE_OVERRIDE_FOR_TESTS", app_state_file.to_str().unwrap());
cmd.env_remove("RUST_LOG");
cmd.env_remove("CLEANSH_ALLOW_DEBUG_PII"); debug!("Command setup: CLEANSH_STATE_FILE_OVERRIDE_FOR_TESTS set to {:?}", app_state_file);
cmd
}
fn is_json() -> impl Predicate<str> {
predicate::function(|s: &str| {
serde_json::from_str::<Value>(s).is_ok()
})
}
#[test]
fn test_stats_only_no_matches() -> anyhow::Result<()> {
let test_paths = get_test_paths("test_stats_only_no_matches")?;
debug!("Running test_stats_only_no_matches");
run_cleansh_cmd(&test_paths.app_state_file_path)
.write_stdin("This is a clean string with no PII.")
.arg("scan") .arg("--rules")
.arg("default")
.assert()
.success() .stderr(predicate::str::contains("No redaction matches found."));
Ok(())
}
#[test]
fn test_stats_only_with_simple_matches() -> anyhow::Result<()> {
let test_paths = get_test_paths("test_stats_only_with_simple_matches")?;
debug!("Running test_stats_only_with_simple_matches");
let input_content = "My email is test@example.com and my IP is 192.168.1.1.";
let output = run_cleansh_cmd(&test_paths.app_state_file_path)
.write_stdin(input_content)
.arg("scan")
.arg("--rules")
.arg("default")
.output()?;
let stderr = String::from_utf8_lossy(&output.stderr);
debug!("Stderr for simple_matches: \n{}", stderr);
assert!(output.status.success()); assert!(stderr.contains("Redaction Statistics Summary"));
assert!(stderr.contains("Email: 1 match"));
assert!(stderr.contains("Ipv4 Address: 1 match"));
Ok(())
}
#[test]
fn test_stats_with_json_file_output() -> anyhow::Result<()> {
let test_paths = get_test_paths("test_stats_with_json_file_output")?;
debug!("Running test_stats_with_json_file_output");
let input_content = "An email: test@example.com. Another: user@domain.com.";
let json_output_path = test_paths._temp_dir.path().join("stats.json");
run_cleansh_cmd(&test_paths.app_state_file_path)
.write_stdin(input_content)
.arg("scan")
.arg("--rules")
.arg("default")
.arg("--json-file")
.arg(&json_output_path)
.assert()
.success();
let json_content = fs::read_to_string(&json_output_path)?;
assert!(is_json().eval(&json_content));
let json: Value = serde_json::from_str(&json_content)?;
let email_count = json["redaction_summary"]["email"].as_u64().unwrap_or(0);
assert_eq!(email_count, 2);
Ok(())
}
#[test]
fn test_stats_with_fail_over() -> anyhow::Result<()> {
let test_paths = get_test_paths("test_stats_with_fail_over")?;
debug!("Running test_stats_with_fail_over");
let input_content = "Email is test1@example.com. Another is test2@example.com.";
run_cleansh_cmd(&test_paths.app_state_file_path)
.write_stdin(input_content)
.arg("scan")
.arg("--rules")
.arg("default")
.arg("--fail-over-threshold")
.arg("1") .assert()
.failure() .stderr(predicate::str::contains("FAIL-OVER triggered: Found 2 redaction matches, which exceeds the specified threshold of 1."));
Ok(())
}
#[test]
fn test_stats_rule_disable() -> anyhow::Result<()> {
let test_paths = get_test_paths("test_stats_rule_disable")?;
debug!("Running test_stats_rule_disable");
let input_content = "Email is test1@example.com. IPv4 is 192.168.1.1.";
let output = run_cleansh_cmd(&test_paths.app_state_file_path)
.write_stdin(input_content)
.arg("scan")
.arg("--rules")
.arg("default")
.arg("--disable")
.arg("email")
.output()?;
let stderr = String::from_utf8_lossy(&output.stderr);
debug!("Stderr for rule_disable: \n{}", stderr);
assert!(output.status.success()); assert!(!stderr.contains("Email:"));
assert!(stderr.contains("Ipv4 Address: 1 match"));
Ok(())
}