#[cfg(test)]
mod property_tests {
use crate::models::proxy::{
ProxyMode, ProxyOperation, ProxyRequest, ProxyStatus, QualityConfig,
};
use crate::services::quality_proxy::QualityProxyService;
use proptest::prelude::*;
fn arb_rust_code() -> impl Strategy<Value = String> {
prop_oneof![
Just(
r#"/// A well-documented function
///
/// This function does something useful.
pub fn good_function(x: i32) -> i32 {
x * 2
}"#
.to_string()
),
Just(
r#"fn bad_function() {
// TODO: implement this
unimplemented!()
}"#
.to_string()
),
Just(
r#"fn complex_function(a: i32, b: i32, c: i32) -> i32 {
if a > 0 {
if b > 0 {
if c > 0 {
for i in 0..10 {
if i % 2 == 0 {
for j in 0..5 {
if j > 2 {
return a + b + c + i + j;
}
}
}
}
}
}
}
0
}"#
.to_string()
),
Just(
r#"pub fn undocumented(x: i32) -> i32 {
x + 1
}"#
.to_string()
),
]
}
fn arb_proxy_operation() -> impl Strategy<Value = ProxyOperation> {
prop_oneof![
Just(ProxyOperation::Write),
Just(ProxyOperation::Edit),
Just(ProxyOperation::Append),
]
}
fn arb_proxy_mode() -> impl Strategy<Value = ProxyMode> {
prop_oneof![
Just(ProxyMode::Strict),
Just(ProxyMode::Advisory),
Just(ProxyMode::AutoFix),
]
}
fn arb_quality_config() -> impl Strategy<Value = QualityConfig> {
(
10u32..=30u32, any::<bool>(), any::<bool>(), any::<bool>(), )
.prop_map(|(max_complexity, allow_satd, require_docs, auto_format)| {
QualityConfig {
max_complexity,
allow_satd,
require_docs,
auto_format,
}
})
}
#[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]
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]
fn test_advisory_mode_always_accepts(
code in arb_rust_code(),
file_path in "[a-z]+\\.rs",
config in arb_quality_config(),
) {
if std::env::var("SKIP_SLOW_TESTS").is_ok() || std::env::var("CI").is_ok() {
prop_assume!(false); }
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]
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]
fn test_complexity_threshold_enforcement(
file_path in "[a-z]+\\.rs",
threshold in 3u32..=8u32,
) {
let rt = tokio::runtime::Runtime::new().unwrap();
let complex_code = r#"fn complex(a: i32, b: i32, c: i32, d: i32) -> i32 {
if a > 0 {
if b > 0 {
if c > 0 {
if d > 0 {
for i in 0..10 {
if i % 2 == 0 {
for j in 0..5 {
if j > 2 {
return a + b + c + d + i + j;
}
}
}
}
}
}
}
}
0
}"#;
let service = QualityProxyService::new();
let request = ProxyRequest {
operation: ProxyOperation::Write,
file_path,
content: Some(complex_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();
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]
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();
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]
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();
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());
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));
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]
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 {
let doc_violations = response.quality_report.violations
.iter()
.any(|v| matches!(v.violation_type, crate::models::proxy::ViolationType::Docs));
prop_assert!(doc_violations);
}
}
#[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();
let _ = response.quality_report.metrics.satd_count;
let _ = response.quality_report.metrics.lint_violations;
let _ = response.quality_report.metrics.max_complexity;
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);
}
if matches!(response.status, ProxyStatus::Rejected) {
prop_assert_eq!(response.final_content, "");
}
}
}
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,
}
})
}
}