use anyhow::Result;
use redactor::{RedactionService, RedactionTarget, SecureRedactionStrategy};
use std::sync::Mutex;
use tempfile::TempDir;
mod common;
use common::*;
static MUPDF_LOCK: Mutex<()> = Mutex::new(());
macro_rules! with_mupdf_lock {
($body:expr) => {{
let _guard = MUPDF_LOCK.lock().expect("MuPDF lock poisoned");
$body
}};
}
#[test]
fn test_full_page_redaction_pattern() -> Result<()> {
let temp_dir = TempDir::new()?;
let input = temp_dir.path().join("input.pdf");
let output = temp_dir.path().join("output.pdf");
TestPdfBuilder::new()
.with_title("Full Redaction Test")
.with_content("Line 1: Sensitive data")
.with_content("Line 2: More sensitive data")
.with_content("Line 3: Everything should be redacted")
.build(&input)?;
let service = RedactionService::with_secure_strategy();
let result = with_mupdf_lock!(service.redact(
&input,
&output,
&[RedactionTarget::Literal(".+".to_string())]
))?;
assert!(output.exists());
assert!(result.has_redactions());
Ok(())
}
#[test]
fn test_verizon_call_details_no_table_present() -> Result<()> {
let temp_dir = TempDir::new()?;
let input = temp_dir.path().join("input.pdf");
let output = temp_dir.path().join("output.pdf");
TestPdfBuilder::new()
.with_title("Regular Bill")
.with_content("Total charges: $100.00")
.with_content("No call details included")
.build(&input)?;
let service = RedactionService::with_secure_strategy();
let result =
with_mupdf_lock!(service.redact(&input, &output, &[RedactionTarget::VerizonCallDetails]))?;
assert!(output.exists());
assert_eq!(result.instances_redacted, 0);
Ok(())
}
#[test]
fn test_redaction_with_unicode_paths() -> Result<()> {
let temp_dir = TempDir::new()?;
let input = temp_dir.path().join("文档_test.pdf");
let output = temp_dir.path().join("输出_test.pdf");
TestPdfBuilder::new()
.with_title("Unicode Path Test")
.with_content("Test content")
.build(&input)?;
let service = RedactionService::with_secure_strategy();
let result = with_mupdf_lock!(service.redact(
&input,
&output,
&[RedactionTarget::Literal("Test".to_string())]
))?;
assert!(output.exists());
let _ = result.instances_redacted;
Ok(())
}
#[test]
fn test_strategy_with_custom_max_hits() -> Result<()> {
let temp_dir = TempDir::new()?;
let input = temp_dir.path().join("input.pdf");
let output = temp_dir.path().join("output.pdf");
TestPdfBuilder::new()
.with_title("Max Hits Test")
.with_content("Pattern Pattern Pattern")
.build(&input)?;
let strategy = SecureRedactionStrategy::new().with_max_hits(2);
let service = RedactionService::new(Box::new(strategy));
let result = with_mupdf_lock!(service.redact(
&input,
&output,
&[RedactionTarget::Literal("Pattern".to_string())]
))?;
assert!(output.exists());
assert!(result.instances_redacted <= 2);
Ok(())
}
#[test]
fn test_resolve_patterns_no_matches() -> Result<()> {
let temp_dir = TempDir::new()?;
let input = temp_dir.path().join("input.pdf");
let output = temp_dir.path().join("output.pdf");
TestPdfBuilder::new()
.with_title("No Phones")
.with_content("This document has no phone numbers")
.build(&input)?;
let service = RedactionService::with_secure_strategy();
let result =
with_mupdf_lock!(service.redact(&input, &output, &[RedactionTarget::PhoneNumbers]))?;
assert!(output.exists());
assert_eq!(result.instances_redacted, 0);
assert_eq!(result.pages_modified, 0);
Ok(())
}
#[test]
fn test_account_variants_12_digit_format() {
use redactor::domain::{PatternMatcher, VerizonAccountMatcher};
let matcher = VerizonAccountMatcher::new();
let account_12_digit = "123456789012";
let variants = matcher.generate_variants(account_12_digit);
assert!(!variants.is_empty());
assert!(variants.contains(&account_12_digit.to_string()));
assert!(variants.iter().any(|v| v.contains('-')));
}
#[test]
fn test_find_account_generic_pattern() {
use redactor::domain::VerizonAccountMatcher;
let text = "Account Number: 1234-5678-9012";
let result = VerizonAccountMatcher::find_account_number(text);
assert!(result.is_some());
}
#[test]
fn test_account_with_spaces() {
use redactor::domain::VerizonAccountMatcher;
let text = "Account: 123 456 789 00 001";
let result = VerizonAccountMatcher::find_account_number(text);
assert!(result.is_some());
}
#[test]
fn test_phone_validation_edge_cases() {
use redactor::domain::PhoneNumberMatcher;
assert!(PhoneNumberMatcher::validate("200", "234", "5678"));
assert!(PhoneNumberMatcher::validate("999", "234", "5678"));
assert!(PhoneNumberMatcher::validate("555", "200", "5678"));
assert!(PhoneNumberMatcher::validate("555", "999", "5678"));
}
#[test]
fn test_handler_verbose_output() -> Result<()> {
use redactor::RedactionResult;
let result = RedactionResult {
instances_redacted: 5,
pages_processed: 3,
pages_modified: 2,
secure: true,
};
assert_eq!(result.instances_redacted, 5);
assert_eq!(result.pages_processed, 3);
assert_eq!(result.pages_modified, 2);
assert!(result.secure);
assert!(result.has_redactions());
Ok(())
}
#[test]
fn test_call_details_normalize_no_match() {
use redactor::domain::{PatternMatcher, VerizonCallDetailsMatcher};
let matcher = VerizonCallDetailsMatcher::new();
let result = matcher.normalize("Regular text");
assert_eq!(result, None);
}
#[test]
fn test_call_details_generate_variants() {
use redactor::domain::{PatternMatcher, VerizonCallDetailsMatcher};
let matcher = VerizonCallDetailsMatcher::new();
let variants = matcher.generate_variants("3:45 PM");
assert!(!variants.is_empty());
assert_eq!(variants[0], "3:45 PM");
}