use apcore_cli::security::audit::AuditLogger;
use serde_json::json;
use tempfile::tempdir;
#[test]
fn test_audit_logger_disabled_no_file_written() {
let logger = AuditLogger::new(Some(std::path::PathBuf::from("/dev/null")));
let dir = tempdir().unwrap(); let unrelated_path = dir.path().join("should_not_exist.jsonl");
drop(logger);
assert!(
!unrelated_path.exists(),
"no file should be created in unrelated dir"
);
}
#[test]
fn test_audit_logger_writes_jsonl_record() {
let dir = tempdir().unwrap(); let log_path = dir.path().join("audit.jsonl");
let logger = AuditLogger::new(Some(log_path.clone()));
logger.log_execution("math.add", &json!({"a": 1}), "success", 0, 5);
let raw = std::fs::read_to_string(&log_path).expect("log file must exist after log_execution");
let entry: serde_json::Value =
serde_json::from_str(raw.trim()).expect("log line must be valid JSON");
assert_eq!(entry["module_id"], "math.add");
assert_eq!(entry["status"], "success");
assert_eq!(entry["exit_code"], 0);
assert_eq!(entry["duration_ms"], 5);
}
#[test]
fn test_audit_logger_appends_multiple_records() {
let dir = tempdir().unwrap(); let log_path = dir.path().join("audit.jsonl");
let logger = AuditLogger::new(Some(log_path.clone()));
for i in 0..3 {
logger.log_execution("math.add", &json!({"a": i}), "success", 0, i as u64);
}
let raw = std::fs::read_to_string(&log_path).expect("log file must exist");
let lines: Vec<&str> = raw.lines().collect();
assert_eq!(lines.len(), 3, "expected 3 log lines, got {}", lines.len());
}
#[test]
fn test_audit_logger_record_has_required_fields() {
let dir = tempdir().unwrap(); let log_path = dir.path().join("audit.jsonl");
let logger = AuditLogger::new(Some(log_path.clone()));
logger.log_execution("math.add", &json!({"a": 1}), "success", 0, 10);
let raw = std::fs::read_to_string(&log_path).expect("log file must exist");
let entry: serde_json::Value =
serde_json::from_str(raw.trim()).expect("log line must be valid JSON");
assert!(
entry["timestamp"].as_str().unwrap().ends_with('Z'),
"timestamp must be ISO 8601 UTC"
);
assert!(entry["user"].is_string(), "user field must be a string");
assert_eq!(entry["module_id"], "math.add");
assert!(
entry.get("input_salt").is_none(),
"input_salt must not be present in entry"
);
assert!(
entry["input_hash"]
.as_str()
.map(|s| s.len() == 64)
.unwrap_or(false),
"input_hash must be a 64-char hex SHA-256"
);
assert_eq!(entry["status"], "success");
assert!(entry["exit_code"].is_number(), "exit_code must be a number");
assert!(
entry["duration_ms"].is_number(),
"duration_ms must be a number"
);
}