#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use crate::models::proxy::{
ProxyMode, ProxyOperation, ProxyRequest, ProxyStatus, QualityConfig,
};
use crate::services::quality_proxy::QualityProxyService;
use proptest::prelude::*;
const SAMPLE_GOOD_CODE: &str = "/// A well-documented function\n/// \n/// This function does something useful.\npub fn good_function(x: i32) -> i32 {\n x * 2\n}";
const SAMPLE_SATD_CODE: &str =
"fn bad_function() {\n // TODO: implement this\n unimplemented!()\n}";
const SAMPLE_COMPLEX_CODE: &str = "fn complex_function(a: i32, b: i32, c: i32) -> i32 {\n if a > 0 { if b > 0 { if c > 0 { return a + b + c; } } }\n 0\n}";
const SAMPLE_UNDOC_CODE: &str = "pub fn undocumented(x: i32) -> i32 {\n x + 1\n}";
const KNOWN_COMPLEXITY_9_CODE: &str = "fn complex(a: i32, b: i32, c: i32, d: i32) -> i32 {\n if a > 0 {\n if b > 0 {\n if c > 0 {\n if d > 0 {\n for i in 0..10 {\n if i % 2 == 0 {\n for j in 0..5 {\n if j > 2 { return a + b + c + d + i + j; }\n }\n }\n }\n }\n }\n }\n }\n 0\n}";
/// Generate arbitrary Rust code for testing
fn arb_rust_code() -> impl Strategy<Value = String> {
prop::sample::select(vec![
SAMPLE_GOOD_CODE.to_string(),
SAMPLE_SATD_CODE.to_string(),
SAMPLE_COMPLEX_CODE.to_string(),
SAMPLE_UNDOC_CODE.to_string(),
])
}
/// Generate arbitrary proxy operation
fn arb_proxy_operation() -> impl Strategy<Value = ProxyOperation> {
prop_oneof![
Just(ProxyOperation::Write),
Just(ProxyOperation::Edit),
Just(ProxyOperation::Append),
]
}
/// Generate arbitrary proxy mode
fn arb_proxy_mode() -> impl Strategy<Value = ProxyMode> {
prop_oneof![
Just(ProxyMode::Strict),
Just(ProxyMode::Advisory),
Just(ProxyMode::AutoFix),
]
}
/// Generate arbitrary quality config
fn arb_quality_config() -> impl Strategy<Value = QualityConfig> {
(
10u32..=30u32, // max_complexity
any::<bool>(), // allow_satd
any::<bool>(), // require_docs
any::<bool>(), // auto_format
)
.prop_map(|(max_complexity, allow_satd, require_docs, auto_format)| {
QualityConfig {
max_complexity,
allow_satd,
require_docs,
auto_format,
}
})
}
// Skip all property tests in CI to prevent timeout
#[cfg(test)]
proptest! {
#![proptest_config(if std::env::var("SKIP_SLOW_TESTS").is_ok() || std::env::var("CI").is_ok() {
ProptestConfig::with_cases(0)
} else {
ProptestConfig::default()
})]
/// Test that strict mode always rejects code with SATD when not allowed
#[test]
fn test_strict_mode_rejects_satd(
file_path in "[a-z]+\\.rs",
max_complexity in 10u32..=30u32,
) {
let rt = tokio::runtime::Runtime::new().unwrap();
let service = QualityProxyService::new();
let request = ProxyRequest {
operation: ProxyOperation::Write,
file_path,
content: Some("// TODO: fix this\nfn stub() {}".to_string()),
old_content: None,
new_content: None,
mode: ProxyMode::Strict,
quality_config: QualityConfig {
max_complexity,
allow_satd: false,
require_docs: false,
auto_format: false,
},
};
let response = rt.block_on(service.proxy_operation(request)).unwrap();
prop_assert!(matches!(response.status, ProxyStatus::Rejected));
prop_assert!(!response.quality_report.passed);
prop_assert!(response.quality_report.metrics.satd_count > 0);
}
/// Test that advisory mode always accepts code regardless of quality
#[test]
fn test_advisory_mode_always_accepts(
code in arb_rust_code(),
file_path in "[a-z]+\\.rs",
config in arb_quality_config(),
) {
let rt = tokio::runtime::Runtime::new().unwrap();
let service = QualityProxyService::new();
let request = ProxyRequest {
operation: ProxyOperation::Write,
file_path,
content: Some(code),
old_content: None,
new_content: None,
mode: ProxyMode::Advisory,
quality_config: config,
};
let response = rt.block_on(service.proxy_operation(request)).unwrap();
prop_assert!(matches!(response.status, ProxyStatus::Accepted));
}
/// Test that high-quality code is always accepted in strict mode
#[test]
fn test_high_quality_code_accepted(
file_path in "[a-z]+\\.rs",
fn_name in "[a-z_]+",
param_name in "[a-z_]+",
) {
let rt = tokio::runtime::Runtime::new().unwrap();
let code = format!(
r#"/// A well-documented function
///
/// This function performs a simple calculation.
pub fn {}({}: i32) -> i32 {{
{} * 2
}}"#,
fn_name, param_name, param_name
);
let service = QualityProxyService::new();
let request = ProxyRequest {
operation: ProxyOperation::Write,
file_path,
content: Some(code),
old_content: None,
new_content: None,
mode: ProxyMode::Strict,
quality_config: QualityConfig::default(),
};
let response = rt.block_on(service.proxy_operation(request)).unwrap();
prop_assert!(matches!(response.status, ProxyStatus::Accepted));
prop_assert!(response.quality_report.passed);
}
/// Test that complexity threshold is enforced
#[cfg(not(feature = "skip-slow-tests"))] // SLOW: 61s - excluded from fast test suite
#[test]
fn test_complexity_threshold_enforcement(
file_path in "[a-z]+\\.rs",
threshold in 3u32..=8u32,
) {
let rt = tokio::runtime::Runtime::new().unwrap();
let service = QualityProxyService::new();
let request = ProxyRequest {
operation: ProxyOperation::Write,
file_path,
content: Some(KNOWN_COMPLEXITY_9_CODE.to_string()),
old_content: None,
new_content: None,
mode: ProxyMode::Strict,
quality_config: QualityConfig {
max_complexity: threshold,
allow_satd: true,
require_docs: false,
auto_format: false,
},
};
let response = rt.block_on(service.proxy_operation(request)).unwrap();
// Code with complexity 9 should be rejected when threshold < 9
if threshold < 9 {
prop_assert!(matches!(response.status, ProxyStatus::Rejected));
prop_assert!(!response.quality_report.passed);
} else {
prop_assert!(matches!(response.status, ProxyStatus::Accepted));
prop_assert!(response.quality_report.passed);
}
}
/// Test that auto-fix mode attempts to fix issues
#[cfg(not(feature = "skip-slow-tests"))] // SLOW: 122s - excluded from fast test suite
#[test]
fn test_auto_fix_mode_removes_satd(
file_path in "[a-z]+\\.rs",
fn_name in "[a-z_]+",
) {
let rt = tokio::runtime::Runtime::new().unwrap();
let code_with_satd = format!(
r#"fn {}() {{
// TODO: implement this properly
println!("Working");
}}"#,
fn_name
);
let service = QualityProxyService::new();
let request = ProxyRequest {
operation: ProxyOperation::Write,
file_path,
content: Some(code_with_satd),
old_content: None,
new_content: None,
mode: ProxyMode::AutoFix,
quality_config: QualityConfig {
max_complexity: 20,
allow_satd: false,
require_docs: false,
auto_format: false,
},
};
let response = rt.block_on(service.proxy_operation(request)).unwrap();
// Auto-fix should either:
// 1. Accept if the original code already passes quality checks
// 2. Modify and fix the issues (returning Modified status)
// 3. Reject if it can't fix the issues
prop_assert!(
matches!(response.status, ProxyStatus::Accepted) ||
matches!(response.status, ProxyStatus::Modified) ||
matches!(response.status, ProxyStatus::Rejected)
);
if matches!(response.status, ProxyStatus::Modified) {
prop_assert!(response.refactoring_applied);
prop_assert!(!response.final_content.contains("TODO"));
}
}
/// Test operation content extraction
#[test]
fn test_operation_content_extraction(
content in "[a-zA-Z0-9 ]+",
old in "[a-zA-Z0-9 ]+",
new in "[a-zA-Z0-9 ]+",
file_path in "[a-z]+\\.txt",
) {
let rt = tokio::runtime::Runtime::new().unwrap();
let service = QualityProxyService::new();
// Test Write operation
let write_request = ProxyRequest {
operation: ProxyOperation::Write,
file_path: file_path.clone(),
content: Some(content.clone()),
old_content: None,
new_content: None,
mode: ProxyMode::Advisory,
quality_config: QualityConfig::default(),
};
let response = rt.block_on(service.proxy_operation(write_request)).unwrap();
prop_assert_eq!(response.final_content, content.clone());
// Test Edit operation
let full_content = format!("{} {} more text", old, content);
let edit_request = ProxyRequest {
operation: ProxyOperation::Edit,
file_path: file_path.clone(),
content: Some(full_content.clone()),
old_content: Some(old.clone()),
new_content: Some(new.clone()),
mode: ProxyMode::Advisory,
quality_config: QualityConfig::default(),
};
let response = rt.block_on(service.proxy_operation(edit_request)).unwrap();
prop_assert_eq!(response.final_content, full_content.replace(&old, &new));
// Test Append operation
let append_request = ProxyRequest {
operation: ProxyOperation::Append,
file_path,
content: Some(new.clone()),
old_content: Some(old.clone()),
new_content: None,
mode: ProxyMode::Advisory,
quality_config: QualityConfig::default(),
};
let response = rt.block_on(service.proxy_operation(append_request)).unwrap();
prop_assert_eq!(response.final_content, format!("{}\n{}", old, new));
}
/// Test that documentation requirements are enforced
#[test]
fn test_documentation_requirement(
file_path in "[a-z]+\\.rs",
fn_name in "[a-z_]+",
require_docs in any::<bool>(),
) {
let rt = tokio::runtime::Runtime::new().unwrap();
let code_without_docs = format!(
r#"pub fn {}() {{
println!("No docs");
}}"#,
fn_name
);
let service = QualityProxyService::new();
let request = ProxyRequest {
operation: ProxyOperation::Write,
file_path,
content: Some(code_without_docs),
old_content: None,
new_content: None,
mode: ProxyMode::Strict,
quality_config: QualityConfig {
max_complexity: 20,
allow_satd: true,
require_docs,
auto_format: false,
},
};
let response = rt.block_on(service.proxy_operation(request)).unwrap();
if require_docs {
// Should have documentation violations
let doc_violations = response.quality_report.violations
.iter()
.any(|v| matches!(v.violation_type, crate::models::proxy::ViolationType::Docs));
prop_assert!(doc_violations);
}
}
/// Test that quality report metrics are consistent
#[cfg(not(feature = "skip-slow-tests"))] // SLOW: 60s - excluded from fast test suite
#[test]
fn test_quality_report_consistency(
request in arb_proxy_request(),
) {
let rt = tokio::runtime::Runtime::new().unwrap();
let service = QualityProxyService::new();
let response = rt.block_on(service.proxy_operation(request)).unwrap();
// Metrics are already non-negative by type (usize/u32)
// Just check they exist
let _ = response.quality_report.metrics.satd_count;
let _ = response.quality_report.metrics.lint_violations;
let _ = response.quality_report.metrics.max_complexity;
// Check that passed status is consistent with violations
if response.quality_report.passed {
let has_errors = response.quality_report.violations
.iter()
.any(|v| matches!(v.severity, crate::models::proxy::ViolationSeverity::Error));
prop_assert!(!has_errors);
}
// Check that final content is empty for rejected status
if matches!(response.status, ProxyStatus::Rejected) {
prop_assert_eq!(response.final_content, "");
}
}
}
/// Generate arbitrary proxy request for testing
fn arb_proxy_request() -> impl Strategy<Value = ProxyRequest> {
(
arb_proxy_operation(),
"[a-z]+\\.rs",
arb_rust_code(),
arb_proxy_mode(),
arb_quality_config(),
)
.prop_map(|(operation, file_path, code, mode, quality_config)| {
let (content, old_content, new_content) = match operation {
ProxyOperation::Write => (Some(code), None, None),
ProxyOperation::Edit => {
let old = "old_code".to_string();
let new = code;
(Some(format!("{} more", old)), Some(old), Some(new))
}
ProxyOperation::Append => (Some(code.clone()), Some(code), None),
};
ProxyRequest {
operation,
file_path,
content,
old_content,
new_content,
mode,
quality_config,
}
})
}
}