#![allow(clippy::unwrap_used)]
use crate::repl::purifier_transforms::*;
#[test]
fn test_REPL_013_001_transformation_category_display() {
let idempotency = TransformationCategory::Idempotency;
let determinism = TransformationCategory::Determinism;
let safety = TransformationCategory::Safety;
assert_ne!(idempotency, determinism);
assert_ne!(determinism, safety);
assert_ne!(safety, idempotency);
}
#[test]
fn test_REPL_013_001_transformation_explanation_new() {
let explanation = TransformationExplanation::new(
TransformationCategory::Idempotency,
"mkdir -p",
"mkdir /tmp",
"mkdir -p /tmp",
"Added -p flag",
"Prevents failure if exists",
);
assert_eq!(explanation.category, TransformationCategory::Idempotency);
assert_eq!(explanation.title, "mkdir -p");
assert_eq!(explanation.original, "mkdir /tmp");
assert_eq!(explanation.transformed, "mkdir -p /tmp");
assert_eq!(explanation.what_changed, "Added -p flag");
assert_eq!(explanation.why_it_matters, "Prevents failure if exists");
assert_eq!(explanation.line_number, None);
}
#[test]
fn test_REPL_013_001_transformation_with_line_number() {
let explanation = TransformationExplanation::new(
TransformationCategory::Safety,
"Quote variables",
"echo $var",
"echo \"$var\"",
"Added quotes",
"Prevents splitting",
)
.with_line_number(42);
assert_eq!(explanation.line_number, Some(42));
}
#[test]
fn test_REPL_013_001_explain_mkdir_p_detailed() {
let original = "mkdir /tmp/test";
let result = explain_purification_changes_detailed(original);
assert!(result.is_ok());
let explanations = result.unwrap();
assert_eq!(explanations.len(), 1);
assert_eq!(
explanations[0].category,
TransformationCategory::Idempotency
);
assert_eq!(explanations[0].title, "mkdir → mkdir -p");
assert!(explanations[0].what_changed.contains("-p flag"));
}
#[test]
fn test_REPL_013_001_format_empty_report() {
let transformations: Vec<TransformationExplanation> = vec![];
let report = format_transformation_report(&transformations);
assert!(report.contains("No transformations"));
assert!(report.contains("already purified"));
}
#[cfg(test)]
mod transformation_explanation_property_tests {
use crate::repl::purifier_transforms::*;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_REPL_013_001_explanation_new_never_panics(
title in ".{0,100}",
original in ".{0,200}",
transformed in ".{0,200}",
what in ".{0,200}",
why in ".{0,300}",
) {
let _explanation = TransformationExplanation::new(
TransformationCategory::Idempotency,
title,
original,
transformed,
what,
why
);
}
#[test]
fn prop_REPL_013_001_format_report_never_panics(
count in 0usize..10,
) {
let transformations: Vec<TransformationExplanation> = (0..count)
.map(|i| {
TransformationExplanation::new(
TransformationCategory::Idempotency,
format!("Transform {}", i),
"original",
"transformed",
"what changed",
"why it matters"
)
})
.collect();
let report = format_transformation_report(&transformations);
if count == 0 {
prop_assert!(report.contains("No transformations"));
} else {
prop_assert!(report.contains("Transformation Report"));
}
}
#[test]
fn prop_REPL_013_001_explain_detailed_never_panics(
input in ".*{0,500}",
) {
let _ = explain_purification_changes_detailed(&input);
}
#[test]
fn prop_REPL_013_001_line_numbers_always_positive(
line in 1usize..1000,
) {
let explanation = TransformationExplanation::new(
TransformationCategory::Safety,
"test",
"a",
"b",
"c",
"d"
)
.with_line_number(line);
prop_assert_eq!(explanation.line_number, Some(line));
}
}
#[cfg(test)]
mod safety_rationale_tests {
use crate::repl::purifier_transforms::*;
#[test]
fn test_REPL_013_002_safety_idempotency() {
let rationale = generate_idempotency_rationale("mkdir → mkdir -p");
assert!(!rationale.failures_eliminated.is_empty());
assert!(rationale
.failures_eliminated
.iter()
.any(|f| f.contains("already exists")));
assert_eq!(rationale.severity, SafetySeverity::High);
assert!(rationale.impact_without_fix.contains("re-run"));
}
#[test]
fn test_REPL_013_002_safety_determinism() {
let rationale = generate_determinism_rationale("Remove $RANDOM");
assert!(!rationale.vulnerabilities_prevented.is_empty());
assert!(rationale
.vulnerabilities_prevented
.iter()
.any(|v| v.contains("reproducible") || v.contains("audit")));
assert_eq!(rationale.severity, SafetySeverity::Critical);
assert!(rationale.impact_without_fix.contains("unpredictable"));
}
#[test]
fn test_REPL_013_002_safety_injection() {
let rationale = generate_safety_rationale("Quote variables");
assert!(rationale
.vulnerabilities_prevented
.iter()
.any(|v| v.contains("injection")));
assert!(!rationale.attack_vectors_closed.is_empty());
assert!(rationale
.attack_vectors_closed
.iter()
.any(|a| a.contains("metacharacters") || a.contains("execution")));
assert_eq!(rationale.severity, SafetySeverity::Critical);
assert!(
rationale
.impact_without_fix
.to_lowercase()
.contains("attack")
|| rationale
.impact_without_fix
.to_lowercase()
.contains("inject")
);
}
#[test]
fn test_REPL_013_002_rationale_builder() {
let rationale = SafetyRationale::new()
.add_vulnerability("SQL injection")
.add_vulnerability("XSS attack")
.add_failure("Script crashes")
.add_attack_vector("Malicious input")
.with_impact("Data breach")
.with_severity(SafetySeverity::Critical);
assert_eq!(rationale.vulnerabilities_prevented.len(), 2);
assert_eq!(rationale.failures_eliminated.len(), 1);
assert_eq!(rationale.attack_vectors_closed.len(), 1);
assert_eq!(rationale.impact_without_fix, "Data breach");
assert_eq!(rationale.severity, SafetySeverity::Critical);
}
#[test]
fn test_REPL_013_002_explanation_with_rationale() {
let rationale = SafetyRationale::new()
.add_failure("Non-idempotent")
.with_severity(SafetySeverity::High);
let explanation = TransformationExplanation::new(
TransformationCategory::Idempotency,
"mkdir -p",
"mkdir /tmp",
"mkdir -p /tmp",
"Added -p",
"Prevents failure",
)
.with_safety_rationale(rationale.clone());
assert_eq!(explanation.safety_rationale, rationale);
assert_eq!(explanation.safety_rationale.severity, SafetySeverity::High);
}
#[test]
fn test_REPL_013_002_format_rationale() {
let rationale = SafetyRationale::new()
.add_vulnerability("Injection")
.add_failure("Crash")
.add_attack_vector("Malicious input")
.with_impact("Data loss")
.with_severity(SafetySeverity::Critical);
let formatted = format_safety_rationale(&rationale);
assert!(formatted.contains("CRITICAL"));
assert!(formatted.contains("Vulnerabilities Prevented"));
assert!(formatted.contains("Injection"));
assert!(formatted.contains("Failures Eliminated"));
assert!(formatted.contains("Crash"));
assert!(formatted.contains("Attack Vectors Closed"));
assert!(formatted.contains("Malicious input"));
assert!(formatted.contains("Impact Without Fix"));
assert!(formatted.contains("Data loss"));
}
}
#[cfg(test)]
mod safety_rationale_property_tests {
use crate::repl::purifier_transforms::*;
#[allow(unused_imports)] use proptest::prelude::*;
proptest! {
#[test]
fn prop_REPL_013_002_rationale_builder_never_panics(
vuln_count in 0usize..5,
failure_count in 0usize..5,
attack_count in 0usize..5,
) {
let mut rationale = SafetyRationale::new();
for i in 0..vuln_count {
rationale = rationale.add_vulnerability(format!("vuln_{}", i));
}
for i in 0..failure_count {
rationale = rationale.add_failure(format!("failure_{}", i));
}
for i in 0..attack_count {
rationale = rationale.add_attack_vector(format!("attack_{}", i));
}
prop_assert_eq!(rationale.vulnerabilities_prevented.len(), vuln_count);
prop_assert_eq!(rationale.failures_eliminated.len(), failure_count);
prop_assert_eq!(rationale.attack_vectors_closed.len(), attack_count);
}
#[test]
fn prop_REPL_013_002_format_never_panics(
impact in ".*{0,200}",
) {
let rationale = SafetyRationale::new()
.with_impact(impact)
.with_severity(SafetySeverity::Medium);
let _ = format_safety_rationale(&rationale);
}
#[test]
fn prop_REPL_013_002_severity_always_valid(
severity_index in 0usize..4,
) {
let severity = match severity_index {
0 => SafetySeverity::Critical,
1 => SafetySeverity::High,
2 => SafetySeverity::Medium,
_ => SafetySeverity::Low,
};
let rationale = SafetyRationale::new()
.with_severity(severity.clone());
prop_assert_eq!(rationale.severity, severity);
}
}
}
}
#[cfg(test)]
mod alternatives_tests {
use crate::repl::purifier_transforms::*;
#[test]
fn test_REPL_013_003_alternatives_mkdir() {
let transformation_title = "mkdir → mkdir -p (idempotent)";
let alternatives = generate_idempotency_alternatives(transformation_title);
assert!(!alternatives.is_empty());
assert_eq!(alternatives.len(), 2);
assert_eq!(alternatives[0].approach, "Check before creating");
assert!(alternatives[0].example.contains("[ -d"));
assert_eq!(alternatives[1].approach, "Use mkdir with error suppression");
}
#[test]
fn test_REPL_013_003_alternatives_random() {
let transformation_title = "$RANDOM → Seeded random (deterministic)";
let alternatives = generate_determinism_alternatives(transformation_title);
assert!(!alternatives.is_empty());
assert_eq!(alternatives.len(), 4);
assert_eq!(alternatives[0].approach, "Use UUID for unique IDs");
assert!(alternatives[1].approach.contains("timestamp"));
assert!(alternatives[2].approach.contains("hash"));
assert!(alternatives[3].approach.contains("counter"));
}
#[test]
fn test_REPL_013_003_alternatives_quoting() {
let transformation_title = "$var → \"$var\" (quoted)";
let alternatives = generate_safety_alternatives(transformation_title);
assert!(!alternatives.is_empty());
assert_eq!(alternatives.len(), 3);
assert!(alternatives[0].approach.contains("printf"));
assert!(alternatives[1].approach.contains("arrays"));
assert!(alternatives[2].approach.contains("Validate"));
}
#[test]
fn test_REPL_013_003_alternative_builder() {
let alternative = Alternative::new(
"Use conditional mkdir",
"[ -d /tmp/app ] || mkdir /tmp/app",
"When you need explicit control",
)
.add_pro("Explicit logic")
.add_pro("Works in POSIX sh")
.add_con("More verbose");
assert_eq!(alternative.approach, "Use conditional mkdir");
assert_eq!(alternative.example, "[ -d /tmp/app ] || mkdir /tmp/app");
assert_eq!(alternative.when_to_use, "When you need explicit control");
assert_eq!(alternative.pros.len(), 2);
assert_eq!(alternative.cons.len(), 1);
assert_eq!(alternative.pros[0], "Explicit logic");
assert_eq!(alternative.pros[1], "Works in POSIX sh");
assert_eq!(alternative.cons[0], "More verbose");
}
#[test]
fn test_REPL_013_003_format_alternatives() {
let alternatives = vec![Alternative::new(
"Use mkdir -p",
"mkdir -p /tmp/app",
"When you want simple idempotency",
)
.add_pro("Simple and concise")
.add_con("No explicit error handling")];
let formatted = format_alternatives(&alternatives);
assert!(!formatted.is_empty());
assert!(formatted.contains("Alternative Approaches:"));
assert!(formatted.contains("1. Use mkdir -p"));
assert!(formatted.contains("Example: mkdir -p /tmp/app"));
assert!(formatted.contains("Pros:"));
assert!(formatted.contains("+ Simple and concise"));
assert!(formatted.contains("Cons:"));
assert!(formatted.contains("- No explicit error handling"));
}